如何理解class_eval()和instance_eval()之间的区别?
Foo = Class.new Foo.class_eval do def class_bar "class_bar" end end Foo.instance_eval do def instance_bar "instance_bar" end end Foo.class_bar #=> undefined method 'class_bar' for Foo:Class Foo.new.class_bar #=> "class_bar" Foo.instance_bar #=> "instance_bar" Foo.new.instance_bar #=> undefined method 'instance_bar' for #<Foo:0x7dce8>
只是基于方法的名称, 我希望class_eval允许您将类方法添加到Foo和instance_eval,以允许您将实例方法添加到Foo。 但他们似乎做了相反的事情 。
在上面的例子中,如果你调用Foo类的class_bar,你会得到一个未定义的方法错误,如果你在Foo.new返回的实例上调用instance_bar,你也会得到一个未定义的方法错误。 这两个错误似乎都与对class_eval和instance_eval应该做什么的直观理解相矛盾。
这些方法真的有什么区别?
class_eval的文档:
mod.class_eval(string [,filename [,lineno]])=> obj
评估mod的上下文中的string或块。 这可以用来添加方法到一个类。
文档instance_eval :
obj.instance_eval {| | block} => obj
在接收器(obj)的上下文中评估包含Ruby源代码或给定块的string。 为了设置上下文,variablesself在代码执行时被设置为obj,从而赋予代码访问obj的实例variables的权限。
正如文档所述, class_eval
在模块或类的上下文中评估string或块。 所以下面的代码段是等价的:
class String def lowercase self.downcase end end String.class_eval do def lowercase self.downcase end end
在每种情况下,都重新打开了String类并定义了一个新的方法。 该方法在类的所有实例中都可用,所以:
"This Is Confusing".lowercase => "this is confusing" "The Smiths on Charlie's Bus".lowercase => "the smiths on charlie's bus"
class_eval
与简单地重新打开类相比有许多优点。 首先,你可以很容易地把它称为一个variables,而且很清楚你的意图是什么。 另一个好处是,如果这个类不存在,它将会失败。 所以下面的例子会失败,因为Array
拼写错误。 如果这个阶级简单地重新开放,它就会成功(并且将定义一个新的不正确的Aray
类):
Aray.class_eval do include MyAmazingArrayExtensions end
最后, class_eval
可以接受一个string,如果你正在做一些更邪恶的事情,这将是有用的。
另一方面,instance_eval针对单个对象实例计算代码:
confusing = "This Is Confusing" confusing.instance_eval do def lowercase self.downcase end end confusing.lowercase => "this is confusing" "The Smiths on Charlie's Bus".lowercase NoMethodError: undefined method 'lowercase' for "The Smiths on Charlie's Bus":String
因此,使用instance_eval
,该方法仅针对该string的单个实例进行定义。
那么为什么一个Class
instance_eval
定义类方法呢?
正如"This Is Confusing"
和"The Smiths on Charlie's Bus"
都是String
实例, Array
, String
, Hash
和所有其他类本身都是Class
实例。 你可以通过调用#class
进行检查:
"This Is Confusing".class => String String.class => Class
所以当我们调用instance_eval
时候,它和其他对象上的类一样。 如果我们使用instance_eval
在一个类上定义一个方法,它将为这个类的实例定义一个方法,而不是所有的类。 我们可以把这个方法称为一个类方法,但这只是该类的一个实例方法。
另一个答案是正确的,但让我深入一点。
Ruby有许多不同的范围, 六个根据维基百科 ,虽然详细的正式文件似乎缺乏。 涉及这个问题的范围的种类,毫不奇怪, 实例和类 。
当前实例范围由self
的值定义。 所有不合格的方法调用都将被分派到当前实例,对实例variables的引用也是如此(看起来像@this
)。
但是, def
不是一个方法调用。 def
创build的方法的目标是当前的类(或模块),可以通过Module.nesting[0]
。
让我们看看这两种不同的评价风格如何影响这些范围:
String.class_eval { [self, Module.nesting[0]] } => [String, String] String.instance_eval { [self, Module.nesting[0]] } => [String, #<Class:String>]
在这两种情况下,实例作用域都是调用* _eval的对象。
对于class_eval
,类作用域也成为目标对象,所以def
为该类/模块创build实例方法。
对于instance_eval
,类作用域成为目标对象的单例类 (也称为metaclass,eigenclass)。 在单例类上为对象创build的实例方法成为该对象的单例方法。 用于类或模块的单例方法通常称为类方法 (并且有些不准确)。
类作用域也用于parsing常量。 类variables( @@these @@things
)用类作用域parsing,但是在search模块嵌套链时跳过单例类。 我发现在单例类中访问类variables的唯一方法是使用class_variable_get/set
。
我想你错了。 class_eval在类中添加方法,所以所有的实例都有方法。 instance_eval只会将该方法添加到一个特定的对象。
foo = Foo.new foo.instance_eval do def instance_bar "instance_bar" end end foo.instance_bar #=> "instance_bar" baz = Foo.new baz.instance_bar #=> undefined method
instance_eval有效地为有问题的对象实例创build一个单例方法。 class_eval将在给定的类的上下文中创build一个普通的方法,可用于该类的所有对象。
这里是关于单例方法和单例模式的链接(非特定于ruby的)