以下是纯干货总结的手写版笔试题代码,拿走不谢!
Promise 变体
promise.first
代码语言:javascript复制第一个返回成功的 Promise
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
代码语言:javascript复制最后一个返回成功的 Promise
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
代码语言:javascript复制与 Promise.all 相反,所有的 promise 都被拒绝,则 Promise.none 变成完成状态
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
代码语言:javascript复制表示只获取所有的 promise 中进入完成状态的结果,被拒绝的则忽略掉
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
代码语言:javascript复制所有 promise 都进入完成状态,则返回 true,否则返回 false
if (!Promise.every) {
Promise.every = (promiseList) => {
return new Promise((resolve, reject) => {
Promise.all(promiseList)
.then(() => Promise.resolve(true))
.catch(() => Promise.reject(false));
});
};
}
Promisify
题意
代码语言:javascript复制// 使用前
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);
});
解法
代码语言:javascript复制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
代码语言:javascript复制async function sleep(sleepTime) {
return new Promise((resolve) => {
setTimeout(resolve, sleepTime);
});
}
JS 实现一个带有并发限制的异步调度器
题意
JS 实现一个带有并发限制的异步调度器 Scheduler,保证同时运行的任务最多有十个,完善代码中 Scheduler 类:
代码语言:javascript复制Class Scheduler{
constructor(max){}
run(callback){/*...*/}
}
let s = new Scheduler(10);
s.run(async () => {
/* some async operations */
});
解法
代码语言:javascript复制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
手写版
代码语言:javascript复制// 状态常量
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 版本
代码语言:javascript复制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 版本
代码语言:javascript复制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 的实现
代码语言:javascript复制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
代码语言:javascript复制// 实现
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 种基本类型:
boolean
、string
、number
、undefined
、null
、Symbol
、BigInt
Object
类型的可能情况(Object.prototype.toString.call):- object Object
- object Function -
typeof function == function
- object Null
- object Array
- object RegExp
解法
代码语言:javascript复制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
代码语言:javascript复制const uniqueArr = [...new Set(arr)];
const uniqueArr = Array.from(new Set(arr));
reduce
代码语言:javascript复制function unique(arr) {
// 先排序,如果重复,则上一个下标的内容一样
return arr.sort().reduce((acc, current) => {
if (acc.length == 0 || acc[acc.length - 1] !== current) {
acc.push(current);
}
return acc;
}, []);
}
filter
代码语言:javascript复制function unique(arr) {
return arr.filter((element, index, array) => {
return array.indexOf(element) == index;
});
}
扁平化数组
使用 reduce 方法进行扁平化
代码语言:javascript复制function flattenDeep(arr) {
return Array.isArray(arr)
? arr.reduce((acc, current) => {
return [...acc, ...flattenDeep(current)];
}, [])
: [arr];
}
模拟栈
代码语言:javascript复制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;
}
柯里化
代码语言:javascript复制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));
};
}
};
}
防抖
代码语言:javascript复制function debounce(fn, wait) {
let timer = null;
return () => {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, Array.from(arguments));
}, wait);
};
}
节流
代码语言:javascript复制function throttle(fn, wait) {
let timer = null;
return () => {
if (timer !== null) {
return;
}
timer = setTimeout(() => {
fn.apply(this.Array.from(arguments));
timer = null;
}, wait);
};
}
最近笔者在整理第一本电子书书稿《前端面试手册》,有兴趣的同学可以关注下~
喜欢我文章的朋友,可以通过以下方式关注我:
- 「star」 或 「watch」 我的GitHub blog - RSS订阅我的个人博客:王先生的基地