Ruby 基础 理解 class_eval 和 instance_eval

skpark1987 · 2015年05月25日 · 最后由 haobangpig 回复于 2018年09月12日 · 7224 次阅读

原文链接:Understanding class_eval and instance_eval 比起别的方法,这个两个方法更加让人困惑,尤其是他们的解释。它们是 class_evalinstance_eval。名字很相似,但行为有悖常理。

  1. 使用ClassName.instance_eval方法可以定义一个类方法
  2. 使用ClassName.class_eval可以定义一个实例方法。 为了明白这个定义的原因,我们举个例子。 ```ruby class MyClass def initialize(num) @num = num end end

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

一直觉得翻译成“实例方法”是不太对的……这种叫法就像是抱着 java,C++ 那类语言的概念

原文“Invoking class_eval on an instance wouldn't work because class_eval isn't a method of arbitrary objects, only of class objects like MyClass”也没有出现"instance method"这种字眼

不能再 a 上调用 class_eval 是因为,class_eval 是 Module/Class 的实例方法,而 a 所指向的东西不是由 Module/Class 的 new 所产生出来的东西

那么问题来了 为什么 在 class_eval 和 instance_eval 中用 define_method 定义的都是实例方法 class_eval 中 def 定义的是实例方法而 instance_eval 中 def 定义的是类方法呢?

#3 楼 @secondrocker 个人理解是跟 current class 有关:

#3 楼 @secondrocker 比如说有个类 C, 调用 class_eval 和 instance_eval 的时候 scope 是 C 这个 class object,也就是 self 是 C,所以 define_method 的 receiver 都是 C,所以定义的都是 instance method.

而 def 是在 current class 上定义 instant method,class_eval 中的 current class 自然是 C,instance_eval 中的 current class 是 C 的 singleton_class,所以是 class method.

使用 ClassName.class_eval 可以定义一个实例方法。

这句话说得不完全,是可以定义一个实例方法,也可以定义类方法,类变量等等等.... 用 class_eval 相当于在重新打开了这个类,然后写代码。

一开始看这里感觉只能定义实例方法一样

需要 登录 后方可回复, 如果你还没有账号请 注册新账号