如何理解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实例, ArrayStringHash和所有其他类本身都是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的)