浅谈js中的装饰器
栏目: JavaScript · 发布时间: 6年前
内容简介:装饰器模式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--装饰器详解
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Java Web入门经典
王国辉、陈英 / 机械工业出版社 / 2013-6 / 69.00元
《Java Web入门经典》以初学者为核心,全面介绍了JavaWeb开发中常用的各种技术。内容排列上由浅入深,让读者循序渐进掌握编程技术;在内容讲解上结合丰富的图解和形象的比喻,帮助读者理解“晦涩难懂”的技术;在内容形式上附有大量的提示、技巧、说明等栏目,夯实读者编程技术,丰富编程经验。全书共分4篇19章,其中,第一篇为“起步篇”,主要包括开启JavaWeb之门、不可不知的客户端应用技术、驾驭Ja......一起来看看 《Java Web入门经典》 这本书的介绍吧!