这里有一篇 Yuki Sonoda (园田裕贵)的博文:http://yugui.jp/articles/846
其中介绍了三种隐式的上下文:self,klass,常量定义点(这个是我从英语直接翻译过来的)。下面的概念是直接从该博文中摘抄下来的,如果你能很自在地读英语,那推荐看原文,其中包含了例子代码。
所谓 “klass”,是指定义方法(不包括单例方法的定义)的默认(隐式)类,Yuki 称之为 “default definee”(默认被定义者)。比如,在任意处写下一个方法的定义(def foo; end),实际上是定义了某个类的实例方法,这个类就是这里说的 “klass”。Ruby 总是会保存这个类的一个引用,但官方的实现中并没有直接的方法获取这个类。
在 Ruby 顶层,定义一个方法,其 default definee 是 Object 类型。这个可以参考:https://szsu.wordpress.com/2009/11/07/top_level_object_kernel/
当你使用 class 的语法时,在 class … end 内部会产生一个新的上下文,这时 self 和 default definee 都会指向这个类本身。所以在内部定义的方法就是该类的实例方法,这个大家都知道。
如果你已经在一个方法主体中,那么 self 是这个方法的接收者,而 default definee 则是语法上的方法定义外部的最近的那个类。
class Klass
def foo
def bar
end
end
end
Klass.new.foo
p Klass.instance_method :bar # => #<UnboundMethod: Klass#bar>
这里 foo 也是 Klass 的实例方法。
当然,方法的定义还可以出现在其它地方,但无论在哪儿,都会有一个 default definee。比如 Yuki 给出的这个例子就是在处理方法参数默认值的时候定义的方法,其 default definee 仍然是语法上的外部的 Bar 类:
class Bar; end
$bar = Bar.new
class Baz
def $bar.hoge(a = (def fuga; end))
def piyo; end
end
end
$bar.hoge
Baz.instance_method(:fuga) # => #<UnboundMethod: Baz#fuga>
Baz.instance_method(:piyo) # => #<UnboundMethod: Baz#piyo>
$bar.method(:fuga) # raises a NameError "undefined method `fuga' for class `Bar'"
$bar.method(:piyo) # raises a NameError "undefined method `piyo' for class `Bar'"
一句话:class 的定义改变了 default definee,而方法定义不会改变。
instance_eval 和 class_eval 的区别,也是在于对 default definee 的处理上。由于 class_eval 是 Module 类的实例方法,所以下面进行测试使用的接收者是一个兼容 Module 类型的实例(其运行时类型是 Class 类型),否则 class_eval 这个方法压根儿没法用。另:module_eval 只是 class_eval 的一个别名,但如果按照 Ruby 编程的习俗,我们通常会对一个可能是 Module 类型的对象则使用 module_eval,而其余场合则使用 class_eval。
其实你需要记住的就一句话:在 instance_eval 的块内部,self 会指向 instance_eval 的接收者,而 default definee 会成为接收者的 eigenclass(关于 eigenclass 可以参考 73 楼);在 class_eval 的块内部,self 和 default definee 都会成为 class_eval 的接收者。
class Klass
end
Klass.instance_eval do
def class_method
p :class_method
end
def self.another_class_method
p :another_class_method
end
end
Klass.class_eval do
def instance_method
p :instance_method
end
def self.yet_another_class_method
p :yet_another_class_method
end
end
k = Klass.new
k.instance_method # => :instance_method
Klass.class_method # => :class_method
Klass.another_class_method # => :another_class_method
Klass.yet_another_class_method # => :yet_another_class_method
我们一步一步地来看。
首先,我们在 instance_eval 的块内部定义了一个普通方法 class_method,这是实例方法的定义方式。什么?你问我为什么把它命名为“类方法”(class_method)?别忘了之前的那句加粗的话。在 instance_eval 的块内部,default definee 会成为 instance_eval 的接收者的 eigenclass。此处,instance_eval 的接收者是 Klass,所以我们定义的 class_method 方法实际上就成为了 Klass 的 eigenclass 的实例方法,这用以前的术语来说,就是定义了 Klass 这个对象(Klass 是 Class 类型)的单例方法(singleton method)。而我们知道,Class 对象的单例方法,实际上就是传统意义上的“类方法”,也就类似于 Java、C# 中的类静态方法。所以此处的 class_method 就成为了 Klass 类的类方法,在最后的输出语句中,可以直接通过 Klass 这个 Class 对象来调用 class_method 方法,而不需要 Klass 的实例。
之后我们又定义了一个 another_class_method,这次是用定义单例方法的方式定义的,因为在方法名前多了一个接收者 self。之前说了 instance_eval 的块内部的 self 会指向 instance_eval 的接收者,也就是这里的 Klass,所以 self 实际上就是 Klass 这个 Class 对象。我们对 Klass 这个对象定义单例方法,那就相当于定义了 Klass 的 eigenclass 的实例方法,所以和上面定义 class_method 的上下文没有区别,都是 Klass 的 eigenclass,故而他们都是 Klass 的类方法。
接着我们进入了 class_eval,然后定义了一个 instance_method。再次回顾之前被加粗的那句话,我们就知道,class_eval 的块的内部的 default definee 会成为 class_eval 的接收者,在这里就是 Klass 本身,所以我们定义的 instance_method 就成为了 Klass 的实例方法。在最后的输出语句中,可以看到通过 Klass 的实例 k 调用 instance_method。
最后我们又通过单例方法定义的形式定义了一个 yet_another_class_method 方法。class_eval 的块内部的 self 仍然是指向 class_eval 的接收者,也就还是 Klass 本身,所以 self 仍然是指向 Klass。如此一来,这个方法定义又成了给 Klass 这个对象定义单例方法,所以和 class_method、another_class_method 一样都是 Klass 的类方法,故而命名为 yet_another_class_method。
所以,千万不要被 instance_eval 和 class_eval 的名字迷惑。在 Module#instance_eval 中定义的普通方法,反而会成为该 Module 的类方法;在 Module#class_eval 中定义的普通方法,反而会成为该 Module 的实例方法。