在函数式 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的更多相关文章

  1. python装饰器、继承、元类、mixin,四种給类动态添加类属性和方法的方式(一)

    介绍装饰器.继承.元类.mixin,四种給类动态添加类属性和方法的方式 有时候需要給类添加额外的东西,有些东西很频繁,每个类都需要,如果不想反复的复制粘贴到每个类,可以动态添加. # coding=u ...

  2. ES 6 装饰器与 React 高阶组件

    关于 Decorator 到底是 ES 6 引入的还是 ES 7 引入的我也不是很明白了,两种说法都有,这种问题懒得纠结了--在用的时候发现这个东西很好用,平常用处可能不大,但是结合 React 就很 ...

  3. ES6 系列之我们来聊聊装饰器

    Decorator 装饰器主要用于: 装饰类 装饰方法或属性 装饰类 @annotation class MyClass { } function annotation(target) { targe ...

  4. guxh的python笔记三:装饰器

    1,函数作用域 这种情况可以顺利执行: total = 0 def run(): print(total) 这种情况会报错: total = 0 def run(): print(total) tot ...

  5. 面向切面编程AOP,一些通用装饰器

    1.一些装饰器,可以减少重复编写.比较常用的. 用的时候函数上面加上装饰器就可以.这是一些装饰器,加在函数或者方法上,减少了很多重复代码. 除此之外工作中也用一些mixin类大幅减少代码. impor ...

  6. es7 class装饰器

    文档http://es6.ruanyifeng.com/#docs/decorator ts文档 https://www.tslang.cn/docs/handbook/decorators.html ...

  7. Javascript装饰器的妙用

    最近新开了一个Node项目,采用TypeScript来开发,在数据库及路由管理方面用了不少的装饰器,发觉这的确是一个好东西.装饰器是一个还处于草案中的特性,目前木有直接支持该语法的环境,但是可以通过 ...

  8. 巨蟒python全栈开发django5:组件&&CBV&FBV&&装饰器&&ORM增删改查

    内容回顾: 补充反向解析 Html:{% url ‘别名’ 参数 %} Views:reverse(‘别名’,args=(参数,)) 模板渲染 变量 {{ 变量名 }} 逻辑相关 {% %} 过滤器: ...

  9. TypeScript 装饰器

    装饰器(Decorators)可用来装饰类,属性,及方法,甚至是函数的参数,以改变和控制这些对象的表现,获得一些功能. 装饰器以 @expression 形式呈现在被装饰对象的前面或者上方,其中 ex ...

随机推荐

  1. vue(5)—— vue的路由插件—vue-router 常用属性方法

    前端路由 看到这里可能有朋友有疑惑了,前端也有路由吗?这些难道不应该是在后端部分操作的吗?确实是这样,但是现在前后端分离后,加上现在的前端框架的实用性,为的就是均衡前后端的工作量,所以在前端也有了路由 ...

  2. 【原】使用IDEA创建Maven工程时提示"...xxx/pom.xml already exists in VFS"的解决

    问题:使用IDEA创建Maven工程时提示"...xxx/pom.xml already exists in VFS",怎么办? 解决:如果只是删除工程,还会有这样的提示.说到底, ...

  3. ARTS打卡第四周

    Algorithm 只出现一次的数字   给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次.找出那个只出现了一次的元素. 说明: 你的算法应该具有线性时间复杂度. 你可以不使用 ...

  4. RobotFramework和Eclipse集成-安装和使用说明

    1.安装python3. 安装说明: https://www.cnblogs.com/Simple-Small/p/9179061.html 2.RF安装命令:Pip install RobotFra ...

  5. [解读REST] 3.基于网络应用的架构

    链接上文[解读REST] 2.REST用来干什么的?,上文中解释到什么是架构风格和应该以怎样的视角来理解REST(Web的架构风格).本篇来介绍一组自洽的术语,用它来描述和解释软件架构:以及列举下对于 ...

  6. Linux程序前台后台切换

    1.在Linux终端运行命令的时候,在命令末尾加上 & 符号,就可以让程序在后台运行 root@Ubuntu$ ./tcpserv01 & 2.如果程序正在前台运行,可以使用 Ctrl ...

  7. pytorch错误:Missing key(s) in state_dict、Unexpected key(s) in state_dict解决

    版权声明:本文为博主原创文章,欢迎转载,并请注明出处.联系方式:460356155@qq.com 在模型训练时加上: model = nn.DataParallel(model)cudnn.bench ...

  8. UA大全

    ####PC端UA #Opera "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Ch ...

  9. springboot 注册dao层 service 层

    可以使用三种注解来引入DAO层的接口到spring容器中.1.@Mapper,写在每一个DAO层接口上,如下: 2.@MapperScan和@ComponentScan两者之一.前者的意义是将指定包中 ...

  10. 面试题(转载csdn)

    转自https://blog.csdn.net/linzhiqiang0316/article/details/80473906 相关概念 面向对象的三个特征 封装,继承,多态,这个应该是人人皆知,有 ...