常见题
Function篇
不能使用用箭头函数,会造成this丢失
Function.prototype._call = function (ctx = window, ...args) {
const fn = Symbol("fn"); // 使用Symbol确保不会和现有属性冲突
ctx[fn] = this; // this指向调用_call的函数
const res = ctx[fn](...args);
delete ctx[fn];
return res;
};
Function.prototype._apply = function (ctx = window, argsArr = []) {
const fn = Symbol("fn");
ctx[fn] = this;
const res = ctx[fn](...argsArr);
delete ctx[fn];
return res;
};
Function.prototype._bind = function (ctx = window, ...args) {
const _this = this;
return function () {
const fn = Symbol("fn");
ctx[fn] = _this;
const res = ctx[fn](...args, ...arguments);
delete ctx[fn];
return res;
};
};
Class篇
class Animal {
constructor(height) {
this.height = height;
}
announce() {
console.log("announce");
}
}
class Bird extends Animal {
constructor(height, size) {
super(height);
this.size = size;
}
fly() {
console.log("fly");
}
}
// function实现
function Animal(height) {
this.height = height;
}
Animal.prototype.announce = function () {
console.log("announce");
};
function Bird(height, size) {
Animal.call(this, height);
this.size = size;
}
Bird.prototype = Object.create(Animal.prototype);
Bird.prototype.fly = function () {
console.log("fly");
};
// 继承多个父类
function ParentA(propA) {
this.propertyA = propA;
}
ParentA.prototype.methodA = function () {
console.log("Method A");
};
function ParentB(propB) {
this.propertyB = propB;
}
ParentB.prototype.methodB = function () {
console.log("Method B");
};
function Child(propA, propB, prop) {
// 调用父类的构造函数
ParentA.call(this, propA);
ParentB.call(this, propB);
// 添加自身特定的属性或方法
this.propertyC = prop;
}
Child.prototype.methodC = function () {
console.log("Method C");
};
// 重点,使用一个对象将多个父类的原型属性copy过来
const FatherPrototype = Object.assign({}, ParentA.prototype, ParentB.prototype);
Object.setPrototypeOf(Child.prototype, FatherPrototype);
Object篇
// 浅拷贝
function copy(obj) {
return Object.assign({}, obj);
}
function copy(obj) {
return { ...obj };
}
function copy(obj) {
const _obj = {};
Object.keys(obj).forEach((key) => (_obj[key] = obj[key]));
return _obj;
}
// 深拷贝
// 序列化反序列化
function deepCopy(obj) {
return JSON.parse(JSON.stringify(obj)); // undefine,symbol,function的属性会被忽略
}
// 递归
function deepCopy(obj) {
const _obj = Array.isArray(obj) ? {} : [];
Object.keys(obj).forEach((key) => {
const val = obj[key];
if (typeof val === "object") {
_obj[key] = val === null ? val : deepCopy(val);
} else {
_obj[key] = val;
}
});
return _obj;
}
// 非递归,层序遍历
function deepCopy(obj) {
const _obj = Array.isArray(obj) ? [] : {};
const node = { val: obj, parent: _obj };
const queue = [node];
let len, root;
while(len = queue.length) {
while(len--) {
const node = queue.shift();
root = node.parent;
const { val } = node;
for(let k in val) {
if(typeof val[k] === 'object' && val[k] !== null) {
root[k] = {};
const _node = { val: val[k], parent: root[k]};
queue.push(_node);
} else {
root[k] = val[k];
}
}
}
}
return _obj;
}
function _instanceOf(instance, constructor) {
let proto = instance.__proto__;
while (proto) {
if (proto === constructor.prototype) {
return true;
}
proto = proto.__proto__;
}
return false;
}
function _new(constructor, ...args) {
const obj = Object.create(constructor.prototype);
const res = constructor.call(obj, ...args);
return Object.prototype.toString.call(res) === "[object Object]" ? res : obj;
}
Object._is = function(a, b) {
if(a === b) {
// 判断+0 和 -0
return a !== 0 || 1/a === 1/b;
}
// 判断NaN
return a !== a && b !== b;
}
Object._assign = function(target, ...args) {
if(target === null || target === undefined) {
throw Error('')
}
target = Object(target);
args.forEach(obj => {
for(let key in obj) {
obj.hasOwnProperty(key) && (target[key] = obj[key]);
}
});
return target;
}
Array篇
Array.prototype._forEach = function (cb, thisValue = window) {
for (let i = 0; i < this.length; i++) {
cb.call(thisValue, this[i], i, this);
}
};
Array.prototype._map = function (cb, thisValue = window) {
const res = [];
for (let i = 0; i < this.length; i++) {
res.push(cb.call(thisValue, this[i], i, this));
}
return res;
};
Array.prototype._filter = function (cb, thisValue = window) {
const res = [];
for (let i = 0; i < this.length; i++) {
cb.call(thisValue, this[i], i, this) && res.push(this[i]);
}
return res;
};
Array.prototype._every = function (cb, thisValue = window) {
for (let i = 0; i < this.length; i++) {
if (!cb.call(thisValue, this[i], i, this)) {
return false;
}
}
return true;
};
Array.prototype._some = function (cb, thisValue = window) {
for (let i = 0; i < this.length; i++) {
if (cb.call(thisValue, this[i], i, this)) {
return true;
}
}
return false;
};
Array.prototype._reduce = function (cb, ...args) {
let start = 1, accumulate = this[0];
if (args.length) {
accumulate = args[0];
start = 0;
}
for (let i = start; i < this.length; i++) {
accumulate = cb(accumulate, this[i], i, this);
}
return accumulate;
};
Array.prototype.find = function (cb, thisValue = window) {
for (let i = 0; i < this.length; i++) {
if (cb.call(thisValue, this[i], i, this)) {
return this[i];
}
}
};
Array.prototype._findIndex = function (cb, thisValue = window) {
for (let i = 0; i < this.length; i++) {
if (cb.call(thisValue, this[i], i, this)) {
return i;
}
}
return -1;
};
Array.prototype._includes = function (value, start = 0) {
start = start < 0 ? start + this.length : start;
for (let i = start; i < this.length; i++) {
if (Object.is(value, this[i])) {
return true;
}
}
return false;
};
Array.prototype._join = function (sperator = ",") {
let res = "";
for (var i = 0; i < this.length - 1; i++) {
res += this[i].toString();
res += sperator;
}
res += this[i];
return res;
};
// 数组拍平
Array.prototype.flatten = function () {
return this.reduce((pre, item) => {
return pre.concat(Array.isArray(item) ? item.flatten() : item);
}, []);
};
// 数组去重
Array.prototype._unique = function () {
const st = new Set(this);
return [...st];
};
// 数组转化为树形结构
// 1.使用Map,利用引用类型的特性
function transform(arr) {
if (!Array.isArray(arr) || !arr.length) return;
const map = new Map();
let root;
arr.forEach((item) => map.set(item.id, item));
arr.forEach((item) => {
if (!item.pid) {
root = item;
} else {
const parent = map.get(item.pid);
parent.children = parent.children || [];
parent.children.push(item);
}
});
return root;
}
// 2.层序遍历
function transform(arr) {
if (!Array.isArray(arr) || !arr.length) return;
const root = arr.find((item) => !item.pid);
const queue = [root];
let len;
while ((len = queue.length)) {
while (len--) {
const node = queue.shift();
arr.forEach((item) => {
if ((item.pid = node.id)) {
node.children = node.children || [];
node.children.push(item);
queue.push(item);
}
});
}
}
return root;
}
Promise篇
Promise._all = function (promises) {
const resArr = new Array(promises.length);
let count = 0;
return new Promise((resolve, reject) => {
promises.forEach((item, index) => {
Promise.resolve(item).then(
(value) => {
resArr[index] = value;
count++;
if (count >= promises.length) {
resolve(resArr);
}
},
(rej) => {
reject(rej);
}
);
});
});
};
Promise._race = function (promises) {
return new Promise((resolve, reject) => {
promises.forEach((item) => {
Promise.resolve(item).then(
(res) => {
resolve(res);
},
(rej) => {
reject(rej);
}
);
});
});
};
Promise._allSettled = function (promises) {
const resArr = new Array(promises.length);
let count = 0;
// 这个promise只会决议为fulfilled
return new Promise(function (resolve, reject) {
const addData = (status, value, index) => {
if (status === "fulfilled") {
resArr[index] = {
status,
value,
};
} else {
resArr[index] = {
status,
reason: value,
};
}
count++;
if (count === promises.length) {
resolve(resArr);
}
};
promises.forEach((promise, index) => {
Promise.resolve(promise).then(
(res) => {
addData("fulfilled", res, index);
},
(rej) => {
addData("rejected", rej, index);
}
);
});
});
};
Promise._any = function (promises) {
let count = 0;
return new Promise((resolve, reject) => {
promises.forEach((item) => {
Promise.resolve(item).then(
(res) => {
resolve(res);
},
() => {
count++;
if (count === promises.length) {
reject("All promises were rejected");
}
}
);
});
});
};
手写Promise
const STATE = {
PENDING: "pending",
FULFILLED: "fulfilled",
REJECTED: "rejected",
};
class MyPromise {
constructor(fn) {
this.state = STATE.PENDING;
this.value = null;
this.fulfilledSubs = [];
this.rejectedSubs = [];
try {
fn(this.resolve.bind(this), this.reject.bind(this));
} catch (error) {
this.reject(error);
}
}
resolve(result) {
if (this.state === STATE.PENDING) {
this.state = STATE.FULFILLED;
this.value = result;
setTimeout(() => {
this.fulfilledSubs.forEach((sub) => {
sub(result);
});
}, 0);
}
}
reject(reason) {
if (this.state === STATE.PENDING) {
this.state = STATE.REJECTED;
this.value = reason;
setTimeout(() => {
this.rejectedSubs.forEach((sub) => {
sub(reason);
});
}, 0);
}
}
then(onFulfilled, onRejected) {
onFulfilled = onFulfilled || ((val) => val);
onRejected =
onRejected ||
((rej) => {
throw Error(rej);
});
if (this.state === STATE.PENDING) {
this.fulfilledSubs.push(onFulfilled);
this.rejectedSubs.push(onRejected);
} else if (this.state === STATE.FULFILLED) {
setTimeout(() => {
onFulfilled(this.value);
}, 0);
} else {
setTimeout(() => {
onRejected(this.value);
}, 0);
}
}
finally(callback) {
return this.then((data) => {
// 让函数执行 内部会调用方法,如果方法是promise,需要等待它完成
// 如果当前promise执行时失败了,会把err传递到err的回调函数中
return Promise.resolve(callback()).then(() => data); // data 上一个promise的成功态
}, err => {
return Promise.resolve(callback()).then(() => {
throw err; // 把之前的失败的err,抛出去
});
})
}
}
节流、防抖
function debounce(fn, delay = 200) {
let timer;
return function (...args) {
if (timer) {
clearTimeout(timer);
timer = null;
}
timer = setTimeout(() => {
fn(...args);
timer = null;
}, delay);
};
}
function throttle(fn, delay = 200) {
let timer;
return function (...args) {
if (timer) return;
timer = setTimeout(() => {
fn(...args);
timer = null;
}, delay);
};
}
异步
并行执行异步任务
function foo() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("p1");
}, 2000);
});
}
function bar() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve("p2");
}, 2000);
});
}
// 大约四秒
async function serial() {
console.time("serial");
const p1 = await foo();
const p2 = await bar();
console.log("res", p1, p2);
console.timeEnd("serial");
}
// 大约两秒
async function parallel() {
console.time("parallel");
const p1 = foo();
const p2 = bar();
const res1 = await p1;
const res2 = await p2;
console.log("res", res1, res2);
console.timeEnd("parallel");
}
// 大约两秒
async function promiseall() {
console.time("promiseall");
const res = await Promise.all([foo(), bar()]);
console.log("res", res);
console.timeEnd("promiseall");
}
serial();
parallel();
promiseall();
基于Generator函数实现async/await
把生成器函数看做异步函数,yield当作await,可看作是执行async函数
function _async(gen) {
const it = gen();
return Promise.resolve().then(function handleNext(value) {
// 递归,自动迭代执行生成器
const next = it.next(value);
if(next.done) {
return next.value;
} else {
// yield返回的next.value是个promise
return Promise.resolve(next.value).then(handleNext)
}
})
}
asyncFunc(function* () {
const data1 = yield request('a.js');
console.log('data1', data1);
const data2 = yield request(data1 + 'b.js');
console.log('data2', data2);
return data2;
}).then(res =>{
console.log('res', res);
});
异步调度器控制最大并发执行数
class Schedule {
constructor(maxLimit) {
this.maxLimit = maxLimit;
this.count = 0;
this.cache = [];
}
add(task) {
if (this.count < this.maxLimit) {
this.run(task);
} else {
this.cache.push(task);
}
}
run(task) {
this.count++;
task()
.then(
(res) => {
console.timeLog("complish");
console.log("res", res);
},
(rej) => {
console.timeLog("complish");
console.log("rej", rej);
}
)
.finally(() => {
this.count--;
if (this.cache.length) {
const top = this.cache.shift();
this.run(top);
}
});
}
}
const createTask = (timeout, res, rej) => () =>
new Promise(function (resolve, reject) {
if (res) {
setTimeout(() => {
resolve(res);
}, timeout);
} else {
setTimeout(() => {
reject(rej);
});
}
});
const p1 = createTask(1000, 'p1')
const p2 = createTask(2000, 'p2')
const p3 = createTask(1000, 'p3')
const p4 = createTask(3000, 'p4')
const p5 = createTask(4000, 'p5')
const schedule = new Schedule(2);
console.time('complish');
schedule.add(p1);
schedule.add(p2);
schedule.add(p3);
schedule.add(p4);
schedule.add(p5);
实现一个请求池并发控制请求数量,给定url数组要获得返回结果
function requestLimit(urls = [], maxLimit = 2) {
let count = 0;
const result = new Array(urls.length).fill(false);
return new Promise((resolve, reject) => {
function request(url) {
const current = count++;
if (current >= urls.length && !result.includes(false)) {
resolve(result);
}
const url = urls[current];
fetch(url)
.then(
(res) => {
result[current] = res;
},
(rej) => {
result[current] = rej;
}
)
.finally(() => {
if (current < urls.length) {
request();
}
});
}
while (count < maxLimit) {
request();
}
});
}
function requestPool(urls, limit = 3) {
const cache = [];
const result = new Array(urls.length);
let count = 0;
return new Promise((resolve, reject) => {
function request(url, index) {
if (count < limit) {
count++;
fetch(url)
.then(
(res) => {
result[index] = res;
},
(rej) => {
result[index] = rej;
}
)
.finally(() => {
count--;
if (cache.length) {
const { url, index } = cache.shift();
request(url, index);
} else if (count === 0) {
resolve(result);
}
});
} else {
cache.push({ url, index });
}
}
urls.forEach((url, index) => {
request(url, index);
});
});
}
手写一个发布订阅
class EventBus {
constructor() {
this.clients = {};
}
$on(eventName, fn) {
this.clients[eventName] = this.clients[eventName] || [];
this.clients[eventName].push(fn);
}
$off(eventName, fn) {
if (!this.clients[eventName]) return;
if (!fn) {
this.clients[eventName] = [];
} else {
const subs = this.clients[eventName];
for (let i = subs.length - 1; i >= 0; i--) {
if (fn === subs[i]) {
subs.splice(i, 1);
}
}
}
}
$emit(eventName, ...args) {
const subs = this.clients[eventName];
if (subs && subs.length) {
subs.forEach((cb) => cb(...args));
}
}
}
高级题
函数柯里化
// 多参数柯里化;
// 将fn(a, b, c, b)转换为fn(a)(b)(c)(d);
// 需要明确fn的输入参数个数
const fn = (x, y, z, a) => x + y + z + a;
const curry = function (fn) {
return function curriedFn(...args) {
// 如果输入的参数个数少于fn接收的参数个数,继续进行递归,返回一个函数
if (args.length < fn.length) {
return function () {
// 将每次的参数收集起来,存在args数组中,最后一次性计算
return curriedFn(...args, ...arguments);
};
}
return fn.apply(this, args);
};
};
const myfn = curry(fn);
console.log(myfn(1)(2)(3)(1));
console.log(myfn(1)(2, 3)(2));
// 函数柯里化,部分求值
const fn = function (...args) {
let res = 0;
args.forEach((el) => (res += el));
return res;
};
const _curry = (fn) => {
const cache = [];
return function _curriedFn(...args) {
if (args && args.length) {
cache.push(...args);
return _curriedFn;
}
const res = fn(...cache); // this指向全局对象
cache.length = 0;
return res;
};
};
const myfn = _curry(fn);
console.log(myfn(1)(2)(3)(1)());
console.log(myfn(1)(2, 3)(2)());
console.log(myfn(1)(2)(10)());
Koa2中间件原理compose函数
function compose(middlewares) {
return function (ctx, next) {
let index = -1;
function dispatch(i) {
index = i;
if (index === middlewares.length) {
return Promise.resolve();
}
const fn = middlewares[i];
try {
return Promise.resolve(fn(ctx, dispatch.bind(null, i + 1)));
} catch (error) {
return Promise.reject(error);
}
}
return dispatch(0);
};
}
async function fn1(ctx, next) {
console.log("fn1");
await next();
console.log("end fn1");
}
async function fn2(ctx, next) {
console.log("fn2");
await next();
console.log("end fn2");
}
function fn3(ctx, next) {
console.log("fn3");
}
const finallyFn = compose([fn1, fn2, fn3]);
finallyFn(window);
生成器
function foo() {
setTimeout(() => {
it.next("foo");
}, 1000);
}
function bar() {
setTimeout(() => {
it.next("next");
}, 1000);
}
function* main() {
const res1 = yield foo();
const res2 = yield bar();
console.log("res1", res1);
console.log("res2", res2);
}
const it = main();
it.next();
function run(generator) {
const it = generator();
return Promise.resolve().then(function handleResult(value) {
const nextTick = it.next(value);
if (nextTick.done) {
return nextTick.value;
} else {
return Promise.resolve(nextTick.value).then(
handleResult,
function handleError(err) {
return Promise.reject(it.throw(err));
}
);
}
});
}
请求失败重试,超时重传
function retryRequest(url, time = 3, delay = 3000) {
return new Promise((resolve, reject) => {
let timer;
const request = (count, start, lastError) => {
if (count <= 0) {
clearTimeout(timer);
reject(lastError);
return;
}
timer = setTimeout(() => {
request(count - 1, Date.now(), "超时");
}, delay);
fetch(url)
.then((res) => {
const end = Date.now();
if (end - start < delay) {
clearTimeout(timer);
resolve(res);
}
})
.catch((error) => {
const end = Date.now();
if (end - start < delay) {
clearTimeout(timer);
request(count - 1, Date.now(), error);
}
});
};
request(time, Date.now());
});
}