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

关于@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. Reading papers_15(Graph cuts optimization for multi-limb human segmentation in depth maps)
  2. php订单系统 帝国cms,帝国CMS商城系统在线支付后,订单邮件提醒
  3. 一个简单的if else优化
  4. Sybase资料下载(参考手册,功能讲解很全)
  5. 7-37 模拟EXCEL排序 (25 分)(思路+详解+超时解决 兄弟们冲呀呀呀呀呀呀)
  6. Windows下python发送邮件_CustomEmail.py[text、html、附件、读取文本到正文]
  7. org.apache.ibatis.builder.BuilderException: An invalid property ‘jdbcType ‘ was found in mapping
  8. 自动化测试基础篇--Selenium多窗口、句柄问题
  9. oracle中表为啥会死锁,Oracle数据表中的死锁情况解决方法
  10. ubuntu锐捷校园网
  11. 唐宇迪opencv-背景建模
  12. Python实现历史记录功能
  13. 【Android学习日记】
  14. SVN版本冲突原因以及详细解决办法
  15. DSPE-PEG近年来在长循环脂质体、高分子胶束等药物载体中的应用获得了较快发展
  16. 编译器错误 C2355
  17. 将KITTI数据集的odometry部分velodyne的bin文件转换成rosbag
  18. python怎么做类型标注
  19. 用Python做数据分析之生成数据表
  20. 写一个trim函数,兼容IE firefox chrome(正则)

热门文章

  1. Java细节:单等于号、双等于号、三等于号(js中才有)的作用及双等于号和equals(源码)的区别
  2. 凤凰新闻自动评论推荐软件--把自己的评论推荐到最前面,然后通过头像、用户名或者评论内容进行展示
  3. 线性空间(向量空间)
  4. 7个银行的软件测试项目实战,别再说简历项目不知道怎么写了
  5. 小程序 制作自定义弹层 添加弹层显示和隐藏动画 父组件与子组件(自定义组件)之间传值
  6. java语言获取数据库中的表的总行数
  7. Java代码的登录界面
  8. 浙大PAT甲级-1017
  9. 从红队视角看AWD攻击
  10. 由Jack Dorsey背书支持的CoinList进军DeFi市场