- 这里就不介绍修饰器了, 感兴趣的可以去看 juejin.cn/editor/draf…
- 本文我们介绍一下 修饰器 Stage1 的用法
- 本文的所有示例都可以通过 Babel在线工具 进行编译后直接运行
注意: 在 Decorators version 的配置要选用 Legacy 因为我们使用的语法是比较旧的
简介
在 JavaScript 中,装饰器有一种特殊的语法。它们以 @
符号为前缀,放置在我们需要装饰的代码之前。(可参考: 难道你还不知道 JS修饰器(Decorators)
)。另外,可以一次使用多个装饰器。
先看一个 mobx4 文档上的例子
import {observer} from "mobx-react"
import {observable} from "mobx"
@observer
class Timer extends React.Component {
@observable secondsPassed = 0
componentWillMount() {
setInterval(() => {
this.secondsPassed++
}, 1000)
}
render() {
return (<span>Seconds passed: { this.secondsPassed } </span> )
}
}
ReactDOM.render(<Timer />, document.body);
observer
和observable
就是修饰器的用法- 用法就是示例的样子, 那
observer
和observable
是什么呢? 他们都干了什么呢? - 其实他们都是
Function
, 我们按照修饰器的语法写一个Function
也是可以当作修饰器使用的
装饰器类型
函数装饰器
- 你可能会注意到有的文章说到函数是没有修饰器的, 这个主要看自己的理解
- 我们通过
@
符号是没办法修饰一个Function
的, 语法上不支持 - 但我们可以通过使用高阶函数的方式, 实现修饰
Function
的功能- 高阶函数: 一个函数接收另一个函数作为参数,这种函数就称之为高阶函数
function logMessage(message) {
console.log(message);
}
function record(fun) {
let count = 0;
return function(...args) {
console.log(`Function: ${fun.name || ''}, 运行次数: ${++count}`);
return fun.apply(this, args);
}
}
const newLogMessage = record(logMessage);
newLogMessage('message 0');
// Function: logMessage, 运行次数: 1
// message 0
newLogMessage('message 1');
// Function: logMessage, 运行次数: 2
// message 1
newLogMessage('message 2');
// Function: logMessage, 运行次数: 3
// message 2
类(class)装饰器
API 定义
function decorator(target: Class) {
// do something
}
- 装饰器本质上是一个
Function
, 他有一个参数即 class - 我们可以根据实际需求实现装饰器内部的处理逻辑
用法
@decorator
class A {}
// 等同于
class A {}
A = decorator(A) || A;
示例一
function classDecorator(target) {
target.color = 'red';
target.logMsg = function(msg) {
console.log(msg);
}
Object.assign(target.prototype, {
name: '张三',
logName() {
console.log(this.name);
}
});
}
@classDecorator
class A {}
console.log(A.color); // red
A.logMsg('msg'); // msg
const a = new A();
console.log(a.name); // 张三
a.logName(); // 张三
我们对 classDecorator
装饰器做个简单的分析
- 在第 2~5 行, 我们对被装饰的 class 新增了一个静态属性
color
和一个静态方法logMsg
- 在第 7~12 行, 我们对被装饰的 class 新增了类属性
name
和类方法logName
Object.defineProperties(target.prototype, {
name: {
value: '张三',
writable: false,
},
logName: {
value: function() {
console.log(this.name);
}
}
})
我们也可以使用这种方式代替上面的处理, 这样我们就可以设置一些属性的描述符
示例二
function recordClassDecorator(recorder) {
return function(Target) {
return function(...args) {
const target = new Target(...args);
recorder.push(target);
return target;
}
}
}
const recorder = [];
@recordClassDecorator(recorder)
class User {
name;
constructor(name) {
this.name = name;
}
}
cons = zhangsan = new User('张三');
cons = lisi = new User('李四');
console.log(recorder); // [User, User]
简单的说一下这个示例的知识点
recordClassDecorator
本身不是一个装饰器方法, 但他执行后返回的值是一个装饰器, 这样也是可以的. 通过这种方式可以支持传递参数- 这个示例告诉我们怎么在实例化的时候做一些处理
类成员(属性/方法)
API 定义
function decorator(
target: Class,
name: PropertyKey,
descriptor: PropertyDescriptor
): PropertyDescriptor {
// do something
return descriptor;
}
- target: 类的原型对象
- name: 要装饰的属性名(string | number | symbol)
- descriptor: 属性的描述对象
用法
class A {
@decorator1
name = '';
@decorator2
name2() {
}
}
示例一(类方法装饰器)
function log(target, name, descriptor) {
const oldValue = descriptor.value;
descriptor.value = function() {
console.log(`Calling ${name} with`, arguments);
return oldValue.apply(this, arguments);
};
return descriptor;
}
class Math {
@log
add(a, b) {
return a + b;
}
}
const math = new Math();
// passed parameters should get logged now
math.add(2, 4);
- 可以看到
add
方法已经被重写了, 他第一步是打印信息, 第二步是实现原函数的处理逻辑
示例二(类属性装饰器)
function readonly(target, name, descriptor) {
descriptor.writable = false;
return descriptor;
}
class User {
@readonly
name = '张三'
}
const user = new User();
user.name = '李四';
console.log(user.name); // 张三
name
属性被readonly
修饰后, 去修改name
后,name
的值没有发生变化
示例三(访问器装饰器)
function counter() {
let count = 0;
return function (target, name, descriptor) {
const oldGet = descriptor.get;
descriptor.get = function(...args) {
console.log(`class: ${target.constructor.name}, 属性: ${name}, 计算值次数: ${++count}`);
const value = oldGet.apply(this, args);
return value;
};
return descriptor;
}
}
class A {
@counter()
get c() {
return 'tset';
}
}
const a = new A();
a.c;
console.log(a.c);
// class: A, 属性: c, 计算值次数: 1
// class: A, 属性: c, 计算值次数: 2
// test
装饰器执行顺序
function a(value) {
console.log('a-outer');
return function(target) {
console.log('a-inner');
target.a = value;
}
}
function b(value) {
console.log('b-outer');
return function(target) {
console.log('b-inner');
target.b = value;
}
}
@a('a')
@b('b')
class A {}
// or: @a('a') @b('b') class A {}
const aa = new A();
// a-outer
// b-outer
// b-inner
// a-inner
- 可以看到创建修饰器时, 是从上到下的顺序执行了代码
- 真正调用修饰器对内容进行修饰, 是从下到上的顺序执行的(也可以理解为由内到外)
小结
- 装饰器的种类及使用方法
- 函数:
decorator(fun)
- 类(class):
@decorator class
ordecorator(class)
- 类属性(包括类方法和类属性):
class User { @readonly name = '张三'}
- 函数:
- 如何实现装饰器: 装饰器的本质是一个函数, 通过实现函数的行为完成对 方法 类 的装饰
- 那我们如何实现装饰器? 它有哪些特征?
- 函数: 入参-被修饰的函数, 出参-新函数
- 类: 入参-是
class
, 出参-可以有也可以没有- 当需要在
new
的阶段加入一些我们的处理时, 需要有返回参数, 参数是一个函数(注意: 不要用箭头函数) - 我们要在这个函数里面完成
new
操作并返回实例;
- 当需要在
- 类属性: 入参–
target
class,name
字段key,descriptor
属性的描述对象, 出参-属性的描述对象
相关文档
参考文档
es6.ruanyifeng.com/#docs/decor…
www.tslang.cn/docs/handbo…
juejin.cn/post/705999…
© 版权声明
文章版权归作者所有,未经允许请勿转载,侵权请联系 admin@trc20.tw 删除。
THE END