使用 ES.later 的装饰器作为 mixin
Posted averyby
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用 ES.later 的装饰器作为 mixin相关的知识,希望对你有一定的参考价值。
在函数式 mixin 中,我们讨论了将功能糅合进 javascript 类中,从而改变类。我们发现这种方式对于已经在现有代码中使用的类来说存在缺陷,但是对于从头构建一个全新的类不失为一个绝好的技术。当把 mixin 用于类的创建时,mixin 可以使我们得以将类的功能分解成更小的单元,每个小单元专注于自己的事,并按需在多个类之间共享。
让我们回顾一下生成函数式 mixin 的辅助函数,姑且叫它 mixin:
function mixin (behaviour, sharedBehaviour = {}) { const instanceKeys = Reflect.ownKeys(behaviour); const sharedKeys = Reflect.ownKeys(sharedBehaviour); const typeTag = Symbol(‘isa‘); function _mixin (target) { for (let property of instanceKeys) Object.defineProperty(target, property, { value: behaviour[property] }); Object.defineProperty(target, typeTag, { value: true }); return target; } for (let property of sharedKeys) Object.defineProperty(_mixin, property, { value: sharedBehaviour[property], enumerable: sharedBehaviour.propertyIsEnumerable(property) }); Object.defineProperty(_mixin, Symbol.hasInstance, { value: (i) => !!i[typeTag] }); return _mixin; }
上面的 mixin 函数返回一个函数,用于将行为糅合进目标对象,该目标对象可以是类原型或者一个独立的对象。上面的代码片段还提供了一个便利功能,用于给返回的 _mixin 函数自身添加静态的、多实例共享的属性,甚至对 hasInstance 做了简单处理,以便 instanceof 操作符能正常工作。
下面我们将它应用在类的原型上:
const BookCollector = mixin({ 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; } }; BookCollector(Person.prototype); const president = new Person(‘Barak‘, ‘Obama‘) president .addToCollection("JavaScript Allongé") .addToCollection("Kestrels, Quirky Birds, and Hopeless Egocentricity"); president.collection() //=> ["JavaScript Allongé","Kestrels, Quirky Birds, and Hopeless Egocentricity"]
只针对类的 mixin
我们的 mixin 能支持任何目标对象,这点很不错,不过我们也可以使其只针对类:
function mixin (behaviour, sharedBehaviour = {}) { const instanceKeys = Reflect.ownKeys(behaviour); const sharedKeys = Reflect.ownKeys(sharedBehaviour); const typeTag = Symbol(‘isa‘); function _mixin (clazz) { for (let property of instanceKeys) Object.defineProperty(clazz.prototype, property, { value: behaviour[property], writable: true }); Object.defineProperty(clazz.prototype, typeTag, { value: true }); return clazz; } for (let property of sharedKeys) Object.defineProperty(_mixin, property, { value: sharedBehaviour[property], enumerable: sharedBehaviour.propertyIsEnumerable(property) }); Object.defineProperty(_mixin, Symbol.hasInstance, { value: (i) => !!i[typeTag] }); return _mixin; }
这一版的 _mixin 函数将实例行为糅合进类的原型,虽然没有支持任何对象那般的灵活,但是使用起来更方便:
const BookCollector = mixin({ 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; } }; BookCollector(Person); const president = new Person(‘Barak‘, ‘Obama‘) president .addToCollection("JavaScript Allongé") .addToCollection("Kestrels, Quirky Birds, and Hopeless Egocentricity"); president.collection() //=> ["JavaScript Allongé","Kestrels, Quirky Birds, and Hopeless Egocentricity"]
至此,非常不错,但总觉得有点马后炮的感觉。我们可以利用一下“类即表达式”这一事实:
const BookCollector = mixin({ addToCollection (name) { this.collection().push(name); return this; }, collection () { return this._collected_books || (this._collected_books = []); } }); const Person = BookCollector(class { constructor (first, last) { this.rename(first, last); } fullName () { return this.firstName + " " + this.lastName; } rename (first, last) { this.firstName = first; this.lastName = last; return this; } });
这样一来在结构上更漂亮了,因为我们将行为糅合这一过程和类声明写在了一个表达式里,就不会看起来像先创建类,再将其他东西添加到类里一样。
然而(总是能找到不完美之处),我们的模式具有三个不同的元素(被绑定的变量名,mixin,以及声明的类)。如果我们希望糅合进更多功能,不得不像这样嵌套函数调用:
const Author = mixin({ writeBook (name) { this.books().push(name); return this; }, books () { return this._books_written || (this._books_written = []); } }); const Person = Author(BookCollector(class { // ... }));
有些人觉得这非常明了,因为他们觉得如此简单的表达式充分体现了 JavaScript 的简洁性。并且 mixin 的内部实现简单易读,只要你理解原型,便能理解该表达式的含义。
但另外一些人希望编程语言能给他们提供“救赎”,他们只需单独学习语法带来的抽象。目前,JavaScript 还没有将功能糅合进类的相关“魔法”。但如果有的话会是什么样呢?
类装饰器
其实在 ECMAScript 2015 之后的对 JavaScript 的主要修订中,有一个备受瞩目的提案——将 python 风格的类装饰器加入 JavaScript 中。
装饰器就是一个作用于类的函数。这里有一个例子,其所基于的装饰器实现符合上述提案的要求:
function annotation(target) { // Add a property on target target.annotated = true; } @annotation class MyClass { // ... } MyClass.annotated //=> true
上例中,annotation 是一个类装饰器,它接受一个类作为参数。该装饰器函数可以做任何事情,包括修改类本身或类的原型。如果装饰器函数没有返回值,参数类的名字就被绑定到修改后的类。
想让某个类被函数“修饰”,只需要在某个表达式前添加 @ 符号,而该表达式需要被解读成装饰器函数。
O__O “…,你是说能够修改类的函数吗?那我们就试试吧:
const BookCollector = mixin({ addToCollection (name) { this.collection().push(name); return this; }, collection () { return this._collected_books || (this._collected_books = []); } }); @BookCollector 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; } }; const president = new Person(‘Barak‘, ‘Obama‘) president .addToCollection("JavaScript Allongé") .addToCollection("Kestrels, Quirky Birds, and Hopeless Egocentricity"); president.collection() //=> ["JavaScript Allongé","Kestrels, Quirky Birds, and Hopeless Egocentricity"]
你也可以使用装饰器添加多个行为:
const BookCollector = mixin({ addToCollection (name) { this.collection().push(name); return this; }, collection () { return this._collected_books || (this._collected_books = []); } }); const Author = mixin({ writeBook (name) { this.books().push(name); return this; }, books () { return this._books_written || (this._books_written = []); } }); @BookCollector @Author 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; } };
如果你想使用装饰器模拟“纯函数式复合”,这也是简单易行的惯常模式:
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; } }; @BookCollector @Author class BookLover extends Person {};
类装饰器提供了一个紧凑的、魔法般的语法,该语法和类的创建紧密关联。它的引入确实需要你学习新的语法,但不同的语法实现不同的事情让代码理解起来更容易,比如 @foo 表示装饰器,bar(…) 表示函数调用,这不失为成功之举。
使用装饰器
装饰器提案尚未被正式通过,然而将装饰器语法转译成 ES5 语法存在多种可用实现。这篇文章中的例子都是使用 Babel 转译的。
如果你倾向于使用语法糖让代码具有声明式编程的形态,那就将 mixin 函数和 ES.later 的类装饰器结合起来使用吧。
以上是关于使用 ES.later 的装饰器作为 mixin的主要内容,如果未能解决你的问题,请参考以下文章
Typescript:如何在实例成员属性(mixin)中使用方法装饰器?