<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>molezz (mole)</title>
    <link>https://ruby-china.org/molezz</link>
    <description></description>
    <language>en-us</language>
    <item>
      <title>Ruby 的方法查找与 method_missing</title>
      <description>&lt;h2 id="第一次发帖子，多少有些紧张 ..."&gt;第一次发帖子，多少有些紧张 ...&lt;/h2&gt;
&lt;p&gt;在 Ruby 中方法调用都是通过&lt;strong&gt;对象。方法&lt;/strong&gt;的形式。当调用一个方法时，Ruby 会在对象的类中查找那个方法，如果当前类中没有这个方法的定义，Ruby 会搜索祖先链，看看其中是否存在这个方法。再更进一步前，我们得先来了解下什么是“祖先链”。&lt;/p&gt;

&lt;p&gt;在 Ruby 的世界里，“祖先链”是一个很重要的概念。祖先链为我们展示了一个完整的类继承路径，同时它也是 Ruby 中方法查找的路径。
先来看一个简单的情况：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Dog&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Animal&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;my_ancestors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;klass&lt;/span&gt;
  &lt;span class="n"&gt;sc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;superclass&lt;/span&gt;
  &lt;span class="k"&gt;until&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="kp"&gt;nil&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;
    &lt;span class="n"&gt;sc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;superclass&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="n"&gt;my_ancestors&lt;/span&gt; &lt;span class="no"&gt;Dog&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;首先，我们定义两个类&lt;code&gt;Animal&lt;/code&gt;、&lt;code&gt;Dog&lt;/code&gt;。其中&lt;code&gt;Dog&lt;/code&gt;继承&lt;code&gt;Animal&lt;/code&gt;类。&lt;code&gt;superclass()&lt;/code&gt;方法可以访问到某个类的父类。&lt;code&gt;my_ancestors()&lt;/code&gt;方法，会帮助我们输出一个类的所有父类。我们想象中&lt;code&gt;Dog&lt;/code&gt;的祖先链应该是这样：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2014/8dcc662fcea357089dfa406641651a41.png" title="" alt="想象的图"&gt;&lt;/p&gt;

&lt;p&gt;但是执行这段代码会输出如下的内容：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Dog, Animal, Object, BasicObject]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;其中多了 &lt;code&gt;Object&lt;/code&gt;,&lt;code&gt;BasicObject&lt;/code&gt;两个类。可见一个 Ruby 类如果不声明父类，默认继承于&lt;code&gt;Object&lt;/code&gt;类。而&lt;code&gt;Object&lt;/code&gt;又继承于&lt;code&gt;BasicObject&lt;/code&gt;类（Ruby 1.9 版本引入了 BasicObject）。&lt;code&gt;BasicObject&lt;/code&gt;是这条继承链的顶端。于是现在的继承链应该是：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2014/b0f9504191eed66e5d94d4fdaa1c2bdb.png" title="" alt="实际的图"&gt;&lt;/p&gt;

&lt;p&gt;这就是一个类的祖先链了吗？别忘了还有模块没有试验。下面我们来添加一个&lt;code&gt;Action&lt;/code&gt;模块，这个模块可以为类添加一些方法：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Action&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;eat&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'eat'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Dog&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Animal&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Action&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;my_ancestors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;klass&lt;/span&gt;
  &lt;span class="n"&gt;sc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;superclass&lt;/span&gt;
  &lt;span class="k"&gt;until&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;
    &lt;span class="n"&gt;sc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;superclass&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="n"&gt;my_ancestors&lt;/span&gt; &lt;span class="no"&gt;Dog&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;再运行这个脚本，发现输出跟刚才没有什么区别：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Dog, Animal, Object, BasicObject]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在这个输出中，我们并没有发现添加进来的模块。看来使用&lt;code&gt;superclass()&lt;/code&gt;方法并不能访问到添加到类中模块。那么模块被添加到哪里了呢？还好，Ruby 提供了一个更强力的方法&lt;code&gt;ancestors()&lt;/code&gt;，可以打印一个类所有的祖先。在脚本文件中加上一行&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="no"&gt;Dog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ancestors&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后执行脚本：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Dog, Animal, Object, BasicObject]
[Dog, Action, Animal, Object, Kernel, BasicObject]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在新的输出中，我们找到了&lt;code&gt;Action&lt;/code&gt;模块，它位于&lt;code&gt;Dog&lt;/code&gt;类的上方。同时我们还发现了另一个东西 -- &lt;code&gt;kernel&lt;/code&gt;，它是 Ruby 在&lt;code&gt;Object&lt;/code&gt;类中引入的一个模块。这使得我们可以在任何对象上调用 &lt;code&gt;kernel&lt;/code&gt; 中定义的方法。&lt;/p&gt;

&lt;p&gt;如果一个类中添加了多个模块呢？再添加一个模块，看看输出：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Action&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;eat&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'eat'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Status&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sleeping?&lt;/span&gt;
    &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Dog&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Animal&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Action&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Status&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;my_ancestors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;klass&lt;/span&gt;
  &lt;span class="n"&gt;sc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;superclass&lt;/span&gt;
  &lt;span class="k"&gt;until&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;
    &lt;span class="n"&gt;sc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;superclass&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="n"&gt;my_ancestors&lt;/span&gt; &lt;span class="no"&gt;Dog&lt;/span&gt;
&lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="no"&gt;Dog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ancestors&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Dog, Animal, Object, BasicObject]
[Dog, Status, Action, Animal, Object, Kernel, BasicObject]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可见新的模块被添加到&lt;code&gt;Dog&lt;/code&gt;上方，最接近&lt;code&gt;Dog&lt;/code&gt;的地方。现在祖先链完整了。它包括了从当前类到&lt;code&gt;BasicObject&lt;/code&gt;类的完整路径。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2014/a94710ab20fb6de6b6f1afc989b604e2.png" title="" alt="祖先链"&gt;&lt;/p&gt;

&lt;p&gt;好了，明白了什么是祖先链，现在让我们回到主题，看看当在一个对象上调用方法时，Ruby 如何查找这个方法。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. 先看一个简单的情况，在一个包含模块且有继承关系的类如何查找方法。&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Action&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;eat&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'eat in module Action'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="nn"&gt;Status&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sleeping?&lt;/span&gt;
    &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Animal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Dog&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Animal&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Action&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Status&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;my_ancestors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;klass&lt;/span&gt;
  &lt;span class="n"&gt;sc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;klass&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;superclass&lt;/span&gt;
  &lt;span class="k"&gt;until&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;
    &lt;span class="n"&gt;sc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;superclass&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
  &lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="c1"&gt;#p my_ancestors Dog&lt;/span&gt;
&lt;span class="no"&gt;Dog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eat&lt;/span&gt;
&lt;span class="nb"&gt;p&lt;/span&gt; &lt;span class="no"&gt;Dog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ancestors&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Dog&lt;/code&gt;类本身没有&lt;code&gt;eat()&lt;/code&gt;方法，我们在&lt;code&gt;Dog&lt;/code&gt;的对象上调用&lt;code&gt;eat()&lt;/code&gt;方法后，Ruby 会先查找&lt;code&gt;Dog&lt;/code&gt;类中的实例方法，然后沿着祖先链一直向上找，直到找到&lt;code&gt;Action&lt;/code&gt;模块中的方法后进行了调用。&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2014/162dc1875e15da4c93292a0c1e87148e.png" title="" alt="find eat"&gt;&lt;/p&gt;

&lt;p&gt;这种查找能到什么程度呢？如祖先链表示的，接下来就是 Object 类，这是 Ruby 中一切对象的开始。其中有一个模块&lt;code&gt;Kernel&lt;/code&gt;。也就是说，如果你在 Kernel 中定义了一个方法，那么 Ruby 中的所有对象都可以用这个方法。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. 如果类和混入的模块中有相同的方法，会发生什么呢？&lt;/strong&gt;&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Dog&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Animal&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Action&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Status&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;eat&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'eat from dog'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们在&lt;code&gt;Dog&lt;/code&gt;类中添加了新的&lt;code&gt;eat()&lt;/code&gt;方法。运行后输出：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eat from dog
[Dog, Status, Action, Animal, Object, Kernel, BasicObject]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;可以看出来，Ruby 调用了&lt;code&gt;Dog&lt;/code&gt;类中的&lt;code&gt;eat()&lt;/code&gt;方法。也就是说，如果存在同名方法，在祖先链中靠前的方法会覆盖靠后的方法。这个规则同样适用于混入的多个模块中存在同名方法的情况。&lt;/p&gt;

&lt;p&gt;这是不是 Ruby 方法查找路径的全部呢？我们来看个简单的例子：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;bar&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'bar'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bar&lt;/span&gt;
&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bar&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在 irb 里运行：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;2.1.2 :001 &amp;gt; class Foo;end
 =&amp;gt; nil
2.1.2 :002 &amp;gt; a = Foo.new
 =&amp;gt; #&amp;lt;Foo:0x007fc019952e38&amp;gt;
2.1.2 :003 &amp;gt; b = Foo.new
 =&amp;gt; #&amp;lt;Foo:0x007fc01994ae90&amp;gt;
2.1.2 :004 &amp;gt; class &amp;lt;&amp;lt; b
2.1.2 :005?&amp;gt;   def bar; puts 'bar'; end
2.1.2 :006?&amp;gt;   end
 =&amp;gt; :bar
2.1.2 :007 &amp;gt; b.bar
bar
 =&amp;gt; nil
2.1.2 :008 &amp;gt; a.bar
NoMethodError: undefined method `bar' for #&amp;lt;Foo:0x007fc019952e38&amp;gt;
  from (irb):8
  from /Users/molezz/.rvm/rubies/ruby-2.1.2/bin/irb:11:in `&amp;lt;main&amp;gt;'
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;很奇怪，&lt;code&gt;a&lt;/code&gt;和&lt;code&gt;b&lt;/code&gt;都是&lt;code&gt;Foo&lt;/code&gt;类的对象，为什么&lt;code&gt;bar()&lt;/code&gt;方法在&lt;code&gt;b&lt;/code&gt;对象中存在，而&lt;code&gt;a&lt;/code&gt;中却没有呢？看来 Ruby 还有隐藏关卡。&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;bar&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'bar'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这个写法，打开了&lt;code&gt;b&lt;/code&gt;对象的单件类。单件类（singleton class/eigenclass）又称，元类(meta class)，影子类...。在 Ruby 2.0 之前，官方一直没有一个明确的名字，2.0 以后官方称之为“Singleton class”。在单件类中定义的方法只在当前对象中存在。这就是为什么&lt;code&gt;a&lt;/code&gt;对象没有响应&lt;code&gt;bar()&lt;/code&gt;方法的原因。Ruby 在进行方法查找的时候会优先查找对象单件类中定义的方法。所以，现在的方法查找路径变成了：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2014/35a5823ef2fe05c99c9684e9c7d704c4.png" title="" alt="find singleton eat"&gt;&lt;/p&gt;

&lt;p&gt;有一点很有意思：在 Ruby 的世界里，一切皆是对象。我们定义的类也是对象（它们是&lt;code&gt;Class&lt;/code&gt;类的对象）。所以每个类也有自己的单件类。我们常见的类方法实际上就定义在这个类的单件类中。模块也有自己的单件类，但模块的单件类并不在祖先链中。单件类不能被继承，它只属于当前的对象。但是单件类可以有父类或子类。&lt;strong&gt;一个类的单件类的父类或子类是这个类的父类或子类的单件类&lt;/strong&gt;。这读起来有点绕口，画个图就明白了：&lt;/p&gt;

&lt;p&gt;&lt;img src="https://l.ruby-china.com/photo/2014/a6f5eee0c4efd89912b7c43d11b12848.png" title="" alt="find singleton class"&gt;&lt;/p&gt;

&lt;p&gt;图中虚线框的类是单件类。虚线箭头是实例方法的查找路径。实线箭头是类方法的查找路径。这就是较为完整的 Ruby 方法查找路径了。&lt;/p&gt;

&lt;p&gt;当方法被调用时，Ruby 会沿着这样的路径去查找，一直到顶部。如果方法没有找到，Ruby 会调用一个叫做&lt;code&gt;method_missing()&lt;/code&gt;的方法。默认情况下，这个方法会抛出一个&lt;code&gt;NoMethodError&lt;/code&gt;的异常。我们可以在自己的类中覆盖这个方法，从而实现各种惊奇的功能。Rails 中使用&lt;code&gt;method_missing()&lt;/code&gt;的代码随处可见：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;#rails / activesupport / lib / active_support / option_merger.rb&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;method_missing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_a?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;Proc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;proc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;
    &lt;span class="n"&gt;arguments&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="nb"&gt;lambda&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="vi"&gt;@options.deep_merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;proc&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="n"&gt;arguments&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond_to?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:to_hash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="vi"&gt;@options.deep_merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="vi"&gt;@options.dup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="vi"&gt;@context.__send__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们在&lt;code&gt;Dog&lt;/code&gt;类中也添加一个自己的方法：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Dog&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Animal&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Action&lt;/span&gt;
  &lt;span class="kp"&gt;include&lt;/span&gt; &lt;span class="no"&gt;Status&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;eat&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s1"&gt;'eat from dog'&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="kp"&gt;private&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;method_missing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;arguments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="s2"&gt;"call method: &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;然后调用下不存在的&lt;code&gt;wang()&lt;/code&gt;方法&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="no"&gt;Dog&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wang&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eat from dog
[Dog, Status, Action, Animal, Object, Kernel, BasicObject]
call method: wang
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;奇迹出现了！Ruby 没有报&lt;code&gt;NoMethodError&lt;/code&gt;而是打印出了我们调用的方法名字。当你沉浸在&lt;code&gt;method_missing()&lt;/code&gt;带来的喜悦中的时候，你可能已经掉进了一个坑中。来看下面一段测试：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'benchmark'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rubygems'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'sqlite3'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'active_record'&lt;/span&gt;


&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;establish_connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;:adapter&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'sqlite3'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:database&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;':memory:'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class_eval&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:as&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;

    &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;column&lt;/span&gt; &lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;
    &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;column&lt;/span&gt; &lt;span class="ss"&gt;:performer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;

  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:bs&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;

    &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;column&lt;/span&gt; &lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;
    &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;column&lt;/span&gt; &lt;span class="ss"&gt;:performer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;

  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;A&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test&lt;/span&gt;
    &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;B&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;method_missing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;A&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;B&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;


&lt;span class="no"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bmbm&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'call method:'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="mi"&gt;10000000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'call method_missing:'&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt; &lt;span class="mi"&gt;10000000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;我们使用 Ruby 提供的&lt;code&gt;Benchmark&lt;/code&gt;进行基准测试。为了尽可能减小误差，代码中使用了&lt;code&gt;bmbm()&lt;/code&gt;方法，给要测试的代码进行“预热”。输出的结果如下：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Ruby 2.1 / Activerecord 3.2.16

Rehearsal --------------------------------------------------------
call method:           1.040000   0.000000   1.040000 (  1.047809)
call method_missing:   1.300000   0.010000   1.310000 (  1.295754)
----------------------------------------------- total: 2.350000sec

                           user     system      total        real
call method:           1.050000   0.000000   1.050000 (  1.047334)
call method_missing:   1.280000   0.000000   1.280000 (  1.286637)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;method_missing()&lt;/code&gt;固然很强大，但是它是有一定性能损失的 (从测试上看，大概 20% 左右)。这个性能损失一部分就是来自于祖先链的查找。祖先链越长，性能损失越大。虽然相比性能损失，&lt;code&gt;method_missing()&lt;/code&gt;带来的便捷性和灵活性是我们更看重的，但是在一些高负载的环节，性能问题还是不得不留意。&lt;/p&gt;

&lt;p&gt;##讨论&lt;/p&gt;

&lt;p&gt;我们在 Rails 的源代码里经常见到这样的写法：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="c1"&gt;#rails / actionpack / lib / action_dispatch / routing / routes_proxy.rb line 25&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;method_missing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;routes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;url_helpers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;respond_to?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class_eval&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;-&lt;/span&gt;&lt;span class="no"&gt;RUBY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;__FILE__&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;__LINE__&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;
      def &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;(*args)
        options = args.extract_options!
        args &amp;lt;&amp;lt; url_options.merge((options || {}).symbolize_keys)
        routes.url_helpers.&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;(*args)
      end
&lt;/span&gt;&lt;span class="no"&gt;    RUBY&lt;/span&gt;
    &lt;span class="nb"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;else&lt;/span&gt;
    &lt;span class="k"&gt;super&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在一些文章里也有很多人讨论使用类似的方法提高&lt;code&gt;method_missing()&lt;/code&gt;的性能。即在&lt;code&gt;method_missing()&lt;/code&gt;中动态创建方法，这样可以省掉后面调用时对祖先链进行查找消耗的时间。我们也把试验代码改动下：&lt;/p&gt;
&lt;pre class="highlight ruby"&gt;&lt;code&gt;&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'benchmark'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'rubygems'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'sqlite3'&lt;/span&gt;
&lt;span class="nb"&gt;require&lt;/span&gt; &lt;span class="s1"&gt;'active_record'&lt;/span&gt;


&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;establish_connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="ss"&gt;:adapter&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;'sqlite3'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="ss"&gt;:database&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s1"&gt;':memory:'&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Migration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class_eval&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
  &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:as&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;

    &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;column&lt;/span&gt; &lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;
    &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;column&lt;/span&gt; &lt;span class="ss"&gt;:performer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;

  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:bs&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;

    &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;column&lt;/span&gt; &lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;
    &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;column&lt;/span&gt; &lt;span class="ss"&gt;:performer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;

  &lt;span class="k"&gt;end&lt;/span&gt;

  &lt;span class="n"&gt;create_table&lt;/span&gt; &lt;span class="ss"&gt;:cs&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;

    &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;column&lt;/span&gt; &lt;span class="ss"&gt;:title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;
    &lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;column&lt;/span&gt; &lt;span class="ss"&gt;:performer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;:string&lt;/span&gt;

  &lt;span class="k"&gt;end&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;A&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test&lt;/span&gt;
    &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;B&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;method_missing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="kp"&gt;true&lt;/span&gt;
  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;C&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;ActiveRecord&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="no"&gt;Base&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;method_missing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nb"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;class&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:define_method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
      &lt;span class="kp"&gt;true&lt;/span&gt;
    &lt;span class="k"&gt;end&lt;/span&gt;

    &lt;span class="c1"&gt;# self.class.class_eval &amp;lt;&amp;lt;-RUBY&lt;/span&gt;
    &lt;span class="c1"&gt;#   def #{method}(*args)&lt;/span&gt;
    &lt;span class="c1"&gt;#     true&lt;/span&gt;
    &lt;span class="c1"&gt;#   end&lt;/span&gt;
    &lt;span class="c1"&gt;# RUBY&lt;/span&gt;

    &lt;span class="nb"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;A&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;B&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;C&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;



&lt;span class="no"&gt;Benchmark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;bmbm&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
  &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'call method:'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'call method_missing:'&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt; &lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;report&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'call dynamic method:'&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt; &lt;span class="mi"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;times&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;test&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这次加入&lt;code&gt;C&lt;/code&gt;类，并且在&lt;code&gt;method_missing()&lt;/code&gt;中动态定义了方法，然后执行测试：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Ruby 2.1 / Activerecord 3.2.16

Rehearsal --------------------------------------------------------
call method:           0.110000   0.000000   0.110000 (  0.110478)
call method_missing:   0.140000   0.000000   0.140000 (  0.137801)
call dynamic method:   0.160000   0.000000   0.160000 (  0.158153)
----------------------------------------------- total: 0.410000sec

                           user     system      total        real
call method:           0.110000   0.000000   0.110000 (  0.105553)
call method_missing:   0.130000   0.000000   0.130000 (  0.134528)
call dynamic method:   0.150000   0.000000   0.150000 (  0.155159)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;很奇怪动态创建方法不仅没有提升性能，反而略有降低。我们把同样的代码在 Ruby 1.9.3 环境下测试一遍：&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#Ruby 1.9.3 p547 / Activerecord 3.2.16

Rehearsal --------------------------------------------------------
call method:           0.290000   0.000000   0.290000 (  0.294679)
call method_missing:   0.450000   0.000000   0.450000 (  0.449482)
call dynamic method:   0.410000   0.000000   0.410000 (  0.410275)
----------------------------------------------- total: 1.150000sec

                           user     system      total        real
call method:           0.300000   0.000000   0.300000 (  0.297368)
call method_missing:   0.460000   0.000000   0.460000 (  0.454473)
call dynamic method:   0.410000   0.000000   0.410000 (  0.409693)

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这次跟我们的猜测一致了。在&lt;code&gt;method_missing()&lt;/code&gt;中动态定义方法确实能提高一些性能。看来 Ruby 2.1 版对&lt;code&gt;method_missing()&lt;/code&gt;进行了优化。“今后是否还要在&lt;code&gt;method_missing()&lt;/code&gt;中使用动态方法定义来提高性能？”成为一个值得考虑的问题。&lt;/p&gt;</description>
      <author>molezz</author>
      <pubDate>Sat, 30 Aug 2014 13:49:08 +0800</pubDate>
      <link>https://ruby-china.org/topics/21304</link>
      <guid>https://ruby-china.org/topics/21304</guid>
    </item>
  </channel>
</rss>
