ES6 系列之我们来聊聊装饰器
2019独角兽企业重金招聘Python工程师标准>>>
摘要: ## Decorator 装饰器主要用于: 1. 装饰类 2. 装饰方法或属性 ### 装饰类 ```js @annotation class MyClass { } function annotation(target) { target.annotated = true; } ``` ### 装饰方法或属性 ```js class
Decorator
装饰器主要用于:
- 装饰类
- 装饰方法或属性
装饰类
@annotation
class MyClass { }function annotation(target) {target.annotated = true;
}
装饰方法或属性
class MyClass {@readonlymethod() { }
}function readonly(target, name, descriptor) {descriptor.writable = false;return descriptor;
}
Babel
安装编译
我们可以在 Babel 官网的 Try it out,查看 Babel 编译后的代码。
不过我们也可以选择本地编译:
npm initnpm install --save-dev @babel/core @babel/clinpm install --save-dev @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties
新建 .babelrc 文件
{"plugins": [["@babel/plugin-proposal-decorators", { "legacy": true }],["@babel/plugin-proposal-class-properties", {"loose": true}]]
}
再编译指定的文件
babel decorator.js --out-file decorator-compiled.js
装饰类的编译
编译前:
@annotation
class MyClass { }function annotation(target) {target.annotated = true;
}
编译后:
var _class;let MyClass = annotation(_class = class MyClass {}) || _class;function annotation(target) {target.annotated = true;
}
我们可以看到对于类的装饰,其原理就是:
@decorator
class A {}// 等同于class A {}
A = decorator(A) || A;
装饰方法的编译
编译前:
class MyClass {@unenumerable@readonlymethod() { }
}function readonly(target, name, descriptor) {descriptor.writable = false;return descriptor;
}function unenumerable(target, name, descriptor) {descriptor.enumerable = false;return descriptor;
}
编译后:
var _class;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;}/*** 第二部分* 应用多个 decorators*/desc = decorators.slice().reverse().reduce(function(desc, decorator) {return decorator(target, property, desc) || desc;}, desc);/*** 第三部分* 设置要 decorators 的属性*/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;
}let MyClass = ((_class = class MyClass {method() {}
}),
_applyDecoratedDescriptor(_class.prototype,"method",[readonly],Object.getOwnPropertyDescriptor(_class.prototype, "method"),_class.prototype
),
_class);function readonly(target, name, descriptor) {descriptor.writable = false;return descriptor;
}
装饰方法的编译源码解析
我们可以看到 Babel 构建了一个 _applyDecoratedDescriptor 函数,用于给方法装饰。
Object.getOwnPropertyDescriptor()
在传入参数的时候,我们使用了一个 Object.getOwnPropertyDescriptor() 方法,我们来看下这个方法:
Object.getOwnPropertyDescriptor() 方法返回指定对象上的一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性)
顺便注意这是一个 ES5 的方法。
举个例子:
const foo = { value: 1 };
const bar = Object.getOwnPropertyDescriptor(foo, "value");
// bar {
// value: 1,
// writable: true
// enumerable: true,
// configurable: true,
// }const foo = { get value() { return 1; } };
const bar = Object.getOwnPropertyDescriptor(foo, "value");
// bar {
// get: /*the getter function*/,
// set: undefined
// enumerable: true,
// configurable: true,
// }
第一部分源码解析
在 _applyDecoratedDescriptor 函数内部,我们首先将 Object.getOwnPropertyDescriptor() 返回的属性描述符对象做了一份拷贝:
// 拷贝一份 descriptor
var desc = {};
Object["ke" + "ys"](descriptor).forEach(function(key) {desc[key] = descriptor[key];
});
desc.enumerable = !!desc.enumerable;
desc.configurable = !!desc.configurable;// 如果没有 value 属性或者没有 initializer 属性,表明是 getter 和 setter
if ("value" in desc || desc.initializer) {desc.writable = true;
}
那么 initializer 属性是什么呢?Object.getOwnPropertyDescriptor() 返回的对象并不具有这个属性呀,确实,这是 Babel 的 Class 为了与 decorator 配合而产生的一个属性,比如说对于下面这种代码:
class MyClass {@readonlyborn = Date.now();
}function readonly(target, name, descriptor) {descriptor.writable = false;return descriptor;
}var foo = new MyClass();
console.log(foo.born);
Babel 就会编译为:
// ...
(_descriptor = _applyDecoratedDescriptor(_class.prototype, "born", [readonly], {configurable: true,enumerable: true,writable: true,initializer: function() {return Date.now();}
}))
// ...
此时传入 _applyDecoratedDescriptor 函数的 descriptor 就具有 initializer 属性。
第二部分源码解析
接下是应用多个 decorators:
/*** 第二部分* @type {[type]}*/
desc = decorators.slice().reverse().reduce(function(desc, decorator) {return decorator(target, property, desc) || desc;}, desc);
对于一个方法应用了多个 decorator,比如:
class MyClass {@unenumerable@readonlymethod() { }
}
Babel 会编译为:
_applyDecoratedDescriptor(_class.prototype,"method",[unenumerable, readonly],Object.getOwnPropertyDescriptor(_class.prototype, "method"),_class.prototype
)
在第二部分的源码中,执行了 reverse() 和 reduce() 操作,由此我们也可以发现,如果同一个方法有多个装饰器,会由内向外执行。
第三部分源码解析
/*** 第三部分* 设置要 decorators 的属性*/
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;
如果 desc 有 initializer 属性,意味着当装饰的是类的属性时,会将 value 的值设置为:
desc.initializer.call(context)
而 context 的值为 _class.prototype
,之所以要 call(context)
,这也很好理解,因为有可能
class MyClass {@readonlyvalue = this.getNum() + 1;getNum() {return 1;}
}
最后无论是装饰方法还是属性,都会执行:
Object["define" + "Property"](target, property, desc);
由此可见,装饰方法本质上还是使用 Object.defineProperty()
来实现的。
应用
1.log
为一个方法添加 log 函数,检查输入的参数:
class Math {@logadd(a, b) {return a + b;}
}function log(target, name, descriptor) {var oldValue = descriptor.value;descriptor.value = function(...args) {console.log(`Calling ${name} with`, args);return oldValue.apply(this, args);};return descriptor;
}const math = new Math();// Calling add with [2, 4]
math.add(2, 4);
再完善点:
let log = (type) => {return (target, name, descriptor) => {const method = descriptor.value;descriptor.value = (...args) => {console.info(`(${type}) 正在执行: ${name}(${args}) = ?`);let ret;try {ret = method.apply(target, args);console.info(`(${type}) 成功 : ${name}(${args}) => ${ret}`);} catch (error) {console.error(`(${type}) 失败: ${name}(${args}) => ${error}`);}return ret;}}
};
2.autobind
class Person {@autobindgetPerson() {return this;}
}let person = new Person();
let { getPerson } = person;getPerson() === person;
// true
我们很容易想到的一个场景是 React 绑定事件的时候:
class Toggle extends React.Component {@autobindhandleClick() {console.log(this)}render() {return (<button onClick={this.handleClick}>button</button>);}
}
我们来写这样一个 autobind 函数:
const { defineProperty, getPrototypeOf} = Object;function bind(fn, context) {if (fn.bind) {return fn.bind(context);} else {return function __autobind__() {return fn.apply(context, arguments);};}
}function createDefaultSetter(key) {return function set(newValue) {Object.defineProperty(this, key, {configurable: true,writable: true,enumerable: true,value: newValue});return newValue;};
}function autobind(target, key, { value: fn, configurable, enumerable }) {if (typeof fn !== 'function') {throw new SyntaxError(`@autobind can only be used on functions, not: ${fn}`);}const { constructor } = target;return {configurable,enumerable,get() {/*** 使用这种方式相当于替换了这个函数,所以当比如* Class.prototype.hasOwnProperty(key) 的时候,为了正确返回* 所以这里做了 this 的判断*/if (this === target) {return fn;}const boundFn = bind(fn, this);defineProperty(this, key, {configurable: true,writable: true,enumerable: false,value: boundFn});return boundFn;},set: createDefaultSetter(key)};
}
3.debounce
有的时候,我们需要对执行的方法进行防抖处理:
class Toggle extends React.Component {@debounce(500, true)handleClick() {console.log('toggle')}render() {return (<button onClick={this.handleClick}>button</button>);}
}
我们来实现一下:
function _debounce(func, wait, immediate) {var timeout;return function () {var context = this;var args = arguments;if (timeout) clearTimeout(timeout);if (immediate) {var callNow = !timeout;timeout = setTimeout(function(){timeout = null;}, wait)if (callNow) func.apply(context, args)}else {timeout = setTimeout(function(){func.apply(context, args)}, wait);}}
}function debounce(wait, immediate) {return function handleDescriptor(target, key, descriptor) {const callback = descriptor.value;if (typeof callback !== 'function') {throw new SyntaxError('Only functions can be debounced');}var fn = _debounce(callback, wait, immediate)return {...descriptor,value() {fn()}};}
}
4.time
用于统计方法执行的时间:
function time(prefix) {let count = 0;return function handleDescriptor(target, key, descriptor) {const fn = descriptor.value;if (prefix == null) {prefix = `${target.constructor.name}.${key}`;}if (typeof fn !== 'function') {throw new SyntaxError(`@time can only be used on functions, not: ${fn}`);}return {...descriptor,value() {const label = `${prefix}-${count}`;count++;console.time(label);try {return fn.apply(this, arguments);} finally {console.timeEnd(label);}}}}
}
5.mixin
用于将对象的方法混入 Class 中:
const SingerMixin = {sing(sound) {alert(sound);}
};const FlyMixin = {// All types of property descriptors are supportedget speed() {},fly() {},land() {}
};@mixin(SingerMixin, FlyMixin)
class Bird {singMatingCall() {this.sing('tweet tweet');}
}var bird = new Bird();
bird.singMatingCall();
// alerts "tweet tweet"
mixin 的一个简单实现如下:
function mixin(...mixins) {return target => {if (!mixins.length) {throw new SyntaxError(`@mixin() class ${target.name} requires at least one mixin as an argument`);}for (let i = 0, l = mixins.length; i < l; i++) {const descs = Object.getOwnPropertyDescriptors(mixins[i]);const keys = Object.getOwnPropertyNames(descs);for (let j = 0, k = keys.length; j < k; j++) {const key = keys[j];if (!target.prototype.hasOwnProperty(key)) {Object.defineProperty(target.prototype, key, descs[key]);}}}};
}
6.redux
实际开发中,React 与 Redux 库结合使用时,常常需要写成下面这样。
class MyReactComponent extends React.Component {}export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);
有了装饰器,就可以改写上面的代码。
@connect(mapStateToProps, mapDispatchToProps)
export default class MyReactComponent extends React.Component {};
相对来说,后一种写法看上去更容易理解。
7.注意
以上我们都是用于修饰类方法,我们获取值的方式为:
const method = descriptor.value;
但是如果我们修饰的是类的实例属性,因为 Babel 的缘故,通过 value 属性并不能获取值,我们可以写成:
const value = descriptor.initializer && descriptor.initializer();
参考
- ECMAScript 6 入门
- core-decorators
- ES7 Decorator 装饰者模式
- JS 装饰器(Decorator)场景实战
ES6 系列
ES6 系列目录地址:https://github.com/mqyqingfeng/Blog
ES6 系列预计写二十篇左右,旨在加深 ES6 部分知识点的理解,重点讲解块级作用域、标签模板、箭头函数、Symbol、Set、Map 以及 Promise 的模拟实现、模块加载方案、异步处理等内容。
原文链接
转载于:https://my.oschina.net/u/1464083/blog/2885373
ES6 系列之我们来聊聊装饰器相关推荐
- Flask系列06--(中间件)Flask的特殊装饰器 before_request,after_request, errorhandler
一.使用 Flask中的特殊装饰器(中间件)方法常用的有三个 @app.before_request # 在请求进入视图函数之前 @app.after_request # 在请求结束视图函数之后 响应 ...
- 设计模式系列【24】:装饰器模式(装饰设计模式)详解
上班族大多都有睡懒觉的习惯,每天早上上班时间都很紧张,于是很多人为了多睡一会,就会用方便的方式解决早餐问题.有些人早餐可能会吃煎饼,煎饼中可以加鸡蛋,也可以加香肠,但是不管怎么"加码&quo ...
- create-react-app 配置scss,ant-design,装饰器,代理,node支持最新语法,express es6 后端,链接mongodb...
新建一个项目 npm install -g create-react-app create-react-app my-app cd my-app npm i npm start # 或者,npm 5. ...
- python 打印皮卡丘_来简单聊聊python的装饰器呀~
原文链接 来简单聊聊python的装饰器呀~mp.weixin.qq.com 导语 之前很多小伙伴留言给我说看别人写的代码经常会感觉云里雾里的,完全看不懂,其实那些代码无非就是用了些python语法 ...
- TypeScript系列教程十一《装饰器》 -- 属性装饰器
系列教程 TypeScript系列教程一<开篇> TypeScript系列教程二<安装起步> TypeScript系列教程三<基础类型> TypeScript系列教 ...
- 面试系列 | 带你彻底搞懂 Python 装饰器
本文作者:Rocky0249 公众号:Python空间 写在之前 「装饰器」作为 Python 高级语言特性中的重要部分,是修改函数的一种超级便捷的方式,适当使用能够有效提高代码的可读性和可维护性,非 ...
- python 装饰器实现_Python装饰器系列01 - 如何正确地实现装饰器
虽然人们能利用函数闭包(function clouser)写出简单的装饰器,但其可用范围常受限制.多数实现装饰器的基本方式会破坏与内省(Introspection)的关联性. 可大多数人会说:who ...
- python装饰器系列(五)
带参数的装饰器的应用: 比如有一个函数,只有在对有许可权限的用户开放,执行此函数的用户没有在认证列表里的,就不会执行这个函数.这个该如何实现呢?如下: 1 def check(allow_users) ...
- 设计模式系列:搞懂装饰器模式,增加自身技能
装饰器模式的定义:指在不改变原有对象的基础上,动态地给该对象增加一些额外的功能的模式,属于对象结构型模式. 装饰器模式的核心在于类的扩展,通过装饰器模式可以透明且动态的扩展类的功能. 装饰器模式的结构 ...
- 聊聊在Vue项目中使用Decorator装饰器
戳蓝字" Web前端严选 " 关注我们哦 ! 前言 初衷: 前几天我在公司其它Vue项目中,发现了是用Decorator装饰器模式开发的,看起来整体代码还不错,于是就做了一下笔记分 ...
最新文章
- vue-cli3环境变量与分环境打包
- Mysql中的增删改查操作
- 5加载stm32 keil_「keil」嵌入式STM32开发环境之Keil5的安装(附资源) - 金橙教程网
- unity3d中获得物体的size
- jdk1.8新特性(五)——Stream
- Linux安装或升级openssh步骤和可能遇到的问题
- dalvik虚拟机与Java区别_05 Android---java虚拟机跟dalvik虚拟机的区别(从01开始点点入门,视频+笔记)...
- Python中[:]与[::]的用法
- Java-虚拟机-类加载(装载连接初始化)
- sql server 2008 的安装
- 17.1.1.3 Creating a User for Replication 创建一个用于用于复制:
- qt designer 弹出输入框_Qt编写的项目作品3-输入法V2018
- 逻辑程序设计语言Prolog
- java qq机器人_简单几步教你如何用Java快速制作一个QQ机器人
- 中兴f477v2超级管理员_中兴f677v2联通光猫超级密码及登录地址
- Windows的快捷方式、符号链接、目录联接、硬链接的区别
- 一对一直播技术服务【推广】
- 无胁科技-TVD每日漏洞情报-2022-8-1
- Hive内部表及外部表
- 齐鲁医药学院计算机考试,齐鲁医药学院2020年单独招生和综合评价招生考试时间及考试科目...