diff --git a/algorithm/01-js-basics.md b/algorithm/01-js-basics.md index 160238c..65ed27f 100644 --- a/algorithm/01-js-basics.md +++ b/algorithm/01-js-basics.md @@ -8,39 +8,45 @@ ```js class MyPromise { constructor(executor) { - this.state = 'pending'; - this.value = undefined; - this.callbacks = []; + this.state = 'pending'; // 初始状态为 pending + this.value = undefined; // 存储成功值或失败原因 + this.callbacks = []; // 存储 then 注册的回调函数 + // 成功时调用,将状态改为 fulfilled const resolve = (value) => { - if (this.state !== 'pending') return; + if (this.state !== 'pending') return; // 状态只能改变一次 this.state = 'fulfilled'; this.value = value; - this.callbacks.forEach(cb => cb.onFulfilled(value)); + this.callbacks.forEach(cb => cb.onFulfilled(value)); // 执行所有成功回调 }; + // 失败时调用,将状态改为 rejected const reject = (reason) => { if (this.state !== 'pending') return; this.state = 'rejected'; this.value = reason; - this.callbacks.forEach(cb => cb.onRejected(reason)); + this.callbacks.forEach(cb => cb.onRejected(reason)); // 执行所有失败回调 }; + // 立即执行 executor,捕获异常则 reject try { executor(resolve, reject); } catch (e) { reject(e); } } then(onFulfilled, onRejected) { + // 返回新 Promise 实现链式调用 return new MyPromise((resolve, reject) => { const handle = (callback, fallback) => { try { - const result = (callback || fallback)(this.value); + const result = (callback || fallback)(this.value); // 执行回调 + // 如果返回值是 Promise,等待其完成 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); else if (this.state === 'rejected') handle(onRejected, e => { throw e; }); - else this.callbacks.push({ + else this.callbacks.push({ // pending 状态先存储回调 onFulfilled: () => handle(onFulfilled, v => v), onRejected: () => handle(onRejected, e => { throw e; }) }); @@ -55,23 +61,24 @@ class MyPromise { > **解决**:多异步任务协调与超时兜底问题。 ```js -// Promise.all - 所有成功才成功 +// Promise.all - 所有成功才成功,一个失败则立即失败 Promise.myAll = (promises) => { return new Promise((resolve, reject) => { - const results = []; - let count = 0; + const results = []; // 存储所有结果 + let count = 0; // 记录完成数量 promises.forEach((p, i) => { Promise.resolve(p).then(val => { - results[i] = val; - if (++count === promises.length) resolve(results); - }, reject); + results[i] = val; // 按索引存储,保证顺序 + if (++count === promises.length) resolve(results); // 全部完成则 resolve + }, reject); // 任一失败立即 reject }); }); }; -// Promise.race - 第一个完成就返回 +// Promise.race - 第一个完成就返回(无论成功失败) Promise.myRace = (promises) => { return new Promise((resolve, reject) => { + // 第一个 resolve/reject 的结果会被采纳,后续的会被忽略 promises.forEach(p => Promise.resolve(p).then(resolve, reject)); }); }; @@ -83,21 +90,21 @@ Promise.myRace = (promises) => { > **解决**:高频事件触发导致的性能问题和重复请求。 ```js -// 防抖:停止触发后执行 +// 防抖:停止触发后才执行(适合搜索框输入) function debounce(fn, delay) { - let timer = null; + let timer = null; // 保存定时器 ID return function(...args) { - clearTimeout(timer); - timer = setTimeout(() => fn.apply(this, args), delay); + clearTimeout(timer); // 每次触发都清除上一个定时器 + timer = setTimeout(() => fn.apply(this, args), delay); // 重新设定定时器 }; } -// 节流:固定间隔执行 +// 节流:固定间隔执行(适合滚动事件) function throttle(fn, delay) { - let last = 0; + let last = 0; // 记录上次执行时间 return function(...args) { const now = Date.now(); - if (now - last >= delay) { + if (now - last >= delay) { // 距离上次执行超过 delay 才执行 last = now; fn.apply(this, args); } @@ -112,17 +119,22 @@ function throttle(fn, delay) { ```js function deepClone(obj, map = new WeakMap()) { + // 基本类型和 null 直接返回 if (obj === null || typeof obj !== 'object') return obj; + // 特殊对象处理 if (obj instanceof Date) return new Date(obj); if (obj instanceof RegExp) return new RegExp(obj); - if (map.has(obj)) return map.get(obj); // 处理循环引用 + // 处理循环引用:如果已拷贝过,直接返回缓存 + if (map.has(obj)) return map.get(obj); + // 创建新对象/数组 const clone = Array.isArray(obj) ? [] : {}; - map.set(obj, clone); + map.set(obj, clone); // 先存入 map,防止循环引用 + // 递归拷贝每个属性 for (const key in obj) { - if (obj.hasOwnProperty(key)) { + if (obj.hasOwnProperty(key)) { // 只拷贝自身属性 clone[key] = deepClone(obj[key], map); } } @@ -138,9 +150,11 @@ function deepClone(obj, map = new WeakMap()) { ```js function curry(fn) { return function curried(...args) { + // 如果参数数量足够,直接执行原函数 if (args.length >= fn.length) { return fn.apply(this, args); } + // 参数不够,返回新函数继续收集参数 return (...nextArgs) => curried(...args, ...nextArgs); }; } @@ -148,8 +162,8 @@ function curry(fn) { // 使用示例 const add = (a, b, c) => a + b + c; 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 实现 @@ -158,30 +172,31 @@ curriedAdd(1, 2)(3); // 6 > **解决**:this 指向问题,实现函数借用和预设参数。 ```js -// call +// call - 立即执行,参数逐个传入 Function.prototype.myCall = function(ctx, ...args) { - ctx = ctx || window; - const key = Symbol(); - ctx[key] = this; - const result = ctx[key](...args); - delete ctx[key]; + ctx = ctx || window; // ctx 为 null/undefined 时指向 window + const key = Symbol(); // 用 Symbol 避免属性名冲突 + ctx[key] = this; // 将函数作为 ctx 的方法 + const result = ctx[key](...args); // 调用,此时 this 指向 ctx + delete ctx[key]; // 清理临时属性 return result; }; -// apply +// apply - 立即执行,参数以数组传入 Function.prototype.myApply = function(ctx, args = []) { ctx = ctx || window; const key = Symbol(); ctx[key] = this; - const result = ctx[key](...args); + const result = ctx[key](...args); // 展开数组参数 delete ctx[key]; return result; }; -// bind +// bind - 返回新函数,不立即执行 Function.prototype.myBind = function(ctx, ...args) { - const fn = this; + const fn = this; // 保存原函数 return function(...newArgs) { + // 合并预设参数和新参数 return fn.apply(ctx, [...args, ...newArgs]); }; }; @@ -195,30 +210,36 @@ Function.prototype.myBind = function(ctx, ...args) { ```js class EventEmitter { constructor() { - this.events = {}; + this.events = {}; // 存储事件名 -> 监听器数组的映射 } + // 订阅事件 on(event, listener) { - (this.events[event] ||= []).push(listener); - return this; + (this.events[event] ||= []).push(listener); // 添加监听器 + return this; // 返回 this 支持链式调用 } + // 取消订阅 off(event, listener) { if (this.events[event]) { + // 过滤掉指定的监听器 this.events[event] = this.events[event].filter(l => l !== listener); } return this; } + // 触发事件 emit(event, ...args) { + // 执行该事件的所有监听器 (this.events[event] || []).forEach(listener => listener(...args)); return this; } + // 一次性订阅,触发后自动取消 once(event, listener) { const wrapper = (...args) => { - listener(...args); - this.off(event, wrapper); + listener(...args); // 执行原始监听器 + this.off(event, wrapper); // 立即取消订阅 }; return this.on(event, wrapper); } diff --git a/algorithm/02-array-string.md b/algorithm/02-array-string.md index a7b4ff9..3f7252b 100644 --- a/algorithm/02-array-string.md +++ b/algorithm/02-array-string.md @@ -6,20 +6,20 @@ > **解决**:数据列表中存在重复项影响展示或数据处理的问题。 ```js -// 方法1: Set +// 方法1: Set 自动去重,最简洁 const unique1 = arr => [...new Set(arr)]; -// 方法2: filter + indexOf +// 方法2: filter + indexOf,只保留第一次出现的元素 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], []); // 方法4: 对象数组按属性去重 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)); - return [...map.values()]; + return [...map.values()]; // 返回去重后的值 }; ``` @@ -29,28 +29,30 @@ const uniqueByKey = (arr, key) => { > **解决**:嵌套数组不便于遍历、查找和渲染的问题。 ```js -// 递归实现 +// 递归实现:支持指定展开深度 function flatten(arr, depth = Infinity) { return arr.reduce((acc, val) => { + // 如果是数组且深度>0,递归展开 return acc.concat( Array.isArray(val) && depth > 0 ? flatten(val, depth - 1) : val ); }, []); } -// 迭代实现(使用栈) +// 迭代实现(使用栈):避免递归栈溢出 function flattenIterative(arr) { - const stack = [...arr]; + const stack = [...arr]; // 复制数组作为栈 const result = []; while (stack.length) { - const item = stack.pop(); + const item = stack.pop(); // 从栈顶取出 + // 是数组则展开压回栈,否则加入结果 Array.isArray(item) ? stack.push(...item) : result.unshift(item); } return result; } -// 原生方法 -arr.flat(Infinity); +// 原生方法(ES2019+) +arr.flat(Infinity); // Infinity 表示完全展开 ``` ## 3. 字符串反转 @@ -59,9 +61,10 @@ arr.flat(Infinity); > **解决**:JS 没有内置字符串反转方法,需要手工实现。 ```js +// 方法1:拆分成数组 -> 反转 -> 拼接 const reverse = str => str.split('').reverse().join(''); -// 或者使用扩展运算符 +// 方法2:使用扩展运算符(支持 Unicode 字符) const reverse2 = str => [...str].reverse().join(''); ``` @@ -71,16 +74,19 @@ const reverse2 = str => [...str].reverse().join(''); > **解决**:判断字符串正反读是否一致,常作为算法题输入校验或中间步骤。 ```js +// 方法1:清理后比较反转结果 const isPalindrome = str => { + // 转小写并去除非字母数字字符 const clean = str.toLowerCase().replace(/[^a-z0-9]/g, ''); return clean === clean.split('').reverse().join(''); }; -// 双指针法 +// 方法2:双指针法(性能更优) const isPalindrome2 = str => { const s = str.toLowerCase().replace(/[^a-z0-9]/g, ''); let left = 0, right = s.length - 1; while (left < right) { + // 从两端向中间比较 if (s[left++] !== s[right--]) return false; } return true; @@ -96,11 +102,12 @@ const isPalindrome2 = str => { function longestCommonPrefix(strs) { if (!strs.length) return ''; - let prefix = strs[0]; + let prefix = strs[0]; // 以第一个字符串为初始前缀 for (let i = 1; i < strs.length; i++) { + // 逐步缩短前缀,直到它是当前字符串的前缀 while (strs[i].indexOf(prefix) !== 0) { - prefix = prefix.slice(0, -1); - if (!prefix) return ''; + prefix = prefix.slice(0, -1); // 去掉最后一个字符 + if (!prefix) return ''; // 前缀为空,无公共前缀 } } return prefix; @@ -115,19 +122,21 @@ function longestCommonPrefix(strs) { > **解决**:将模板字符串中的占位符替换为实际数据,类似 Mustache/Handlebars 语法。 ```js +// 基础版:支持 {{key}} 语法 function render(template, data) { + // \{\{(\w+)\}\} 匹配 {{xxx}},\w+ 捕获 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) { return template.replace(/\{\{(.+?)\}\}/g, (match, path) => { - const keys = path.trim().split('.'); + const keys = path.trim().split('.'); // 按 . 拆分路径 let value = data; for (const key of keys) { - value = value?.[key]; + value = value?.[key]; // 逐层取值,可选链防止报错 if (value === undefined) return match; } return value; @@ -146,13 +155,15 @@ renderDeep('{{user.info.name}}', { user: { info: { name: 'Tom' } } }); // "Tom" ```js function uniqueAndSort(arr, key, sortKey, order = 'asc') { - // 去重 + // 第一步:按指定 key 去重 const map = new Map(); arr.forEach(item => !map.has(item[key]) && map.set(item[key], item)); - // 排序 + // 第二步:按指定 sortKey 排序 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 = [ { id: 1, name: 'Tom', age: 20 }, { 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 升序排序 ``` diff --git a/algorithm/03-data-structure.md b/algorithm/03-data-structure.md index 7d1d311..f6608fc 100644 --- a/algorithm/03-data-structure.md +++ b/algorithm/03-data-structure.md @@ -9,28 +9,29 @@ ```js function reverseList(head) { - let prev = null, curr = head; + let prev = null, curr = head; // prev 记录前一个节点 while (curr) { - const next = curr.next; - curr.next = prev; - prev = curr; - curr = next; + const next = curr.next; // 暂存下一个节点 + curr.next = prev; // 将当前节点指向前一个(反转) + prev = curr; // prev 前移 + curr = next; // curr 前移 } - return prev; + return prev; // prev 就是新的头节点 } ``` ### 环形链表判断 ```js +// 快慢指针法:如果有环,快指针一定会追上慢指针 function hasCycle(head) { - let slow = head, fast = head; - while (fast?.next) { - slow = slow.next; - fast = fast.next.next; - if (slow === fast) return true; + let slow = head, fast = head; // 两指针都从头开始 + while (fast?.next) { // fast 能继续走两步 + slow = slow.next; // 慢指针走一步 + fast = fast.next.next; // 快指针走两步 + if (slow === fast) return true; // 相遇说明有环 } - return false; + return false; // fast 走到结尾,无环 } ``` @@ -38,20 +39,20 @@ function hasCycle(head) { ```js function mergeTwoLists(l1, l2) { - const dummy = { next: null }; + const dummy = { next: null }; // 哑节点,简化处理 let curr = dummy; - while (l1 && l2) { + while (l1 && l2) { // 两个链表都有剩余节点 if (l1.val <= l2.val) { - curr.next = l1; + curr.next = l1; // 取较小的节点 l1 = l1.next; } else { curr.next = l2; l2 = l2.next; } - curr = curr.next; + curr = curr.next; // 移动当前指针 } - curr.next = l1 || l2; - return dummy.next; + curr.next = l1 || l2; // 接上剩余部分 + return dummy.next; // 返回哑节点的下一个即真正头节点 } ``` @@ -65,30 +66,30 @@ function mergeTwoLists(l1, l2) { ### 遍历(前中后序) ```js -// 前序:根-左-右 +// 前序遍历:根 -> 左 -> 右(先访问根节点) const preorder = (root, res = []) => { if (!root) return res; - res.push(root.val); - preorder(root.left, res); - preorder(root.right, res); + res.push(root.val); // 访问根 + preorder(root.left, res); // 递归左子树 + preorder(root.right, res); // 递归右子树 return res; }; -// 中序:左-根-右 +// 中序遍历:左 -> 根 -> 右(二叉搜索树得到有序数组) const inorder = (root, res = []) => { if (!root) return res; - inorder(root.left, res); - res.push(root.val); - inorder(root.right, res); + inorder(root.left, res); // 先左 + res.push(root.val); // 再根 + inorder(root.right, res); // 后右 return res; }; -// 后序:左-右-根 +// 后序遍历:左 -> 右 -> 根(常用于删除操作) const postorder = (root, res = []) => { if (!root) return res; - postorder(root.left, res); - postorder(root.right, res); - res.push(root.val); + postorder(root.left, res); // 先左 + postorder(root.right, res); // 再右 + res.push(root.val); // 最后根 return res; }; ``` @@ -96,8 +97,9 @@ const postorder = (root, res = []) => { ### 求最大深度 ```js +// 递归求最大深度:左右子树深度的较大值 + 1 const maxDepth = root => { - if (!root) return 0; + if (!root) return 0; // 空节点深度为 0 return 1 + Math.max(maxDepth(root.left), maxDepth(root.right)); }; ``` @@ -105,9 +107,12 @@ const maxDepth = root => { ### 路径和 ```js +// 判断是否存在根到叶子的路径,使得路径上所有节点值之和等于目标值 function hasPathSum(root, targetSum) { if (!root) return false; + // 叶子节点:检查剩余值是否等于当前节点值 if (!root.left && !root.right) return targetSum === root.val; + // 递归检查左右子树,目标值减去当前节点值 return hasPathSum(root.left, targetSum - root.val) || hasPathSum(root.right, targetSum - root.val); } @@ -123,14 +128,17 @@ function hasPathSum(root, targetSum) { ### 用栈实现队列 ```js +// 使用两个栈实现队列:入栈和出栈 class MyQueue { constructor() { - this.inStack = []; - this.outStack = []; + this.inStack = []; // 入队栈 + this.outStack = []; // 出队栈 } + // 入队:直接压入 inStack push(x) { this.inStack.push(x); } + // 出队:从 outStack 取,如果空则把 inStack 全部倒入 outStack pop() { if (!this.outStack.length) { while (this.inStack.length) this.outStack.push(this.inStack.pop()); @@ -138,6 +146,7 @@ class MyQueue { return this.outStack.pop(); } + // 查看队首:同 pop 逻辑,但不弹出 peek() { if (!this.outStack.length) { while (this.inStack.length) this.outStack.push(this.inStack.pop()); @@ -145,6 +154,7 @@ class MyQueue { return this.outStack[this.outStack.length - 1]; } + // 判断是否为空 empty() { return !this.inStack.length && !this.outStack.length; } } ``` @@ -153,16 +163,18 @@ class MyQueue { ```js function isValid(s) { + // 右括号到左括号的映射 const map = { ')': '(', ']': '[', '}': '{' }; const stack = []; for (const c of s) { - if (map[c]) { + if (map[c]) { // 如果是右括号 + // 栈顶必须是对应的左括号 if (stack.pop() !== map[c]) return false; } else { - stack.push(c); + stack.push(c); // 左括号入栈 } } - return !stack.length; + return !stack.length; // 栈必须为空才有效 } ``` @@ -176,12 +188,13 @@ function isValid(s) { ### 两数之和 ```js +// 用哈希表存储已遍历的数字及其索引 function twoSum(nums, target) { const map = new Map(); for (let i = 0; i < nums.length; i++) { - const diff = target - nums[i]; - if (map.has(diff)) return [map.get(diff), i]; - map.set(nums[i], i); + const diff = target - nums[i]; // 计算需要的配对数字 + if (map.has(diff)) return [map.get(diff), i]; // 找到则返回两个索引 + map.set(nums[i], i); // 存储当前数字和索引 } return []; } @@ -190,13 +203,14 @@ function twoSum(nums, target) { ### 字母异位词分组 ```js +// 异位词排序后结果相同,以此为 key 分组 function groupAnagrams(strs) { const map = new Map(); for (const s of strs) { - const key = [...s].sort().join(''); - map.set(key, (map.get(key) || []).concat(s)); + const key = [...s].sort().join(''); // 排序后作为 key + map.set(key, (map.get(key) || []).concat(s)); // 加入对应分组 } - return [...map.values()]; + return [...map.values()]; // 返回所有分组 } ``` @@ -210,12 +224,14 @@ function groupAnagrams(strs) { ### 快速排序 ```js +// 快速排序:分治思想,选取基准值分区 function quickSort(arr) { - if (arr.length <= 1) return arr; - const pivot = arr[Math.floor(arr.length / 2)]; - const left = arr.filter(x => x < pivot); - const middle = arr.filter(x => x === pivot); - const right = arr.filter(x => x > pivot); + if (arr.length <= 1) return arr; // 基准情况 + const pivot = arr[Math.floor(arr.length / 2)]; // 选取中间元素为基准值 + const left = arr.filter(x => x < pivot); // 小于基准值 + const middle = arr.filter(x => x === pivot); // 等于基准值 + const right = arr.filter(x => x > pivot); // 大于基准值 + // 递归排序左右部分,合并结果 return [...quickSort(left), ...middle, ...quickSort(right)]; } ``` @@ -223,20 +239,24 @@ function quickSort(arr) { ### 归并排序 ```js +// 归并排序:分治思想,先拆分再合并 function mergeSort(arr) { if (arr.length <= 1) return arr; const mid = Math.floor(arr.length / 2); - const left = mergeSort(arr.slice(0, mid)); - const right = mergeSort(arr.slice(mid)); - return merge(left, right); + const left = mergeSort(arr.slice(0, mid)); // 递归排序左半部分 + const right = mergeSort(arr.slice(mid)); // 递归排序右半部分 + return merge(left, right); // 合并两个有序数组 } +// 合并两个有序数组 function merge(left, right) { const result = []; let i = 0, j = 0; + // 双指针比较,取较小的元素 while (i < left.length && j < right.length) { result.push(left[i] < right[j] ? left[i++] : right[j++]); } + // 拼接剩余部分 return result.concat(left.slice(i), right.slice(j)); } ``` @@ -251,32 +271,36 @@ function merge(left, right) { ### 基础二分查找 ```js +// 标准二分查找:在有序数组中查找目标值 function binarySearch(arr, target) { let left = 0, right = arr.length - 1; while (left <= right) { - const mid = Math.floor((left + right) / 2); - if (arr[mid] === target) return mid; + const mid = Math.floor((left + right) / 2); // 计算中间位置 + if (arr[mid] === target) return mid; // 找到目标 + // 根据大小关系缩小范围 arr[mid] < target ? (left = mid + 1) : (right = mid - 1); } - return -1; + return -1; // 未找到 } ``` ### 旋转数组查找 ```js +// 旋转数组查找:数组被旋转过,如 [4,5,6,7,0,1,2] function searchRotated(nums, target) { let left = 0, right = nums.length - 1; while (left <= right) { const mid = Math.floor((left + right) / 2); 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; else left = mid + 1; - } else { - // 右半边有序 + } else { // 右半边有序 + // target 在右半边有序区间内 if (target > nums[mid] && target <= nums[right]) left = mid + 1; else right = mid - 1; } @@ -297,8 +321,8 @@ function searchRotated(nums, target) { ```js // 时间复杂度 O(2^n),存在大量重复计算 function fib(n) { - if (n <= 1) return n; - return fib(n - 1) + fib(n - 2); + if (n <= 1) return n; // 基准情况:F(0)=0, F(1)=1 + return fib(n - 1) + fib(n - 2); // 递归调用 } ``` @@ -308,8 +332,8 @@ function fib(n) { // 时间复杂度 O(n),空间复杂度 O(n) function fib(n, memo = {}) { if (n <= 1) return n; - if (memo[n]) return memo[n]; - return memo[n] = fib(n - 1, memo) + fib(n - 2, memo); + if (memo[n]) return memo[n]; // 已计算过,直接返回缓存 + return memo[n] = fib(n - 1, memo) + fib(n - 2, memo); // 计算并缓存 } ``` @@ -319,9 +343,9 @@ function fib(n, memo = {}) { // 时间复杂度 O(n),空间复杂度 O(n) function fib(n) { if (n <= 1) return n; - const dp = [0, 1]; + const dp = [0, 1]; // dp[i] 表示第 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]; } @@ -333,9 +357,9 @@ function fib(n) { // 时间复杂度 O(n),空间复杂度 O(1) function fib(n) { if (n <= 1) return n; - let prev = 0, curr = 1; + let prev = 0, curr = 1; // 只保留前两个值 for (let i = 2; i <= n; i++) { - [prev, curr] = [curr, prev + curr]; + [prev, curr] = [curr, prev + curr]; // 滚动更新 } return curr; } @@ -345,26 +369,30 @@ function fib(n) { ```js // 时间复杂度 O(log n),适合求极大位数 +// 原理:|F(n) | |1 1|^(n-1) |F(1)| +// |F(n-1)| = |1 0| * |F(0)| function fib(n) { if (n <= 1) return n; + // 2x2 矩阵乘法 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[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) => { let result = [[1, 0], [0, 1]]; // 单位矩阵 while (p > 0) { - if (p & 1) result = multiply(result, m); - m = multiply(m, m); - p >>= 1; + if (p & 1) result = multiply(result, m); // 奇数幂则乘一次 + m = multiply(m, m); // 底数平方 + p >>= 1; // 指数减半 } return result; }; const matrix = [[1, 1], [1, 0]]; - return power(matrix, n - 1)[0][0]; + return power(matrix, n - 1)[0][0]; // 结果在左上角 } ``` diff --git a/algorithm/04-practical-scenarios.md b/algorithm/04-practical-scenarios.md index 2eb4056..d4e7216 100644 --- a/algorithm/04-practical-scenarios.md +++ b/algorithm/04-practical-scenarios.md @@ -7,28 +7,30 @@ ```js async function limitConcurrency(tasks, limit) { - const results = []; - const executing = []; + const results = []; // 存储所有任务结果 + const executing = []; // 当前正在执行的任务 for (const [index, task] of tasks.entries()) { + // 包装任务为 Promise const p = Promise.resolve().then(() => task()).then(res => { - results[index] = res; - executing.splice(executing.indexOf(p), 1); + results[index] = res; // 存储结果,保证顺序 + executing.splice(executing.indexOf(p), 1); // 完成后从执行列表移除 }); executing.push(p); + // 如果达到并发上限,等待任一任务完成 if (executing.length >= limit) { await Promise.race(executing); } } - await Promise.all(executing); + await Promise.all(executing); // 等待剩余任务完成 return results; } -// 使用示例 +// 使用示例:批量请求,最多 3 个并发 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 class Scheduler { constructor(limit) { - this.limit = limit; - this.queue = []; - this.running = 0; + this.limit = limit; // 最大并发数 + this.queue = []; // 等待队列 + this.running = 0; // 当前运行中的任务数 } + // 添加任务,返回 Promise add(promiseCreator) { return new Promise((resolve, reject) => { + // 将任务和对应的 resolve/reject 存入队列 this.queue.push({ promiseCreator, resolve, reject }); - this.run(); + this.run(); // 尝试执行 }); } + // 执行队列中的任务 run() { + // 未达上限且队列有任务时,取出执行 while (this.running < this.limit && this.queue.length) { const { promiseCreator, resolve, reject } = this.queue.shift(); this.running++; promiseCreator() .then(resolve, reject) - .finally(() => { this.running--; this.run(); }); + .finally(() => { this.running--; this.run(); }); // 完成后继续执行下一个 } } } @@ -68,28 +74,30 @@ class Scheduler { > **解决**:相同输入重复计算浪费性能,用空间换时间。 ```js +// 基础版:永久缓存 function memoize(fn) { - const cache = new Map(); + const cache = new Map(); // 缓存存储 return function(...args) { - const key = JSON.stringify(args); - if (cache.has(key)) return cache.get(key); - const result = fn.apply(this, args); - cache.set(key, result); + const key = JSON.stringify(args); // 参数序列化作为 key + if (cache.has(key)) return cache.get(key); // 命中缓存 + const result = fn.apply(this, args); // 执行原函数 + cache.set(key, result); // 存入缓存 return result; }; } -// 支持过期时间 +// 进阶版:支持过期时间 TTL function memoizeWithTTL(fn, ttl = 60000) { const cache = new Map(); return function(...args) { const key = JSON.stringify(args); const cached = cache.get(key); + // 检查缓存是否存在且未过期 if (cached && Date.now() - cached.time < ttl) { return cached.value; } const result = fn.apply(this, args); - cache.set(key, { value: result, time: Date.now() }); + cache.set(key, { value: result, time: Date.now() }); // 存储值和时间戳 return result; }; } @@ -103,11 +111,13 @@ function memoizeWithTTL(fn, ttl = 60000) { > **解决**:将 URL 查询字符串转换为结构化对象,方便业务使用。 ```js +// 方法1:字符串分割法 function parseQuery(url) { - const query = url.split('?')[1] || ''; + const query = url.split('?')[1] || ''; // 取 ? 后的部分 return query.split('&').reduce((acc, pair) => { - const [key, value] = pair.split('=').map(decodeURIComponent); + const [key, value] = pair.split('=').map(decodeURIComponent); // 解码 if (key) { + // 处理重复 key:转为数组 acc[key] = acc[key] ? [].concat(acc[key], value) : value; @@ -116,9 +126,10 @@ function parseQuery(url) { }, {}); } -// 使用正则 +// 方法2:正则表达式 function parseQueryRegex(url) { const result = {}; + // 匹配 ?key=value 或 &key=value url.replace(/[?&]([^=&#]+)=([^&#]*)/g, (_, key, value) => { result[decodeURIComponent(key)] = decodeURIComponent(value); }); @@ -126,7 +137,7 @@ function parseQueryRegex(url) { } // 示例: "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 function findCommonAncestor(node1, node2) { - const ancestors = new Set(); + const ancestors = new Set(); // 存储 node1 的所有祖先 - // 收集 node1 的所有祖先 + // 第一步:收集 node1 的所有祖先节点 let current = node1; while (current) { ancestors.add(current); current = current.parentNode; } - // 查找 node2 的祖先中第一个在 ancestors 中的节点 + // 第二步:遍历 node2 的祖先,找第一个在 ancestors 中的 current = node2; while (current) { - if (ancestors.has(current)) return current; + if (ancestors.has(current)) return current; // 找到公共祖先 current = current.parentNode; } - return null; + return null; // 无公共祖先 } // 原生方法(现代浏览器) @@ -169,31 +180,32 @@ function findCommonAncestor(node1, node2) { > **解决**:首屏加载慢、一次性加载大量数据卡顿的问题。 ```js -// IntersectionObserver 实现懒加载 +// 使用 IntersectionObserver 实现懒加载 function lazyLoad(selector) { const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { - if (entry.isIntersecting) { + if (entry.isIntersecting) { // 元素进入可视区域 const img = entry.target; - img.src = img.dataset.src; - observer.unobserve(img); + img.src = img.dataset.src; // 将 data-src 赋值给 src + observer.unobserve(img); // 停止观察该元素 } }); }); + // 观察所有带指定选择器的图片 document.querySelectorAll(selector).forEach(img => observer.observe(img)); } -// 无限滚动 +// 无限滚动:滚动到底部时加载更多 function infiniteScroll(container, loadMore) { const observer = new IntersectionObserver(([entry]) => { - if (entry.isIntersecting) loadMore(); + if (entry.isIntersecting) loadMore(); // 触底则加载更多 }); - // 监听底部占位元素 + // 创建底部哨兵元素 const sentinel = document.createElement('div'); container.appendChild(sentinel); - observer.observe(sentinel); + observer.observe(sentinel); // 观察哨兵元素 } ``` @@ -205,24 +217,28 @@ function infiniteScroll(container, loadMore) { > **解决**:内存有限时如何淘汰最久未使用的数据,保留热点数据。 ```js +// LRU (Least Recently Used) 最近最少使用缓存 +// 利用 Map 的有序性:插入顺序即为访问顺序 class LRUCache { constructor(capacity) { - this.capacity = capacity; - this.cache = new Map(); + this.capacity = capacity; // 最大容量 + this.cache = new Map(); // Map 保持插入顺序 } 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; } put(key, value) { - if (this.cache.has(key)) this.cache.delete(key); - this.cache.set(key, value); + if (this.cache.has(key)) this.cache.delete(key); // 已存在则先删除 + this.cache.set(key, value); // 重新插入到最后 if (this.cache.size > this.capacity) { + // 超出容量,删除最早插入的(Map 的第一个 key) this.cache.delete(this.cache.keys().next().value); } } @@ -237,19 +253,21 @@ class LRUCache { > **解决**:JS 数字最大安全整数 2^53-1 限制,大数运算精度丢失问题。 ```js +// 模拟竖式加法,从个位开始相加 function addBigNumbers(a, b) { const maxLen = Math.max(a.length, b.length); + // 对齐长度,前面补 0 a = a.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--) { - const sum = +a[i] + +b[i] + carry; - result = (sum % 10) + result; - carry = Math.floor(sum / 10); + const sum = +a[i] + +b[i] + carry; // 当前位相加 + result = (sum % 10) + result; // 取个位数 + carry = Math.floor(sum / 10); // 计算进位 } - return carry ? carry + result : result; + return carry ? carry + result : result; // 最高位有进位则补上 } // 示例: addBigNumbers("12345678901234567890", "98765432109876543210") diff --git a/algorithm/05-framework-algorithms.md b/algorithm/05-framework-algorithms.md index 23d3239..0f22aee 100644 --- a/algorithm/05-framework-algorithms.md +++ b/algorithm/05-framework-algorithms.md @@ -9,15 +9,15 @@ ```js function diff(oldVNode, newVNode) { - const patches = []; + const patches = []; // 存储所有差异 - // 节点不存在 + // 新节点不存在,删除老节点 if (!newVNode) { patches.push({ type: 'REMOVE' }); return patches; } - // 类型不同,替换节点 + // 类型不同或文本不同或标签不同,直接替换 if (typeof oldVNode !== typeof newVNode || (typeof oldVNode === 'string' && oldVNode !== newVNode) || oldVNode.tag !== newVNode.tag) { @@ -25,7 +25,7 @@ function diff(oldVNode, newVNode) { return patches; } - // 比较属性 + // 比较属性差异 if (newVNode.props) { const propsPatches = diffProps(oldVNode.props || {}, newVNode.props); if (Object.keys(propsPatches).length) { @@ -33,32 +33,35 @@ function diff(oldVNode, newVNode) { } } - // 比较子节点 + // 递归比较子节点 diffChildren(oldVNode.children || [], newVNode.children || [], patches); return patches; } +// 属性对比 function diffProps(oldProps, newProps) { const patches = {}; - // 新增或修改的属性 + // 检查新增或修改的属性 for (const key in newProps) { if (newProps[key] !== oldProps[key]) { patches[key] = newProps[key]; } } - // 删除的属性 + // 检查删除的属性 for (const key in oldProps) { if (!(key in newProps)) { - patches[key] = undefined; + patches[key] = undefined; // 标记为删除 } } return patches; } +// 子节点对比 function diffChildren(oldChildren, newChildren, patches) { const len = Math.max(oldChildren.length, newChildren.length); for (let i = 0; i < len; i++) { + // 递归对比每个子节点 const childPatches = diff(oldChildren[i], newChildren[i]); if (childPatches.length) { patches.push({ type: 'CHILDREN', index: i, patches: childPatches }); @@ -77,34 +80,37 @@ function diffChildren(oldChildren, newChildren, patches) { ### Vue 2 (Object.defineProperty) ```js +// Vue 2 使用 Object.defineProperty 劫持 getter/setter function observe(obj) { if (typeof obj !== 'object' || obj === null) return; Object.keys(obj).forEach(key => { let value = obj[key]; - const dep = new Set(); + const dep = new Set(); // 依赖收集器,存储依赖该属性的效果函数 - observe(value); // 递归处理嵌套对象 + observe(value); // 递归处理嵌套对象 Object.defineProperty(obj, key, { get() { + // 读取时收集依赖 if (currentEffect) dep.add(currentEffect); return value; }, set(newVal) { - if (newVal === value) return; + if (newVal === value) return; // 值未变化则不触发 value = newVal; - observe(newVal); - dep.forEach(fn => fn()); + observe(newVal); // 新值也要响应式化 + dep.forEach(fn => fn()); // 触发所有依赖更新 } }); }); } let currentEffect = null; +// 注册副作用函数,自动追踪依赖 function watchEffect(fn) { - currentEffect = fn; - fn(); + currentEffect = fn; // 设置当前执行的效果函数 + fn(); // 执行时触发 getter,完成依赖收集 currentEffect = null; } ``` @@ -112,42 +118,48 @@ function watchEffect(fn) { ### Vue 3 (Proxy) ```js +// Vue 3 使用 Proxy 代理对象 function reactive(obj) { return new Proxy(obj, { get(target, key, receiver) { - track(target, key); + track(target, key); // 收集依赖 const result = Reflect.get(target, key, receiver); + // 嵌套对象也要转为响应式 return typeof result === 'object' ? reactive(result) : result; }, set(target, key, value, receiver) { const result = Reflect.set(target, key, value, receiver); - trigger(target, key); + trigger(target, key); // 触发更新 return result; } }); } +// 依赖存储结构:WeakMap>> const targetMap = new WeakMap(); let activeEffect = null; +// 收集依赖 function track(target, key) { if (!activeEffect) return; let depsMap = targetMap.get(target); if (!depsMap) targetMap.set(target, (depsMap = new Map())); let dep = depsMap.get(key); if (!dep) depsMap.set(key, (dep = new Set())); - dep.add(activeEffect); + dep.add(activeEffect); // 将当前效果函数加入依赖集合 } +// 触发更新 function trigger(target, key) { const depsMap = targetMap.get(target); if (!depsMap) return; - depsMap.get(key)?.forEach(effect => effect()); + depsMap.get(key)?.forEach(effect => effect()); // 执行所有依赖的效果函数 } +// 注册效果函数 function effect(fn) { activeEffect = fn; - fn(); + fn(); // 执行时触发 getter,完成依赖收集 activeEffect = null; } ``` @@ -160,26 +172,29 @@ function effect(fn) { > **解决**:函数组件无状态,通过闭包和数组模拟类组件的状态能力。 ```js -let state = []; -let stateIndex = 0; +let state = []; // 全局状态数组 +let stateIndex = 0; // 当前状态索引 function useState(initialValue) { - const currentIndex = stateIndex; + const currentIndex = stateIndex; // 闭包保存当前索引 + // 首次调用时初始化 state[currentIndex] = state[currentIndex] ?? initialValue; const setState = (newValue) => { + // 支持函数式更新:setState(prev => prev + 1) state[currentIndex] = typeof newValue === 'function' ? newValue(state[currentIndex]) : newValue; - render(); // 触发重新渲染 + render(); // 触发重新渲染 }; - stateIndex++; + stateIndex++; // 下一个 useState 使用下一个索引 return [state[currentIndex], setState]; } +// 渲染函数:重置索引,重新执行组件 function render() { - stateIndex = 0; // 重置索引 + stateIndex = 0; // 重置索引,这就是为什么 Hooks 不能在条件语句中调用 // 调用组件函数... } ``` @@ -192,23 +207,24 @@ function render() { > **解决**:函数组件中处理生命周期和副作用,替代类组件的 componentDidMount 等。 ```js -let effectIndex = 0; -let effects = []; +let effectIndex = 0; // 当前 effect 索引 +let effects = []; // 存储所有 effect 信息 function useEffect(callback, deps) { const currentIndex = effectIndex; - const prevDeps = effects[currentIndex]?.deps; + const prevDeps = effects[currentIndex]?.deps; // 上次的依赖数组 + // 判断依赖是否变化 const hasChanged = !prevDeps || deps.some((dep, i) => !Object.is(dep, prevDeps[i])); if (hasChanged) { - // 执行清理函数 + // 执行上次的清理函数 effects[currentIndex]?.cleanup?.(); - // 延迟执行 effect + // 异步执行 effect(模拟 React 的行为) Promise.resolve().then(() => { - const cleanup = callback(); + const cleanup = callback(); // 执行 effect,返回清理函数 effects[currentIndex] = { deps, cleanup }; }); } @@ -226,35 +242,39 @@ function useEffect(callback, deps) { ```js function createStore(reducer) { - let state; - const listeners = []; + let state; // 应用状态 + const listeners = []; // 订阅的监听器列表 + // 获取当前状态 const getState = () => state; + // 发起 action,更新状态 const dispatch = (action) => { - state = reducer(state, action); - listeners.forEach(listener => listener()); + state = reducer(state, action); // 通过 reducer 计算新状态 + listeners.forEach(listener => listener()); // 通知所有订阅者 }; + // 订阅状态变化 const subscribe = (listener) => { listeners.push(listener); + // 返回取消订阅函数 return () => { const index = listeners.indexOf(listener); listeners.splice(index, 1); }; }; - dispatch({ type: '@@INIT' }); // 初始化 state + dispatch({ type: '@@INIT' }); // 初始化 state return { getState, dispatch, subscribe }; } -// 使用示例 +// 使用示例:计数器 reducer const reducer = (state = { count: 0 }, action) => { switch (action.type) { case 'INCREMENT': return { count: state.count + 1 }; case 'DECREMENT': return { count: state.count - 1 }; - default: return state; + default: return state; // 未知 action 返回原状态 } }; ``` diff --git a/algorithm/06-performance.md b/algorithm/06-performance.md index 7024a1f..9bbde48 100644 --- a/algorithm/06-performance.md +++ b/algorithm/06-performance.md @@ -11,17 +11,18 @@ function lazyLoadImages() { const observer = new IntersectionObserver((entries, observer) => { entries.forEach(entry => { - if (entry.isIntersecting) { + if (entry.isIntersecting) { // 图片进入可视区域 const img = entry.target; - img.src = img.dataset.src; + img.src = img.dataset.src; // 加载真实图片 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)); } @@ -34,17 +35,19 @@ function lazyLoadImages() { function lazyLoadScroll() { const lazyImages = document.querySelectorAll('img.lazy'); + // 节流处理,避免频繁触发 const loadImage = throttle(() => { lazyImages.forEach(img => { + // 图片顶部进入视口下方 100px 范围内 if (img.getBoundingClientRect().top < window.innerHeight + 100) { img.src = img.dataset.src; img.classList.remove('lazy'); } }); - }, 200); + }, 200); // 每 200ms 最多执行一次 window.addEventListener('scroll', loadImage); - loadImage(); // 初始加载 + loadImage(); // 初始加载可见的图片 } ``` @@ -134,16 +137,17 @@ new VirtualList({ > **解决**:滚动事件每秒触发数十次,频繁执行回调导致页面卡顿。 ```js -// 节流函数 +// 节流函数:确保最后一次触发也会执行 function throttle(fn, delay) { - let lastTime = 0; - let timer = null; + let lastTime = 0; // 上次执行时间 + let timer = null; // 定时器 ID return function(...args) { const now = Date.now(); - const remaining = delay - (now - lastTime); + const remaining = delay - (now - lastTime); // 剩余等待时间 if (remaining <= 0) { + // 已超过等待时间,立即执行 if (timer) { clearTimeout(timer); timer = null; @@ -151,6 +155,7 @@ function throttle(fn, delay) { lastTime = now; fn.apply(this, args); } else if (!timer) { + // 未到时间且没有定时器,设置定时器保证最后一次执行 timer = setTimeout(() => { lastTime = Date.now(); timer = null; @@ -160,11 +165,11 @@ function throttle(fn, delay) { }; } -// 应用场景 +// 应用场景示例 window.addEventListener('scroll', throttle(() => { console.log('滚动位置:', window.scrollY); // 吸顶效果、返回顶部按钮显示等 -}, 100)); +}, 100)); // 每 100ms 最多执行一次 window.addEventListener('resize', throttle(() => { console.log('窗口大小:', window.innerWidth, window.innerHeight); @@ -179,31 +184,31 @@ window.addEventListener('resize', throttle(() => { > **解决**:setTimeout/setInterval 动画帧率不稳定,rAF 与屏幕刷新率同步,动画更流畅。 ```js -// 使用 rAF 实现平滑滚动 +// 使用 requestAnimationFrame 实现平滑滚动 function smoothScrollTo(target, duration = 500) { - const start = window.scrollY; - const distance = target - start; - const startTime = performance.now(); + const start = window.scrollY; // 起始位置 + const distance = target - start; // 滚动距离 + const startTime = performance.now(); // 高精度起始时间 function step(currentTime) { - const elapsed = currentTime - startTime; - const progress = Math.min(elapsed / duration, 1); + const elapsed = currentTime - startTime; // 已经过的时间 + const progress = Math.min(elapsed / duration, 1); // 进度 0-1 - // 缓动函数 + // 缓动函数:easeOutCubic,动画先快后慢 const easeProgress = 1 - Math.pow(1 - progress, 3); window.scrollTo(0, start + distance * easeProgress); if (progress < 1) { - requestAnimationFrame(step); + requestAnimationFrame(step); // 继续下一帧 } } - requestAnimationFrame(step); + requestAnimationFrame(step); // 开始动画 } -// rAF 节流 +// rAF 节流:与屏幕刷新率同步 function rafThrottle(fn) { - let ticking = false; + let ticking = false; // 是否已请求下一帧 return function(...args) { if (!ticking) { requestAnimationFrame(() => { @@ -224,20 +229,23 @@ function rafThrottle(fn) { > **解决**:耗时JS计算阻塞主线程导致页面卡顿,Worker 在后台线程执行不影响UI。 ```js -// main.js -const worker = new Worker('worker.js'); +// 主线程 (main.js) +const worker = new Worker('worker.js'); // 创建后台线程 +// 发送数据给 Worker worker.postMessage({ type: 'heavyTask', data: largeArray }); +// 接收 Worker 返回的结果 worker.onmessage = (e) => { console.log('处理结果:', e.data); }; -// worker.js +// Worker 线程 (worker.js) self.onmessage = (e) => { if (e.data.type === 'heavyTask') { + // 在后台线程执行耗时计算,不影响主线程 UI const result = e.data.data.map(item => /* 耗时计算 */); - self.postMessage(result); + self.postMessage(result); // 返回结果给主线程 } }; ``` @@ -250,6 +258,7 @@ self.onmessage = (e) => { > **解决**:长任务占据主线程超过50ms导致用户交互无响应,分片执行保证响应性。 ```js +// 时间切片:将长任务分成小块执行 async function timeSlice(tasks, chunkSize = 5) { const results = []; @@ -261,27 +270,28 @@ async function timeSlice(tasks, chunkSize = 5) { results.push(task()); } - // 让出主线程 + // 让出主线程,让浏览器有机会处理用户交互 await new Promise(resolve => setTimeout(resolve, 0)); - // 或使用 requestIdleCallback } return results; } -// 使用 requestIdleCallback +// 使用 requestIdleCallback:在浏览器空闲时执行 function processInIdle(tasks) { let index = 0; function work(deadline) { + // 当前帧还有剩余时间时执行任务 while (index < tasks.length && deadline.timeRemaining() > 0) { tasks[index++](); } + // 如果还有任务,等待下次空闲时继续 if (index < tasks.length) { requestIdleCallback(work); } } - requestIdleCallback(work); + requestIdleCallback(work); // 开始执行 } ```