视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
详解ES7 Decorator 入门解析
2020-11-27 22:00:50 责编:小采
文档


Decorator 提供了一种独特的抽象逻辑,可在原有代码基础上,零侵入添加新功能特性。商业代码总是多种交织并存的,在日常开发中,除了实现业务功能之外,我们还需要考虑诸如:异常处理、性能分析、日志等额外的需求。未经设计的的开发方法会倾向于将各种需求耦合组成一个功能模块,比如:

class Math{
 static add(num1,num2){
 try{
 console.time('some label');
 log('log for something');
 const result= num1+num2;
 console.timeEnd('some label');
 return result;
 }catch(e){
 error('something had broken');
 }
 }
}

上述简单的两数相加功能,在添加各类需求之后,已经变的面目全非。Decorator 语法通过描述,可将功能特性叠加到原有功能中:

class Math{
 @log
 @error
 @time
 static add(num1,num2){
 return num1+num2;
 }
}

Decorator 是什么

Decorator 就是一个的包裹函数,运行时在编译阶段调用该函数,修改目标对象的行为、属性。我们先来看一个简单实例:

const log = (target,prop)=>console.log(`Wrap function: '${prop}'`);

const tec={
 @log
 say(){
 console.log('hello world')
 }
}

// => Wrap function 'say'

Decorator 函数签名如下:

// @param target 作用对象
// @param prop 作用的属性名
// @param descriptor 属性描述符
// @return descriptor 属性描述符
function decorator(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 
    class Person{}
    // 实现代码
    const Person = log(Person);
    

    属性装饰器机制则比较复杂,babel 就此提供了一个参考范例:

    // decorator 处理
    function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) {
     var desc = {};
     Object['ke' + 'ys'](descriptor).forEach(function (key) {
     desc[key] = descriptor[key];
     });
     desc.enumerable = !!desc.enumerable;
     desc.configurable = !!desc.configurable;
    
     if ('value' in desc || desc.initializer) {
     desc.writable = true;
     }
    
     desc = decorators.slice().reverse().reduce(function (desc, decorator) {
     return decorator(target, property, desc) || desc;
     }, desc);
    
     if (context && desc.initializer !== void 0) {
     desc.value = desc.initializer ? desc.initializer.call(context) : void 0;
     desc.initializer = undefined;
     }
    
     if (desc.initializer === void 0) {
     Object['define' + 'Property'](target, property, desc);
     desc = null;
     }
    
     return desc;
    }
    
    // 调用实例
    class Person{
     @log
     say(){}
    }
    
    // 实现代码
    _applyDecoratedDescriptor(
     Person.prototype, 
     'say', 
     [log],
     Object.getOwnPropertyDescriptor(Person.prototype, 'say'),
     Person.prototype)
    )

    用例

    Decorator 主要应用于如下几类对象:

    1. class
    2. class 中,除构造函数外的方法
    3. class 中的属性
    4. 对象字面量中的函数
    5. 对象字面量中的属性
    // 类
    @log
    class Person{
     // 函数
     @log
     say(){}
     
     // 属性
     @log
     name = 'tec';
    }
    
    // 同样适用于对象字面量的方法、属性
    const tec = {
     @log
     name:'tec',
     
     @log
     walk(){}
    };

    Decorator 实践

    在JS中,Decorator 是一个新概念,对于多数没有接触过诸如python、C#的开发者而言,很难理解实际应用场景。幸运的是github已经有人封装了常用Decorator。笔者分析该库,总结如下几种定义模式:

    通过 descriptor 的 value 值修改:

    function decorate(target, key, descriptor) {
     const fn = descriptor.value;
    
     return {
     ...descriptor,
     value() {
     return fn.apply(this, arguments);
     }
     }
    }
    
    

    通过 descriptor 的 get、set 函数修改:

    function decorate(target, key, descriptor) {
     let value = descriptor.value;
    
     return {
     ...descriptor,
     get() {
     return value;
     }
     set(v) {
     value=v;
     }
     }
    }
    
    

    通过 descriptor 的 writable、enumerable 等属性修改:

    function readonly(target, key, descriptor) {
     return {
     ...descriptor,
     writable:false
     }
    }
    

    针对 class ,返回包裹函数

    function log(target){
     let initTimes=0;
     return (...arg)=>{
     console.log(++initTimes);
     target.call(this,...arg);
     };
    }
    

    在实际开发中,还需要注意以下事项:

  • Decorator 的目标是在原有功能基础上,添加功能,切忌覆盖原有功能
  • Decorator 不是管道模式,decorator之间不存在交互,所以必须注意保持decorator性、透明性
  • Decorator 更适用于非业务功能需求
  • 确定 decorator 的用途后,切记执行判断参数类型
  • decorator 针对每个装饰目标,仅执行一次
  • 下载本文
    显示全文
    专题