你不知道的 JavaScript 系列上, 56 - 委托行为

[[Prototype]] 机制就是指对象中的一个内部链接引用另一个对象。如果在第一个对象上没有找到需要的属性或者方法引用,引擎就会继续在 [[Prototype]] 关联的对象上进行查找。同理,如果后者中也没有找到需要的引用就会继续查找它的 [[Prototype]] ,以此类推。这一系列对象的链接被称为 “原型链”。

换句话说,JavaScript 中这个机制的本质就是对象之间的关联关系

[[Prototype]] ,它是一种不同于类的设计模式。我们需要试着把思路从类和继承的设计模式转换到委托行为的设计模式。

类理论

class Task {
  id;

  // 构造函数 Task()
  Task(ID) {id = ID;}

  outputTask() {output(id);}
}

class XYZ inherits Task {
  label;
  XYZ(ID, Lable){ super(ID); label = Label; }
  outputTask() { super(); output(label) }
}
class ABC inherits Task { //... }

Task 是通用父类,XYZ,ABC 是子类,继承 Task。类设计模式鼓励你在继承时使用方法重写,甚至在添加新行为时通过 super 调用这个方法的原始版本。许多方法可以先抽象到父类然后再用子类进行特殊化。可以实例化子类 XYZ 的一些副本然后使用这些实例来执行任务 XYZ。这个实例会复制 Task 定义的通用行为以及 XYZ 定义的特殊行为。同理,ABC 类的实例也会复制 Task 的行为和 ABC 的行为。

但是我们试着用委托行为,而不是类来思考同样的问题。

委托理论

Task = {
  setID: function(ID) { this.id = ID; }
  outputID: function(){ console.log(this.id); }
};


// 让 XYZ 委托 Task
XYZ = Object.create(Task);


XYZ.prepareTask = function(ID, Label) {
  this.setID( ID );
  this.label = Label;
}
XYZ.outputTaskDetails = function() {
  this.outputID();
  console.log( this.label );
};
// ABC = Object.create( Task );
// ABC ... = ...

在这段代码中,Task 和 XYZ 并不是类或者函数,它们是对象。 XYZ 通过 Object.create(...) 创建,它的 [[Prototype]] 委托了 Task 对象。

相比于类或者说面向对象,我会把这种编码风格称为 “对象关联”。我们真正关心的只是 XYZ 对象委托了 Task 对象

两种方式不同之处

1、id 和 label 都是直接存储在 XYZ 上而不是 Task 上
2、在类设计中,父类和子类利用同样的方法重写,委托行为中则恰恰相反,尽量避免不同级别使用相同的命名
3、setID 方法在 XYZ 中没有找到,通过 [[Prototype]] 委托关联到 Task 继续寻找,找到了,由于调用位置触发了 this 隐式绑定规则,这个 this 仍然会绑定到 XYZ的,这正是我们想要

委托行为意味着某些对象在找不到属性或者方法引用时会把这个请求委托给另一个对象。这是一种极其强大的设计模式,和父类、子类、继承、多态等概念不同,并不是按照父类到子类的关系垂直组织的,而是通过任意方向的委托关联并排组织的