详解ES7 Decorator 入门解析
Decorator提供了一种独特的抽象逻辑,可在原有代码基础上,零侵入添加新功能特性。商业代码总是多种交织并存的,在日常开发中,除了实现业务功能之外,我们还需要考虑诸如:异常处理、性能分析、日志等额外的需求。未经设计的的开发方法会倾向于将各种需求耦合组成一个功能模块,比如:
classMath{ staticadd(num1,num2){ try{ console.time('somelabel'); log('logforsomething'); constresult=num1+num2; console.timeEnd('somelabel'); returnresult; }catch(e){ error('somethinghadbroken'); } } }
上述简单的两数相加功能,在添加各类需求之后,已经变的面目全非。Decorator语法通过描述,可将功能特性叠加到原有功能中:
classMath{ @log @error @time staticadd(num1,num2){ returnnum1+num2; } }
Decorator是什么
Decorator就是一个的包裹函数,运行时在编译阶段调用该函数,修改目标对象的行为、属性。我们先来看一个简单实例:
constlog=(target,prop)=>console.log(`Wrapfunction:'${prop}'`); consttec={ @log say(){ console.log('helloworld') } } //=>Wrapfunction'say'
Decorator函数签名如下:
//@paramtarget作用对象 //@paramprop作用的属性名 //@paramdescriptor属性描述符 //@returndescriptor属性描述符 functiondecorator(target,prop,descriptor){}
参数详解:
- target:作用的对象,有如下情况:
- 作用于class时,target为该class函数
- 作用于class中的函数、属性时,target为该class的prototype对象
- 作用于对象字面量中的函数、属性时,target为该对象
- prop:描述的属性名,若decorator作用于class时,该参数为空
- descriptor:属性原本的描述符,该描述符可通过Object.getOwnPropertyDescriptor()获取,若decorator作用于class时,该参数为空
- decorator函数支持返回描述符或undefined,当返回值为描述符时,运行时会调用Object.defineProperty()修改原有属性。
Decorator的ES5实现
理解Decorator机制,最佳方式是使用ES5实现该过程。
class装饰器机制比较简单,仅做一层包装,伪代码:
//调用实例 @log classPerson{} //实现代码 constPerson=log(Person);
属性装饰器机制则比较复杂,babel就此提供了一个参考范例:
//decorator处理 function_applyDecoratedDescriptor(target,property,decorators,descriptor,context){ vardesc={}; Object['ke'+'ys'](descriptor).forEach(function(key){ desc[key]=descriptor[key]; }); desc.enumerable=!!desc.enumerable; desc.configurable=!!desc.configurable; if('value'indesc||desc.initializer){ desc.writable=true; } desc=decorators.slice().reverse().reduce(function(desc,decorator){ returndecorator(target,property,desc)||desc; },desc); if(context&&desc.initializer!==void0){ desc.value=desc.initializer?desc.initializer.call(context):void0; desc.initializer=undefined; } if(desc.initializer===void0){ Object['define'+'Property'](target,property,desc); desc=null; } returndesc; } //调用实例 classPerson{ @log say(){} } //实现代码 _applyDecoratedDescriptor( Person.prototype, 'say', [log], Object.getOwnPropertyDescriptor(Person.prototype,'say'), Person.prototype) )
用例
Decorator主要应用于如下几类对象:
- class
- class中,除构造函数外的方法
- class中的属性
- 对象字面量中的函数
- 对象字面量中的属性
//类 @log classPerson{ //函数 @log say(){} //属性 @log name='tec'; } //同样适用于对象字面量的方法、属性 consttec={ @log name:'tec', @log walk(){} };
Decorator实践
在JS中,Decorator是一个新概念,对于多数没有接触过诸如python、C#的开发者而言,很难理解实际应用场景。幸运的是github已经有人封装了常用Decorator。笔者分析该库,总结如下几种定义模式:
通过descriptor的value值修改:
functiondecorate(target,key,descriptor){ constfn=descriptor.value; return{ ...descriptor, value(){ returnfn.apply(this,arguments); } } }
通过descriptor的get、set函数修改:
functiondecorate(target,key,descriptor){ letvalue=descriptor.value; return{ ...descriptor, get(){ returnvalue; } set(v){ value=v; } } }
通过descriptor的writable、enumerable等属性修改:
functionreadonly(target,key,descriptor){ return{ ...descriptor, writable:false } }
针对class,返回包裹函数
functionlog(target){ letinitTimes=0; return(...arg)=>{ console.log(++initTimes); target.call(this,...arg); }; }
在实际开发中,还需要注意以下事项:
- Decorator的目标是在原有功能基础上,添加功能,切忌覆盖原有功能
- Decorator不是管道模式,decorator之间不存在交互,所以必须注意保持decorator独立性、透明性
- Decorator更适用于非业务功能需求
- 确定decorator的用途后,切记执行判断参数类型
- decorator针对每个装饰目标,仅执行一次
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。