日常使用 Ruby 时,在 「 运行时 」 ( on-the-fly ) 向类或模块添加方法或属性是司空见惯的事情,例如 activerecord
、activesupport
、rank
、rack
等等。
为什么这么司空见惯呢 ?
因为 Ruby 提供了 Module#class_eval
和 Module#module_eval
两个方法可以在 「 运行时 」 注入属性和方法。
你接下来可能会有疑问,既然两个方法都在 Module
上,那它们的区别是什么呢 ?
class_eval
在 Ruby 中,当我们想要向 类 添加方法时,我们可以使用 Module#class_eval
方法。该方法接受 字符串 或 块 ( block ) 作为参数
array_second = <<-RUBY def second self[1] end RUBY Array.class_eval(array_second) String.class_eval do def /(delimiter) split(delimiter) end end $> [1,2,3].second => 2 $> "1,2,3" / ',' => ["1", "2", "3"]
上面的代码中,调用 Array.class_eval(array_second)
会将 array_second
方法添加到 Array
的所有实例中。
class_eval
的机制很简单,就会在类的上下文运行 class_eval
中的字符串来达到此目的。
而向 String#class_eval
传递一个块 ( block ) 时,则会在 String
的上下文中运行参数块的内容。上面的代码,我们添加了 String#/(delimiter)
方法,这是一个运算符 /
,对 String
的所有实例都可用。
提示,如果你对
<<-RUBY
语法不熟悉的话,可以阅读 Ruby heredoc 中不使用字符串插值 来了解这种语法
module_eval
Module#module_eval
方法和 Module#class_eval
方法类似,只不过它是向 模块 module
添加运行时方法而已。
module Commentable def add_comment(comment) self.comments << comment end def comments @comments ||= [] end end Commentable.module_eval do def comment_count comments.count end end class Post include Commentable end $> post = Post.new => #<Post:0x00007fd9ac0238b0> $> post.add_comment("Very nice !") => ["Very nice !"] $> post.comment_count => 1
一套核心,两套皮肤
class_eval
方法用于向类添加运行时属性或方法。而 module_eval
则在运行时向模块添加属性或方法。
但这只是表面的情况。
在内部,class_eval
其实就是 module_eval
的别名
我们只要看一下 Ruby 的源代码 ruby/vm_eval.c
就能证明我们刚刚所说的
rb_define_method(rb_cModule, "module_eval", rb_mod_module_eval, -1); rb_define_method(rb_cModule, "class_eval", rb_mod_module_eval, -1);
从源代码中可以看到, module_eval
和 class_eval
都指向了同样的 C 语言函数 rb_mod_module_eval()
由于 C 语言函数的名称为 module_eval
,因此,我们可以说 class_eval
是 module_eval
的别名