前端面试经典手写题
1、手写Promise
class Promise2 {
state = "pending";
callbacks = [];
constructor(fn) {
fn(this.resolve.bind(this), this.reject.bind(this));
}
resolve(result) {
if (this.state !== "pending") return;
this.state = "fullFilled";
nextTick(() => {
this.callbacks.forEach((handle) => {
if (typeof handle[0] === "function") {
handle[0].call(undefined, result);
}
});
});
}
reject(reason) {
if (this.state !== "pending") return;
this.state = "rejected";
nextTick(() => {
this.callbacks.forEach((handle) => {
if (typeof handle[1] === "function") {
handle[1].call(undefined, reason);
}
});
});
}
then(success, fail) {
const handle = [];
if (typeof success === "function") {
handle[0] = success;
}
if (typeof fail === "function") {
handle[1] = fail;
}
this.callbacks.push(handle);
return this;
}
}
function nextTick(fn) {
if (process !== undefined && typeof process.nextTick === "function") {
return process.nextTick(fn);
} else {
var counter = 1;
const observer = new MutationObserver(fn);
var textNode = document.createTextNode(String(counter));
observer.observe(textNode, {
// 踪字符更改
characterData: true,
});
counter += 1;
textNode.data = String(counter);
}
}
// 方法返回一个Promise实例,此实例在 iterable 参数内所有的promise 都完成(resolved)时回调完成(resolve);
// 如果参数中 promise有一个失败(rejected),此实例回调失败(reject),失败的原因是第一个失败promise的结果。
Promise2.all = function(arrP) {
let list = [];
len = 0;
return new Promise2((resolve, reject) => {
for (let i = 0; i < arrP.length; i++) {
arrP[i].then(
(val) => {
list[i] = val;
len++;
len === arrP.length && resolve(list);
},
(err) => {
reject(error);
}
);
}
});
};
Promise.prototype.myAll = (iterator) => {
return new Promise((resolve, reject) => {
const ret = []
let count = 0
Array.from(iterator).forEach((item, index) => {
Promise.resolve(item).then(data => {
ret[index] = data
count++
if(count === iterator.length) {
resolve(ret)
}
}, reject)
})
})
}
// 方法返回一个Promise实例,一旦迭代器中的某个 promise 完成(resolved)或失败(rejected),返回的 promise 就会 resolve 或 reject
Promise2.race = function(arrP) {
let flag1 = false;
let flag2 = false;
return new Promise2((resolve, reject) => {
for (let i = 0; i < arrP.length; i++) {
arrP[i].then(
(data) => {
!flag2 && !flag1 && resolve(data);
flag1 = true;
return;
},
(error) => {
!flag2 && !flag1 && reject(error);
flag2 = true;
return;
}
);
}
});
};
new Promise2((resolve, reject) => {
let [val, time] = [Math.random(), Math.random() * 1000];
setTimeout(() => {
val > 0.2 ? resolve(val) : reject(val);
}, time);
}).then(
(val) => console.log("promise 测试:", val),
(err) => console.error("promise 测试:" + err)
);
const getPList = () => {
let arrP = [];
for (let i = 0; i < 10; i++) {
arrP[i] = new Promise2((resolve, reject) => {
let [v, t] = [Math.random(), Math.random() * 1000];
setTimeout(() => {
v > 0.1 ? resolve(v) : reject(v);
}, t);
});
}
return arrP;
};
Promise2.all(getPList()).then(
(data) => console.log("promise.all 测试:", data),
(err) => console.error("promise.all 测试:" + err)
);
Promise2.race(getPList()).then(
(data) => console.log("promise.race 测试:", data),
(err) => console.error("promise.race 测试:" + err)
);
2、手写new
// 新生成一个对象
// 将构造函数的作用域赋值给新对象(即绑定新对象的 this)
// 执行构造函数中的代码(即为这个新对象添加属性)
// 返回新对象
function myNew() {
// 创建对象
let obj = new Object();
// 取第一个参数
let fn = Array.prototype.shift.call(arguments);
//obj.__proto__指向fn.prototype
obj.__proto__ = fn.prototype;
// 执行结果
let result = fn.apply(obj, arguments);
return typeof result === "object" ? result : obj;
}
function Person(name) {
this.name = name;
}
// var p1 = myNew(Person, "xx");
// console.log(p1.name);
function P(name) {
this.name = name;
return 1;
}
var p2 = myNew(P, "xm");
console.log(p2);
3、手写instanceof
实现思路:
1、leftVaule代表实例对象
2.rightVaule代表构造函数
3.利用typeof方法,判断输入的leftVaule是否为对象,如果不是,则返回false
4.遍历leftVaule的原型链,直到找到rightVaule的prototype,如果查找失败的话,返回false,反之,返回true
function myInstanceof(leftValue, rightValue) {
if (typeof leftValue !== "object" || leftValue === null) return false;
let leftProto = leftValue.__proto__;
let rightProto = rightValue.prototype;
while (true) {
if (leftProto === null) {
return false;
}
if (leftProto === rightProto) {
return true;
}
leftProto = leftProto.__proto__;
}
}
myInstanceof([], Array);
4、并发请求限制
限制请求数,一个请求完成替换下一个请求
第一次分段
第二次添加下一个
控制startIndex与endIndex
终止态:返回值已等于请求数,执行cb
// class LimitFetch {}
//
// 第一次分段
// 第二次添加下一个
// 控制startIndex与endIndex
// 终止态:返回值已等于请求数,执行cb
class LimitFetch {
constructor(opts) {
this.requestList = opts.requestList;
this.limit = opts.limit;
this.cb = opts.cb;
this.startIndex = 0;
this.result = {};
this.resultCount = 0;
this.batchRequest();
}
batchRequest(num) {
const endIndex = this.startIndex + (num || this.limit);
const len = this.requestList.length;
for (let i = this.startIndex; i < endIndex; i++) {
this.startIndex++;
if (!this.requestList[i]) return;
this.requestList[i]().then((res) => {
this.result[i] = res;
this.resultCount++;
if (this.resultCount === len) {
this.cb(this.result);
}
if (i < len - 1) {
this.batchRequest(1);
}
});
}
}
}
// 函数写法
function limitFetch(requestList, limit, cb) {
let startIndex = 0;
let results = {};
let resultCount = 0;
function batchRequest(num) {
const endIndex = startIndex + (num || limit);
for (let i = startIndex, len = requestList.length; i < endIndex; i++) {
if (!requestList[i]) continue;
startIndex++;
requestList[i]().then((res) => {
resultCount++;
results[i] = res;
if (i < len - 1) {
batchRequest(1);
}
if (resultCount === len) {
cb(results);
}
});
}
}
batchRequest();
}
let requestList = [];
function fn(time) {
return function () {
// console.log(time);
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log(time);
resolve(time);
}, time);
});
};
}
for (let i = 0; i < 5; i++) {
requestList.push(fn(1 * 1000));
}
// limitFetch(requestList, 3, (res) => {
// console.log(res);
// });
new LimitFetch({
requestList,
limit: 3,
cb: (res) => {
console.log(res);
},
});
// 限制并发
// 可以新增加
class BtRequest{
constructor(opts) {
this.limit = opts.limit
this.isRequest = false
this.queue = []
}
add(fn) {
this.queue.push(fn)
if(!this.isRequest) {
this.request()
}
}
request(end) {
this.isRequest = true
end = end || this.limit
const requestList = this.queue.splice(0, end)
if(!requestList.length) {
this.isRequest = false
}
requestList.forEach(item => {
Promise.resolve(item()).then((res) => {
console.log(1,res);
this.request(1)
})
})
}
}
const request = new BtRequest({limit: 1})
request.add(() => {
console.log(100);
// return 500
})
request.add(() => {
console.log(200);
// return 600
})
request.add(() => {
console.log(300);
// return 700
})
request.add(() => {
console.log(400);
// return 800
})
5、手写发布订阅者
使用一个对象作为缓存
on 负责把方法发布到缓存的 EventName 对应的数组
emit 负责遍历触发(订阅) EventName 下的方法数组
off 找方法的索引,并删除
// 使用一个对象作为缓存
// on 负责把方法发布到缓存的 EventName 对应的数组
// emit 负责遍历触发(订阅) EventName 下的方法数组
// off 找方法的索引,并删除
function indexOf(a, b) {
return a.indexOf(b);
}
class EventBus {
constructor() {
this.cache = {};
}
on(eventName, fn) {
this.cache[eventName] = this.cache[eventName] || [];
this.cache[eventName].push(fn);
}
off(eventName, fn) {
const index = this.cache[eventName].indexOf(fn);
if (index !== -1) {
this.cache[eventName].splice(index, 1);
}
}
emit(eventName) {
this.cache[eventName].forEach((fn) => {
fn();
});
}
once(eventName, cb) {
const one = (...args) => {
cb(...args)
this.off(eventName, one)
}
this.on(eventName,one)
}
}
6、手写一个搜索的组件
支持防抖
<template>
<div>
{{a}}
<input type='text' @input="onInput()">
<p>{{res}}</p>
</div>
</template>
<script>
const fetch = () => Promise.resolve('this is fetch data')
export default{
data() {
return {
a: '1',
res: undefined
}
},
methods: {
deboundce(fn, time) {
let timer;
return function() {
if(timer) {
clearTimeout(timer)
}
timer = setTimeout(fn, time)
}
},
async fetchData() {
console.log(11)
// return Promise.resolve(1)
this.res = await fetch()
},
async onInput() {
const fn = this.deboundce(this.fetchData, 1000)
fn()
// console.log(data)
}
}
}
</script>
7、手写Promise.allSettled
Promise.allSettled 只关心所有 promise 是不是都被 settle 了,不管其是 rejected状态的 promise,还是非 rejected状态(即fulfilled)的 promise, 我都可以拿到它的最终状态并对其进行处理
Promise.allSettled 的结果数组中可能包含以下两种格式的数据
{status:"fulfilled", value:result} 对于成功的响应
{status:"rejected", reason:error} 对于 error
const promise1 = Promise.resolve(3)
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100,'foo'))
const promise3 = [promise1, promise2]
Promise.myAllSettled = function(promises) {
return new Promise(resolve => {
const data = [], len = promises.length
let count = len;
for(let i = 0; i < len; i++) {
const promise = promises[i]
promise.then(res => {
data[i] = {status: 'fulfilled', value: res}
},error => {
data[i] = {status: 'rejected', value: error}
}).finally(() => {
if(!--count) {
resolve(data)
}
})
}
})
}
Promise.myAllSettled(promise3)
.then(results => results.forEach(result => console.log(result.status)))
8、手写bind
要支持能做为构造函数
思路:Function 的原型对象上增加一个函数,返回值是一个函数,函数的fn.prototype.constructor 指向函数和函数的 prototype 指向 Object.create(this.prototype)
const obj = {
name: 'xiao'
}
function func(first,last){
console.log(first + this.name, last);
}
Function.prototype.myBind = function(context,...args ){
console.log(context);
context.fn = this;
const fn = function() {
context.fn.apply(context,[...args])
}
fn.prototype = Object.create(this.prototype)
fn.prototype.constructor = this;
return fn
}
const fn1 = func.myBind(obj,'li', 'ming')
fn1()
const fn2 = new fn1()
console.log(fn2);
9、手写一个防抖
// 防抖 // 不管事件触发频率多高,一定在事件触发n秒后才执行,如果你在一个事件触发的 n 秒内又触发了这个事件,就以新的事件的时间为准,n秒后才执行,总之,触发完事件 n 秒内不再触发事件,n秒后再执行。 // 关联记忆: 函数防抖就是法师发技能的时候要读条,技能读条没完再按技能就会重新读条。
function debounce(fn, wait, immediate) {
let timeout;
return function () {
let context = this;
let args = arguments;
if (timeout) clearTimeout(timeout);
if (immediate) {
var callNow = !timeout;
timeout = setTimeout(() => {
timeout = null;
}, wait);
if (callNow) fn.apply(context, args);
} else {
timeout = setTimeout(function () {
fn.apply(context, args);
}, wait);
}
};
}
10、手写一个 深拷贝
// 基础版本
function clone2=(target, map = new WeakMap()) {
if (typeof target === 'object') {
let cloneTarget = Array.isArray(target) ? [] : {};
if(map.get(target)) return map.get(target)
map.set(target, cloneTarget)
for (const key in target) {
cloneTarget[key] = clone(target[key], map);
}
return cloneTarget;
} else {
return target;
}
};
// 资深版本
function isObject (target) {
const type = typeof target
return target !== null && (type === 'object' || type === 'function')
}
function getType(target) {
return Object.prototype.toString.call(target)
}
function getInit(target) {
const Ctor = target.constructor
return new Ctor()
}
const mapTag = '[object Map]';
const setTag = '[object Set]';
const arrayTag = '[object Array]';
const objectTag = '[object Object]';
const argsTag = '[object Arguments]'
const boolTag = '[object Boolean]';
const dateTag = '[object Date]';
const errorTag = '[object Error]';
const numberTag = '[object Number]';
const regexpTag = '[object RegExp]';
const stringTag = '[object String]';
const symbolTag = '[object Symbol]';
const funcTag = '[object Function]'
function forEach(array, iteratee) {
let index = -1;
const length = array.length;
while (++index < length) {
iteratee(array[index], index);
}
return array;
}
const deepTag = [mapTag, setTag, arrayTag, objectTag, argsTag]
//TODO: Reg 的拷贝方法
function cloneReg(targe) {
const reFlags = /\w*$/;
const result = new targe.constructor(targe.source, reFlags.exec(targe));
result.lastIndex = targe.lastIndex;
return result;
}
//TODO: Symbol 的拷贝方法
function cloneSymbol(targe) {
return Object(Symbol.prototype.valueOf.call(targe));
}
//TODO: cloneFunction
function cloneFunction(func) {
const bodyReg = /(?<={)(.|\n)+(?=})/m;
const paramReg = /(?<=().+(?=)\s+{)/;
const funcString = func.toString();
if (func.prototype) {
console.log('普通函数');
const param = paramReg.exec(funcString);
const body = bodyReg.exec(funcString);
if (body) {
console.log('匹配到函数体:', body[0]);
if (param) {
const paramArr = param[0].split(',');
console.log('匹配到参数:', paramArr);
return new Function(...paramArr, body[0]);
} else {
return new Function(body[0]);
}
} else {
return null;
}
} else {
return eval(funcString);
}
}
function cloneOtherType(targe, type) {
//TODO: constructor
const Ctor = targe.constructor;
switch (type) {
case boolTag:
case numberTag:
case stringTag:
case errorTag:
case dateTag:
return new Ctor(targe);
case regexpTag:
return cloneReg(targe);
case symbolTag:
return cloneSymbol(targe);
case funcTag:
return cloneFunction(targe)
default:
return null;
}
}
function clone(target, map = new WeakMap()) {
if(!isObject(target)) return target
const type = getType(target)
let cloneTarget
if(deepTag.includes(type)) {
cloneTarget = getInit(target, type)
}else {
return cloneOtherType(target, type)
}
if (map.get(target)) {
return map.get(target);
}
map.set(target, cloneTarget);
if(type === setTag) {
target.forEach(val => {
cloneTarget.add(clone(val, map))
})
return cloneTarget
}
// 克隆map
if (type === mapTag) {
target.forEach((value, key) => {
cloneTarget.set(key, clone(value,map));
});
return cloneTarget;
}
// 克隆对象和数组
const keys = type === arrayTag ? undefined : Object.keys(target);
forEach(keys || target, (value, key) => {
if (keys) {
key = value;
}
cloneTarget[key] = clone(target[key], map);
});
return cloneTarget;
}