美文网首页ruby on rails
Ruby的方法和常量查找

Ruby的方法和常量查找

作者: falm | 来源:发表于2017-04-20 19:22 被阅读395次

    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在第一次方法查找之后就会将查找链路添加的映射表中,当第二次查找该方法的时候就可以直接使用映射表中的目标类了。
    • 内联方法缓存,是将方法执行的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类和模块的引用对象,也就是类和模块的名字都是常量,那么常量查找的其实就是查找类和模块。

    常量本身是存放在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

    相关文章

      网友评论

        本文标题:Ruby的方法和常量查找

        本文链接:https://www.haomeiwen.com/subject/funaattx.html