设为首页 加入收藏

TOP

理解Ruby中的作用域(二)
2017-10-09 14:22:54 】 浏览:866
Tags:理解 Ruby 作用
e_class
= SomeClass.new some_class.some_method

  当你运行上面的代码后,你会看到分别打印出[:v1]、[:v2],为什么v0不会在SomeClass中的局部变量中?v1不再some_method方法中?正是因为class和def关键字分别开辟了新的作用域,将旧的作用域替代了,旧作用域的局部变量在新的作用域中便不复存在了,为什么说作用域是被"替代”了?因为旧的作用域只是暂时被替换而已,在新作用域关闭时会再次回到旧的作用域,在some_class.som_method这条语句之后运行p local_variables你会看到v0重新出现在局部变量列表中。

  打破作用域门

  正如你所见,通过class/def/module会限制局部变量的作用域,并且会屏蔽掉之前的作用域,使得原来定义的变量在新作用域中不可见,那假如我们想要处理作用定义的方法、类、模块之外的局部变量,应该如何打破作用域之间的隔离?

  答案很简单,只需要用方法调用的方式替换作用域门的方式,就是说:

  • 用Class.new 代替 class
  • 用Module.new 代替 module
  • 用define_method代替def

  下面是一个例子:

v0 = 0
SomeClass = Class.new do
  v1 = 1
  p local_variables

  define_method(:some_method) do
    v2 = 2
    p local_variables
  end
end

some_class = SomeClass.new
some_class.some_method

  运行上面的代码后,会打印出[:v1, :v0, :some_class]和[:v2, :v1, :v0, :some_class]这两行局部变量名,可以看到我们成功地打破了作用域的限制,这归功于我们接下来需要学习的ruby中的blocks的功能。

  Blocks也是作用域门的一种吗?

  你也许会认为blocks也是作用域门的一种,毕竟它也创建了一个包含局部变量的作用域,并且在blocks中定义的局部变量在外部是不可访问的,就像下面的例子一样:

sample_list = [1,2,3]
hi = '123'
sample_list.each do |item| # block代码块的开始
  puts hi # 是打印出123还是抛出错误?
  hello = 'hello' # 声明并赋值给变量hello
end

p hello # 打印出‘hello’还是抛出undefined local variable 异常

  如你所见,在blocks代码块中定义的变量‘hello’是只存在block作用域中的局部变量,外部不能访问也不可见。

  如果block代码块是一个作用域门,那么在puts hi这条语句执行时应该会触发异常,但在block中却能成功打印出hi的值,而且在blocks中你不仅能访问hi的值,并且能够对其进行修改,尝试在do/end代码块中修改hi的值为‘456’,你会发现外部变量 hi 的值成功被修改。

  那如果不想让block中的代码修改外部局部变量的值呢?这是我们可以使用block-local variable(类似方法中的形参),只需在block中将参数用 ; 分割后填写外部同名的局部变量名(这些变量在block中会成功block-local variables),在block里面对这些变量的修改不会影响其在外部原来的值,下面是一个例子:

hi = 'hi'
hello ='hello'
3.times do |i; hi, hello|
  p i
  hi = 'hi again'
  hello = 'hello again'
end
p hi # "hi"
p hello # "hello"

  如果你在block的参数列表中移除 ; hi, hello ,在代码块外面你会发现变量 hi 和 hello 的值变成‘hi again' 和 'hello again'了。

  记住使用do和end创建block代码块的同时会创建一个新的作用域。

[1,2,3].select do |item| # do is here, new scope is being introduced
  # some code
end

使用each、map、detect或者其它方法,当你使用do/end创建代码块当做参数传给这些方法,只不过是创建了一个新的作用域。

Blocks和作用域的一些小怪癖

试想下面的代码会输出什么:

y to guess what will this Ruby code print:

2.times do
  i ||= 1
  print "#{i} "
  i += 1
  print "#{i} "
end

  你是不是以为会输出 1 2 2 2 ?但答案是1 2 1 2 ,因为每一次的迭代会创建一个新的作用域并重置局部变量。因此,在这段代码中我们分别创建了两个迭代,每次迭代开始都将变量重置为1。

  那么你认为下面的代码会输出什么?

def foo
  x = 1
  lambda { x }
end

x = 2

p foo.call

  答案是1,因为blocks和blocks对象看到的是定义在其内的作用域而不是调用该block时的作用域。这与它们在ruby中是被看作闭包有关,闭包是一种对代码的包含,从而使该段代码带有以下特征:

  • 该部分代码可以像对象一样被调用(可以在定义之后通过call调用)
  • 当闭包被定义时,记录该作用域下的变量

  这些特性给我们在编写无限数字生成器等情况时提供方便:

def increase_by(i)
  start = 0
  lambda { start += i }
end

increase = increase_by(3)
start = 453534534 # won't affect anything
p increase.call # 3
p increase.call # 6

  你可以利用lambda表达式方便地对变量进行延迟赋值:

i = 0
a_lambda = lambda do
  i = 3
end

p i # 0
a_lambda.call
p i # 3

  你认为下面代码段中的最后一行代码会输出什么?

a = 1
ld = lambda { a }
a = 2
p ld.call

  如果你的回答是1,那么你就错了,最后一行语句会输出2。咦,等一下,lambda表达式没有看到它们的作用域吗?如果你再仔细想一想,你会发现这其实是正确的,a = 2也在lambda表达式的作用域当中,真如你所见到的,lambda表达式在其被调用时才开始计算变量的值。如果你没有考虑到这点,将会容易触发难以追踪的bug。

  如何在两个方法中共享变量

  一旦我们知道如何打破作用域门,我们就可以利用这些来实现很惊人的效果,我从ruby元编程这本书中学习到这些知识,这本

首页 上一页 1 2 3 下一页 尾页 2/3/3
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇Logstash为什么那么慢?—— json.. 下一篇关于安装ruby brew 提示失败

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目