手写篇
函数相关
实现防抖函数(debounce)
防抖(debounce)是指触发事件后,在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则重新计算函数执行时间。
适用场景: 搜索框输入、窗口resize、按钮点击提交等。
/**
* 防抖函数
* @param {Function} fn - 需要防抖的函数
* @param {number} delay - 延迟时间(毫秒)
* @param {boolean} immediate - 是否立即执行(可选,默认false)
* @returns {Function} 返回防抖处理后的函数
*/
function debounce(fn, delay, immediate = false) {
let timer = null;
return function (...args) {
const context = this;
if (immediate && !timer) {
// 立即执行:第一次触发时立即执行,然后等待delay时间内不再触发才重置
fn.apply(context, args);
}
// 清除上一次的定时器
if (timer) clearTimeout(timer);
// 设置新的定时器
timer = setTimeout(() => {
if (!immediate) {
fn.apply(context, args);
}
timer = null;
}, delay);
};
}
// --- 使用示例 ---
// const handleInput = debounce((e) => {
// console.log('输入内容:', e.target.value);
// }, 300);
// inputEl.addEventListener('input', handleInput);
实现节流函数(throttle)
节流(throttle)是指连续触发事件,但在 n 秒内只执行一次函数。节流会稀释函数的执行频率。
适用场景: 滚动加载、拖拽、鼠标移动等。
/**
* 节流函数 - 时间戳版(立即执行)
* @param {Function} fn - 需要节流的函数
* @param {number} delay - 间隔时间(毫秒)
* @returns {Function} 返回节流处理后的函数
*/
function throttle(fn, delay) {
let lastTime = 0;
return function (...args) {
const context = this;
const now = Date.now();
if (now - lastTime >= delay) {
fn.apply(context, args);
lastTime = now;
}
};
}
/**
* 节流函数 - 定时器版(延迟执行)
* @param {Function} fn - 需要节流的函数
* @param {number} delay - 间隔时间(毫秒)
* @returns {Function} 返回节流处理后的函数
*/
function throttleTimer(fn, delay) {
let timer = null;
return function (...args) {
const context = this;
if (!timer) {
timer = setTimeout(() => {
fn.apply(context, args);
timer = null;
}, delay);
}
};
}
/**
* 节流函数 - 完整版(结合时间戳和定时器,保证最后一次执行)
* @param {Function} fn - 需要节流的函数
* @param {number} delay - 间隔时间(毫秒)
* @returns {Function} 返回节流处理后的函数
*/
function throttleComplete(fn, delay) {
let lastTime = 0;
let timer = null;
return function (...args) {
const context = this;
const now = Date.now();
const remaining = delay - (now - lastTime);
// 如果剩余时间 <= 0,说明可以立即执行
if (remaining <= 0) {
if (timer) {
clearTimeout(timer);
timer = null;
}
fn.apply(context, args);
lastTime = now;
} else if (!timer) {
// 否则设置一个定时器在剩余时间后执行(保证最后一次触发能执行)
timer = setTimeout(() => {
fn.apply(context, args);
lastTime = Date.now();
timer = null;
}, remaining);
}
};
}
实现instanceOf
instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。
/**
* 自定义 instanceof
* @param {object} instance - 实例对象
* @param {Function} constructor - 构造函数
* @returns {boolean}
*/
function myInstanceof(instance, constructor) {
// 基本类型直接返回 false
if (typeof instance !== 'object' || instance === null) {
return false;
}
// 获取实例的原型(__proto__)
let proto = Object.getPrototypeOf(instance);
// 获取构造函数的 prototype 对象
const prototype = constructor.prototype;
// 沿着原型链向上查找
while (proto !== null) {
if (proto === prototype) {
return true;
}
proto = Object.getPrototypeOf(proto);
}
return false;
}
// --- 测试 ---
// console.log(myInstanceof([], Array)); // true
// console.log(myInstanceof({}, Object)); // true
// console.log(myInstanceof(1, Number)); // false(基本类型)
实现new的过程
new 关键字做了以下事情:
- 创建一个空对象
- 将空对象的
__proto__指向构造函数的prototype - 将构造函数的
this绑定到新对象上并执行构造函数 - 如果构造函数返回对象,则返回该对象;否则返回新创建的对象
/**
* 模拟实现 new 操作符
* @param {Function} constructor - 构造函数
* @param {...any} args - 参数
* @returns {object} 实例对象
*/
function myNew(constructor, ...args) {
// 1. 创建一个空对象,并将它的原型指向构造函数的 prototype
const obj = Object.create(constructor.prototype);
// 2. 将构造函数中的 this 指向这个新对象,并执行构造函数
const result = constructor.apply(obj, args);
// 3. 如果构造函数返回了对象(引用类型),则返回该对象;否则返回新创建的对象
return (typeof result === 'object' && result !== null) || typeof result === 'function'
? result
: obj;
}
// --- 测试 ---
// function Person(name, age) {
// this.name = name;
// this.age = age;
// }
// const p = myNew(Person, '张三', 18);
// console.log(p.name); // 张三
实现call方法
call 方法调用一个函数,并指定 this 值和参数列表(逐个传入)。
/**
* 模拟实现 Function.prototype.call
* @param {object|null} context - 需要绑定的 this 上下文
* @param {...any} args - 参数列表
* @returns {any} 函数执行结果
*/
Function.prototype.myCall = function (context, ...args) {
// 如果 context 为 null/undefined,则指向全局对象(浏览器中是 window,Node 中是 global)
context = context ?? globalThis;
// 使用 Symbol 创建唯一键,避免属性名冲突
const fnKey = Symbol('fn');
// 将当前函数(this)作为 context 的一个属性
context[fnKey] = this;
// 执行函数并获取结果
const result = context[fnKey](...args);
// 删除添加的属性
delete context[fnKey];
return result;
};
// --- 测试 ---
// const obj = { value: 1 };
// function test(a, b) { return this.value + a + b; }
// console.log(test.myCall(obj, 2, 3)); // 6
实现apply方法
apply 方法与 call 类似,区别在于参数以数组形式传入。
/**
* 模拟实现 Function.prototype.apply
* @param {object|null} context - 需要绑定的 this 上下文
* @param {Array} args - 参数数组
* @returns {any} 函数执行结果
*/
Function.prototype.myApply = function (context, args) {
context = context ?? globalThis;
const fnKey = Symbol('fn');
context[fnKey] = this;
// args 可能为 null/undefined
const result = args ? context[fnKey](...args) : context[fnKey]();
delete context[fnKey];
return result;
};
实现bind方法
bind 方法创建一个新函数,当调用时将其 this 值绑定到提供的值,并可预设部分参数。
/**
* 模拟实现 Function.prototype.bind
* @param {object|null} context - 需要绑定的 this 上下文
* @param {...any} args - 预设的参数
* @returns {Function} 返回绑定后的新函数
*/
Function.prototype.myBind = function (context, ...args) {
const originalFn = this;
// 返回的新函数
function boundFn(...newArgs) {
// 如果使用 new 调用(boundFn 作为构造函数),this 指向新创建的实例
// 此时 this instanceof boundFn 为 true
return originalFn.apply(
this instanceof boundFn ? this : (context ?? globalThis),
[...args, ...newArgs]
);
}
// 维护原型链,使 new 调用时能正确继承
if (originalFn.prototype) {
boundFn.prototype = Object.create(originalFn.prototype);
}
return boundFn;
};
实现深拷贝
深拷贝会拷贝对象的所有层级,新对象与原对象完全独立,互不影响。
/**
* 深拷贝函数 - 基础版(支持对象、数组、基本类型)
* @param {any} obj - 需要拷贝的对象
* @returns {any} 拷贝后的对象
*/
function deepClone(obj) {
// 基本类型或 null,直接返回
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 处理 Date
if (obj instanceof Date) {
return new Date(obj.getTime());
}
// 处理 RegExp
if (obj instanceof RegExp) {
return new RegExp(obj.source, obj.flags);
}
// 处理数组或对象
const clone = Array.isArray(obj) ? [] : {};
// 使用 Reflect.ownKeys 可以获取包括 Symbol 在内的所有键
for (const key of Reflect.ownKeys(obj)) {
clone[key] = deepClone(obj[key]);
}
return clone;
}
/**
* 深拷贝函数 - 完整版(支持循环引用、Map、Set 等)
* @param {any} obj - 需要拷贝的对象
* @param {WeakMap} cache - 缓存已拷贝对象,解决循环引用
* @returns {any} 拷贝后的对象
*/
function deepCloneComplete(obj, cache = new WeakMap()) {
// 基本类型或 null
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 解决循环引用
if (cache.has(obj)) {
return cache.get(obj);
}
// 处理 Date
if (obj instanceof Date) {
return new Date(obj.getTime());
}
// 处理 RegExp
if (obj instanceof RegExp) {
return new RegExp(obj.source, obj.flags);
}
// 处理 Map
if (obj instanceof Map) {
const cloneMap = new Map();
cache.set(obj, cloneMap);
obj.forEach((value, key) => {
cloneMap.set(deepCloneComplete(key, cache), deepCloneComplete(value, cache));
});
return cloneMap;
}
// 处理 Set
if (obj instanceof Set) {
const cloneSet = new Set();
cache.set(obj, cloneSet);
obj.forEach(value => {
cloneSet.add(deepCloneComplete(value, cache));
});
return cloneSet;
}
// 处理数组或普通对象
const clone = Array.isArray(obj) ? [] : {};
cache.set(obj, clone);
for (const key of Reflect.ownKeys(obj)) {
clone[key] = deepCloneComplete(obj[key], cache);
}
return clone;
}
实现类的继承
/**
* 类的继承 - 使用 ES6 class 语法
*/
class Animal {
constructor(name) {
this.name = name;
}
speak() {
return `${this.name} 发出声音`;
}
static isAnimal(obj) {
return obj instanceof Animal;
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 调用父类构造函数
this.breed = breed;
}
speak() {
return `${this.name}(${this.breed})汪汪叫`;
}
fetch() {
return `${this.name} 在捡球`;
}
}
实现类的继承-简版
/**
* 类的继承 - 简版(ES5 寄生组合式继承)
*/
function Parent(name) {
this.name = name;
}
Parent.prototype.getName = function () {
return this.name;
};
function Child(name, age) {
Parent.call(this, name); // 继承属性
this.age = age;
}
// 继承方法:Child.prototype.__proto__ = Parent.prototype
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
Child.prototype.getAge = function () {
return this.age;
};
ES5实现继承-详细
ES5 中实现继承有多种方式,寄生组合式继承是最优方案。
/**
* ES5 实现继承 - 寄生组合式继承(最优方案)
*/
// 工具函数:用于子类继承父类原型
function inherit(child, parent) {
// 1. 创建以 parent.prototype 为原型的对象
// 2. 将该对象赋值给 child.prototype
child.prototype = Object.create(parent.prototype);
// 3. 修正 constructor 指向
child.prototype.constructor = child;
// 4. 存储父类引用(便于访问父类方法)
child._super = parent;
}
// 父类
function Person(name, age) {
this.name = name;
this.age = age;
this.hobbies = ['读书', '运动'];
}
Person.prototype.sayHello = function () {
return `你好,我叫${this.name},今年${this.age}岁`;
};
Person.staticMethod = function () {
return 'Person 静态方法';
};
// 子类
function Student(name, age, grade) {
// 调用父类构造函数,继承实例属性
Person.call(this, name, age);
this.grade = grade;
this.study = function () {
return `${this.name} 在学习`;
};
}
// 继承父类原型方法
inherit(Student, Person);
// 子类自己的原型方法
Student.prototype.getGrade = function () {
return `${this.name} 在${this.grade}年级`;
};
// 继承静态属性
Student.staticMethod = Person.staticMethod;
Promise相关
实现Promise的resolve
/**
* 实现 Promise.resolve
* 返回一个以给定值解析后的 Promise 对象
* - 如果值是 Promise,则返回该 Promise
* - 如果值是 thenable 对象,则将其转换为 Promise 并执行 then
* - 否则返回一个以该值 resolved 的 Promise
*/
Promise.myResolve = function (value) {
// 如果已经是 Promise 实例,直接返回
if (value instanceof Promise) {
return value;
}
// 如果值是 thenable 对象,用 Promise 包装并执行 then
if (value !== null && typeof value === 'object' && typeof value.then === 'function') {
return new Promise((resolve, reject) => {
value.then(resolve, reject);
});
}
// 普通值,返回 resolved 的 Promise
return new Promise(resolve => resolve(value));
};
实现Promise.reject
/**
* 实现 Promise.reject
* 返回一个以给定原因 rejected 的 Promise 对象
*/
Promise.myReject = function (reason) {
return new Promise((_, reject) => reject(reason));
};
实现Promise.prototype.finally
/**
* 实现 Promise.prototype.finally
* - 无论 Promise 是 fulfilled 还是 rejected,都会执行回调
* - 回调不接收任何参数
* - 返回一个新的 Promise,保持状态与原始 Promise 一致
* - 如果回调中抛出错误或返回 rejected Promise,则 finally 返回的 Promise 会以此错误 reject
*/
Promise.prototype.myFinally = function (callback) {
return this.then(
value => Promise.resolve(callback()).then(() => value),
reason => Promise.resolve(callback()).then(() => { throw reason; })
);
};
实现Promise.all
/**
* 实现 Promise.all
* - 接收一个 Promise 可迭代对象
* - 所有 Promise 都成功时,返回一个包含所有结果的数组(顺序与输入一致)
* - 任一 Promise 失败,立即 reject 该错误
* - 空的可迭代对象返回一个 resolved 的空数组
*/
Promise.myAll = function (promises) {
return new Promise((resolve, reject) => {
// 将可迭代对象转为数组
const iterable = Array.from(promises);
if (iterable.length === 0) {
resolve([]);
return;
}
const results = [];
let count = 0;
iterable.forEach((item, index) => {
// 用 Promise.resolve 包装,确保非 Promise 值也能处理
Promise.resolve(item).then(
value => {
results[index] = value; // 保持顺序
count++;
if (count === iterable.length) {
resolve(results);
}
},
reason => {
reject(reason); // 任一失败立即 reject
}
);
});
});
};
实现Promise.allSettled
/**
* 实现 Promise.allSettled
* - 等待所有 Promise 完成(无论成功或失败)
* - 返回每个 Promise 的结果对象:{status: 'fulfilled', value} 或 {status: 'rejected', reason}
* - 不会因为某个 Promise 失败而 reject
*/
Promise.myAllSettled = function (promises) {
return new Promise(resolve => {
const iterable = Array.from(promises);
if (iterable.length === 0) {
resolve([]);
return;
}
const results = [];
let count = 0;
iterable.forEach((item, index) => {
Promise.resolve(item).then(
value => {
results[index] = { status: 'fulfilled', value };
},
reason => {
results[index] = { status: 'rejected', reason };
}
).finally(() => {
count++;
if (count === iterable.length) {
resolve(results);
}
});
});
});
};
实现Promise.race
/**
* 实现 Promise.race
* - 返回第一个 settled 的 Promise 的结果(无论成功或失败)
* - 空数组时 Promise 永远处于 pending 状态
*/
Promise.myRace = function (promises) {
return new Promise((resolve, reject) => {
const iterable = Array.from(promises);
iterable.forEach(item => {
Promise.resolve(item).then(resolve, reject);
});
});
};
实现一个简版Promise
/**
* 简版 Promise - 包含核心功能
* 状态:pending -> fulfilled / rejected
*/
class SimplePromise {
constructor(executor) {
this.status = 'pending'; // 初始状态
this.value = undefined; // 成功值
this.reason = undefined; // 失败原因
this.onFulfilledCallbacks = []; // 异步时存储成功回调
this.onRejectedCallbacks = []; // 异步时存储失败回调
const resolve = value => {
if (this.status === 'pending') {
this.status = 'fulfilled';
this.value = value;
// 执行所有缓存的成功回调
this.onFulfilledCallbacks.forEach(fn => fn());
}
};
const reject = reason => {
if (this.status === 'pending') {
this.status = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
if (this.status === 'fulfilled') {
onFulfilled(this.value);
}
if (this.status === 'rejected') {
onRejected(this.reason);
}
if (this.status === 'pending') {
this.onFulfilledCallbacks.push(() => onFulfilled(this.value));
this.onRejectedCallbacks.push(() => onRejected(this.reason));
}
}
}
Promise实现-详细
/**
* Promise 详细实现 - 支持链式调用
* - 通过 then 方法返回新的 Promise 实现链式调用
* - 处理回调返回值和 Promise 的情况
*/
class DetailedPromise {
constructor(executor) {
this.status = 'pending';
this.value = undefined;
this.reason = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = value => {
if (this.status === 'pending') {
this.status = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach(fn => fn());
}
};
const reject = reason => {
if (this.status === 'pending') {
this.status = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
// 参数可选,如果非函数则透传值
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : r => { throw r; };
const newPromise = new DetailedPromise((resolve, reject) => {
const handleFulfilled = () => {
setTimeout(() => { // 保证异步执行
try {
const result = onFulfilled(this.value);
resolvePromise(newPromise, result, resolve, reject);
} catch (err) {
reject(err);
}
});
};
const handleRejected = () => {
setTimeout(() => {
try {
const result = onRejected(this.reason);
resolvePromise(newPromise, result, resolve, reject);
} catch (err) {
reject(err);
}
});
};
if (this.status === 'fulfilled') {
handleFulfilled();
} else if (this.status === 'rejected') {
handleRejected();
} else {
this.onFulfilledCallbacks.push(handleFulfilled);
this.onRejectedCallbacks.push(handleRejected);
}
});
return newPromise;
}
catch(onRejected) {
return this.then(null, onRejected);
}
}
/**
* 解析 then 回调的返回值
* - 如果返回值是新 Promise 自身,抛循环引用错误
* - 如果返回值是 Promise,则等待其完成
* - 如果返回值是 thenable 对象,执行其 then 方法
* - 否则直接 resolve
*/
function resolvePromise(promise, result, resolve, reject) {
if (promise === result) {
reject(new TypeError('Chaining cycle detected for promise'));
return;
}
if (result instanceof DetailedPromise) {
result.then(resolve, reject);
} else if (result !== null && typeof result === 'object' && typeof result.then === 'function') {
result.then(resolve, reject);
} else {
resolve(result);
}
}
实现Promisify
Promisify 将 Node.js 风格的回调函数(最后一个参数是回调函数,回调的第一个参数是 error)转换为 Promise 版本。
/**
* 实现 promisify - 将 Node.js 回调风格转为 Promise
* @param {Function} fn - Node.js 风格函数(最后一个参数为回调)
* @returns {Function} 返回 Promise 风格的函数
*/
function promisify(fn) {
return function (...args) {
return new Promise((resolve, reject) => {
// 追加一个回调函数作为最后一个参数
fn.call(this, ...args, (err, ...results) => {
if (err) {
reject(err);
} else {
// 如果只有一个结果值,直接返回;多个结果以数组形式返回
resolve(results.length === 1 ? results[0] : results);
}
});
});
};
}
/**
* promisify 全部方法 - 将一个对象中的所有回调风格方法转为 Promise
* @param {object} obj - 包含回调风格方法的对象
* @returns {object} 返回新对象,所有方法都 Promise 化
*/
function promisifyAll(obj) {
const result = {};
for (const key of Object.keys(obj)) {
const fn = obj[key];
if (typeof fn === 'function') {
result[key + 'Async'] = promisify(fn);
}
}
return result;
}
完整实现Promises/A+规范
完整的 Promises/A+ 规范实现,包含 then 链式调用、值穿透、异步执行、错误处理等。
/**
* Promises/A+ 规范完整实现
* 参考:https://promisesaplus.com/
*/
// 状态常量
const PENDING = 'pending';
const FULFILLED = 'fulfilled';
const REJECTED = 'rejected';
class PromiseA {
constructor(executor) {
this.status = PENDING;
this.value = null;
this.reason = null;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = value => {
if (this.status === PENDING) {
this.status = FULFILLED;
this.value = value;
this.onFulfilledCallbacks.forEach(fn => fn(this.value));
}
};
const reject = reason => {
if (this.status === PENDING) {
this.status = REJECTED;
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn(this.reason));
}
};
try {
executor(resolve, reject);
} catch (err) {
reject(err);
}
}
then(onFulfilled, onRejected) {
// 值穿透:onFulfilled/onRejected 如果不是函数则创建默认函数透传值
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : v => v;
onRejected = typeof onRejected === 'function' ? onRejected : r => { throw r; };
const promise2 = new PromiseA((resolve, reject) => {
// 封装执行函数,确保异步调用
const wrapFulfilled = () => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
this.resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
};
const wrapRejected = () => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
this.resolvePromise(promise2, x, resolve, reject);
} catch (e) {
reject(e);
}
});
};
if (this.status === FULFILLED) {
wrapFulfilled();
} else if (this.status === REJECTED) {
wrapRejected();
} else {
this.onFulfilledCallbacks.push(wrapFulfilled);
this.onRejectedCallbacks.push(wrapRejected);
}
});
return promise2;
}
catch(onRejected) {
return this.then(null, onRejected);
}
// Promise 解析过程:Resolve(promise2, x)
resolvePromise(promise2, x, resolve, reject) {
// 2.3.1 如果 promise2 和 x 指向同一对象,抛出 TypeError
if (promise2 === x) {
reject(new TypeError('Chaining cycle detected for promise'));
return;
}
// 2.3.2 如果 x 是 PromiseA 实例
if (x instanceof PromiseA) {
if (x.status === FULFILLED) {
resolve(x.value);
} else if (x.status === REJECTED) {
reject(x.reason);
} else {
x.then(y => this.resolvePromise(promise2, y, resolve, reject), reject);
}
return;
}
// 2.3.3 如果 x 是对象或函数(thenable)
if (x !== null && (typeof x === 'object' || typeof x === 'function')) {
let called = false; // 2.3.3.3.3 确保只调用一次
try {
const then = x.then;
if (typeof then === 'function') {
// 2.3.3.3 如果 then 是函数,将 x 作为 this 调用
then.call(
x,
y => {
if (called) return;
called = true;
this.resolvePromise(promise2, y, resolve, reject);
},
r => {
if (called) return;
called = true;
reject(r);
}
);
} else {
// 2.3.3.4 如果 then 不是函数,以 x 为值 resolve
resolve(x);
}
} catch (e) {
// 2.3.3.2 如果取 then 抛出异常,reject
if (called) return;
called = true;
reject(e);
}
} else {
// 2.3.4 如果 x 不是对象或函数,以 x 为值 resolve
resolve(x);
}
}
// 静态方法
static resolve(value) {
if (value instanceof PromiseA) return value;
return new PromiseA(resolve => resolve(value));
}
static reject(reason) {
return new PromiseA((_, reject) => reject(reason));
}
static all(iterable) {
return new PromiseA((resolve, reject) => {
const arr = Array.from(iterable);
if (arr.length === 0) return resolve([]);
const results = [];
let count = 0;
arr.forEach((item, i) => {
PromiseA.resolve(item).then(
val => {
results[i] = val;
count++;
if (count === arr.length) resolve(results);
},
reject
);
});
});
}
static race(iterable) {
return new PromiseA((resolve, reject) => {
Array.from(iterable).forEach(item => {
PromiseA.resolve(item).then(resolve, reject);
});
});
}
}
// 用于测试的 deferred 方法
PromiseA.deferred = function () {
const result = {};
result.promise = new PromiseA((resolve, reject) => {
result.resolve = resolve;
result.reject = reject;
});
return result;
};
// module.exports = PromiseA;
设计模式
实现发布订阅模式
发布订阅模式中,订阅者(Subscriber)将自己想订阅的事件注册到调度中心(Event Channel),当发布者(Publisher)发布事件到调度中心时,由调度中心统一处理订阅者的回调。
/**
* 发布订阅模式(Event Emitter)
* - on: 订阅事件
* - emit: 发布事件
* - off: 取消订阅
* - once: 订阅一次
*/
class EventEmitter {
constructor() {
this.events = {}; // 存储所有事件及其对应的回调
}
/**
* 订阅事件
* @param {string} event - 事件名
* @param {Function} callback - 回调函数
* @returns {Function} 取消订阅的函数
*/
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
// 返回取消订阅的函数
return () => this.off(event, callback);
}
/**
* 发布事件
* @param {string} event - 事件名
* @param {...any} args - 传递给回调的参数
*/
emit(event, ...args) {
const callbacks = this.events[event];
if (callbacks) {
callbacks.forEach(cb => {
try {
cb(...args);
} catch (err) {
console.error(`Event "${event}" callback error:`, err);
}
});
}
}
/**
* 取消订阅
* @param {string} event - 事件名
* @param {Function} callback - 要移除的回调(不传则移除该事件所有回调)
*/
off(event, callback) {
if (!callback) {
// 不传回调则移除该事件所有订阅
delete this.events[event];
return;
}
const callbacks = this.events[event];
if (callbacks) {
this.events[event] = callbacks.filter(cb => cb !== callback);
}
}
/**
* 订阅一次(触发后自动取消)
* @param {string} event - 事件名
* @param {Function} callback - 回调函数
*/
once(event, callback) {
const wrapper = (...args) => {
callback(...args);
this.off(event, wrapper);
};
this.on(event, wrapper);
}
}
实现观察者模式
观察者模式(Observer)中,目标对象(Subject)维护一组观察者(Observer),当自身状态变化时通知所有观察者。与发布订阅的区别:观察者模式是松耦合的,通常在同一个应用内,目标直接通知观察者。
/**
* 观察者模式
*/
// 目标对象(被观察者)
class Subject {
constructor() {
this.observers = []; // 观察者列表
}
// 添加观察者
addObserver(observer) {
if (observer && observer.update) {
this.observers.push(observer);
}
}
// 移除观察者
removeObserver(observer) {
this.observers = this.observers.filter(obs => obs !== observer);
}
// 通知所有观察者
notify(data) {
this.observers.forEach(observer => observer.update(data));
}
}
// 观察者
class Observer {
constructor(name) {
this.name = name;
}
// 被通知时调用的方法
update(data) {
console.log(`${this.name} 收到通知:`, data);
}
}
// --- 使用示例 ---
// const subject = new Subject();
// const obs1 = new Observer('观察者1');
// const obs2 = new Observer('观察者2');
// subject.addObserver(obs1);
// subject.addObserver(obs2);
// subject.notify('状态变了!');
实现单例模式
单例模式确保一个类只有一个实例,并提供一个全局访问点。
/**
* 单例模式 - 使用闭包实现
*/
function createSingleton(className) {
let instance = null;
return class Singleton {
constructor(...args) {
if (instance) {
return instance;
}
instance = this;
if (typeof className === 'function') {
className.apply(this, args);
}
}
};
}
/**
* 单例模式 - 使用静态方法实现
*/
class Singleton {
constructor(name) {
if (Singleton._instance) {
return Singleton._instance;
}
this.name = name;
Singleton._instance = this;
}
static getInstance(name) {
if (!Singleton._instance) {
Singleton._instance = new Singleton(name);
}
return Singleton._instance;
}
}
Ajax相关
实现Ajax - 原生实现
/**
* 原生 Ajax 实现 - 使用 XMLHttpRequest
* @param {object} options - 配置项
* @param {string} options.url - 请求地址
* @param {string} [options.method='GET'] - 请求方法
* @param {object} [options.data] - 请求数据
* @param {boolean} [options.async=true] - 是否异步
* @param {object} [options.headers] - 自定义请求头
* @param {Function} [options.onSuccess] - 成功回调
* @param {Function} [options.onError] - 失败回调
* @param {Function} [options.onProgress] - 进度回调
*/
function ajax(options) {
const xhr = new XMLHttpRequest();
const method = (options.method || 'GET').toUpperCase();
const async = options.async !== false;
// 处理 GET 请求参数拼接到 URL
let url = options.url;
if (method === 'GET' && options.data) {
const params = Object.entries(options.data)
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
.join('&');
url += (url.includes('?') ? '&' : '?') + params;
}
xhr.open(method, url, async);
// 设置请求头
if (options.headers) {
Object.entries(options.headers).forEach(([key, value]) => {
xhr.setRequestHeader(key, value);
});
}
// 设置响应类型
xhr.responseType = options.responseType || 'json';
// 监听状态变化
xhr.onreadystatechange = function () {
if (xhr.readyState === 4) {
if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
options.onSuccess && options.onSuccess(xhr.response);
} else {
options.onError && options.onError(new Error(`请求失败: ${xhr.status}`));
}
}
};
// 监听进度
if (options.onProgress) {
xhr.onprogress = options.onProgress;
}
// 发送请求
const body = method === 'POST' && options.data ? JSON.stringify(options.data) : null;
xhr.send(body);
}
Ajax相关 - Promise实现
/**
* Promise 版本的 Ajax
* @param {string} url - 请求地址
* @param {object} [options] - 配置项
* @returns {Promise} 返回 Promise
*/
function ajaxPromise(url, options = {}) {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
const method = (options.method || 'GET').toUpperCase();
let requestUrl = url;
if (method === 'GET' && options.data) {
const params = Object.entries(options.data)
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
.join('&');
requestUrl += (url.includes('?') ? '&' : '?') + params;
}
xhr.open(method, requestUrl, true);
if (options.headers) {
Object.entries(options.headers).forEach(([key, value]) => {
xhr.setRequestHeader(key, value);
});
}
xhr.responseType = options.responseType || 'json';
xhr.withCredentials = options.withCredentials || false;
xhr.timeout = options.timeout || 0;
xhr.onload = function () {
if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
resolve(xhr.response);
} else {
reject(new Error(`HTTP ${xhr.status}: ${xhr.statusText}`));
}
};
xhr.onerror = function () {
reject(new Error('网络错误'));
};
xhr.ontimeout = function () {
reject(new Error('请求超时'));
};
const body = ['POST', 'PUT', 'PATCH'].includes(method) && options.data
? JSON.stringify(options.data)
: null;
xhr.send(body);
});
}
// --- 使用示例 ---
// ajaxPromise('/api/users')
// .then(data => console.log(data))
// .catch(err => console.error(err));
实现JSONP方法
JSONP 利用 <script> 标签没有跨域限制的特性来实现跨域请求。
/**
* JSONP 实现
* @param {string} url - 请求地址
* @param {object} params - URL 参数
* @param {string} [callbackName='callback'] - 后端接收的回调函数名
* @returns {Promise} 返回 Promise
*/
function jsonp(url, params = {}, callbackName = 'callback') {
return new Promise((resolve, reject) => {
// 生成唯一的回调函数名
const uniqueCallback = `jsonp_${Date.now()}_${Math.random().toString(36).slice(2)}`;
// 构建 URL 参数
const queryParams = {
...params,
[callbackName]: uniqueCallback
};
const queryString = Object.entries(queryParams)
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
.join('&');
const fullUrl = url + (url.includes('?') ? '&' : '?') + queryString;
// 创建全局回调函数
window[uniqueCallback] = function (data) {
resolve(data);
cleanup();
};
// 创建 script 标签
const script = document.createElement('script');
script.src = fullUrl;
script.async = true;
// 超时处理
const timeout = setTimeout(() => {
reject(new Error('JSONP 请求超时'));
cleanup();
}, params.timeout || 10000);
// 错误处理
script.onerror = function () {
reject(new Error('JSONP 加载失败'));
cleanup();
};
// 清理函数
function cleanup() {
clearTimeout(timeout);
delete window[uniqueCallback];
document.body.removeChild(script);
}
document.body.appendChild(script);
});
}
实现async/await
async/await 本质上是 Generator 和 Promise 的语法糖。
/**
* 模拟实现 async/await - 使用 Generator + Promise
* async 函数返回 Promise,内部通过 Generator 控制执行流程
*/
// 一个 async 函数的示例
// async function fetchData() {
// const data1 = await api1();
// const data2 = await api2(data1);
// return data2;
// }
基于Generator函数实现async/await原理
/**
* async/await 原理实现 - 使用 Generator + Promise
*
* async/await 的本质:
* 1. async 函数会返回一个 Promise
* 2. await 后面的表达式会被 Promise.resolve() 包装
* 3. Generator 函数通过 yield 暂停,通过 next() 恢复执行
* 4. 自执行器(runner)自动执行 Generator,每次 yield 返回 Promise
* 并在 Promise resolve 后继续 next()
*/
/**
* Generator 自执行器 - 模拟 async
* @param {GeneratorFunction} generatorFn - Generator 函数
* @returns {Function} 返回一个函数,执行后返回 Promise
*/
function asyncWrapper(generatorFn) {
return function (...args) {
const generator = generatorFn.apply(this, args);
return new Promise((resolve, reject) => {
function step(key, arg) {
let result;
try {
result = generator[key](arg);
} catch (err) {
return reject(err);
}
const { value, done } = result;
if (done) {
return resolve(value);
}
// value 可能是 Promise 或普通值,统一用 Promise.resolve 包装
Promise.resolve(value).then(
val => step('next', val),
err => step('throw', err)
);
}
step('next');
});
};
}
// --- 使用示例 ---
// function* fetchData() {
// const data1 = yield Promise.resolve('数据1');
// console.log(data1); // '数据1'
// const data2 = yield Promise.resolve('数据2');
// console.log(data2); // '数据2'
// return '完成';
// }
//
// const asyncFetchData = asyncWrapper(fetchData);
// asyncFetchData().then(result => console.log(result)); // '完成'
ES相关
实现ES6的const
ES6 的 const 声明一个只读的常量,一旦声明就不能改变(对于引用类型,引用地址不能改变)。ES5 环境下可以通过 Object.defineProperty 模拟。
/**
* 模拟 const - 使用 Object.defineProperty
* 利用全局对象的属性,设置 writable: false 使其不可修改
*/
function myConst(key, value) {
// 在全局对象上定义不可写的属性
Object.defineProperty(globalThis, key, {
value,
writable: false, // 不可写
configurable: false, // 不可配置
enumerable: true // 可枚举
});
}
// --- 使用示例 ---
// myConst('MY_CONST', 100);
// console.log(MY_CONST); // 100
// MY_CONST = 200; // 严格模式下报错,非严格模式下静默失败
实现ES6的extends
ES6 extends 关键字用于类继承,内部原理是寄生组合式继承。
/**
* ES6 extends 实现原理(ES5 模拟)
* - 子类继承父类:原型链继承 + 构造函数窃取
*/
function extendsImpl(child, parent) {
// 1. 继承父类静态属性
Object.setPrototypeOf(child, parent);
// 2. 继承父类原型方法
child.prototype = Object.create(parent.prototype, {
constructor: {
value: child,
writable: true,
configurable: true
}
});
// 3. 存储父类引用
child._super = parent;
}
// --- 使用示例 ---
// function Animal(name) { this.name = name; }
// Animal.prototype.speak = function() { return this.name; };
//
// function Dog(name, breed) {
// Animal.call(this, name);
// this.breed = breed;
// }
// extendsImpl(Dog, Animal);
实现Object.create
Object.create 方法创建一个新对象,使用现有的对象作为新创建对象的原型。
/**
* 实现 Object.create
* @param {object|null} proto - 新对象的原型
* @param {object} [propertiesObject] - 属性描述符
* @returns {object} 新对象
*/
function myObjectCreate(proto, propertiesObject) {
// proto 必须为对象或 null
if (typeof proto !== 'object' && typeof proto !== 'function') {
throw new TypeError('Object prototype may only be an Object or null');
}
// 创建一个临时构造函数
function F() {}
F.prototype = proto;
F.prototype.constructor = F;
const obj = new F();
// 如果 proto 为 null,设置原型为 null
if (proto === null) {
Object.setPrototypeOf(obj, null);
}
// 处理属性描述符
if (propertiesObject !== undefined) {
Object.defineProperties(obj, propertiesObject);
}
return obj;
}
实现Object.freeze
Object.freeze 冻结一个对象,使其属性不可修改、不可添加、不可删除。
/**
* 实现 Object.freeze
* @param {object} obj - 要冻结的对象
* @returns {object} 被冻结的对象
*/
function myObjectFreeze(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 获取所有属性(包括不可枚举的、Symbol 的)
const keys = [
...Object.getOwnPropertyNames(obj),
...Object.getOwnPropertySymbols(obj)
];
keys.forEach(key => {
const descriptor = Object.getOwnPropertyDescriptor(obj, key);
// 重新定义属性为不可写、不可配置
Object.defineProperty(obj, key, {
writable: false,
configurable: false
});
// 如果属性值是对象,递归冻结
if (descriptor && typeof descriptor.value === 'object' && descriptor.value !== null) {
myObjectFreeze(descriptor.value);
}
});
// 阻止添加/删除属性
Object.preventExtensions(obj);
return obj;
}
实现Object.is
Object.is 判断两个值是否为同一个值。与 === 的区别:
Object.is(NaN, NaN)返回trueObject.is(-0, +0)返回false
/**
* 实现 Object.is
* @param {any} a
* @param {any} b
* @returns {boolean}
*/
function myObjectIs(a, b) {
// 处理 NaN 的情况
if (a !== a && b !== b) {
return true;
}
// 处理 +0 和 -0 的情况
if (a === 0 && b === 0) {
return 1 / a === 1 / b; // 1/+0 = Infinity, 1/-0 = -Infinity
}
return a === b;
}
实现一个compose函数
compose 函数将多个函数组合成一个函数,从右到左执行(类似 redux 的 compose)。
/**
* 实现 compose 函数 - 从右到左组合
* @param {...Function} fns - 要组合的函数
* @returns {Function} 组合后的函数
*/
function compose(...fns) {
if (fns.length === 0) {
return arg => arg;
}
if (fns.length === 1) {
return fns[0];
}
return fns.reduce((prev, next) => {
return (...args) => prev(next(...args));
});
}
/**
* 实现 pipe 函数 - 从左到右组合(compose 的反向)
* @param {...Function} fns - 要组合的函数
* @returns {Function} 组合后的函数
*/
function pipe(...fns) {
if (fns.length === 0) {
return arg => arg;
}
if (fns.length === 1) {
return fns[0];
}
return fns.reduce((prev, next) => {
return (...args) => next(prev(...args));
});
}
// --- 使用示例 ---
// const add1 = x => x + 1;
// const double = x => x * 2;
// const composed = compose(double, add1); // double(add1(x))
// console.log(composed(3)); // 8 ((3+1)*2)
实现一个迭代器生成函数
/**
* 手动实现迭代器生成函数
* 迭代器必须实现 next() 方法,返回 { value, done }
*/
/**
* 创建一个范围迭代器
* @param {number} start - 起始值
* @param {number} end - 结束值
* @param {number} [step=1] - 步长
* @returns {object} 迭代器
*/
function createRangeIterator(start, end, step = 1) {
let current = start;
const iterator = {
next() {
if (current <= end) {
const value = current;
current += step;
return { value, done: false };
}
return { value: undefined, done: true };
}
};
return iterator;
}
/**
* 使普通对象可迭代 - 实现 [Symbol.iterator]
*/
function makeIterable(obj) {
obj[Symbol.iterator] = function () {
const keys = Object.keys(this);
let index = 0;
return {
next: () => {
if (index < keys.length) {
const key = keys[index++];
return { value: { key, value: obj[key] }, done: false };
}
return { value: undefined, done: true };
}
};
};
return obj;
}
ES6对迭代器的实现
ES6 提供了多种迭代器实现方式:for...of、Array、Map、Set、String、Generator 等。
/**
* ES6 迭代器实现 - 可迭代协议 [Symbol.iterator]
*/
// 实现一个可迭代的数组包装器
class IterableArray {
constructor(arr) {
this.data = arr;
}
// 实现 [Symbol.iterator] 方法
[Symbol.iterator]() {
let index = 0;
const data = this.data;
return {
next() {
if (index < data.length) {
return { value: data[index++], done: false };
}
return { value: undefined, done: true };
}
};
}
}
// --- 使用示例 ---
// const arr = new IterableArray([10, 20, 30]);
// for (const item of arr) {
// console.log(item); // 10, 20, 30
// }
// console.log([...arr]); // [10, 20, 30]
实现迭代器生成函数 - Generator版
/**
* 使用 Generator 函数实现迭代器
* Generator 函数返回一个迭代器,每次调用 next() 执行到下一个 yield
*/
// 创建一个范围迭代器 - Generator 版
function* rangeGenerator(start, end, step = 1) {
for (let i = start; i <= end; i += step) {
yield i;
}
}
// --- 使用示例 ---
// const iter = rangeGenerator(1, 5);
// console.log(iter.next()); // { value: 1, done: false }
// console.log([...rangeGenerator(1, 3)]); // [1, 2, 3]
/**
* 使对象可迭代 - Generator 版
* @param {object} obj - 普通对象
* @returns {object} 可迭代的对象
*/
function makeIterableWithGenerator(obj) {
obj[Symbol.iterator] = function* () {
const keys = Object.keys(this);
for (const key of keys) {
yield { key, value: this[key] };
}
};
return obj;
}
setTimeout 模拟实现 setInterval
/**
* 使用 setTimeout 模拟实现 setInterval
* 优势:可以等待上一次任务执行完成后再开始计时,避免 setInterval 可能存在的执行叠加问题
*
* @param {Function} fn - 要执行的函数
* @param {number} delay - 间隔时间(毫秒)
* @param {...any} args - 传递给函数的参数
* @returns {object} 返回一个包含 clear 方法的对象,用于停止定时器
*/
function mySetInterval(fn, delay, ...args) {
let timerId = null;
function loop() {
timerId = setTimeout(() => {
fn(...args);
loop(); // 递归调用,实现循环
}, delay);
}
loop();
// 返回一个对象,提供取消方法
return {
clear() {
clearTimeout(timerId);
timerId = null;
}
};
}
// --- 使用示例 ---
// const timer = mySetInterval(() => console.log('tick'), 1000);
// setTimeout(() => timer.clear(), 5000); // 5秒后停止
setInterval 模拟实现 setTimeout
/**
* 使用 setInterval 模拟实现 setTimeout
*
* @param {Function} fn - 要执行的函数
* @param {number} delay - 延迟时间(毫秒)
* @param {...any} args - 传递给函数的参数
* @returns {object} 返回一个包含 clear 方法的对象
*/
function mySetTimeout(fn, delay, ...args) {
const timerId = setInterval(() => {
clearInterval(timerId);
fn(...args);
}, delay);
return {
clear() {
clearInterval(timerId);
}
};
}
// --- 使用示例 ---
// const timer = mySetTimeout(() => console.log('执行一次'), 2000);
// timer.clear(); // 取消
实现Node的require方法
/**
* 简易实现 Node.js 的 require 方法
*
* require 的核心机制:
* 1. 解析模块路径
* 2. 读取文件内容
* 3. 用函数包装器包裹模块代码
* 4. 传入 exports, require, module, __filename, __dirname
* 5. 执行模块代码
* 6. 返回 exports
*/
const fs = require('fs');
const path = require('path');
// 缓存已加载的模块
const moduleCache = {};
/**
* 模拟 require 函数
* @param {string} filePath - 模块文件路径
* @returns {any} 模块导出的内容
*/
function myRequire(filePath) {
// 1. 解析绝对路径
const absolutePath = path.resolve(__dirname, filePath);
// 2. 检查缓存
if (moduleCache[absolutePath]) {
return moduleCache[absolutePath].exports;
}
// 3. 创建模块对象
const module = { exports: {} };
moduleCache[absolutePath] = module;
// 4. 读取文件内容
let code;
try {
code = fs.readFileSync(absolutePath, 'utf-8');
} catch (err) {
throw new Error(`Cannot find module '${filePath}'`);
}
// 5. 创建包装函数(Node.js 实际上用 V8 的 compileFunction 包装)
const wrapper = Function('exports', 'require', 'module', '__filename', '__dirname', code);
// 6. 执行模块代码
wrapper(module.exports, myRequire, module, absolutePath, path.dirname(absolutePath));
return module.exports;
}
实现LRU淘汰算法
LRU(Least Recently Used)最近最少使用缓存淘汰算法。当缓存满时,淘汰最久未使用的数据。
/**
* LRU 缓存淘汰算法实现
* 使用 Map 保证插入顺序(迭代顺序 = 插入顺序)
*
* 时间复杂度:get O(1), put O(1)
* 空间复杂度:O(capacity)
*/
class LRUCache {
constructor(capacity) {
this.capacity = capacity; // 缓存容量
this.cache = new Map(); // 使用 Map 保持键值对顺序
}
/**
* 获取缓存值
* @param {any} key - 键
* @returns {any} 值,不存在返回 -1
*/
get(key) {
if (!this.cache.has(key)) {
return -1;
}
// 将该键移到末尾(表示最近使用)
const value = this.cache.get(key);
this.cache.delete(key);
this.cache.set(key, value);
return value;
}
/**
* 设置缓存值
* @param {any} key - 键
* @param {any} value - 值
*/
put(key, value) {
// 如果已存在,先删除
if (this.cache.has(key)) {
this.cache.delete(key);
}
// 如果容量满了,删除最久未使用的(Map 的第一个元素)
if (this.cache.size >= this.capacity) {
const oldestKey = this.cache.keys().next().value;
this.cache.delete(oldestKey);
}
// 插入到末尾(表示最近使用)
this.cache.set(key, value);
}
/**
* 获取当前缓存大小
* @returns {number}
*/
size() {
return this.cache.size;
}
/**
* 清空缓存
*/
clear() {
this.cache.clear();
}
}
// --- 使用示例 ---
// const lru = new LRUCache(2);
// lru.put(1, 'a');
// lru.put(2, 'b');
// console.log(lru.get(1)); // 'a'(1被移到最近使用)
// lru.put(3, 'c'); // 容量已满,淘汰最久未使用的2
// console.log(lru.get(2)); // -1(已被淘汰)
框架相关
将虚拟 Dom 转化为真实 Dom
/**
* 将虚拟 DOM 转化为真实 DOM
*
* 虚拟 DOM 结构示例:
* {
* tag: 'div',
* attrs: { class: 'container', id: 'app' },
* children: [
* { tag: 'h1', attrs: {}, children: ['标题'] },
* { tag: 'p', attrs: {}, children: ['段落'] }
* ]
* }
*/
/**
* 将虚拟 DOM 节点渲染为真实 DOM 节点
* @param {object} vnode - 虚拟 DOM 对象
* @returns {HTMLElement} 真实 DOM 节点
*/
function render(vnode) {
// 如果是字符串或数字,创建文本节点
if (typeof vnode === 'string' || typeof vnode === 'number') {
return document.createTextNode(vnode);
}
// 如果 vnode 是数组(Fragment),创建文档片段
if (Array.isArray(vnode)) {
const fragment = document.createDocumentFragment();
vnode.forEach(child => fragment.appendChild(render(child)));
return fragment;
}
// 创建 DOM 元素
const dom = document.createElement(vnode.tag);
// 设置属性
if (vnode.attrs) {
for (const [key, value] of Object.entries(vnode.attrs)) {
// 处理事件监听
if (key.startsWith('on')) {
const eventType = key.slice(2).toLowerCase();
dom.addEventListener(eventType, value);
} else if (key === 'className' || key === 'class') {
dom.className = value;
} else if (key === 'style' && typeof value === 'object') {
Object.assign(dom.style, value);
} else if (key === 'htmlFor') {
dom.htmlFor = value;
} else {
dom.setAttribute(key, value);
}
}
}
// 递归渲染子节点
if (vnode.children) {
vnode.children.forEach(child => dom.appendChild(render(child)));
}
return dom;
}
实现事件总线结合Vue应用
/**
* 事件总线 - 结合 Vue 应用
* Vue 2 中常用 EventBus 进行组件间通信
*/
// 事件总线类(发布订阅模式)
class EventBus {
constructor() {
this.events = {};
this._eventId = 0;
}
/**
* 监听事件
* @param {string} event - 事件名
* @param {Function} callback - 回调
* @returns {number} 事件 ID,用于取消
*/
on(event, callback) {
if (!this.events[event]) {
this.events[event] = new Map();
}
const id = ++this._eventId;
this.events[event].set(id, callback);
return id;
}
/**
* 触发事件
* @param {string} event - 事件名
* @param {...any} args - 参数
*/
emit(event, ...args) {
if (this.events[event]) {
this.events[event].forEach(callback => {
callback(...args);
});
}
}
/**
* 取消监听
* @param {string} event - 事件名
* @param {number|Function} target - 事件 ID 或回调函数
*/
off(event, target) {
if (!this.events[event]) return;
if (typeof target === 'number') {
this.events[event].delete(target);
} else if (typeof target === 'function') {
for (const [id, cb] of this.events[event]) {
if (cb === target) {
this.events[event].delete(id);
}
}
} else {
delete this.events[event];
}
}
/**
* 监听一次
* @param {string} event - 事件名
* @param {Function} callback - 回调
*/
once(event, callback) {
const wrapper = (...args) => {
callback(...args);
this.off(event, wrapper);
};
this.on(event, wrapper);
}
}
// 创建全局事件总线
const eventBus = new EventBus();
// --- Vue 中使用示例 ---
// // 组件A:发送数据
// eventBus.emit('userLogin', { name: '张三', id: 123 });
//
// // 组件B:接收数据
// eventBus.on('userLogin', (user) => {
// console.log('用户登录:', user);
// });
实现一个双向绑定
/**
* 实现双向绑定 - 基于 Object.defineProperty
* 类似于 Vue 2 的响应式原理
*/
function twoWayBinding(data, key, selector) {
const element = document.querySelector(selector);
// 定义响应式属性
Object.defineProperty(data, key, {
get() {
return element ? element.value : null;
},
set(newValue) {
if (element) {
element.value = newValue;
}
},
configurable: true
});
// 监听输入事件,更新数据
if (element) {
element.addEventListener('input', function () {
data[key] = this.value; // 触发 setter
});
}
}
// --- 使用示例 ---
// const data = {};
// twoWayBinding(data, 'message', '#inputEl');
// // 页面输入变化 -> data.message 更新
// // data.message = '新值' -> 页面输入框更新
实现一个简易的MVVM
/**
* 简易 MVVM 实现
*
* MVVM = Model(数据模型)+ View(视图)+ ViewModel(视图模型)
* 核心是数据劫持 + 发布订阅模式,实现数据变化驱动视图更新
*/
// 1. 观察者(依赖收集器)
class Dep {
constructor() {
this.subscribers = new Set();
}
depend() {
if (Dep.target) {
this.subscribers.add(Dep.target);
}
}
notify() {
this.subscribers.forEach(sub => sub.update());
}
}
Dep.target = null;
// 2. 观察者(Watcher)
class Watcher {
constructor(vm, key, callback) {
this.vm = vm;
this.key = key;
this.callback = callback;
this.value = this.get();
}
get() {
Dep.target = this;
const value = this.vm.data[this.key];
Dep.target = null;
return value;
}
update() {
const newValue = this.vm.data[this.key];
if (newValue !== this.value) {
this.value = newValue;
this.callback(newValue);
}
}
}
// 3. 数据劫持
function observe(data) {
if (!data || typeof data !== 'object') return;
Object.keys(data).forEach(key => {
defineReactive(data, key, data[key]);
});
}
function defineReactive(obj, key, val) {
const dep = new Dep();
// 递归劫持嵌套对象
observe(val);
Object.defineProperty(obj, key, {
get() {
dep.depend();
return val;
},
set(newVal) {
if (newVal === val) return;
val = newVal;
observe(newVal); // 新值可能是对象
dep.notify();
}
});
}
// 4. 编译器(Compile)
class Compile {
constructor(el, vm) {
this.vm = vm;
this.el = document.querySelector(el);
this.compile(this.el);
}
compile(node) {
const childNodes = node.childNodes;
childNodes.forEach(child => {
if (child.nodeType === 1) { // 元素节点
this.compileElement(child);
this.compile(child); // 递归编译子节点
} else if (child.nodeType === 3) { // 文本节点
this.compileText(child);
}
});
}
compileElement(node) {
// 处理 v-model 指令
if (node.hasAttribute('v-model')) {
const key = node.getAttribute('v-model');
node.addEventListener('input', () => {
this.vm.data[key] = node.value;
});
new Watcher(this.vm, key, value => {
node.value = value;
});
}
}
compileText(node) {
const reg = /\{\{(.+?)\}\}/g;
let match;
while (match = reg.exec(node.textContent)) {
const key = match[1].trim();
new Watcher(this.vm, key, value => {
node.textContent = node.textContent.replace(match[0], value);
});
// 初始渲染
node.textContent = node.textContent.replace(match[0], this.vm.data[key]);
}
}
}
// 5. MVVM 主类
class MVVM {
constructor(options) {
this.el = options.el;
this.data = options.data;
// 数据劫持
observe(this.data);
// 编译模板
if (this.el) {
new Compile(this.el, this);
}
}
}
// --- 使用示例 ---
// <div id="app">
// <input v-model="message" />
// <p>{{ message }}</p>
// </div>
//
// const vm = new MVVM({
// el: '#app',
// data: {
// message: 'Hello MVVM!'
// }
// });
实现Vue reactive响应式
/**
* 实现 Vue 3 的 reactive 响应式 - 基于 Proxy
*
* Proxy 相比 Object.defineProperty 的优势:
* 1. 可以监听数组变化
* 2. 可以监听新增/删除属性
* 3. 不需要递归遍历,访问到才进行代理
*/
// 响应式系统
const reactiveMap = new WeakMap();
const effectStack = [];
let activeEffect = null;
// 依赖收集
class Dependency {
constructor() {
this.subscribers = new Set();
}
depend() {
if (activeEffect) {
this.subscribers.add(activeEffect);
}
}
notify() {
this.subscribers.forEach(effect => effect());
}
}
// 创建依赖映射
const targetMap = new WeakMap();
function getDep(target, key) {
let depsMap = targetMap.get(target);
if (!depsMap) {
depsMap = new Map();
targetMap.set(target, depsMap);
}
let dep = depsMap.get(key);
if (!dep) {
dep = new Dependency();
depsMap.set(key, dep);
}
return dep;
}
// reactive 函数 - 使用 Proxy
function reactive(target) {
if (target === null || typeof target !== 'object') {
return target;
}
// 如果已经 reactive,返回缓存版本
if (reactiveMap.has(target)) {
return reactiveMap.get(target);
}
const proxy = new Proxy(target, {
get(target, key, receiver) {
const dep = getDep(target, key);
dep.depend(); // 依赖收集
const result = Reflect.get(target, key, receiver);
// 深层响应式:如果值是对象,递归代理
if (typeof result === 'object' && result !== null) {
return reactive(result);
}
return result;
},
set(target, key, value, receiver) {
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
if (oldValue !== value) {
const dep = getDep(target, key);
dep.notify(); // 触发更新
}
return result;
},
deleteProperty(target, key) {
const hadKey = key in target;
const result = Reflect.deleteProperty(target, key);
if (hadKey) {
const dep = getDep(target, key);
dep.notify(); // 删除属性时触发更新
}
return result;
}
});
reactiveMap.set(target, proxy);
return proxy;
}
// effect 函数 - 注册副作用
function effect(fn) {
const wrappedEffect = () => {
try {
activeEffect = wrappedEffect;
effectStack.push(wrappedEffect);
return fn();
} finally {
effectStack.pop();
activeEffect = effectStack[effectStack.length - 1];
}
};
wrappedEffect(); // 立即执行,完成依赖收集
return wrappedEffect;
}
// ref 函数
function ref(value) {
const refObj = {
get value() {
const dep = getDep(refObj, 'value');
dep.depend();
return value;
},
set value(newValue) {
if (newValue !== value) {
value = newValue;
const dep = getDep(refObj, 'value');
dep.notify();
}
}
};
return refObj;
}
// computed 函数
function computed(getter) {
let cachedValue;
let dirty = true;
const computedRef = {
get value() {
if (dirty) {
effect(() => {
cachedValue = getter();
dirty = false;
});
}
const dep = getDep(computedRef, 'value');
dep.depend();
return cachedValue;
}
};
return computedRef;
}
// --- 使用示例 ---
// const state = reactive({ count: 0, name: 'Vue' });
//
// effect(() => {
// console.log('count 变化:', state.count);
// });
//
// state.count++; // 触发 effect 重新执行
实现模板字符串解析功能
/**
* 实现模板字符串解析
* 将 "{name} 今年 {age} 岁" 中的占位符替换为实际值
*/
/**
* 模板字符串解析函数
* @param {string} template - 模板字符串,如 "你好,{name}"
* @param {object} data - 数据对象
* @returns {string} 解析后的字符串
*/
function renderTemplate(template, data) {
// 匹配 {{xxx}} 或 {xxx} 格式
return template.replace(/\{\{?(.+?)\}?\}/g, (match, key) => {
const trimmedKey = key.trim();
// 支持嵌套路径,如 {user.name}
const value = trimmedKey.split('.').reduce((obj, k) => {
return obj ? obj[k] : undefined;
}, data);
return value !== undefined ? value : match;
});
}
// --- 使用示例 ---
// const tpl = '你好,{name},你今年{age}岁了';
// const data = { name: '张三', age: 18 };
// console.log(renderTemplate(tpl, data)); // '你好,张三,你今年18岁了'
实现一下hash路由
/**
* 实现 hash 路由
* hash 路由通过 window.location.hash 和 hashchange 事件实现
*/
class HashRouter {
constructor() {
this.routes = {}; // 路由表
this.currentHash = ''; // 当前 hash
// 监听 hash 变化
window.addEventListener('hashchange', () => {
this.handleRouteChange();
});
// 页面加载时处理 hash
window.addEventListener('load', () => {
this.handleRouteChange();
});
}
/**
* 注册路由
* @param {string} path - 路径
* @param {Function} callback - 路由处理回调
*/
route(path, callback) {
this.routes[path] = callback || function () {};
}
/**
* 处理路由变化
*/
handleRouteChange() {
this.currentHash = window.location.hash.slice(1) || '/';
const callback = this.routes[this.currentHash];
if (callback) {
callback(this.currentHash);
} else if (this.routes['*']) {
// 404 处理
this.routes['*'](this.currentHash);
}
}
/**
* 导航到指定路径
* @param {string} path - 路径
*/
push(path) {
window.location.hash = path;
}
/**
* 获取当前 hash
* @returns {string}
*/
getCurrentHash() {
return this.currentHash;
}
}
// --- 使用示例 ---
// const router = new HashRouter();
// router.route('/', () => console.log('首页'));
// router.route('/about', () => console.log('关于页'));
// router.route('*', () => console.log('404 页面'));
// router.push('/about');
实现redux中间件
/**
* 实现 redux 中间件机制
*
* Redux 中间件的本质:在 dispatch 前后添加自定义逻辑
* 中间件签名:store => next => action => {}
*/
// 简易 redux 实现
function createStore(reducer, preloadedState, enhancer) {
// 如果有 enhancer,使用 enhancer 创建 store
if (typeof enhancer === 'function') {
return enhancer(createStore)(reducer, preloadedState);
}
let state = preloadedState;
let listeners = [];
const store = {
getState() {
return state;
},
dispatch(action) {
state = reducer(state, action);
listeners.forEach(listener => listener());
return action;
},
subscribe(listener) {
listeners.push(listener);
return () => {
listeners = listeners.filter(l => l !== listener);
};
}
};
// 初始化状态
store.dispatch({ type: '@@INIT' });
return store;
}
/**
* applyMiddleware - 应用中间件
* @param {...Function} middlewares - 中间件函数列表
* @returns {Function} enhancer 函数
*/
function applyMiddleware(...middlewares) {
return createStore => (reducer, preloadedState) => {
const store = createStore(reducer, preloadedState);
// 简化的 dispatch,用于在中间件构造中避免调用完整 dispatch
let dispatch = () => {
throw new Error('中间件构造中不允许 dispatch');
};
// 传递给中间件的 API
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
};
// 执行所有中间件,获得 dispatch 函数链
const chain = middlewares.map(middleware => middleware(middlewareAPI));
// 组合中间件链,返回增强后的 dispatch
dispatch = compose(...chain)(store.dispatch);
return {
...store,
dispatch
};
};
}
// compose 函数
function compose(...fns) {
if (fns.length === 0) return arg => arg;
if (fns.length === 1) return fns[0];
return fns.reduce((a, b) => (...args) => a(b(...args)));
}
实现redux-thunk
/**
* 实现 redux-thunk 中间件
*
* 作用:允许 action 是一个函数(thunk),而不仅仅是普通对象
* 常用于处理异步操作
*/
/**
* redux-thunk 中间件
* 如果 action 是函数,则执行函数并传入 dispatch 和 getState
* 如果 action 是普通对象,则直接 dispatch
*/
function thunkMiddleware(store) {
return next => action => {
// 如果 action 是函数(thunk),执行它
if (typeof action === 'function') {
return action(store.dispatch, store.getState);
}
// 普通 action,传递给下一个中间件
return next(action);
};
}
// 也可以简写为:
const thunk = store => next => action => {
return typeof action === 'function'
? action(store.dispatch, store.getState)
: next(action);
};
// --- 使用示例 ---
// // 异步 action creator
// function fetchUser(id) {
// return async (dispatch, getState) => {
// dispatch({ type: 'FETCH_USER_START' });
// try {
// const user = await api.getUser(id);
// dispatch({ type: 'FETCH_USER_SUCCESS', payload: user });
// } catch (err) {
// dispatch({ type: 'FETCH_USER_ERROR', error: err });
// }
// };
// }
//
// // 创建 store 时应用中间件
// const store = createStore(
// reducer,
// applyMiddleware(thunk)
// );
// store.dispatch(fetchUser(1));
实现一个迷你版的vue
/**
* 实现一个迷你版的 Vue
* 结合了响应式系统、编译器和双向绑定
*/
class MiniVue {
constructor(options) {
this.$el = document.querySelector(options.el);
this.$data = options.data;
this.$methods = options.methods;
this._binding = {}; // 存储绑定关系
// 1. 数据劫持
this._observe(this.$data);
// 2. 编译模板
this._compile(this.$el);
}
/**
* 数据劫持 - 使用 Object.defineProperty
*/
_observe(data) {
Object.keys(data).forEach(key => {
this._defineReactive(data, key, data[key]);
});
}
_defineReactive(obj, key, val) {
const self = this;
// 存储所有依赖该属性的 watcher
this._binding[key] = {
_directives: []
};
// 递归劫持
if (typeof val === 'object' && val !== null) {
this._observe(val);
}
Object.defineProperty(obj, key, {
get() {
return val;
},
set(newVal) {
if (newVal === val) return;
val = newVal;
// 通知所有绑定了该属性的指令更新
self._binding[key]._directives.forEach(item => {
item.update();
});
}
});
}
/**
* 编译模板
*/
_compile(root) {
const nodes = root.children;
for (const node of nodes) {
// 处理 v-model
if (node.hasAttribute('v-model')) {
this._compileModel(node);
}
// 处理 v-click / @click
if (node.hasAttribute('v-click') || node.hasAttribute('@click')) {
this._compileClick(node);
}
// 处理 {{ }} 插值表达式
if (node.textContent.match(/\{\{(.+?)\}\}/)) {
this._compileText(node);
}
// 递归编译子节点
if (node.children.length > 0) {
this._compile(node);
}
}
}
_compileModel(node) {
const key = node.getAttribute('v-model');
node.removeAttribute('v-model');
// 创建 watcher
const watcher = {
update: () => {
node.value = this._getValue(this.$data, key);
}
};
this._binding[key]._directives.push(watcher);
// 初始赋值
node.value = this._getValue(this.$data, key);
// 监听输入事件
node.addEventListener('input', () => {
this._setValue(this.$data, key, node.value);
});
}
_compileClick(node) {
const methodName = node.getAttribute('v-click') || node.getAttribute('@click');
node.removeAttribute('v-click');
node.removeAttribute('@click');
node.addEventListener('click', () => {
this.$methods && this.$methods[methodName] && this.$methods[methodName].call(this.$data);
});
}
_compileText(node) {
const originalText = node.textContent;
const reg = /\{\{(.+?)\}\}/g;
let match;
while (match = reg.exec(originalText)) {
const key = match[1].trim();
const watcher = {
update: () => {
node.textContent = originalText.replace(reg, (_, k) => {
return this._getValue(this.$data, k.trim());
});
}
};
this._binding[key]._directives.push(watcher);
// 初始渲染
node.textContent = originalText.replace(reg, (_, k) => {
return this._getValue(this.$data, k.trim());
});
}
}
_getValue(obj, path) {
return path.split('.').reduce((current, key) => current ? current[key] : undefined, obj);
}
_setValue(obj, path, value) {
const keys = path.split('.');
let current = obj;
for (let i = 0; i < keys.length - 1; i++) {
current = current[keys[i]];
}
current[keys[keys.length - 1]] = value;
}
}
// --- 使用示例 ---
// <div id="app">
// <input v-model="message" />
// <p>{{ message }}</p>
// <button @click="sayHello">按钮</button>
// </div>
//
// const vm = new MiniVue({
// el: '#app',
// data: {
// message: 'Hello MiniVue!'
// },
// methods: {
// sayHello() {
// alert(this.message);
// }
// }
// });
数组相关
实现forEach方法
/**
* 实现 Array.prototype.forEach
* @param {Function} callback - (currentValue, index, array) => void
* @param {object} [thisArg] - 执行 callback 时的 this 值
*/
Array.prototype.myForEach = function (callback, thisArg) {
if (this === null || this === undefined) {
throw new TypeError('Cannot read property of null/undefined');
}
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
const arr = Object(this);
const len = arr.length >>> 0; // 确保为正整数
for (let i = 0; i < len; i++) {
if (i in arr) {
callback.call(thisArg, arr[i], i, arr);
}
}
};
实现filter方法
/**
* 实现 Array.prototype.filter
* @param {Function} callback - (currentValue, index, array) => boolean
* @param {object} [thisArg] - 执行 callback 时的 this 值
* @returns {Array} 新数组
*/
Array.prototype.myFilter = function (callback, thisArg) {
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
const arr = Object(this);
const len = arr.length >>> 0;
const result = [];
for (let i = 0; i < len; i++) {
if (i in arr) {
if (callback.call(thisArg, arr[i], i, arr)) {
result.push(arr[i]);
}
}
}
return result;
};
实现find方法
/**
* 实现 Array.prototype.find
* @param {Function} callback - (currentValue, index, array) => boolean
* @param {object} [thisArg] - 执行 callback 时的 this 值
* @returns {any} 第一个满足条件的元素,否则 undefined
*/
Array.prototype.myFind = function (callback, thisArg) {
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
const arr = Object(this);
const len = arr.length >>> 0;
for (let i = 0; i < len; i++) {
if (i in arr) {
if (callback.call(thisArg, arr[i], i, arr)) {
return arr[i];
}
}
}
return undefined;
};
实现findIndex方法
/**
* 实现 Array.prototype.findIndex
* @param {Function} callback - (currentValue, index, array) => boolean
* @param {object} [thisArg] - 执行 callback 时的 this 值
* @returns {number} 第一个满足条件的元素的索引,否则 -1
*/
Array.prototype.myFindIndex = function (callback, thisArg) {
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
const arr = Object(this);
const len = arr.length >>> 0;
for (let i = 0; i < len; i++) {
if (i in arr) {
if (callback.call(thisArg, arr[i], i, arr)) {
return i;
}
}
}
return -1;
};
实现map方法
/**
* 实现 Array.prototype.map
* @param {Function} callback - (currentValue, index, array) => newValue
* @param {object} [thisArg] - 执行 callback 时的 this 值
* @returns {Array} 新数组
*/
Array.prototype.myMap = function (callback, thisArg) {
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
const arr = Object(this);
const len = arr.length >>> 0;
const result = new Array(len);
for (let i = 0; i < len; i++) {
if (i in arr) {
result[i] = callback.call(thisArg, arr[i], i, arr);
}
}
return result;
};
实现reduce方法
/**
* 实现 Array.prototype.reduce
* @param {Function} callback - (accumulator, currentValue, index, array) => newAccumulator
* @param {any} [initialValue] - 初始值
* @returns {any} 累积结果
*/
Array.prototype.myReduce = function (callback, initialValue) {
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
const arr = Object(this);
const len = arr.length >>> 0;
// 没有初始值且数组为空,抛出错误
if (len === 0 && arguments.length < 2) {
throw new TypeError('Reduce of empty array with no initial value');
}
let accumulator = arguments.length >= 2 ? initialValue : arr[0];
let startIndex = arguments.length >= 2 ? 0 : 1;
for (let i = startIndex; i < len; i++) {
if (i in arr) {
accumulator = callback(accumulator, arr[i], i, arr);
}
}
return accumulator;
};
实现every方法
/**
* 实现 Array.prototype.every
* @param {Function} callback - (currentValue, index, array) => boolean
* @param {object} [thisArg] - 执行 callback 时的 this 值
* @returns {boolean} 所有元素都满足条件返回 true,否则 false
*/
Array.prototype.myEvery = function (callback, thisArg) {
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
const arr = Object(this);
const len = arr.length >>> 0;
for (let i = 0; i < len; i++) {
if (i in arr) {
if (!callback.call(thisArg, arr[i], i, arr)) {
return false;
}
}
}
return true;
};
实现some方法
/**
* 实现 Array.prototype.some
* @param {Function} callback - (currentValue, index, array) => boolean
* @param {object} [thisArg] - 执行 callback 时的 this 值
* @returns {boolean} 有一个元素满足条件返回 true,否则 false
*/
Array.prototype.mySome = function (callback, thisArg) {
if (typeof callback !== 'function') {
throw new TypeError(callback + ' is not a function');
}
const arr = Object(this);
const len = arr.length >>> 0;
for (let i = 0; i < len; i++) {
if (i in arr) {
if (callback.call(thisArg, arr[i], i, arr)) {
return true;
}
}
}
return false;
};
实现数组扁平化flat方法
/**
* 实现 Array.prototype.flat
* @param {number} [depth=1] - 扁平化深度
* @returns {Array} 扁平化后的新数组
*/
// 方法1:递归实现
Array.prototype.myFlat = function (depth = 1) {
const arr = this;
const result = [];
function flatten(array, currentDepth) {
for (const item of array) {
if (Array.isArray(item) && currentDepth > 0) {
flatten(item, currentDepth - 1);
} else {
result.push(item);
}
}
}
flatten(arr, depth);
return result;
};
// 方法2:使用 reduce + 递归
Array.prototype.myFlatReduce = function (depth = 1) {
if (depth === 0) return this.slice();
return this.reduce((acc, val) => {
return acc.concat(Array.isArray(val) ? val.myFlatReduce(depth - 1) : val);
}, []);
};
// 方法3:完全扁平化(深度无穷大)
function flattenDeep(arr) {
return arr.reduce((acc, val) => {
return acc.concat(Array.isArray(val) ? flattenDeep(val) : val);
}, []);
}
实现Array.isArray方法
/**
* 实现 Array.isArray
* @param {any} value - 要检查的值
* @returns {boolean}
*/
function myIsArray(value) {
return Object.prototype.toString.call(value) === '[object Array]';
}
// 另一种实现:使用 instanceof
function myIsArray2(value) {
return value instanceof Array;
}
// 另一种实现:使用原型
function myIsArray3(value) {
return value !== null && typeof value === 'object' && value.constructor === Array;
}
实现Array.of方法
/**
* 实现 Array.of
* @param {...any} args - 参数
* @returns {Array} 新数组
*/
function myArrayOf(...args) {
return args;
}
// 或手动实现
Array.myOf = function (...args) {
const result = [];
for (let i = 0; i < args.length; i++) {
result[i] = args[i];
}
return result;
};
数组去重方法汇总
/**
* 数组去重 - 多种方法汇总
*/
const arr = [1, 2, 2, 3, 4, 4, 5, '2', undefined, undefined, null, null, NaN, NaN];
// 方法1:Set(最简洁,无法去重 {})
function uniqueSet(arr) {
return [...new Set(arr)];
}
// [1, 2, 3, 4, 5, '2', undefined, null, NaN]
// 方法2:filter + indexOf(可以区分 NaN)
function uniqueFilter(arr) {
return arr.filter((item, index) => arr.indexOf(item) === index);
}
// 方法3:reduce
function uniqueReduce(arr) {
return arr.reduce((acc, cur) => {
if (!acc.includes(cur)) {
acc.push(cur);
}
return acc;
}, []);
}
// 方法4:Map(适合对象数组去重)
function uniqueMap(arr) {
const map = new Map();
arr.forEach(item => {
if (!map.has(item)) {
map.set(item, true);
}
});
return [...map.keys()];
}
// 方法5:双重循环 + splice(会修改原数组)
function uniqueSplice(arr) {
const result = [...arr];
for (let i = 0; i < result.length; i++) {
for (let j = i + 1; j < result.length; j++) {
if (result[i] === result[j]) {
result.splice(j, 1);
j--;
}
}
}
return result;
}
对象数组如何去重
/**
* 对象数组去重
* 根据对象的所有属性值是否相等来判断
*/
// 方法1:利用 JSON.stringify 比较
function uniqueObjectArray(arr) {
const map = new Map();
arr.forEach(item => {
const key = JSON.stringify(item);
if (!map.has(key)) {
map.set(key, item);
}
});
return [...map.values()];
}
// 方法2:利用 findIndex + some
function uniqueObjectArray2(arr) {
const result = [];
arr.forEach(item => {
const isDuplicate = result.some(existing => {
return JSON.stringify(existing) === JSON.stringify(item);
});
if (!isDuplicate) {
result.push(item);
}
});
return result;
}
数组中的数据根据key去重
/**
* 数组中根据指定 key 去重(常用于接口数据去重)
* @param {Array} arr - 对象数组
* @param {string} key - 作为去重依据的键名
* @returns {Array} 去重后的数组
*/
function uniqueByKey(arr, key) {
const map = new Map();
arr.forEach(item => {
const value = item[key];
if (!map.has(value)) {
map.set(value, item);
}
});
return [...map.values()];
}
/**
* 根据多个 key 去重
*/
function uniqueByKeys(arr, keys) {
const map = new Map();
arr.forEach(item => {
const key = keys.map(k => item[k]).join('_');
if (!map.has(key)) {
map.set(key, item);
}
});
return [...map.values()];
}
类数组转化为数组的方法
/**
* 类数组转化为数组的多种方法
* 类数组:有 length 属性且可以通过索引访问元素的对象
*/
// 方法1:Array.from
function toArray1(arrayLike) {
return Array.from(arrayLike);
}
// 方法2:展开运算符(需要可迭代)
function toArray2(arrayLike) {
return [...arrayLike];
}
// 方法3:Array.prototype.slice
function toArray3(arrayLike) {
return Array.prototype.slice.call(arrayLike);
}
// 方法4:Array.prototype.concat
function toArray4(arrayLike) {
return Array.prototype.concat.apply([], arrayLike);
}
// 方法5:Array.prototype.splice
function toArray5(arrayLike) {
return Array.prototype.splice.call(arrayLike, 0);
}
// 方法6:手写循环
function toArray6(arrayLike) {
const result = [];
for (let i = 0; i < arrayLike.length; i++) {
result.push(arrayLike[i]);
}
return result;
}
reduce用法汇总
/**
* reduce 用法汇总
*/
// 1. 求和
const sum = arr => arr.reduce((acc, cur) => acc + cur, 0);
// 2. 求最大值
const max = arr => arr.reduce((acc, cur) => Math.max(acc, cur));
// 3. 数组去重
const unique = arr => arr.reduce((acc, cur) => {
return acc.includes(cur) ? acc : [...acc, cur];
}, []);
// 4. 数组扁平化
const flat = arr => arr.reduce((acc, cur) => {
return acc.concat(Array.isArray(cur) ? flat(cur) : cur);
}, []);
// 5. 统计出现次数
const count = arr => arr.reduce((acc, cur) => {
acc[cur] = (acc[cur] || 0) + 1;
return acc;
}, {});
// 6. 对象数组分组
const groupBy = (arr, key) => arr.reduce((acc, cur) => {
const group = cur[key];
(acc[group] = acc[group] || []).push(cur);
return acc;
}, {});
// 7. 两个数组合并为对象
const zip = (keys, values) => keys.reduce((acc, key, i) => {
acc[key] = values[i];
return acc;
}, {});
// 8. 管道函数组合
const pipe = (...fns) => x => fns.reduce((v, f) => f(v), x);
正则相关
实现千位分隔符
/**
* 实现千位分隔符
* 将数字转为千位分隔格式,如 1234567 -> 1,234,567
*/
// 方法1:正则表达式(使用先行断言)
function formatWithCommas(num) {
// 将数字转为字符串,然后从右向左每三位加一个逗号
// (?=(\d{3})+(?!\d)) 表示匹配后面有3位数字的位置,且这3位数字后面不能再有数字
const parts = num.toString().split('.');
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
return parts.join('.');
}
// 方法2:使用 toLocaleString
function formatWithCommas2(num) {
return num.toLocaleString('en-US');
}
// 方法3:使用 Intl.NumberFormat
function formatWithCommas3(num) {
return new Intl.NumberFormat('en-US').format(num);
}
// --- 测试 ---
// console.log(formatWithCommas(1234567.89)); // "1,234,567.89"
判断是否是电话号码
/**
* 验证是否是电话号码
* 支持:手机号(11位)、座机号(区号+号码)
*/
/**
* 验证手机号(中国大陆)
* 规则:1开头的11位数字
*/
function isPhoneNumber(phone) {
return /^1[3-9]\d{9}$/.test(phone);
}
/**
* 验证座机号(含区号)
* 规则:区号(3-4位)- 号码(7-8位)
*/
function isLandline(phone) {
return /^0\d{2,3}-?\d{7,8}$/.test(phone);
}
/**
* 验证手机号或座机号
*/
function isValidPhone(phone) {
return isPhoneNumber(phone) || isLandline(phone);
}
验证是否是邮箱
/**
* 验证是否是邮箱地址
* 规则:用户名@域名
* 用户名:字母、数字、下划线、点、减号
* 域名:至少包含一个点
*/
function isEmail(email) {
return /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(email);
}
// 更严谨的邮箱验证
function isEmailStrict(email) {
return /^[a-zA-Z0-9](?:[a-zA-Z0-9._%+-]*[a-zA-Z0-9])?@[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?)*\.[a-zA-Z]{2,}$/.test(email);
}
验证是否是身份证
/**
* 验证身份证号码(中国大陆)
* 18位:前17位数字 + 最后1位数字或X
* 15位:全部为数字
*
* 注意:正则只能验证格式,不能验证真实性
*/
// 格式验证
function isValidIdCard(id) {
// 18位
if (/^\d{17}[\dXx]$/.test(id)) {
return validateCheckDigit(id);
}
// 15位(升级到18位后验证)
if (/^\d{15}$/.test(id)) {
return true; // 15位旧版身份证,不做校验码验证
}
return false;
}
/**
* 验证18位身份证的校验码
* 算法:加权求和 -> 取模 -> 查表得校验码
*/
function validateCheckDigit(id) {
const weights = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
const checkCodes = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
let sum = 0;
for (let i = 0; i < 17; i++) {
sum += parseInt(id[i]) * weights[i];
}
const mod = sum % 11;
const expectedCheckCode = checkCodes[mod];
return expectedCheckCode === id[17].toUpperCase();
}
用正则写一个根据name获取cookie中的值的方法
/**
* 根据 name 获取 cookie 中的值
* @param {string} name - cookie 的键名
* @returns {string|null} cookie 值,不存在返回 null
*/
function getCookie(name) {
// 转义特殊字符
const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
// 匹配:name=value; 或 name=value(结尾)
const match = document.cookie.match(new RegExp(`(?:^|;\\s*)${escapedName}=([^;]*)`));
return match ? decodeURIComponent(match[1]) : null;
}
// --- 使用示例 ---
// document.cookie = 'username=zhangsan; path=/';
// console.log(getCookie('username')); // 'zhangsan'
函数柯里化相关
实现一个JS函数柯里化
/**
* 函数柯里化
* 将一个多参数函数转换为一系列单参数函数
*
* @param {Function} fn - 需要柯里化的函数
* @param {number} [arity] - 函数的参数数量
* @returns {Function} 柯里化后的函数
*/
function curry(fn, arity = fn.length) {
return function curried(...args) {
if (args.length >= arity) {
// 参数数量足够,直接执行
return fn.apply(this, args);
} else {
// 参数不足,返回新函数继续接收参数
return function (...nextArgs) {
return curried.apply(this, [...args, ...nextArgs]);
};
}
};
}
/**
* 柯里化 - 支持占位符版本
* 使用 _ 作为占位符
*/
function curryWithPlaceholder(fn, arity = fn.length) {
const placeholder = curryWithPlaceholder.placeholder || '_';
return function curried(...args) {
if (args.length >= arity && !args.slice(0, arity).includes(placeholder)) {
return fn.apply(this, args);
}
return function (...nextArgs) {
const mergedArgs = [];
let argIndex = 0;
let nextIndex = 0;
// 合并参数:用新参数替换占位符
while (argIndex < args.length && nextIndex < nextArgs.length) {
if (args[argIndex] === placeholder) {
mergedArgs.push(nextArgs[nextIndex++]);
} else {
mergedArgs.push(args[argIndex]);
}
argIndex++;
}
// 将剩余参数全部合并
while (argIndex < args.length) {
mergedArgs.push(args[argIndex++]);
}
while (nextIndex < nextArgs.length) {
mergedArgs.push(nextArgs[nextIndex++]);
}
return curried.apply(this, mergedArgs);
};
};
}
curryWithPlaceholder.placeholder = '_';
// --- 使用示例 ---
// const add = (a, b, c) => a + b + c;
// const curriedAdd = curry(add);
// console.log(curriedAdd(1)(2)(3)); // 6
// console.log(curriedAdd(1, 2)(3)); // 6
请实现一个 add 函数,满足以下功能
/**
* 实现 add 函数,支持无限柯里化调用
* add(1)(2)(3) => 6
* add(1, 2, 3) => 6
* add(1)(2)(3)(4) => 10
*/
// 方法1:重写 valueOf / toString
function add(...args) {
const sum = args.reduce((a, b) => a + b, 0);
function inner(...moreArgs) {
if (moreArgs.length === 0) {
return sum;
}
return add(sum, ...moreArgs);
}
// 当进行隐式类型转换时返回当前累计值
inner.valueOf = () => sum;
inner.toString = () => String(sum);
return inner;
}
// 方法2:使用 Proxy
function addWithProxy(...args) {
let sum = args.reduce((a, b) => a + b, 0);
const proxy = new Proxy(() => {}, {
apply(target, thisArg, argumentsList) {
if (argumentsList.length === 0) {
return sum;
}
sum += argumentsList.reduce((a, b) => a + b, 0);
return proxy;
},
get(target, prop) {
if (prop === Symbol.toPrimitive || prop === 'valueOf' || prop === 'toString') {
return () => sum;
}
return undefined;
}
});
return proxy;
}
// --- 使用示例 ---
// console.log(add(1)(2)(3) + 0); // 6
// console.log(add(1, 2, 3) == 6); // true
// console.log(add(1)(2)(3)(4) == 10); // true
实现 (5).add(3).minus(2) 功能
/**
* 实现链式调用: (5).add(3).minus(2) => 6
* 通过扩展 Number 原型实现
*/
// 方法1:扩展 Number.prototype
Number.prototype.add = function (value) {
return this + value;
};
Number.prototype.minus = function (value) {
return this - value;
};
// --- 使用示例 ---
// console.log((5).add(3).minus(2)); // 6
/**
* 如果要求始终返回 Number 类型以便继续链式调用:
* 注意:上面这种写法已经满足需求,因为返回的数值可以继续调用原型方法
* 但如果想要更安全(避免浮点数问题),可以使用如下方法:
*/
Number.prototype.add = function (value) {
return +(this + value).toFixed(10);
};
Number.prototype.minus = function (value) {
return +(this - value).toFixed(10);
};
字符串相关
查找字符串中出现最多的字符和个数
/**
* 查找字符串中出现最多的字符和个数
* @param {string} str - 输入字符串
* @returns {object} { char: 出现最多的字符, count: 出现次数 }
*/
function findMostFrequentChar(str) {
if (!str) return { char: '', count: 0 };
const countMap = {};
let maxChar = '';
let maxCount = 0;
for (const char of str) {
countMap[char] = (countMap[char] || 0) + 1;
if (countMap[char] > maxCount) {
maxCount = countMap[char];
maxChar = char;
}
}
return { char: maxChar, count: maxCount };
}
/**
* 如果有多个字符出现次数相同,返回所有
*/
function findMostFrequentChars(str) {
if (!str) return { chars: [], count: 0 };
const countMap = {};
let maxCount = 0;
for (const char of str) {
countMap[char] = (countMap[char] || 0) + 1;
maxCount = Math.max(maxCount, countMap[char]);
}
const chars = Object.keys(countMap).filter(char => countMap[char] === maxCount);
return { chars, count: maxCount };
}
字符串查找
/**
* 字符串查找 - 实现 String.prototype.indexOf
* 在字符串中查找子串,返回第一次出现的索引
* @param {string} text - 原字符串
* @param {string} pattern - 要查找的子串
* @param {number} [start=0] - 开始查找的位置
* @returns {number} 索引,未找到返回 -1
*/
// 方法1:朴素字符串匹配(BF算法)
function indexOf(text, pattern, start = 0) {
const n = text.length;
const m = pattern.length;
if (m === 0) return start;
if (start < 0) start = 0;
if (start + m > n) return -1;
for (let i = start; i <= n - m; i++) {
let match = true;
for (let j = 0; j < m; j++) {
if (text[i + j] !== pattern[j]) {
match = false;
break;
}
}
if (match) return i;
}
return -1;
}
// 方法2:使用内置方法(模拟)
function indexOfSimple(text, pattern, start = 0) {
if (pattern === '') return start;
return text.slice(start).search(pattern) + (start > 0 && text.slice(start).search(pattern) >= 0 ? start : text.slice(start).search(pattern));
}
// 方法3:KMP 算法(高效)
// 时间复杂度:O(n+m),空间复杂度:O(m)
function kmpSearch(text, pattern) {
const n = text.length;
const m = pattern.length;
if (m === 0) return 0;
// 构建部分匹配表(next数组)
const next = buildKMPNext(pattern);
let i = 0; // text 的指针
let j = 0; // pattern 的指针
while (i < n) {
if (text[i] === pattern[j]) {
i++;
j++;
if (j === m) {
return i - j; // 找到匹配
}
} else if (j > 0) {
j = next[j - 1]; // 利用 next 数组回溯
} else {
i++;
}
}
return -1;
}
function buildKMPNext(pattern) {
const next = [0];
let prefixLen = 0;
let i = 1;
while (i < pattern.length) {
if (pattern[i] === pattern[prefixLen]) {
prefixLen++;
next[i] = prefixLen;
i++;
} else if (prefixLen > 0) {
prefixLen = next[prefixLen - 1];
} else {
next[i] = 0;
i++;
}
}
return next;
}
字符串最长的不重复子串
/**
* 查找字符串中最长的不重复子串(无重复字符的最长子串)
* 滑动窗口算法
*
* @param {string} s - 输入字符串
* @returns {{ length: number, substring: string }} 最长不重复子串的长度和内容
*
* 时间复杂度:O(n)
* 空间复杂度:O(min(m, n)),m 是字符集大小
*/
function longestUniqueSubstring(s) {
const charMap = new Map(); // 存储字符及其最近出现的位置
let maxLength = 0;
let start = 0; // 窗口左边界
let resultStart = 0; // 最长子串的起始位置
for (let end = 0; end < s.length; end++) {
const char = s[end];
// 如果字符已存在且在当前窗口内,移动左边界
if (charMap.has(char) && charMap.get(char) >= start) {
start = charMap.get(char) + 1;
}
// 更新字符位置
charMap.set(char, end);
// 更新最大长度
const currentLength = end - start + 1;
if (currentLength > maxLength) {
maxLength = currentLength;
resultStart = start;
}
}
return {
length: maxLength,
substring: s.slice(resultStart, resultStart + maxLength)
};
}
// --- 使用示例 ---
// console.log(longestUniqueSubstring('abcabcbb')); // { length: 3, substring: 'abc' }
// console.log(longestUniqueSubstring('bbbbb')); // { length: 1, substring: 'b' }
// console.log(longestUniqueSubstring('pwwkew')); // { length: 3, substring: 'wke' }
工具函数
对象扁平化
/**
* 对象扁平化
* 将嵌套对象转换为单层对象,键名为路径拼接
*
* 输入:{ a: { b: { c: 1 }, d: 2 }, e: 3 }
* 输出:{ 'a.b.c': 1, 'a.d': 2, 'e': 3 }
*/
function flattenObject(obj, prefix = '', result = {}) {
if (obj === null || typeof obj !== 'object') {
result[prefix] = obj;
return result;
}
for (const key of Object.keys(obj)) {
const newKey = prefix ? `${prefix}.${key}` : key;
flattenObject(obj[key], newKey, result);
}
return result;
}
// 支持数组索引
function flattenObjectFull(obj, prefix = '', result = {}) {
if (obj === null || typeof obj !== 'object') {
result[prefix] = obj;
return result;
}
const isArray = Array.isArray(obj);
for (const key of Object.keys(obj)) {
const newKey = prefix
? `${prefix}${isArray ? `[${key}]` : `.${key}`}`
: key;
flattenObjectFull(obj[key], newKey, result);
}
return result;
}
// 对象反扁平化
function unflattenObject(flatObj) {
const result = {};
for (const [key, value] of Object.entries(flatObj)) {
const keys = key.split('.');
let current = result;
for (let i = 0; i < keys.length - 1; i++) {
if (!current[keys[i]]) {
current[keys[i]] = {};
}
current = current[keys[i]];
}
current[keys[keys.length - 1]] = value;
}
return result;
}
实现一个管理本地缓存过期的函数
/**
* 本地缓存过期管理
* 封装 localStorage,支持设置过期时间
*/
const cacheManager = {
/**
* 设置缓存
* @param {string} key - 键
* @param {any} value - 值
* @param {number} [ttl] - 过期时间(毫秒),不传则永不过期
*/
set(key, value, ttl) {
const data = {
value,
timestamp: Date.now(),
ttl: ttl || null
};
localStorage.setItem(key, JSON.stringify(data));
},
/**
* 获取缓存
* @param {string} key - 键
* @returns {any|null} 值,过期或不存在返回 null
*/
get(key) {
const item = localStorage.getItem(key);
if (!item) return null;
try {
const data = JSON.parse(item);
// 检查是否过期
if (data.ttl && Date.now() - data.timestamp > data.ttl) {
localStorage.removeItem(key);
return null;
}
return data.value;
} catch {
return null;
}
},
/**
* 删除缓存
* @param {string} key - 键
*/
remove(key) {
localStorage.removeItem(key);
},
/**
* 清空所有缓存
*/
clear() {
localStorage.clear();
},
/**
* 获取缓存剩余存活时间
* @param {string} key - 键
* @returns {number|null} 剩余毫秒数,永不过期返回 null,不存在返回 -1
*/
getTTL(key) {
const item = localStorage.getItem(key);
if (!item) return -1;
try {
const data = JSON.parse(item);
if (!data.ttl) return null;
const elapsed = Date.now() - data.timestamp;
return Math.max(0, data.ttl - elapsed);
} catch {
return -1;
}
}
};
实现lodash的chunk方法 -- 数组按指定长度拆分
/**
* 实现数组按指定长度拆分(chunk)
* @param {Array} arr - 原数组
* @param {number} size - 每个分组的长度
* @returns {Array} 拆分后的二维数组
*/
function chunk(arr, size = 1) {
if (!Array.isArray(arr) || size < 1) {
return [];
}
const result = [];
for (let i = 0; i < arr.length; i += size) {
result.push(arr.slice(i, i + size));
}
return result;
}
// --- 使用示例 ---
// console.log(chunk([1, 2, 3, 4, 5], 2)); // [[1, 2], [3, 4], [5]]
手写深度比较isEqual
/**
* 手写深度比较 isEqual
* 比较两个值是否深度相等
*
* @param {any} a - 值 a
* @param {any} b - 值 b
* @param {WeakMap} [cache] - 缓存,处理循环引用
* @returns {boolean}
*/
function isEqual(a, b, cache = new WeakMap()) {
// 严格相等(处理 NaN 等)
if (Object.is(a, b)) return true;
// 其中一个为 null/undefined 或非对象类型
if (a === null || b === null || typeof a !== 'object' || typeof b !== 'object') {
return false;
}
// 处理循环引用
if (cache.has(a) && cache.get(a) === b) return true;
cache.set(a, b);
// 处理 Date
if (a instanceof Date && b instanceof Date) {
return a.getTime() === b.getTime();
}
// 处理 RegExp
if (a instanceof RegExp && b instanceof RegExp) {
return a.source === b.source && a.flags === b.flags;
}
// 比较原型
const protoA = Object.getPrototypeOf(a);
const protoB = Object.getPrototypeOf(b);
if (protoA !== protoB) return false;
// 处理 Map
if (a instanceof Map && b instanceof Map) {
if (a.size !== b.size) return false;
for (const [key, value] of a) {
if (!b.has(key) || !isEqual(value, b.get(key), cache)) return false;
}
return true;
}
// 处理 Set
if (a instanceof Set && b instanceof Set) {
if (a.size !== b.size) return false;
return isEqual([...a].sort(), [...b].sort(), cache);
}
// 获取所有键(包括不可枚举的,但不包括 Symbol)
const keysA = Object.keys(a);
const keysB = Object.keys(b);
if (keysA.length !== keysB.length) return false;
// 比较每个键的值
for (const key of keysA) {
if (!keysB.includes(key) || !isEqual(a[key], b[key], cache)) return false;
}
return true;
}
// --- 使用示例 ---
// console.log(isEqual({ a: 1, b: { c: 2 } }, { a: 1, b: { c: 2 } })); // true
// console.log(isEqual([1, 2, 3], [1, 2, 3])); // true
// console.log(isEqual(NaN, NaN)); // true(Object.is)
实现一个JSON.stringify
/**
* 实现 JSON.stringify
* @param {any} value - 要序列化的值
* @param {Function|Array} [replacer] - 替换函数或属性白名单
* @param {number|string} [space] - 缩进格式
* @returns {string} JSON 字符串
*/
function myJSONStringify(value, replacer, space) {
const visited = new WeakSet(); // 处理循环引用
function stringify(val, indent = '') {
// 处理 replacer 为函数
if (typeof replacer === 'function') {
val = replacer('', val);
}
if (val === null) return 'null';
if (typeof val === 'boolean') return String(val);
if (typeof val === 'number') {
// NaN 和 Infinity 转为 null
return Number.isFinite(val) ? String(val) : 'null';
}
if (typeof val === 'string') return `"${val.replace(/["\\\n\r\t\f\b]/g, escapeChar)}"`;
if (typeof val === 'symbol' || typeof val === 'undefined') return undefined;
if (typeof val === 'function') return undefined;
// 处理循环引用
if (visited.has(val)) {
throw new TypeError('Converting circular structure to JSON');
}
visited.add(val);
const isArray = Array.isArray(val);
const spaceIndent = typeof space === 'number' ? ' '.repeat(space) : space || '';
const newIndent = indent + spaceIndent;
const separator = space ? ':' + (space ? ' ' : '') : ':';
if (isArray) {
const items = val.map((item, index) => {
if (typeof replacer === 'function') {
item = replacer(String(index), item);
}
if (item === undefined || typeof item === 'function' || typeof item === 'symbol') {
return 'null';
}
return newIndent + stringify(item, newIndent);
});
if (items.length === 0) return '[]';
if (space) {
return '[\n' + items.join(',\n') + '\n' + indent + ']';
}
return '[' + items.join(',') + ']';
}
// 处理对象
let keys = Object.keys(val);
// 如果 replacer 是数组,过滤键
if (Array.isArray(replacer)) {
keys = keys.filter(key => replacer.includes(key));
}
const items = keys.map(key => {
let value = val[key];
if (typeof replacer === 'function') {
value = replacer(key, value);
}
if (value === undefined || typeof value === 'function' || typeof value === 'symbol') {
return null;
}
const strValue = stringify(value, newIndent);
if (strValue === undefined) return null;
return newIndent + `"${key}"${separator}${strValue}`;
}).filter(item => item !== null);
if (items.length === 0) return '{}';
if (space) {
return '{\n' + items.join(',\n') + '\n' + indent + '}';
}
return '{' + items.join(',') + '}';
}
return stringify(value, '');
}
function escapeChar(c) {
const map = {
'"': '\\"',
'\\': '\\\\',
'\n': '\\n',
'\r': '\\r',
'\t': '\\t',
'\f': '\\f',
'\b': '\\b'
};
return map[c] || '\\u' + c.charCodeAt(0).toString(16).padStart(4, '0');
}
实现一个JSON.parse
/**
* 实现 JSON.parse
* @param {string} json - JSON 字符串
* @returns {any} 解析后的值
*/
// 方法1:使用 eval(简单但不安全)
function myJSONParse(str) {
return eval('(' + str + ')');
}
// 方法2:手动解析器(安全)
function myJSONParseSafe(str) {
let index = 0;
function parse() {
skipWhitespace();
if (index >= str.length) {
throw new SyntaxError('Unexpected end of JSON input');
}
const char = str[index];
if (char === '{') return parseObject();
if (char === '[') return parseArray();
if (char === '"') return parseString();
if (char === 't' || char === 'f') return parseBoolean();
if (char === 'n') return parseNull();
if (char === '-' || (char >= '0' && char <= '9')) return parseNumber();
throw new SyntaxError(`Unexpected token '${char}' at position ${index}`);
}
function skipWhitespace() {
while (index < str.length && /\s/.test(str[index])) {
index++;
}
}
function parseObject() {
index++; // 跳过 {
const obj = {};
skipWhitespace();
if (str[index] === '}') {
index++;
return obj;
}
while (index < str.length) {
skipWhitespace();
if (str[index] !== '"') {
throw new SyntaxError('Expected string key');
}
const key = parseString();
skipWhitespace();
if (str[index] !== ':') {
throw new SyntaxError('Expected :');
}
index++; // 跳过 :
skipWhitespace();
obj[key] = parse();
skipWhitespace();
if (str[index] === ',') {
index++;
skipWhitespace();
if (str[index] === '}') {
throw new SyntaxError('Trailing comma in object');
}
} else if (str[index] === '}') {
index++;
return obj;
} else {
throw new SyntaxError('Expected , or }');
}
}
throw new SyntaxError('Unterminated object');
}
function parseArray() {
index++; // 跳过 [
const arr = [];
skipWhitespace();
if (str[index] === ']') {
index++;
return arr;
}
while (index < str.length) {
arr.push(parse());
skipWhitespace();
if (str[index] === ',') {
index++;
skipWhitespace();
if (str[index] === ']') {
throw new SyntaxError('Trailing comma in array');
}
} else if (str[index] === ']') {
index++;
return arr;
} else {
throw new SyntaxError('Expected , or ]');
}
}
throw new SyntaxError('Unterminated array');
}
function parseString() {
index++; // 跳过 "
let result = '';
while (index < str.length) {
const char = str[index];
if (char === '"') {
index++;
return result;
}
if (char === '\\') {
index++;
const next = str[index];
switch (next) {
case '"': result += '"'; break;
case '\\': result += '\\'; break;
case '/': result += '/'; break;
case 'b': result += '\b'; break;
case 'f': result += '\f'; break;
case 'n': result += '\n'; break;
case 'r': result += '\r'; break;
case 't': result += '\t'; break;
case 'u':
const hex = str.slice(index + 1, index + 5);
result += String.fromCharCode(parseInt(hex, 16));
index += 4;
break;
default:
result += next;
}
index++;
} else {
result += char;
index++;
}
}
throw new SyntaxError('Unterminated string');
}
function parseNumber() {
const start = index;
if (str[index] === '-') index++;
if (str[index] === '0') {
index++;
} else if (str[index] >= '1' && str[index] <= '9') {
index++;
while (index < str.length && str[index] >= '0' && str[index] <= '9') index++;
}
if (str[index] === '.') {
index++;
if (index >= str.length || str[index] < '0' || str[index] > '9') {
throw new SyntaxError('Invalid number');
}
while (index < str.length && str[index] >= '0' && str[index] <= '9') index++;
}
if (str[index] === 'e' || str[index] === 'E') {
index++;
if (str[index] === '+' || str[index] === '-') index++;
while (index < str.length && str[index] >= '0' && str[index] <= '9') index++;
}
return Number(str.slice(start, index));
}
function parseBoolean() {
if (str.startsWith('true', index)) {
index += 4;
return true;
}
if (str.startsWith('false', index)) {
index += 5;
return false;
}
throw new SyntaxError('Unexpected token');
}
function parseNull() {
if (str.startsWith('null', index)) {
index += 4;
return null;
}
throw new SyntaxError('Unexpected token');
}
return parse();
}
解析 URL Params 为对象
/**
* 解析 URL Params 为对象
* 如:?name=张三&age=18&hobby=篮球&hobby=足球 => { name: '张三', age: '18', hobby: ['篮球', '足球'] }
*
* @param {string} [url] - URL,默认取 window.location.href
* @returns {object} 解析后的参数对象
*/
function parseURLParams(url) {
const queryString = url
? url.split('?')[1] || ''
: window.location.search.slice(1);
if (!queryString) return {};
const params = {};
queryString.split('&').forEach(param => {
const [key, value] = param.split('=').map(decodeURIComponent);
// 处理 key 没有值的情况(如 ?debug)
if (value === undefined) {
params[key] = true;
return;
}
// 处理数组(相同 key 出现多次)
if (params.hasOwnProperty(key)) {
params[key] = Array.isArray(params[key])
? [...params[key], value]
: [params[key], value];
} else {
params[key] = value;
}
});
return params;
}
// --- 使用示例 ---
// console.log(parseURLParams('?name=张三&age=18&hobby=篮球&hobby=足球'));
// { name: '张三', age: '18', hobby: ['篮球', '足球'] }
转化为驼峰命名
/**
* 字符串转化为驼峰命名
* 'hello-world' => 'helloWorld'
* '-webkit-border-radius' => 'webkitBorderRadius'
* 'foo_bar' => 'fooBar'
*/
function toCamelCase(str) {
return str.replace(/[-_]+(.)?/g, (match, char) => {
return char ? char.toUpperCase() : '';
});
}
// 转为大驼峰(PascalCase)
function toPascalCase(str) {
const camel = toCamelCase(str);
return camel.charAt(0).toUpperCase() + camel.slice(1);
}
// 转为中划线命名(kebab-case)
function toKebabCase(str) {
return str
.replace(/([A-Z])/g, '-$1')
.toLowerCase()
.replace(/^-/, '')
.replace(/[_\s]+/g, '-');
}
实现一个函数判断数据类型
/**
* 判断数据类型
* 返回更精确的类型字符串
*
* @param {any} value - 要判断的值
* @returns {string} 类型名称,如 'array', 'object', 'number', 'string', 'null', 'undefined', 'boolean', 'symbol', 'function', 'date', 'regexp', 'map', 'set', 'promise' 等
*/
function getType(value) {
if (value === null) return 'null';
if (value === undefined) return 'undefined';
// 使用 Object.prototype.toString 获取类型标签
const typeTag = Object.prototype.toString.call(value);
// 提取类型名称(如 '[object Array]' -> 'array')
const match = typeTag.match(/^\[object (\w+)\]$/);
if (match) {
return match[1].toLowerCase();
}
return typeof value;
}
/**
* 更精细的类型判断,区分原始类型和包装对象
*/
function getTypeDetailed(value) {
if (value === null) return 'null';
const type = typeof value;
// 原始类型
if (type !== 'object' && type !== 'function') {
return type; // 'string', 'number', 'boolean', 'symbol', 'undefined', 'bigint'
}
// 对象类型
if (type === 'function') return 'function';
const tag = Object.prototype.toString.call(value);
const match = tag.match(/^\[object (\w+)\]$/);
return match ? match[1].toLowerCase() : 'object';
}
对象数组列表转成树形结构(处理菜单)
/**
* 对象数组列表转成树形结构
* 常用于处理菜单、组织架构等层级数据
*
* @param {Array} list - 扁平列表,每个元素包含 id 和 parentId
* @param {string} [parentKey='parentId'] - 父级 id 的字段名
* @param {string} [idKey='id'] - 自身 id 的字段名
* @param {string} [childrenKey='children'] - 子节点数组的字段名
* @param {any} [rootValue=null] - 根节点的 parentId 值
* @returns {Array} 树形结构
*/
function listToTree(list, parentKey = 'parentId', idKey = 'id', childrenKey = 'children', rootValue = null) {
if (!Array.isArray(list) || list.length === 0) return [];
const map = new Map();
const tree = [];
// 将所有节点存入 Map
list.forEach(item => {
map.set(item[idKey], { ...item, [childrenKey]: [] });
});
// 建立父子关系
list.forEach(item => {
const node = map.get(item[idKey]);
if (item[parentKey] === rootValue) {
// 根节点
tree.push(node);
} else {
// 子节点,添加到父节点的 children 中
const parent = map.get(item[parentKey]);
if (parent) {
parent[childrenKey].push(node);
} else {
// 父节点不存在(数据异常),作为根节点处理
tree.push(node);
}
}
});
return tree;
}
// --- 使用示例 ---
// const list = [
// { id: 1, name: '菜单1', parentId: null },
// { id: 2, name: '菜单1-1', parentId: 1 },
// { id: 3, name: '菜单1-2', parentId: 1 },
// { id: 4, name: '菜单2', parentId: null },
// { id: 5, name: '菜单1-1-1', parentId: 2 },
// ];
// console.log(listToTree(list));
树形结构转成列表(处理菜单)
/**
* 树形结构转成扁平列表
* 将树结构展开为扁平数组
*
* @param {Array} tree - 树形结构
* @param {string} [childrenKey='children'] - 子节点数组的字段名
* @returns {Array} 扁平列表
*/
function treeToList(tree, childrenKey = 'children') {
if (!Array.isArray(tree) || tree.length === 0) return [];
const result = [];
function flatten(node) {
const { [childrenKey]: children, ...rest } = node;
result.push(rest);
if (children && children.length > 0) {
children.forEach(flatten);
}
}
tree.forEach(flatten);
return result;
}
/**
* 树形结构转列表 - 使用 DFS 迭代(避免递归)
*/
function treeToListIterative(tree, childrenKey = 'children') {
const result = [];
const stack = [...tree];
while (stack.length > 0) {
const node = stack.pop();
const { [childrenKey]: children, ...rest } = node;
result.push(rest);
if (children && children.length > 0) {
// 反向入栈以保持原有顺序
for (let i = children.length - 1; i >= 0; i--) {
stack.push(children[i]);
}
}
}
return result.reverse(); // 反转以恢复正确顺序
}
手写常见排序
冒泡排序
/**
* 冒泡排序
* 重复遍历数组,比较相邻元素,大数逐步"冒泡"到末尾
*
* 时间复杂度:O(n^2)
* 空间复杂度:O(1)
* 稳定性:稳定
*/
function bubbleSort(arr) {
const len = arr.length;
const result = [...arr]; // 不修改原数组
for (let i = 0; i < len - 1; i++) {
let swapped = false;
for (let j = 0; j < len - 1 - i; j++) {
if (result[j] > result[j + 1]) {
[result[j], result[j + 1]] = [result[j + 1], result[j]];
swapped = true;
}
}
// 如果没有发生交换,说明已排序完成,提前退出
if (!swapped) break;
}
return result;
}
快速排序
/**
* 快速排序
* 选择一个基准元素,将数组分为小于基准和大于基准两部分,递归排序
*
* 时间复杂度:O(n log n)(平均),O(n^2)(最坏)
* 空间复杂度:O(log n)
* 稳定性:不稳定
*/
// 方法1:简单版(使用额外空间)
function quickSortSimple(arr) {
if (arr.length <= 1) return arr;
const pivot = arr[0];
const left = [];
const right = [];
for (let i = 1; i < arr.length; i++) {
if (arr[i] < pivot) {
left.push(arr[i]);
} else {
right.push(arr[i]);
}
}
return [...quickSortSimple(left), pivot, ...quickSortSimple(right)];
}
// 方法2:原地排序版(高效)
function quickSortInPlace(arr, left = 0, right = arr.length - 1) {
if (left >= right) return arr;
const pivotIndex = partition(arr, left, right);
quickSortInPlace(arr, left, pivotIndex - 1);
quickSortInPlace(arr, pivotIndex + 1, right);
return arr;
}
function partition(arr, left, right) {
// 三数取中法选择基准,优化最坏情况
const mid = Math.floor((left + right) / 2);
if (arr[left] > arr[mid]) [arr[left], arr[mid]] = [arr[mid], arr[left]];
if (arr[left] > arr[right]) [arr[left], arr[right]] = [arr[right], arr[left]];
if (arr[mid] > arr[right]) [arr[mid], arr[right]] = [arr[right], arr[mid]];
// 将基准移到倒数第二个位置
[arr[mid], arr[right - 1]] = [arr[right - 1], arr[mid]];
const pivot = arr[right - 1];
let i = left;
let j = right - 1;
while (i < j) {
while (arr[++i] < pivot) {}
while (arr[--j] > pivot) {}
if (i < j) {
[arr[i], arr[j]] = [arr[j], arr[i]];
}
}
// 将基准放到正确位置
[arr[i], arr[right - 1]] = [arr[right - 1], arr[i]];
return i;
}
选择排序
/**
* 选择排序
* 每次从未排序部分选最小元素放到已排序部分的末尾
*
* 时间复杂度:O(n^2)
* 空间复杂度:O(1)
* 稳定性:不稳定
*/
function selectionSort(arr) {
const len = arr.length;
const result = [...arr];
for (let i = 0; i < len - 1; i++) {
let minIndex = i;
for (let j = i + 1; j < len; j++) {
if (result[j] < result[minIndex]) {
minIndex = j;
}
}
if (minIndex !== i) {
[result[i], result[minIndex]] = [result[minIndex], result[i]];
}
}
return result;
}
插入排序
/**
* 插入排序
* 将未排序元素逐个插入到已排序部分的正确位置
*
* 时间复杂度:O(n^2)
* 空间复杂度:O(1)
* 稳定性:稳定
*/
function insertionSort(arr) {
const len = arr.length;
const result = [...arr];
for (let i = 1; i < len; i++) {
const current = result[i];
let j = i - 1;
// 将大于 current 的元素向右移动
while (j >= 0 && result[j] > current) {
result[j + 1] = result[j];
j--;
}
result[j + 1] = current;
}
return result;
}
二分查找
/**
* 二分查找
* 在有序数组中查找目标值,每次将搜索范围缩小一半
*
* 时间复杂度:O(log n)
* 空间复杂度:O(1)
*
* @param {Array} arr - 已排序的数组
* @param {any} target - 目标值
* @returns {number} 目标值的索引,未找到返回 -1
*/
// 迭代版本
function binarySearch(arr, target) {
let left = 0;
let right = arr.length - 1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) {
return mid;
} else if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
// 递归版本
function binarySearchRecursive(arr, target, left = 0, right = arr.length - 1) {
if (left > right) return -1;
const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) return mid;
if (arr[mid] < target) {
return binarySearchRecursive(arr, target, mid + 1, right);
}
return binarySearchRecursive(arr, target, left, mid - 1);
}
/**
* 二分查找 - 查找第一个等于 target 的位置(有重复元素时)
*/
function binarySearchFirst(arr, target) {
let left = 0;
let right = arr.length - 1;
let result = -1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) {
result = mid;
right = mid - 1; // 继续向左查找
} else if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return result;
}
/**
* 二分查找 - 查找最后一个等于 target 的位置
*/
function binarySearchLast(arr, target) {
let left = 0;
let right = arr.length - 1;
let result = -1;
while (left <= right) {
const mid = Math.floor((left + right) / 2);
if (arr[mid] === target) {
result = mid;
left = mid + 1; // 继续向右查找
} else if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return result;
}
算法数据结构
实现一个链表结构
/**
* 链表节点
*/
class ListNode {
constructor(value) {
this.value = value;
this.next = null;
}
}
/**
* 单向链表
*/
class LinkedList {
constructor() {
this.head = null;
this.tail = null;
this.length = 0;
}
/**
* 在链表末尾添加节点
* @param {any} value
*/
append(value) {
const node = new ListNode(value);
if (!this.head) {
this.head = node;
this.tail = node;
} else {
this.tail.next = node;
this.tail = node;
}
this.length++;
return this;
}
/**
* 在链表头部插入节点
* @param {any} value
*/
prepend(value) {
const node = new ListNode(value);
if (!this.head) {
this.head = node;
this.tail = node;
} else {
node.next = this.head;
this.head = node;
}
this.length++;
return this;
}
/**
* 在指定位置插入节点
* @param {any} value
* @param {number} index
*/
insert(value, index) {
if (index < 0 || index > this.length) return false;
if (index === 0) return this.prepend(value);
if (index === this.length) return this.append(value);
const node = new ListNode(value);
let current = this.head;
for (let i = 0; i < index - 1; i++) {
current = current.next;
}
node.next = current.next;
current.next = node;
this.length++;
return this;
}
/**
* 删除指定位置的节点
* @param {number} index
* @returns {any|null} 被删除节点的值
*/
removeAt(index) {
if (index < 0 || index >= this.length || !this.head) return null;
let removedNode;
if (index === 0) {
removedNode = this.head;
this.head = this.head.next;
if (!this.head) this.tail = null;
} else {
let current = this.head;
for (let i = 0; i < index - 1; i++) {
current = current.next;
}
removedNode = current.next;
current.next = removedNode.next;
if (!current.next) this.tail = current;
}
this.length--;
return removedNode.value;
}
/**
* 查找值的索引
* @param {any} value
* @returns {number} 索引,未找到返回 -1
*/
indexOf(value) {
let current = this.head;
let index = 0;
while (current) {
if (current.value === value) return index;
current = current.next;
index++;
}
return -1;
}
/**
* 遍历链表
* @param {Function} callback
*/
forEach(callback) {
let current = this.head;
let index = 0;
while (current) {
callback(current.value, index);
current = current.next;
index++;
}
}
/**
* 将链表转为数组
* @returns {Array}
*/
toArray() {
const result = [];
this.forEach(value => result.push(value));
return result;
}
}
实现一个队列
/**
* 队列 - 先进先出(FIFO)
*/
// 方法1:基于数组
class Queue {
constructor() {
this.items = [];
}
enqueue(element) {
this.items.push(element);
}
dequeue() {
return this.items.shift();
}
front() {
return this.items.length > 0 ? this.items[0] : null;
}
isEmpty() {
return this.items.length === 0;
}
size() {
return this.items.length;
}
clear() {
this.items = [];
}
}
// 方法2:基于链表(性能更好,shift 是 O(n),链表头尾操作都是 O(1))
class QueueByLinkedList {
constructor() {
this.head = null;
this.tail = null;
this._size = 0;
}
enqueue(value) {
const node = { value, next: null };
if (!this.head) {
this.head = node;
this.tail = node;
} else {
this.tail.next = node;
this.tail = node;
}
this._size++;
}
dequeue() {
if (!this.head) return null;
const value = this.head.value;
this.head = this.head.next;
if (!this.head) this.tail = null;
this._size--;
return value;
}
front() {
return this.head ? this.head.value : null;
}
isEmpty() {
return this._size === 0;
}
size() {
return this._size;
}
}
递归反转链表
/**
* 递归反转链表
*
* 时间复杂度:O(n)
* 空间复杂度:O(n)(递归调用栈)
*/
function reverseLinkedList(head) {
// 空链表或只有一个节点
if (!head || !head.next) {
return head;
}
// 递归反转后续链表
const newHead = reverseLinkedList(head.next);
// 将当前节点接到反转后的链表末尾
head.next.next = head;
head.next = null;
return newHead;
}
/**
* 迭代反转链表
*
* 时间复杂度:O(n)
* 空间复杂度:O(1)
*/
function reverseLinkedListIterative(head) {
let prev = null;
let current = head;
while (current) {
const next = current.next; // 保存下一个节点
current.next = prev; // 反转指针
prev = current; // 移动 prev
current = next; // 移动 current
}
return prev; // 新的头节点
}
二叉树搜索
/**
* 二叉搜索树(BST)
* 左子树所有节点 < 根节点 < 右子树所有节点
*/
class TreeNode {
constructor(value) {
this.value = value;
this.left = null;
this.right = null;
}
}
class BinarySearchTree {
constructor() {
this.root = null;
}
/**
* 插入节点
* @param {any} value
*/
insert(value) {
const node = new TreeNode(value);
if (!this.root) {
this.root = node;
return this;
}
let current = this.root;
while (true) {
if (value < current.value) {
if (!current.left) {
current.left = node;
return this;
}
current = current.left;
} else if (value > current.value) {
if (!current.right) {
current.right = node;
return this;
}
current = current.right;
} else {
return this; // 不允许重复
}
}
}
/**
* 搜索节点
* @param {any} value
* @returns {TreeNode|null}
*/
search(value) {
let current = this.root;
while (current) {
if (value === current.value) return current;
if (value < current.value) current = current.left;
else current = current.right;
}
return null;
}
/**
* 查找最小值
* @param {TreeNode} [node] - 起始节点,默认 root
* @returns {TreeNode|null}
*/
findMin(node = this.root) {
if (!node) return null;
while (node.left) node = node.left;
return node;
}
/**
* 查找最大值
* @param {TreeNode} [node] - 起始节点,默认 root
* @returns {TreeNode|null}
*/
findMax(node = this.root) {
if (!node) return null;
while (node.right) node = node.right;
return node;
}
/**
* 移除节点
* @param {any} value
*/
remove(value) {
this.root = this._removeNode(this.root, value);
}
_removeNode(node, value) {
if (!node) return null;
if (value < node.value) {
node.left = this._removeNode(node.left, value);
return node;
}
if (value > node.value) {
node.right = this._removeNode(node.right, value);
return node;
}
// 找到要删除的节点
// 情况1:叶子节点
if (!node.left && !node.right) return null;
// 情况2:只有一个子节点
if (!node.left) return node.right;
if (!node.right) return node.left;
// 情况3:有两个子节点,找右子树的最小节点替换
const minRight = this.findMin(node.right);
node.value = minRight.value;
node.right = this._removeNode(node.right, minRight.value);
return node;
}
}
二叉树层次遍历
/**
* 二叉树层次遍历(广度优先 BFS)
* 从上到下,从左到右逐层访问节点
*
* 时间复杂度:O(n)
* 空间复杂度:O(n)
*/
function levelOrder(root) {
if (!root) return [];
const result = [];
const queue = [root]; // 使用队列实现 BFS
while (queue.length > 0) {
const levelSize = queue.length;
const currentLevel = [];
for (let i = 0; i < levelSize; i++) {
const node = queue.shift();
currentLevel.push(node.value);
if (node.left) queue.push(node.left);
if (node.right) queue.push(node.right);
}
result.push(currentLevel);
}
return result;
}
// --- 使用示例 ---
// 1
// / \
// 2 3
// / \ \
// 4 5 6
// levelOrder(root) => [[1], [2, 3], [4, 5, 6]]
二叉树深度遍历
/**
* 二叉树深度遍历(深度优先 DFS)
* 三种方式:前序、中序、后序
*
* 时间复杂度:O(n)
* 空间复杂度:O(n)
*/
// 前序遍历:根 -> 左 -> 右
function preorderTraversal(root) {
const result = [];
function dfs(node) {
if (!node) return;
result.push(node.value); // 访问根
dfs(node.left); // 遍历左子树
dfs(node.right); // 遍历右子树
}
dfs(root);
return result;
}
// 中序遍历:左 -> 根 -> 右(BST 下为升序)
function inorderTraversal(root) {
const result = [];
function dfs(node) {
if (!node) return;
dfs(node.left); // 遍历左子树
result.push(node.value); // 访问根
dfs(node.right); // 遍历右子树
}
dfs(root);
return result;
}
// 后序遍历:左 -> 右 -> 根
function postorderTraversal(root) {
const result = [];
function dfs(node) {
if (!node) return;
dfs(node.left); // 遍历左子树
dfs(node.right); // 遍历右子树
result.push(node.value); // 访问根
}
dfs(root);
return result;
}
// 前序遍历 - 迭代版本
function preorderTraversalIterative(root) {
if (!root) return [];
const result = [];
const stack = [root];
while (stack.length > 0) {
const node = stack.pop();
result.push(node.value);
// 先压右再压左,这样弹栈时先处理左
if (node.right) stack.push(node.right);
if (node.left) stack.push(node.left);
}
return result;
}
// 中序遍历 - 迭代版本
function inorderTraversalIterative(root) {
const result = [];
const stack = [];
let current = root;
while (current || stack.length > 0) {
while (current) {
stack.push(current);
current = current.left; // 先走到最左
}
current = stack.pop();
result.push(current.value);
current = current.right; // 处理右子树
}
return result;
}
// 后序遍历 - 迭代版本(前序的变体)
function postorderTraversalIterative(root) {
if (!root) return [];
const result = [];
const stack = [root];
while (stack.length > 0) {
const node = stack.pop();
result.unshift(node.value); // 插入到数组头部
if (node.left) stack.push(node.left);
if (node.right) stack.push(node.right);
}
return result;
}
综合
实现一个 sleep 函数
/**
* 实现 sleep 函数,等待指定毫秒数
* 基于 Promise 实现
*
* @param {number} ms - 毫秒数
* @returns {Promise} 返回 Promise
*/
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// --- 使用示例 ---
// async function test() {
// console.log('开始');
// await sleep(1000);
// console.log('1秒后执行');
// }
给定两个数组,写一个方法来计算它们的交集
/**
* 计算两个数组的交集
*
* @param {Array} arr1 - 数组1
* @param {Array} arr2 - 数组2
* @returns {Array} 交集
*/
// 方法1:使用 Set(适用于基本类型)
function intersection(arr1, arr2) {
const set1 = new Set(arr1);
return arr2.filter(item => set1.has(item));
}
// 方法2:支持对象数组(通过比较函数)
function intersectionBy(arr1, arr2, comparator) {
// comparator: (item1, item2) => boolean
return arr1.filter(item1 => arr2.some(item2 => comparator(item1, item2)));
}
// 方法3:保留重复元素
function intersectionWithDuplicates(arr1, arr2) {
const result = [];
const arr2Copy = [...arr2];
for (const item of arr1) {
const index = arr2Copy.indexOf(item);
if (index !== -1) {
result.push(item);
arr2Copy.splice(index, 1); // 从副本中删除已匹配的元素
}
}
return result;
}
异步并发数限制
/**
* 异步并发数限制
* 控制同时执行的异步任务数量
*
* @param {Array} tasks - 异步任务数组
* @param {number} limit - 并发限制数
* @returns {Promise<Array>} 所有任务结果
*/
async function asyncPool(tasks, limit) {
const results = [];
const executing = new Set();
for (const [index, task] of tasks.entries()) {
// 创建任务并添加到执行集合
const promise = Promise.resolve().then(() => task());
results.push(promise);
executing.add(promise);
// 任务完成后从执行集合移除
const clean = () => executing.delete(promise);
promise.then(clean, clean);
// 如果达到并发上限,等待其中一个完成
if (executing.size >= limit) {
await Promise.race(executing);
}
}
return Promise.all(results);
}
// --- 使用示例 ---
// const tasks = [
// () => fetch('/api/1'),
// () => fetch('/api/2'),
// () => fetch('/api/3'),
// () => fetch('/api/4'),
// () => fetch('/api/5'),
// ];
// asyncPool(tasks, 2).then(console.log);
异步串行 | 异步并行
/**
* 异步串行 - 依次执行异步任务
* @param {Array<Function>} tasks - 返回 Promise 的函数数组
* @returns {Promise<Array>} 结果数组
*/
async function serial(tasks) {
const results = [];
for (const task of tasks) {
const result = await task();
results.push(result);
}
return results;
}
/**
* 异步并行 - 同时执行异步任务
* @param {Array<Function>} tasks - 返回 Promise 的函数数组
* @returns {Promise<Array>} 结果数组
*/
async function parallel(tasks) {
return Promise.all(tasks.map(task => task()));
}
实现有并行限制的 Promise 调度器
/**
* 有并行限制的 Promise 调度器
*
* 功能:控制并发执行的任务数量,超出限制的任务排队等待
*/
class Scheduler {
constructor(limit) {
this.limit = limit;
this.queue = []; // 等待队列
this.running = 0; // 当前执行的任务数
}
/**
* 添加任务到调度器
* @param {Function} task - 返回 Promise 的函数
* @returns {Promise} 返回 Promise,任务完成后 resolve
*/
add(task) {
return new Promise((resolve, reject) => {
// 将任务包装后加入队列
this.queue.push(async () => {
try {
const result = await task();
resolve(result);
} catch (err) {
reject(err);
}
});
// 尝试执行任务
this.schedule();
});
}
/**
* 调度执行
*/
schedule() {
// 当有等待任务且运行数未达上限时,执行任务
while (this.queue.length > 0 && this.running < this.limit) {
const task = this.queue.shift();
this.running++;
task().finally(() => {
this.running--;
this.schedule(); // 执行下一个任务
});
}
}
}
// --- 使用示例 ---
// const scheduler = new Scheduler(2);
//
// const timeout = (ms) => new Promise(resolve => setTimeout(resolve, ms));
//
// scheduler.add(() => timeout(1000).then(() => console.log('任务1完成')));
// scheduler.add(() => timeout(500).then(() => console.log('任务2完成')));
// scheduler.add(() => timeout(300).then(() => console.log('任务3完成')));
// // 任务1和2同时开始,任务2先完成,然后任务3开始
图片懒加载
/**
* 图片懒加载
* 当图片进入视口时才加载真正的图片
*/
// 方法1:基于 IntersectionObserver(推荐)
function lazyLoadImages(selector = 'img[data-src]') {
const images = document.querySelectorAll(selector);
if (!images.length) return;
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
img.src = img.dataset.src; // 替换真实地址
img.removeAttribute('data-src'); // 清除 data-src
observer.unobserve(img); // 停止观察已加载的图片
}
});
}, {
rootMargin: '0px 0px 200px 0px', // 提前 200px 加载
threshold: 0.01
});
images.forEach(img => observer.observe(img));
}
// 方法2:基于监听滚动事件
function lazyLoadImagesScroll(selector = 'img[data-src]') {
const images = [...document.querySelectorAll(selector)];
function checkImages() {
for (let i = images.length - 1; i >= 0; i--) {
const img = images[i];
const rect = img.getBoundingClientRect();
if (rect.top < window.innerHeight + 200 && rect.bottom > 0) {
img.src = img.dataset.src;
img.removeAttribute('data-src');
images.splice(i, 1);
}
}
if (images.length === 0) {
window.removeEventListener('scroll', checkImages);
window.removeEventListener('resize', checkImages);
}
}
window.addEventListener('scroll', checkImages, { passive: true });
window.addEventListener('resize', checkImages, { passive: true });
// 初始检查
checkImages();
}
实现 getValue/setValue 函数来获取path对应的值
/**
* 根据路径获取对象中的值
* 类似 lodash 的 _.get
*
* @param {object} obj - 源对象
* @param {string|Array} path - 路径,如 'a.b.c' 或 ['a', 'b', 'c']
* @param {any} [defaultValue] - 默认值,路径不存在时返回
* @returns {any} 路径对应的值
*/
function getValue(obj, path, defaultValue = undefined) {
// 将路径统一转为数组
const keys = Array.isArray(path) ? path : path.split(/[\.\[\]'"]/).filter(Boolean);
let current = obj;
for (const key of keys) {
if (current === null || current === undefined || typeof current !== 'object') {
return defaultValue;
}
current = current[key];
}
return current !== undefined ? current : defaultValue;
}
/**
* 根据路径设置对象中的值
* 类似 lodash 的 _.set
*
* @param {object} obj - 源对象
* @param {string|Array} path - 路径
* @param {any} value - 要设置的值
* @returns {object} 修改后的对象
*/
function setValue(obj, path, value) {
const keys = Array.isArray(path) ? path : path.split(/[\.\[\]'"]/).filter(Boolean);
let current = obj;
for (let i = 0; i < keys.length - 1; i++) {
const key = keys[i];
const nextKey = keys[i + 1];
// 如果当前路径不存在,创建中间对象或数组
if (!(key in current)) {
current[key] = /^\d+$/.test(nextKey) ? [] : {};
}
current = current[key];
}
current[keys[keys.length - 1]] = value;
return obj;
}
// --- 使用示例 ---
// const obj = { a: { b: { c: 42 } } };
// console.log(getValue(obj, 'a.b.c')); // 42
// console.log(getValue(obj, 'x.y', 'default')); // 'default'
// setValue(obj, 'a.b.c', 100);
创建10个标签,点击的时候弹出来对应的序号
/**
* 创建10个标签,点击的时候弹出来对应的序号
* 考察闭包和事件监听
*/
// 方法1:使用 let 块级作用域
function createButtons1() {
for (let i = 0; i < 10; i++) {
const btn = document.createElement('button');
btn.textContent = `按钮 ${i}`;
btn.addEventListener('click', () => {
alert(i); // let 的块级作用域,i 每次都是新的绑定
});
document.body.appendChild(btn);
}
}
// 方法2:使用闭包(适用于 var)
function createButtons2() {
for (var i = 0; i < 10; i++) {
const btn = document.createElement('button');
btn.textContent = `按钮 ${i}`;
// 使用 IIFE 创建闭包,保存当前的 i
(function (index) {
btn.addEventListener('click', () => {
alert(index);
});
})(i);
document.body.appendChild(btn);
}
}
// 方法3:使用事件委托(性能更好)
function createButtons3() {
const container = document.createElement('div');
for (let i = 0; i < 10; i++) {
const btn = document.createElement('button');
btn.textContent = `按钮 ${i}`;
btn.dataset.index = i; // 将序号存在 data 属性中
container.appendChild(btn);
}
// 事件委托:在父容器上监听点击事件
container.addEventListener('click', (e) => {
const btn = e.target.closest('button');
if (btn) {
alert(btn.dataset.index);
}
});
document.body.appendChild(container);
}
版本号排序的方法
/**
* 版本号排序
* 版本号格式:主版本号.次版本号.修订号(如 1.0.0, 2.10.3)
*
* @param {Array<string>} versions - 版本号数组
* @param {string} [order='asc'] - 排序方向:'asc' 升序,'desc' 降序
* @returns {Array<string>} 排序后的版本号数组
*/
function sortVersions(versions, order = 'asc') {
return versions.sort((a, b) => {
const partsA = a.split('.').map(Number);
const partsB = b.split('.').map(Number);
const maxLen = Math.max(partsA.length, partsB.length);
for (let i = 0; i < maxLen; i++) {
const numA = partsA[i] || 0;
const numB = partsB[i] || 0;
if (numA !== numB) {
return order === 'asc' ? numA - numB : numB - numA;
}
}
return 0;
});
}
// --- 使用示例 ---
// const versions = ['1.0.0', '2.10.0', '2.1.0', '0.9.9', '1.5.2'];
// console.log(sortVersions(versions));
// // ['0.9.9', '1.0.0', '1.5.2', '2.1.0', '2.10.0']
请实现 DOM2JSON 一个函数,可以把一个 DOM 节点输出 JSON 的格式
/**
* DOM 节点转 JSON
* 将 DOM 树结构转换为 JSON 对象
*
* @param {HTMLElement} node - DOM 节点
* @returns {object} JSON 对象
*/
function dom2JSON(node) {
const obj = {
tag: node.tagName.toLowerCase()
};
// 处理属性
if (node.attributes && node.attributes.length > 0) {
obj.attrs = {};
for (const attr of node.attributes) {
obj.attrs[attr.name] = attr.value;
}
}
// 处理子节点
const children = [];
for (const child of node.childNodes) {
if (child.nodeType === 1) {
// 元素节点
children.push(dom2JSON(child));
} else if (child.nodeType === 3) {
// 文本节点,过滤空白文本
const text = child.textContent.trim();
if (text) {
children.push(text);
}
}
}
if (children.length > 0) {
obj.children = children;
}
return obj;
}
// --- 使用示例 ---
// // <div class="container" id="app">
// // <h1>标题</h1>
// // <p>内容</p>
// // </div>
// const json = dom2JSON(document.getElementById('app'));
// console.log(json);
// // { tag: 'div', attrs: { class: 'container', id: 'app' }, children: [
// // { tag: 'h1', children: ['标题'] },
// // { tag: 'p', children: ['内容'] }
// // ]}
分片思想解决大数据量渲染问题
/**
* 分片思想解决大数据量渲染问题
* 使用 requestAnimationFrame 将渲染任务分批执行,避免一次性渲染过多 DOM 导致卡顿
*/
/**
* 向页面中插入大量数据
* @param {Array} data - 数据数组
* @param {Function} createItem - 创建 DOM 元素的函数
* @param {HTMLElement} container - 容器元素
* @param {number} [batchSize=20] - 每批渲染的数量
*/
function renderLargeList(data, createItem, container, batchSize = 20) {
let index = 0;
function batchRender() {
// 计算本次要渲染的数据范围
const end = Math.min(index + batchSize, data.length);
// 使用文档片段减少回流
const fragment = document.createDocumentFragment();
for (let i = index; i < end; i++) {
const item = createItem(data[i]);
fragment.appendChild(item);
}
container.appendChild(fragment);
index = end;
// 如果还有数据未渲染,继续分片
if (index < data.length) {
requestAnimationFrame(batchRender);
}
}
batchRender();
}
/**
* 使用虚拟滚动 - 更高效的大数据量渲染方案
* 只渲染可视区域内的元素
*/
class VirtualScroll {
constructor(options) {
this.container = options.container;
this.itemHeight = options.itemHeight;
this.items = options.items;
this.renderItem = options.renderItem;
this.visibleCount = Math.ceil(this.container.clientHeight / this.itemHeight) + 2;
// 设置容器
this.container.style.position = 'relative';
this.container.style.overflow = 'auto';
// 创建占位元素(用于撑开滚动条)
this.placeholder = document.createElement('div');
this.placeholder.style.height = `${this.items.length * this.itemHeight}px`;
this.container.appendChild(this.placeholder);
// 渲染可视区域
this.render();
// 监听滚动事件
this.container.addEventListener('scroll', () => {
requestAnimationFrame(() => this.render());
});
}
render() {
const scrollTop = this.container.scrollTop;
const startIndex = Math.floor(scrollTop / this.itemHeight);
const endIndex = Math.min(startIndex + this.visibleCount, this.items.length);
// 清空现有可视区域的元素(保留占位符)
while (this.placeholder.previousSibling) {
this.container.removeChild(this.placeholder.previousSibling);
}
// 渲染新的可视区域元素
const fragment = document.createDocumentFragment();
for (let i = startIndex; i < endIndex; i++) {
const item = this.renderItem(this.items[i], i);
item.style.position = 'absolute';
item.style.top = `${i * this.itemHeight}px`;
fragment.appendChild(item);
}
this.container.insertBefore(fragment, this.placeholder);
}
}
实现一个add方法完成两个大数相加
/**
* 两个大数相加
* JS 中 Number 能安全表示的最大整数是 2^53 - 1
* 超过这个范围的整数运算会丢失精度,需要用字符串模拟加法
*
* @param {string} a - 大数字符串
* @param {string} b - 大数字符串
* @returns {string} 和
*/
function addBigNumbers(a, b) {
let i = a.length - 1;
let j = b.length - 1;
let carry = 0;
let result = '';
// 从个位开始逐位相加
while (i >= 0 || j >= 0 || carry > 0) {
const digitA = i >= 0 ? parseInt(a[i]) : 0;
const digitB = j >= 0 ? parseInt(b[j]) : 0;
const sum = digitA + digitB + carry;
carry = Math.floor(sum / 10);
result = (sum % 10) + result;
i--;
j--;
}
return result;
}
// --- 使用示例 ---
// console.log(addBigNumbers('999999999999999999', '1'));
// // '1000000000000000000'
怎么在制定数据源里面生成一个长度为 n 的不重复随机数组
/**
* 从指定数据源中生成一个长度为 n 的不重复随机数组
*
* @param {Array} source - 数据源
* @param {number} n - 需要的长度
* @returns {Array} 随机数组
*
* 时间复杂度分析见各方法注释
*/
// 方法1:洗牌算法(Fisher-Yates)-- 时间复杂度 O(m)(m 为数据源长度)
function randomSampleByShuffle(source, n) {
if (n > source.length) {
throw new Error('n 不能大于数据源长度');
}
// 复制数组,避免修改原数组
const arr = [...source];
// Fisher-Yates 洗牌
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
return arr.slice(0, n);
}
// 时间复杂度:O(m),空间复杂度:O(m)
// 方法2:逐次抽取 -- 最坏 O(n*m)
function randomSampleByPick(source, n) {
if (n > source.length) {
throw new Error('n 不能大于数据源长度');
}
const result = [];
const used = new Set();
while (result.length < n) {
const index = Math.floor(Math.random() * source.length);
if (!used.has(index)) {
used.add(index);
result.push(source[index]);
}
}
return result;
}
// 时间复杂度:O(n) 平均,最坏 O(无穷)(概率上趋向 O(n)),空间复杂度:O(n)
// 方法3:使用 Set 保证不重复
function randomSampleBySet(source, n) {
if (n > source.length) {
throw new Error('n 不能大于数据源长度');
}
const selected = new Set();
while (selected.size < n) {
const index = Math.floor(Math.random() * source.length);
selected.add(source[index]);
}
return [...selected];
}
// 时间复杂度:平均 O(n),空间复杂度:O(n)
// 方法4:当 n 接近 source.length 时,先洗牌再取前 n 个
function randomSampleAdaptive(source, n) {
if (n > source.length) {
throw new Error('n 不能大于数据源长度');
}
// 当 n 接近数据源长度时,洗牌更高效
if (n > source.length / 2) {
return randomSampleByShuffle(source, n);
}
// 当 n 较小时,使用 Set 抽取
return randomSampleBySet(source, n);
}
查找数组公共前缀(美团)
/**
* 查找数组公共前缀
* 如 ['flower', 'flow', 'flight'] => 'fl'
*
* @param {Array<string>} strs - 字符串数组
* @returns {string} 公共前缀
*
* 时间复杂度:O(m*n),m 为最短字符串长度,n 为数组长度
* 空间复杂度:O(1)
*/
function longestCommonPrefix(strs) {
if (!strs || strs.length === 0) return '';
if (strs.length === 1) return strs[0];
// 以第一个字符串为基准
const first = strs[0];
for (let i = 0; i < first.length; i++) {
const char = first[i];
// 检查其他字符串的相同位置是否相同
for (let j = 1; j < strs.length; j++) {
// 如果当前字符串长度不够,或者字符不匹配
if (i >= strs[j].length || strs[j][i] !== char) {
return first.slice(0, i);
}
}
}
return first;
}
// 方法2:分治法
function longestCommonPrefixDivide(strs) {
if (!strs || strs.length === 0) return '';
return divide(strs, 0, strs.length - 1);
}
function divide(strs, left, right) {
if (left === right) return strs[left];
const mid = Math.floor((left + right) / 2);
const leftPrefix = divide(strs, left, mid);
const rightPrefix = divide(strs, mid + 1, right);
return commonPrefix(leftPrefix, rightPrefix);
}
function commonPrefix(a, b) {
const minLen = Math.min(a.length, b.length);
for (let i = 0; i < minLen; i++) {
if (a[i] !== b[i]) {
return a.slice(0, i);
}
}
return a.slice(0, minLen);
}
判断括号字符串是否有效(小米)
/**
* 判断括号字符串是否有效
* 有效括号:()、[]、{} 正确嵌套和闭合
*
* @param {string} s - 括号字符串
* @returns {boolean}
*
* 时间复杂度:O(n)
* 空间复杂度:O(n)
*/
function isValidBrackets(s) {
if (s.length % 2 !== 0) return false; // 奇数长度直接返回 false
const stack = [];
const map = {
')': '(',
']': '[',
'}': '{'
};
for (const char of s) {
if (char === '(' || char === '[' || char === '{') {
// 左括号入栈
stack.push(char);
} else {
// 右括号:检查栈顶是否匹配
if (stack.length === 0 || stack.pop() !== map[char]) {
return false;
}
}
}
// 栈为空说明全部匹配
return stack.length === 0;
}
// --- 测试 ---
// console.log(isValidBrackets('()')); // true
// console.log(isValidBrackets('()[]{}')); // true
// console.log(isValidBrackets('(]')); // false
// console.log(isValidBrackets('([)]')); // false
// console.log(isValidBrackets('{[]}')); // true
实现一个padStart()或padEnd()的polyfil
/**
* 实现 String.prototype.padStart
* 用指定字符串填充原字符串到目标长度(从左侧开始)
*
* @param {number} targetLength - 目标长度
* @param {string} [padString=' '] - 填充字符串
* @returns {string}
*/
String.prototype.myPadStart = function (targetLength, padString = ' ') {
const str = String(this);
// 如果目标长度小于等于原字符串长度,返回原字符串
if (targetLength <= str.length) {
return str;
}
// 计算需要填充的长度
const padLength = targetLength - str.length;
// 重复填充字符串到需要的长度
let padding = '';
while (padding.length < padLength) {
padding += padString;
}
// 截取需要的填充部分 + 原字符串
return padding.slice(0, padLength) + str;
};
/**
* 实现 String.prototype.padEnd
* 从右侧开始填充
*/
String.prototype.myPadEnd = function (targetLength, padString = ' ') {
const str = String(this);
if (targetLength <= str.length) {
return str;
}
const padLength = targetLength - str.length;
let padding = '';
while (padding.length < padLength) {
padding += padString;
}
return str + padding.slice(0, padLength);
};
设计一个方法提取对象中所有value大于2的键值对并返回最新的对象
/**
* 提取对象中所有 value 大于 2 的键值对
* 深度递归处理,保留嵌套结构
*
* @param {object} obj - 源对象
* @returns {object} 过滤后的对象
*/
function filterValuesGreaterThan2(obj) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
const result = {};
for (const key of Object.keys(obj)) {
const value = obj[key];
if (typeof value === 'object' && value !== null) {
// 递归处理嵌套对象
const filtered = filterValuesGreaterThan2(value);
if (Object.keys(filtered).length > 0 || Array.isArray(value)) {
result[key] = filtered;
}
} else if (typeof value === 'number' && value > 2) {
// 数值且大于2
result[key] = value;
}
}
return result;
}
// --- 使用示例 ---
// const obj = { a: 1, b: 3, c: { d: 4, e: 1, f: { g: 5 } }, h: 'hello', i: [1, 2, 3] };
// console.log(filterValuesGreaterThan2(obj));
// // { b: 3, c: { d: 4, f: { g: 5 } } }
实现一个拖拽
/**
* 实现拖拽功能
*
* @param {HTMLElement} element - 被拖拽的元素
*/
function makeDraggable(element) {
element.style.position = 'absolute';
element.style.cursor = 'move';
element.style.userSelect = 'none';
element.addEventListener('mousedown', (e) => {
e.preventDefault();
// 记录鼠标与元素左上角的偏移
const startX = e.clientX - element.offsetLeft;
const startY = e.clientY - element.offsetTop;
function onMouseMove(e) {
// 计算新位置(限制在视口内)
const x = Math.max(0, Math.min(e.clientX - startX, window.innerWidth - element.offsetWidth));
const y = Math.max(0, Math.min(e.clientY - startY, window.innerHeight - element.offsetHeight));
element.style.left = `${x}px`;
element.style.top = `${y}px`;
}
function onMouseUp() {
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
}
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
});
}
// 触屏版拖拽
function makeDraggableTouch(element) {
element.style.position = 'absolute';
element.style.touchAction = 'none';
element.addEventListener('touchstart', (e) => {
const touch = e.touches[0];
const startX = touch.clientX - element.offsetLeft;
const startY = touch.clientY - element.offsetTop;
function onTouchMove(e) {
e.preventDefault();
const touch = e.touches[0];
element.style.left = `${touch.clientX - startX}px`;
element.style.top = `${touch.clientY - startY}px`;
}
function onTouchEnd() {
element.removeEventListener('touchmove', onTouchMove);
element.removeEventListener('touchend', onTouchEnd);
}
element.addEventListener('touchmove', onTouchMove, { passive: false });
element.addEventListener('touchend', onTouchEnd);
});
}
基于Promise.all实现Ajax的串行和并行
/**
* 基于 Promise.all 实现 Ajax 的串行和并行
*/
// 假设的请求函数
function request(url) {
return fetch(url).then(res => res.json());
}
/**
* 并行请求 - 所有请求同时发出
* @param {Array<string>} urls - URL 数组
* @returns {Promise<Array>} 所有结果
*/
function parallelRequests(urls) {
return Promise.all(urls.map(url => request(url)));
}
/**
* 串行请求 - 依次发出,后一个依赖前一个
* @param {Array<string>} urls - URL 数组
* @returns {Promise<Array>} 所有结果
*/
async function serialRequests(urls) {
const results = [];
for (const url of urls) {
const data = await request(url);
results.push(data);
}
return results;
}
/**
* 串行请求 - reduce 实现
*/
function serialRequestsReduce(urls) {
return urls.reduce((promise, url) => {
return promise.then(results => {
return request(url).then(data => [...results, data]);
});
}, Promise.resolve([]));
}
/**
* 串行请求 - 带依赖关系(每个请求使用上一个结果)
* @param {Array} tasks - 任务数组,每个任务是一个函数,接收上一个结果
*/
async function serialWithDependency(tasks) {
let result;
for (const task of tasks) {
result = await task(result);
}
return result;
}
修改嵌套层级很深对象的 key
/**
* 修改嵌套层级很深对象的 key
* 将对象中所有指定 key 替换为新 key(递归处理)
*
* @param {object} obj - 源对象
* @param {string} oldKey - 旧键名
* @param {string} newKey - 新键名
* @returns {object} 修改后的新对象(深拷贝)
*/
function renameDeepKey(obj, oldKey, newKey) {
if (obj === null || typeof obj !== 'object') {
return obj;
}
// 处理数组
if (Array.isArray(obj)) {
return obj.map(item => renameDeepKey(item, oldKey, newKey));
}
const result = {};
for (const key of Object.keys(obj)) {
// 替换 key
const newKeyName = key === oldKey ? newKey : key;
// 递归处理值
result[newKeyName] = renameDeepKey(obj[key], oldKey, newKey);
}
return result;
}
// --- 使用示例 ---
// const obj = {
// a: 1,
// b: {
// oldName: 2,
// c: {
// oldName: 3
// }
// },
// oldName: 4
// };
// console.log(renameDeepKey(obj, 'oldName', 'newName'));
// // { a: 1, b: { newName: 2, c: { newName: 3 } }, newName: 4 }