mirror of
https://github.com/ruanyf/es6tutorial.git
synced 2025-05-29 05:42:20 +00:00
docs(decorator): add stage 3 proposal content
This commit is contained in:
parent
41970f7db0
commit
fbd7faf0bc
@ -2,7 +2,7 @@
|
||||
|
||||
[说明] Decorator 提案经过了大幅修改,目前还没有定案,不知道语法会不会再变。下面的内容完全依据以前的提案,已经有点过时了。等待定案以后,需要完全重写。
|
||||
|
||||
装饰器(Decorator)是一种与类(class)相关的语法,用来注释或修改类和类方法。许多面向对象的语言都有这项功能,目前有一个[提案](https://github.com/tc39/proposal-decorators)将其引入了 ECMAScript。
|
||||
装饰器(Decorator)用来增强 JavaScript 类(class)的功能,许多面向对象的语言都有这种语法,目前有一个[提案](https://github.com/tc39/proposal-decorators)将其引入了 ECMAScript。
|
||||
|
||||
装饰器是一种函数,写成`@ + 函数名`。它可以放在类和类方法的定义前面。
|
||||
|
||||
@ -19,6 +19,53 @@
|
||||
|
||||
上面代码一共使用了四个装饰器,一个用在类本身,另外三个用在类方法。它们不仅增加了代码的可读性,清晰地表达了意图,而且提供一种方便的手段,增加或修改类的功能。
|
||||
|
||||
装饰器可以用来装饰四种类型的值。
|
||||
|
||||
- 类
|
||||
- 类的属性(public, private, and static)
|
||||
- 类的方法(public, private, and static)
|
||||
- 类的访问器(accessor)(public, private, and static)
|
||||
|
||||
## 装饰器 API(新语法)
|
||||
|
||||
装饰器是一个函数,它的 API 采用 TypeScript 描述,就是下面的形式。
|
||||
|
||||
```typescript
|
||||
type Decorator = (value: Input, context: {
|
||||
kind: string;
|
||||
name: string | symbol;
|
||||
access: {
|
||||
get?(): unknown;
|
||||
set?(value: unknown): void;
|
||||
};
|
||||
private?: boolean;
|
||||
static?: boolean;
|
||||
addInitializer?(initializer: () => void): void;
|
||||
}) => Output | void;
|
||||
```
|
||||
|
||||
装饰器函数调用时,会接收到两个参数。
|
||||
|
||||
- `value`:被装饰的值,某些情况下可能是`undefined`(装饰属性时)。
|
||||
- `context`:一个对象,包含了被装饰的值的上下文信息。
|
||||
|
||||
另外,`input`和`output`表示输入的值和输出的值,每种装饰器都不一样,放在后面介绍。所有装饰器都可以不返回任何值。
|
||||
|
||||
`context`对象的属性如下。
|
||||
|
||||
- `kind`:字符串,表示装饰类型,可能取值有`class`、`method`、`getter`、`setter`、`field`、`accessor`。
|
||||
- `name`:被装饰的值的名称: The name of the value, or in the case of private elements the description of it (e.g. the readable name).
|
||||
- `access`:对象,包含访问这个值的方法,即存值器和取值器。
|
||||
- `static`: 布尔值,该值是否为静态元素。
|
||||
- `private`:布尔值,该值是否为私有元素。
|
||||
- `addInitializer`:函数,允许用户增加初始化逻辑。
|
||||
|
||||
装饰器的执行步骤如下。
|
||||
|
||||
1. 计算各个装饰器的值,按照从左到右,从上到下的顺序。
|
||||
1. 调用方法装饰器。
|
||||
1. 调用类装饰器。
|
||||
|
||||
## 类的装饰
|
||||
|
||||
装饰器可以用来装饰整个类。
|
||||
@ -154,6 +201,118 @@ export default class MyReactComponent extends React.Component {}
|
||||
|
||||
相对来说,后一种写法看上去更容易理解。
|
||||
|
||||
## 类装饰器(新语法)
|
||||
|
||||
类装饰器的类型描述如下。
|
||||
|
||||
```typescript
|
||||
type ClassDecorator = (value: Function, context: {
|
||||
kind: "class";
|
||||
name: string | undefined;
|
||||
addInitializer(initializer: () => void): void;
|
||||
}) => Function | void;
|
||||
```
|
||||
|
||||
类装饰器的第一个参数,就是被装饰的类。第二个参数是上下文对象,如果被装饰的类是一个匿名类,`name`属性就为`undefined`。
|
||||
|
||||
类装饰器可以返回一个新的类,取代原来的类,也可以不返回任何值。如果返回的不是构造函数,就会报错。
|
||||
|
||||
下面是一个例子。
|
||||
|
||||
```javascript
|
||||
function logged(value, { kind, name }) {
|
||||
if (kind === "class") {
|
||||
return class extends value {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
console.log(`constructing an instance of ${name} with arguments ${args.join(", ")}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
@logged
|
||||
class C {}
|
||||
|
||||
new C(1);
|
||||
// constructing an instance of C with arguments 1
|
||||
```
|
||||
|
||||
如果不使用装饰器,类装饰器实际上执行的是下面的语法。
|
||||
|
||||
```javascript
|
||||
class C {}
|
||||
|
||||
C = logged(C, {
|
||||
kind: "class",
|
||||
name: "C",
|
||||
}) ?? C;
|
||||
|
||||
new C(1);
|
||||
```
|
||||
|
||||
## 方法装饰器(新语法)
|
||||
|
||||
方式装饰器使用 TypeScript 描述类型如下。
|
||||
|
||||
```typescript
|
||||
type ClassMethodDecorator = (value: Function, context: {
|
||||
kind: "method";
|
||||
name: string | symbol;
|
||||
access: { get(): unknown };
|
||||
static: boolean;
|
||||
private: boolean;
|
||||
addInitializer(initializer: () => void): void;
|
||||
}) => Function | void;
|
||||
```
|
||||
|
||||
方法装饰器的第一个参数`value`,就是所要装饰的方法。
|
||||
|
||||
方法装饰器可以返回一个新方法,取代原来的方法,也可以不返回值,表示依然使用原来的方法。如果返回其他类型的值,就会报错。
|
||||
|
||||
下面是一个例子。
|
||||
|
||||
```typescript
|
||||
function logged(value, { kind, name }) {
|
||||
if (kind === "method") {
|
||||
return function (...args) {
|
||||
console.log(`starting ${name} with arguments ${args.join(", ")}`);
|
||||
const ret = value.call(this, ...args);
|
||||
console.log(`ending ${name}`);
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class C {
|
||||
@logged
|
||||
m(arg) {}
|
||||
}
|
||||
|
||||
new C().m(1);
|
||||
// starting m with arguments 1
|
||||
// ending m
|
||||
```
|
||||
|
||||
上面示例中,装饰器`@logged`返回一个函数,代替原来的`m()`方法。
|
||||
|
||||
这里的装饰器实际上是一个语法糖,真正的操作是像下面这样,改掉原型链上面`m()`方法。
|
||||
|
||||
```javascript
|
||||
class C {
|
||||
m(arg) {}
|
||||
}
|
||||
|
||||
C.prototype.m = logged(C.prototype.m, {
|
||||
kind: "method",
|
||||
name: "m",
|
||||
static: false,
|
||||
private: false,
|
||||
}) ?? C.prototype.m;
|
||||
```
|
||||
|
||||
## 方法的装饰
|
||||
|
||||
装饰器不仅可以装饰类,还可以装饰类的属性。
|
||||
@ -366,6 +525,355 @@ function loggingDecorator(wrapped) {
|
||||
const wrapped = loggingDecorator(doSomething);
|
||||
```
|
||||
|
||||
## 存取器装饰器(新语法)
|
||||
|
||||
存取器装饰器使用 TypeScript 描述的类型如下。
|
||||
|
||||
```typescript
|
||||
type ClassGetterDecorator = (value: Function, context: {
|
||||
kind: "getter";
|
||||
name: string | symbol;
|
||||
access: { get(): unknown };
|
||||
static: boolean;
|
||||
private: boolean;
|
||||
addInitializer(initializer: () => void): void;
|
||||
}) => Function | void;
|
||||
|
||||
type ClassSetterDecorator = (value: Function, context: {
|
||||
kind: "setter";
|
||||
name: string | symbol;
|
||||
access: { set(value: unknown): void };
|
||||
static: boolean;
|
||||
private: boolean;
|
||||
addInitializer(initializer: () => void): void;
|
||||
}) => Function | void;
|
||||
```
|
||||
|
||||
存取器装饰器的第一个参数就是原始的存值器(setter)和取值器(getter)。
|
||||
|
||||
存取器装饰器的返回值如果是一个函数,就会取代原来的存取器。本质上,就像方法装饰器一样,修改发生在类的原型对象上。它也可以不返回任何值,继续使用原来的存取器。如果返回其他类型的值,就会报错。
|
||||
|
||||
存取器装饰器对存值器(setter)和取值器(getter)是分开作用的。下面的例子里面,`@foo`只装饰`get x()`,不装饰`set x()`。
|
||||
|
||||
```javascript
|
||||
class C {
|
||||
@foo
|
||||
get x() {
|
||||
// ...
|
||||
}
|
||||
|
||||
set x(val) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
上一节的`@logged`装饰器稍加修改,就可以用在存取装饰器。
|
||||
|
||||
```javascript
|
||||
function logged(value, { kind, name }) {
|
||||
if (kind === "method" || kind === "getter" || kind === "setter") {
|
||||
return function (...args) {
|
||||
console.log(`starting ${name} with arguments ${args.join(", ")}`);
|
||||
const ret = value.call(this, ...args);
|
||||
console.log(`ending ${name}`);
|
||||
return ret;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class C {
|
||||
@logged
|
||||
set x(arg) {}
|
||||
}
|
||||
|
||||
new C().x = 1
|
||||
// starting x with arguments 1
|
||||
// ending x
|
||||
```
|
||||
|
||||
如果去掉语法糖,使用传统语法来写,就是改掉了类的原型链。
|
||||
|
||||
```javascript
|
||||
class C {
|
||||
set x(arg) {}
|
||||
}
|
||||
|
||||
let { set } = Object.getOwnPropertyDescriptor(C.prototype, "x");
|
||||
set = logged(set, {
|
||||
kind: "setter",
|
||||
name: "x",
|
||||
static: false,
|
||||
private: false,
|
||||
}) ?? set;
|
||||
|
||||
Object.defineProperty(C.prototype, "x", { set });
|
||||
```
|
||||
|
||||
## 属性装饰器(新语法)
|
||||
|
||||
属性装饰器的类型描述如下。
|
||||
|
||||
```typescript
|
||||
type ClassFieldDecorator = (value: undefined, context: {
|
||||
kind: "field";
|
||||
name: string | symbol;
|
||||
access: { get(): unknown, set(value: unknown): void };
|
||||
static: boolean;
|
||||
private: boolean;
|
||||
}) => (initialValue: unknown) => unknown | void;
|
||||
```
|
||||
|
||||
属性装饰器的第一个参数是`undefined`,即没有一个直接的输入值。用户可以选择让装饰器返回一个初始化函数,当该属性被赋值时,这个初始化函数会自动运行,它会收到属性的初始值,然后返回一个新的初始值。属性装饰器也可以不返回任何值。只要返回的不是函数,而是其他类型的值,就会报错。
|
||||
|
||||
下面是一个例子。
|
||||
|
||||
```javascript
|
||||
function logged(value, { kind, name }) {
|
||||
if (kind === "field") {
|
||||
return function (initialValue) {
|
||||
console.log(`initializing ${name} with value ${initialValue}`);
|
||||
return initialValue;
|
||||
};
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
class C {
|
||||
@logged x = 1;
|
||||
}
|
||||
|
||||
new C();
|
||||
// initializing x with value 1
|
||||
```
|
||||
|
||||
如果不使用装饰器语法,属性装饰器的实际作用如下。
|
||||
|
||||
```javascript
|
||||
let initializeX = logged(undefined, {
|
||||
kind: "field",
|
||||
name: "x",
|
||||
static: false,
|
||||
private: false,
|
||||
}) ?? (initialValue) => initialValue;
|
||||
|
||||
class C {
|
||||
x = initializeX.call(this, 1);
|
||||
}
|
||||
```
|
||||
|
||||
## accessor 命令(新语法)
|
||||
|
||||
类装饰器引入了一个新命令`accessor`,用来属性的前缀。
|
||||
|
||||
```javascript
|
||||
class C {
|
||||
accessor x = 1;
|
||||
}
|
||||
```
|
||||
|
||||
它是一种简写形式,相当于声明属性`x`是私有属性`#x`的存取接口。上面的代码等同于下面的代码。
|
||||
|
||||
```javascript
|
||||
class C {
|
||||
#x = 1;
|
||||
|
||||
get x() {
|
||||
return this.#x;
|
||||
}
|
||||
|
||||
set x(val) {
|
||||
this.#x = val;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`accessor`命令前面,还可以加上`static`命令和`private`命令。
|
||||
|
||||
```javascript
|
||||
class C {
|
||||
static accessor x = 1;
|
||||
accessor #y = 2;
|
||||
}
|
||||
```
|
||||
|
||||
`accessor`命令前面还可以接受属性装饰器。
|
||||
|
||||
```javascript
|
||||
function logged(value, { kind, name }) {
|
||||
if (kind === "accessor") {
|
||||
let { get, set } = value;
|
||||
|
||||
return {
|
||||
get() {
|
||||
console.log(`getting ${name}`);
|
||||
|
||||
return get.call(this);
|
||||
},
|
||||
|
||||
set(val) {
|
||||
console.log(`setting ${name} to ${val}`);
|
||||
|
||||
return set.call(this, val);
|
||||
},
|
||||
|
||||
init(initialValue) {
|
||||
console.log(`initializing ${name} with value ${initialValue}`);
|
||||
return initialValue;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
class C {
|
||||
@logged accessor x = 1;
|
||||
}
|
||||
|
||||
let c = new C();
|
||||
// initializing x with value 1
|
||||
c.x;
|
||||
// getting x
|
||||
c.x = 123;
|
||||
// setting x to 123
|
||||
```
|
||||
|
||||
上面的示例等同于使用`@logged`装饰器,改写`accessor`属性的 getter 和 setter 方法。
|
||||
|
||||
用于`accessor`的属性装饰器的类型描述如下。
|
||||
|
||||
```typescript
|
||||
type ClassAutoAccessorDecorator = (
|
||||
value: {
|
||||
get: () => unknown;
|
||||
set(value: unknown) => void;
|
||||
},
|
||||
context: {
|
||||
kind: "accessor";
|
||||
name: string | symbol;
|
||||
access: { get(): unknown, set(value: unknown): void };
|
||||
static: boolean;
|
||||
private: boolean;
|
||||
addInitializer(initializer: () => void): void;
|
||||
}
|
||||
) => {
|
||||
get?: () => unknown;
|
||||
set?: (value: unknown) => void;
|
||||
initialize?: (initialValue: unknown) => unknown;
|
||||
} | void;
|
||||
```
|
||||
|
||||
`accessor`命令的第一个参数接收到的是一个对象,包含了`accessor`命令定义的属性的存取器 get 和 set。属性装饰器可以返回一个新对象,其中包含了新的存取器,用来取代原来的,即相当于拦截了原来的存取器。此外,返回的对象还可以包括一个`initialize`函数,用来改变私有属性的初始值。装饰器也可以不返回值,如果返回的是其他类型的值,或者包含其他属性的对象,就会报错。
|
||||
|
||||
## addInitializer() 方法(新语法)
|
||||
|
||||
除了属性装饰器,其他装饰器的上下文对象还包括一个`addInitializer()`方法,用来完成初始化操作。
|
||||
|
||||
它的运行时间如下。
|
||||
|
||||
- 类装饰器:在类被完全定义之后。
|
||||
- 方法装饰器:在类构造期间运行,在属性初始化之前。
|
||||
- 静态方法装饰器:在类定义期间运行,早于静态属性定义,但晚于类方法的定义。
|
||||
|
||||
下面是一个例子。
|
||||
|
||||
```javascript
|
||||
function customElement(name) {
|
||||
return (value, { addInitializer }) => {
|
||||
addInitializer(function() {
|
||||
customElements.define(name, this);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@customElement('my-element')
|
||||
class MyElement extends HTMLElement {
|
||||
static get observedAttributes() {
|
||||
return ['some', 'attrs'];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
上面的代码等同于下面不使用装饰器的代码。
|
||||
|
||||
```javascript
|
||||
class MyElement {
|
||||
static get observedAttributes() {
|
||||
return ['some', 'attrs'];
|
||||
}
|
||||
}
|
||||
|
||||
let initializersForMyElement = [];
|
||||
|
||||
MyElement = customElement('my-element')(MyElement, {
|
||||
kind: "class",
|
||||
name: "MyElement",
|
||||
addInitializer(fn) {
|
||||
initializersForMyElement.push(fn);
|
||||
},
|
||||
}) ?? MyElement;
|
||||
|
||||
for (let initializer of initializersForMyElement) {
|
||||
initializer.call(MyElement);
|
||||
}
|
||||
```
|
||||
|
||||
下面是方法装饰器的例子。
|
||||
|
||||
```javascript
|
||||
function bound(value, { name, addInitializer }) {
|
||||
addInitializer(function () {
|
||||
this[name] = this[name].bind(this);
|
||||
});
|
||||
}
|
||||
|
||||
class C {
|
||||
message = "hello!";
|
||||
|
||||
@bound
|
||||
m() {
|
||||
console.log(this.message);
|
||||
}
|
||||
}
|
||||
|
||||
let { m } = new C();
|
||||
|
||||
m(); // hello!
|
||||
```
|
||||
|
||||
上面的代码等同于下面不使用装饰器的代码。
|
||||
|
||||
```javascript
|
||||
class C {
|
||||
constructor() {
|
||||
for (let initializer of initializersForM) {
|
||||
initializer.call(this);
|
||||
}
|
||||
|
||||
this.message = "hello!";
|
||||
}
|
||||
|
||||
m() {}
|
||||
}
|
||||
|
||||
let initializersForM = []
|
||||
|
||||
C.prototype.m = bound(
|
||||
C.prototype.m,
|
||||
{
|
||||
kind: "method",
|
||||
name: "m",
|
||||
static: false,
|
||||
private: false,
|
||||
addInitializer(fn) {
|
||||
initializersForM.push(fn);
|
||||
},
|
||||
}
|
||||
) ?? C.prototype.m;
|
||||
```
|
||||
|
||||
## core-decorators.js
|
||||
|
||||
[core-decorators.js](https://github.com/jayphelps/core-decorators.js)是一个第三方模块,提供了几个常见的装饰器,通过它可以更好地理解装饰器。
|
||||
|
Loading…
x
Reference in New Issue
Block a user