elephant

赫本之后 再无女神

Instance_eval,instance_exec,class_eval,class_exec 区别

对于instance_eval和class_eval,在看ruby元编程时以为搞清楚了,但最近发现一种情况,却又让我迷糊了:

class_eval

1
2
3
4
5
6
7
8
9
10
11
12
13
class A
end
A.class_eval
  def a
    puts 'a'
  end
  define_method :b do
    puts 'b'
  end
end
A.new.a # 'a'
A.new.b # 'b'
#a和b都是A的实例方法

instance_eval

1
2
3
4
5
6
7
8
9
10
11
12
13
class A
end
A.instance_eval do
  def a
    puts 'a'
  end
  define_method :b do
    puts 'b'
  end
end
A.new.b # 'b' b为实例方法
A.new.a #  no method error!!!
A.a # ‘a’

为什么在instance_eval中 def 和define_method定义的一个为类方法(类的单件方法) 一个为实例方法呢?

要解释这个问题,首先要有以下概念: ruby在执行时,会一直追踪当前对象(receiver)即self,但也会追踪当前类(current class)。instance_eval和class_eval都会修改self和current class:

  • klass.class_eval 修改self为klass,修改current class为klass
  • obj.instance_eval 修改self为obj,修改current class为obj的eigen_class(singleton_class)。

了解了以上知识,我们知道define_method的接受者为self,class_eval和instance_eval都改变self为调用者本身,所以定义的为实例方法;而对def起作用的是当前类(current class),class_eval修改当前类为调用者本身,所以定义的是类方法,而instance_eval修改当前类为调用者的eigen_class(singleton_class),所以定义的是类方法(类的单件方法)。

另外,class_eval仅类可调用,instance_eval则类和对象都可调用。

class(instance)_exec与eval基本相似,但有以下不同:

  • _evals既可传递字符串,也可传递块,如123.instance_eval ‘to_s’
  • exec只能传递块、不能传字符串,但exec可以为block传递参数如 Class.instance_exec(‘Self’){|x|p “#{x}:self”}

至此,class_eval,instance_eval,instance_exec,instance_eval概念基本都透彻了。 对于current class有些未说清的(未研究透彻,初步判断current class 对关键字起作用,self对方法调用起作用~),回头研究完单写一篇。