前言:接上篇,本篇为面试中常见的手写题,最基础的东西一定要记牢,常见的实现方式,一些方法的实现原理,了然于心。
往期文章:
高频面试题整理
leetcode Hot 100 题目整理
1、手写 promiseAll、 promiseRace
function promiseAll(promiseArray) {
return new Promise((resolve, reject) => {
if(!Array.isArray(promiseArray)) {
return reject(new Error('传入的参数必须是数组!'));
}
const res = [];
const promiseNums = promiseArray.length;
let counter = 0;
for(let i = 0; i < promiseNums; i++) {
Promise.resolve(promiseArray[i]).then(value => {
counter++;
res[i] = value;
if(counter === promiseNums) {
resolve(res);
}
}).catch(e => reject(e));
}
})
}
function promiseRace(promiseArr) {
return new Promise((resolve, reject) => {
for (let i = 0; i < promiseArr.length; i++) {
Promise.resolve(promiseArr[i]).then(
(res) => {
resolve(res);
},
(err) => {
reject(err);
}
);
}
});
}
2、并发控制
function limitLoad(urls, handler, limit) {
const sequence = [].concat(urls);
let promises = [];
promises = sequence.splice(0, limit).map((url, index) => {
return handler(url).then(() => {
return index;
})
})
let p = Promise.race(promises);
for(let i = 0; i < sequence.length; i++) {
p = p.then((res) => {
promises[res] = handler(sequence[i]).then(() => {
return res;
})
return Promise.race(promises);
})
}
}
3、重写xhr
class XhrHook {
constructor(beforeHooks = {}, afterHooks = {}) {
this.XHR = window.XMLHttpRequest;
this.beforeHooks = beforeHooks;
this.afterHooks = afterHooks;
this.init();
}
init() {
let _this = this;
window.XMLHttpRequest = function() {
this._xhr = new _this.XHR();
_this.overwrite(this);
}
}
overwrite(proxyXHR) {
for(let key in proxyXHR._xhr) {
if(typeof proxyXHR._xhr[key] === 'function') {
this.overwriteMethod(key, proxyXHR);
continue;
}
this.overwriteAttributes(key, proxyXHR);
}
}
overwriteMethod(key, proxyXHR) {
let beforeHooks = this.beforeHooks;
let afterHooks = this.afterHooks;
proxyXHR[key] = (...args) => {
if(beforeHooks[key]) {
const result = beforeHooks[key].call(proxyXHR, args);
if(result === false) return;
}
const res = proxyXHR._xhr[key].apply(proxyXHR._xhr, args);
afterHooks[key] && afterHooks[key].call(proxyXHR._xhr, res);
return res;
}
}
overwriteAttributes(key, proxyXHR) {
Object.defineProperties(proxyXHR, key, this.setPropetyDescriptor(key, proxyXHR));
}
setPropetyDescriptor(key, proxyXHR) {
let obj = Object.create(null);
let _this = this;
obj.set = function(val) {
if(!key.startsWith('on')) {
proxyXHR['__' + key] = val;
return;
}
if(_this.beforeHooks[key]) {
this._xhr[key] = function(...args) {
_this.beforeHooks[key].call(proxyXHR);
val.apply(proxyXHR, args);
}
return;
}
this._xhr[key] = val;
}
obj.get = function() {
return proxyXHR['__' + key] || this._xhr[key];
}
return obj;
}
}
var xhr = new XMLHttpRequset();
4、实现 sizeof
function sizeOfObject(object) {
const seen = new WeakSet();
if(object === null) return 0;
let bytes = 0;
const properties = Object.keys(object);
for(let i = 0; i < properties.length; i++) {
const key = properties[i];
bytes += calculator(key);
if(typeof object[key] === 'object' && object[key] !== null) {
if(seen.has(object[key])) {
continue;
}
seen.add(object[key]);
}
bytes += calculator(object[key]);
}
return bytes;
}
function calculator(object) {
const objectType = typeof object;
switch(objectType) {
case 'string': {
return object.length * 2;
}
case 'boolean': {
return 4;
}
case 'number': {
return 8;
}
case 'object': {
if(Array.isArray(object)) {
return object.map(calculator).reduce((res, current) => res + current, 0);
} else {
return sizeOfObject(object);
}
}
default: {
return 0;
}
}
}
5、发布订阅
class EventEmitter {
constructor(maxListeners) {
this.events = {};
this.maxListeners = maxListeners || Infinity;
}
emit(event, ...args) {
const cbs = this.events[event];
if(!cbs) {
console.log('没有这个事件');
return this;
}
cbs.forEach(cb => cb.apply(this, args));
return this;
}
on(event, cb) {
if(!this.events[event]) {
this.events[event] = [];
}
if(this.maxListeners !== Infinity && this.events[event].length >= this.maxListeners) {
console.warn(`当前事件${event}超过最大监听数`);
return this;
}
this.events[event].push(cb);
return this;
}
once(event, cb) {
const func = (...args) => {
this.off(event, func);
cb.apply(this, args);
}
this.on(event, func);
return this;
}
off(event, cb) {
if(!cb) {
this.events[event] = null;
} else {
this.events[event] = this.events[event].filter(item => item !== cb);
}
return this;
}
}
6、实现 LazyMan
class LazyMan {
constructor(name) {
this.name = name;
this.taskQueue = [];
this.named(name);
}
named(name) {
this.taskQueue.push(() => {
console.log(`I am ${name}`);
this.next();
})
return this;
}
sleep(delay) {
this.taskQueue.push(() => {
setTimeout(() => {
console.log(`I am sleeping for ${delay} ms`);
this.next();
}, delay)
})
return this;
}
eat() {
this.taskQueue.push(() => {
console.log(`I am having dinner`);
this.next();
})
return this;
}
start() {
this.taskQueue.shift()();
}
next() {
if(this.taskQueue.length) {
const task = this.taskQueue.shift();
task();
}
}
}
class LazyMan {
constructor(name) {
this.name = name;
this.taskQueue = [];
this.named(name);
Promise.resolve()
.then(res => this.start())
}
named(name) {
this.taskQueue.push(() => new Promise((resolve, reject) => {
console.log(`I am ${name}`);
resolve();
}))
}
sleep(delay) {
this.taskQueue.push(() => new Promise((resolve, reject) => {
setTimeout(() => {
console.log(`I am sleeping for ${delay} ms`);
resolve();
}, delay)
}))
return this;
}
eat() {
this.taskQueue.push(() => new Promise((resolve, reject) => {
console.log(`I am having dinner`);
resolve();
}))
return this;
}
start() {
this.taskQueue.reduce((total, fn) => total.then(fn), Promise.resolve());
}
}
const halk = new LazyMan('halk');
halk.sleep(1000).eat().sleep(1000).eat();
7、防抖
const useDebounce = (fn, interval, args) => {
useEffect(() => {
const timer = setTimeout(fn.bind(null, args), interval);
return () => {
clearTimeout(timer);
}
}, args)
}
function debounce(fn, interval) {
let timer = null;
return function() {
clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, arguments);
}, interval);
}
}
8、节流
function throttle(fn, interval) {
let last = 0;
return function() {
let now = Date.now();
if(now - last >= interval) {
last = now;
fn.apply(this, arguments);
}
}
}
function throttle1(fn, interval) {
let timer = null;
return function() {
if(!timer) {
timer = setTimeout(() => {
fn.apply(this, arguments);
timer = null;
}, interval)
}
}
}
function throttle2(fn, interval) {
let timer = null;
let start = Date.now();
return function() {
let cur = Date.now();
let remain = interval - (cur - start);
clearTimeout(timer);
if(remain <= 0) {
fn.apply(this, arguments);
start = Date.now();
} else {
timer = setTimeout(() => {
fn.apply(this, arguments);
start = Date.now();
}, remain);
}
}
}
9、mockSetInterval
function mockSetInterval(fn, delay, ...args) {
let timer = null;
const recur = function() {
timer = setTimeout(() => {
fn.apply(this, args);
recur();
}, delay)
}
recur();
}
function mockClearInterval(id) {
clearTimeout(id);
}
10、对象响应式
const reactive = obj => {
if(typeof obj === 'object') {
for(const key in obj) {
defineReactive(obj, key, obj[key]);
}
}
}
const defineReactive = (obj, key, val) => {
reactive(val);
Object.defineProperty(obj, key, {
get() {
return val;
},
set(newVal) {
if(val === newVal) {
return;
}
val = newVal;
render(key, val);
}
})
}
const render = (key, val) => {
console.log(`SET key=${key} val=${val}`);
}
11、数组响应式
const arrPrototype = Array.prototype;
const newArrProtoType = Object.create(arrPrototype);
['push', 'pop', 'shift', 'unshift', 'sort', 'splice', 'reverse'].forEach(methodName => {
newArrProtoType[methodName] = function() {
arrPrototype[methodName].call(this, ...arguments);
render1(methodName, ...arguments);
}
})
const render1 = (action, ...args) => {
console.log(`Action = ${action} args=${args.join(',')}`)
}
const reactive1 = obj => {
if(Array.isArray(obj)) {
obj.__proto__ = newArrProtoType;
}
}
12、基于 proxy 监听属性删除
function makeObservable(target) {
let observeStore = new Map();
let handlerName = Symbol('handler');
observeStore.set(handlerName, []);
target.observe = function(handler) {
observeStore.get(handlerName).push(handler);
}
const proxyHandler = {
get(target, property, receiver) {
if(typeof target[property] === 'object' && target[property] !== null) {
return new Proxy(target[property], proxyHandler);
}
let success = Reflect.get(...arguments);
if(success) {
observeStore.get(handlerName).forEach(handler => handler('GET', property, target[property]));
}
return success;
},
set(target, property, value, receiver) {
let success = Reflect.set(...arguments);
if(success) {
observeStore.get(handlerName).forEach(handler => handler('SET', property, value));
}
return success;
},
deleteProperty(target, property) {
let success = Reflect.deleteProperty(...arguments);
if(success) {
observeStore.get(handlerName).forEach(handler => handler('DELETE', property));
}
return success;
}
};
return new Proxy(target, proxyHandler);
}
let user = {}
user = makeObservable(user);
user.observe((action, key, value) => {
console.log(`${action} key=${key} value=${value || ''}`);
})
13、将虚拟dom转为真实数据结构
function render(vnode) {
if(typeof vnode === 'number') {
vnode = String(vnode);
}
if(typeof vnode === 'string') {
return document.createTextNode(vnode);
}
const element = document.createElement(vnode.tag);
if(vnode.attrs) {
Object.keys(vnode.attrs).forEach(attrKey => {
element.setAttribute(attrKey, vnode.attrs[attrKey]);
})
}
if(vnode.children) {
vnode.children.forEach(childNode => {
element.appendChild(render(childNode));
})
}
return element;
}
14、实现对象可遍历
const obj = {
count: 0,
[Symbol.iterator]: () => {
return {
next: () => {
obj.count++;
if(obj.count <= 10) {
return {
value: obj.count,
done: false
}
} else {
return {
value: undefined,
done: true
}
}
}
}
}
}
for(const item of obj) {
console.log(item);
}
15、深拷贝
function deepClone(obj, hash = new WeakMap()) {
if(obj === null) return null;
if(obj instanceof Date) return new Date(obj);
if(obj instanceof RegExp) return new RegExp(obj);
if(typeof obj !== 'object') return obj;
if(hash.has(obj)) return hash.get(obj);
const resObj = Array.isArray(obj) ? [] : {};
hash.set(obj, resObj);
Reflect.ownKeys(obj).forEach(key => {
resObj[key] = deepClone(obj[key], hash);
})
return resObj;
}
function instanceOf(left, right) {
if(typeof left !== 'object' || left === null) {
return false;
}
while(true) {
if(left === null) return false;
if(left.__proto__ === right.prototype) return true;
left = left.__proto__;
}
}
16、compose
function compose() {
const argFnList = [...arguments];
return num => {
return argFnList.reduce((pre, cur) => cur(pre), num);
}
}
const a = compose(fn1,fn2,fn3,fn4);
a(3);
17、柯理化
function currying(fn, ...args) {
const originFnArgumentLength = fn.length;
let allArgs = [...args];
const resFn = (...newArgs) => {
allArgs = [...allArgs, ...newArgs];
if(allArgs.length === originFnArgumentLength) {
return fn(...allArgs);
} else {
return resFn;
}
}
return resFn;
}
const add = (a, b, c) => a + b + c;
const a1 = currying(add, 1);
const a2 = a1(2);
console.log(a2(3));
18、实现 map、reduce
function reduce(fn, initVal) {
let pre = initVal ? initVal : 0;
for(let i = 0; i < this.length; i++) {
pre = fn(pre, this[i]);
}
return pre;
}
function map(fn) {
let res = [];
for(let i = 0; i < this.length; i++) {
res.push(fn(this[i]));
}
return res;
}
19、实现 new
function _new() {
let obj = {};
let [constructor, ...args] = [...arguments];
obj.__proto__ = constructor.prototype;
let result = constructor.apply(obj, args);
if (result && typeof result === 'function' || typeof result === 'object') {
return result;
}
return obj;
}
20、实现 bind、call、apply
function bind(context, ...args) {
return (...newArgs) => {
return this.call(context, ...args, ...newArgs);
}
}
function call(context, ...args) {
context = Object(context) || window;
let fn = Symbol(1);
context[fn] = this;
let result = context[fn](...args);
delete context[fn];
return result;
}
function apply() {
let [thisArg, args] = [...arguments];
thisArg = Object(thisArg);
let fn = Symbol();
thisArg[fn] = this;
let result = thisArg[fn](...args);
delete thisArg[fn];
return result;
}
21、数组、对象扁平化
function flatdeep(arr) {
return arr.reduce((res, cur) => {
if(Array.isArray(cur)) {
return [...res, ...flatdeep(cur)];
} else {
return [...res, cur];
}
}, [])
}
function flatten(obj, key = '', res = {}, isArray = false) {
for (let [k, v] of Object.entries(obj)) {
if(v) {
if(Array.isArray(v)) {
let temp = isArray ? `${key}[${k}]` : `${key}${k}`;
flatten(v, temp, res, true);
} else if(typeof v === 'object') {
let temp = isArray ? `${key}[${k}].` : `${key}${k}.`;
flatten(v, temp, res);
} else {
let temp = isArray ? `${key}[${k}]` : `${key}${k}`;
res[temp] = v;
}
}
}
return res;
}
const input = {
a: 1,
b: [1, 2, { c: true }, [3]],
d: { e: 2, f: 3 },
g: null,
};
console.log(flatten(input));
22、下划线转驼峰
function transform(str) {
const arr = str.split('_');
return arr.reduce((pre, cur) => {
if(pre === '') {
return cur.slice(0, 1).toLowerCase() + cur.slice(1);
} else {
return pre + cur.slice(0, 1).toUpperCase() + cur.slice(1);
}
}, '');
}
23、匹配手机号
function phone(nums) {
const reg = /^[1][3,4,5,7,8,9][0-9]{9}$/;
if(reg.test(nums)) return true;
return false;
}
24、数字变为数组
Array.from({length: 5}, (v, i) => i);
Array.from(Array(5), (v, i) => i);
25、树转 Json
function convert(data, pid) {
let list = data.filter(item => item.pid === pid);
list.forEach(item => item.children = convert(data, item.id));
return list;
}
function convert(data) {
const map = {}, res = [];
data.forEach(item => {
if(!item.children) item.children = [];
map[item.id] = item;
if(map[item.pid]) {
map[item.pid].children.push(item);
} else {
res.push(item);
}
})
return res;
}
let arr = [
{id: 1, name: '部门1', pid: 0},
{id: 2, name: '部门2', pid: 1},
{id: 3, name: '部门3', pid: 1},
{id: 4, name: '部门4', pid: 3},
{id: 5, name: '部门5', pid: 4},
]
26、LRU
var LRUCache = function(capacity) {
this.capacity = capacity;
this.map = new Map();
};
LRUCache.prototype.get = function(key) {
if(this.map.has(key)) {
let temp = this.map.get(key);
this.map.delete(key);
this.map.set(key, temp);
return temp;
} else {
return -1;
}
};
LRUCache.prototype.put = function(key, value) {
if(this.map.has(key)) {
this.map.delete(key);
}
this.map.set(key, value);
if(this.map.size > this.capacity) {
this.map.delete(this.map.keys().next().value);
}
};