🚀 JavaScript 手撕面试题合集
整理了前端开发中最常见的手撕代码面试题,包含详细注释和测试用例
📑 目录
一、数组相关
1.1 数组与树的转换
⭐⭐⭐⭐⭐ 扁平数组转树形结构
题目描述: 将带有 id 和 parentId 的扁平数组转换为树形结构
解题思路:
- 第一次遍历:创建哈希表,建立 id 到节点的映射
- 第二次遍历:根据 parentId 建立父子关系
/**
* 数组转树形结构
* @param {Array} arr - 扁平数组
* @returns {Array} - 树形结构数组
*/
function arrayToTree(arr) {
if (!Array.isArray(arr) || !arr?.length) return [];
// 1. 创建哈希表映射
let map = {};
let tree = [];
arr.forEach((item) => {
map[item?.id] = { ...item, children: [] };
});
// 2. 建立父子关系
arr.forEach((item) => {
if (item?.parentId) {
map[item?.parentId].children.push(map[item.id]);
} else {
tree.push(map[item.id]);
}
});
return tree;
}
// 测试用例
const arr = [
{ id: 1, parentId: null, name: 'A' },
{ id: 2, parentId: 1, name: 'B' },
{ id: 3, parentId: 1, name: 'C' },
{ id: 4, parentId: 2, name: 'D' }
];
console.log(arrayToTree(arr));
// 结果: [{ id: 1, name: 'A', children: [...] }]
时间复杂度: O(n) 空间复杂度: O(n)
⭐⭐⭐⭐ 树形结构转扁平数组
解题思路: 深度优先遍历,提取节点信息
/**
* 树形结构转扁平数组
* @param {Array} tree - 树形结构
* @returns {Array} - 扁平数组
*/
function treeToFlat(tree) {
const result = [];
function traverse(node) {
const { children, ...nodeData } = node;
result.push(nodeData);
if (children && children.length > 0) {
children.forEach(child => traverse(child));
}
}
if (Array.isArray(tree)) {
tree.forEach(node => traverse(node));
} else {
traverse(tree);
}
return result;
}
时间复杂度: O(n) 空间复杂度: O(h) - h为树的高度
1.2 数组去重
⭐⭐⭐⭐⭐ 三种去重方法
方法1:双层循环 + splice
function unique(arr) {
const result = [...arr];
for (let i = 0; i < result.length; i++) {
for (let j = i + 1; j < result.length; j++) {
if (result[i] === result[j]) {
result.splice(j, 1);
j--; // 注意:删除后需要j--
}
}
}
return result;
}
- 时间复杂度:O(n²)
- 空间复杂度:O(n)
方法2:filter + indexOf
function unique2(arr) {
return arr.filter((item, index, self) => {
return self.indexOf(item) === index;
});
}
- 时间复杂度:O(n²)
- 空间复杂度:O(n)
方法3:Set(推荐)
function unique3(arr) {
return Array.from(new Set(arr));
// 或者:return [...new Set(arr)];
}
- 时间复杂度:O(n)
- 空间复杂度:O(n)
- 最简洁高效的方法
1.3 数组扁平化
⭐⭐⭐⭐⭐ 三种扁平化方法
方法1:递归 + reduce
function flatten(arr) {
return arr.reduce((acc, curr) => {
return acc.concat(Array.isArray(curr) ? flatten(curr) : curr);
}, []);
}
方法2:flat方法(ES2019)
function flatten2(arr) {
return arr.flat(Infinity);
}
方法3:toString(仅适用于数字)
function flatten3(arr) {
return arr.toString().split(',').map(Number);
}
1.4 栈与队列
⭐⭐⭐⭐ 括号匹配检查
function isBalanced(expression) {
const stack = [];
const brackets = { '(': ')', '[': ']', '{': '}' };
for (let char of expression) {
if (brackets[char]) {
// 左括号入栈
stack.push(char);
} else if (Object.values(brackets).includes(char)) {
// 右括号检查匹配
if (stack.length === 0 || brackets[stack.pop()] !== char) {
return false;
}
}
}
return stack.length === 0;
}
console.log(isBalanced('({[]})')); // true
console.log(isBalanced('({[})')); // false
⭐⭐⭐⭐ 实现Undo/Redo功能
class Editor {
constructor() {
this.content = '';
this.undoStack = [];
this.redoStack = [];
}
type(text) {
this.undoStack.push(this.content);
this.redoStack = [];
this.content += text;
}
undo() {
if (this.undoStack.length > 0) {
this.redoStack.push(this.content);
this.content = this.undoStack.pop();
}
}
redo() {
if (this.redoStack.length > 0) {
this.undoStack.push(this.content);
this.content = this.redoStack.pop();
}
}
}
二、字符串相关
2.1 URL解析
⭐⭐⭐⭐⭐ 解析URL参数
function parseUrl(url) {
const str = url.split("?")[1];
if (!str) return {};
const result = {};
str.split("&").forEach(item => {
const [key, value] = item.split("=");
const decodedValue = decodeURIComponent(value || '');
if (Object.hasOwn(result, key)) {
// 处理重复key,转为数组
result[key] = [].concat(result[key], decodedValue);
} else if (decodedValue === "undefined") {
result[key] = true;
} else {
result[key] = decodedValue;
}
});
return result;
}
const url = "https://example.com?name=John&age=30&tag=js&tag=html";
console.log(parseUrl(url));
// { name: 'John', age: '30', tag: ['js', 'html'] }
2.2 命名转换
⭐⭐⭐⭐ 下划线转驼峰
function toCamelCase(str) {
return str.replace(/_(\w)/g, (_, letter) => letter.toUpperCase());
}
console.log(toCamelCase('hello_world_test')); // helloWorldTest
⭐⭐⭐⭐ 驼峰转下划线
function toSnakeCase(str) {
return str.replace(/([A-Z])/g, '_$1').toLowerCase().replace(/^_/, '');
}
console.log(toSnakeCase('helloWorldTest')); // hello_world_test
2.3 字符串反转与回文
⭐⭐⭐ 字符串反转
function reverseString(str) {
return str.split('').reverse().join('');
}
⭐⭐⭐⭐ 判断回文字符串
function isPalindrome(str) {
const cleaned = str.toLowerCase().replace(/[^a-z0-9]/g, '');
return cleaned === cleaned.split('').reverse().join('');
}
console.log(isPalindrome('A man, a plan, a canal: Panama')); // true
2.4 字符串压缩
⭐⭐⭐⭐ 连续字符计数压缩
function compressString(str) {
if (!str) return str;
let result = '';
let count = 1;
for (let i = 0; i < str.length; i++) {
if (str[i] === str[i + 1]) {
count++;
} else {
result += str[i] + (count > 1 ? count : '');
count = 1;
}
}
return result.length < str.length ? result : str;
}
console.log(compressString('aabcccccaaa')); // a2bc5a3
2.5 千分位格式化
⭐⭐⭐⭐⭐ 支持小数的千分位格式化
// 方法1: 正则(支持小数)
function formatNumberWithDecimal(num) {
const [integer, decimal] = num.toString().split('.');
const formattedInteger = integer.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
return decimal ? `${formattedInteger}.${decimal}` : formattedInteger;
}
// 方法2: 支持负数和指定精度
function formatNumberComplete(num, decimals = 2) {
const isNegative = num < 0;
const absNum = Math.abs(num);
const rounded = absNum.toFixed(decimals);
const [integer, decimal] = rounded.split('.');
const formattedInteger = integer.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
let result = decimal ? `${formattedInteger}.${decimal}` : formattedInteger;
return isNegative ? `-${result}` : result;
}
console.log(formatNumberComplete(1234567.89, 2)); // 1,234,567.89
console.log(formatNumberComplete(-1234567.89, 2)); // -1,234,567.89
正则解释:
\B:非单词边界(?=(\d{3})+(?!\d)):正向预查,匹配后面是3的倍数个数字且后面不是数字的位置
2.6 模板引擎
⭐⭐⭐⭐ 简易模板引擎
function templateEngine(template, data) {
return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
return data[key] !== undefined ? data[key] : match;
});
}
const template = 'Hello {{name}}, you are {{age}} years old!';
const data = { name: 'Alice', age: 25 };
console.log(templateEngine(template, data));
// Hello Alice, you are 25 years old!
2.7 版本号比较
⭐⭐⭐⭐ 版本号大小比较
function compareVersion(v1, v2) {
const arr1 = v1.split('.').map(Number);
const arr2 = v2.split('.').map(Number);
const maxLen = Math.max(arr1.length, arr2.length);
for (let i = 0; i < maxLen; i++) {
const num1 = arr1[i] || 0;
const num2 = arr2[i] || 0;
if (num1 > num2) return 1;
if (num1 < num2) return -1;
}
return 0;
}
console.log(compareVersion('1.2.3', '1.2.4')); // -1
console.log(compareVersion('1.3.0', '1.2.9')); // 1
2.8 大数相加
⭐⭐⭐⭐⭐ 字符串模拟大数相加
function addBigNumbers(num1, num2) {
let i = num1.length - 1;
let j = num2.length - 1;
let carry = 0;
let result = '';
while (i >= 0 || j >= 0 || carry > 0) {
const digit1 = i >= 0 ? parseInt(num1[i]) : 0;
const digit2 = j >= 0 ? parseInt(num2[j]) : 0;
const sum = digit1 + digit2 + carry;
result = (sum % 10) + result;
carry = Math.floor(sum / 10);
i--;
j--;
}
return result;
}
console.log(addBigNumbers('123456789', '987654321')); // 1111111110
三、Promise相关
3.1 手写Promise方法
⭐⭐⭐⭐⭐ Promise.all
function promiseAll(promises) {
return new Promise((resolve, reject) => {
if (promises.length === 0) {
resolve([]);
return;
}
const result = new Array(promises.length);
let count = 0;
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then(res => {
result[index] = res;
count++;
if (count === promises.length) {
resolve(result);
}
})
.catch(reject);
});
});
}
特点:
- 所有成功才成功
- 一个失败就失败
- 保持结果顺序
⭐⭐⭐⭐⭐ Promise.race
function promiseRace(promises) {
return new Promise((resolve, reject) => {
if (promises.length === 0) return;
promises.forEach(promise => {
Promise.resolve(promise)
.then(resolve)
.catch(reject);
});
});
}
特点: 第一个完成的结果(成功或失败)
⭐⭐⭐⭐ Promise.allSettled
function promiseAllSettled(promises) {
return new Promise((resolve) => {
if (promises.length === 0) {
resolve([]);
return;
}
const result = new Array(promises.length);
let count = 0;
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then(value => {
result[index] = { status: 'fulfilled', value };
})
.catch(reason => {
result[index] = { status: 'rejected', reason };
})
.finally(() => {
count++;
if (count === promises.length) {
resolve(result);
}
});
});
});
}
特点: 等待所有完成,返回每个的状态
3.2 Promise并发控制
⭐⭐⭐⭐⭐ 限制并发数量
function promiseLimit(promises, limit) {
return new Promise((resolve, reject) => {
let index = 0;
let running = 0;
let finished = 0;
const result = [];
function run() {
while (running < limit && index < promises.length) {
const currentIndex = index;
const promise = promises[index++];
running++;
Promise.resolve(promise)
.then(res => {
result[currentIndex] = res;
})
.catch(err => {
result[currentIndex] = err;
})
.finally(() => {
running--;
finished++;
if (finished === promises.length) {
resolve(result);
} else {
run();
}
});
}
}
run();
});
}
应用场景:
- 批量上传文件(限制并发数)
- 批量请求(避免服务器压力过大)
- 爬虫控制并发
3.3 Promise重试机制
⭐⭐⭐⭐ 请求失败自动重试
function promiseRetry(fn, times = 3, delay = 1000) {
return new Promise((resolve, reject) => {
function attempt(remainingTimes) {
fn()
.then(resolve)
.catch(error => {
if (remainingTimes === 0) {
reject(error);
} else {
console.log(`重试中... 剩余次数: ${remainingTimes}`);
setTimeout(() => {
attempt(remainingTimes - 1);
}, delay);
}
});
}
attempt(times);
});
}
3.4 手写Promise类
⭐⭐⭐⭐⭐ 简易版Promise实现
class MyPromise {
constructor(executor) {
this.state = 'pending';
this.value = undefined;
this.reason = undefined;
this.onFulfilledCallbacks = [];
this.onRejectedCallbacks = [];
const resolve = (value) => {
if (this.state === 'pending') {
this.state = 'fulfilled';
this.value = value;
this.onFulfilledCallbacks.forEach(fn => fn());
}
};
const reject = (reason) => {
if (this.state === 'pending') {
this.state = 'rejected';
this.reason = reason;
this.onRejectedCallbacks.forEach(fn => fn());
}
};
try {
executor(resolve, reject);
} catch (error) {
reject(error);
}
}
then(onFulfilled, onRejected) {
onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : value => value;
onRejected = typeof onRejected === 'function' ? onRejected : reason => { throw reason; };
const promise2 = new MyPromise((resolve, reject) => {
if (this.state === 'fulfilled') {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolve(x);
} catch (error) {
reject(error);
}
});
}
if (this.state === 'rejected') {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolve(x);
} catch (error) {
reject(error);
}
});
}
if (this.state === 'pending') {
this.onFulfilledCallbacks.push(() => {
setTimeout(() => {
try {
const x = onFulfilled(this.value);
resolve(x);
} catch (error) {
reject(error);
}
});
});
this.onRejectedCallbacks.push(() => {
setTimeout(() => {
try {
const x = onRejected(this.reason);
resolve(x);
} catch (error) {
reject(error);
}
});
});
}
});
return promise2;
}
catch(onRejected) {
return this.then(null, onRejected);
}
}
四、定时器相关
4.1 setTimeout实现setInterval
⭐⭐⭐⭐⭐ 用setTimeout模拟setInterval
function mySetInterval(fn, delay) {
let timerId = null;
let stopped = false;
function interval() {
if (stopped) return;
fn();
timerId = setTimeout(interval, delay);
}
timerId = setTimeout(interval, delay);
// 返回清除函数
return () => {
stopped = true;
clearTimeout(timerId);
};
}
// 使用
const clear = mySetInterval(() => {
console.log('执行');
}, 1000);
// 清除
// clear();
优势:
- 避免setInterval的任务堆积问题
- 更精确的时间控制
4.2 防抖与节流
⭐⭐⭐⭐⭐ 防抖(Debounce)
function debounce(fn, delay) {
let timerId = null;
return function(...args) {
if (timerId) {
clearTimeout(timerId);
}
timerId = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
// 使用场景:搜索框输入、窗口resize
const debouncedSearch = debounce((keyword) => {
console.log('搜索:', keyword);
}, 300);
原理: 等待delay时间后执行,期间有新调用则重新计时
⭐⭐⭐⭐⭐ 节流(Throttle)
// 方法1: 时间戳版本(立即执行)
function throttle(fn, delay) {
let lastTime = 0;
return function(...args) {
const now = Date.now();
if (now - lastTime >= delay) {
fn.apply(this, args);
lastTime = now;
}
};
}
// 方法2: 定时器版本(延迟执行)
function throttle2(fn, delay) {
let timerId = null;
return function(...args) {
if (timerId) return;
timerId = setTimeout(() => {
fn.apply(this, args);
timerId = null;
}, delay);
};
}
// 使用场景:滚动事件、鼠标移动
const throttledScroll = throttle(() => {
console.log('滚动事件');
}, 200);
原理: 每隔delay时间最多执行一次
4.3 定时打印
⭐⭐⭐⭐⭐ 每隔一秒打印1,2,3,4
方法1:使用let(块级作用域)
function printNumbers1() {
for (let i = 1; i <= 4; i++) {
setTimeout(() => {
console.log(i);
}, i * 1000);
}
}
方法2:使用闭包
function printNumbers2() {
for (var i = 1; i <= 4; i++) {
(function(num) {
setTimeout(() => {
console.log(num);
}, num * 1000);
})(i);
}
}
方法3:使用setTimeout的第三个参数
function printNumbers3() {
for (var i = 1; i <= 4; i++) {
setTimeout((num) => {
console.log(num);
}, i * 1000, i);
}
}
五、设计模式
5.1 发布-订阅模式
⭐⭐⭐⭐⭐ EventEmitter实现
class EventEmitter {
constructor() {
this.events = {};
}
// 订阅事件
on(event, callback) {
if (!this.events[event]) {
this.events[event] = [];
}
this.events[event].push(callback);
// 返回取消订阅函数
return () => this.off(event, callback);
}
// 订阅一次
once(event, callback) {
const wrapper = (...args) => {
callback(...args);
this.off(event, wrapper);
};
this.on(event, wrapper);
}
// 发布事件
emit(event, ...args) {
if (!this.events[event]) return;
this.events[event].forEach(callback => {
callback(...args);
});
}
// 取消订阅
off(event, callback) {
if (!this.events[event]) return;
if (!callback) {
delete this.events[event];
} else {
this.events[event] = this.events[event].filter(cb => cb !== callback);
}
}
// 清空所有订阅
clear() {
this.events = {};
}
}
// 使用
const eventBus = new EventEmitter();
eventBus.on('login', (user) => console.log(`${user} 登录了`));
eventBus.emit('login', 'Alice');
应用场景:
- Vue的EventBus
- Node.js的EventEmitter
- 跨组件通信
5.2 观察者模式
⭐⭐⭐⭐ Subject-Observer
// 主题(被观察者)
class Subject {
constructor() {
this.observers = [];
this.state = null;
}
attach(observer) {
this.observers.push(observer);
}
detach(observer) {
const index = this.observers.indexOf(observer);
if (index > -1) {
this.observers.splice(index, 1);
}
}
notify() {
this.observers.forEach(observer => {
observer.update(this.state);
});
}
setState(state) {
this.state = state;
this.notify();
}
}
// 观察者
class Observer {
constructor(name) {
this.name = name;
}
update(state) {
console.log(`${this.name} 收到更新: ${state}`);
}
}
// 使用
const subject = new Subject();
const observer1 = new Observer('观察者1');
subject.attach(observer1);
subject.setState('新状态');
发布订阅 vs 观察者:
- 观察者:Subject直接通知Observer(紧耦合)
- 发布订阅:通过事件中心解耦(松耦合)
5.3 单例模式
⭐⭐⭐⭐⭐ 三种单例实现
方法1:闭包
const Singleton = (function() {
let instance;
class Singleton {
constructor(name) {
if (instance) {
return instance;
}
this.name = name;
instance = this;
}
}
return Singleton;
})();
方法2:静态属性
class Singleton {
constructor(name) {
if (Singleton.instance) {
return Singleton.instance;
}
this.name = name;
Singleton.instance = this;
}
static getInstance(name) {
if (!Singleton.instance) {
Singleton.instance = new Singleton(name);
}
return Singleton.instance;
}
}
方法3:Proxy
function proxySingleton(className) {
let instance;
return new Proxy(className, {
construct(target, args) {
if (!instance) {
instance = new target(...args);
}
return instance;
}
});
}
5.4 工厂模式
⭐⭐⭐⭐ 简单工厂
class SimpleFactory {
static createProduct(type) {
switch (type) {
case 'A':
return new ProductA();
case 'B':
return new ProductB();
default:
throw new Error('未知产品类型');
}
}
}
class ProductA {
constructor() {
this.type = 'A';
}
}
class ProductB {
constructor() {
this.type = 'B';
}
}
// 使用
const productA = SimpleFactory.createProduct('A');
5.5 代理模式
⭐⭐⭐⭐⭐ 缓存代理
function cacheProxy(fn) {
const cache = new Map();
return function(...args) {
const key = JSON.stringify(args);
if (cache.has(key)) {
console.log('从缓存获取');
return cache.get(key);
}
const result = fn.apply(this, args);
cache.set(key, result);
return result;
};
}
// 使用
const fibonacci = (n) => {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
};
const cachedFib = cacheProxy(fibonacci);
console.log(cachedFib(10)); // 计算
console.log(cachedFib(10)); // 从缓存获取
六、数学相关
6.1 斐波那契数列
⭐⭐⭐⭐⭐ 三种实现方式
方法1:动态规划(推荐)
function fibonacciDP(n) {
if (n <= 1) return n;
let prev = 0, curr = 1;
for (let i = 2; i <= n; i++) {
[prev, curr] = [curr, prev + curr];
}
return curr;
}
- 时间复杂度:O(n)
- 空间复杂度:O(1)
方法2:递归(效率低)
function fibonacci(n) {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
- 时间复杂度:O(2^n)
方法3:记忆化递归
function fibonacciMemo(n, memo = {}) {
if (n in memo) return memo[n];
if (n <= 1) return n;
memo[n] = fibonacciMemo(n - 1, memo) + fibonacciMemo(n - 2, memo);
return memo[n];
}
- 时间复杂度:O(n)
- 空间复杂度:O(n)
6.2 质数判断
⭐⭐⭐⭐ 判断是否为质数
function isPrime(n) {
if (n <= 1) return false;
if (n <= 3) return true;
if (n % 2 === 0 || n % 3 === 0) return false;
// 只需检查到 sqrt(n)
for (let i = 5; i * i <= n; i += 6) {
if (n % i === 0 || n % (i + 2) === 0) {
return false;
}
}
return true;
}
优化点:
- 排除2和3的倍数
- 只检查到√n
- 步长为6(质数分布规律)
6.3 最大公约数与最小公倍数
⭐⭐⭐⭐⭐ 辗转相除法(欧几里得算法)
最大公约数(GCD)
function gcd(a, b) {
return b === 0 ? a : gcd(b, a % b);
}
// 迭代版本
function gcdIterative(a, b) {
while (b !== 0) {
[a, b] = [b, a % b];
}
return a;
}
最小公倍数(LCM)
function lcm(a, b) {
return Math.abs(a * b) / gcd(a, b);
}
console.log(gcd(48, 18)); // 6
console.log(lcm(12, 18)); // 36
6.4 快速幂算法
⭐⭐⭐⭐ 二进制优化幂运算
function power(base, exp) {
if (exp === 0) return 1;
if (exp < 0) return 1 / power(base, -exp);
let result = 1;
while (exp > 0) {
if (exp % 2 === 1) {
result *= base;
}
base *= base;
exp = Math.floor(exp / 2);
}
return result;
}
console.log(power(2, 10)); // 1024
优势: O(log n) 时间复杂度,比直接连乘快得多
七、排序算法
7.1 冒泡排序
⭐⭐⭐ 基础排序算法
function bubbleSort(arr) {
const len = arr.length;
for (let i = 0; i < len - 1; i++) {
let swapped = false;
for (let j = 0; j < len - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
[arr[j], arr[j + 1]] = [arr[j + 1], arr[j]];
swapped = true;
}
}
// 优化:如果没有交换,提前结束
if (!swapped) break;
}
return arr;
}
- 时间复杂度:O(n²)
- 空间复杂度:O(1)
- 稳定性:稳定
7.2 快速排序
⭐⭐⭐⭐⭐ 最常用的排序算法
function quickSort(arr, left = 0, right = arr.length - 1) {
if (left < right) {
const pivotIndex = partition(arr, left, right);
quickSort(arr, left, pivotIndex - 1);
quickSort(arr, pivotIndex + 1, right);
}
return arr;
}
function partition(arr, left, right) {
const pivot = arr[right];
let i = left - 1;
for (let j = left; j < right; j++) {
if (arr[j] <= pivot) {
i++;
[arr[i], arr[j]] = [arr[j], arr[i]];
}
}
[arr[i + 1], arr[right]] = [arr[right], arr[i + 1]];
return i + 1;
}
- 时间复杂度:平均O(nlogn),最坏O(n²)
- 空间复杂度:O(logn)
- 稳定性:不稳定
- 实际应用中最快的排序算法
7.3 归并排序
⭐⭐⭐⭐⭐ 稳定的O(nlogn)排序
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) {
if (left[i] <= right[j]) {
result.push(left[i++]);
} else {
result.push(right[j++]);
}
}
return result.concat(left.slice(i)).concat(right.slice(j));
}
- 时间复杂度:O(nlogn)
- 空间复杂度:O(n)
- 稳定性:稳定
- 需要稳定排序时的首选
7.4 堆排序
⭐⭐⭐⭐ 原地排序
function heapSort(arr) {
const len = arr.length;
// 1. 构建最大堆
for (let i = Math.floor(len / 2) - 1; i >= 0; i--) {
heapify(arr, len, i);
}
// 2. 依次取出堆顶元素
for (let i = len - 1; i > 0; i--) {
[arr[0], arr[i]] = [arr[i], arr[0]];
heapify(arr, i, 0);
}
return arr;
}
function heapify(arr, len, i) {
let largest = i;
const left = 2 * i + 1;
const right = 2 * i + 2;
if (left < len && arr[left] > arr[largest]) {
largest = left;
}
if (right < len && arr[right] > arr[largest]) {
largest = right;
}
if (largest !== i) {
[arr[i], arr[largest]] = [arr[largest], arr[i]];
heapify(arr, len, largest);
}
}
- 时间复杂度:O(nlogn)
- 空间复杂度:O(1)
- 稳定性:不稳定
- 空间受限时的最佳选择
八、JS基础
8.1 深拷贝
⭐⭐⭐⭐⭐ 完整的深拷贝实现
function deepClone(obj, map = new WeakMap()) {
// 基本类型直接返回
if (obj === null || typeof obj !== 'object') return obj;
// 处理循环引用
if (map.has(obj)) return map.get(obj);
// 特殊对象处理
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
// Set
if (obj instanceof Set) {
const cloneSet = new Set();
map.set(obj, cloneSet);
obj.forEach(value => {
cloneSet.add(deepClone(value, map));
});
return cloneSet;
}
// Map
if (obj instanceof Map) {
const cloneMap = new Map();
map.set(obj, cloneMap);
obj.forEach((value, key) => {
cloneMap.set(deepClone(key, map), deepClone(value, map));
});
return cloneMap;
}
// 数组或对象
const clone = Array.isArray(obj) ? [] : {};
map.set(obj, clone);
// 包括 Symbol 和不可枚举属性
Reflect.ownKeys(obj).forEach(key => {
clone[key] = deepClone(obj[key], map);
});
return clone;
}
处理的场景:
- ✅ 基本类型
- ✅ 普通对象和数组
- ✅ 循环引用
- ✅ Date、RegExp
- ✅ Set、Map
- ✅ Symbol属性
8.2 手写new
⭐⭐⭐⭐⭐ new操作符的实现
function myNew(constructor, ...args) {
// 1. 创建一个新对象,继承构造函数的原型
const obj = Object.create(constructor.prototype);
// 2. 执行构造函数,将this绑定到新对象
const result = constructor.apply(obj, args);
// 3. 如果构造函数返回对象,则返回该对象,否则返回新对象
return result !== null && (typeof result === 'object' || typeof result === 'function')
? result
: obj;
}
// 测试
function Person(name, age) {
this.name = name;
this.age = age;
}
const person = myNew(Person, 'John', 20);
console.log(person); // { name: 'John', age: 20 }
new的四个步骤:
- 创建新对象
- 绑定原型
- 绑定this并执行构造函数
- 返回新对象(或构造函数返回的对象)
8.3 手写instanceof
⭐⭐⭐⭐⭐ instanceof操作符的实现
function myInstanceOf(obj, constructor) {
let proto = Object.getPrototypeOf(obj);
let prototype = constructor.prototype;
while (true) {
if (proto === null) return false;
if (proto === prototype) return true;
proto = Object.getPrototypeOf(proto);
}
}
// 测试
console.log(myInstanceOf([], Array)); // true
console.log(myInstanceOf({}, Array)); // false
原理: 沿着原型链查找,判断对象的原型链上是否存在构造函数的prototype
8.4 this指向
⭐⭐⭐⭐⭐ 经典this指向问题
var name = 'window';
const obj = {
name: 'obj',
sayName: function() {
console.log(this.name);
},
sayArrow: () => {
console.log(this.name);
}
};
// 情况1:直接调用
obj.sayName(); // 'obj' - 隐式绑定
// 情况2:赋值后调用
const fn = obj.sayName;
fn(); // 'window' - 默认绑定
// 情况3:setTimeout
setTimeout(obj.sayName, 100); // 'window' - 回调函数默认绑定
setTimeout(() => obj.sayName(), 100); // 'obj' - 箭头函数内调用
// 情况4:箭头函数
obj.sayArrow(); // 'window' - 箭头函数继承外层this
// 情况5:构造函数
function Person(name) {
this.name = name;
}
const person = new Person('person');
console.log(person.name); // 'person' - new绑定
this绑定规则优先级:
- new绑定 > 显式绑定(call/apply/bind)
- 显式绑定 > 隐式绑定(obj.method())
- 隐式绑定 > 默认绑定(独立函数调用)
- 箭头函数不绑定this,继承外层
8.5 手写call
⭐⭐⭐⭐⭐ 改变this指向并立即执行
Function.prototype.myCall = function(context, ...args) {
// 1. 判断调用者是否为函数
if (typeof this !== 'function') {
throw new TypeError('Error');
}
// 2. 处理context(null/undefined指向window)
context = context || window;
// 3. 将函数作为context的属性
const fn = Symbol('fn'); // 使用Symbol避免属性名冲突
context[fn] = this;
// 4. 执行函数并获取结果
const result = context[fn](...args);
// 5. 删除临时属性
delete context[fn];
// 6. 返回结果
return result;
};
// 测试
function greet(greeting, punctuation) {
return `${greeting}, ${this.name}${punctuation}`;
}
const person = { name: 'Alice' };
console.log(greet.myCall(person, 'Hello', '!')); // "Hello, Alice!"
核心思路:
- 将函数设为对象的方法
- 执行该方法
- 删除该方法
- 返回结果
8.6 手写apply
⭐⭐⭐⭐⭐ call的数组版本
Function.prototype.myApply = function(context, args = []) {
// 1. 判断调用者是否为函数
if (typeof this !== 'function') {
throw new TypeError('Error');
}
// 2. 处理context
context = context || window;
// 3. 将函数作为context的属性
const fn = Symbol('fn');
context[fn] = this;
// 4. 执行函数(参数以数组形式传入)
const result = context[fn](...args);
// 5. 删除临时属性
delete context[fn];
// 6. 返回结果
return result;
};
// 测试
function sum(a, b, c) {
console.log(this.prefix);
return a + b + c;
}
const obj = { prefix: '结果是:' };
console.log(sum.myApply(obj, [1, 2, 3])); // "结果是:" 6
call vs apply:
call(context, arg1, arg2, ...)- 参数列表apply(context, [arg1, arg2, ...])- 参数数组
8.7 手写bind
⭐⭐⭐⭐⭐ 返回一个绑定this的新函数
Function.prototype.myBind = function(context, ...args) {
// 1. 判断调用者是否为函数
if (typeof this !== 'function') {
throw new TypeError('Error');
}
// 2. 保存原函数
const self = this;
// 3. 返回一个新函数
const fBound = function(...newArgs) {
// 4. 如果作为构造函数调用(new fBound()),this指向实例
// 否则this指向绑定的context
return self.apply(
this instanceof fBound ? this : context,
args.concat(newArgs)
);
};
// 5. 维护原型链
if (this.prototype) {
fBound.prototype = Object.create(this.prototype);
}
return fBound;
};
// 测试1:普通函数
function greet(greeting, punctuation) {
return `${greeting}, ${this.name}${punctuation}`;
}
const person = { name: 'Bob' };
const boundGreet = greet.myBind(person, 'Hi');
console.log(boundGreet('!')); // "Hi, Bob!"
console.log(boundGreet('?')); // "Hi, Bob?"
// 测试2:构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
const BoundPerson = Person.myBind(null, 'Charlie');
const p = new BoundPerson(25);
console.log(p.name); // "Charlie"
console.log(p.age); // 25
bind的特点:
- 返回新函数,不立即执行
- 可以分步传参(柯里化)
- 作为构造函数时,绑定的this无效
- 维护原型链
简化版(不支持new):
Function.prototype.myBindSimple = function(context, ...args) {
const self = this;
return function(...newArgs) {
return self.apply(context, args.concat(newArgs));
};
};
8.8 函数柯里化
⭐⭐⭐⭐⭐ 将多参数函数转换为单参数函数序列
基础版本:
function curry(fn) {
return function curried(...args) {
// 如果参数够了,直接执行
if (args.length >= fn.length) {
return fn.apply(this, args);
}
// 参数不够,返回一个新函数继续接收参数
return function(...newArgs) {
return curried.apply(this, args.concat(newArgs));
};
};
}
// 测试
function add(a, b, c) {
return a + b + c;
}
const curriedAdd = curry(add);
console.log(curriedAdd(1)(2)(3)); // 6
console.log(curriedAdd(1, 2)(3)); // 6
console.log(curriedAdd(1)(2, 3)); // 6
高级版本(支持占位符):
function curry(fn, placeholder = '_') {
return function curried(...args) {
// 检查是否有占位符
const hasPlaceholder = args.some(arg => arg === placeholder);
// 没有占位符且参数够了,直接执行
if (!hasPlaceholder && args.length >= fn.length) {
return fn.apply(this, args);
}
// 返回新函数
return function(...newArgs) {
// 替换占位符
let index = 0;
const mergedArgs = args.map(arg => {
if (arg === placeholder && index < newArgs.length) {
return newArgs[index++];
}
return arg;
});
// 添加剩余参数
while (index < newArgs.length) {
mergedArgs.push(newArgs[index++]);
}
return curried.apply(this, mergedArgs);
};
};
}
// 测试
function sum(a, b, c, d) {
return a + b + c + d;
}
const curriedSum = curry(sum);
console.log(curriedSum(1, 2, 3, 4)); // 10
console.log(curriedSum('_', 2)(1, 3, 4)); // 10
console.log(curriedSum('_', '_', 3)(1)(2, 4)); // 10
无限参数柯里化:
function curryInfinity(fn) {
const args = [];
function curried(...newArgs) {
if (newArgs.length === 0) {
// 没有参数时,执行函数
return fn.apply(this, args);
}
// 收集参数
args.push(...newArgs);
return curried;
}
return curried;
}
// 测试
function sumAll(...nums) {
return nums.reduce((a, b) => a + b, 0);
}
const curriedSumAll = curryInfinity(sumAll);
console.log(curriedSumAll(1)(2)(3)(4)()); // 10
console.log(curriedSumAll(1, 2)(3, 4)()); // 10
应用场景:
- 参数复用
- 延迟执行
- 函数组合
- 事件处理
实际应用例子:
// 1. 正则验证
const match = curry((pattern, str) => pattern.test(str));
const hasNumber = match(/[0-9]+/);
const hasLetter = match(/[a-zA-Z]+/);
console.log(hasNumber('abc123')); // true
console.log(hasLetter('123')); // false
// 2. 日志记录
const log = curry((level, time, message) => {
console.log(`[${level}] ${time}: ${message}`);
});
const errorLog = log('ERROR');
const errorLogNow = errorLog(new Date().toISOString());
errorLogNow('Something went wrong'); // [ERROR] 2026-01-29...: Something went wrong
8.9 手写Object.create
⭐⭐⭐⭐⭐ 创建新对象并设置原型
function myCreate(proto, propertiesObject) {
// 1. 参数校验
if (typeof proto !== 'object' && typeof proto !== 'function') {
throw new TypeError('Object prototype may only be an Object or null');
}
// 2. 创建一个空的构造函数
function F() {}
// 3. 将构造函数的原型指向proto
F.prototype = proto;
// 4. 创建实例
const obj = new F();
// 5. 处理第二个参数(属性描述符)
if (propertiesObject !== undefined) {
Object.defineProperties(obj, propertiesObject);
}
return obj;
}
// 测试1:基本使用
const person = {
isHuman: true,
greet: function() {
console.log(`Hello, I'm ${this.name}`);
}
};
const john = myCreate(person);
john.name = 'John';
john.greet(); // "Hello, I'm John"
console.log(john.isHuman); // true
// 测试2:使用属性描述符
const obj = myCreate(Object.prototype, {
foo: {
value: 'bar',
writable: true,
enumerable: true,
configurable: true
},
baz: {
value: 42,
writable: false
}
});
console.log(obj.foo); // "bar"
console.log(obj.baz); // 42
简化版本:
function myCreateSimple(proto) {
function F() {}
F.prototype = proto;
return new F();
}
Object.create的特点:
- 创建一个新对象
- 新对象的
__proto__指向传入的原型对象 - 可以传入
null创建纯净对象(没有原型) - 第二个参数可以定义属性
使用场景:
// 1. 实现继承
function Parent(name) {
this.name = name;
}
Parent.prototype.sayHello = function() {
console.log(`Hello, I'm ${this.name}`);
};
function Child(name, age) {
Parent.call(this, name);
this.age = age;
}
// 使用Object.create实现原型继承
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;
// 2. 创建纯净对象(没有Object.prototype的方法)
const pureObj = Object.create(null);
pureObj.name = 'test';
console.log(pureObj.toString); // undefined(没有继承Object.prototype)
// 3. 克隆对象(保持原型链)
const original = { x: 1, y: 2 };
const clone = Object.create(
Object.getPrototypeOf(original),
Object.getOwnPropertyDescriptors(original)
);
📊 复杂度对比表
排序算法对比
| 算法 | 平均时间 | 最坏时间 | 空间 | 稳定性 | 适用场景 |
|---|---|---|---|---|---|
| 冒泡排序 | O(n²) | O(n²) | O(1) | 稳定 | 教学 |
| 选择排序 | O(n²) | O(n²) | O(1) | 不稳定 | 交换次数少 |
| 插入排序 | O(n²) | O(n²) | O(1) | 稳定 | 小规模/近乎有序 |
| 快速排序 | O(nlogn) | O(n²) | O(logn) | 不稳定 | 大规模数据 |
| 归并排序 | O(nlogn) | O(nlogn) | O(n) | 稳定 | 需要稳定排序 |
| 堆排序 | O(nlogn) | O(nlogn) | O(1) | 不稳定 | 空间受限 |
🎯 面试高频 TOP 15
- ⭐⭐⭐⭐⭐ Promise.all - 几乎必考
- ⭐⭐⭐⭐⭐ 深拷贝 - 基础必会
- ⭐⭐⭐⭐⭐ 手写call/apply/bind - 原理必会
- ⭐⭐⭐⭐⭐ 防抖节流 - 性能优化
- ⭐⭐⭐⭐⭐ 函数柯里化 - 函数式编程
- ⭐⭐⭐⭐⭐ 数组转树 - 业务常见
- ⭐⭐⭐⭐⭐ 快速排序 - 算法基础
- ⭐⭐⭐⭐⭐ EventEmitter - 设计模式
- ⭐⭐⭐⭐⭐ 手写new - 原理理解
- ⭐⭐⭐⭐⭐ 千分位格式化 - 字符串处理
- ⭐⭐⭐⭐⭐ 并发控制 - 异步编程
- ⭐⭐⭐⭐⭐ this指向 - 基础必会
- ⭐⭐⭐⭐⭐ 手写instanceof - 原型链理解
- ⭐⭐⭐⭐⭐ Object.create - 对象创建
- ⭐⭐⭐⭐⭐ Promise并发控制 - 高级异步
💡 学习建议
按优先级学习
-
必须掌握(⭐⭐⭐⭐⭐)
- Promise相关(all、race、并发控制)
- 深拷贝
- 手写call/apply/bind - 新增
- 函数柯里化 - 新增
- 防抖节流
- 数组转树
- EventEmitter
- Object.create - 新增
-
重点掌握(⭐⭐⭐⭐)
- 快速排序、归并排序
- 千分位格式化
- URL解析
- 手写new/instanceof
- 单例模式
- this指向问题
-
了解即可(⭐⭐⭐)
- 其他排序算法
- 数学算法
- 其他设计模式