本文只浅析类装饰器和方法装饰器,其他原理相似,暂不赘述。

关于@Component类装饰器及vue-class-component源码可查看本人另一篇:源码探秘之 vue-class-component
关于@Prop属性装饰器、@Watch方法装饰器及vue-property-decorator源码可查看本人另一篇:源码探秘之 vue-property-decorator

一、简介

官方定义

随着TypeScript和ES6里引入了类,在一些场景下我们需要额外的特性来支持标注或修改类及其成员。 装饰器(Decorators)为我们在类的声明及成员上通过元编程语法添加标注提供了一种方式。

装饰器是一种特殊类型的声明,它能够被附加到类声明,方法,访问符,属性或参数上。

注意  Javascript里的装饰器目前处在 提案阶段,在未来的版本中可能会发生改变。

属于一种设计模式,许多面向对象的语言都有这项功能。

特点

  • 解耦

可以在不侵入到原有代码内部的情况下而通过标注的方式修改类代码行为。(将辅助性的功能和核心功能分开)

  • 优雅复用, 使用 @expression 写法。

二、举个栗子

运行环境

目前想要在 JS 中使用装饰器,需要通过 babel 将装饰器语法转义成 ES5 语法执行

package.json

{"scripts": {"build": "babel src -d build"},"dependencies": {"babel-cli": "6.26.0","babel-plugin-transform-decorators-legacy": "1.3.5","babel-preset-env": "1.7.0",}
}

.babelrc

{"presets": ["env"],"plugins": ["transform-decorators-legacy"]
}

新建 build 文件夹和 src 文件夹。

在src文件夹中新建js 文件,编写完执行 npm run build 后,就可以直接运行对应 build 文件夹下的转义好的 js 文件。

栗子1:类装饰器

主要将类的构造函数传递给装饰器为参数,可以给这个类新增属性/方法,也可以返回一个新的类替换原类。

如下,我们有一个 YaSuo 的类。

class YaSuo {constructor() {this.name = '亚索'}
}

接着给这个类增加一个类装饰器:

@testDecorator
class YaSuo {constructor() {this.name = '亚索'}
}
// 类装饰器
function testDecorator() {console.log(arguments);
}

我们看一下转义后的代码:

'use strict';var _class;// 如果 this 不是 YaSuo 的子类实例,就抛错
// 比如直接 YaSuo() 调用
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }// 将类的构造函数传递给装饰器
var YaSuo = testDecorator(_class = function YaSuo() {// 防止构造函数被当做普通函数执行_classCallCheck(this, YaSuo);this.name = '亚索';
}) || _class;// 类装饰器
function testDecorator() {console.log(arguments);
}

现在需要给 YaSuo 类新增一些其他的属性和方法

@attackDecorator
class YaSuo {constructor() {this.name = '亚索'}
}
// 向类新增属性/方法
function attackDecorator(target) {target.prototype.attack = 50; // 初始攻击力target.prototype.speak = () => {console.log('面对疾风吧!');}
}
const yaSuo = new YaSuo();
console.log(yaSuo.attack); // >>> 50
yaSuo.speak(); // >>> 面对疾风吧!

可以看到已经成功给类新增了属性和方法(其实是给原型添加)。
接着为了让装饰器复用,增加参数传递,采用高阶函数方式。

@attackDecorator(50)
class YaSuo {constructor() {this.name = '亚索'}
}
@attackDecorator(70)
class GaiLun {constructor() {this.name = '盖伦'}
}
// 高阶函数可传参
function attackDecorator(attack) {return function (target) {target.prototype.attack = attack}
}const yaSuo = new YaSuo()
console.log(yaSuo.attack); // >>> 50const gaiLun = new GaiLun()
console.log(gaiLun.attack); // >>> 70

类装饰器还可以覆盖原类:

@nickNameDecorator('哈撒给')
class YaSuo {constructor() {this.name = '亚索'this.nickName = '疾风剑豪'}
}// 覆盖原类,修改 nickName 属性
function nickNameDecorator(nickName) {return function (target) {return class extends target {constructor() {super();this.nickName = nickName}}// return 1 // 任意覆盖,甚至可以为 1}
}const yaSuo = new YaSuo()
console.log(yaSuo.nickName); // >>> 哈撒给
console.log(yaSuo.name); // >>> 亚索

栗子2:方法装饰器

与装饰类不同,对类方法的装饰本质是操作其描述符

可以把此时的装饰器理解成是 Object.defineProperty(obj, prop, descriptor) 的语法糖。

同样有这么一个 YaSuo 类,他有一个 init 方法并且使用了 methodDecorator 装饰器,看一下转义后的代码:

"use strict";// 主要是将类中的方法拷贝到构造函数的原型上去
// 该函数也是一个自执行的函数,其返回值是一个函数
var _createClass = function () {// 把props数组上每一个对象,通过Object.defineProperty方法,都定义到目标对象target上去// target: YaSuo 类的构造函数的原型     props:包含 init 方法的数组function defineProperties(target, props) {for (var i = 0; i < props.length; i++) {//这里要确保props[i]是一个对象,并且有key和value两个键var descriptor = props[i];// 定义是否可以从原型上访问descriptor.enumerable = descriptor.enumerable || false;// 定义其是否可删除descriptor.configurable = true;// 定义该属性是否可写if ("value" in descriptor) descriptor.writable = true;// 把 init 方法定义到 YaSuo 类的构造函数上Object.defineProperty(target, descriptor.key, descriptor);}}// Constructor: YaSuo 类的构造函数     protoProps:包含 init 方法的数组return function (Constructor, protoProps, staticProps) {// 传入构造函数的原型 和 包含 init 方法的数组if (protoProps) defineProperties(Constructor.prototype, protoProps);if (staticProps) defineProperties(Constructor, staticProps);// 返回这个构造函数return Constructor;};
}();var _desc, _value, _class;// 上面说过,防止构造函数被当做普通函数执行
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }// target:原型、property:方法名、decorators:装饰器数组、descriptor:方法描述对象、context:原型
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) {// 装饰器内可修改 descreturn decorator(target, property, desc) || desc;}, desc);// void 0 === undefined// initializer 函数是实例的初始化函数if (context && desc.initializer !== void 0) {// 将这个初始化函数调用,并且赋值给 valuedesc.value = desc.initializer ? desc.initializer.call(context) : void 0;desc.initializer = undefined;}// 利用 Object.definePropertype,将这个方法加到原型上if (desc.initializer === void 0) {Object['define' + 'Property'](target, property, desc);desc = null;}return desc;
}var YaSuo = (// 首先定义一个自执行函数_class = function () {// 定义了 YaSuo 类的构造函数function YaSuo() {var attack = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 50;_classCallCheck(this, YaSuo);this.init(attack);}// 创建一个类,传入 YaSuo 类的构造函数 和 包含 init 方法的数组// 主要是将类中的方法拷贝到构造函数的原型上去// 该函数也是一个自执行的函数,其返回值是一个函数 _createClass(YaSuo, [{key: "init",value: function init(attack) {this.attack = attack;}}]);return YaSuo;}(),// 调用装饰器,处理方法的描述对象(_applyDecoratedDescriptor(_class.prototype, // 原型"init", // 方法名[methodDecorator], // 装饰器函数数组Object.getOwnPropertyDescriptor(_class.prototype, "init"), // init方法的描述对象_class.prototype  // 原型)),// 最后返回 YaSuo 类的构造函数_class
);function methodDecorator() {console.log(arguments);// '0': { },// '1': 'init',// '2': {// value: [Function: init],//   writable: true,//   enumerable: false,//   configurable: true// }
}

接下来我们继续装饰亚索!如果亚索穿了皮肤,那么攻击力加10点:

class YaSuo {constructor(attack = 50, hasDress = false) {this.init(attack, hasDress)}@dressDecoratorinit(attack, hasDress) {this.attack = attackthis.hasDress = hasDress}
}function dressDecorator(target, name, descriptor) {const method = descriptor.value;descriptor.value = (attack, hasDress) => {let realAttack = attack; // 获取初始攻击力if (hasDress) {realAttack += 10}return method.apply(target, [realAttack, hasDress]); // 调用原方法,传入新参数}return descriptor;
}const yaSuo = new YaSuo(50, true)
console.log(yaSuo.attack); // >>> 60

属性装饰器即可以修改原属性值,原理和方法装饰器基本一致,不再赘述。


码字不易,觉得有帮助的小伙伴点个赞支持下~


扫描上方二维码关注我的订阅号~

JS Decorator —— 装饰器(装饰模式)相关推荐

  1. 一文读懂 @Decorator 装饰器——理解 VS Code 源码的基础

    作者:easonruan,腾讯 CSIG 前端开发工程师 1. 装饰器的样子 我们先来看看 Decorator 装饰器长什么样子,大家可能没在项目中用过 Decorator 装饰器,但多多少少会看过下 ...

  2. 聊聊在Vue项目中使用Decorator装饰器

    戳蓝字" Web前端严选 " 关注我们哦 ! 前言 初衷: 前几天我在公司其它Vue项目中,发现了是用Decorator装饰器模式开发的,看起来整体代码还不错,于是就做了一下笔记分 ...

  3. Java —— Decorator 装饰器模式

    文章目录 Java -- Decorator 装饰器模式 简介 用处 简单例子 结构 代码 涉及角色 相关的设计模式 应用实例 优点 缺点 使用场景 注意事项 代码 Java -- Decorator ...

  4. decorator 装饰器

    许多面向对象都有decorator(装饰器)函数,比如python中也可以用decorator函数来强化代码,decorator相当于一个高阶函数,接收一个函数,返回一个被装饰后的函数. 注: jav ...

  5. Python 中的闭包、匿名函数、decorator 装饰器与python的偏函数

    Python中的闭包 def calc_sum(lst):def lazy_sum():return sum(lst)return lazy_sum 像这种内层函数引用了外层函数的变量(参数也算变量) ...

  6. Python中的decorator装饰器使用方法

    装饰器的运用是Python编程中的一项高级技巧,这里由浅入深,整理了12步入门Python中的decorator装饰器使用方法,需要的朋友可以参考下 装饰器(decorator)是一种高级Python ...

  7. Decorator 装饰器模式 -动态组合

    为什么80%的码农都做不了架构师?>>>    一:业务场景 奖金计算,每个部门,有不同的计算方法,且每个部门有不同类型的奖金项:而且每年或每隔几个季度奖金算法都要重新实现下. 这个 ...

  8. JS设计模式——装饰器模式

    什么是装饰器模式? 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构.这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装. 实例 拿最近比 ...

  9. Python进阶: Decorator 装饰器你太美

    函数 -> 装饰器 函数的4个核心概念 1.函数可以赋与变量 def func(message):print('Got a message: {}'.format(message))send_m ...

最新文章

  1. 【SpringMVC】概述
  2. PHP autoload 函数
  3. 南京林业大学计算机专升本,2018江苏专转本学校之:南京林业大学
  4. python保存到txt_python 如何将数据写入本地txt文本文件的实现方法
  5. maven设置jdk版本(全局设置和工程设置)
  6. IOS基础之使用UICollectionView纯代码创建
  7. js `` 手机不支持
  8. Java笔记:Statement和PreparedStatement的区别
  9. Spring Quartz配置
  10. web前端常见浏览器兼容性问题以及解决技巧
  11. uc android快捷键,UC手机浏览器助力Android快速上网
  12. 【多字典公共键】快速找到多个字典的公共键及非公共键
  13. itext合并两个PDF流
  14. 【无标题】setting.json 配置
  15. 整流二极管的细节分析
  16. GitHub上12k Star的《Java工程师成神之路》终于开放阅读了!
  17. uniapp+nvue开发之仿微信语音+视频通话功能 :实现一对一语音视频在线通话
  18. excel快速把公式应用到一整列
  19. php反转图片颜色,PHP 图片处理类(水印、透明度、缩放、相框、锐化、旋转、翻转、剪切、反色)...
  20. 剖析8B/10B的实现机制

热门文章

  1. 你是否有疑问,软件是如何驱动硬件,代码又是如何对计算机进行控制的呢?
  2. R 图表中的字体调整
  3. Google Map 开发笔记——基础篇(Javascript )
  4. Hbase(一)入门
  5. 软件技术部第一次机器学习培训
  6. ESP32-CAM、ESP8266、WIFI、蓝牙、单片机、热点创建嵌入式DNS服务器
  7. 你和csdn是什么关系
  8. matlab牛顿法求区间根程序,MATLAB用二分法、不动点迭代法及Newton迭代(切线)法求非线性方程的根...
  9. python不改变图片尺寸压缩到指定大小
  10. 如何关闭电脑自动更新