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= " ...
随机推荐
- 数据结构(三)串---KMP模式匹配算法之获取next数组
(一)获取模式串T的next数组值 1.回顾 我们所知道的KMP算法next数组的作用 next[j]表示当前模式串T的j下标对目标串S的i值失配时,我们应该使用模式串的下标为next[j]接着去和目 ...
- Github 开源项目(二) jsmpeg-vnc
参考文章:http://blog.csdn.net/qq_28877125/article/details/70141713 适用于Windows的低延迟,高帧率屏幕共享服务器以及用于浏览器的客户端 ...
- scala 基础到高阶
本文打算对这小段时间学习 scala 以及 spark 编程技术做个小结,一来温故而知新,而来为以后查阅方便 spark scala 入门小例子 文本文件 UserPurchaseHistory.c ...
- J2EE完全手册(一)
为了使开发者能尽快的开发企业级的应用程序,Sun在1999年推出一种基于J2SE(用于开发桌面应的Java标准版)的开发模型:J2EE,用于开发服务器应用程序与服务的Java企业版,他运行于J2EE服 ...
- 爬虫笔记之自如房屋价格图片识别(价格字段css背景图片偏移显示)
一.前言 自如房屋详情页的价格字段用图片显示,特此破解一下以丰富一下爬虫笔记系列博文集. 二.分析 & 实现 先打开一个房屋详情页观察一下: 网页的源代码中没有直接显示价格字段,价格的显示是使 ...
- mybatis关联查询数据模型分析——(七)
1. 数据模型分析思路 1.每张表记录的数据内容 分模块对每张表记录的内容进行熟悉,相当 于你学习系统 需求(功能)的过程. 2.每张表重要的字段设置 非空字段.外键字段 3.数据库级别表与表 ...
- Linux下JDK到底应该安装在哪儿?
1 Linux 目录结构 即使这是个菜鸟级的问题,也经常难住老鸟.我就见过很资深的程序员把JDK不合适地安装到/home目录下.虽然不一定有最正确的安装位置,但一定有不适当的安装位置.为了确定我们到底 ...
- SQL Server修改默认端口号1433
方法1: 1) SqlServer服务使用两个端口:TCP-1433.UDP-1434. 其中1433用于供SqlServer对外提供服务,1434用于向请求者返回SqlServer使用了那个TCP/ ...
- mysql 字符编码设置
安装mysql时如果字符编码为默认值latin1,则需要修改为utf8以便支持中文数据. 命令如下: 1.显示数据库字符集 mysql> show create database test;+- ...
- MAVEN:不能互相引用
工程A,工程B,工程C,这三个工程:C依赖B,B依赖A,这是没有问题的. 但是不能A依赖B,B又依赖A,这是不允许的.