对象模型
Module#constants
可以获取当前范围内所有的常量Module.constants
获取当前程序中所有顶层的常量Module.nesting
可以得到当前代码所在的路径ClassName.ancestors
可以获取类的祖先链
require
和 load
的区别:
load
用于加载代码,require
用于导入类库require
对每个文件只加载一次,而load
每次调用时都会再次运行所加载的文件load
有副作用,常量(包括类)有可能污染当前程序的命名空间,load('xxx.rb', true)
才约等于require 'xxx.rb'
方法查找口诀:向右一步,再向上
include
和 prepend
的异同:
共同点是,都将模块加入包含者的祖先链
不同点是,
include
引入的模块在包含者的上面,prepend
引入的模块在包含者的下面
每个模块都只会引入一次,如果该模块已经存在于祖先链,则再次引入不会有任何影响
私有规则:
如果调用方法的接收者不是自己,那就必须明确指明接收者
私有方法只能通过隐性的接收者调用
顶层上下文的 self
是 main
对象
在类和模块的定义中,在任何方法定义以外,self
就是类或模块本身
refine
和打开类的异同:
两者都可以打开类定义,给类添加新的方法
打开类全局有效,而
refine
只在using
以后并在using
作用域以内才有效
小结:
对象由一组实例变量和类的引用组成
对象的方法存在于对象所属的类中
类本身是
Class
类的对象Class
类是Module
的子类,类只是比模块多了new
和superclass
方法类祖先链的最顶端是
BasicObject
实例变量永远被认为是
self
的实例变量
方法
动态派发:person.hello('Ruchee')
与 person.send(:hello, 'Ruchee')
等价
可以用 send
调用任何方法,包括私有方法
可以用 public_send
限制对私有方法的调用
动态方法:使用 define_method
来定义方法
幽灵方法:使用 method_missing
截取所有未定义的方法调用
动态代理:用幽灵方法捕获方法调用,并转发给另外一个对象
每次覆写 method_missing
方法时,最好也同时覆写 respond_to_missing?
方法,以使得可以用 respond_to_missing?
来正确检测是不是存在某幽灵方法
对于方法,找不到时会被 method_missing
截取,而对于常量,找不到时同样会被一个叫 const_missing
的方法截取
白板类:拥有极少方法的类,可以避免祖先链中存在同名方法而导致 method_missing
调用不到的问题
Module#undef_method
和 Module#remove_method
的异同:
相同点都是删除方法
undef_method
删除所有的方法,包括继承来的remove_method
只删除自己的方法,继承来的方法保留
代码块
可以用 block_given?
检测方法调用是否有传递代码块,代码块也就是闭包
全局变量可以在任何作用域中访问和修改
三个作用域门:class
、module
、def
穿越作用域门:
用
Class.new
穿越class
作用域用
Module.new
穿越module
作用域用
define_method
穿越def
作用域
上下文探针:
instance_val
在一个对象的上下文中执行代码块instance_exec
和instance_val
功能基本一致,但允许给代码块传入参数
延迟执行:将代码块转成 proc
存储,后续再用 Proc#call
调用执行
创建 proc
的几种方法,以下各代码段作用等价
inc = Proc.new { |x| x + 1 }
puts inc.call(10)
inc = lambda { |x| x + 1 }
puts inc.call(10)
inc = ->x { x + 1 }
puts inc.call(10)
def create_proc (&block)
block
end
inc = create_proc { |x| x + 1 }
puts inc.call(10)
def test (&inc)
inc.call(10)
end
puts test { |x| x + 1 }
def test
yield 10
end
puts test { |x| x + 1 }
class Test
def inc (x)
x + 1
end
end
inc = Test.new.method :inc
puts inc.call(10)
定义方法时,最后一个参数以 &
打头可以将传递给该方法的代码块转成 proc
;而调用方法时,在保存 proc
的变量名前加 &
可以将 proc
转回代码块
Proc
与 Lambda
的区别:
用
lambda
方法创建的Proc
称为lambda
,而用其他方式创建的则称为proc
(可以用Proc#lambda?
检测是不是lambda
)lambda
里面的return
仅从该lambda
中返回,而proc
里面的return
却是从定义该proc
的作用域中返回在参数适应能力上,
proc
适应能力更强,而lambda
对传递的参数个数要求严格综合
return
和参数,lambda
的表现更像真实的方法:严格检查参数个数,只从自己的代码区域返回
Proc
和 Lambda
return
的差异代码示例:
def test
l = -> { return 10 }
l.call * 2
end
puts test # 输出 20
def test
p = Proc.new { return 10 }
p.call * 2 # 这一行代码压根执行不到,前一行就已经返回了
end
puts test # 输出 10
可以用 Method#unbind
或 Module#instance_method
将一个方法变成自由方法,也可以用 UnboundMethod#bind
或 Module#define_method
再次将自由方法绑定到某个对象
类定义
Module#class_eval
可以在不使用 class
关键字的情况下修改当前类
Module#module_eval
是 Module#class_eval
的别名
class_eval
和 module_eval
同样有孪生方法 class_exec
和 module_exec
类变量和类实例变量的区别:
类变量以
@@
打头,而类实例变量以@
打头类变量可以被子类或类的实例所使用
类变量的值可以在类定义以外的区域被修改,尽管会得到一个警告
单件方法:在单个对象上定义,且只对单个对象生效的方法
可以使用 def obj.xxx
定义单件方法,也可以使用 Object#define_singleton_method
方法来定义,可以用 Object.singleton_methods
获取某个对象全部的单件方法
类方法的实质:类方法其实就是一个类的单件方法
单件类:
保存一个对象所有的单件方法
只有一个实例,不能被继承
在类继承链中不可见,只能用
Object#singleton_class
或class<<
来获取如果将单件类从继承链标识出来,它是在该对象所属类下方的,也就是对象的方法调用是先查找的单件类,再查找的类本身
单件类的超类是该单件类所归属类的超类的单件类,正因为如此,子类才可以调用到父类的类方法,因为类方法就是类的单件方法,保存在单件类之中,而方法调用会先查找单件类
定义类方法的三种方式:
def Hello.hello
# xxx
end
class Hello
def self.hello
# xxx
end
end
class Hello
class <<self
def hello
# xxx
end
end
end
include
和 extend
include
是在类层面包含模块,所包含模块的方法将成为类的实例方法extend
是在单件类层面包含模块,所包含模块的方法将成为单件类的实例方法,也就是类的类方法
extend
实例:
module HelloModule
def hello
"Hello, World"
end
end
class HelloClass
# 写法1
class <<self
include HelloModule
end
# 写法2
extend HelloModule
end
puts HelloClass.hello
编写环绕别名的三个步骤:
给方法定义一个别名
重定义这个方法
在新方法中调用老方法
编写代码的代码
Binding
:一个用对象表示的完整作用域,可以用 Kernel#binding
方法捕获当前作用域,然后可以通过 eval
方法在这个绑定对象所携带的作用域中执行代码
TOPLEVEL_BINDING
:表示顶级作用域的绑定对象,可以在程序的任何地方访问到
全局变量 $SAFE
用于控制安全级别,默认为 0
,可设置值为 0
到 3
Object#instance_variable_set
和 Object#instance_variable_get
可用来操作实例变量
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。