最近面试结束,整理了一下最近的js手写题吧
Promise相关
1. Promise.all实现
Promise.all: 按顺序拿到所有结果, 有错误就直接返回。
function promiseAll(arr) {
return new Promise((resolve, reject) => {
if (!Array.isArray(arr)) reject('not array!');
const result = [];
const count = 0;
arr.forEach((item, index) => {
// 确保是promise
Promise.resolve(item).then(res => {
// 确保结果的顺序
result[index] = res;
count++;
if (count === arr.length) resolve(result);
}, rej => {
reject(rej);
});
});
});
}
2.Promise.allSettled实现
面试的时候,叫你延伸,实现不管是否reject,都要拿到所有结果,其实就是promise.allSettled的实现。
方法一:Promise.all的基础上改造一下。
function promiseAllSettled(promises) {
return new Promise((resolve, reject) => {
if (!Array.isArray(promises)) reject([]);
const result = [];
let count = 0;
let len = promises.length;
promises.forEach((item, index) => {
Promise.resolve(item).then(res => {
result[index] = {
status: 'success',
res,
};
count++;
if (count >= len) resolve(result);
}, rej => {
result[index] = {
status: 'error',
res,
};
count++;
if (count >= len) resolve(result);
})
});
});
}
方法二:先包一层promise,全部resolve,再调用Promise.all。
function promiseAllSettled1(promises) {
if (!Array.isArray(promises)) Promise.reject('Not an array');
const success = promises.map(item => {
return new Promise((resolve, reject) => {
Promise.resolve(item).then(resolve, resolve);
});
})
return Promise.all(success);
}
3. Promise.race实现
Promise.race: 拿到最快的结果
function promiseRace(arr) {
return new Promise((resolve, reject) => {
if (!Array.isArray(arr)) reject('not array!');
arr.forEach(item => {
Promise.resolve(item).then(resolve,reject);
});
});
}
简单应用: 一个是定义一个myFetch方法,实现timeout秒服务端没返回的话,就报错
function myFetch(params, timeout) {
return Promise.race([new Promise((resolve, reject) => {
setTimeout(() => {
reject('error');
}, timeout);
}), fetch(params)])
}
4. Promise封装ajax
function ajax(methods, url) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open(methods, url);
xhr.onreadystatechange = () => {
if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
resolve(xhr.response);
} else {
reject(xhr.status);
}
}
xhr.send();
});
}
5.链式调用(两种方法)
1.Promise.resolve()辅助实现
class Chains {
task = Promise.resolve()
eat() {
this.task = this.task.then(() => {
console.log('eat')
})
return this;
}
work() {
this.task = this.task.then(() => {
console.log('work')
})
return this;
}
sleep(delay) {
this.task = this.task.then(() => {
return new Promise((resolve) => {
setTimeout(() => {
console.log('sleep')
resolve()
}, delay)
})
})
return this;
}
}
const chain = () => new Chains()
chain().eat().sleep(1000).work();
//输出:eat,(等待1秒)sleep,work
抛出一个疑问:如果要实现(不使用队列):
chain().eat().sleep(1000).firstRun().work();
// 输出:firstRun,eat,(等待1秒)sleep,work
这个怎么实现?? 我的思路:可能先把firstRun放微任务,其他放宏任务。后面如果实现了再来补充吧 如果你实现了,欢迎评论留言你的方法,我会标明作者更新在这里。
2.队列+next()辅助实现
这里是使用队列可以比较清晰的实现:
class Arrange {
constructor(name) {
this.name = name;
this.queue = [];
this.queue.push(() => {
console.log('init', this.name);
this.next();
});
Promise.resolve().then(res => {
this.next();
});
}
next() {
const fn = this.queue.shift();
fn && fn();
}
dosth(param) {
const fn = () => {
console.log(param);
this.next();
}
this.queue.push(fn);
return this;
}
awaitFirst(timeout) {
const fn = () => {
setTimeout(() => {
console.log('awaitFirst', timeout);
this.next();
}, timeout);
}
this.queue.unshift(fn);
return this;
}
await(timeout) {
const fn = () => {
setTimeout(() => {
console.log('await', timeout);
this.next();
}, timeout);
}
this.queue.push(fn);
return this;
}
execute() {
const fn = () => {
console.log('execute');
this.next();
}
this.queue.push(fn);
return this;
}
}
new Arrange('william').dosth('push').awaitFirst(1000).await(2000).execute();
6.异步并发控制(思路清晰)
关键思路是:当任务达到最大的时候,使用promise来阻塞后续代码的执行,并且将该promise的resolve存入队列,其他正在执行的任务执行完后,后续再拿出来执行。
// 异步任务调度器
class Scheduler {
constructor(max) {
// 最大可并发任务数
this.max = max;
// 当前并发任务数
this.count = 0;
// 阻塞的任务队列
this.queue = [];
}
async add(fn) {
if (this.count >= this.max) {
// 若当前正在执行的任务,达到最大容量max
// 阻塞在此处,等待前面的任务执行完毕后将resolve弹出并执行
await new Promise(resolve => this.queue.push(resolve));
}
// 当前并发任务数++
this.count++;
// 使用await执行此函数
const res = await fn();
// 执行完毕,当前并发任务数--
this.count--;
// 若队列中有值,将其resolve弹出,并执行
// 以便阻塞的任务,可以正常执行
this.queue.length && this.queue.shift()();
// 返回函数执行的结果
return res;
}
}
const scheduler = new Scheduler(3);
const timeout = (time) => {
return new Promise(resolve => {setTimeout(resolve, time);})
}
const addTask = (time, order) => {
scheduler.add(() => timeout(time)).then((res) => console.log(`task ${order} done`));
};
addTask(1000, "1");
addTask(500, "2");
addTask(300, "3");
addTask(400, "4");
两个常见算法
1.快排
const quickSort = (arr = []) => {
if (arr.length <= 1) return arr;
const left = [];
const right = [];
let pivotIndex = Math.floor(arr.length / 2);
let pivot = arr.splice(pivotIndex, 1)[0];
for (const item of arr) {
if (item <= pivot) {
left.push(item);
} else {
right.push(item);
}
}
return quickSort(left).concat([pivot], quickSort(right));
}
console.log(quickSort([3,5,7,7,2,3,6,8,0,76,3,3,5,67,73,5,56]));
抛砖:leecode:215. 数组中的第K个最大元素。(答案就不贴了,官网很多解法)
2.二分查找
二分查找的变式很多,个人感觉是,关键点需要找到缩小范围的条件,和你要拿到的值。
const binSearch = (arr, target) => {
let left = 0;
let right = arr.length - 1;
while (left <= right) {
let mid = Math.floor((left + right) / 2);
if (arr[mid] < target) {
left = mid + 1;
} else if (arr[mid] > target) {
right = mid - 1;
} else {
return mid;
}
}
return -1;
}
console.log(binSearch([1, 3, 5, 7, 9, 11, 55, 84, 97, 99],9));
抛砖:数组中小于t的元素的个数
/** 给一个递增的数组 a (元素可以有相同的值),含有 a.length 个元素,一个目标值 t, 求数组 a 中小于 t 的元素个数
(请用最高效的算法实现,并注明时间复杂度)
参数:
- a: 递增的数组
- t: 目标值 t
return 数组 a 中小于 t 的元素的个数
例子:
f([1, 3, 6, 6, 8, 12], 0) === 0
f([1, 3, 6, 6, 8, 12], 1) === 0
f([1, 3, 6, 6, 8, 12], 2) === 1
f([1, 3, 6, 6, 8, 12], 3) === 1
f([1, 3, 6, 6, 8, 12], 4) === 2
f([1, 3, 6, 6, 8, 12], 6) === 2
f([1, 3, 6, 6, 8, 12], 7) === 4
f([1, 3, 6, 6, 8, 12], 8) === 4
f([1, 3, 6, 6, 8, 12], 9) === 5
f([1, 3, 6, 6, 8, 12], 12) === 5
f([1, 3, 6, 6, 8, 12], 13) === 6
*/
// 思考下??
function f(arr, target) {
let left = 0;
let right = arr.length - 1;
while (left <= right) {
let mid = (left + right) >> 1;
if (arr[mid] < target) {
left = mid + 1;
} else if (arr[mid] > target) {
right = mid - 1;
} else {
return mid;
}
}
return left;
}
console.log(f([1, 3, 6, 6,7, 8, 12], 7))
js原生实现
1.实现new
new一个对象的过程:
- 新建一个对象A
- 将该对象A的原型指向构造函数的原型
- 将构造函数的this指向这个对象A,执行构造函数
- 返回构造函数返回的对象,或者返回该对象A
function myNew(constructor, ...args) {
// 将构造函数的原型绑定到新创的对象实例上
const obj = Object.create(constructor.prototype);
// 执行构造函数
const res = con.call(obj, ...args);
return res && res instanceof Object ? res : obj;
}
function myNew1(constructor, ...args) {
const obj = {};
// 这句是影响性能的。参考MDN
obj.__proto__ = constructor.prototype;
const res = constructor.call(obj, ...args);
return res && res instanceof Object ? res : obj;
}
// 摘自MDN:
// 由于现代 JavaScript 引擎优化属性访问所带来的特性的关系,
// 更改对象的[[Prototype]]在各个浏览器和 JavaScript 引擎上都是一个很慢的操作。
// 其在更改继承的性能上的影响是微妙而又广泛的,
// 这不仅仅限于 obj.__proto__ = ...语句上的时间花费,
// 而且可能会延伸到任何代码,那些可以访问任何[[Prototype]]已被更改的对象的代码。
// 如果你关心性能,你应该避免设置一个对象的[[Prototype]]。
// 相反,你应该使用 Object.create()来创建带有你想要的[[Prototype]]的新对象。
2.实现instanceOf
A instanceOf B: 判断A的原型链上是否有B
function instanceOf(left, right) {
let leftVal = left.__proto__;
let rightVal = right.prototype;
while (true) {
if (leftVal === null) return false;
if (rightVal === rightVal) return true;
leftVal = leftVal.__proto__;
}
}
3.实现数组reduce
Array.prototype.myReduce = function (callback, initialValue) {
let result = initialValue ? initialValue : this[0];
for (let i = initialValue ? 0 : 1; i < this.length; i++) {
result = callback(result, this[i], i, this);
}
return result;
}
console.log([1, 2, 3, 4, 5].myReduce((a, b) => a + b,10));
4.reduce实现map
Array.prototype.myMap = function (fn, thisArg) {
return (thisArg || this).reduce((pre, cur, i, arr) => {
pre.push(fn.call(thisArg, cur, i, arr))
return pre;
}, [])
}
console.log([1, 2, 3, 4].myMap((item, i, arr) => {
return item * 2;
}, [2, 3, 4]))
5. 实现深拷贝(解决循环引用)
const deepClone = (obj, cached = new Set()) => {
const copy = Array.isArray(obj) ? [] : {};
for (const key in obj) {
if (obj.hasOwnProperty(key)) {
// 如果是对象,并且缓存中没有改对象,才进行深拷贝
if (typeof obj[key] === 'object' && !cached.has(obj[key])) {
// 把对象加入缓存中
cached.add(obj[key]);
copy[key] = deepClone(obj[key], cached);
} else {
copy[key] = obj[key];
}
}
}
return copy;
}
const obj = {
name: 'william',
age: 18,
address: {
city: 'beijing',
street: 'xizhimen'
},
hobbies: ['basketball', 'football'],
}
obj.family = obj;
console.log(obj);
const copy = deepClone(obj);
console.log(copy);
copy.name = '梨花';
console.log(obj);
console.log(copy);
6.防抖节流
//防抖:一段时间内触发,重新计时,只执行最后一次。例子:百度搜索的联想
function debounce(fn, timeout) {
let timer = null;
return (...args) => {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, timeout);
}
}
// 节流:一段时间只执行一次,稀释作用,例子:onresize等
// 时间戳版
function throttle(fn, timeout) {
let pre = 0;
return (...args) => {
let cur = new Date().getTime();
if (cur - pre > timeout) {
fn.apply(this, args);
pre = cur;
}
}
}
// 定时器版
function throttle1(fn, delay) {
let timer = null;
return (...args) => {
if (!timer) {
timer = setTimeout(() => {
fn(...args);
timer = null;
}, delay);
}
}
}
// 时间戳+定时器 TODO
7.实现数组flat扁平化
Array.prototype.myFlat = function (deep = Infinity) {
const arr = this;
if (deep === 0) {
return arr;
}
return arr.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? cur.myFlat(--deep) : cur);
}, [])
}
console.log([[[[1], 2], 3], 8].myFlat());
8.实现call,apply,bind
使用Symbol,你可以理解为防止覆盖掉context上的其他方法
Function.prototype.myCall = function (context, ...args) {
context = typeof context === 'object' ? context : window;
const key = Symbol('myCall');
// this代表的就是执行的函数
context[key] = this;
return context[key](...args);
}
Function.prototype.myApply = function (context, args) {
context = typeof context === 'object' ? context : window;
const key = Symbol('myApply');
context[key] = this;
return context[key](...args);
}
Function.prototype.myBind = function (context, ...args) {
context = typeof context === 'object' ? context : window;
const key = Symbol('myBind');
context[key] = this;
return (...args1) => {
context[key](...args,...args1);
};
}