Ruby instance_eval 是如何做到修改 Block 里的 self 引用的?

lululau · August 29, 2013 · Last by fredwu replied at September 17, 2013 · 3329 hits

比如这段代码

class Person
    def hello
        yield
    end
    def world(&proc)
        proc.call
    end
end

p = Person.new
p.hello {puts self.class}
p.world {puts self.class}
p.instance_eval {puts self.class}

输出:

Object
Object
Person

如果我想写一个叫做haha方法,然后通过的调用 p.haha {puts self.class} 输出 Person,如果不借助 instance_eval (包括不借助那些已经借助了instance_eval的方法:Module#define_method 等),可以写出这个 haha 方法吗?说到底就是像 instance_eval 那样修改一个 Block 里的 self 引用是可行的吗?

说到底就是像 instance_eval 那样修改一个 Block 里的 self 引用是可行的吗?

我觉得可行

可以,sinatra 就是把 block 转换成 method, 然后 unbind,然后再 bind 到一个 object 上调用,这时 self 就是 object 具体是 define_method &blk --> method(:xxx) #得到UnbindMethod --> undefine_method(:xxx) --> unbind_method.bind(obj).call

instance_eval 内部 c 实现是调用了 specific_eval,specific_eval 里判断如果参数是 block,就调用yield_under(klass, self, Qundef);,会改变 block 的 self,如果既不用 instance_eval/exec,也不用 define_method 来用 UnbindMethod 去 bind 对象,貌似没办法的样子。

呼唤大神 @luikore

#3 楼 @kenshin54 感谢!其实我不是非要自己做一个 instance_eval,就是想明白 instance_eval 是如何做到这一点的。通过的你的回复我已经差不多明白了。

class Person
  def haha(&blk)
     blk.bind(self).call
  end
end

 Person.new.haha { p self.class }
=> Person

#5 楼 @zhenjunluo Proc 貌似没有 bind 这个实例方法

#3 楼 @kenshin54 #6 楼 @lululau

ActiveSupport 加了 Proc#bind, 但是每次 bind 都生成一个新的 symbol, 会引起严重的 GC 问题,已经 deprecated 了

#7 楼 @luikore 请问下,还有哪些使用方式会引起 GC 问题,hash 中用 string 代替 symbol 做 key,GC 的释放上会有多大差别,局部变量 array,hash 中存了大量的数据,用 clear 是否能达到释放内存的目的?

#8 楼 @zhenjunluo symbol 只存一份,但不释放,string 没有 symbol 那样的优化,但可以释放。不该让程序存在生成无穷多 symbol 的可能 (例如把用户输入转换成 symbol).

params[:a] # 随便用
params['a'].to_sym # 不好
"#{i += 1}".to_sym # 如果调用次数不多, 也没问题

用 clear 可以,不过退出局部作用域就全释放了,不用处处 clear.

#9 楼 @luikore 谢谢答复,rails 中的 instance variables 在页面渲染完后会及时释放内存吗?

#5 楼 @zhenjunluo 看了一下 ActiveSupport,发现 Proc#bind 也是调用了 define_method,define_method 又是调用了 instance_eval

13 Floor has deleted
class Person
  def hello
    yield
  end

  def world(&proc)
    proc.call
  end

  def haha(&block)
    instance_exec(&block)
  end
end

p = Person.new
p.hello {puts self.class}
p.world {puts self.class}
p.instance_eval {puts self.class}
p.haha {puts self.class}
Object
Object
Person
Person
You need to Sign in before reply, if you don't have an account, please Sign up first.