Ruby中的instance_eavl和class_eval

ruby元编程中的instance_eavl和class_eval简单介绍

第二次看Ruby元编程这本书,感觉里面能学到的东西很多,但是很容易忘记,所以把一些想法写下来,以备后用。

instance_eval

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class My
  def initialize
    @v = 1
  end
end

obj = My.new
v = 3
obj.instance_eval do
  puts self
  puts @v
  puts v
  def one_method
    puts "I'm one_method"
  end
end
#=> #<My:0xa2b76a4>
#=> 1
#=> 3
obj.one_method
#=> I'm one_method
obj2 = My.new
obj2.one_method
#=> NoMethodError: undefined method `one_method' for #<My:0xa2f3f14 @v=1>
  • 首先,我们分析上面代码:

    • 当我们实例化一个类时,我们可以通过instance_eval来访问到该类的实例方法,所以我们可以看到self返回一个对象,@v返回1
    • 由于block是一个闭包,所以在运行的时候,可以访问局部变量v,而且能访问局部变量@v,这种情况,我们把他叫做上下文探针,它就像是一个深入到对象中的代码片段,对其进行操作。
    • 当我们在instance_eval块里面定义方法是,由于self为当前对象,所以 def关键子打开的作用域门为self,即定义的方法只有obj可以访问,我们称它为单件方法。
  • 然后,我们总结instance_eval的作用:

    • instance_eval的调用者必须是一个实例
    • 改变当前block的接受者为self,因此它可以访问接受者的私有方法和实例变量
    • 可以在block内使用单件方法
  • 最后,我们还发现,可以用instance_eavl来定义类方法

1
2
3
4
5
6
7
8
9
class A
end
A.instance_eval do
  def a_method
    puts "I'm class method"
  end
end
A.a_method
#=>I'm class method

class_eval(又名module_eval)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def add_method_to(a_class)
  a_class.class_eval do
    puts self
    def m
      puts 'hello'
    end
  end
end
#=> String

"abc".m
add_method_to String
'abc'.m
#=> NoMethodError: undefined method `m' for "abc":String
#=> "hello"
  • 分析上面代码,我们可以发现class_evalinstance_eval的区别: class_eval的调用对象必须是一个类 class_evalblock里面的self为类本身(实际上是重新打开了该类,相当于关键字class) *class_eval定义的方法为类的实例方法(instance_eval是单件方法)

总结:什么时候用class_evalinstance_eval

如果打开的对象不是类,则使用instance_eval,如果想打开一个类定义并且用def定义实例方法,则选择class_eval 根据调用者来决定 *根据你的意图来决定

Comments