详解ES7 Decorator 入门解析


Posted in Javascript onFebruary 18, 2019

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 针对每个装饰目标,仅执行一次

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
如何取得中文输入的真实长度?
Jun 24 Javascript
jQuery带箭头提示框tooltips插件集锦
Nov 17 Javascript
javascript中关于&& 和 || 表达式的小技巧分享
Apr 10 Javascript
浅析JavaScript中的变量复制、参数传递和作用域链
Jan 13 Javascript
JS自定义函数对web前端上传的文件进行类型大小判断
Oct 19 Javascript
easyui combobox开启搜索自动完成功能的实例代码
Nov 08 Javascript
Bootstrap fileinput组件封装及使用详解
Mar 10 Javascript
Javascript实现登录记住用户名和密码功能
Mar 22 Javascript
Angular 4依赖注入学习教程之组件服务注入(二)
Jun 04 Javascript
Node.JS中快速扫描端口并发现局域网内的Web服务器地址(80)
Sep 18 Javascript
mpvue 如何使用腾讯视频插件的方法
Jul 16 Javascript
详解React中共享组件逻辑的三种方式
Feb 02 Javascript
jQuery插件实现非常实用的tab栏切换功能【案例】
Feb 18 #jQuery
详解关于微信setData回调函数中的坑
Feb 18 #Javascript
jQuery实现的五星点评功能【案例】
Feb 18 #jQuery
JS中min函数实例讲解
Feb 18 #Javascript
jQuery实现的隔行变色功能【案例】
Feb 18 #jQuery
小程序实现列表多个批量倒计时
Jan 29 #Javascript
记一次vue-webpack项目优化实践详解
Feb 17 #Javascript
You might like
解析Win7 XAMPP apache无法启动的问题
2013/06/26 PHP
ThinkPHP查询中的魔术方法简述
2014/06/25 PHP
thinkphp3.2.2前后台公用类架构问题分析
2014/11/25 PHP
CodeIgniter与PHP5.6的兼容问题
2015/07/16 PHP
php三种实现多线程类似的方法
2015/10/30 PHP
PHP中explode函数和split函数的区别小结
2016/08/24 PHP
jquery中输入验证中一个不错的效果
2010/08/21 Javascript
jQuery中addClass()方法用法实例
2015/01/05 Javascript
JS实现兼容性好,自动置顶的淘宝悬浮工具栏效果
2015/09/18 Javascript
javascript事件捕获机制【深入分析IE和DOM中的事件模型】
2016/12/15 Javascript
js Canvas绘制圆形时钟效果
2017/02/17 Javascript
Angular中$broadcast和$emit的使用方法详解
2017/05/22 Javascript
详解webpack模块加载器兼打包工具
2018/09/11 Javascript
JavaScript Array对象基本方法详解
2019/09/03 Javascript
小程序使用wxs解决wxml保留2位小数问题
2019/12/13 Javascript
[00:17]天涯墨客一技能展示
2018/08/25 DOTA
Python查询Mysql时返回字典结构的代码
2012/06/18 Python
Python解惑之整数比较详解
2017/04/24 Python
python中numpy包使用教程之数组和相关操作详解
2017/07/30 Python
Python paramiko模块的使用示例
2018/04/11 Python
解决python 输出是省略号的问题
2018/04/19 Python
spark: RDD与DataFrame之间的相互转换方法
2018/06/07 Python
Tesserocr库的正确安装方式
2018/10/19 Python
GitHub 热门:Python 算法大全,Star 超过 2 万
2019/04/29 Python
python用match()函数爬数据方法详解
2019/07/23 Python
如何用OpenCV -python3实现视频物体追踪
2019/12/04 Python
怎样有效的进行自我评价
2013/10/06 职场文书
花店创业计划书范文
2014/02/07 职场文书
员工年终自我评价
2014/09/14 职场文书
乡镇党建工作汇报材料
2014/10/27 职场文书
入党介绍人意见范文
2015/06/01 职场文书
超市店长竞聘书
2015/09/15 职场文书
学习型家庭事迹材料(2016精选版)
2016/02/29 职场文书
Javascript中async与await的捕捉错误详解
2022/03/03 Javascript
使用python绘制横竖条形图
2022/04/21 Python
Java中生成微信小程序太阳码的实现方案
2022/06/01 Java/Android