2019独角兽企业重金招聘Python工程师标准>>>

摘要: ## Decorator 装饰器主要用于: 1. 装饰类 2. 装饰方法或属性 ### 装饰类 ```js @annotation class MyClass { } function annotation(target) { target.annotated = true; } ``` ### 装饰方法或属性 ```js class

Decorator

装饰器主要用于:

  1. 装饰类
  2. 装饰方法或属性

装饰类

@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();

参考

  1. ECMAScript 6 入门
  2. core-decorators
  3. ES7 Decorator 装饰者模式
  4. JS 装饰器(Decorator)场景实战

ES6 系列

ES6 系列目录地址:https://github.com/mqyqingfeng/Blog

ES6 系列预计写二十篇左右,旨在加深 ES6 部分知识点的理解,重点讲解块级作用域、标签模板、箭头函数、Symbol、Set、Map 以及 Promise 的模拟实现、模块加载方案、异步处理等内容。

原文链接

转载于:https://my.oschina.net/u/1464083/blog/2885373

ES6 系列之我们来聊聊装饰器相关推荐

  1. Flask系列06--(中间件)Flask的特殊装饰器 before_request,after_request, errorhandler

    一.使用 Flask中的特殊装饰器(中间件)方法常用的有三个 @app.before_request # 在请求进入视图函数之前 @app.after_request # 在请求结束视图函数之后 响应 ...

  2. 设计模式系列【24】:装饰器模式(装饰设计模式)详解

    上班族大多都有睡懒觉的习惯,每天早上上班时间都很紧张,于是很多人为了多睡一会,就会用方便的方式解决早餐问题.有些人早餐可能会吃煎饼,煎饼中可以加鸡蛋,也可以加香肠,但是不管怎么"加码&quo ...

  3. 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. ...

  4. python 打印皮卡丘_来简单聊聊python的装饰器呀~

    原文链接 来简单聊聊python的装饰器呀~​mp.weixin.qq.com 导语 之前很多小伙伴留言给我说看别人写的代码经常会感觉云里雾里的,完全看不懂,其实那些代码无非就是用了些python语法 ...

  5. TypeScript系列教程十一《装饰器》 -- 属性装饰器

    系列教程 TypeScript系列教程一<开篇> TypeScript系列教程二<安装起步> TypeScript系列教程三<基础类型> TypeScript系列教 ...

  6. 面试系列 | 带你彻底搞懂 Python 装饰器

    本文作者:Rocky0249 公众号:Python空间 写在之前 「装饰器」作为 Python 高级语言特性中的重要部分,是修改函数的一种超级便捷的方式,适当使用能够有效提高代码的可读性和可维护性,非 ...

  7. python 装饰器实现_Python装饰器系列01 - 如何正确地实现装饰器

    虽然人们能利用函数闭包(function clouser)写出简单的装饰器,但其可用范围常受限制.多数实现装饰器的基本方式会破坏与内省(Introspection)的关联性. 可大多数人会说:who ...

  8. python装饰器系列(五)

    带参数的装饰器的应用: 比如有一个函数,只有在对有许可权限的用户开放,执行此函数的用户没有在认证列表里的,就不会执行这个函数.这个该如何实现呢?如下: 1 def check(allow_users) ...

  9. 设计模式系列:搞懂装饰器模式,增加自身技能

    装饰器模式的定义:指在不改变原有对象的基础上,动态地给该对象增加一些额外的功能的模式,属于对象结构型模式. 装饰器模式的核心在于类的扩展,通过装饰器模式可以透明且动态的扩展类的功能. 装饰器模式的结构 ...

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

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

最新文章

  1. vue-cli3环境变量与分环境打包
  2. Mysql中的增删改查操作
  3. 5加载stm32 keil_「keil」嵌入式STM32开发环境之Keil5的安装(附资源) - 金橙教程网
  4. unity3d中获得物体的size
  5. jdk1.8新特性(五)——Stream
  6. Linux安装或升级openssh步骤和可能遇到的问题
  7. dalvik虚拟机与Java区别_05 Android---java虚拟机跟dalvik虚拟机的区别(从01开始点点入门,视频+笔记)...
  8. Python中[:]与[::]的用法
  9. Java-虚拟机-类加载(装载连接初始化)
  10. sql server 2008 的安装
  11. 17.1.1.3 Creating a User for Replication 创建一个用于用于复制:
  12. qt designer 弹出输入框_Qt编写的项目作品3-输入法V2018
  13. 逻辑程序设计语言Prolog
  14. java qq机器人_简单几步教你如何用Java快速制作一个QQ机器人
  15. 中兴f477v2超级管理员_中兴f677v2联通光猫超级密码及登录地址
  16. Windows的快捷方式、符号链接、目录联接、硬链接的区别
  17. 一对一直播技术服务【推广】
  18. 无胁科技-TVD每日漏洞情报-2022-8-1
  19. Hive内部表及外部表
  20. 齐鲁医药学院计算机考试,齐鲁医药学院2020年单独招生和综合评价招生考试时间及考试科目...

热门文章

  1. spring的自动装配(default-autowire=byName)
  2. 字符串格式参数的日期比较函数
  3. Tomcat 7 证书配置中遇到的一个问题
  4. 一些有趣的404错误设计
  5. imagej软件分析划痕实验(计算划痕面积及伤口愈合百分比)
  6. NSGA2算法MATLAB
  7. matlab保存每次循环的结果
  8. 将print的内容保存到txt文本中
  9. ArcGIS 城市生活区用地适宜性评价(一)
  10. 测绘工程野外测量实践实习