如何理解class_eval()和instance_eval()之间的区别?

pez*_*ser 54 ruby class-method instance-method

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>
Run Code Online (Sandbox Code Playgroud)

只是基于方法的名称,我希望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的上下文中计算字符串或块.这可以用于向类添加方法.

instance_eval的文档:

obj.instance_eval {| | block} => obj

在接收器(obj)的上下文中计算包含Ruby源代码或给定块的字符串.为了设置上下文,在代码执行时将变量self设置为obj,使代码可以访问obj的实例变量.

tom*_*fro 91

正如文档class_eval所述,在Module或Class的上下文中计算字符串或块.所以下面的代码是等价的:

class String
  def lowercase
    self.downcase
  end
end

String.class_eval do
  def lowercase
    self.downcase
  end
end
Run Code Online (Sandbox Code Playgroud)

在每种情况下,String类都已重新打开并定义了一个新方法.该方法适用于所有类的实例,因此:

"This Is Confusing".lowercase 
=> "this is confusing"
"The Smiths on Charlie's Bus".lowercase
=> "the smiths on charlie's bus"
Run Code Online (Sandbox Code Playgroud)

class_eval与简单地重新开课相比,它有许多优点.首先,您可以轻松地在变量上调用它,并且很清楚您的意图是什么.另一个优点是,如果该类不存在,它将失败.因此,下面的示例将失败,因为Array拼写不正确.如果只是重新打开该类,它将成功(并且Aray将定义一个新的不正确的类):

Aray.class_eval do
  include MyAmazingArrayExtensions
end
Run Code Online (Sandbox Code Playgroud)

最后class_eval可以拿一个字符串,如果你做一些更邪恶的事情,这可能是有用的...

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
Run Code Online (Sandbox Code Playgroud)

因此instance_eval,该方法仅针对字符串的单个实例进行定义.

那么为什么instance_evalClass定义类方法呢?

正如"This Is Confusing""The Smiths on Charlie's Bus"String实例Array,String,Hash和所有其他类本身的实例Class.您可以通过调用#class它们来检查:

"This Is Confusing".class
=> String

String.class
=> Class
Run Code Online (Sandbox Code Playgroud)

所以当我们调用instance_eval它时,它在类上和在任何其他对象上一样.如果我们使用instance_eval在类上定义方法,它将仅为该类实例定义一个方法,而不是所有类.我们可能将该方法称为类方法,但它只是该特定类的实例方法.

  • 这是一个很好的解释.谢谢.我一直跟着你,直到你试图解释为什么instance_eval在Class上调用时定义类方法的最后一段.最后一行对我来说特别难以理解:"将它称为类方法是一个错误,它只是该特定类实例上的一个实例方法." 你的意思是所有类方法都只是那个吗? (5认同)

jed*_*iah 17

另一个答案是正确的,但请允许我深入一点.

Ruby有许多不同的范围; 虽然详细的正式文件似乎缺乏,但根据维基百科的说法有六个.毫无疑问,这个问题涉及的范围是实例.

当前实例范围由值的定义self.所有非限定方法调用都被调度到当前实例,对实例变量(看起来像@this)的任何引用都是如此.

但是,def不是方法调用.创建的方法的目标def是当前的类(或模块),可以在其中找到Module.nesting[0].

让我们看看两种不同的eval风格如何影响这些范围:

String.class_eval { [self, Module.nesting[0]] } => [String, String] String.instance_eval { [self, Module.nesting[0]] } => [String, #<Class:String>]

在这两种情况下,实例范围都是调用*_eval的对象.

因为class_eval,类范围也成为目标对象,因此def为该类/模块创建实例方法.

因为instance_eval,类范围成为目标对象的单例类(也称为元类,本征类).在单个类上为对象创建的实例方法成为该对象的单例方法.类或模块的单例方法通常(并且有些不准确)称为类方法.

类范围也用于解析常量.类变量(@@these @@things)使用类作用域解析,但在搜索模块嵌套链时会跳过单例类.我发现在单例类中访问类变量的唯一方法是使用class_variable_get/set.

  • +1除了其他答案之外,还在这里介绍了范围。 (2认同)

bes*_*sen 5

我觉得你弄错了.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
Run Code Online (Sandbox Code Playgroud)