以下是纯干货总结的手写版笔试题代码,拿走不谢!

Promise 变体

promise.first

第一个返回成功的 Promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
if (!Promise.first) {
Promise.first = (promiseList) => {
return new Promise((resolve, reject) => {
let rejectNum = 0;
const len = promiseList.length;

promiseList.forEach((pr) => {
Promise.resolve(pr)
.then(resolve)
.catch(() => {
rejectNum++; // 记录reject的个数
if (rejectNum == len) {
reject("all promise is reject");
}
});
});
});
};
}

promise.last

最后一个返回成功的 Promise

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
if (!Promise.last) {
Promise.last = (promiseList) => {
return new Promise((resolve, reject) => {
let num = 0;
const len = promiseList.length;
let lastResult;

const fn = () => {
if (num !== len) {
return;
}
lastResult ? resolve(lastResult) : reject("all promise is reject");
};

promiseList.forEach((pr) => {
Promise.resolve(pr)
.then((data) => {
lastResult = data;
num++;
fn();
})
.catch(() => {
num++;
fn();
});
});
});
};
}

promise.none

与 Promise.all 相反,所有的 promise 都被拒绝,则 Promise.none 变成完成状态

1
2
3
4
5
6
7
8
9
10
11
if (!Promise.none) {
Promise.none = (promiseList) => {
return Promise.all(
promiseList.map((pr) => {
return new Promise((resolve, reject) => {
return Promise.resolve(pr).then(reject, resolve);
});
})
);
};
}

Promise.any

表示只获取所有的 promise 中进入完成状态的结果,被拒绝的则忽略掉

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
if (!Promise.any) {
Promise.any = (promiseList) => {
return new Promise((resolve, reject) => {
const result = [];
let num = 0;
const len = promiseList.length;

const fn = () => {
if (num == len) {
result.length < 1 ? reject("all promise is reject") : resolve(result);
}
};

promiseList.forEach((pr) => {
Promise.resolve(pr)
.then((data) => {
result.push(data);
num++;
fn();
})
.catch(() => {
num++;
fn();
});
});
});
};
}

Promise.every

所有 promise 都进入完成状态,则返回 true,否则返回 false

1
2
3
4
5
6
7
8
9
if (!Promise.every) {
Promise.every = (promiseList) => {
return new Promise((resolve, reject) => {
Promise.all(promiseList)
.then(() => Promise.resolve(true))
.catch(() => Promise.reject(false));
});
};
}

Promisify

题意

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 使用前

fs.readFile("./index.js", (err, data) => {
if (!err) {
console.log(data.toString());
}

console.log(err);
});

// 使用promisify后

const readFile = promisify(fs.readFile);

readFile("./index.js")
.then((data) => {
console.log(data.toString());
})

.catch((err) => {
console.log("error:", err);
});

解法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function promisify(fn) {
return (...args) => {
return new Promise((resolve, reject) => {
fn.apply(this, [
...args,
(err, data) => {
if (!err) {
return resolve(data);
}

return reject(err);
},
]);
});
};
}

模拟实现 sleep

1
2
3
4
5
async function sleep(sleepTime) {
return new Promise((resolve) => {
setTimeout(resolve, sleepTime);
});
}

JS 实现一个带有并发限制的异步调度器

题意

JS 实现一个带有并发限制的异步调度器 Scheduler,保证同时运行的任务最多有十个,完善代码中 Scheduler 类:

1
2
3
4
5
6
7
8
9
Class Scheduler{
constructor(max){}
run(callback){/*...*/}
}

let s = new Scheduler(10);
s.run(async () => {
/* some async operations */
});

解法

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
Class Scheduler{
COUNT = 0;
LIST = [];
constructor(max){
this.LIMIT = 10;
}
async run(callback){
if(this.COUNT >= this.LIMIT){
// 通过await,只要不resolve,代码运行就会阻塞在这里
await new Promise((resolve) => {
this.list.push(resolve);
});

}
this.COUNT++;

const result = await callback();

this.COUNT--;

if(this.list.length > 0){
this.list.shift()(); // 解锁代码阻塞
}

return result;
}
}

let s = new Scheduler(10);
s.run(async () => {
/* some async operations */
});
s.run(async () => {
/* some async operations */
});
s.run(async () => {
/* some async operations */
});

手写 Promise

Promise 要点

  • Promise 是一个类,传入的参数是一个执行器,会立即执行
  • Promise 有三种状态
    • pending 等待
    • fulfilled 完成
    • rejected 失败
  • 状态只能改变一次,不能逆向。
    • pending -> fulfilled
    • pending -> rejected
  • Promise 使用 resolve 和 reject 两个函数来改变状态
  • then 方法内部做状态判断,执行对应的方法
  • 有静态方法 Promise.resolve 和 Promise.reject

手写版

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
// 状态常量
const PENDING = "pending";
const FULFILLED = "fulfilled";
const REJECTED = "rejected";

class Promise {
state = PENDING;
value = undefined;
reason = undefined;
onResolveCallback = [];
onRejectCallback = [];

constructor(executor) {
try {
executor(this.resolve, this.reject);
} catch (err) {
this.reject(err);
}
}
resolve(data) {
if (this.state !== PENDING) {
return;
}
this.state = FULFILLED;
this.value = data;

while (this.onResolveCallback.length > 0) {
const currentResolve = this.onResolveCallback.shift();
currentResolve(this.value);
}
}
reject(err) {
if (this.state !== PENDING) {
return;
}
this.state = REJECTED;
this.reason = err;

while (this.onRejectCallback.length > 0) {
const currentReject = this.onRejectCallback.shift();
currentReject(this.reason);
}
}
static resolve(param) {
// param是Promise对象,直接返回
if (param instanceof Promise) {
return param;
}

// 转成普通的Promise
return new Promise((resolve) => {
resolve(param);
});
}
static reject(reason) {
return new Promise((resolve, reject) => {
reject(reason);
});
}
catch(fn) {
if (this.state == REJECTED) {
typeof fn == "function" && fn(this.reason);
}
}
then(resolve, reject) {
const realResolve =
typeof resolve == "function" ? resolve : (value) => value;
const realReject =
typeof reject == "function"
? reject
: (reason) => {
throw reason;
};

// 链式调用,需要返回新的Promise实例

const newPromise = new Promise((resolve, reject) => {
// 创建一个微任务,等待Promise初始化完成

const microResolve = () => {
queueMicrotask(() => {
try {
const x = realResolve(this.value);
resolvePromise(newPromise, x, resolve, reject);
} catch (err) {
reject(err);
}
});
};

const microReject = () => {
queueMicrotask(() => {
try {
const x = realReject(this.reason);
resolvePromise(newPromise, x, reasolve, reject);
} catch (err) {
reject(err);
}
});
};

if (this.state == FULFILLED) {
return microResolve(this.value);
}
if (this.state == REJECTED) {
return microReject(this.reason);
}
if (this.state == PENDING) {
this.onResolveCallback.push(microResolve);
this.onRejectCallback.push(microReject);
}
});

return newPromise;
}
}

function resolvePromise(newPromise, x, resolve, reject) {
if (newPromise == x) {
return reject(new Error("循环引用"));
}

if (x instanceof Promise) {
x.then(resolve, reject);
} else {
resolve(x);
}
}

模拟 bind 的实现

ES5 版本

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
Function.prototype.bind =
Function.prototype.bind ||
function (context) {
if (typeof this !== "function") {
throw new Error("can't bind");
}

let self = this;
let args = Array.prototype.slice(arguments, 1); // 缓存参数

const fnOP = function () {};

const bound = function () {
let innerArgs = Array.prototype.slice(arguments);

return self.apply(
this instanceof fnOP ? this : context,
args.concat(innerArgs)
);
};

fnOP.prototype = this.prototype;
bound.prototype = new fnOP();

return bound;
};

ES6 版本

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
Function.prototype.bind =
Function.prototype.bind ||
function () {
if (typeof this !== "function") {
throw new Error("can't bind");
}

const args = Array.from(arguments);
const context = args.shift();
const self = this;

const fnOP = Object.create(this.prototype);

const fn = function () {
const innerArgs = Array.from(arguments);
return self.apply(
this instanceof fnOP ? this : context,
args.concat(innerArgs)
);
};

fn.prototype = fnOP;

return fn;
};

模拟 new 的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function Otaku(name, age) {
this.name = name;
this.age = age;

this.habit = "Games";
}

Otaku.prototype.strength = 60;

Otaku.prototype.sayYourName = function () {
console.log("I am " + this.name);
};

// 调用
myNew(Otaku, "Kevin", "18");

console.log(person.name); // Kevin
console.log(person.habit); // Games
console.log(person.strength); // 60

person.sayYourName(); // I am Kevin
1
2
3
4
5
6
7
8
// 实现
function myNew(fn, ...args) {
const obj = Object.create(fn.prototype);

const ret = fn.apply(obj, args); // ret有可能为null

return typeof ret === "object" ? ret || obj : obj;
}

深拷贝

深拷贝注意点

  • 注意类型,7 种基本类型: booleanstringnumberundefinednullSymbolBigInt
  • Object类型的可能情况(Object.prototype.toString.call):
    • [object Object]
    • [object Function] - typeof function == function
    • [object Null]
    • [object Array]
    • [object RegExp]

解法

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
function deepCopy(data, hash = new WeakMap()) {
if (typeof data !== "object" || data == null) {
return data;
}
// 判断传入的待拷贝对象是否已经存在hash中,避免循环引用造成死循环
if (hash.has(data)) {
return hash.get(data);
}

const newData = Array.isArray(data) ? [] : {};
const dataKeys = Object.keys(data);
dataKeys.forEach((key) => {
const current = data[key];
const typeString = Object.prototype.toString.call(current).toLowerCase();

if (typeof current !== "object" || current == null) {
// 基本类型
newData[key] = current;
return;
}

switch (typeString) {
case "[object array]":
newData[key] = [...deepCopy(current, hash)];
break;
case "[object set]":
newData[key] = new Set([...current.values()]);
break;
case "[object map]":
newData[key] = new Map([...current]);
break;
default:
// 普通对象进行递归操作
hash.set(data, data);
newData[key] = deepCopy(current, hash);
}
});

return newData;
}

数组去重

ES6 Set

1
2
const uniqueArr = [...new Set(arr)];
const uniqueArr = Array.from(new Set(arr));

reduce

1
2
3
4
5
6
7
8
9
function unique(arr) {
// 先排序,如果重复,则上一个下标的内容一样
return arr.sort().reduce((acc, current) => {
if (acc.length == 0 || acc[acc.length - 1] !== current) {
acc.push(current);
}
return acc;
}, []);
}

filter

1
2
3
4
5
function unique(arr) {
return arr.filter((element, index, array) => {
return array.indexOf(element) == index;
});
}

扁平化数组

使用 reduce 方法进行扁平化

1
2
3
4
5
6
7
function flattenDeep(arr) {
return Array.isArray(arr)
? arr.reduce((acc, current) => {
return [...acc, ...flattenDeep(current)];
}, [])
: [arr];
}

模拟栈

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function flattenDeep(arr) {
const stack = [...arr]; // 先平展复制到stack
const result = [];

while (stack.length) {
// 取出末尾的值
const current = stack.pop();
if (Array.isArray(current)) {
// 如果值是数组,平展后,重新推入stack
stack.push(...current);
} else {
// 末尾是普通值,则存入result
result.unshift(current);
}
}
return result;
}

柯里化

1
2
3
4
5
6
7
8
9
10
11
12
function curry(fn) {
return function curried(...args) {
if (args.length >= fn.length) {
// 传入参数已覆盖所有形参
fn.apply(this, args);
} else {
return function (...args2) {
return curried.apply(this, args.concat(args2));
};
}
};
}

防抖

1
2
3
4
5
6
7
8
9
function debounce(fn, wait) {
let timer = null;
return () => {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, Array.from(arguments));
}, wait);
};
}

节流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function throttle(fn, wait) {
let timer = null;

return () => {
if (timer !== null) {
return;
}

timer = setTimeout(() => {
fn.apply(this.Array.from(arguments));
timer = null;
}, wait);
};
}