调用方法时发生了什么?
What happens when you call a method?
调用方法时Ruby会做两件事:
(1)找到这个方法,这个过程成为方法查找。
(2)执行这个方法,为了做到这点,ruby要用到一个称为self 的东西。
1.方法查找
Method lookup
调用一个方法前,Ruby会在对象中查找那个方法。不过,在进一步学习之前,我们还要掌握两个新概念:接受者(receiver)和祖先链(ancestors chain)。接受者就是你调用方法所在的对象。例如,在my_string.reverse()语句中,my_string就是接收者。为了理解祖先链 的概念,可以先观察一个Ruby类。想像从一个类找到它的超类,然后依次往上找,直到找到basicObject(Ruby类体系结构的根节点)。在这个过程中,经历的类的路径就是该类的祖先链。(祖先链中可能会包含模块,后面再说。)
知道了接收者和祖先链,就可以用一句话来概括方法查找的过程:Ruby首先在接收者的类中查找。然后顺着祖先链向上查找,直到找到这个方法为止。
class MyClass def my_method; 'my_method'; end end class MySubclass < MyClass end
obj = MySubclass.new obj.my_method() # => "my_method()"
我们已经知道了祖先链是从类开始到其超类结束。 实际上,祖先链中也包含模块(module)。当吧一 个模块包含在一个类(或者一个模块)中时,Ruby就会把这个模块加入该类的祖先链中,该模块在祖先链 中的位置就在包含它的类之上。
module M1 def my_method 'M1#my_method()' end end class C inculde M1 end class D < C; en
D.ancestors # => [D, C, M1, Object, Kernel, BasicObject]
从Ruby 2.0 开始,还可以用另外一种方法吧模块插入一个类的祖先链中:使用prepend方法。 他的功能和include方法相似, 不过这个方法会把模块插入到祖先链中包含它的该类的下方,而不像include方法那样插入上方:
class C2 prepend M2 end class D2 < C2; end
D2.ancestors # => [D2, M2, C2, Object, Kennel, BasicObject]
关于include和prepend,还有一个重要的知识点。
多重包含
如果试图在某个类的祖先链中多次加入同一个模块,会如何呢?
module M1; end module M2 include M1 end module M3 prepend M1 inculde M2 end
M3.ancestors # => [M1, M2, M3]
可以看出虽然我们多次的添加模块但是最后并没有重复,每次include或者prepend的时候,如果该模块 已经存在于祖先链中, 那么Ruby会悄悄的忽略这个包含(include或prepend)指令。因此一个模块只会在一条祖先链中出现一次。
无处不在的Kernel模块
Ruby中有一些方法(如print)可以随时随地的进行调用,看起来就像是所有对象都有print方法一样。这是因为这些方法实际上都是kerenl模块的私有方法:
Kernel.pricate_instance_methods.grep(/^pr/) # => [:printf, :print, :proc]
这里的秘密在于Object类包含了Kernel模块,因此Kernel模块就进入了每个对象的祖先链。于是,无论哪个对象都可以随意调用Kernel模块的方法。这使得print看起来就像一个关键字,其实它只是一个方法而已。