为何使用class_eval会将类变量设置到顶级Object类中?
为什么用
class_eval设置类变量会跑到Object类里? 你提到的Foo.class_eval确实会把self设置为Foo类,这部分理解完全正确——在class_eval块里的代码,就像直接写在class Foo; ... end块里一样,上下文是Foo类本身。那为什么你会发现类变量被赋值到了Object?核心问题出在Ruby类变量(@@开头)的作用域规则上,和self的指向不是一回事。
先明确类变量的本质
Ruby的类变量是跨继承层次共享的,它属于整个类的继承链,而不是单个类。当你在某个类上下文中赋值一个未定义的类变量时,Ruby会沿着当前类的祖先链向上查找:
- 如果找到已存在的同名类变量,就直接修改它的值;
- 如果整条祖先链(直到
Object)都找不到,Ruby就会在当前类中创建这个类变量——除非你是在一个“无类上下文”(比如顶级作用域的class_eval)里赋值,但你用的是Foo.class_eval,所以正常情况不会跑到Object。
你可能遇到的两种情况
1. 你误把全局变量当成了类变量
如果你的代码里写的是$var(全局变量)而不是@@var,那不管在哪个上下文赋值,全局变量都会挂在Object上,这是很容易犯的低级错误。比如:
Foo.class_eval do $test = "oops, global variable" # 这不是类变量! end puts Object.instance_variables.include?(:$test) # 会返回true
2. 你的Foo类的祖先链中已经有同名类变量
假设你之前在Object或者Foo的某个父类里定义过@@var,那在Foo.class_eval里赋值@@var时,Ruby会直接修改祖先链中已存在的那个变量,看起来就像“跑到了Object里”。比如:
# 先在Object里定义类变量 class Object @@shared_var = "original value" end class Foo; end Foo.class_eval do @@shared_var = "modified in Foo" # 这里修改的是Object里的@@shared_var end puts Object.class_variables.include?(:@@shared_var) # true puts Foo.class_variables.include?(:@@shared_var) # true,因为继承链共享 puts Object.class_variable_get(:@@shared_var) # => "modified in Foo"
验证正确的行为
如果你的代码是干净的(没有提前定义同名类变量),那Foo.class_eval里定义的类变量应该属于Foo:
class Foo; end Foo.class_eval do @@foo_var = "I belong to Foo" end puts Foo.class_variables # => [:@@foo_var] puts Object.class_variables.include?(:@@foo_var) # => false
总结
class_eval的上下文确实是目标类,但类变量的查找和赋值是基于继承链的,而不是当前self的直接归属。你看到变量跑到Object,大概率是要么误写了全局变量,要么祖先链里已经存在同名类变量,导致你修改了上层的变量而非在Foo里新建。
内容的提问来源于stack exchange,提问作者nickackerman42
相关产品推荐
相关产品推荐

