Ruby的方法和常量查找

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使用了同样的方式实现,模块的extend,只不过被修改的继承链换成了,目标类的元类(metaclass)上了

方法查找

方法调用,其实就是给被调用的方法的类发消息,那么具体想哪一个类发消息就是关键的问题了,方法查找算法就是,确定方法的定义是存在于哪一个类的上下文当中,在Ruby中因为整个的类结构是属性的,包括继承链中的模块也是按照继承的方式被添加到其中的,所以方法查找算法子Ruby是非常巧妙的简单。

ruby_method_lookup.png

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

  • 全局方法缓存,是用于保存接收者和实现类之间的映射表,Ruby在第一次方法查找之后就会将查找链路添加的映射表中,当第二次查找该方法的时候就可以直接使用映射表中的目标类了。
  • 内联方法缓存,是将方法执行的YARV指令直接进行缓存的工具,这样在方法的第二次查找是可以直接的执行YARV指令,进一步提升速度。

有缓存就有缓存的失效机制,两个方法查找缓存,都是在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在创建模块的副本的时候并没有一并负责模块的方法表,而是让副本和模块的指针共同引用同一个方法表,所以当方法的定义被修改后,引入模块的类还是会调用新的方法。

常量查找

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

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

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

词法作用域

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

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

  • nd_next 指针,被设置为父层或上下文的词法作用域。
  • nd_class 指针,表示Ruby类或模块对应的作用域。

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

ruby_constants_lookup.png

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

ruby_cons_lp.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,408评论 4 371
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,690评论 2 307
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,036评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,726评论 0 221
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,123评论 3 296
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,037评论 1 225
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,178评论 2 318
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,964评论 0 213
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,703评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,863评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,333评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,658评论 3 263
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,374评论 3 244
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,195评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,988评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,167评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,970评论 2 279

推荐阅读更多精彩内容

  • 一、 让自己熟悉Ruby 1、理解 Ruby 中的 True 在 Ruby 中,除了 false 和 nil, 其...
    Sgemini阅读 648评论 0 1
  • 一、异同对比选择1、Python和ruby的相同点: * 都强调语法简单,都具有更一般的表达方式。python是缩...
    沃伦盖茨阅读 4,105评论 2 24
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,300评论 18 399
  • 最近复习了下 ruby 对象模型的知识,参照了 Ruby Metaprogramming,于是边看边做笔记,还是收...
    张羽辰阅读 497评论 0 4
  • 朱砂一抹,心安处,恋生 小时候听人讲胸有朱砂痣,是前世舍不得忘掉一些人,死去后请孟婆封存的一丝记忆溶于心口的一滴血...
    婳期莫染阅读 366评论 0 1