原文链接:Understanding class_eval and instance_eval 比起别的方法,这个两个方法更加让人困惑,尤其是他们的解释。它们是 class_eval 和 instance_eval。名字很相似,但行为有悖常理。
a = MyClass.new(1) b = MyClass.new(2)
继续学习下一步之前,要记住Ruby中任何东西都是对象。这意味着类也是一个对象。当你定义MyClass,Ruby会创建名字叫 MyClass 的全局变量, 它是一个类对象。当你使用MyClass.new时,它会给你MyClass的一个实例。你平常使用的实际对象都是实例。一个类拥有多个实例。因此我们拥有两个相同类名的实例对象。当然,目前为止它还不是很有用,因为它没做任何的工作。我们无法访问 @num 变量,因为我们还没定义 getter 和 setter方法。
```ruby
irb> a.num
NoMethodError: undefined method `num' for #<MyClass:0x007fba5c02c858 @num="1">
让我们更加清楚地了解 instance_eval。使用它我们能做什么?我们可以像正在访问某个对象的内部方法一样运行代码。意思是我们可以访问实例变量及 private 方法。为了取出@num的值,让我们评估一下 MyClass 实例。
irb> a.instance_eval { @num }
=> 1
irb> b.instance_eval { @num }
=> 2
这个执行的很好,但当做很多事的时候有些不方便,让我们定义一个方法,简化操作。
irb> a.instance_eval do
irb> def num
irb> @num
irb> end
irb> end
=> nil
irb> a.num
=> 1
irb> b.num
NoMethodError: undefined method `num' for #<MyClass:0x007fba5c08e5f8 @num="2">
我们使用了 instance_eval,它只能在某个对象的内部进行评估。我们定义了一个方法,但是它只对应于特定的对象 a。我们如何定义一个可以被类的所有对象共享的方法呢?获取我们需要在类对象中定义一个方法。
irb> MyClass.instance_eval do
irb> def num
irb> @num
irb> end
irb> end
=> nil
irb> b.num
NoMethodError: undefined method `num' for #<MyClass:0x007fba5c08e5f8 @num="2">
很不幸运,它也不好用。到底发生了什么?我们在类中做了跟之前的例子一样的操作。我们在类中定义了一个方法,但它不意味着该类的对象可以把方法继承下来,而是我们在类中定义了类似于 def self.num的方法,它跟 Java 中的静态方法很像。这个方法必须以 MyClass.num 的方式进行激活,所以它无法被实例方法调用:
irb> MyClass.num
=> nil
在这里我们会获得 nil。因为在 MyClass 对象中没有@num变量。未定义的变量会有默认值 nil。 好吧,那正确的解决方式是什么呢?答案为 class_eval:
irb> MyClass.class_eval do
irb> def num
irb> @num
irb> end
irb> end
=> nil
irb> b.num
=> 2
Yes! 成功了。我们给类定义了方法,而不是类对象,并且该方法可以被所有的类对象所使用。 注意我们是在 MyClass 中调用了class_eval,而不是类对象。在实例中无法调用class_eval因为class_eval不是实例方法,只有像 MyClass 这样的类才能使用。但是你可以通过调用 class 方法获取该实例的 Class。
irb> a.class_eval
NoMethodError: undefined method `class_eval' for #<MyClass:0x007fba5c02c858 @num="1">
irb> a.class.class_eval {}
=> nil
需要注意的是上例中使用 class_eval 定义的方法其实跟下例有一样的效果。
class MyClass
def num
@num
end
end