可以在块内更改变量吗?

pie*_*fou 3 ruby

让我们直接转到代码:

#!/usr/bin/ruby
 require 'tk'


class Epg

def initialize
    @var = "bad"    
    @cvs = nil  
    @items_demo = TkRoot.new() {title "EPG"}
    TkFrame.new(@items_demo) {|cf|
          @var = "good" 
          @cvs = TkCanvas.new(cf) {|c|}
        puts  "@cvs 1  is #{@cvs}"
        puts  "@var 1 is #{@var}"
    }.pack('side'=>'top', 'fill'=>'both', 'expand'=>'yes')

    puts  "@cvs 2 is #{@cvs}"
    puts  "@var 2 is #{@var}"

end #initialize

def test
    @var = "bad"
    puts " @var 3 :#{@var}"
    (1..3).each {|x| @var="good"}
    puts " @var 4 :#{@var}"
end
 end

 e= Epg.new 
 e.test 
Run Code Online (Sandbox Code Playgroud)

这是输出:

@cvs 1  is #<Tk::Canvas:0xb7cecb08>
@var 1 is good
@cvs 2 is 
@var 2 is bad           #@var has NOT been changed by the code in the block
@var 3 :bad
@var 4 :good            #@var has been changed by the code in the block
Run Code Online (Sandbox Code Playgroud)

为什么我们在这里看到不同的行为?

Yeh*_*atz 5

您可以将块视为封闭局部变量集和current self

在Ruby中,无论如何,您始终可以访问局部变量。在self当前对象的包囊实例方法和实例变量。

考虑以下代码:

class Table
  def initialize(legs)
    @legs = legs
  end

  def with_legs
    yield @legs
  end
end
Run Code Online (Sandbox Code Playgroud)

然后:

def some_calling_method
  name = "Yehuda"
  Table.new(4) {|legs| puts "#{name} gnaws off one of the #{legs} legs" }
end
Run Code Online (Sandbox Code Playgroud)

通过Ruby的块语义,您可以放心name,即使在不查看所调用方法的情况下,该块内也将可用。

但是,请考虑以下事项:

class Person
  def initialize(name)
    @name = name
  end

  def gnaw
    Table.new(4).with_legs do |legs| 
      puts "#{@name} gnaws off one of the #{legs} legs"
    end
  end
end

Person.new("Yehuda").gnaw
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我们@name从块内部访问实例变量。在这种情况下,它很好用,但不能保证。如果我们实现表有些不同怎么办:

class Table
  def initialize(legs)
    @legs = legs
  end

  def with_legs(&block)
    self.instance_eval(&block)
  end
end
Run Code Online (Sandbox Code Playgroud)

实际上,我们所说的是“在不同自我的背景下评估障碍”。在这种情况下,我们将在表的上下文中评估该块。为什么要这么做?

class Leg
  attr_accessor :number
  def initialize(number)
    @number = number
  end
end

class Table
  def initialize(legs)
    @legs = legs
  end

  def with_leg(&block)
    Leg.new(rand(@legs).instance_eval(&block)
  end
end
Run Code Online (Sandbox Code Playgroud)

现在,您可以执行以下操作:

class Person
  def initialize(name)
    @name = name
  end

  def gnaw
    Table.new(4).with_leg do
      puts "I'm gnawing off one of leg #{number}"
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

如果要访问块内的人员对象,则必须执行以下操作:

class Person
  def initialize(name)
    @name = name
  end

  def gnaw
    my_name = name
    Table.new(4).with_leg do
      puts "#{my_name} is gnawing off one of leg #{number}"
    end
  end
end
Run Code Online (Sandbox Code Playgroud)

如您所见,instance_eval的使用可以使访问块内遥远对象的方法更简单,更省力,但代价是无法self访问。该技术通常用在DSL中,在DSL中,将许多方法注入到该数据块中,但是它本身并不重要。

Tk就是这样。他们正在使用instance_eval将自身self注入到块中,这将使您self心旷神怡。