原型即对象(以及认识这点的重要性)
原文链接:http://raganwald.com/2015/06/10/mixins.html
预备知识:该文章假设读者熟悉JavaScript 对象,知道原型是如何给对象定义行为的,知道构造函数是什么,知道构造器的 prototype 属性和它创建的对象之间是如何产生联系的。对 ES2015 语法有些了解也将有所帮助。
一直以来,我们可以这样创建一个 JavaScript 类:
function Person (first, last) { this.rename(first, last); } Person.prototype.fullName = function fullName () { return this.firstName + " " + this.lastName; }; Person.prototype.rename = function rename (first, last) { this.firstName = first; this.lastName = last; return this; }
Person 是一个构造函数,同时也是一个类,当然是 JavaScript 世界中的“类”。
ECMAScript 2015 提供了 class 关键字以及“紧凑的方法记号”,它们是编写函数,并给其 prototype 属性赋值方法的语法糖(虽然实际情况更复杂,但和此处不相关)。所以我们现在可以如下编写 Person 类:
class Person { constructor (first, last) { this.rename(first, last); } fullName () { return this.firstName + " " + this.lastName; } rename (first, last) { this.firstName = first; this.lastName = last; return this; } };
不错。但究其本质,你仍然不过编写了一个名为 Person 的构造函数,并且 Person.prototype 是这样的一个对象:
{ fullName: function fullName () { return this.firstName + " " + this.lastName; }, rename: function rename (first, last) { this.firstName = first; this.lastName = last; return this; } }
原型即对象
如果我们想改变一个 JS 对象的行为,可以通过添加、删除,或修改作为对象属性的函数,它们就是对象的方法。这和大多数基于类的语言不同,这些语言往往提供特殊的语法形式来定义方法(如 Ruby 的 def)。
JavaScript 中的原型不过是普通的对象,正由于它们是普通的对象,我们便可以对原型的方法进行增删改,即对绑定到原型对象作为其属性的那些函数进行操作。
这也正是上面 ES5 代码所干的事,并且 ES6 的 class 语法“去语法糖”后也会得到相同代码。
原型即普通对象的概念意味着我们可以把原型当普通对象对待。例如,除了将函数一个个绑定到原型上,还可以使用 Object.assign 一次性绑定。
function Person (first, last) { this.rename(first, last); } Object.assign(Person.prototype, { fullName: function fullName () { return this.firstName + " " + this.lastName; }, rename: function rename (first, last) { this.firstName = first; this.lastName = last; return this; } })
当然,我们也可以使用紧凑方法声明语法
function Person (first, last) { this.rename(first, last); } Object.assign(Person.prototype, { fullName () { return this.firstName + " " + this.lastName; }, rename (first, last) { this.firstName = first; this.lastName = last; return this; } })
mixins
由于 class 语法最终转化为构造函数和原型的形式,我们可以混合使用两种技术:
class Person { constructor (first, last) { this.rename(first, last); } fullName () { return this.firstName + " " + this.lastName; } rename (first, last) { this.firstName = first; this.lastName = last; return this; } }; Object.assign(Person.prototype, { addToCollection (name) { this.collection().push(name); return this; }, collection () { return this._collected_books || (this._collected_books = []); } })
上例中我们将书籍收集相关的方法糅合到了 Person 类上。这点非常不错,因为我们得以让代码具有 point-free 风格,同时命名方面也很棒。
const BookCollector = { addToCollection (name) { this.collection().push(name); return this; }, collection () { return this._collected_books || (this._collected_books = []); } }; class Person { constructor (first, last) { this.rename(first, last); } fullName () { return this.firstName + " " + this.lastName; } rename (first, last) { this.firstName = first; this.lastName = last; return this; } };
Object.assign(Person.prototype, BookCollector);
只要愿意,这一过程可一直进行
const BookCollector = { addToCollection (name) { this.collection().push(name); return this; }, collection () { return this._collected_books || (this._collected_books = []); } }; const Author = { writeBook (name) { this.books().push(name); return this; }, books () { return this._books_written || (this._books_written = []); } }; class Person { constructor (first, last) { this.rename(first, last); } fullName () { return this.firstName + " " + this.lastName; } rename (first, last) { this.firstName = first; this.lastName = last; return this; } }; Object.assign(Person.prototype, BookCollector, Author);
为什么 mixins 可能有用武之地
通过基础功能(Person)和 mixins(BookCollector 和 Author)相结合的方式创建类可获得几点好处。
首先,有时候无法将功能完美分解成树形结构。书的作者有时是公司,而不是人。并且古籍书店也可能像藏书家一样收集图书。
类似于 BookCollector 或 Author 的 mixin 可被糅合到多个类上。试图使用“继承”来实现功能有时不太确切。
另一个好处不容易从玩具用例上看出,但在实际项目中类的定义可以变得非常庞杂。即使某个 mixin 没有在多个类中使用,将一个大类分解成多个 mixin 也有助于实现“单一责任原则”。每个 mixin 可以只做一件事。这使得代码变得容易理解和测试。
为什么要知道这些
还有其他方法可以分解类的功能(如委托和组合),但这里想说的是如果我们想使用 mixin,这是非常容易的,因为 JavaScript 并没有庞杂的 OOP 机制对程序施加进行严格的模型限制。
例如在 Ruby 中,mixins 用起来也很方便,这得益于与生俱来的 modules 特性。但在其他面向对象语言中,mixins 就没那么顺手了,因为类系统没有相应的支持,并且 mixin 对元编程也不是很友好。
JavaScript 使用简单部件(对象,函数以及属性)实现 OOP 的这一选择促进了新思潮的发展。

浙公网安备 33010602011771号