no message

This commit is contained in:
KuroSago 2026-01-19 10:21:18 +08:00
parent d09fb5bf75
commit 0daf1bd097
6 changed files with 360 additions and 252 deletions

View File

@ -8,39 +8,45 @@
```js ```js
class MyPromise { class MyPromise {
constructor(executor) { constructor(executor) {
this.state = 'pending'; this.state = 'pending'; // 初始状态为 pending
this.value = undefined; this.value = undefined; // 存储成功值或失败原因
this.callbacks = []; this.callbacks = []; // 存储 then 注册的回调函数
// 成功时调用,将状态改为 fulfilled
const resolve = (value) => { const resolve = (value) => {
if (this.state !== 'pending') return; if (this.state !== 'pending') return; // 状态只能改变一次
this.state = 'fulfilled'; this.state = 'fulfilled';
this.value = value; this.value = value;
this.callbacks.forEach(cb => cb.onFulfilled(value)); this.callbacks.forEach(cb => cb.onFulfilled(value)); // 执行所有成功回调
}; };
// 失败时调用,将状态改为 rejected
const reject = (reason) => { const reject = (reason) => {
if (this.state !== 'pending') return; if (this.state !== 'pending') return;
this.state = 'rejected'; this.state = 'rejected';
this.value = reason; this.value = reason;
this.callbacks.forEach(cb => cb.onRejected(reason)); this.callbacks.forEach(cb => cb.onRejected(reason)); // 执行所有失败回调
}; };
// 立即执行 executor捕获异常则 reject
try { executor(resolve, reject); } catch (e) { reject(e); } try { executor(resolve, reject); } catch (e) { reject(e); }
} }
then(onFulfilled, onRejected) { then(onFulfilled, onRejected) {
// 返回新 Promise 实现链式调用
return new MyPromise((resolve, reject) => { return new MyPromise((resolve, reject) => {
const handle = (callback, fallback) => { const handle = (callback, fallback) => {
try { try {
const result = (callback || fallback)(this.value); const result = (callback || fallback)(this.value); // 执行回调
// 如果返回值是 Promise等待其完成
result instanceof MyPromise ? result.then(resolve, reject) : resolve(result); result instanceof MyPromise ? result.then(resolve, reject) : resolve(result);
} catch (e) { reject(e); } } catch (e) { reject(e); } // 回调报错则 reject
}; };
// 根据当前状态决定立即执行还是存储回调
if (this.state === 'fulfilled') handle(onFulfilled, v => v); if (this.state === 'fulfilled') handle(onFulfilled, v => v);
else if (this.state === 'rejected') handle(onRejected, e => { throw e; }); else if (this.state === 'rejected') handle(onRejected, e => { throw e; });
else this.callbacks.push({ else this.callbacks.push({ // pending 状态先存储回调
onFulfilled: () => handle(onFulfilled, v => v), onFulfilled: () => handle(onFulfilled, v => v),
onRejected: () => handle(onRejected, e => { throw e; }) onRejected: () => handle(onRejected, e => { throw e; })
}); });
@ -55,23 +61,24 @@ class MyPromise {
> **解决**:多异步任务协调与超时兜底问题。 > **解决**:多异步任务协调与超时兜底问题。
```js ```js
// Promise.all - 所有成功才成功 // Promise.all - 所有成功才成功,一个失败则立即失败
Promise.myAll = (promises) => { Promise.myAll = (promises) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const results = []; const results = []; // 存储所有结果
let count = 0; let count = 0; // 记录完成数量
promises.forEach((p, i) => { promises.forEach((p, i) => {
Promise.resolve(p).then(val => { Promise.resolve(p).then(val => {
results[i] = val; results[i] = val; // 按索引存储,保证顺序
if (++count === promises.length) resolve(results); if (++count === promises.length) resolve(results); // 全部完成则 resolve
}, reject); }, reject); // 任一失败立即 reject
}); });
}); });
}; };
// Promise.race - 第一个完成就返回 // Promise.race - 第一个完成就返回(无论成功失败)
Promise.myRace = (promises) => { Promise.myRace = (promises) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// 第一个 resolve/reject 的结果会被采纳,后续的会被忽略
promises.forEach(p => Promise.resolve(p).then(resolve, reject)); promises.forEach(p => Promise.resolve(p).then(resolve, reject));
}); });
}; };
@ -83,21 +90,21 @@ Promise.myRace = (promises) => {
> **解决**:高频事件触发导致的性能问题和重复请求。 > **解决**:高频事件触发导致的性能问题和重复请求。
```js ```js
// 防抖:停止触发后执行 // 防抖:停止触发后执行(适合搜索框输入)
function debounce(fn, delay) { function debounce(fn, delay) {
let timer = null; let timer = null; // 保存定时器 ID
return function(...args) { return function(...args) {
clearTimeout(timer); clearTimeout(timer); // 每次触发都清除上一个定时器
timer = setTimeout(() => fn.apply(this, args), delay); timer = setTimeout(() => fn.apply(this, args), delay); // 重新设定定时器
}; };
} }
// 节流:固定间隔执行 // 节流:固定间隔执行(适合滚动事件)
function throttle(fn, delay) { function throttle(fn, delay) {
let last = 0; let last = 0; // 记录上次执行时间
return function(...args) { return function(...args) {
const now = Date.now(); const now = Date.now();
if (now - last >= delay) { if (now - last >= delay) { // 距离上次执行超过 delay 才执行
last = now; last = now;
fn.apply(this, args); fn.apply(this, args);
} }
@ -112,17 +119,22 @@ function throttle(fn, delay) {
```js ```js
function deepClone(obj, map = new WeakMap()) { function deepClone(obj, map = new WeakMap()) {
// 基本类型和 null 直接返回
if (obj === null || typeof obj !== 'object') return obj; if (obj === null || typeof obj !== 'object') return obj;
// 特殊对象处理
if (obj instanceof Date) return new Date(obj); if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj); if (obj instanceof RegExp) return new RegExp(obj);
if (map.has(obj)) return map.get(obj); // 处理循环引用 // 处理循环引用:如果已拷贝过,直接返回缓存
if (map.has(obj)) return map.get(obj);
// 创建新对象/数组
const clone = Array.isArray(obj) ? [] : {}; const clone = Array.isArray(obj) ? [] : {};
map.set(obj, clone); map.set(obj, clone); // 先存入 map防止循环引用
// 递归拷贝每个属性
for (const key in obj) { for (const key in obj) {
if (obj.hasOwnProperty(key)) { if (obj.hasOwnProperty(key)) { // 只拷贝自身属性
clone[key] = deepClone(obj[key], map); clone[key] = deepClone(obj[key], map);
} }
} }
@ -138,9 +150,11 @@ function deepClone(obj, map = new WeakMap()) {
```js ```js
function curry(fn) { function curry(fn) {
return function curried(...args) { return function curried(...args) {
// 如果参数数量足够,直接执行原函数
if (args.length >= fn.length) { if (args.length >= fn.length) {
return fn.apply(this, args); return fn.apply(this, args);
} }
// 参数不够,返回新函数继续收集参数
return (...nextArgs) => curried(...args, ...nextArgs); return (...nextArgs) => curried(...args, ...nextArgs);
}; };
} }
@ -148,8 +162,8 @@ function curry(fn) {
// 使用示例 // 使用示例
const add = (a, b, c) => a + b + c; const add = (a, b, c) => a + b + c;
const curriedAdd = curry(add); const curriedAdd = curry(add);
curriedAdd(1)(2)(3); // 6 curriedAdd(1)(2)(3); // 6 - 每次传一个参数
curriedAdd(1, 2)(3); // 6 curriedAdd(1, 2)(3); // 6 - 也可以一次传多个
``` ```
## 6. call / apply / bind 实现 ## 6. call / apply / bind 实现
@ -158,30 +172,31 @@ curriedAdd(1, 2)(3); // 6
> **解决**this 指向问题,实现函数借用和预设参数。 > **解决**this 指向问题,实现函数借用和预设参数。
```js ```js
// call // call - 立即执行,参数逐个传入
Function.prototype.myCall = function(ctx, ...args) { Function.prototype.myCall = function(ctx, ...args) {
ctx = ctx || window; ctx = ctx || window; // ctx 为 null/undefined 时指向 window
const key = Symbol(); const key = Symbol(); // 用 Symbol 避免属性名冲突
ctx[key] = this; ctx[key] = this; // 将函数作为 ctx 的方法
const result = ctx[key](...args); const result = ctx[key](...args); // 调用,此时 this 指向 ctx
delete ctx[key]; delete ctx[key]; // 清理临时属性
return result; return result;
}; };
// apply // apply - 立即执行,参数以数组传入
Function.prototype.myApply = function(ctx, args = []) { Function.prototype.myApply = function(ctx, args = []) {
ctx = ctx || window; ctx = ctx || window;
const key = Symbol(); const key = Symbol();
ctx[key] = this; ctx[key] = this;
const result = ctx[key](...args); const result = ctx[key](...args); // 展开数组参数
delete ctx[key]; delete ctx[key];
return result; return result;
}; };
// bind // bind - 返回新函数,不立即执行
Function.prototype.myBind = function(ctx, ...args) { Function.prototype.myBind = function(ctx, ...args) {
const fn = this; const fn = this; // 保存原函数
return function(...newArgs) { return function(...newArgs) {
// 合并预设参数和新参数
return fn.apply(ctx, [...args, ...newArgs]); return fn.apply(ctx, [...args, ...newArgs]);
}; };
}; };
@ -195,30 +210,36 @@ Function.prototype.myBind = function(ctx, ...args) {
```js ```js
class EventEmitter { class EventEmitter {
constructor() { constructor() {
this.events = {}; this.events = {}; // 存储事件名 -> 监听器数组的映射
} }
// 订阅事件
on(event, listener) { on(event, listener) {
(this.events[event] ||= []).push(listener); (this.events[event] ||= []).push(listener); // 添加监听器
return this; return this; // 返回 this 支持链式调用
} }
// 取消订阅
off(event, listener) { off(event, listener) {
if (this.events[event]) { if (this.events[event]) {
// 过滤掉指定的监听器
this.events[event] = this.events[event].filter(l => l !== listener); this.events[event] = this.events[event].filter(l => l !== listener);
} }
return this; return this;
} }
// 触发事件
emit(event, ...args) { emit(event, ...args) {
// 执行该事件的所有监听器
(this.events[event] || []).forEach(listener => listener(...args)); (this.events[event] || []).forEach(listener => listener(...args));
return this; return this;
} }
// 一次性订阅,触发后自动取消
once(event, listener) { once(event, listener) {
const wrapper = (...args) => { const wrapper = (...args) => {
listener(...args); listener(...args); // 执行原始监听器
this.off(event, wrapper); this.off(event, wrapper); // 立即取消订阅
}; };
return this.on(event, wrapper); return this.on(event, wrapper);
} }

View File

@ -6,20 +6,20 @@
> **解决**:数据列表中存在重复项影响展示或数据处理的问题。 > **解决**:数据列表中存在重复项影响展示或数据处理的问题。
```js ```js
// 方法1: Set // 方法1: Set 自动去重,最简洁
const unique1 = arr => [...new Set(arr)]; const unique1 = arr => [...new Set(arr)];
// 方法2: filter + indexOf // 方法2: filter + indexOf,只保留第一次出现的元素
const unique2 = arr => arr.filter((v, i) => arr.indexOf(v) === i); const unique2 = arr => arr.filter((v, i) => arr.indexOf(v) === i);
// 方法3: reduce // 方法3: reduce 累加,不存在才添加
const unique3 = arr => arr.reduce((acc, v) => acc.includes(v) ? acc : [...acc, v], []); const unique3 = arr => arr.reduce((acc, v) => acc.includes(v) ? acc : [...acc, v], []);
// 方法4: 对象数组按属性去重 // 方法4: 对象数组按属性去重
const uniqueByKey = (arr, key) => { const uniqueByKey = (arr, key) => {
const map = new Map(); const map = new Map(); // 用 Map 存储已出现的 key
arr.forEach(item => !map.has(item[key]) && map.set(item[key], item)); arr.forEach(item => !map.has(item[key]) && map.set(item[key], item));
return [...map.values()]; return [...map.values()]; // 返回去重后的值
}; };
``` ```
@ -29,28 +29,30 @@ const uniqueByKey = (arr, key) => {
> **解决**:嵌套数组不便于遍历、查找和渲染的问题。 > **解决**:嵌套数组不便于遍历、查找和渲染的问题。
```js ```js
// 递归实现 // 递归实现:支持指定展开深度
function flatten(arr, depth = Infinity) { function flatten(arr, depth = Infinity) {
return arr.reduce((acc, val) => { return arr.reduce((acc, val) => {
// 如果是数组且深度>0递归展开
return acc.concat( return acc.concat(
Array.isArray(val) && depth > 0 ? flatten(val, depth - 1) : val Array.isArray(val) && depth > 0 ? flatten(val, depth - 1) : val
); );
}, []); }, []);
} }
// 迭代实现(使用栈) // 迭代实现(使用栈):避免递归栈溢出
function flattenIterative(arr) { function flattenIterative(arr) {
const stack = [...arr]; const stack = [...arr]; // 复制数组作为栈
const result = []; const result = [];
while (stack.length) { while (stack.length) {
const item = stack.pop(); const item = stack.pop(); // 从栈顶取出
// 是数组则展开压回栈,否则加入结果
Array.isArray(item) ? stack.push(...item) : result.unshift(item); Array.isArray(item) ? stack.push(...item) : result.unshift(item);
} }
return result; return result;
} }
// 原生方法 // 原生方法ES2019+
arr.flat(Infinity); arr.flat(Infinity); // Infinity 表示完全展开
``` ```
## 3. 字符串反转 ## 3. 字符串反转
@ -59,9 +61,10 @@ arr.flat(Infinity);
> **解决**JS 没有内置字符串反转方法,需要手工实现。 > **解决**JS 没有内置字符串反转方法,需要手工实现。
```js ```js
// 方法1拆分成数组 -> 反转 -> 拼接
const reverse = str => str.split('').reverse().join(''); const reverse = str => str.split('').reverse().join('');
// 或者使用扩展运算符 // 方法2使用扩展运算符支持 Unicode 字符)
const reverse2 = str => [...str].reverse().join(''); const reverse2 = str => [...str].reverse().join('');
``` ```
@ -71,16 +74,19 @@ const reverse2 = str => [...str].reverse().join('');
> **解决**:判断字符串正反读是否一致,常作为算法题输入校验或中间步骤。 > **解决**:判断字符串正反读是否一致,常作为算法题输入校验或中间步骤。
```js ```js
// 方法1清理后比较反转结果
const isPalindrome = str => { const isPalindrome = str => {
// 转小写并去除非字母数字字符
const clean = str.toLowerCase().replace(/[^a-z0-9]/g, ''); const clean = str.toLowerCase().replace(/[^a-z0-9]/g, '');
return clean === clean.split('').reverse().join(''); return clean === clean.split('').reverse().join('');
}; };
// 双指针法 // 方法2双指针法(性能更优)
const isPalindrome2 = str => { const isPalindrome2 = str => {
const s = str.toLowerCase().replace(/[^a-z0-9]/g, ''); const s = str.toLowerCase().replace(/[^a-z0-9]/g, '');
let left = 0, right = s.length - 1; let left = 0, right = s.length - 1;
while (left < right) { while (left < right) {
// 从两端向中间比较
if (s[left++] !== s[right--]) return false; if (s[left++] !== s[right--]) return false;
} }
return true; return true;
@ -96,11 +102,12 @@ const isPalindrome2 = str => {
function longestCommonPrefix(strs) { function longestCommonPrefix(strs) {
if (!strs.length) return ''; if (!strs.length) return '';
let prefix = strs[0]; let prefix = strs[0]; // 以第一个字符串为初始前缀
for (let i = 1; i < strs.length; i++) { for (let i = 1; i < strs.length; i++) {
// 逐步缩短前缀,直到它是当前字符串的前缀
while (strs[i].indexOf(prefix) !== 0) { while (strs[i].indexOf(prefix) !== 0) {
prefix = prefix.slice(0, -1); prefix = prefix.slice(0, -1); // 去掉最后一个字符
if (!prefix) return ''; if (!prefix) return ''; // 前缀为空,无公共前缀
} }
} }
return prefix; return prefix;
@ -115,19 +122,21 @@ function longestCommonPrefix(strs) {
> **解决**:将模板字符串中的占位符替换为实际数据,类似 Mustache/Handlebars 语法。 > **解决**:将模板字符串中的占位符替换为实际数据,类似 Mustache/Handlebars 语法。
```js ```js
// 基础版:支持 {{key}} 语法
function render(template, data) { function render(template, data) {
// \{\{(\w+)\}\} 匹配 {{xxx}}\w+ 捕获 key
return template.replace(/\{\{(\w+)\}\}/g, (match, key) => { return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
return data[key] !== undefined ? data[key] : match; return data[key] !== undefined ? data[key] : match; // 无值则保留原样
}); });
} }
// 支持嵌套属性 // 进阶版:支持嵌套属性 {{a.b.c}}
function renderDeep(template, data) { function renderDeep(template, data) {
return template.replace(/\{\{(.+?)\}\}/g, (match, path) => { return template.replace(/\{\{(.+?)\}\}/g, (match, path) => {
const keys = path.trim().split('.'); const keys = path.trim().split('.'); // 按 . 拆分路径
let value = data; let value = data;
for (const key of keys) { for (const key of keys) {
value = value?.[key]; value = value?.[key]; // 逐层取值,可选链防止报错
if (value === undefined) return match; if (value === undefined) return match;
} }
return value; return value;
@ -146,13 +155,15 @@ renderDeep('{{user.info.name}}', { user: { info: { name: 'Tom' } } }); // "Tom"
```js ```js
function uniqueAndSort(arr, key, sortKey, order = 'asc') { function uniqueAndSort(arr, key, sortKey, order = 'asc') {
// 去重 // 第一步:按指定 key 去重
const map = new Map(); const map = new Map();
arr.forEach(item => !map.has(item[key]) && map.set(item[key], item)); arr.forEach(item => !map.has(item[key]) && map.set(item[key], item));
// 排序 // 第二步:按指定 sortKey 排序
return [...map.values()].sort((a, b) => { return [...map.values()].sort((a, b) => {
return order === 'asc' ? a[sortKey] - b[sortKey] : b[sortKey] - a[sortKey]; return order === 'asc'
? a[sortKey] - b[sortKey] // 升序
: b[sortKey] - a[sortKey]; // 降序
}); });
} }
@ -160,7 +171,7 @@ function uniqueAndSort(arr, key, sortKey, order = 'asc') {
const users = [ const users = [
{ id: 1, name: 'Tom', age: 20 }, { id: 1, name: 'Tom', age: 20 },
{ id: 2, name: 'Jerry', age: 18 }, { id: 2, name: 'Jerry', age: 18 },
{ id: 1, name: 'Tom2', age: 25 } { id: 1, name: 'Tom2', age: 25 } // id 重复,会被过滤
]; ];
uniqueAndSort(users, 'id', 'age'); // 按id去重按age升序排序 uniqueAndSort(users, 'id', 'age'); // 按 id 去重,按 age 升序排序
``` ```

View File

@ -9,28 +9,29 @@
```js ```js
function reverseList(head) { function reverseList(head) {
let prev = null, curr = head; let prev = null, curr = head; // prev 记录前一个节点
while (curr) { while (curr) {
const next = curr.next; const next = curr.next; // 暂存下一个节点
curr.next = prev; curr.next = prev; // 将当前节点指向前一个(反转)
prev = curr; prev = curr; // prev 前移
curr = next; curr = next; // curr 前移
} }
return prev; return prev; // prev 就是新的头节点
} }
``` ```
### 环形链表判断 ### 环形链表判断
```js ```js
// 快慢指针法:如果有环,快指针一定会追上慢指针
function hasCycle(head) { function hasCycle(head) {
let slow = head, fast = head; let slow = head, fast = head; // 两指针都从头开始
while (fast?.next) { while (fast?.next) { // fast 能继续走两步
slow = slow.next; slow = slow.next; // 慢指针走一步
fast = fast.next.next; fast = fast.next.next; // 快指针走两步
if (slow === fast) return true; if (slow === fast) return true; // 相遇说明有环
} }
return false; return false; // fast 走到结尾,无环
} }
``` ```
@ -38,20 +39,20 @@ function hasCycle(head) {
```js ```js
function mergeTwoLists(l1, l2) { function mergeTwoLists(l1, l2) {
const dummy = { next: null }; const dummy = { next: null }; // 哑节点,简化处理
let curr = dummy; let curr = dummy;
while (l1 && l2) { while (l1 && l2) { // 两个链表都有剩余节点
if (l1.val <= l2.val) { if (l1.val <= l2.val) {
curr.next = l1; curr.next = l1; // 取较小的节点
l1 = l1.next; l1 = l1.next;
} else { } else {
curr.next = l2; curr.next = l2;
l2 = l2.next; l2 = l2.next;
} }
curr = curr.next; curr = curr.next; // 移动当前指针
} }
curr.next = l1 || l2; curr.next = l1 || l2; // 接上剩余部分
return dummy.next; return dummy.next; // 返回哑节点的下一个即真正头节点
} }
``` ```
@ -65,30 +66,30 @@ function mergeTwoLists(l1, l2) {
### 遍历(前中后序) ### 遍历(前中后序)
```js ```js
// 前序:根-左-右 // 前序遍历:根 -> -> (先访问根节点)
const preorder = (root, res = []) => { const preorder = (root, res = []) => {
if (!root) return res; if (!root) return res;
res.push(root.val); res.push(root.val); // 访问根
preorder(root.left, res); preorder(root.left, res); // 递归左子树
preorder(root.right, res); preorder(root.right, res); // 递归右子树
return res; return res;
}; };
// 中序:左-根-右 // 中序遍历:左 -> -> (二叉搜索树得到有序数组)
const inorder = (root, res = []) => { const inorder = (root, res = []) => {
if (!root) return res; if (!root) return res;
inorder(root.left, res); inorder(root.left, res); // 先左
res.push(root.val); res.push(root.val); // 再根
inorder(root.right, res); inorder(root.right, res); // 后右
return res; return res;
}; };
// 后序:左-右-根 // 后序遍历:左 -> -> (常用于删除操作)
const postorder = (root, res = []) => { const postorder = (root, res = []) => {
if (!root) return res; if (!root) return res;
postorder(root.left, res); postorder(root.left, res); // 先左
postorder(root.right, res); postorder(root.right, res); // 再右
res.push(root.val); res.push(root.val); // 最后根
return res; return res;
}; };
``` ```
@ -96,8 +97,9 @@ const postorder = (root, res = []) => {
### 求最大深度 ### 求最大深度
```js ```js
// 递归求最大深度:左右子树深度的较大值 + 1
const maxDepth = root => { const maxDepth = root => {
if (!root) return 0; if (!root) return 0; // 空节点深度为 0
return 1 + Math.max(maxDepth(root.left), maxDepth(root.right)); return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
}; };
``` ```
@ -105,9 +107,12 @@ const maxDepth = root => {
### 路径和 ### 路径和
```js ```js
// 判断是否存在根到叶子的路径,使得路径上所有节点值之和等于目标值
function hasPathSum(root, targetSum) { function hasPathSum(root, targetSum) {
if (!root) return false; if (!root) return false;
// 叶子节点:检查剩余值是否等于当前节点值
if (!root.left && !root.right) return targetSum === root.val; if (!root.left && !root.right) return targetSum === root.val;
// 递归检查左右子树,目标值减去当前节点值
return hasPathSum(root.left, targetSum - root.val) return hasPathSum(root.left, targetSum - root.val)
|| hasPathSum(root.right, targetSum - root.val); || hasPathSum(root.right, targetSum - root.val);
} }
@ -123,14 +128,17 @@ function hasPathSum(root, targetSum) {
### 用栈实现队列 ### 用栈实现队列
```js ```js
// 使用两个栈实现队列:入栈和出栈
class MyQueue { class MyQueue {
constructor() { constructor() {
this.inStack = []; this.inStack = []; // 入队栈
this.outStack = []; this.outStack = []; // 出队栈
} }
// 入队:直接压入 inStack
push(x) { this.inStack.push(x); } push(x) { this.inStack.push(x); }
// 出队:从 outStack 取,如果空则把 inStack 全部倒入 outStack
pop() { pop() {
if (!this.outStack.length) { if (!this.outStack.length) {
while (this.inStack.length) this.outStack.push(this.inStack.pop()); while (this.inStack.length) this.outStack.push(this.inStack.pop());
@ -138,6 +146,7 @@ class MyQueue {
return this.outStack.pop(); return this.outStack.pop();
} }
// 查看队首:同 pop 逻辑,但不弹出
peek() { peek() {
if (!this.outStack.length) { if (!this.outStack.length) {
while (this.inStack.length) this.outStack.push(this.inStack.pop()); while (this.inStack.length) this.outStack.push(this.inStack.pop());
@ -145,6 +154,7 @@ class MyQueue {
return this.outStack[this.outStack.length - 1]; return this.outStack[this.outStack.length - 1];
} }
// 判断是否为空
empty() { return !this.inStack.length && !this.outStack.length; } empty() { return !this.inStack.length && !this.outStack.length; }
} }
``` ```
@ -153,16 +163,18 @@ class MyQueue {
```js ```js
function isValid(s) { function isValid(s) {
// 右括号到左括号的映射
const map = { ')': '(', ']': '[', '}': '{' }; const map = { ')': '(', ']': '[', '}': '{' };
const stack = []; const stack = [];
for (const c of s) { for (const c of s) {
if (map[c]) { if (map[c]) { // 如果是右括号
// 栈顶必须是对应的左括号
if (stack.pop() !== map[c]) return false; if (stack.pop() !== map[c]) return false;
} else { } else {
stack.push(c); stack.push(c); // 左括号入栈
} }
} }
return !stack.length; return !stack.length; // 栈必须为空才有效
} }
``` ```
@ -176,12 +188,13 @@ function isValid(s) {
### 两数之和 ### 两数之和
```js ```js
// 用哈希表存储已遍历的数字及其索引
function twoSum(nums, target) { function twoSum(nums, target) {
const map = new Map(); const map = new Map();
for (let i = 0; i < nums.length; i++) { for (let i = 0; i < nums.length; i++) {
const diff = target - nums[i]; const diff = target - nums[i]; // 计算需要的配对数字
if (map.has(diff)) return [map.get(diff), i]; if (map.has(diff)) return [map.get(diff), i]; // 找到则返回两个索引
map.set(nums[i], i); map.set(nums[i], i); // 存储当前数字和索引
} }
return []; return [];
} }
@ -190,13 +203,14 @@ function twoSum(nums, target) {
### 字母异位词分组 ### 字母异位词分组
```js ```js
// 异位词排序后结果相同,以此为 key 分组
function groupAnagrams(strs) { function groupAnagrams(strs) {
const map = new Map(); const map = new Map();
for (const s of strs) { for (const s of strs) {
const key = [...s].sort().join(''); const key = [...s].sort().join(''); // 排序后作为 key
map.set(key, (map.get(key) || []).concat(s)); map.set(key, (map.get(key) || []).concat(s)); // 加入对应分组
} }
return [...map.values()]; return [...map.values()]; // 返回所有分组
} }
``` ```
@ -210,12 +224,14 @@ function groupAnagrams(strs) {
### 快速排序 ### 快速排序
```js ```js
// 快速排序:分治思想,选取基准值分区
function quickSort(arr) { function quickSort(arr) {
if (arr.length <= 1) return arr; if (arr.length <= 1) return arr; // 基准情况
const pivot = arr[Math.floor(arr.length / 2)]; const pivot = arr[Math.floor(arr.length / 2)]; // 选取中间元素为基准值
const left = arr.filter(x => x < pivot); const left = arr.filter(x => x < pivot); // 小于基准值
const middle = arr.filter(x => x === pivot); const middle = arr.filter(x => x === pivot); // 等于基准值
const right = arr.filter(x => x > pivot); const right = arr.filter(x => x > pivot); // 大于基准值
// 递归排序左右部分,合并结果
return [...quickSort(left), ...middle, ...quickSort(right)]; return [...quickSort(left), ...middle, ...quickSort(right)];
} }
``` ```
@ -223,20 +239,24 @@ function quickSort(arr) {
### 归并排序 ### 归并排序
```js ```js
// 归并排序:分治思想,先拆分再合并
function mergeSort(arr) { function mergeSort(arr) {
if (arr.length <= 1) return arr; if (arr.length <= 1) return arr;
const mid = Math.floor(arr.length / 2); const mid = Math.floor(arr.length / 2);
const left = mergeSort(arr.slice(0, mid)); const left = mergeSort(arr.slice(0, mid)); // 递归排序左半部分
const right = mergeSort(arr.slice(mid)); const right = mergeSort(arr.slice(mid)); // 递归排序右半部分
return merge(left, right); return merge(left, right); // 合并两个有序数组
} }
// 合并两个有序数组
function merge(left, right) { function merge(left, right) {
const result = []; const result = [];
let i = 0, j = 0; let i = 0, j = 0;
// 双指针比较,取较小的元素
while (i < left.length && j < right.length) { while (i < left.length && j < right.length) {
result.push(left[i] < right[j] ? left[i++] : right[j++]); result.push(left[i] < right[j] ? left[i++] : right[j++]);
} }
// 拼接剩余部分
return result.concat(left.slice(i), right.slice(j)); return result.concat(left.slice(i), right.slice(j));
} }
``` ```
@ -251,32 +271,36 @@ function merge(left, right) {
### 基础二分查找 ### 基础二分查找
```js ```js
// 标准二分查找:在有序数组中查找目标值
function binarySearch(arr, target) { function binarySearch(arr, target) {
let left = 0, right = arr.length - 1; let left = 0, right = arr.length - 1;
while (left <= right) { while (left <= right) {
const mid = Math.floor((left + right) / 2); const mid = Math.floor((left + right) / 2); // 计算中间位置
if (arr[mid] === target) return mid; if (arr[mid] === target) return mid; // 找到目标
// 根据大小关系缩小范围
arr[mid] < target ? (left = mid + 1) : (right = mid - 1); arr[mid] < target ? (left = mid + 1) : (right = mid - 1);
} }
return -1; return -1; // 未找到
} }
``` ```
### 旋转数组查找 ### 旋转数组查找
```js ```js
// 旋转数组查找:数组被旋转过,如 [4,5,6,7,0,1,2]
function searchRotated(nums, target) { function searchRotated(nums, target) {
let left = 0, right = nums.length - 1; let left = 0, right = nums.length - 1;
while (left <= right) { while (left <= right) {
const mid = Math.floor((left + right) / 2); const mid = Math.floor((left + right) / 2);
if (nums[mid] === target) return mid; if (nums[mid] === target) return mid;
// 左半边有序 // 判断哪一半是有序的
if (nums[left] <= nums[mid]) { if (nums[left] <= nums[mid]) { // 左半边有序
// target 在左半边有序区间内
if (target >= nums[left] && target < nums[mid]) right = mid - 1; if (target >= nums[left] && target < nums[mid]) right = mid - 1;
else left = mid + 1; else left = mid + 1;
} else { } else { // 右半边有序
// 右半边有序 // target 在右半边有序区间内
if (target > nums[mid] && target <= nums[right]) left = mid + 1; if (target > nums[mid] && target <= nums[right]) left = mid + 1;
else right = mid - 1; else right = mid - 1;
} }
@ -297,8 +321,8 @@ function searchRotated(nums, target) {
```js ```js
// 时间复杂度 O(2^n),存在大量重复计算 // 时间复杂度 O(2^n),存在大量重复计算
function fib(n) { function fib(n) {
if (n <= 1) return n; if (n <= 1) return n; // 基准情况F(0)=0, F(1)=1
return fib(n - 1) + fib(n - 2); return fib(n - 1) + fib(n - 2); // 递归调用
} }
``` ```
@ -308,8 +332,8 @@ function fib(n) {
// 时间复杂度 O(n),空间复杂度 O(n) // 时间复杂度 O(n),空间复杂度 O(n)
function fib(n, memo = {}) { function fib(n, memo = {}) {
if (n <= 1) return n; if (n <= 1) return n;
if (memo[n]) return memo[n]; if (memo[n]) return memo[n]; // 已计算过,直接返回缓存
return memo[n] = fib(n - 1, memo) + fib(n - 2, memo); return memo[n] = fib(n - 1, memo) + fib(n - 2, memo); // 计算并缓存
} }
``` ```
@ -319,9 +343,9 @@ function fib(n, memo = {}) {
// 时间复杂度 O(n),空间复杂度 O(n) // 时间复杂度 O(n),空间复杂度 O(n)
function fib(n) { function fib(n) {
if (n <= 1) return n; if (n <= 1) return n;
const dp = [0, 1]; const dp = [0, 1]; // dp[i] 表示第 i 位斐波那契数
for (let i = 2; i <= n; i++) { for (let i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2]; dp[i] = dp[i - 1] + dp[i - 2]; // 状态转移方程
} }
return dp[n]; return dp[n];
} }
@ -333,9 +357,9 @@ function fib(n) {
// 时间复杂度 O(n),空间复杂度 O(1) // 时间复杂度 O(n),空间复杂度 O(1)
function fib(n) { function fib(n) {
if (n <= 1) return n; if (n <= 1) return n;
let prev = 0, curr = 1; let prev = 0, curr = 1; // 只保留前两个值
for (let i = 2; i <= n; i++) { for (let i = 2; i <= n; i++) {
[prev, curr] = [curr, prev + curr]; [prev, curr] = [curr, prev + curr]; // 滚动更新
} }
return curr; return curr;
} }
@ -345,26 +369,30 @@ function fib(n) {
```js ```js
// 时间复杂度 O(log n),适合求极大位数 // 时间复杂度 O(log n),适合求极大位数
// 原理:|F(n) | |1 1|^(n-1) |F(1)|
// |F(n-1)| = |1 0| * |F(0)|
function fib(n) { function fib(n) {
if (n <= 1) return n; if (n <= 1) return n;
// 2x2 矩阵乘法
const multiply = (a, b) => [ const multiply = (a, b) => [
[a[0][0] * b[0][0] + a[0][1] * b[1][0], a[0][0] * b[0][1] + a[0][1] * b[1][1]], [a[0][0] * b[0][0] + a[0][1] * b[1][0], a[0][0] * b[0][1] + a[0][1] * b[1][1]],
[a[1][0] * b[0][0] + a[1][1] * b[1][0], a[1][0] * b[0][1] + a[1][1] * b[1][1]] [a[1][0] * b[0][0] + a[1][1] * b[1][0], a[1][0] * b[0][1] + a[1][1] * b[1][1]]
]; ];
// 快速幂:计算 m^p
const power = (m, p) => { const power = (m, p) => {
let result = [[1, 0], [0, 1]]; // 单位矩阵 let result = [[1, 0], [0, 1]]; // 单位矩阵
while (p > 0) { while (p > 0) {
if (p & 1) result = multiply(result, m); if (p & 1) result = multiply(result, m); // 奇数幂则乘一次
m = multiply(m, m); m = multiply(m, m); // 底数平方
p >>= 1; p >>= 1; // 指数减半
} }
return result; return result;
}; };
const matrix = [[1, 1], [1, 0]]; const matrix = [[1, 1], [1, 0]];
return power(matrix, n - 1)[0][0]; return power(matrix, n - 1)[0][0]; // 结果在左上角
} }
``` ```

View File

@ -7,28 +7,30 @@
```js ```js
async function limitConcurrency(tasks, limit) { async function limitConcurrency(tasks, limit) {
const results = []; const results = []; // 存储所有任务结果
const executing = []; const executing = []; // 当前正在执行的任务
for (const [index, task] of tasks.entries()) { for (const [index, task] of tasks.entries()) {
// 包装任务为 Promise
const p = Promise.resolve().then(() => task()).then(res => { const p = Promise.resolve().then(() => task()).then(res => {
results[index] = res; results[index] = res; // 存储结果,保证顺序
executing.splice(executing.indexOf(p), 1); executing.splice(executing.indexOf(p), 1); // 完成后从执行列表移除
}); });
executing.push(p); executing.push(p);
// 如果达到并发上限,等待任一任务完成
if (executing.length >= limit) { if (executing.length >= limit) {
await Promise.race(executing); await Promise.race(executing);
} }
} }
await Promise.all(executing); await Promise.all(executing); // 等待剩余任务完成
return results; return results;
} }
// 使用示例 // 使用示例:批量请求,最多 3 个并发
const tasks = urls.map(url => () => fetch(url)); const tasks = urls.map(url => () => fetch(url));
await limitConcurrency(tasks, 3); // 最多3个并发 await limitConcurrency(tasks, 3);
``` ```
### 异步任务调度器 ### 异步任务调度器
@ -36,25 +38,29 @@ await limitConcurrency(tasks, 3); // 最多3个并发
```js ```js
class Scheduler { class Scheduler {
constructor(limit) { constructor(limit) {
this.limit = limit; this.limit = limit; // 最大并发数
this.queue = []; this.queue = []; // 等待队列
this.running = 0; this.running = 0; // 当前运行中的任务数
} }
// 添加任务,返回 Promise
add(promiseCreator) { add(promiseCreator) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// 将任务和对应的 resolve/reject 存入队列
this.queue.push({ promiseCreator, resolve, reject }); this.queue.push({ promiseCreator, resolve, reject });
this.run(); this.run(); // 尝试执行
}); });
} }
// 执行队列中的任务
run() { run() {
// 未达上限且队列有任务时,取出执行
while (this.running < this.limit && this.queue.length) { while (this.running < this.limit && this.queue.length) {
const { promiseCreator, resolve, reject } = this.queue.shift(); const { promiseCreator, resolve, reject } = this.queue.shift();
this.running++; this.running++;
promiseCreator() promiseCreator()
.then(resolve, reject) .then(resolve, reject)
.finally(() => { this.running--; this.run(); }); .finally(() => { this.running--; this.run(); }); // 完成后继续执行下一个
} }
} }
} }
@ -68,28 +74,30 @@ class Scheduler {
> **解决**:相同输入重复计算浪费性能,用空间换时间。 > **解决**:相同输入重复计算浪费性能,用空间换时间。
```js ```js
// 基础版:永久缓存
function memoize(fn) { function memoize(fn) {
const cache = new Map(); const cache = new Map(); // 缓存存储
return function(...args) { return function(...args) {
const key = JSON.stringify(args); const key = JSON.stringify(args); // 参数序列化作为 key
if (cache.has(key)) return cache.get(key); if (cache.has(key)) return cache.get(key); // 命中缓存
const result = fn.apply(this, args); const result = fn.apply(this, args); // 执行原函数
cache.set(key, result); cache.set(key, result); // 存入缓存
return result; return result;
}; };
} }
// 支持过期时间 // 进阶版:支持过期时间 TTL
function memoizeWithTTL(fn, ttl = 60000) { function memoizeWithTTL(fn, ttl = 60000) {
const cache = new Map(); const cache = new Map();
return function(...args) { return function(...args) {
const key = JSON.stringify(args); const key = JSON.stringify(args);
const cached = cache.get(key); const cached = cache.get(key);
// 检查缓存是否存在且未过期
if (cached && Date.now() - cached.time < ttl) { if (cached && Date.now() - cached.time < ttl) {
return cached.value; return cached.value;
} }
const result = fn.apply(this, args); const result = fn.apply(this, args);
cache.set(key, { value: result, time: Date.now() }); cache.set(key, { value: result, time: Date.now() }); // 存储值和时间戳
return result; return result;
}; };
} }
@ -103,11 +111,13 @@ function memoizeWithTTL(fn, ttl = 60000) {
> **解决**:将 URL 查询字符串转换为结构化对象,方便业务使用。 > **解决**:将 URL 查询字符串转换为结构化对象,方便业务使用。
```js ```js
// 方法1字符串分割法
function parseQuery(url) { function parseQuery(url) {
const query = url.split('?')[1] || ''; const query = url.split('?')[1] || ''; // 取 ? 后的部分
return query.split('&').reduce((acc, pair) => { return query.split('&').reduce((acc, pair) => {
const [key, value] = pair.split('=').map(decodeURIComponent); const [key, value] = pair.split('=').map(decodeURIComponent); // 解码
if (key) { if (key) {
// 处理重复 key转为数组
acc[key] = acc[key] acc[key] = acc[key]
? [].concat(acc[key], value) ? [].concat(acc[key], value)
: value; : value;
@ -116,9 +126,10 @@ function parseQuery(url) {
}, {}); }, {});
} }
// 使用正则 // 方法2正则表达式
function parseQueryRegex(url) { function parseQueryRegex(url) {
const result = {}; const result = {};
// 匹配 ?key=value 或 &key=value
url.replace(/[?&]([^=&#]+)=([^&#]*)/g, (_, key, value) => { url.replace(/[?&]([^=&#]+)=([^&#]*)/g, (_, key, value) => {
result[decodeURIComponent(key)] = decodeURIComponent(value); result[decodeURIComponent(key)] = decodeURIComponent(value);
}); });
@ -126,7 +137,7 @@ function parseQueryRegex(url) {
} }
// 示例: "https://example.com?a=1&b=2&a=3" // 示例: "https://example.com?a=1&b=2&a=3"
// => { a: ['1', '3'], b: '2' } // => { a: ['1', '3'], b: '2' } // a 有两个值
``` ```
--- ---
@ -138,23 +149,23 @@ function parseQueryRegex(url) {
```js ```js
function findCommonAncestor(node1, node2) { function findCommonAncestor(node1, node2) {
const ancestors = new Set(); const ancestors = new Set(); // 存储 node1 的所有祖先
// 收集 node1 的所有祖先 // 第一步:收集 node1 的所有祖先节点
let current = node1; let current = node1;
while (current) { while (current) {
ancestors.add(current); ancestors.add(current);
current = current.parentNode; current = current.parentNode;
} }
// 查找 node2 的祖先中第一个在 ancestors 中的节点 // 第二步:遍历 node2 的祖先,找第一个在 ancestors 中的
current = node2; current = node2;
while (current) { while (current) {
if (ancestors.has(current)) return current; if (ancestors.has(current)) return current; // 找到公共祖先
current = current.parentNode; current = current.parentNode;
} }
return null; return null; // 无公共祖先
} }
// 原生方法(现代浏览器) // 原生方法(现代浏览器)
@ -169,31 +180,32 @@ function findCommonAncestor(node1, node2) {
> **解决**:首屏加载慢、一次性加载大量数据卡顿的问题。 > **解决**:首屏加载慢、一次性加载大量数据卡顿的问题。
```js ```js
// IntersectionObserver 实现懒加载 // 使用 IntersectionObserver 实现懒加载
function lazyLoad(selector) { function lazyLoad(selector) {
const observer = new IntersectionObserver((entries) => { const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => { entries.forEach(entry => {
if (entry.isIntersecting) { if (entry.isIntersecting) { // 元素进入可视区域
const img = entry.target; const img = entry.target;
img.src = img.dataset.src; img.src = img.dataset.src; // 将 data-src 赋值给 src
observer.unobserve(img); observer.unobserve(img); // 停止观察该元素
} }
}); });
}); });
// 观察所有带指定选择器的图片
document.querySelectorAll(selector).forEach(img => observer.observe(img)); document.querySelectorAll(selector).forEach(img => observer.observe(img));
} }
// 无限滚动 // 无限滚动:滚动到底部时加载更多
function infiniteScroll(container, loadMore) { function infiniteScroll(container, loadMore) {
const observer = new IntersectionObserver(([entry]) => { const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) loadMore(); if (entry.isIntersecting) loadMore(); // 触底则加载更多
}); });
// 监听底部占位元素 // 创建底部哨兵元素
const sentinel = document.createElement('div'); const sentinel = document.createElement('div');
container.appendChild(sentinel); container.appendChild(sentinel);
observer.observe(sentinel); observer.observe(sentinel); // 观察哨兵元素
} }
``` ```
@ -205,24 +217,28 @@ function infiniteScroll(container, loadMore) {
> **解决**:内存有限时如何淘汰最久未使用的数据,保留热点数据。 > **解决**:内存有限时如何淘汰最久未使用的数据,保留热点数据。
```js ```js
// LRU (Least Recently Used) 最近最少使用缓存
// 利用 Map 的有序性:插入顺序即为访问顺序
class LRUCache { class LRUCache {
constructor(capacity) { constructor(capacity) {
this.capacity = capacity; this.capacity = capacity; // 最大容量
this.cache = new Map(); this.cache = new Map(); // Map 保持插入顺序
} }
get(key) { get(key) {
if (!this.cache.has(key)) return -1; if (!this.cache.has(key)) return -1;
const value = this.cache.get(key); const value = this.cache.get(key);
// 访问后移动到最后(标记为最近使用)
this.cache.delete(key); this.cache.delete(key);
this.cache.set(key, value); this.cache.set(key, value);
return value; return value;
} }
put(key, value) { put(key, value) {
if (this.cache.has(key)) this.cache.delete(key); if (this.cache.has(key)) this.cache.delete(key); // 已存在则先删除
this.cache.set(key, value); this.cache.set(key, value); // 重新插入到最后
if (this.cache.size > this.capacity) { if (this.cache.size > this.capacity) {
// 超出容量删除最早插入的Map 的第一个 key
this.cache.delete(this.cache.keys().next().value); this.cache.delete(this.cache.keys().next().value);
} }
} }
@ -237,19 +253,21 @@ class LRUCache {
> **解决**JS 数字最大安全整数 2^53-1 限制,大数运算精度丢失问题。 > **解决**JS 数字最大安全整数 2^53-1 限制,大数运算精度丢失问题。
```js ```js
// 模拟竖式加法,从个位开始相加
function addBigNumbers(a, b) { function addBigNumbers(a, b) {
const maxLen = Math.max(a.length, b.length); const maxLen = Math.max(a.length, b.length);
// 对齐长度,前面补 0
a = a.padStart(maxLen, '0'); a = a.padStart(maxLen, '0');
b = b.padStart(maxLen, '0'); b = b.padStart(maxLen, '0');
let carry = 0, result = ''; let carry = 0, result = ''; // carry 进位
for (let i = maxLen - 1; i >= 0; i--) { for (let i = maxLen - 1; i >= 0; i--) {
const sum = +a[i] + +b[i] + carry; const sum = +a[i] + +b[i] + carry; // 当前位相加
result = (sum % 10) + result; result = (sum % 10) + result; // 取个位数
carry = Math.floor(sum / 10); carry = Math.floor(sum / 10); // 计算进位
} }
return carry ? carry + result : result; return carry ? carry + result : result; // 最高位有进位则补上
} }
// 示例: addBigNumbers("12345678901234567890", "98765432109876543210") // 示例: addBigNumbers("12345678901234567890", "98765432109876543210")

View File

@ -9,15 +9,15 @@
```js ```js
function diff(oldVNode, newVNode) { function diff(oldVNode, newVNode) {
const patches = []; const patches = []; // 存储所有差异
// 节点不存在 // 节点不存在,删除老节点
if (!newVNode) { if (!newVNode) {
patches.push({ type: 'REMOVE' }); patches.push({ type: 'REMOVE' });
return patches; return patches;
} }
// 类型不同,替换节点 // 类型不同或文本不同或标签不同直接替换
if (typeof oldVNode !== typeof newVNode || if (typeof oldVNode !== typeof newVNode ||
(typeof oldVNode === 'string' && oldVNode !== newVNode) || (typeof oldVNode === 'string' && oldVNode !== newVNode) ||
oldVNode.tag !== newVNode.tag) { oldVNode.tag !== newVNode.tag) {
@ -25,7 +25,7 @@ function diff(oldVNode, newVNode) {
return patches; return patches;
} }
// 比较属性 // 比较属性差异
if (newVNode.props) { if (newVNode.props) {
const propsPatches = diffProps(oldVNode.props || {}, newVNode.props); const propsPatches = diffProps(oldVNode.props || {}, newVNode.props);
if (Object.keys(propsPatches).length) { if (Object.keys(propsPatches).length) {
@ -33,32 +33,35 @@ function diff(oldVNode, newVNode) {
} }
} }
// 比较子节点 // 递归比较子节点
diffChildren(oldVNode.children || [], newVNode.children || [], patches); diffChildren(oldVNode.children || [], newVNode.children || [], patches);
return patches; return patches;
} }
// 属性对比
function diffProps(oldProps, newProps) { function diffProps(oldProps, newProps) {
const patches = {}; const patches = {};
// 新增或修改的属性 // 检查新增或修改的属性
for (const key in newProps) { for (const key in newProps) {
if (newProps[key] !== oldProps[key]) { if (newProps[key] !== oldProps[key]) {
patches[key] = newProps[key]; patches[key] = newProps[key];
} }
} }
// 删除的属性 // 检查删除的属性
for (const key in oldProps) { for (const key in oldProps) {
if (!(key in newProps)) { if (!(key in newProps)) {
patches[key] = undefined; patches[key] = undefined; // 标记为删除
} }
} }
return patches; return patches;
} }
// 子节点对比
function diffChildren(oldChildren, newChildren, patches) { function diffChildren(oldChildren, newChildren, patches) {
const len = Math.max(oldChildren.length, newChildren.length); const len = Math.max(oldChildren.length, newChildren.length);
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
// 递归对比每个子节点
const childPatches = diff(oldChildren[i], newChildren[i]); const childPatches = diff(oldChildren[i], newChildren[i]);
if (childPatches.length) { if (childPatches.length) {
patches.push({ type: 'CHILDREN', index: i, patches: childPatches }); patches.push({ type: 'CHILDREN', index: i, patches: childPatches });
@ -77,34 +80,37 @@ function diffChildren(oldChildren, newChildren, patches) {
### Vue 2 (Object.defineProperty) ### Vue 2 (Object.defineProperty)
```js ```js
// Vue 2 使用 Object.defineProperty 劫持 getter/setter
function observe(obj) { function observe(obj) {
if (typeof obj !== 'object' || obj === null) return; if (typeof obj !== 'object' || obj === null) return;
Object.keys(obj).forEach(key => { Object.keys(obj).forEach(key => {
let value = obj[key]; let value = obj[key];
const dep = new Set(); const dep = new Set(); // 依赖收集器,存储依赖该属性的效果函数
observe(value); // 递归处理嵌套对象 observe(value); // 递归处理嵌套对象
Object.defineProperty(obj, key, { Object.defineProperty(obj, key, {
get() { get() {
// 读取时收集依赖
if (currentEffect) dep.add(currentEffect); if (currentEffect) dep.add(currentEffect);
return value; return value;
}, },
set(newVal) { set(newVal) {
if (newVal === value) return; if (newVal === value) return; // 值未变化则不触发
value = newVal; value = newVal;
observe(newVal); observe(newVal); // 新值也要响应式化
dep.forEach(fn => fn()); dep.forEach(fn => fn()); // 触发所有依赖更新
} }
}); });
}); });
} }
let currentEffect = null; let currentEffect = null;
// 注册副作用函数,自动追踪依赖
function watchEffect(fn) { function watchEffect(fn) {
currentEffect = fn; currentEffect = fn; // 设置当前执行的效果函数
fn(); fn(); // 执行时触发 getter完成依赖收集
currentEffect = null; currentEffect = null;
} }
``` ```
@ -112,42 +118,48 @@ function watchEffect(fn) {
### Vue 3 (Proxy) ### Vue 3 (Proxy)
```js ```js
// Vue 3 使用 Proxy 代理对象
function reactive(obj) { function reactive(obj) {
return new Proxy(obj, { return new Proxy(obj, {
get(target, key, receiver) { get(target, key, receiver) {
track(target, key); track(target, key); // 收集依赖
const result = Reflect.get(target, key, receiver); const result = Reflect.get(target, key, receiver);
// 嵌套对象也要转为响应式
return typeof result === 'object' ? reactive(result) : result; return typeof result === 'object' ? reactive(result) : result;
}, },
set(target, key, value, receiver) { set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver); const result = Reflect.set(target, key, value, receiver);
trigger(target, key); trigger(target, key); // 触发更新
return result; return result;
} }
}); });
} }
// 依赖存储结构WeakMap<target, Map<key, Set<effect>>>
const targetMap = new WeakMap(); const targetMap = new WeakMap();
let activeEffect = null; let activeEffect = null;
// 收集依赖
function track(target, key) { function track(target, key) {
if (!activeEffect) return; if (!activeEffect) return;
let depsMap = targetMap.get(target); let depsMap = targetMap.get(target);
if (!depsMap) targetMap.set(target, (depsMap = new Map())); if (!depsMap) targetMap.set(target, (depsMap = new Map()));
let dep = depsMap.get(key); let dep = depsMap.get(key);
if (!dep) depsMap.set(key, (dep = new Set())); if (!dep) depsMap.set(key, (dep = new Set()));
dep.add(activeEffect); dep.add(activeEffect); // 将当前效果函数加入依赖集合
} }
// 触发更新
function trigger(target, key) { function trigger(target, key) {
const depsMap = targetMap.get(target); const depsMap = targetMap.get(target);
if (!depsMap) return; if (!depsMap) return;
depsMap.get(key)?.forEach(effect => effect()); depsMap.get(key)?.forEach(effect => effect()); // 执行所有依赖的效果函数
} }
// 注册效果函数
function effect(fn) { function effect(fn) {
activeEffect = fn; activeEffect = fn;
fn(); fn(); // 执行时触发 getter完成依赖收集
activeEffect = null; activeEffect = null;
} }
``` ```
@ -160,26 +172,29 @@ function effect(fn) {
> **解决**:函数组件无状态,通过闭包和数组模拟类组件的状态能力。 > **解决**:函数组件无状态,通过闭包和数组模拟类组件的状态能力。
```js ```js
let state = []; let state = []; // 全局状态数组
let stateIndex = 0; let stateIndex = 0; // 当前状态索引
function useState(initialValue) { function useState(initialValue) {
const currentIndex = stateIndex; const currentIndex = stateIndex; // 闭包保存当前索引
// 首次调用时初始化
state[currentIndex] = state[currentIndex] ?? initialValue; state[currentIndex] = state[currentIndex] ?? initialValue;
const setState = (newValue) => { const setState = (newValue) => {
// 支持函数式更新setState(prev => prev + 1)
state[currentIndex] = typeof newValue === 'function' state[currentIndex] = typeof newValue === 'function'
? newValue(state[currentIndex]) ? newValue(state[currentIndex])
: newValue; : newValue;
render(); // 触发重新渲染 render(); // 触发重新渲染
}; };
stateIndex++; stateIndex++; // 下一个 useState 使用下一个索引
return [state[currentIndex], setState]; return [state[currentIndex], setState];
} }
// 渲染函数:重置索引,重新执行组件
function render() { function render() {
stateIndex = 0; // 重置索引 stateIndex = 0; // 重置索引,这就是为什么 Hooks 不能在条件语句中调用
// 调用组件函数... // 调用组件函数...
} }
``` ```
@ -192,23 +207,24 @@ function render() {
> **解决**:函数组件中处理生命周期和副作用,替代类组件的 componentDidMount 等。 > **解决**:函数组件中处理生命周期和副作用,替代类组件的 componentDidMount 等。
```js ```js
let effectIndex = 0; let effectIndex = 0; // 当前 effect 索引
let effects = []; let effects = []; // 存储所有 effect 信息
function useEffect(callback, deps) { function useEffect(callback, deps) {
const currentIndex = effectIndex; const currentIndex = effectIndex;
const prevDeps = effects[currentIndex]?.deps; const prevDeps = effects[currentIndex]?.deps; // 上次的依赖数组
// 判断依赖是否变化
const hasChanged = !prevDeps || const hasChanged = !prevDeps ||
deps.some((dep, i) => !Object.is(dep, prevDeps[i])); deps.some((dep, i) => !Object.is(dep, prevDeps[i]));
if (hasChanged) { if (hasChanged) {
// 执行清理函数 // 执行上次的清理函数
effects[currentIndex]?.cleanup?.(); effects[currentIndex]?.cleanup?.();
// 延迟执行 effect // 异步执行 effect模拟 React 的行为)
Promise.resolve().then(() => { Promise.resolve().then(() => {
const cleanup = callback(); const cleanup = callback(); // 执行 effect返回清理函数
effects[currentIndex] = { deps, cleanup }; effects[currentIndex] = { deps, cleanup };
}); });
} }
@ -226,35 +242,39 @@ function useEffect(callback, deps) {
```js ```js
function createStore(reducer) { function createStore(reducer) {
let state; let state; // 应用状态
const listeners = []; const listeners = []; // 订阅的监听器列表
// 获取当前状态
const getState = () => state; const getState = () => state;
// 发起 action更新状态
const dispatch = (action) => { const dispatch = (action) => {
state = reducer(state, action); state = reducer(state, action); // 通过 reducer 计算新状态
listeners.forEach(listener => listener()); listeners.forEach(listener => listener()); // 通知所有订阅者
}; };
// 订阅状态变化
const subscribe = (listener) => { const subscribe = (listener) => {
listeners.push(listener); listeners.push(listener);
// 返回取消订阅函数
return () => { return () => {
const index = listeners.indexOf(listener); const index = listeners.indexOf(listener);
listeners.splice(index, 1); listeners.splice(index, 1);
}; };
}; };
dispatch({ type: '@@INIT' }); // 初始化 state dispatch({ type: '@@INIT' }); // 初始化 state
return { getState, dispatch, subscribe }; return { getState, dispatch, subscribe };
} }
// 使用示例 // 使用示例:计数器 reducer
const reducer = (state = { count: 0 }, action) => { const reducer = (state = { count: 0 }, action) => {
switch (action.type) { switch (action.type) {
case 'INCREMENT': return { count: state.count + 1 }; case 'INCREMENT': return { count: state.count + 1 };
case 'DECREMENT': return { count: state.count - 1 }; case 'DECREMENT': return { count: state.count - 1 };
default: return state; default: return state; // 未知 action 返回原状态
} }
}; };
``` ```

View File

@ -11,17 +11,18 @@
function lazyLoadImages() { function lazyLoadImages() {
const observer = new IntersectionObserver((entries, observer) => { const observer = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => { entries.forEach(entry => {
if (entry.isIntersecting) { if (entry.isIntersecting) { // 图片进入可视区域
const img = entry.target; const img = entry.target;
img.src = img.dataset.src; img.src = img.dataset.src; // 加载真实图片
img.classList.remove('lazy'); img.classList.remove('lazy');
observer.unobserve(img); observer.unobserve(img); // 加载完成后停止观察
} }
}); });
}, { }, {
rootMargin: '50px' // 提前50px开始加载 rootMargin: '50px' // 提前 50px 开始加载,优化体验
}); });
// 观察所有带 lazy 类的图片
document.querySelectorAll('img.lazy').forEach(img => observer.observe(img)); document.querySelectorAll('img.lazy').forEach(img => observer.observe(img));
} }
@ -34,17 +35,19 @@ function lazyLoadImages() {
function lazyLoadScroll() { function lazyLoadScroll() {
const lazyImages = document.querySelectorAll('img.lazy'); const lazyImages = document.querySelectorAll('img.lazy');
// 节流处理,避免频繁触发
const loadImage = throttle(() => { const loadImage = throttle(() => {
lazyImages.forEach(img => { lazyImages.forEach(img => {
// 图片顶部进入视口下方 100px 范围内
if (img.getBoundingClientRect().top < window.innerHeight + 100) { if (img.getBoundingClientRect().top < window.innerHeight + 100) {
img.src = img.dataset.src; img.src = img.dataset.src;
img.classList.remove('lazy'); img.classList.remove('lazy');
} }
}); });
}, 200); }, 200); // 每 200ms 最多执行一次
window.addEventListener('scroll', loadImage); window.addEventListener('scroll', loadImage);
loadImage(); // 初始加载 loadImage(); // 初始加载可见的图片
} }
``` ```
@ -134,16 +137,17 @@ new VirtualList({
> **解决**:滚动事件每秒触发数十次,频繁执行回调导致页面卡顿。 > **解决**:滚动事件每秒触发数十次,频繁执行回调导致页面卡顿。
```js ```js
// 节流函数 // 节流函数:确保最后一次触发也会执行
function throttle(fn, delay) { function throttle(fn, delay) {
let lastTime = 0; let lastTime = 0; // 上次执行时间
let timer = null; let timer = null; // 定时器 ID
return function(...args) { return function(...args) {
const now = Date.now(); const now = Date.now();
const remaining = delay - (now - lastTime); const remaining = delay - (now - lastTime); // 剩余等待时间
if (remaining <= 0) { if (remaining <= 0) {
// 已超过等待时间,立即执行
if (timer) { if (timer) {
clearTimeout(timer); clearTimeout(timer);
timer = null; timer = null;
@ -151,6 +155,7 @@ function throttle(fn, delay) {
lastTime = now; lastTime = now;
fn.apply(this, args); fn.apply(this, args);
} else if (!timer) { } else if (!timer) {
// 未到时间且没有定时器,设置定时器保证最后一次执行
timer = setTimeout(() => { timer = setTimeout(() => {
lastTime = Date.now(); lastTime = Date.now();
timer = null; timer = null;
@ -160,11 +165,11 @@ function throttle(fn, delay) {
}; };
} }
// 应用场景 // 应用场景示例
window.addEventListener('scroll', throttle(() => { window.addEventListener('scroll', throttle(() => {
console.log('滚动位置:', window.scrollY); console.log('滚动位置:', window.scrollY);
// 吸顶效果、返回顶部按钮显示等 // 吸顶效果、返回顶部按钮显示等
}, 100)); }, 100)); // 每 100ms 最多执行一次
window.addEventListener('resize', throttle(() => { window.addEventListener('resize', throttle(() => {
console.log('窗口大小:', window.innerWidth, window.innerHeight); console.log('窗口大小:', window.innerWidth, window.innerHeight);
@ -179,31 +184,31 @@ window.addEventListener('resize', throttle(() => {
> **解决**setTimeout/setInterval 动画帧率不稳定rAF 与屏幕刷新率同步,动画更流畅。 > **解决**setTimeout/setInterval 动画帧率不稳定rAF 与屏幕刷新率同步,动画更流畅。
```js ```js
// 使用 rAF 实现平滑滚动 // 使用 requestAnimationFrame 实现平滑滚动
function smoothScrollTo(target, duration = 500) { function smoothScrollTo(target, duration = 500) {
const start = window.scrollY; const start = window.scrollY; // 起始位置
const distance = target - start; const distance = target - start; // 滚动距离
const startTime = performance.now(); const startTime = performance.now(); // 高精度起始时间
function step(currentTime) { function step(currentTime) {
const elapsed = currentTime - startTime; const elapsed = currentTime - startTime; // 已经过的时间
const progress = Math.min(elapsed / duration, 1); const progress = Math.min(elapsed / duration, 1); // 进度 0-1
// 缓动函数 // 缓动函数easeOutCubic动画先快后慢
const easeProgress = 1 - Math.pow(1 - progress, 3); const easeProgress = 1 - Math.pow(1 - progress, 3);
window.scrollTo(0, start + distance * easeProgress); window.scrollTo(0, start + distance * easeProgress);
if (progress < 1) { if (progress < 1) {
requestAnimationFrame(step); requestAnimationFrame(step); // 继续下一帧
} }
} }
requestAnimationFrame(step); requestAnimationFrame(step); // 开始动画
} }
// rAF 节流 // rAF 节流:与屏幕刷新率同步
function rafThrottle(fn) { function rafThrottle(fn) {
let ticking = false; let ticking = false; // 是否已请求下一帧
return function(...args) { return function(...args) {
if (!ticking) { if (!ticking) {
requestAnimationFrame(() => { requestAnimationFrame(() => {
@ -224,20 +229,23 @@ function rafThrottle(fn) {
> **解决**耗时JS计算阻塞主线程导致页面卡顿Worker 在后台线程执行不影响UI。 > **解决**耗时JS计算阻塞主线程导致页面卡顿Worker 在后台线程执行不影响UI。
```js ```js
// main.js // 主线程 (main.js)
const worker = new Worker('worker.js'); const worker = new Worker('worker.js'); // 创建后台线程
// 发送数据给 Worker
worker.postMessage({ type: 'heavyTask', data: largeArray }); worker.postMessage({ type: 'heavyTask', data: largeArray });
// 接收 Worker 返回的结果
worker.onmessage = (e) => { worker.onmessage = (e) => {
console.log('处理结果:', e.data); console.log('处理结果:', e.data);
}; };
// worker.js // Worker 线程 (worker.js)
self.onmessage = (e) => { self.onmessage = (e) => {
if (e.data.type === 'heavyTask') { if (e.data.type === 'heavyTask') {
// 在后台线程执行耗时计算,不影响主线程 UI
const result = e.data.data.map(item => /* 耗时计算 */); const result = e.data.data.map(item => /* 耗时计算 */);
self.postMessage(result); self.postMessage(result); // 返回结果给主线程
} }
}; };
``` ```
@ -250,6 +258,7 @@ self.onmessage = (e) => {
> **解决**长任务占据主线程超过50ms导致用户交互无响应分片执行保证响应性。 > **解决**长任务占据主线程超过50ms导致用户交互无响应分片执行保证响应性。
```js ```js
// 时间切片:将长任务分成小块执行
async function timeSlice(tasks, chunkSize = 5) { async function timeSlice(tasks, chunkSize = 5) {
const results = []; const results = [];
@ -261,27 +270,28 @@ async function timeSlice(tasks, chunkSize = 5) {
results.push(task()); results.push(task());
} }
// 让出主线程 // 让出主线程,让浏览器有机会处理用户交互
await new Promise(resolve => setTimeout(resolve, 0)); await new Promise(resolve => setTimeout(resolve, 0));
// 或使用 requestIdleCallback
} }
return results; return results;
} }
// 使用 requestIdleCallback // 使用 requestIdleCallback:在浏览器空闲时执行
function processInIdle(tasks) { function processInIdle(tasks) {
let index = 0; let index = 0;
function work(deadline) { function work(deadline) {
// 当前帧还有剩余时间时执行任务
while (index < tasks.length && deadline.timeRemaining() > 0) { while (index < tasks.length && deadline.timeRemaining() > 0) {
tasks[index++](); tasks[index++]();
} }
// 如果还有任务,等待下次空闲时继续
if (index < tasks.length) { if (index < tasks.length) {
requestIdleCallback(work); requestIdleCallback(work);
} }
} }
requestIdleCallback(work); requestIdleCallback(work); // 开始执行
} }
``` ```