JS装饰器函数用法总结
在ES6中增加了对类对象的相关定义和操作(比如class和extends),这就使得我们在多个不同类之间共享或者扩展一些方法或者行为的时候,变得并不是那么优雅。这个时候,我们就需要一种更优雅的方法来帮助我们完成这些事情。
什么是装饰器
Python的装饰器
在面向对象(OOP)的设计模式中,decorator被称为装饰模式。OOP的装饰模式需要通过继承和组合来实现,而Python除了能支持OOP的decorator外,直接从语法层次支持decorator。
如果你熟悉python的话,对它一定不会陌生。那么我们先来看一下python里的装饰器是什么样子的吧:
defdecorator(f): print"mydecorator" returnf @decorator defmyfunc(): print"myfunction" myfunc() #mydecorator #myfunction
这里的@decorator就是我们说的装饰器。在上面的代码中,我们利用装饰器给我们的目标方法执行前打印出了一行文本,并且并没有对原方法做任何的修改。代码基本等同于:
defdecorator(f): defwrapper(): print"mydecorator" returnf() returnwrapper defmyfunc(): print"myfunction" myfunc=decorator(myfuc)
通过代码我们也不难看出,装饰器decorator接收一个参数,也就是我们被装饰的目标方法,处理完扩展的内容以后再返回一个方法,供以后调用,同时也失去了对原方法对象的访问。当我们对某个应用了装饰以后,其实就改变了被装饰方法的入口引用,使其重新指向了装饰器返回的方法的入口点,从而来实现我们对原函数的扩展、修改等操作。
ES7的装饰器
ES7中的decorator同样借鉴了这个语法糖,不过依赖于ES5的Object.defineProperty方法。
Object.defineProperty
Object.defineProperty()方法会直接在一个对象上定义一个新属性,或者修改一个对象的现有属性,并返回这个对象。
该方法允许精确添加或修改对象的属性。通过赋值来添加的普通属性会创建在属性枚举期间显示的属性(for...in或Object.keys方法),这些值可以被改变,也可以被删除。这种方法允许这些额外的细节从默认值改变。默认情况下,使用Object.defineProperty()添加的属性值是不可变的。
语法
Object.defineProperty(obj,prop,descriptor)
- obj:要在其上定义属性的对象。
- prop:要定义或修改的属性的名称。
- descriptor:将被定义或修改的属性描述符。
- 返回值:被传递给函数的对象。
在ES6中,由于Symbol类型的特殊性,用Symbol类型的值来做对象的key与常规的定义或修改不同,而Object.defineProperty是定义key为Symbol的属性的方法之一。
属性描述符
对象里目前存在的属性描述符有两种主要形式:数据描述符和存取描述符。
数据描述符是一个具有值的属性,该值可能是可写的,也可能不是可写的。
- 存取描述符是由getter-setter函数对描述的属性。
- 描述符必须是这两种形式之一;不能同时是两者。
数据描述符和存取描述符均具有以下可选键值:
configurable
当且仅当该属性的configurable为true时,该属性描述符才能够被改变,同时该属性也能从对应的对象上被删除。默认为false。
enumerable
enumerable定义了对象的属性是否可以在for...in循环和Object.keys()中被枚举。
当且仅当该属性的enumerable为true时,该属性才能够出现在对象的枚举属性中。默认为false。
数据描述符同时具有以下可选键值:
value
该属性对应的值。可以是任何有效的JavaScript值(数值,对象,函数等)。默认为undefined。
writable
当且仅当该属性的writable为true时,value才能被赋值运算符改变。默认为false。
存取描述符同时具有以下可选键值:
get
一个给属性提供getter的方法,如果没有getter则为undefined。该方法返回值被用作属性值。默认为undefined。
set
一个给属性提供setter的方法,如果没有setter则为undefined。该方法将接受唯一参数,并将该参数的新值分配给该属性。默认为undefined。
如果一个描述符不具有value,writable,get和set任意一个关键字,那么它将被认为是一个数据描述符。如果一个描述符同时有(value或writable)和(get或set)关键字,将会产生一个异常。
用法
类的装饰
@testable classMyTestableClass{ //... } functiontestable(target){ target.isTestable=true; } MyTestableClass.isTestable//true
上面代码中,@testable就是一个装饰器。它修改了MyTestableClass这个类的行为,为它加上了静态属性isTestable。testable函数的参数target是MyTestableClass类本身。
基本上,装饰器的行为就是下面这样。
@decorator classA{} //等同于 classA{} A=decorator(A)||A;
也就是说,装饰器是一个对类进行处理的函数。装饰器函数的第一个参数,就是所要装饰的目标类。
如果觉得一个参数不够用,可以在装饰器外面再封装一层函数。
functiontestable(isTestable){ returnfunction(target){ target.isTestable=isTestable; } } @testable(true) classMyTestableClass{} MyTestableClass.isTestable//true @testable(false) classMyClass{} MyClass.isTestable//false
上面代码中,装饰器testable可以接受参数,这就等于可以修改装饰器的行为。
注意,装饰器对类的行为的改变,是代码编译时发生的,而不是在运行时。这意味着,装饰器能在编译阶段运行代码。也就是说,装饰器本质就是编译时执行的函数。
前面的例子是为类添加一个静态属性,如果想添加实例属性,可以通过目标类的prototype对象操作。
下面是另外一个例子。
//mixins.js exportfunctionmixins(...list){ returnfunction(target){ Object.assign(target.prototype,...list) } } //main.js import{mixins}from'./mixins' constFoo={ foo(){console.log('foo')} }; @mixins(Foo) classMyClass{} letobj=newMyClass(); obj.foo()//'foo'
上面代码通过装饰器mixins,把Foo对象的方法添加到了MyClass的实例上面。
方法的装饰
装饰器不仅可以装饰类,还可以装饰类的属性。
classPerson{ @readonly name(){return`${this.first}${this.last}`} }
上面代码中,装饰器readonly用来装饰“类”的name方法。
装饰器函数readonly一共可以接受三个参数。
functionreadonly(target,name,descriptor){ //descriptor对象原来的值如下 //{ //value:specifiedFunction, //enumerable:false, //configurable:true, //writable:true //}; descriptor.writable=false; returndescriptor; } readonly(Person.prototype,'name',descriptor); //类似于 Object.defineProperty(Person.prototype,'name',descriptor);
- 装饰器第一个参数是类的原型对象,上例是Person.prototype,装饰器的本意是要“装饰”类的实例,但是这个时候实例还没生成,所以只能去装饰原型(这不同于类的装饰,那种情况时target参数指的是类本身);
- 第二个参数是所要装饰的属性名
- 第三个参数是该属性的描述对象
另外,上面代码说明,装饰器(readonly)会修改属性的描述对象(descriptor),然后被修改的描述对象再用来定义属性。
函数方法的装饰
装饰器只能用于类和类的方法,不能用于函数,因为存在函数提升。
另一方面,如果一定要装饰函数,可以采用高阶函数的形式直接执行。
functiondoSomething(name){ console.log('Hello,'+name); } functionloggingDecorator(wrapped){ returnfunction(){ console.log('Starting'); constresult=wrapped.apply(this,arguments); console.log('Finished'); returnresult; } } constwrapped=loggingDecorator(doSomething);
core-decorators.js
core-decorators.js是一个第三方模块,提供了几个常见的装饰器,通过它可以更好地理解装饰器。
@autobind
autobind装饰器使得方法中的this对象,绑定原始对象。
@readonly
readonly装饰器使得属性或方法不可写。
@override
override装饰器检查子类的方法,是否正确覆盖了父类的同名方法,如果不正确会报错。
import{override}from'core-decorators'; classParent{ speak(first,second){} } classChildextendsParent{ @override speak(){} //SyntaxError:Child#speak()doesnotproperlyoverrideParent#speak(first,second) } //or classChildextendsParent{ @override speaks(){} //SyntaxError:NodescriptormatchingChild#speaks()wasfoundontheprototypechain. // //Didyoumean"speak"? }
@deprecate(别名@deprecated)
deprecate或deprecated装饰器在控制台显示一条警告,表示该方法将废除。
import{deprecate}from'core-decorators'; classPerson{ @deprecate facepalm(){} @deprecate('Westoppedfacepalming') facepalmHard(){} @deprecate('Westoppedfacepalming',{url:'http://knowyourmeme.com/memes/facepalm'}) facepalmHarder(){} } letperson=newPerson(); person.facepalm(); //DEPRECATIONPerson#facepalm:Thisfunctionwillberemovedinfutureversions. person.facepalmHard(); //DEPRECATIONPerson#facepalmHard:Westoppedfacepalming person.facepalmHarder(); //DEPRECATIONPerson#facepalmHarder:Westoppedfacepalming // //Seehttp://knowyourmeme.com/memes/facepalmformoredetails. //
@suppressWarnings
suppressWarnings装饰器抑制deprecated装饰器导致的console.warn()调用。但是,异步代码发出的调用除外。
使用场景
装饰器有注释的作用
@testable classPerson{ @readonly @nonenumerable name(){return`${this.first}${this.last}`} }
有了装饰器,就可以改写上面的代码。装饰
@connect(mapStateToProps,mapDispatchToProps) exportdefaultclassMyReactComponentextendsReact.Component{}
相对来说,后一种写法看上去更容易理解。
新功能提醒或权限
菜单点击时,进行事件拦截,若该菜单有新功能更新,则弹窗显示。
/** *@description在点击时,如果有新功能提醒,则弹窗显示 *@paramcode新功能的code *@returns{function(*,*,*)} */ constcheckRecommandFunc=(code)=>(target,property,descriptor)=>{ letdesF=descriptor.value; descriptor.value=function(...args){ letrecommandFuncModalData=SYSTEM.recommandFuncCodeMap[code]; if(recommandFuncModalData&&recommandFuncModalData.id){ setTimeout(()=>{ this.props.dispatch({type:'global/setRecommandFuncModalData',recommandFuncModalData}); },1000); } desF.apply(this,args); }; returndescriptor; };
loading
在React项目中,我们可能需要在向后台请求数据时,页面出现loading动画。这个时候,你就可以使用装饰器,优雅地实现功能。
@autobind @loadingWrap(true) asynchandleSelect(params){ awaitthis.props.dispatch({ type:'product_list/setQuerypParams', querypParams:params }); }
loadingWrap函数如下:、
exportfunctionloadingWrap(needHide){ constdefaultLoading=( 加载中...
问题:这里大家可以想想看,如果我们不希望每次请求数据时都出现loading,而是要求只要后台请求时间大于300ms时,才显示loading,这里需要怎么改?
以上就是本次小编整理的关于JS装饰器的相关知识点,感谢大家对毛票票的支持。