You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
constwithSpeak=(targetClass)=>{constprototype=targetClass.prototype;prototype.speak=function(){console.log('I can speak ',this.language);}}
@withSpeakclassStudent{constructor(language){this.language=language;}}conststudent1=newStudent('Chinese');conststudent2=newStudent('English');student1.speak();// I can speak Chinesestudent2.speak();// I can speak Englist
classParent1{p1(){console.log('this is parent1')}}classParent2{p2(){console.log('this is parent2')}}classParent3{p3(){console.log('this is parent3')}}
@mixin(Parent1,Parent2,Parent3)classChild{c1=()=>{console.log('this is child')}}constchild=newChild();console.log(child);
constvalidate=(type)=>(target,name)=>{if(typeoftarget[name]!==type){thrownewError(`attribute ${name} must be ${type} type`)}}classForm{
@validate('string')staticname=111// Error: attribute name must be ${type} type}
如果你觉得对属性一个个手动去校验太过麻烦,也可以通过编写校验规则,来对整个类进行校验。
construles={name: 'string',password: 'string',age: 'number'}constvalidator=rules=>targetClass=>{returnnewProxy(targetClass,{construct(target,args){constobj=newtarget(...args);for(let[name,type]ofObject.entries(rules)){if(typeofobj[name]!==type){thrownewError(`${name} must be ${type}`)}}returnobj;}})}
@validator(rules)classPerson{name='tom'password='123'age='21'}constperson=newPerson();
1. 前言
装饰器是最新的 ECMA 中的一个提案,是一种与类(
class
)相关的语法,用来注释或修改类和类方法。装饰器在 Python 和 Java 等语言中也被大量使用。装饰器是实现 AOP(面向切面)编程的一种重要方式。下面是一个使用装饰器的简单例子,这个
@readonly
可以将count
属性设置为只读。可以看出来,装饰器大大提高了代码的简洁性和可读性。由于浏览器还未支持装饰器,为了让大家能够正常看到效果,这里我使用 Parcel 进行了一下简单的配置,可以去 clone 这个仓库后再来运行本文涉及到的所有例子,仓库地址:learn es6
2. 装饰器模式
在开始讲解装饰器之前,先从经典的装饰器模式说起。装饰器模式是一种结构型设计模式,它允许向一个现有的对象添加新的功能,同时又不改变其结构,是作为对现有类的一个包装。
一般来说,在代码设计中,我们应当遵循「多用组合,少用继承」的原则。通过装饰器模式动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
2.1 一个英雄联盟的例子
下班回去和朋友愉快地开黑,当我正在用亚索「面对疾风吧」的时候,突然想到,如果让我设计亚索英雄,我该怎么实现呢?
我灵光一闪,那肯定会先设计一个英雄的类。
然后,再实现一个 Yasuo 的类来继承这个
Hero
类。我还在想这个问题的时候,队友已经打了大龙,我的亚索身上就出现了大龙
buff
的印记。我突然想到,那该怎么给英雄增加大龙buff
呢?那增加个大龙buff
的属性不行吗?当然不太行,要知道,英雄联盟里面的大龙
buff
是会增加收益的。嗯,聪明的我已经想到办法了,再继承一次不就好了吗?
厉害了,但是如果亚索身上还有其他
buff
呢?毕竟LOL
里面是有红buff
、蓝buff
、大龙buff
等等存在,那岂不是有多少种就要增加多少个类吗?可以换种思路来思考这个问题,如果把
buff
当做我们身上的衣服。在不同的季节,我们会换上不同的衣服,到了冬天,甚至还会叠加多件衣服。当buff
消失了,就相当于把这件衣服脱了下来。如下图所示:衣服对人来说起到装饰的作用,
buff
对于亚索来说也只是增强效果。那么,你是不是有思路了呢?没错,可以创建
Buff
类,传入英雄类后获得一个新的增强后的英雄类。定义好所有的
buff
类之后,就可以直接套用到英雄身上,这样看起来是不是清爽了很多呢?这种写法看起来很像函数组合。3. ES7 装饰器
decorator
(装饰器)是 ES7 中的一个提案,目前处于stage-2
阶段,提案地址:JavaScript Decorators。装饰器与之前讲过的函数组合(
compose
)以及高阶函数很相似。装饰器使用@
作为标识符,被放置在被装饰代码前面。在其他语言中,早就已经有了比较成熟的装饰器方案。3.1 Python 中的装饰器
先来看一下 Python 中的一个装饰器的例子:
这个
auth
装饰器是通过检查cookie
来判断用户是否登录的。auth
函数是一个高阶函数,它接收了一个func
函数作为参数,返回了一个新的inner
函数。在
inner
函数中进行cookie
的检查,由此来判断是跳回登录页面还是继续执行func
函数。在所有需要权限验证的函数上,都可以使用这个
auth
装饰器,很简洁明了且无侵入。3.2 JavaScript 装饰器
JavaScript 中的装饰器和 Python 的装饰器类似,依赖于
Object.defineProperty
,一般是用来装饰类、类属性、类方法。使用装饰器可以做到不直接修改代码,就实现某些功能,做到真正的面向切面编程。这在一定程度上和
Proxy
很相似,但使用起来比Proxy
会更加简洁。3.3 类装饰器
装饰类的时候,装饰器方法一般会接收一个目标类作为参数。下面是一个给目标类增加静态属性
test
的例子:除了可以修改类本身,还可以通过修改原型,给实例增加新属性。下面是给目标类增加
speak
方法的例子:利用高阶函数的属性,还可以给装饰器传参,通过参数来判断对类进行什么处理。
如果你经常编写 react-redux 的代码,那么也会遇到需要将
store
中的数据映射到组件中的情况。connect
是一个高阶组件,它接收了两个函数mapStateToProps
和mapDispatchToProps
以及一个组件App
,最终返回了一个增强版的组件。有了装饰器之后,connect 的写法可以变得更加优雅。
3.4 类属性装饰器
类属性装饰器可以用在类的属性、方法、
get/set
函数中,一般会接收三个参数:Object.defineProperty
使用类属性装饰器可以做到很多有意思的事情,比如最开始举的那个
readonly
的例子:还可以用来统计一个函数的执行时间,以便于后期做一些性能优化。
在 react 知名的状态管理库 mobx 中,也通过装饰器来将类属性置为可观察属性,以此来实现响应式编程。
3.5 装饰器组合
如果你想要使用多个装饰器,那么该怎么办呢?装饰器是可以叠加的,根据离被装饰类/属性的距离来依次执行。
除此之外,在装饰器的提案中,还出现了一种组合了多种装饰器的装饰器例子。目前还没见到被使用。
通过使用
decorator
来声明一个组合装饰器xyz
,这个装饰器组合了多种装饰器。和下面这种写法是一样的。
4. 装饰器可以做哪些有意思的事情?
4.1 多重继承
在实现 JavaScript 多重继承的时候,可以使用
mixin
的方式,这里结合装饰器甚至还能更进一步简化mixin
的使用。mixin
方法将会接收一个父类列表,用其装饰目标类。我们理想中的用法应该是这样:和之前实现多重继承的时候实现原理一致,只需要拷贝父类的原型属性和实例属性就可以实现了。
这里创建了一个新的
Mixin
类,来将mixins
和targetClass
上面的所有属性都拷贝过去。我们来测试一下这个
mixin
方法是否能够正常工作吧。最终在浏览器中打印出来的
child
对象是这样的,证明了这个mixin
是可以正常工作的。也许你会问,为什么还要多创建一个多余的
Mixin
类呢?为什么不能直接修改targetClass
的constructor
呢?前面不是讲过Proxy
可以拦截constructor
吗?恭喜你,你已经想到了
Proxy
的一种使用场景。没错,这里用Proxy
的确会更加优雅。4.2 防抖和节流
以往我们在频繁触发的场景下,为了优化性能,经常会使用到节流函数。下面以 React 组件绑定滚动事件为例子:
在组件中绑定事件需要注意应当在组件销毁的时候进行解绑。而由于节流函数返回了一个新的匿名函数,所以为了之后能够有效解绑,不得不将这个匿名函数存起来,以便于之后使用。
但是在有了装饰器之后,我们就不必在每个绑定事件的地方都手动设置
throttle
方法,只需要在scroll
函数添加一个throttle
的装饰器就行了。使用起来比原来要简洁很多。
而实现防抖(
debounce
)函数装饰器和节流函数类似,这里也不再多说。如果对节流和防抖函数比较感兴趣,那么可以去阅读一下这篇文章:函数节流与函数防抖
4.3 数据格式验证
通过类属性装饰器来对类的属性进行类型的校验。
如果你觉得对属性一个个手动去校验太过麻烦,也可以通过编写校验规则,来对整个类进行校验。
4.4 core-decorators.js
core-decorators
是一个封装了常用装饰器的 JS 库,它归纳了下面这些装饰器(只列举了部分)。this
,告别箭头函数和bind
5. 总结
装饰器虽然还属于不稳定的语法,但在很多框架中都已经广泛使用,例如 Angular、Nestjs 等等,和 Java 中的注解用法非常相似。
装饰器在 TypeScript 中结合反射后还有一些更高级的应用,下篇文章会进行深入讲解。
推荐阅读:
The text was updated successfully, but these errors were encountered: