如何理解class_eval()和instance_eval()之间的区别? [英] How to understand the difference between class_eval() and instance_eval()?

查看:123
本文介绍了如何理解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(字符串[,文件名[, lineno]])=> obj

计算字符串或块中的 mod的上下文.这可以用来 将方法添加到类中.

instance_eval :

obj.instance_eval {| |块} => obj

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

解决方案

如文档所述,class_eval在模块或类的上下文中评估字符串或块.因此,以下几段代码是等效的:

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

Aray.class_eval do
  include MyAmazingArrayExtensions
end

最后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

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

那么为什么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在类上定义方法,则它将仅为该类实例而不是所有类定义方法.我们可以将该方法称为类方法,但这只是该特定类的实例方法.

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>

Just based on the name of the methods, I would expect class_eval to allow you to add a class method to Foo and instance_eval to allow you to add an instance method to Foo. But they seem to do the opposite.

In the example above if you call class_bar on the Foo class you get an undefined method error and if you call instance_bar on the instance returned by Foo.new you also get an undefined method error. Both errors seem to contradict an intuitive understanding of what class_eval and instance_eval should do.

What is really the difference between these methods?

Documentation for class_eval:

mod.class_eval(string [, filename [, lineno]]) => obj

Evaluates the string or block in the context of mod. This can be used to add methods to a class.

Documentation for instance_eval:

obj.instance_eval {| | block } => obj

Evaluates a string containing Ruby source code, or the given block, within the context of the receiver (obj). In order to set the context, the variable self is set to obj while the code is executing, giving the code access to obj’s instance variables.

解决方案

As the documentation says, class_eval evaluates the string or block in the context of the Module or Class. So the following pieces of code are equivalent:

class String
  def lowercase
    self.downcase
  end
end

String.class_eval do
  def lowercase
    self.downcase
  end
end

In each case, the String class has been reopened and a new method defined. That method is available across all instances of the class, so:

"This Is Confusing".lowercase 
=> "this is confusing"
"The Smiths on Charlie's Bus".lowercase
=> "the smiths on charlie's bus"

class_eval has a number of advantages over simply reopening the class. Firstly, you can easily call it on a variable, and it's clear what your intent is. Another advantage is that it will fail if the class doesn't exist. So the example below will fail as Array is spelt incorrectly. If the class was simply reopened, it would succeed (and a new incorrect Aray class would be defined):

Aray.class_eval do
  include MyAmazingArrayExtensions
end

Finally class_eval can take a string, which can be useful if you're doing something a little more nefarious...

instance_eval on the other hand evaluates code against a single object instance:

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

So with instance_eval, the method is only defined for that single instance of a string.

So why does instance_eval on a Class define class methods?

Just as "This Is Confusing" and "The Smiths on Charlie's Bus" are both String instances, Array, String, Hash and all other classes are themselves instances of Class. You can check this by calling #class on them:

"This Is Confusing".class
=> String

String.class
=> Class

So when we call instance_eval it does the same on a class as it would on any other object. If we use instance_eval to define a method on a class, it will define a method for just that instance of class, not all classes. We might call that method a class method, but it is just an instance method for that particular class.

这篇关于如何理解class_eval()和instance_eval()之间的区别?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆