前端面试-手撕题
1. 手撕实现防抖节流函数
1 | |
1 | |
2. 用 setTimeout 实现“每秒执行一次”的任务
方法一:递归 setTimeout(推荐)
1
2
3
4
5
6
7
8
9function runEverySecond() {
console.log('执行任务:', new Date().toLocaleTimeString());
// 任务完成后,再设置下一次执行
setTimeout(runEverySecond, 1000);
}
// 启动
runEverySecond();
3.手撕instanceof
核心:获取对象的原型,判断对象的原型是否等于构造函数的 prototype 对象。原型链的向上查找。
1 | |
4.手撕new
new操作符用于创建一个给定构造函数的实例对象
原理流程:
- 创建一个新的对象obj。
- 将对象与构建函数通过原型链连接起来。
- 将构建函数中的this绑定到新建的对象obj上。
- 根据构建函数返回类型作判断,如果是原始值则被忽略,如果是返回对象,需要正常处理。
1 | |
5.手撕call\apply\bind
call\apply\bind都是函数对象自带的,作用是改变函数执行时的上下文,简而言之就是改变函数运行时的this指向。
他们三个的区别:
| 特性 | call |
apply |
bind |
|---|---|---|---|
| 立即执行 | ✅ 是 | ✅ 是 | ❌ 否(返回新函数) |
| 参数传递方式 | 列表形式:fn.call(obj, arg1, arg2) |
数组形式:fn.apply(obj, [arg1, arg2]) |
列表形式:fn.bind(obj, arg1, arg2) |
改变 this 指向 |
✅ | ✅ | ✅ |
| 常用于 | 一次性调用,传参明确 | 一次性调用,参数在数组中 | 需要延迟执行或保存上下文 |
call
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15Function.prototype.myCall = function (context, ...args) {
context = (context!== null && context !== undefined)? Object(context) : window;
const key = Symbol('key');
context[key] = this;
const result = context[key](...args);
delete context[key];
return result;
}
function greet(greeting, punctuation) {
console.log(`${greeting}, I'm ${this.name}${punctuation}`);
}
const person = { name: 'Alice' };
greet.myCall(person,'Hi', '~~');
// 输出: Hi, I'm Alice~~apply
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15Function.prototype.myApply = function (context, argsArray) {
context = (context!== null && context !== undefined)? Object(context) : window;
const key = Symbol('key');
context[key] = this;
const result = context[key](...argsArray);
delete context[key];
return result;
}
function greet(greeting, punctuation) {
console.log(`${greeting}, I'm ${this.name}${punctuation}`);
}
const person = { name: 'Alice' };
greet.myApply(person,['Hi', '~~']);
// 输出: Hi, I'm Alice~~bind
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17Function.prototype.myBind = function (context, ...args) {
const fn = this;
return function (...args2) {
if(new.target){
return new fn(...args, ...args2);
}
return fn.call(context, ...args, ...args2);
};
}
function greet(greeting, punctuation) {
console.log(`${greeting}, I'm ${this.name}${punctuation}`);
}
const person = { name: 'Alice' };
const newFn = greet.myBind(person,'Hi');
newFn('~~');
// 输出: Hi, I'm Alice~~
6.手撕 浅拷贝 深拷贝
浅拷贝
只复制对象的第一层属性,如果属性是引用类型(如数组、对象),则复制的是引用地址,而非新对象。
手写浅拷贝函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function shallowCopy(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
// 判断是数组还是对象
const result = Array.isArray(obj) ? [] : {};
// 遍历所有可枚举属性(包括自有属性)
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = obj[key];
}
}
return result;
}使用内置方法(等效浅拷贝)
1
2
3
4
5
6
7
8
9
10
11// 对象
const obj1 = { a: 1, b: { c: 2 } };
const obj2 = Object.assign({}, obj1);
// 或
const obj3 = { ...obj1 };
// 数组
const arr1 = [1, 2, { x: 3 }];
const arr2 = arr1.slice();
// 或
const arr3 = [...arr1];
深拷贝
递归复制对象的所有层级,所有引用类型都会创建新的副本,完全断开与原对象的联系。
手写深拷贝函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17function deepCopy(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
// 判断是数组还是对象
const result = Array.isArray(obj) ? [] : {};
// 遍历所有可枚举属性(包括自有属性)
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = deepCopy(obj[key]);
}
}
return result;
}手写深拷贝函数(处理循环引用)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24function deepCopy(obj) {
const cache = new Map();
function _deepCopy(obj) {
if (typeof obj !== 'object' || obj === null) {
return obj;
}
if (cache.has(obj)) {
return cache.get(obj);
}
// 判断是数组还是对象
const result = Array.isArray(obj) ? [] : {};
cache.set(obj, result);
// 遍历所有可枚举属性(包括自有属性)
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
result[key] = deepCopy(obj[key]);
}
}
return result;
}
return _deepCopy(obj);
}使用 JSON.parse(JSON.stringify())(有局限)
缺点:
- 会忽略 undefined、Symbol、函数
- 无法处理 Date(变成字符串)
- 无法处理 RegExp、Error 等特殊对象
- 循环引用会报错
1
const deepCopy = JSON.parse(JSON.stringify(obj));
7. 手写Ajax请求
1 | |
8. 手写数组去重
使用 Set
1
2
3
4
5
6
7function unique1(array){
return Array.from(new Set(array))
}
function unique2(array){
return [...new Set(array)]
}使用filter
1
2
3
4
5function unique1(array){
return Array.filter((item,index)=>{
return array.indexOf(item)===index
})
}
9. 手写数组扁平化
1 | |
10. 手写promise系列
1.构造器
1 | |
2.手写then方法
1 | |
思路:
上述代码存在难以处理异步的问题,因为异步等待是挂起状态。
!then
1 | |
11. 手写promise catch finally 方法
1 | |
12. 手写promise resolve reject 方法
resolve 和 reject 方法,其实有两种不同的语境:
- 作为 Promise 构造函数的参数
- 作为
Promise类上的静态方法(Promise.resolve()和Promise.reject())
Promise.resolve(value) 是一个 静态方法,它返回一个 已解决(fulfilled)状态 的 Promise 对象Promise.reject(reason) 是一个 静态方法,它返回一个 已拒绝(rejected)状态 的 Promise 对象。
1 | |
12. 手写 Promise.all 并讲解思路
Promise.all(iterable) 接收一个可迭代对象(通常是数组),返回一个新的 Promise:
- 全部成功:当所有 Promise 都成功时,返回的 Promise 才成功,结果是按顺序排列的成功值数组。
- 任意失败:只要有一个 Promise 失败,返回的 Promise 就立即失败,原因为第一个失败的原因。
1 | |
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!