ruby on rails

Ruby的方法和常量查找

2017-04-20  本文已影响395人  falm

Ruby是一门单一继承的面向对象语言,那么在内部结构上,它是以object为根节点的树形结构的类图,那么我们在Ruby中定义的方法和常量也,依附在这个结构之上的,那么方法和常量是如何被定为查找的呢,我们要从模块说起。

模块

类在Ruby的内部结构中,是使用RClass结构体来表示的,但是模块不是使用类似,RModule这种结构实现的,而是同样的使用了RClass,这样的结构看起来非常的简洁,在内部保持了一致性,为方法和常量查找算法提供了简便的基础。

模块在内部同样使用了 RClassrb_classext_struct 两个结构体,所以说模块在某种程度上说也是类的一种,我们看到上图的结构体中,较以往的RClass结构略有不同,因为模块不需要实例化,所以也就去掉了一下与实例相关的结构,可以说Ruby的模块就是包含方法定义,常磊指针和常量表的Ruby对象。

那么模块在内部是怎么被添加到类中的呢。

module Professor
end
class Mathematican < Person
  include Professor
end

Ruby是在模块Professor的RClass的基础上做了一个副本,然后将这个副本作为Mathematican类的超类,这种形式将Professor模块添加到了Mathematican类的继承链上。

ruby_method_lookup.png

从上图我们就可以看出来,方法查找的整个过程是一目了然的简单,但是如果每次的方法调用都要经过,整个树形结构的遍历的话,效率不是很好,所以Ruby在方法查找的功能中添加了全局方法缓存和内联方法缓存这两个缓存,来保证方法查找的效率。

有缓存就有缓存的失效机制,两个方法查找缓存,都是在Ruby创建和清除方法或者是include模块的时候进行缓存清除的。

Prepend

类中引入两个模块的时候,Ruby的方法查找是按照模块引入的顺序进行查找的,后引入的模块会在,继承链的倒数第二的位置上。那么Ruby模块的prepend方法的查找又是如果进行的呢,下面的代码中 模块和类都定义了name 方法,那么最后方法调用的时候,调用的会是Mathematician类属性构造器定义的name方法。

module Professor
   def name
        "Prof. #{super}"
   end
end
class Mathematician
   attr_accessor :name
   include Professor
end
m = Mathematician.new
m.name = 'Henri'
p m.name #=> Henri

如果我们想要让Professor模块中的方法重载类中的同名方法,就需要使用prepend修改一个例子了。

module Professor
  def name
    "Prof. #{super}"
  end
end
class Mathematician
  attr_accessor :name
  prepend Professor
end
m = Mathematician.new
m.name = 'Henri'
p m.name  #=> Prof. Henri

那么prepend是如何做到重载类中的方法的呢,其实秘密就是Ruby内部使用了一个小技巧,在使用prepend时,Ruby会在内部的创建目标类的副本(在内部叫原生类 origin class) 并且把它设置成前置模块的超类,Ruby使用了rb_classext_struct结构体中的origin指针来记录该类的原生副本,这样在方法查找的时候,就会先找到prepend模块的方法。

常量查找

在Ruby中常量不仅仅用于表示不可变值,它还是Ruby类和模块的引用对象,也就是类和模块的名字都是常量,那么常量查找的其实就是查找类和模块。

常量本身是存放在RClass 结构体的constants常量表中的,普通的常量查找是通过和方法查找同样的方式进行的,首先是先在本类的常量表中查找常量,如果没有找到的话在到父类的常量表中查找。

class MyClass
  SOME_CONSTANT = "Some value..."
end
class Subclass < MyClass 
  p SOME_CONSTANT
end

词法作用域

上面说到了,Ruby是如何在本类和祖先类中查找常量的,但是在模块实际的使用当中,模块的命名空间常量查找又是如何进行的呢,这里就要提到Ruby在父级空间查找常量的词法作用域问题了。

Ruby的词法作用域,有作用域门控制,也就是 module 或者 class 这样的关键字定义的作用域,还有就是诚信的默认『顶级作用域』在不同的作用域中为了定位程序代码的位置,需要使用一对指针来对应作用域内的YARV指令片段。

有了上面的作用域结构,常量查找的算法也就变得简单了。

ruby_constants_lookup.png

我们上面说到了,Ruby常量查找的两种方式,但是在真实的常量查找中是先使用哪种方式呢,简单的说Ruby或先使用词法作用域查找常量,如果没有找到的话再使用超类链查找常量,注意这里的词法作用域查找在真实的使用场景下,不仅仅是上图所示,它还会在父词法作用域中查找autoload关键字。

ruby_cons_lp.png
上一篇 下一篇

猜你喜欢

热点阅读