浅谈js中的装饰器
栏目: JavaScript · 发布时间: 5年前
内容简介:装饰器模式JS中的装饰器是ES7中的一个新语法,可以对大家可能听说过 组合函数 和 高阶函数 的概念,也可以这么理解。
装饰器模式 (Decorator Pattern)
是一种结构型设计模式,旨在促进代码复用,可以用于修改现有的系统,希望在系统中为对象添加额外的功能,同时又不需要大量修改原有的代码。
JS中的装饰器是ES7中的一个新语法,可以对 类
、 方法
、 属性
进行修饰,从而进行一些相关功能定制, 它的写法与 Java 的注解 (Annotation)
类似,但是功能有比较大的区别。
大家可能听说过 组合函数 和 高阶函数 的概念,也可以这么理解。
我们先来看一下以下代码:
function doSomething(name) { console.log('Hi, I\'' + name); } funtion useLogging(func, name) { console.log('Starting'); func(name); console.log('Finished'); } 复制代码
以上逻辑不难理解,给原有的函数加一个打日志的功能,但是这样的话,每次都要传参数给 useLogging
,而且破坏了之前的代码结构,之前直接 doSomething
就好了,现在要改成 useLogging(doSomething, 'Jiang')
。
那有没有更好的方式呢,当然有啦。
简单装饰器:
function useLogging(func) { return function() { console.log('Starting'); const result = func.apply(this, arguments) console.log('Done'); return result; } } const wrapped = useLogging(doSomething); 复制代码
以上代码返回了一个新的函数 wrapped , 调用方式和 doSomething
相同,在原来的基础上能做多一点事情。
doSomething('angry'); // Hi, I'angry const wrapped = useLogging(doSomething); wrapped('angry'); // Starting // Hi, I'angry // Done 复制代码
怎么使用装饰器?
装饰器主要有两种用法:
- 装饰类方法或属性(类成员)
class MyClass { @readonly method() { } } function readonly(target, name, descriptor) { descriptor.writable = false; return descriptor; } 复制代码
- 装饰类
@annotation class MyClass { } function annotation(target) { target.annotated = true; } 复制代码
类成员装饰器
类成员装饰器用来装饰类里面的属性、方法、 getter
和 setter
。这个装饰器函数调用三个参数调:
-
target
: 被装饰的类的原型 -
name
: 被装饰的类、属性、方法的名字 -
descriptor
: 被装饰的类、属性、方法的descriptor
,将传递给Object.defineProperty
我们来写几个装饰器,代码如下:
写一个 @readonly
装饰器,简单版实现:
class Example { @log add(a, b) { return a + b; } @unenumerable @readonly name = "alibaba" } function readonly(target, name, descriptor) { descriptor.writable = false; return descriptor; } function unenumerable(target, name, descriptor) { descriptor.enumerable = false; return descriptor; } function log(target, name, descriptor) { const original = descriptor.value; if (typeof original === 'function') { descriptor.value = function(...args) { console.log(`Arguments: ${args}`); try { const result = original.apply(this, args); console.log(`Result: ${result}`); return result; } catch (e) { console.log(`Error: ${e}`); throw e; } } } return descriptor; } const e = new Example(); // Calling add with [2, 4] e.add(2, 4); e.name = 'antd'; // Error 复制代码
我们可以通过 Babel
查看编译后的代码,也可以在本地编译。
npm i @babel/core @babel/cli npm i @babel/plugin-proposal-decorators @babel/plugin-proposal-class-properties -D 复制代码
.babelrc
文件
{ "plugins": [ ["@babel/plugin-proposal-decorators", { "legacy": true }], ["@babel/plugin-proposal-class-properties", {"loose": true}] ] } 复制代码
编译 ES6 语法输出到文件
因为没用全局安装 @babel/cli
, 建议用 npx 命令来执行,或者 ./node_modules/.bin/babel
,关于 npx
命令,可以看下官方文档
npx babel decorator.js --out-file complied.js 复制代码
编译后的代码:
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; } _applyDecoratedDescriptor(_class.prototype, "add", [log], Object.getOwnPropertyDescriptor(_class.prototype, "add"), _class.prototype) 复制代码
Babel 构建了一个 _applyDecoratedDescriptor
函数,用于装饰类成员
Object.getOwnPropertyDescriptor
Object.getOwnPropertyDescriptor()
方法返回指定对象上一个自有属性对应的属性描述符。(自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性),不是原型链上的这点很关键。
详情可以查看官方文档,这里就不细说了。
var desc = {}; // 这里对 descriptor 属性做了一层拷贝 Object['ke' + 'ys'](descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; desc.configurable = !!desc.configurable; // 没有 value 或者 initializer 属性的,都是 get 和 set 方法 if ('value' in desc || desc.initializer) { desc.writable = true; } 复制代码
这里的 initializer 是 Babel 为了配合 decorator 而产生的一个属性,就比方说对于上面代码中的 name 属性,被编译成:
_descriptor = _applyDecoratedDescriptor(_class.prototype, "name", [unenumerable, readonly], { configurable: true, enumerable: true, writable: true, initializer: function initializer() { return "alibaba"; } }) 复制代码
desc = decorators.slice().reverse().reduce(function (desc, decorator) { return decorator(target, property, desc) || desc; }, desc); 复制代码
处理多个 decorator 的情况,这里执行了slice()和reverse(),所以我们可以得出,一个类成员有多个装饰器,会由内向外执行。
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; 复制代码
最后无论是装饰方法还是属性,都会执行:
Object["define" + "Property"](target, property, desc); 复制代码
由此可见,装饰方法本质上还是使用 Object.defineProperty() 来实现的。
类装饰器
类装饰器相对简单
function log(Class) { return (...args) => { console.log(args); return new Class(...args); }; } 复制代码
@log class Example { constructor(name, age) { } } const e = new Example('Graham', 34); // [ 'Graham', 34 ] console.log(e); // Example {} 复制代码
装饰器中传入参数:
function log(name) { return function decorator(Class) { return (...args) => { console.log(`Arguments for ${name}: args`); return new Class(...args); }; } } @log('Demo') class Example { constructor(name, age) {} } const e = new Example('Graham', 34); // Arguments for Demo: args console.log(e); // Example {} 复制代码
应用
在 React 中,经常会用到 redux 或者高阶组件。
class A extends React.Component {} export default connect()(A); 复制代码
装饰器写法:
@connect() export default connect()(A); 复制代码
以上所述就是小编给大家介绍的《浅谈js中的装饰器》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 装饰器与元数据反射(1)方法装饰器
- 草根学Python(十六) 装饰器(逐步演化成装饰器)
- 一文读懂 JS 装饰器,这是一个会打扮的装饰器
- 装饰器与元数据反射(2)属与类性装饰器
- Python装饰器
- python--装饰器详解
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
iOS软件开发揭密
虞斌 / 电子工业出版社 / 2011-5-1 / 79.00元
本书以严密的体系性提供了iPhone和iPad软件开发从入门到专家的系统性知识,并提供来源于真实项目的可重用商业代码。书中的每个实例都是项目经验的提炼,深入浅出地讲解iPhone和iPad软件开发的核心技术要点,基本涵盖了iOS软件开发在真实商业项目中所需要的所有主题,并将实例介绍的技术深度和超值的实用性结合在一起,成为本书的特色。 随书附赠的光盘中包含了书中大量案例的完整工程源代码,可以让......一起来看看 《iOS软件开发揭密》 这本书的介绍吧!