Hook
在上节的内容中,我们探讨了 tapable 的 API 概览,包括 Hook 是如何分类的以及有哪些 Hook。这本节中,我们将继续深入源码,探讨 Hook 基类的原理。
# 目录
# Hook
源码如下:
const CALL_DELEGATE = function(...args) {
// 注意:这里会将 _createCall 创建的函数缓存到 this.call 上
// 所以下文 _resetCompilation 就是清除缓存,重新生成函数
this.call = this._createCall("sync");
return this.call(...args);
};
const CALL_ASYNC_DELEGATE = function(...args) {
this.callAsync = this._createCall("async");
return this.callAsync(...args);
};
const PROMISE_DELEGATE = function(...args) {
this.promise = this._createCall("promise");
return this.promise(...args);
};
class Hook {
constructor(args = [], name = undefined) {
this._args = args;
this.name = name;
this.taps = [];
this.interceptors = [];
this._call = CALL_DELEGATE;
this.call = CALL_DELEGATE;
this._callAsync = CALL_ASYNC_DELEGATE;
this.callAsync = CALL_ASYNC_DELEGATE;
this._promise = PROMISE_DELEGATE;
this.promise = PROMISE_DELEGATE;
this._x = undefined;
this.compile = this.compile;
this.tap = this.tap;
this.tapAsync = this.tapAsync;
this.tapPromise = this.tapPromise;
}
compile(options) {
throw new Error("Abstract: should be overridden");
}
// Hook 的调用取决于 _createCall,而 _createCall 依赖于 compile 的实现
_createCall(type) {
return this.compile({
taps: this.taps,
interceptors: this.interceptors,
args: this._args,
type: type
});
}
_tap(type, options, fn) {
// 参数处理
if (typeof options === "string") {
options = {
name: options.trim()
};
} else if (typeof options !== "object" || options === null) {
throw new Error("Invalid tap options");
}
if (typeof options.name !== "string" || options.name === "") {
throw new Error("Missing name for tap");
}
if (typeof options.context !== "undefined") {
deprecateContext();
}
// merge options,type 和 fn 合并到 options
options = Object.assign({ type, fn }, options);
// 触发 register 拦截器
options = this._runRegisterInterceptors(options);
// 将 Callback 入队列 (taps)
this._insert(options);
}
tap(options, fn) {
this._tap("sync", options, fn);
}
tapAsync(options, fn) {
this._tap("async", options, fn);
}
tapPromise(options, fn) {
this._tap("promise", options, fn);
}
_runRegisterInterceptors(options) {
for (const interceptor of this.interceptors) {
// 触发 register 拦截器,拦截器的返回值可以更改 options
if (interceptor.register) {
const newOptions = interceptor.register(options);
if (newOptions !== undefined) {
options = newOptions;
}
}
}
return options;
}
withOptions(options) {
const mergeOptions = opt =>
Object.assign({}, options, typeof opt === "string" ? { name: opt } : opt);
return {
name: this.name,
tap: (opt, fn) => this.tap(mergeOptions(opt), fn),
tapAsync: (opt, fn) => this.tapAsync(mergeOptions(opt), fn),
tapPromise: (opt, fn) => this.tapPromise(mergeOptions(opt), fn),
intercept: interceptor => this.intercept(interceptor),
isUsed: () => this.isUsed(),
withOptions: opt => this.withOptions(mergeOptions(opt))
};
}
isUsed() {
return this.taps.length > 0 || this.interceptors.length > 0;
}
intercept(interceptor) {
this._resetCompilation();
this.interceptors.push(Object.assign({}, interceptor));
if (interceptor.register) {
for (let i = 0; i < this.taps.length; i++) {
this.taps[i] = interceptor.register(this.taps[i]);
}
}
}
// Hook 有三种调用方法,重置调用方法,重新生成调用函数(_createCall)
// 注意:此函数代价很小,因为创建调用函数的成本会分摊在下次调用 Hook 时
_resetCompilation() {
this.call = this._call;
this.callAsync = this._callAsync;
this.promise = this._promise;
}
_insert(item) {
// 重置调用函数
this._resetCompilation();
let before;
// item.before 是 string 或者 string[],处理为 Set
if (typeof item.before === "string") {
before = new Set([item.before]);
} else if (Array.isArray(item.before)) {
before = new Set(item.before);
}
let stage = 0;
if (typeof item.stage === "number") {
stage = item.stage;
}
let i = this.taps.length;
// 从后向前遍历 items
while (i > 0) {
i--;
const x = this.taps[i];
// 依次将 item 后移一位,为什么不采用 linked-list?
// 因为顺序很重要,插入操作频繁,搜索操作很少,使用 linked-list 可以提高性能
this.taps[i + 1] = x;
const xStage = x.stage || 0;
if (before) {
// 使用在队列中最前面的 before 配置
if (before.has(x.name)) {
before.delete(x.name);
continue;
}
if (before.size > 0) {
continue;
}
}
if (xStage > stage) {
continue;
}
// 如果没有传 before 且权重 >= 尾项的权重 ,插入队尾即可,因此将 break 循环,并加入到 i++ 的位置
i++;
break;
}
// 因为跳出时item 的权重 >= i 指针指向的 item 的权重,所以 item 应该放在 i 指针后面一位,所以 i++
this.taps[i] = item;
}
}
Object.setPrototypeOf(Hook.prototype, null);
module.exports = Hook;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
这里如如下几个问题需要注意:
- Hook 终于其调用需要依赖于 compile 来生成调用函数,这个部分还加入了缓存和重置的逻辑。为什么需要重新调用 compile 来处理 Hook 的调用呢?从
_resetCompilation
调用情况可知,在 Tap 或者 intercept 被调用时,即注册 Callback 或者拦截器时会重置调用函数。 - taps 采用了数组来管理。在插入操作时代价较大,个人认为使用链表最合适,具体使用数组的原因尚不清楚。before 表示插入到谁的前面,如果 before 是一个数组,以在队列最前面的为准;stage 表示权重。这里的算法可以研究下。
- Hook 的原型被设置为 null。这么做可能是为了表示 Hook 没有继承自任何对象。
- 这里的 compile 函数使用了空对象模式,详见:空对象模式 - 设计模式 (opens new window)
# Object.setPrototypeOf()
The Object.setPrototypeOf() method sets the prototype (i.e., the internal [[Prototype]] property) of a specified object to another object or null. All JavaScript objects inherit properties and methods from a prototype
. It is generally considered the proper way to set the prototype of an object.
参考
编辑 (opens new window)
上次更新: 2022/09/06, 14:25:16