JavaScript 的装饰器:它们是什么及如何使用
请访问我的独立博客地址:https://imsense.site/2017/06/js-decorator/
装饰器的流行应该感谢在Angular 2+中使用,在Angular中,装饰器因TypeScript能使用。但是在JavaScript中,还处于提议阶段。本文将介绍装饰器是什么,及装饰器如何让代码更加简洁和容易理解。
什么是装饰器
装饰器是用一个代码包装另一个代码的简单方式。
这个概念与之前所听过的函数复合和高阶组件相似。
这已经用于很多情况,就是简单的将一个函数包装成另一个函数:
function doSomething(name) {
console.log('Hello, ' + name);
}
function loggingDecorator(wrapped) {
return function() {
console.log('Starting');
const result = wrapped.apply(this, arguments);
console.log('Finished');
return result;
}
}
const wrapped = loggingDecorator(doSomething);
上个例子产生新函数wrapped,此函数与doSomething做同样事情,但是他们不同在于在包装函数之前和之后输出一些语句。
doSomething('Graham');
// Hello, Graham
wrapped('Graham');
// Starting
// Hello, Graham
// Finished
如何使用JavaScript装饰器
JavaScript中装饰器使用特殊的语法,使用@作为标识符,且放置在被装饰代码之前。
注意:现在装饰器还处于提议阶段,意味着还有可以变化之处
可以放置许多装饰器在同样代码之前,然后解释器会按照顺序相应执行
@log()
@immutable()
class Example {
@time('demo')
doSomething() {
}
}
上例中定义了一个类,采用了三个装饰器:两个用于类本身,一个用于类的属性:
@log能记录所有所有访问类@immutable让类不可变-也许新实例调用了Object.freeze@time会记录一个方法从执行到输出一个独特标签
现在,虽然现在浏览器或Node还没支持。但是如果使用Babel,能使用 transform-decorators-legacy插件使用装饰器。
插件中使用legacy是因为Babel 5支持处理装饰器,但是它也许会跟最终的标准有区别,所以才使用legacy这个词。
为什么使用装饰器
函数复合在JavaScript已经成为可能,但是它相当困难或不可能用于另一个代码(如类或类属性)。
装饰器提议可以用于类或属性,未来JavaScript版本可能会增加用于其他地方。
装饰器也考虑到采用较为简洁的语法。
不同类型的装饰器
现在,装饰器只支持类和类属性,这包含属性、方法、get函数和set函数
装饰器只会在程序第一次运行时执行一次,装饰的代码会被返回的值代替
类属性装饰器
属性装饰器适用于类的单独成员-无论是属性、方法、get函数或set函数。
装饰器函数调用三个参数:
- target-被修饰的类
- name-类成员的名字
- descriptor-成员描述符。对象会将这个参数传给
Object.defineProperty
@readonly是经典的例子:
function readonly(target, name, descriptor) {
descriptor.writable = false;
return descriptor;
}
上例会将成员描述符中的writable设为false。
接着用于类中属性:
class Example {
a() {}
@readonly
b() {}
}
const e = new Example();
e.a = 1;
e.b = 2;
// TypeError: Cannot assign to read only property 'b' of object '#<Example>'
但是我们可以做的更好,可以用别的形式代替装饰函数。例如,记录所有的输入和输出:
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;
}
注意我们使用了扩展运算符,会自动将所有参数转为数组。
class Example {
@log
sum(a, b) {
return a + b;
}
}
const e = new Example();
e.sum(1, 2);
// Arguments: 1,2
// Result: 3
可以让装饰器获取一些参数,例如重写log装饰器如下:
function log(name) {
return function decorator(t, n, descriptor) {
const original = descriptor.value;
if (typeof original === 'function') {
descriptor.value = function(...args) {
console.log(`Arguments for ${name}: ${args}`);
try {
const result = original.apply(this, args);
console.log(`Result from ${name}: ${result}`);
return result;
} catch (e) {
console.log(`Error from ${name}: ${e}`);
throw e;
}
}
}
return descriptor;
};
}
这与之前的log装饰器相同,只是利用了外部函数的name参数。
class Example {
@log('some tag')
sum(a, b) {
return a + b;
}
}
const e = new Example();
e.sum(1, 2);
// Arguments for some tag: 1,2
// Result from some tag: 3
类装饰器
类装饰器用于整个类,装饰器函数的参数为被装饰的构造器函数。
注意只用于构造器函数,而不适用于类的每个实例。这就意味着如果想控制实例,就必须返回一个包装版本的构造器函数。
通常,类装饰器没什么用处,因为你所需要做的,同样可以用一个简单函数来处理。你所做的只需要在结束时返回一个新的构造函数来代替类的构造函数。
回到我们记录那个例子,编写一个记录构造函数参数:
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 {}
构造Example类时会输出提供的参数,构造值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 {}
真实例子
Core decorators
Core decorators是一个库,提供了几个常见的修饰器,通过它可以更好地理解修饰器。
想理解此库,也可以去看看阮老师的关于此库的介绍
React
React广泛运用了高阶组件,这让React组件成为一个函数,并且能包含另一个组件。
使用装饰器是不错的替代法,例如,Redux库有一个connect函数,用于连接React组件和React store。
通常,是这么使用的:
class MyReactComponent extends React.Component {}
export default connect(mapStateToProps, mapDispatchToProps)(MyReactComponent);
然而,可以使用装饰器代替:
@connect(mapStateToProps, mapDispatchToProps)
export default class MyReactComponent extends React.Component {}
参考资料
JavaScript Decorators: What They Are and When to Use Them
阮老师ES6入门-修饰器
JavaScript 的装饰器:它们是什么及如何使用的更多相关文章
- JAVASCRIPT中装饰器是什么(装修)
装饰器是什么? 解码器是将另一段代码包装在一个代码中的简单方法. 这个概念类似于你以前听说过的功能成分和高阶成分. 这在许多情况下都被使用过,也就是说,成都装修公司简单地将一个函数包装到另一个函数中: ...
- 从ES6重新认识JavaScript设计模式: 装饰器模式
1 什么是装饰器模式 向一个现有的对象添加新的功能,同时又不改变其结构的设计模式被称为装饰器模式(Decorator Pattern),它是作为现有的类的一个包装(Wrapper). 可以将装饰器理解 ...
- JavaScript设计模式—装饰器模式
装饰器模式介绍 为对象添加新的功能,不改变其原有的结构和功能,原有的功能还是可以使用,跟适配器模式不一样,适配器模式原有的已经不能使用了,装饰器示例比如手机壳 UML类图和代码示例 Circle示原来 ...
- JavaScript学习笔记(四十四) 装饰器
装饰器模式(Decorator) 在装饰器模式中,可以在运行时给一个对象动态的添加额外的功能.当和静态类打交道的时候(static classes),这可能是一个挑战.但在JavaScript中,对象 ...
- Javascript 装饰器极速指南
pablo.png Decorators 是ES7中添加的JavaScript新特性.熟悉Typescript的同学应该更早的接触到这个特性,TypeScript早些时候已经支持Decorators的 ...
- javascript装饰器模式
装饰器模式 什么是装饰器 原名decorator 被翻译为装饰器 可以理解为装饰 修饰 包装等意 现实中的作用 一间房子通过装饰可以变得更华丽,功能更多 类似一部手机可以单独使用 但是很多人都愿意家个 ...
- Javascript装饰器的妙用
最近新开了一个Node项目,采用TypeScript来开发,在数据库及路由管理方面用了不少的装饰器,发觉这的确是一个好东西.装饰器是一个还处于草案中的特性,目前木有直接支持该语法的环境,但是可以通过 ...
- Python之路第一课Day4--随堂笔记(迭代生成装饰器)
上节回顾: 1.集合 a.关系测试 b.去重 2.文件操作及编码 3.函数 4.局部变量和全局变量 上节回顾 本节课内容: 1.迭代器生成器 2.装饰器 3.json pickle数据序列化 4.软件 ...
- simple_tag,filte,分页以及cookie和装饰器
自定义simple_tag 内置的方法 首先Django中包含了很多内置的方法: 这里通过lower实现 在views视图函数中写如下代码: def tp3(request): name= " ...
随机推荐
- ThinkPHP框架学习(二)
在上一节中,我主要讲到了如何获取ThinkPHP框架,以及虚拟目录和虚拟主机的配置.准备工作完成之后,就可以利用ThinkPHP去部署项目了. 先在工作目录(D:/zend/workspace)下新建 ...
- POJ - 2513 Colored Sticks(欧拉通路+并查集+字典树)
https://vjudge.net/problem/POJ-2513 题解转载自:優YoU http://user.qzone.qq.com/289065406/blog/1304742541 题 ...
- mac 无法验证副本
转: 这个是拆机后断了电源,导致时间不对,也就是说现在电脑的时间比U盘制作的时间还早,所以有这样的错误提示. 在终端里面修改时间请参考下面的代码,按回车键确认:date 062614102014.30 ...
- Codeforces 238 div2 A. Gravity Flip
题目链接:http://codeforces.com/contest/405/problem/A 解题报告:有n列箱子竖直放置,每列箱子上都有数量不等的箱子,这些箱子之间没有固定,当重力方向改为平行向 ...
- 2018 ICPC 沈阳网络赛
2018 ICPC 沈阳网络赛 Call of Accepted 题目描述:求一个算式的最大值与最小值. solution 按普通算式计算方法做,只不过要同时记住最大值和最小值而已. Convex H ...
- @PostContruct注解
@PostContruct是spring框架的注解,在方法上加该注解会在项目启动的时候执行该方法,也可以理解为在spring容器初始化的时候执行该方法.
- js和css实现内容超过边框,就自动省略,自动添加title
在项目汇总,我们有这样的需求,如果内容多了,就自动省略,自动添加title 这个需要判断判断俩个值,一个是width(),一个是scrollWidth, 在div中,如果内容没有超过边框,这俩个值是一 ...
- mysql添加远程访问权限
GRANT 权限列表 ON 数据库.表 TO 用户账号 @ 用户ip IDENTIFIED BY 用户密码 授权命令; 权限列表:允许用户执行的操作权限. 包含select,insert,update ...
- iOS仿安卓手势解锁
界面是一个九宫格的布局.九宫格实现思路. 先确定有多少列 cloum = 3; 计算出每列之间的距离 计算为: CGFloat margin = (当前View的宽度 - 列数 * 按钮的宽度) / ...
- Java编程的逻辑 (35) - 泛型 (上) - 基本概念和原理
本系列文章经补充和完善,已修订整理成书<Java编程的逻辑>,由机械工业出版社华章分社出版,于2018年1月上市热销,读者好评如潮!各大网店和书店有售,欢迎购买,京东自营链接:http:/ ...