Skip to content

Commit 1fc9e44

Browse files
younggglcyxcatliu
authored andcommitted
wip: ch decorator, part 1 & 2
1 parent f6797e1 commit 1fc9e44

File tree

3 files changed

+172
-1
lines changed

3 files changed

+172
-1
lines changed

advanced/decorator.md

+171
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
# 装饰器
2+
3+
写在前面:本章只介绍 TypeScript 5.0+ 的装饰器用法,对于 5.0 以下的版本,请参考 [TypeScript 官方文档](https://www.typescriptlang.org/docs/handbook/decorators.html)
4+
5+
## 什么是装饰器
6+
7+
首先,什么是装饰器呢?[维基百科](https://en.wikipedia.org/wiki/Decorator_pattern)是这么说的:
8+
9+
> In [object-oriented programming](https://en.wikipedia.org/wiki/Object-oriented_programming), the **decorator pattern** is a [design pattern](https://en.wikipedia.org/wiki/Design_pattern_(computer_science)) that allows behavior to be added to an individual [object](https://en.wikipedia.org/wiki/Object_(computer_science)), dynamically, without affecting the behavior of other instances of the same [class](https://en.wikipedia.org/wiki/Class_(computer_science)).
10+
11+
本人的蹩足翻译:在 OOP (面向对象编程)中,装饰器模式是一种允许动态地往一个对象上添加自定义行为,而又不影响该对象所属的类的其他实例的一种设计模式。
12+
13+
> 什么是 OOP 和类?[前面的章节](https://ts.xcatliu.com/advanced/class.html)做过介绍。
14+
15+
这句话未免过于拗口了,我们不妨换个角度去切入。
16+
17+
## 装饰器的使用场景
18+
19+
要知道,一切设计模式的诞生,都是为了解决某个问题。在 JavaScript 的世界中,装饰器通常出现于以下场景:
20+
21+
1. 提供一种易读且容易实现的方式,修改类或者类的方法,避免出现大量重复的代码。
22+
23+
下面以修改类的方法为例。
24+
25+
首先,假设我们有一个 `Animal` 类:
26+
27+
```ts
28+
class Animal {
29+
type: string
30+
constructor(type: string) {
31+
this.type = type
32+
}
33+
34+
greet() {
35+
console.log(`Hello, I'm a(n) ${this.type}!`)
36+
}
37+
}
38+
39+
const xcat = new Animal('cat')
40+
xcat.greet() // Hello, I'm a(n) cat!
41+
```
42+
43+
该类有一个 greet 方法,和调用方打招呼。
44+
45+
假如说,我还希望根据不同的 `type`,往 console 打印不同动物的叫声呢?
46+
47+
聪明的你或许想到了,这不就是**类的继承**吗!在子类的 `greet()` 方法中,实现不同的逻辑,再调用 `super.greet()` 即可。
48+
49+
```ts
50+
class Xcat extends Animal {
51+
constructor() {
52+
super('cat')
53+
}
54+
55+
greet() {
56+
console.log('meow~ meow~')
57+
super.greet()
58+
}
59+
}
60+
61+
const xcat = new Xcat()
62+
xcat.greet() // meow~ meow~
63+
// Hello, I'm a(n) cat!
64+
```
65+
66+
用装饰器实现,也不妨为一种思路,比如在 `Animal` 类中,为 `greet()` 方法添加「打印不同动物叫声的」行为:
67+
68+
```ts
69+
class Animal {
70+
type: string
71+
constructor(type: string) {
72+
this.type = type
73+
}
74+
75+
@yelling
76+
greet() {
77+
console.log(`Hello, I'm a(n) ${this.type}!`)
78+
}
79+
}
80+
81+
const typeToYellingMap = {
82+
cat: 'meow~ meow~'
83+
}
84+
85+
function yelling(originalMethod: any, context: ClassMethodDecoratorContext) {
86+
return function(...args: any[]) {
87+
console.log(typeToYellingMap[this.type])
88+
originalMethod.call(this, ...args)
89+
}
90+
}
91+
92+
const xcat = new Animal('cat')
93+
xcat.greet() // meow~ meow~
94+
// Hello, I'm a(n) cat!
95+
```
96+
97+
`Animal.greet()` 方法上出现的 `@yelling` ,就是 TypeScript 中装饰器的写法,即 @ + 函数名的组合。
98+
99+
上述示例对装饰器的应用属于**方法装饰器**,此类装饰器本身接收两个参数,一是被装饰的方法,二是方法装饰器的上下文。方法装饰器应返回一个函数,此函数在运行时真正被执行。在上述例子中,我们在装饰器返回的函数中做了两件事情:
100+
101+
1. 打印相应类别的动物的叫声。
102+
2. 调用 `originalMethod.call(this, …args)` ,确保原方法(即装饰器所装饰的方法)能够正确地被执行。
103+
2. 结合「**依赖注入**」这一设计模式,优化模块与 class 的依赖关系。
104+
105+
什么是依赖注入呢?引用同事 [zio](https://github.com/ziofat) 的原话:
106+
107+
> **依赖注入其实是将一个模块所依赖的部分作为参数传入,而不是由模块自己去构造。**
108+
109+
可见,依赖注入解决了实际工程项目中,类、模块间依赖关系层级复杂的问题,将构造单例的行为交由实现依赖注入的框架去处理。
110+
111+
举个例子:
112+
113+
```ts
114+
@injectable
115+
class Dog implements IAnimal {
116+
sayHi() {
117+
console.log('woof woof woof')
118+
}
119+
}
120+
121+
@injectable
122+
class Cat implements IAnimal {
123+
sayHi() {
124+
console.log('meow meow meow')
125+
}
126+
}
127+
128+
class AnimalService {
129+
constructor(
130+
@inject dog: Dog
131+
@inject cat: Cat
132+
) {
133+
this._dog = dog
134+
this._cat = cat
135+
}
136+
137+
sayHiByDog() {
138+
this._dog.sayHi()
139+
}
140+
141+
sayHiByCat() {
142+
this._cat.sayHi()
143+
}
144+
}
145+
```
146+
147+
在上述代码中,`@injectable` 将一个类标记为「可被注入的」,在面向业务的类(即 `AnimalService`)中,使用 `@inject` 注入此类的单例,实现了「依赖倒置」。注意到这里的 `implements IAnimal` 用法,也是实战中依赖注入运用的精妙之处 —— 关心接口,而非具体实现。
148+
149+
3. 实现「AOP」,即 Aspect-oriented programming,面向切面编程。
150+
151+
所谓的「切面」,可以理解成,在复杂的各个业务维度中,只关注一个维度的事务。
152+
153+
例如,使用装饰器,实现对类的某个方法的执行时间记录:
154+
155+
```ts
156+
class MyService {
157+
@recordExecution
158+
myFn() {
159+
// do something...
160+
}
161+
}
162+
163+
function recordExecution(originalMethod: any, context: ClassMethodDecoratorContext) {
164+
return function(...args: any[]) {
165+
console.time('mark execution')
166+
originalMethod.call(this, ...args)
167+
console.timeEnd('mark execution')
168+
}
169+
}
170+
```
171+

advanced/further-reading.md

-1
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,4 @@
1818
- [Symbols](http://www.typescriptlang.org/docs/handbook/symbols.html)[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Symbols.html)):新原生类型,这是 [ES6 的知识](http://es6.ruanyifeng.com/#docs/symbol)
1919
- [Iterators and Generators](http://www.typescriptlang.org/docs/handbook/iterators-and-generators.html)[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Iterators%20and%20Generators.html)):迭代器,这是 [ES6 的知识](http://es6.ruanyifeng.com/#docs/iterator)
2020
- [Namespaces](http://www.typescriptlang.org/docs/handbook/namespaces.html)[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Namespaces.html)):避免全局污染,现在已被 [ES6 Module](http://es6.ruanyifeng.com/#docs/module) 替代
21-
- [Decorators](http://www.typescriptlang.org/docs/handbook/decorators.html)[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Decorators.html)):修饰器,这是 [ES7 的一个提案](http://es6.ruanyifeng.com/#docs/decorator)
2221
- [Mixins](http://www.typescriptlang.org/docs/handbook/mixins.html)[中文版](https://zhongsp.gitbooks.io/typescript-handbook/content/doc/handbook/Mixins.html)):一种编程模式,与 TypeScript 没有直接关系,可以参考 [ES6 中 Mixin 模式的实现](http://es6.ruanyifeng.com/#docs/class#Mixin模式的实现)

pagic.config.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ export default {
8585
'advanced/class-and-interfaces.md',
8686
'advanced/generics.md',
8787
'advanced/declaration-merging.md',
88+
'advanced/decorator.md',
8889
'advanced/further-reading.md',
8990
],
9091
},

0 commit comments

Comments
 (0)