diff --git a/algorithm/01-js-basics.md b/algorithm/01-js-basics.md new file mode 100644 index 0000000..b690c8c --- /dev/null +++ b/algorithm/01-js-basics.md @@ -0,0 +1,205 @@ +# JS 基础与手写实现 + +## 1. 手写 Promise + +```js +class MyPromise { + constructor(executor) { + this.state = 'pending'; + this.value = undefined; + this.callbacks = []; + + const resolve = (value) => { + if (this.state !== 'pending') return; + this.state = 'fulfilled'; + this.value = value; + this.callbacks.forEach(cb => cb.onFulfilled(value)); + }; + + const reject = (reason) => { + if (this.state !== 'pending') return; + this.state = 'rejected'; + this.value = reason; + this.callbacks.forEach(cb => cb.onRejected(reason)); + }; + + try { executor(resolve, reject); } catch (e) { reject(e); } + } + + then(onFulfilled, onRejected) { + return new MyPromise((resolve, reject) => { + const handle = (callback, fallback) => { + try { + const result = (callback || fallback)(this.value); + result instanceof MyPromise ? result.then(resolve, reject) : resolve(result); + } catch (e) { reject(e); } + }; + + if (this.state === 'fulfilled') handle(onFulfilled, v => v); + else if (this.state === 'rejected') handle(onRejected, e => { throw e; }); + else this.callbacks.push({ + onFulfilled: () => handle(onFulfilled, v => v), + onRejected: () => handle(onRejected, e => { throw e; }) + }); + }); + } +} +``` + +## 2. Promise.all / Promise.race + +```js +// Promise.all - 所有成功才成功 +Promise.myAll = (promises) => { + return new Promise((resolve, reject) => { + const results = []; + let count = 0; + promises.forEach((p, i) => { + Promise.resolve(p).then(val => { + results[i] = val; + if (++count === promises.length) resolve(results); + }, reject); + }); + }); +}; + +// Promise.race - 第一个完成就返回 +Promise.myRace = (promises) => { + return new Promise((resolve, reject) => { + promises.forEach(p => Promise.resolve(p).then(resolve, reject)); + }); +}; +``` + +## 3. 防抖与节流 + +```js +// 防抖:停止触发后执行 +function debounce(fn, delay) { + let timer = null; + return function(...args) { + clearTimeout(timer); + timer = setTimeout(() => fn.apply(this, args), delay); + }; +} + +// 节流:固定间隔执行 +function throttle(fn, delay) { + let last = 0; + return function(...args) { + const now = Date.now(); + if (now - last >= delay) { + last = now; + fn.apply(this, args); + } + }; +} +``` + +## 4. 深拷贝(处理循环引用) + +```js +function deepClone(obj, map = new WeakMap()) { + if (obj === null || typeof obj !== 'object') return obj; + if (obj instanceof Date) return new Date(obj); + if (obj instanceof RegExp) return new RegExp(obj); + + if (map.has(obj)) return map.get(obj); // 处理循环引用 + + const clone = Array.isArray(obj) ? [] : {}; + map.set(obj, clone); + + for (const key in obj) { + if (obj.hasOwnProperty(key)) { + clone[key] = deepClone(obj[key], map); + } + } + return clone; +} +``` + +## 5. 函数柯里化 + +```js +function curry(fn) { + return function curried(...args) { + if (args.length >= fn.length) { + return fn.apply(this, args); + } + return (...nextArgs) => curried(...args, ...nextArgs); + }; +} + +// 使用示例 +const add = (a, b, c) => a + b + c; +const curriedAdd = curry(add); +curriedAdd(1)(2)(3); // 6 +curriedAdd(1, 2)(3); // 6 +``` + +## 6. call / apply / bind 实现 + +```js +// call +Function.prototype.myCall = function(ctx, ...args) { + ctx = ctx || window; + const key = Symbol(); + ctx[key] = this; + const result = ctx[key](...args); + delete ctx[key]; + return result; +}; + +// apply +Function.prototype.myApply = function(ctx, args = []) { + ctx = ctx || window; + const key = Symbol(); + ctx[key] = this; + const result = ctx[key](...args); + delete ctx[key]; + return result; +}; + +// bind +Function.prototype.myBind = function(ctx, ...args) { + const fn = this; + return function(...newArgs) { + return fn.apply(ctx, [...args, ...newArgs]); + }; +}; +``` + +## 7. 事件总线 EventEmitter + +```js +class EventEmitter { + constructor() { + this.events = {}; + } + + on(event, listener) { + (this.events[event] ||= []).push(listener); + return this; + } + + off(event, listener) { + if (this.events[event]) { + this.events[event] = this.events[event].filter(l => l !== listener); + } + return this; + } + + emit(event, ...args) { + (this.events[event] || []).forEach(listener => listener(...args)); + return this; + } + + once(event, listener) { + const wrapper = (...args) => { + listener(...args); + this.off(event, wrapper); + }; + return this.on(event, wrapper); + } +} +``` diff --git a/algorithm/02-array-string.md b/algorithm/02-array-string.md new file mode 100644 index 0000000..74873f1 --- /dev/null +++ b/algorithm/02-array-string.md @@ -0,0 +1,145 @@ +# 数组与字符串操作 + +## 1. 数组去重(多种方法) + +```js +// 方法1: Set +const unique1 = arr => [...new Set(arr)]; + +// 方法2: filter + indexOf +const unique2 = arr => arr.filter((v, i) => arr.indexOf(v) === i); + +// 方法3: reduce +const unique3 = arr => arr.reduce((acc, v) => acc.includes(v) ? acc : [...acc, v], []); + +// 方法4: 对象数组按属性去重 +const uniqueByKey = (arr, key) => { + const map = new Map(); + arr.forEach(item => !map.has(item[key]) && map.set(item[key], item)); + return [...map.values()]; +}; +``` + +## 2. 扁平化数组 + +```js +// 递归实现 +function flatten(arr, depth = Infinity) { + return arr.reduce((acc, val) => { + return acc.concat( + Array.isArray(val) && depth > 0 ? flatten(val, depth - 1) : val + ); + }, []); +} + +// 迭代实现(使用栈) +function flattenIterative(arr) { + const stack = [...arr]; + const result = []; + while (stack.length) { + const item = stack.pop(); + Array.isArray(item) ? stack.push(...item) : result.unshift(item); + } + return result; +} + +// 原生方法 +arr.flat(Infinity); +``` + +## 3. 字符串反转 + +```js +const reverse = str => str.split('').reverse().join(''); + +// 或者使用扩展运算符 +const reverse2 = str => [...str].reverse().join(''); +``` + +## 4. 判断回文 + +```js +const isPalindrome = str => { + const clean = str.toLowerCase().replace(/[^a-z0-9]/g, ''); + return clean === clean.split('').reverse().join(''); +}; + +// 双指针法 +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; +}; +``` + +## 5. 最长公共前缀 + +```js +function longestCommonPrefix(strs) { + if (!strs.length) return ''; + + 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 ''; + } + } + return prefix; +} + +// 示例: ["flower","flow","flight"] => "fl" +``` + +## 6. 模板字符串解析 + +```js +function render(template, data) { + return template.replace(/\{\{(\w+)\}\}/g, (match, key) => { + return data[key] !== undefined ? data[key] : match; + }); +} + +// 支持嵌套属性 +function renderDeep(template, data) { + return template.replace(/\{\{(.+?)\}\}/g, (match, path) => { + const keys = path.trim().split('.'); + let value = data; + for (const key of keys) { + value = value?.[key]; + if (value === undefined) return match; + } + return value; + }); +} + +// 使用示例 +render('Hello, {{name}}!', { name: 'World' }); // "Hello, World!" +renderDeep('{{user.info.name}}', { user: { info: { name: 'Tom' } } }); // "Tom" +``` + +## 7. 数组按属性去重并排序 + +```js +function uniqueAndSort(arr, key, sortKey, order = 'asc') { + // 去重 + const map = new Map(); + arr.forEach(item => !map.has(item[key]) && map.set(item[key], item)); + + // 排序 + return [...map.values()].sort((a, b) => { + return order === 'asc' ? a[sortKey] - b[sortKey] : b[sortKey] - a[sortKey]; + }); +} + +// 示例 +const users = [ + { id: 1, name: 'Tom', age: 20 }, + { id: 2, name: 'Jerry', age: 18 }, + { id: 1, name: 'Tom2', age: 25 } +]; +uniqueAndSort(users, 'id', 'age'); // 按id去重,按age升序排序 +``` diff --git a/algorithm/03-data-structure.md b/algorithm/03-data-structure.md new file mode 100644 index 0000000..22b16fb --- /dev/null +++ b/algorithm/03-data-structure.md @@ -0,0 +1,268 @@ +# 数据结构与算法 + +## 1. 链表 + +### 反转链表 + +```js +function reverseList(head) { + let prev = null, curr = head; + while (curr) { + const next = curr.next; + curr.next = prev; + prev = curr; + curr = next; + } + return 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; + } + return false; +} +``` + +### 合并有序链表 + +```js +function mergeTwoLists(l1, l2) { + const dummy = { next: null }; + let curr = dummy; + while (l1 && l2) { + if (l1.val <= l2.val) { + curr.next = l1; + l1 = l1.next; + } else { + curr.next = l2; + l2 = l2.next; + } + curr = curr.next; + } + curr.next = l1 || l2; + return dummy.next; +} +``` + +--- + +## 2. 二叉树 + +### 遍历(前中后序) + +```js +// 前序:根-左-右 +const preorder = (root, res = []) => { + if (!root) return 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); + return res; +}; + +// 后序:左-右-根 +const postorder = (root, res = []) => { + if (!root) return res; + postorder(root.left, res); + postorder(root.right, res); + res.push(root.val); + return res; +}; +``` + +### 求最大深度 + +```js +const maxDepth = root => { + if (!root) return 0; + return 1 + Math.max(maxDepth(root.left), maxDepth(root.right)); +}; +``` + +### 路径和 + +```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); +} +``` + +--- + +## 3. 栈与队列 + +### 用栈实现队列 + +```js +class MyQueue { + constructor() { + this.inStack = []; + this.outStack = []; + } + + push(x) { this.inStack.push(x); } + + pop() { + if (!this.outStack.length) { + while (this.inStack.length) this.outStack.push(this.inStack.pop()); + } + return this.outStack.pop(); + } + + peek() { + if (!this.outStack.length) { + while (this.inStack.length) this.outStack.push(this.inStack.pop()); + } + return this.outStack[this.outStack.length - 1]; + } + + empty() { return !this.inStack.length && !this.outStack.length; } +} +``` + +### 有效的括号 + +```js +function isValid(s) { + const map = { ')': '(', ']': '[', '}': '{' }; + const stack = []; + for (const c of s) { + if (map[c]) { + if (stack.pop() !== map[c]) return false; + } else { + stack.push(c); + } + } + return !stack.length; +} +``` + +--- + +## 4. 哈希表 + +### 两数之和 + +```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); + } + return []; +} +``` + +### 字母异位词分组 + +```js +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)); + } + return [...map.values()]; +} +``` + +--- + +## 5. 排序算法 + +### 快速排序 + +```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); + return [...quickSort(left), ...middle, ...quickSort(right)]; +} +``` + +### 归并排序 + +```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); +} + +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)); +} +``` + +--- + +## 6. 二分查找 + +### 基础二分查找 + +```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; + arr[mid] < target ? (left = mid + 1) : (right = mid - 1); + } + return -1; +} +``` + +### 旋转数组查找 + +```js +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 (target >= nums[left] && target < nums[mid]) right = mid - 1; + else left = mid + 1; + } else { + // 右半边有序 + if (target > nums[mid] && target <= nums[right]) left = mid + 1; + else right = mid - 1; + } + } + return -1; +} +``` diff --git a/algorithm/04-practical-scenarios.md b/algorithm/04-practical-scenarios.md new file mode 100644 index 0000000..bb42e47 --- /dev/null +++ b/algorithm/04-practical-scenarios.md @@ -0,0 +1,235 @@ +# 实际场景与逻辑题 + +## 1. 并发控制(限制并发请求数) + +```js +async function limitConcurrency(tasks, limit) { + const results = []; + const executing = []; + + for (const [index, task] of tasks.entries()) { + const p = Promise.resolve().then(() => task()).then(res => { + results[index] = res; + executing.splice(executing.indexOf(p), 1); + }); + executing.push(p); + + if (executing.length >= limit) { + await Promise.race(executing); + } + } + + await Promise.all(executing); + return results; +} + +// 使用示例 +const tasks = urls.map(url => () => fetch(url)); +await limitConcurrency(tasks, 3); // 最多3个并发 +``` + +### 异步任务调度器 + +```js +class Scheduler { + constructor(limit) { + this.limit = limit; + this.queue = []; + this.running = 0; + } + + add(promiseCreator) { + return new Promise((resolve, reject) => { + this.queue.push({ promiseCreator, resolve, reject }); + 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(); }); + } + } +} +``` + +--- + +## 2. 缓存函数(Memoization) + +```js +function memoize(fn) { + 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); + return result; + }; +} + +// 支持过期时间 +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() }); + return result; + }; +} +``` + +--- + +## 3. 解析 URL 参数 + +```js +function parseQuery(url) { + const query = url.split('?')[1] || ''; + return query.split('&').reduce((acc, pair) => { + const [key, value] = pair.split('=').map(decodeURIComponent); + if (key) { + acc[key] = acc[key] + ? [].concat(acc[key], value) + : value; + } + return acc; + }, {}); +} + +// 使用正则 +function parseQueryRegex(url) { + const result = {}; + url.replace(/[?&]([^=&#]+)=([^&#]*)/g, (_, key, value) => { + result[decodeURIComponent(key)] = decodeURIComponent(value); + }); + return result; +} + +// 示例: "https://example.com?a=1&b=2&a=3" +// => { a: ['1', '3'], b: '2' } +``` + +--- + +## 4. DOM 查找最近公共祖先 + +```js +function findCommonAncestor(node1, node2) { + const ancestors = new Set(); + + // 收集 node1 的所有祖先 + let current = node1; + while (current) { + ancestors.add(current); + current = current.parentNode; + } + + // 查找 node2 的祖先中第一个在 ancestors 中的节点 + current = node2; + while (current) { + if (ancestors.has(current)) return current; + current = current.parentNode; + } + + return null; +} + +// 原生方法(现代浏览器) +// node1.compareDocumentPosition(node2) +``` + +--- + +## 5. 懒加载/无限滚动 + +```js +// IntersectionObserver 实现懒加载 +function lazyLoad(selector) { + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + const img = entry.target; + img.src = img.dataset.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(); + }); + + // 监听底部占位元素 + const sentinel = document.createElement('div'); + container.appendChild(sentinel); + observer.observe(sentinel); +} +``` + +--- + +## 6. LRU 缓存 + +```js +class LRUCache { + constructor(capacity) { + this.capacity = capacity; + this.cache = new 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.size > this.capacity) { + this.cache.delete(this.cache.keys().next().value); + } + } +} +``` + +--- + +## 7. 大数相加 + +```js +function addBigNumbers(a, b) { + const maxLen = Math.max(a.length, b.length); + a = a.padStart(maxLen, '0'); + b = b.padStart(maxLen, '0'); + + let carry = 0, result = ''; + for (let i = maxLen - 1; i >= 0; i--) { + const sum = +a[i] + +b[i] + carry; + result = (sum % 10) + result; + carry = Math.floor(sum / 10); + } + + return carry ? carry + result : result; +} + +// 示例: addBigNumbers("12345678901234567890", "98765432109876543210") +``` diff --git a/algorithm/05-framework-algorithms.md b/algorithm/05-framework-algorithms.md new file mode 100644 index 0000000..ebdaf75 --- /dev/null +++ b/algorithm/05-framework-algorithms.md @@ -0,0 +1,245 @@ +# 框架相关算法 + +## 1. 虚拟DOM Diff 算法 + +### 简化版 Diff 实现 + +```js +function diff(oldVNode, newVNode) { + const patches = []; + + // 节点不存在 + if (!newVNode) { + patches.push({ type: 'REMOVE' }); + return patches; + } + + // 类型不同,替换节点 + if (typeof oldVNode !== typeof newVNode || + (typeof oldVNode === 'string' && oldVNode !== newVNode) || + oldVNode.tag !== newVNode.tag) { + patches.push({ type: 'REPLACE', node: newVNode }); + return patches; + } + + // 比较属性 + if (newVNode.props) { + const propsPatches = diffProps(oldVNode.props || {}, newVNode.props); + if (Object.keys(propsPatches).length) { + patches.push({ type: 'PROPS', props: propsPatches }); + } + } + + // 比较子节点 + 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; + } + } + 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 }); + } + } +} +``` + +--- + +## 2. Vue 响应式原理 + +### Vue 2 (Object.defineProperty) + +```js +function observe(obj) { + if (typeof obj !== 'object' || obj === null) return; + + Object.keys(obj).forEach(key => { + let value = obj[key]; + const dep = new Set(); + + observe(value); // 递归处理嵌套对象 + + Object.defineProperty(obj, key, { + get() { + if (currentEffect) dep.add(currentEffect); + return value; + }, + set(newVal) { + if (newVal === value) return; + value = newVal; + observe(newVal); + dep.forEach(fn => fn()); + } + }); + }); +} + +let currentEffect = null; +function watchEffect(fn) { + currentEffect = fn; + fn(); + currentEffect = null; +} +``` + +### Vue 3 (Proxy) + +```js +function reactive(obj) { + return new Proxy(obj, { + get(target, key, receiver) { + 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); + return result; + } + }); +} + +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); +} + +function trigger(target, key) { + const depsMap = targetMap.get(target); + if (!depsMap) return; + depsMap.get(key)?.forEach(effect => effect()); +} + +function effect(fn) { + activeEffect = fn; + fn(); + activeEffect = null; +} +``` + +--- + +## 3. React useState 简单模拟 + +```js +let state = []; +let stateIndex = 0; + +function useState(initialValue) { + const currentIndex = stateIndex; + state[currentIndex] = state[currentIndex] ?? initialValue; + + const setState = (newValue) => { + state[currentIndex] = typeof newValue === 'function' + ? newValue(state[currentIndex]) + : newValue; + render(); // 触发重新渲染 + }; + + stateIndex++; + return [state[currentIndex], setState]; +} + +function render() { + stateIndex = 0; // 重置索引 + // 调用组件函数... +} +``` + +--- + +## 4. useEffect 简单模拟 + +```js +let effectIndex = 0; +let effects = []; + +function useEffect(callback, deps) { + const currentIndex = effectIndex; + const prevDeps = effects[currentIndex]?.deps; + + const hasChanged = !prevDeps || + deps.some((dep, i) => !Object.is(dep, prevDeps[i])); + + if (hasChanged) { + // 执行清理函数 + effects[currentIndex]?.cleanup?.(); + + // 延迟执行 effect + Promise.resolve().then(() => { + const cleanup = callback(); + effects[currentIndex] = { deps, cleanup }; + }); + } + + effectIndex++; +} +``` + +--- + +## 5. 简易 Redux + +```js +function createStore(reducer) { + let state; + const listeners = []; + + const getState = () => state; + + const dispatch = (action) => { + state = reducer(state, action); + listeners.forEach(listener => listener()); + }; + + const subscribe = (listener) => { + listeners.push(listener); + return () => { + const index = listeners.indexOf(listener); + listeners.splice(index, 1); + }; + }; + + dispatch({ type: '@@INIT' }); // 初始化 state + + return { getState, dispatch, subscribe }; +} + +// 使用示例 +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; + } +}; +``` diff --git a/algorithm/06-performance.md b/algorithm/06-performance.md new file mode 100644 index 0000000..e33c1f5 --- /dev/null +++ b/algorithm/06-performance.md @@ -0,0 +1,269 @@ +# 性能与优化 + +## 1. 图片懒加载 + +### IntersectionObserver 方案(推荐) + +```js +function lazyLoadImages() { + const observer = new IntersectionObserver((entries, observer) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + const img = entry.target; + img.src = img.dataset.src; + img.classList.remove('lazy'); + observer.unobserve(img); + } + }); + }, { + rootMargin: '50px' // 提前50px开始加载 + }); + + document.querySelectorAll('img.lazy').forEach(img => observer.observe(img)); +} + +// HTML: +``` + +### 传统滚动监听方案 + +```js +function lazyLoadScroll() { + const lazyImages = document.querySelectorAll('img.lazy'); + + const loadImage = throttle(() => { + lazyImages.forEach(img => { + if (img.getBoundingClientRect().top < window.innerHeight + 100) { + img.src = img.dataset.src; + img.classList.remove('lazy'); + } + }); + }, 200); + + window.addEventListener('scroll', loadImage); + loadImage(); // 初始加载 +} +``` + +--- + +## 2. 虚拟列表(大数据渲染优化) + +```js +class VirtualList { + constructor({ container, itemHeight, totalItems, renderItem }) { + this.container = container; + this.itemHeight = itemHeight; + this.totalItems = totalItems; + this.renderItem = renderItem; + + this.visibleCount = Math.ceil(container.clientHeight / itemHeight) + 2; + this.startIndex = 0; + + this.init(); + } + + init() { + // 创建容器结构 + this.container.style.overflow = 'auto'; + this.container.style.position = 'relative'; + + // 占位元素,撑开滚动高度 + this.phantom = document.createElement('div'); + this.phantom.style.height = `${this.totalItems * this.itemHeight}px`; + + // 实际渲染的列表容器 + this.content = document.createElement('div'); + this.content.style.position = 'absolute'; + this.content.style.top = '0'; + this.content.style.width = '100%'; + + this.container.appendChild(this.phantom); + this.container.appendChild(this.content); + + this.container.addEventListener('scroll', throttle(() => this.onScroll(), 16)); + this.render(); + } + + onScroll() { + const scrollTop = this.container.scrollTop; + this.startIndex = Math.floor(scrollTop / this.itemHeight); + this.content.style.transform = `translateY(${this.startIndex * this.itemHeight}px)`; + this.render(); + } + + render() { + const fragment = document.createDocumentFragment(); + const endIndex = Math.min(this.startIndex + this.visibleCount, this.totalItems); + + for (let i = this.startIndex; i < endIndex; i++) { + const item = this.renderItem(i); + item.style.height = `${this.itemHeight}px`; + fragment.appendChild(item); + } + + this.content.innerHTML = ''; + this.content.appendChild(fragment); + } +} + +// 使用示例 +new VirtualList({ + container: document.getElementById('list'), + itemHeight: 50, + totalItems: 10000, + renderItem: (index) => { + const div = document.createElement('div'); + div.textContent = `Item ${index}`; + return div; + } +}); +``` + +--- + +## 3. 函数节流在滚动事件中的应用 + +```js +// 节流函数 +function throttle(fn, delay) { + let lastTime = 0; + let timer = null; + + return function(...args) { + const now = Date.now(); + const remaining = delay - (now - lastTime); + + if (remaining <= 0) { + if (timer) { + clearTimeout(timer); + timer = null; + } + lastTime = now; + fn.apply(this, args); + } else if (!timer) { + timer = setTimeout(() => { + lastTime = Date.now(); + timer = null; + fn.apply(this, args); + }, remaining); + } + }; +} + +// 应用场景 +window.addEventListener('scroll', throttle(() => { + console.log('滚动位置:', window.scrollY); + // 吸顶效果、返回顶部按钮显示等 +}, 100)); + +window.addEventListener('resize', throttle(() => { + console.log('窗口大小:', window.innerWidth, window.innerHeight); +}, 200)); +``` + +--- + +## 4. requestAnimationFrame 优化动画 + +```js +// 使用 rAF 实现平滑滚动 +function smoothScrollTo(target, duration = 500) { + 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 easeProgress = 1 - Math.pow(1 - progress, 3); + window.scrollTo(0, start + distance * easeProgress); + + if (progress < 1) { + requestAnimationFrame(step); + } + } + + requestAnimationFrame(step); +} + +// rAF 节流 +function rafThrottle(fn) { + let ticking = false; + return function(...args) { + if (!ticking) { + requestAnimationFrame(() => { + fn.apply(this, args); + ticking = false; + }); + ticking = true; + } + }; +} +``` + +--- + +## 5. Web Worker 处理耗时任务 + +```js +// main.js +const worker = new Worker('worker.js'); + +worker.postMessage({ type: 'heavyTask', data: largeArray }); + +worker.onmessage = (e) => { + console.log('处理结果:', e.data); +}; + +// worker.js +self.onmessage = (e) => { + if (e.data.type === 'heavyTask') { + const result = e.data.data.map(item => /* 耗时计算 */); + self.postMessage(result); + } +}; +``` + +--- + +## 6. 时间切片(避免长任务阻塞) + +```js +async function timeSlice(tasks, chunkSize = 5) { + const results = []; + + for (let i = 0; i < tasks.length; i += chunkSize) { + const chunk = tasks.slice(i, i + chunkSize); + + // 处理一批任务 + for (const task of chunk) { + results.push(task()); + } + + // 让出主线程 + await new Promise(resolve => setTimeout(resolve, 0)); + // 或使用 requestIdleCallback + } + + return results; +} + +// 使用 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); +} +``` diff --git a/algorithm/README.md b/algorithm/README.md new file mode 100644 index 0000000..841944a --- /dev/null +++ b/algorithm/README.md @@ -0,0 +1,23 @@ +# 京东前端面试算法题 + +> 难度:中等 | 重点:代码实现能力、逻辑清晰度、实际应用 + +## 📁 目录 + +| 分类 | 文件 | +|------|------| +| JS基础与手写实现 | [01-js-basics.md](./01-js-basics.md) | +| 数组与字符串操作 | [02-array-string.md](./02-array-string.md) | +| 数据结构与算法 | [03-data-structure.md](./03-data-structure.md) | +| 实际场景与逻辑题 | [04-practical-scenarios.md](./04-practical-scenarios.md) | +| 框架相关算法 | [05-framework-algorithms.md](./05-framework-algorithms.md) | +| 性能与优化 | [06-performance.md](./06-performance.md) | + +--- + +## ✅ 备考建议 + +1. **重视基础**:闭包、原型链、异步核心概念必须扎实 +2. **动手实现**:多手写常见工具函数(深拷贝、Promise等) +3. **刷题方向**:LeetCode 简单到中等,重点:字符串、数组、链表 +4. **理解原理**:框架算法需理解思想,不必死记硬背