对象模型

  • Module#constants 可以获取当前范围内所有的常量

  • Module.constants 获取当前程序中所有顶层的常量

  • Module.nesting 可以得到当前代码所在的路径

  • ClassName.ancestors 可以获取类的祖先链

requireload 的区别:

  1. load 用于加载代码,require 用于导入类库

  2. require 对每个文件只加载一次,而 load 每次调用时都会再次运行所加载的文件

  3. load 有副作用,常量(包括类)有可能污染当前程序的命名空间,load('xxx.rb', true) 才约等于 require 'xxx.rb'

方法查找口诀:向右一步,再向上

includeprepend 的异同:

  1. 共同点是,都将模块加入包含者的祖先链

  2. 不同点是,include 引入的模块在包含者的上面,prepend 引入的模块在包含者的下面

每个模块都只会引入一次,如果该模块已经存在于祖先链,则再次引入不会有任何影响

私有规则:

  1. 如果调用方法的接收者不是自己,那就必须明确指明接收者

  2. 私有方法只能通过隐性的接收者调用

顶层上下文的 selfmain 对象

在类和模块的定义中,在任何方法定义以外,self 就是类或模块本身

refine 和打开类的异同:

  1. 两者都可以打开类定义,给类添加新的方法

  2. 打开类全局有效,而 refine 只在 using 以后并在 using 作用域以内才有效

小结:

  • 对象由一组实例变量和类的引用组成

  • 对象的方法存在于对象所属的类中

  • 类本身是 Class 类的对象

  • Class 类是 Module 的子类,类只是比模块多了 newsuperclass 方法

  • 类祖先链的最顶端是 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_methodModule#remove_method 的异同:

  1. 相同点都是删除方法

  2. undef_method 删除所有的方法,包括继承来的

  3. remove_method 只删除自己的方法,继承来的方法保留

代码块

可以用 block_given? 检测方法调用是否有传递代码块,代码块也就是闭包

全局变量可以在任何作用域中访问和修改

三个作用域门:classmoduledef

穿越作用域门:

  • Class.new 穿越 class 作用域

  • Module.new 穿越 module 作用域

  • define_method 穿越 def 作用域

上下文探针:

  • instance_val 在一个对象的上下文中执行代码块

  • instance_execinstance_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 转回代码块

ProcLambda 的区别:

  • lambda 方法创建的 Proc 称为 lambda,而用其他方式创建的则称为 proc(可以用 Proc#lambda? 检测是不是 lambda

  • lambda 里面的 return 仅从该 lambda 中返回,而 proc 里面的 return 却是从定义该 proc 的作用域中返回

  • 在参数适应能力上,proc 适应能力更强,而 lambda 对传递的参数个数要求严格

  • 综合 return 和参数,lambda 的表现更像真实的方法:严格检查参数个数,只从自己的代码区域返回

ProcLambda 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#unbindModule#instance_method 将一个方法变成自由方法,也可以用 UnboundMethod#bindModule#define_method 再次将自由方法绑定到某个对象

类定义

Module#class_eval 可以在不使用 class 关键字的情况下修改当前类

Module#module_evalModule#class_eval 的别名

class_evalmodule_eval 同样有孪生方法 class_execmodule_exec

类变量和类实例变量的区别:

  • 类变量以 @@ 打头,而类实例变量以 @ 打头

  • 类变量可以被子类或类的实例所使用

  • 类变量的值可以在类定义以外的区域被修改,尽管会得到一个警告

单件方法:在单个对象上定义,且只对单个对象生效的方法

可以使用 def obj.xxx 定义单件方法,也可以使用 Object#define_singleton_method 方法来定义,可以用 Object.singleton_methods 获取某个对象全部的单件方法

类方法的实质:类方法其实就是一个类的单件方法

单件类:

  • 保存一个对象所有的单件方法

  • 只有一个实例,不能被继承

  • 在类继承链中不可见,只能用 Object#singleton_classclass<< 来获取

  • 如果将单件类从继承链标识出来,它是在该对象所属类下方的,也就是对象的方法调用是先查找的单件类,再查找的类本身

单件类的超类是该单件类所归属类的超类的单件类,正因为如此,子类才可以调用到父类的类方法,因为类方法就是类的单件方法,保存在单件类之中,而方法调用会先查找单件类

定义类方法的三种方式:

def Hello.hello
  # xxx
end

class Hello
  def self.hello
    # xxx
  end
end

class Hello
  class <<self
    def hello
      # xxx
    end
  end
end

includeextend

  • 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

编写环绕别名的三个步骤:

  1. 给方法定义一个别名

  2. 重定义这个方法

  3. 在新方法中调用老方法

编写代码的代码

Binding:一个用对象表示的完整作用域,可以用 Kernel#binding 方法捕获当前作用域,然后可以通过 eval 方法在这个绑定对象所携带的作用域中执行代码

TOPLEVEL_BINDING:表示顶级作用域的绑定对象,可以在程序的任何地方访问到

全局变量 $SAFE 用于控制安全级别,默认为 0,可设置值为 03

Object#instance_variable_setObject#instance_variable_get 可用来操作实例变量


Ruchee
199 声望3 粉丝

Web 开发工程师


引用和评论

0 条评论