块
- 块可以定义在大括号中, 也可以放在do…end关键字中, 一般来说,只有一行的块使用{},而对多行的块使用do…end
- 只有在调用一个方法时才可以定义一个块,块会被直接传递给这个方法,然后该方法可以通过yield关键字回调这个块.
- 块可以有自己的参数,当回调块时,可以像调用方法一样为块提供参数,并且块中最后一行代码执行的结果会被作为返回值.
- 通过Kernel#block_given?()方法可以询问当前方法调用是否包含块
- 1
- 2
- 3
- 4
- 5
- 6
- 7
对于在块中出现异常的情况,可以使用ensure关键字保证在出错的情况下,还能执行代码块,该方法常用于释放连接的情况,即使块出错也会保证连接释放.使用ruby模仿C#的关键字using.
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
闭包
代码运行时需要一个执行环境:局部变量,实例变量,self等, 这些绑定在对象上的名字我们简称为绑定.
块包含代码和一组绑定,当创建块时会获得到局部绑定,然后连同自己的绑定传递给方法,而方法中的变量对于块是不可见的.
- 1
- 2
- 3
- 4
- 5
- 6
- 7
在上述例子中,第一次块的绑定中无var变量, 导致出错,第二次块绑定中包含var变量,虽然my_method中包含了var但对于块不可见, 这种特性称之为闭包.
作用域
在C++和Java等语言中,有内部作用域和外部作用域的概念,内部作用域可以看到外部作用域的变量, 但在Ruby中,没有这种嵌套式的作用域,它们的作用域之间是分开的: 一但进入一个新的作用域,原先的绑定就会被替换为一组新的绑定.
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
从上述代码可以看出在程序切换作用域时,绑定会发生变化.
作用域门
程序会在三个地方关闭前一个作用域,同时打开新的作用域:
- 类定义
- 模块定义
- 方法
这三个边界分别用class, module和def关键字作为区分标识, 每个关键字都充当了一个作用域门.
全局变量和顶级实例变量
全局变量在任何作用域中都可以访问,正因如此,为了安全尽量少使用全局变量.
有时可以使用顶级实例变量来代替全局变量, 它们是顶级对象main的实例变量.
- 1
- 2
- 3
- 4
- 5
- 6
- 7
如上述代码所示,只要main对象扮演self的角色,就可以访问到顶级实例变量, 但当其它对象成为self时,顶级实例变量就退出作用域了,一般情况下,顶级实例变量比全局变量更安全.
扁平化作用域
既然了解了作用域的功能,那么如何让绑定穿过作用域门就成了一大问题.
Ruby中的类是Class类的实例,所以使用Class.new可以避免class关键字的出现,这样可以让变量穿过该作用域,同样对于def关键字,我们可以使用define_method()方法.
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
这样可以让一个作用域看到另一个作用域, 我么称这种技巧为嵌套文法作用域,也称之为扁平作用域.
共享作用域
但是对于想让某些变量在特定的方法中共享,而不希望其它方法访问,这样便需要另一种技巧共享作用域.
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
上述代码中,通过send给Kernel模块定义counter方法和inc方法, 这样只有Kernel的counter和inc方法可以访问shared变量,而其它方法则无法访问,因为这个变量被def作用域门保护这, 这种用于共享变量的技巧称为共享作用域.
instance_eval()
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
使用instance_eval方法时,该块的接受者会成为self, 因此他可以访问接收者的私有方法和实例变量, instance_eval()方法可以在不碰其它绑定的情况下修改self对象.
- 1
- 2
- 3
这三行代码在同一扁平作用域中执行,故都能访问局部变量v, 而且由于块把运行它的对象作为self,所以它们也能访问obj的实例变量@v.
在这些情况中,可以把传递给instance_eval()方法的块称为一个上下文探针.
洁净室
仅仅为了在其中执行块而创建的对象称之为洁净室
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
可调用对象
从实质上看,使用块有两个步骤:1)将代码打包备用;2)调用块来执行代码.在ruby中,有常用三种情况来打包代码.
- 使用proc, proc实质上是由一个块转换成的对象.
- 使用lambda, 它与proc类似
- 使用方法’
Proc对象
Ruby在标准库中提供了Proc类,可以通过Proc.new方法创建一个Proc,之后可以使用Proc#call()方法进行调用,称之为延迟执行.
Ruby还提供了两个内核方法,用于转换块,lambda()和proc().
- 1
- 2
- 3
- 4
- 5
块就类似与方法的额外参数名,大多情况下,方法可以通过yield语句直接运行一个块,但是对与把块传递给另一个方法或者把该块转换为Proc,yield将力不从心. 这时,我们可以使用&操作符,&块名即可.
注:要将块附加到一个绑定上,可以给这个方法添加一个特殊参数,该参数必须是参数列表最后一个,如&block, block为块名.
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
&操作符,就像一个转换, 它可以将块转换为Proc对象,相反也可以将Proc对象转换为一个块.
- 1
- 2
- 3
- 4
- 5
- 6
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
proc与lambda对比
- proc与lambda的return不同
- 在lambda中,return表示仅从该lambda中返回
- 在proc中,return表示从定义proc的作用域中返回
- 在lambda中,return表示仅从该lambda中返回
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- proc和lambda检查参数的方式不同
- proc中,对于多余的参数,proc则会忽略掉
- lambda中, 对于多余的参数,则会失败,同时抛出ArgumentError
方法
通过调用Objec#method()方法可以获得一个用Method对象表示的方法,之后可以使用Method#call()对其进行调用.
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
Method方法类似lambda, 但有一个重要的区别:lambda在定义它的作用域中执行,而Method对象在它自身所在的对象的作用域中执行.
Method#unbind()方法可以把一个方法跟它所绑定的对象分离,该方法再返回一个UnboundMethod对象,虽然无法直接执行UnboundMethod对象,但可以把它绑定到一个对象上,使其再次成为一个Method对象.