第一章 JavaScript 深度剖析
模块一 函数式编程与 JS 异步编程、手写 Promise
函数式编程
函数柯里化 curry
function curry(fn) {
// 返回一个柯里化函数
return function curriedFn(...args) {
// 参数不够时继续返回柯里化函数,保存已经传入过的参数
if (args.length < fn.length) {
return function () {
return curriedFn(...args.concat(Array.from(arguments)));
};
}
// 参数足够时直接返回执行结果
return fn(...args);
};
}
function add(a, b, c) {
return a + b + c;
}
const curried = curry(add);
console.log(curried(1, 2, 3)); // 6
console.log(curried(1, 2)(3)); // 6
console.log(curried(1)(2, 3)); // 6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
函数的组合 componse
const componse =
(...args) =>
(value) =>
args.reverse().reduce((acc, fn) => fn(acc), value);
const reverse = (arr) => arr.reverse();
const first = (arr) => arr[0];
const toUpper = (s) => s.toUpperCase();
const componseFn = componse(toUpper, first, reverse);
console.log(componseFn(["one", "two", "three"])); // THREE
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
利用柯里化来调试组合函数
const trace = curry((tag, v) => {
console.log(tag, v);
return v;
});
const componseFn = componse(
toUpper,
trace("first 之后"),
first,
trace("reverse 之后"),
reverse
);
console.log(componseFn(["one", "two", "three"]));
// reverse 之后 ["three", "two", "one"]
// first 之后 three
// THREE
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
JS 异步编程
promise 基本应用,ajax 封装
function ajax(url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", url);
xhr.responseType = "json";
xhr.onload = function () {
if (this.status === 200) {
resolve(this.resopnse);
} else {
reject(new Error(this.statusText));
}
};
xhr.send();
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
通用的 Generator 方案
function* main() {
try {
const users = yield ajax("/api/user");
const posts = yield ajax("/api/post");
const urls = yield ajax("/api/url");
} catch (e) {
console.log(e);
}
}
function co(generator) {
const g = generator();
function handleResult(result) {
if (result.done) return;
result.value.then(
(data) => {
handleResult(g.next(data));
},
(err) => {
g.throw(err);
}
);
}
handleResult(g.next());
}
co(main);
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
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
async await 改造,本质是 Generator 的语法糖
async function main() {
try {
const users = await ajax("/api/user");
const posts = await ajax("/api/post");
const urls = await ajax("/api/url");
} catch (e) {
console.log(e);
}
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
手写 Promise
// promise状态
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";
// 对回调的返回值进行处理
function resolvePromise(promise, value, resolve, reject) {
// 返回原来promise时,出现循环调用,报错
if (promise === value) {
return reject(
new TypeError("Chaining cycle detected for promise #<Promise>")
);
}
if (value instanceof MyPromise) {
// 回调值为promise时定义成功回调
value.then(resolve, reject);
} else {
// 回调值为普通值时直接执行成功回调
resolve(value);
}
}
class MyPromise {
// 使用try catch,在执行器函数报错时也能捕获错误并reject
constructor(executor) {
try {
executor(this.resolve, this.reject);
} catch (e) {
this.reject(e);
}
}
// 初始状态
status = PENDING;
// resolve 值
value = undefined;
// reject 原因
reason = undefined;
// then 方法成功回调
successCb = [];
// then 方法失败回调
failCb = [];
// 成功回调
resolve = (value) => {
// 只有pending状态可以修改
if (this.status !== PENDING) return;
// 储存回调值,修改状态,执行成功回调函数
this.value = value;
this.status = FULFILLED;
while (this.successCb.length) {
this.successCb.shift()();
}
};
// 失败回调
reject = (reason) => {
// 只有pending状态可以修改
if (this.status !== PENDING) return;
// 储存失败原因,修改状态,执行失败回调函数
this.reason = reason;
this.status = REJECTED;
while (this.failCb.length) {
this.failCb.shift()();
}
};
// 定义回调
then(successCb, failCb) {
// 将 successCb 和 failCb 格式化为函数
successCb = successCb ? successCb : (value) => value;
failCb = failCb
? failCb
: (reason) => {
throw reason;
};
// 生成新 promise 用于返回
const promise = new MyPromise((resolve, reject) => {
if (this.status === FULFILLED) {
// 处理 FULFILLED 状态,执行成功回调,因为需要 用到 promise 变量,所以用 setTimeout 0 包一层异步调用
setTimeout(() => {
// try catch 执行,方便捕获成功回调的错误
try {
let returnValue = successCb(this.value);
resolvePromise(promise, returnValue, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
} else if (this.status === REJECTED) {
// try catch 执行,方便捕获失败回调的错误
setTimeout(() => {
try {
let returnValue = failCb(this.reason);
resolvePromise(promise, returnValue, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
} else {
// 异步的情况下,状态会是pending,先把回调函数储存起来,等待状态变化后再执行回调
this.successCb.push(() => {
setTimeout(() => {
try {
let returnValue = successCb(this.value);
resolvePromise(promise, returnValue, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
this.failCb.push(() => {
setTimeout(() => {
try {
let returnValue = failCb(this.reason);
resolvePromise(promise, returnValue, resolve, reject);
} catch (e) {
reject(e);
}
}, 0);
});
}
});
// 返回生成的 promise
return promise;
}
finally(cb) {
// 成功或者失败的情况下都会执行finally,并返回一个promise,给后续链式调用
return this.then(
(value) => {
return MyPromise.resolve(cb()).then(() => value);
},
(reason) => {
return MyPromise.resolve(cb()).then(() => {
throw reason;
});
}
);
}
// 捕获reject错误
catch(cb) {
return this.then(undefined, cb);
}
static all(array) {
const result = [];
let index = 0;
return new MyPromise((resolve, reject) => {
// 添加结果进数组,全部添加完成之后resolve最终结果
function addData(key, value) {
result[key] = value;
index++;
if (index === array.length) {
resolve(result);
}
}
// 给数组中的每个值都添加then回调,有一个失败就reject
for (let i = 0; i < array.length; i++) {
const current = array[i];
if (current instanceof MyPromise) {
current.then(
(value) => addData(i, value),
(reason) => reject(reason)
);
} else {
addData(i, array[i]);
}
}
});
}
// 静态resolve方法将传入的值包装成一个promise
static resolve(value) {
if (value instanceof MyPromise) return value;
return new MyPromise((resolve) => resolve(value));
}
// 静态rece方法将返回数组中首个resolve或者reject的结果
static race(array) {
return new MyPromise((resolve, reject) => {
for (let i = 0; i < array.length; i++) {
const current = array[i];
if (current instanceof MyPromise) {
current.then(
(value) => resolve(value),
(reason) => reject(reason)
);
} else {
resolve(current);
}
}
});
}
}
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
181
182
183
184
185
186
187
188
189
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
181
182
183
184
185
186
187
188
189
模块二 ES 新特性与 TypeScript、JS 性能优化
JS 性能优化
垃圾回收机制中的 GC 算法
引用计数算法
设置引用数,判断当前引用数是否为 0,引用数字为 0 时立即回收。
优点:
- 发现垃圾时立即回收;
- 最大限度减少程序暂停,保证内存可用。
缺点:
- 无法回收循环引用的对象;
- 需要实时修改引用数字,时间开销大,资源消耗大。
标记清除算法
分为标记和清除两个阶段,先遍历所有对象找到可达对象做标记,再遍历所有对象清除没有标记的对象,并抹掉第一阶段做的标记。
优点:
- 可以回收循环引用的对象。
缺点:
- 清除会造成空间碎片化,导致清除后内存地址不连续,不能得到空间最大化的使用。
标记整理算法
标记整理算法是标记清除算法的增强操作,会在清除之前先对内存地址进行整理,移动对象的位置,让对象的内存地址连续。
Performance工具使用
内存监控的几种方式:
- 浏览器任务管理器,shift + esc 开启面板;
- Timeline时序图记录,查看内存变化;
- 堆快照查找分离DOM,查找detached,查看页面是否存在分离DOM;
- 判断是否存在频繁的垃圾回收,通过查看内存变化是否频繁来判断。