前端面试系列-手写题
题目:手写 Promise
- TODO
题目:手写 Promise.all
要手写实现一个 Promise.all 方法,我们需要考虑几个关键点:
-
接收一个 promise 数组作为参数。
-
返回一个新的 promise。
-
等待所有的输入 promise 都成功完成(fulfilled),然后以一个数组的形式完成返回的 promise,数组中的元素顺序与输入数组的顺序相对应。
-
如果任何一个输入 promise 失败(rejected),则返回的 promise 应该立即以该 promise 的失败原因拒绝。
function PromiseAll(promises) {
return new Promise((resolve, reject) => {
// 检查输入是否为数组
if (!Array.isArray(promises)) {
return reject(new TypeError('You must pass an array to Promise.all.'));
}
// 创建一个与输入数组长度相同的结果数组,用来存储每个promise的结果
let results = new Array(promises.length);
let completed = 0;
// 遍历每个 promise
promises.forEach((promise, index) => {
// 确保每个元素都是 promise
Promise.resolve(promise).then(
(value) => {
// 存储结果并检查是否所有 promise 都已完成
results[index] = value;
completed++;
if (completed === promises.length) {
resolve(results);
}
},
(error) => {
// 如果任何一个 promise 失败,立即拒绝返回的 promise
reject(error);
}
);
});
});
}
// 使用示例
let promise1 = Promise.resolve(3);
let promise2 = 42;
let promise3 = new Promise((resolve, reject) => {
setTimeout(resolve, 100, 'foo');
});
PromiseAll([promise1, promise2, promise3]).then((values) => {
console.log(values); // 输出 [3, 42, 'foo']
}).catch((error) => {
console.error(error);
});
题目:手写 await async
- TODO
题目:手写 apply
、call
、bind
实现apply
Function.prototype.myApply = function (context, args) {
// 判断是否为函数
if (typeof this !== "function") {
throw new TypeError("not a function");
}
// 确定上下文,默认为全局对象
context = context || (this instanceof Window ? this : window);
// 设置函数引用
context.fn = this;
// 调用函数,并使用扩展运算符展开参数
const result = context.fn(...args);
delete context.fn;
// 清理,删除函数引用
return result;
};
实现call
与 apply 基本一致,主要区别在获取参数处理步骤。
Function.prototype.myCall = function (context) {
// 检查调用者是否为函数
if (typeof this !== "function") {
throw new TypeError("not a function");
}
// 设置默认上下文
context = context || (this instanceof Window ? this : window);
// 设置函数引用
context.fn = this;
// 获取除了 context 之外的所有参数
let args = [...arguments].slice(1);
// 调用函数并传递参数
let result = context.fn(...args);
// 清理函数引用
delete context.fn;
// 返回结果
return result;
};
实现bind
实现关键点:
-
修改this指向;
-
动态传递参数;
-
兼容new关键字;
Function.prototype.myBind = function (context) {
// 判断是否为函数
if (typeof this !== "function") {
throw new TypeError("Error");
}
// 获取参数
const args = [...arguments].slice(1);
// 将当前的函数保存在fn变量中
const fn = this;
// 返回一个函数
return function F() {
// 判断是否为构造函数
// 如果F是作为构造函数使用(即this指向F实例),则会创建并返回一个新的fn实例,并将args和F的参数传递给fn的构造函数
// 否则,会使用apply方法将fn应用到指定的上下文context上,并将args和F的参数合并后传递给fn
if (this instanceof F) {
return new fn(...args, ...arguments);
} else {
return fn.apply(context, args.concat(...arguments));
}
};
};
题目:手写 防抖
、节流
实现 防抖
通常使用setTimeout来实现延迟执行,如果在这个延迟时间内事件再次被触发,则取消之前的定时器,并重新设置一个新的定时器。
// 防抖函数实现
function debounce(func, wait) {
let timeout;
return function(...args) {
const context = this;
clearTimeout(timeout);
timeout = setTimeout(() => {
func.apply(context, args);
}, wait);
};
}
// 使用防抖函数
// 使用示例:假设我们有一个搜索输入框,我们希望在用户停止输入后执行搜索
const handleSearch = debounce(function(event) {
console.log('Searching for:', event.target.value);
}, 300);
document.getElementById('search-input').addEventListener('input', handleSearch);
实现 节流
通常使用setTimeout或时间戳来实现。在setTimeout的实现中,如果事件在延迟时间内再次被触发,则忽略这些触发;在时间戳的实现中,会检查自上次执行以来是否已经过了足够的时间间隔。
// 节流函数实现
function throttle(func, limit) {
let lastFunc;
let lastRan;
return function() {
const context = this;
const args = arguments;
if (!lastRan) {
func.apply(context, args);
lastRan = Date.now();
} else {
clearTimeout(lastFunc);
lastFunc = setTimeout(function() {
if ((Date.now() - lastRan) >= limit) {
func.apply(context, args);
lastRan = Date.now();
}
}, limit - (Date.now() - lastRan));
}
};
}
// 使用节流函数
// 使用示例:假设我们有一个滚动事件,我们希望限制检查是否到达底部的操作频率
const handleScroll = throttle(function() {
console.log('Scroll event handler called');
}, 300);
window.addEventListener('scroll', handleScroll);
题目:手写 深拷贝
简单对象深拷贝
const obj1 = { a: 1, b: { c: 2 } };
const obj2 = JSON.parse(JSON.stringify(obj1));
obj2.b.c = 3;
console.log(obj1.b.c); // 输出2,说明obj1没有被修改
复杂对象拷贝
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); // 解决循环引用问题
let cloneObj = new obj.constructor();
hash.set(obj, cloneObj);
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
cloneObj[key] = deepClone(obj[key], hash); // 递归复制
}
}
return cloneObj;
}
题目:手写 继承方式实现
- TODO
题目:数组去重
方法1:使用Set
对象:
const array = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = [...new Set(array)];
方法2:使用filter
方法:
通过 filter 方法和一个辅助数组来过滤掉重复的元素。
const uniqueArray = array.filter((item, index) => array.indexOf(item) === index);
方法3:使用map
和indexOf
:
const uniqueArray = [];
array.map((item) => {
if (uniqueArray.indexOf(item) === -1) {
uniqueArray.push(item);
}
});
方法4:使用reduce
方法:
reduce 方法可以遍历数组,并利用一个回调函数来决定新数组的元素。
const uniqueArray = array.reduce((acc, current) => {
if (!acc.includes(current)) {
acc.push(current);
}
return acc;
}, []);
方法5:使用Object
作为映射:
利用对象的键值唯一性来过滤掉重复的元素。
const uniqueArray = Object.keys(array.reduce((acc, cur) => {
acc[cur] = true;
return acc;
}, {}));
方法6:使用indexOf
和concat
:
使用 indexOf 检查元素是否已经存在于新数组中,如果不存在则添加。
let uniqueArray = [];
array.forEach((item) => {
if (uniqueArray.indexOf(item) === -1) {
uniqueArray = uniqueArray.concat(item);
}
});
方法7:使用lastIndexOf
:
lastIndexOf 方法从数组的末尾开始向前查找,可以用来检查元素是否重复出现。
const uniqueArray = array.filter((item, index) => array.lastIndexOf(item) === index);
方法8:使用Symbol
作为键:
如果数组元素是对象或数组,可以使用 Symbol 作为键来创建一个映射。
const uniqueMap = new Map();
array.forEach((item) => {
uniqueMap.set(JSON.stringify(item), item);
});
const uniqueArray = Array.from(uniqueMap.values());
方法9:使用第三方库:
使用如 Lodash 等第三方库中的 _.uniq 或 _.uniqBy 方法。
import _ from 'lodash';
const uniqueArray = _.uniq(array);
题目:合并数组的重叠区间
- TODO
题目:求两个数组的交集
求两个数组的交集,即找出两个数组中都存在的元素。
const array1 = [1, 2, 3, 4];
const array2 = [3, 4, 5, 6];
// 交集结果为:[3, 4]
方法1:使用filter
和includes
const intersection = array1.filter(value => array2.includes(value));
方法2:使用reduce
和includes
const intersection = array1.reduce((acc, value) => {
return array2.includes(value) ? [...acc, value] : acc;
}, []);
方法3:使用Set
const intersection = [...new Set(array1.filter(value => array2.includes(value)))];
方法4:使用some
const intersection = array1.filter(value => array2.some(item => item === value));
方法5:使用双层for循环
第二层循环也可以使用 includes 代替,较少代码量。
const intersection = [];
for (let i = 0; i < array1.length; i++) {
for (let j = 0; j < array2.length; j++) {
if (array1[i] === array2[j] && intersection.indexOf(array1[i]) === -1) {
intersection.push(array1[i]);
}
}
}
方法6:排序后去重
首先对两个数组进行排序,然后使用双指针技术找到交集。
const sortAndMerge = (arr1, arr2) => {
// 合并数组并排序
const sorted = arr1.concat(arr2).sort((a, b) => a - b);
const intersection = [];
let i = 0;
while (i < sorted.length - 1) {
if (sorted[i] === sorted[i + 1]) {
intersection.push(sorted[i]);
i++;
}
i++;
}
return intersection;
};
const array1 = [1, 2, 2, 3];
const array2 = [2, 3, 4, 4];
const intersection = sortAndMerge(array1, array2);
方法7:使用 Map
适用于数组元素为对象的情况。
const array1 = [{ id: 1 }, { id: 2 }];
const array2 = [{ id: 2 }, { id: 3 }];
const intersection = array1.filter(item1 =>
array2.some(item2 => item1.id === item2.id)
);
题目:求两个数组的并集
找出两个数组的并集(即两个数组中所有的唯一元素)可以通过多种方式实现。
const array1 = [1, 2, 3];
const array2 = [3, 4, 5];
方法1:使用concat
和Set
const union = [...new Set(array1.concat(array2))];
方法2:使用reduce
和Set
const union = array1.reduce((acc, val) => {
acc.add(val);
return acc;
}, new Set()).size > 0
? Array.from(new Set([...array1, ...array2]))
: [];
方法3:使用push
和indexOf
const union = [];
array1.forEach(item => {
if (union.indexOf(item) === -1) {
union.push(item);
}
});
array2.forEach(item => {
if (union.indexOf(item) === -1) {
union.push(item);
}
});
方法4:使用filter
和indexOf
const union = [
...array1.filter(item => array2.indexOf(item) === -1),
...array2.filter(item => array1.indexOf(item) === -1)
].filter((item, index, arr) => index === arr.indexOf(item));
方法5:使用some
和push
const union = [];
[array1, array2].forEach(arr => {
arr.forEach(item => {
if (!union.some(elem => elem === item)) {
union.push(item);
}
});
});
方法6:排序后合并
如果数组可以排序,可以先对两个数组进行排序,然后合并并去除重复项。
const sortMerge = (arr1, arr2) => {
const sorted = arr1.concat(arr2).sort();
const union = [];
for (let i = 0; i < sorted.length; i++) {
if (sorted[i] !== sorted[i - 1]) {
union.push(sorted[i]);
}
}
return union;
};
const array1 = [1, 2, 2];
const array2 = [2, 3, 3];
const union = sortMerge(array1, array2);
手写题:将arr转换为tree结构
- TODO