委托
[[Prototype]]
机制是一种存在于一个对象上的内部链接,它指向一个其他对象。
作为与面向类(也就是,OO —— 面向对象)的对比,我称这种风格的代码为 “OLOO”(objects-linked-to-other-objects(链接到其他对象的对象))
在 JavaScript 中,[[Prototype]]
机制将 对象 链接到其他 对象。无论你多么想说服自己这不是真的,JavaScript 没有像“类”那样的抽象机制。
行为委托 意味着:在某个对象(XYZ
)的属性或方法没能在这个对象(XYZ
)上找到时,让这个对象(XYZ
)为属性或方法引用提供一个委托(Task
)。
Classes
class Widget {
constructor(width,height) {
this.width = width || 50;
this.height = height || 50;
this.$elem = null;
}
render($where){
if (this.$elem) {
this.$elem.css( {
width: this.width + "px",
height: this.height + "px"
} ).appendTo( $where );
}
}
}
class Button extends Widget {
constructor(width,height,label) {
super( width, height );
this.label = label || "Default";
this.$elem = $( "<button>" ).text( this.label );
}
render($where) {
super.render( $where );
this.$elem.click( this.onClick.bind( this ) );
}
onClick(evt) {
console.log( "Button '" + this.label + "' clicked!" );
}
}
除了语法上 看起来 更好,ES6 还解决了什么?
- 不再有(某种意义上的,继续往下看!)指向
.prototype
的引用来弄乱代码。 Button
被声明为直接“继承自”(也就是extends
)Widget
,而不是需要用Object.create(..)
来替换.prototype
链接的对象,或者用__proto__
和Object.setPrototypeOf(..)
来设置它。super(..)
现在给了我们非常有用的 相对多态 的能力,所以在链条上某一个层级上的任何方法,可以引用链条上相对上一层的同名方法。第四章中有一个关于构造器的奇怪现象:构造器不属于它们的类,而且因此与类没有联系。super(..)
含有一个对此问题的解决方法 ——super()
会在构造器内部想如你期望的那样工作。class
字面语法对指定属性没有什么启发(仅对方法有)。这看起来限制了某些东西,但是绝大多数情况下期望一个属性(状态)存在于链条末端的“实例”以外的地方,这通常是一个错误和令人诧异(因为这个状态被隐含地在所有“实例”中“分享”)的。所以,也可以说class
语法防止你出现错误。extends
甚至允许你用非常自然的方式扩展内建的对象(子)类型,比如Array
或者RegExp
。在没有class .. extends
的情况下这样做一直以来是一个极端复杂而令人沮丧的任务,只有最熟练的框架作者曾经正确地解决过这个问题。
class
的坑
首先,class
语法可能会说服你 JS 在 ES6 中存在一个新的“类”机制。但不是这样。 class
很大程度上仅仅是一个既存的 [[Prototype]]
(委托)机制的语法糖!
这意味着 class
实际上不是像传统面向类的语言那样,在声明时静态地拷贝定义。如果你在“父类”上更改/替换了一个方法(有意或无意地),子“类”和/或实例将会受到“影响”,因为它们在声明时没有得到一份拷贝,它们依然都使用那个基于 [[Prototype]]
的实时委托模型。