2026-01-19 10:21:18 +08:00

248 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# JS 基础与手写实现
## 1. 手写 Promise
> **场景**:理解异步编程核心机制,处理 Ajax 请求、文件读取、定时器等异步操作的链式调用和错误处理。
> **解决**:回调地狱问题,提供优雅的异步流程控制。
```js
class MyPromise {
constructor(executor) {
this.state = 'pending'; // 初始状态为 pending
this.value = undefined; // 存储成功值或失败原因
this.callbacks = []; // 存储 then 注册的回调函数
// 成功时调用,将状态改为 fulfilled
const resolve = (value) => {
if (this.state !== 'pending') return; // 状态只能改变一次
this.state = 'fulfilled';
this.value = value;
this.callbacks.forEach(cb => cb.onFulfilled(value)); // 执行所有成功回调
};
// 失败时调用,将状态改为 rejected
const reject = (reason) => {
if (this.state !== 'pending') return;
this.state = 'rejected';
this.value = reason;
this.callbacks.forEach(cb => cb.onRejected(reason)); // 执行所有失败回调
};
// 立即执行 executor捕获异常则 reject
try { executor(resolve, reject); } catch (e) { reject(e); }
}
then(onFulfilled, onRejected) {
// 返回新 Promise 实现链式调用
return new MyPromise((resolve, reject) => {
const handle = (callback, fallback) => {
try {
const result = (callback || fallback)(this.value); // 执行回调
// 如果返回值是 Promise等待其完成
result instanceof MyPromise ? result.then(resolve, reject) : resolve(result);
} catch (e) { reject(e); } // 回调报错则 reject
};
// 根据当前状态决定立即执行还是存储回调
if (this.state === 'fulfilled') handle(onFulfilled, v => v);
else if (this.state === 'rejected') handle(onRejected, e => { throw e; });
else this.callbacks.push({ // pending 状态先存储回调
onFulfilled: () => handle(onFulfilled, v => v),
onRejected: () => handle(onRejected, e => { throw e; })
});
});
}
}
```
## 2. Promise.all / Promise.race
> **场景**`Promise.all` 用于并行请求多个接口后统一处理(如同时加载用户信息和订单列表);`Promise.race` 用于请求超时控制、竞速取最快响应。
> **解决**:多异步任务协调与超时兜底问题。
```js
// Promise.all - 所有成功才成功,一个失败则立即失败
Promise.myAll = (promises) => {
return new Promise((resolve, reject) => {
const results = []; // 存储所有结果
let count = 0; // 记录完成数量
promises.forEach((p, i) => {
Promise.resolve(p).then(val => {
results[i] = val; // 按索引存储,保证顺序
if (++count === promises.length) resolve(results); // 全部完成则 resolve
}, reject); // 任一失败立即 reject
});
});
};
// Promise.race - 第一个完成就返回(无论成功失败)
Promise.myRace = (promises) => {
return new Promise((resolve, reject) => {
// 第一个 resolve/reject 的结果会被采纳,后续的会被忽略
promises.forEach(p => Promise.resolve(p).then(resolve, reject));
});
};
```
## 3. 防抖与节流
> **场景**:防抖用于搜索框输入(停止输入后才请求)、窗口 resize 结束后计算布局;节流用于滚动事件监听、按钮防重复点击。
> **解决**:高频事件触发导致的性能问题和重复请求。
```js
// 防抖:停止触发后才执行(适合搜索框输入)
function debounce(fn, delay) {
let timer = null; // 保存定时器 ID
return function(...args) {
clearTimeout(timer); // 每次触发都清除上一个定时器
timer = setTimeout(() => fn.apply(this, args), delay); // 重新设定定时器
};
}
// 节流:固定间隔执行(适合滚动事件)
function throttle(fn, delay) {
let last = 0; // 记录上次执行时间
return function(...args) {
const now = Date.now();
if (now - last >= delay) { // 距离上次执行超过 delay 才执行
last = now;
fn.apply(this, args);
}
};
}
```
## 4. 深拷贝(处理循环引用)
> **场景**Redux/Vuex 状态管理中复制 state、表单数据备份与重置、避免修改原始数据造成副作用。
> **解决**:引用类型浅拷贝导致的数据污染问题,特别是循环引用场景。
```js
function deepClone(obj, map = new WeakMap()) {
// 基本类型和 null 直接返回
if (obj === null || typeof obj !== 'object') return obj;
// 特殊对象处理
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// 处理循环引用:如果已拷贝过,直接返回缓存
if (map.has(obj)) return map.get(obj);
// 创建新对象/数组
const clone = Array.isArray(obj) ? [] : {};
map.set(obj, clone); // 先存入 map防止循环引用
// 递归拷贝每个属性
for (const key in obj) {
if (obj.hasOwnProperty(key)) { // 只拷贝自身属性
clone[key] = deepClone(obj[key], map);
}
}
return clone;
}
```
## 5. 函数柯里化
> **场景**:参数复用(如日志函数固定模块名)、延迟执行、函数式编程中的组合与管道操作。
> **解决**:减少重复传参,提高函数复用性和可读性。
```js
function curry(fn) {
return function curried(...args) {
// 如果参数数量足够,直接执行原函数
if (args.length >= fn.length) {
return fn.apply(this, args);
}
// 参数不够,返回新函数继续收集参数
return (...nextArgs) => curried(...args, ...nextArgs);
};
}
// 使用示例
const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add);
curriedAdd(1)(2)(3); // 6 - 每次传一个参数
curriedAdd(1, 2)(3); // 6 - 也可以一次传多个
```
## 6. call / apply / bind 实现
> **场景**改变函数执行上下文如借用数组方法处理类数组、React 类组件中绑定事件处理函数的 this。
> **解决**this 指向问题,实现函数借用和预设参数。
```js
// call - 立即执行,参数逐个传入
Function.prototype.myCall = function(ctx, ...args) {
ctx = ctx || window; // ctx 为 null/undefined 时指向 window
const key = Symbol(); // 用 Symbol 避免属性名冲突
ctx[key] = this; // 将函数作为 ctx 的方法
const result = ctx[key](...args); // 调用,此时 this 指向 ctx
delete ctx[key]; // 清理临时属性
return result;
};
// apply - 立即执行,参数以数组传入
Function.prototype.myApply = function(ctx, args = []) {
ctx = ctx || window;
const key = Symbol();
ctx[key] = this;
const result = ctx[key](...args); // 展开数组参数
delete ctx[key];
return result;
};
// bind - 返回新函数,不立即执行
Function.prototype.myBind = function(ctx, ...args) {
const fn = this; // 保存原函数
return function(...newArgs) {
// 合并预设参数和新参数
return fn.apply(ctx, [...args, ...newArgs]);
};
};
```
## 7. 事件总线 EventEmitter
> **场景**:组件间通信(如 Vue 的 EventBus、插件系统、微前端应用间消息传递、解耦模块依赖。
> **解决**:发布-订阅模式实现松耦合的事件驱动架构。
```js
class EventEmitter {
constructor() {
this.events = {}; // 存储事件名 -> 监听器数组的映射
}
// 订阅事件
on(event, listener) {
(this.events[event] ||= []).push(listener); // 添加监听器
return this; // 返回 this 支持链式调用
}
// 取消订阅
off(event, listener) {
if (this.events[event]) {
// 过滤掉指定的监听器
this.events[event] = this.events[event].filter(l => l !== listener);
}
return this;
}
// 触发事件
emit(event, ...args) {
// 执行该事件的所有监听器
(this.events[event] || []).forEach(listener => listener(...args));
return this;
}
// 一次性订阅,触发后自动取消
once(event, listener) {
const wrapper = (...args) => {
listener(...args); // 执行原始监听器
this.off(event, wrapper); // 立即取消订阅
};
return this.on(event, wrapper);
}
}
```