instance_eval并class_eval允许您执行一大段代码。那你可能会说什么?老式的eval可以做到这一点。但是instance_eval并class_eval接受代码块的块参数。所以代码块不需要是字符串。还允许接收器(instance_eval与class_eval旧的不同eval)。因此,您可以在类对象甚至实例对象上调用这两个现代方法。
class A
end
A.instance_eval do
# self refers to the A class object
self
end
a = A.new
a.instance_eval do
# self refers to the a instance object
self
end
还要记住,在 ruby 中,如果我们调用一个没有接收器的方法,那么该方法将被调用 on self,在instance_eval块中是我们调用的对象instance_eval。实例变量在 ruby 中是私有的。您不能在定义它们的类之外访问它们。但是由于实例变量存储在 中self,我们可以在其中访问它们instance_eval(这同样适用于不能用接收器调用的私有方法):
class A
def initialzie
@a = “a”
end
private
def private_a
puts “private a”
end
end
a = A.new
puts a.instance_eval { @a }
# => “a”
puts a.instance_eval { private_a }
# => “private a”
我们还可以在instance_eval和中为接收者添加方法class_eval。在这里,我们将其添加到instance_eval:
class A
end
A.instance_eval do
def a_method
puts “a method”
end
end
A.a_method
# => a method
现在想想我们刚刚做了什么。我们使用instance_eval,在其中定义了一个方法block,然后在类对象本身上调用该方法。这不是类方法吗?如果您愿意,可以将其视为“类”方法。但是我们所做的只是在instance_eval块中的接收器上定义一个方法,而接收器恰好是A. 我们可以很容易地在实例对象上做同样的事情:
a.instance_eval do
def a_method
puts "a method"
end
end
a.a_method
# => a method
它的工作原理是一样的。不要将类方法视为其他语言中的类方法。它们只是在 上定义的方法self,而self恰好是一个类对象(从Class.newas in扩展class A end)。
但我想把这个答案比接受的答案更深入一点。instance_eval实际上将您放入其中的方法粘贴在哪里?他们进入singleton接收者的班级!一旦您instance_eval在接收器上调用,ruby 解释器就会打开singleton_class并将块中定义的方法放在 thissingleton_class中。就像extend在类中使用一样(因为extend打开了单例类,并将传递给扩展的模块中的方法放入单例类中)!它打开了singleton_class,它是继承层次结构的一部分(就在父类之前):A -> singleton_class -> Parent
现在有什么class_eval不同?class_eval只能在类和模块上调用。self仍然指的是接收者:
class A
end
A.class_eval do
# self is A
self
end
但与 不同instance_eval的是,当您在class_eval块中定义方法时,它们将在类的实例而不是类对象本身上可用。使用class_eval,方法不会添加到继承层次结构中的单例类中。相反,方法被添加到current class接收者的!因此,当您在 中定义方法时class_eval,该方法直接进入current class,因此它成为实例方法。所以你不能在类对象上调用它;您只能在类对象的实例上调用它。