js面试100个经典算法:从基础语法到复杂逻辑的深度突破
在如今的前端招聘市场中,js面试100个经典算法已成为检验开发者基础功底的重要标准。无论是BAT等一线大厂,还是新兴的独角兽企业,JavaScript算法题几乎出现在每一轮技术面试中。这不仅仅是因为算法能体现逻辑思维,更因为它能反映候选人对语言底层原理的理解深度。本站精心整理了这份指南,旨在帮助求职者系统性地掌握这些核心知识点,从基础的排序算法到复杂的动态规划,全方位提升你的竞争力。
① 基础语法与数据类型
掌握JavaScript的基本数据类型,如Number、String、Boolean、Undefined、Null、Symbol以及ES6引入的BigInt。理解基本类型与引用类型的区别,这是处理内存管理和数据拷贝的基础。
② 作用域与闭包
闭包是JavaScript最强大的特性之一,也是js面试100个经典算法中的常客。理解词法作用域、执行上下文栈以及内存泄漏原理,是编写高效代码的关键。
③ 异步编程模型
从回调地狱到Promise,再到Async/Await,异步编程是前端开发的痛点。深入理解Event Loop机制、宏任务与微任务的执行顺序,是解决并发问题的核心。
【核心考点】js面试100个经典算法分类解析
为了更清晰地梳理知识体系,我们将js面试100个经典算法划分为以下几个核心板块。每个板块都包含了高频出现的题目及其解题思路。
1. 数组与字符串处理
数组和字符串是JavaScript中最常用的数据结构。常见的面试题目包括:去重、扁平化、排序、查找最大值/最小值、旋转矩阵等。
| 算法名称 | 难度 | 核心知识点 | 典型场景 |
|---|---|---|---|
| 数组去重 | ⭐ | Set, Filter, Reduce | 数据清洗,用户列表展示 |
| 数组扁平化 | ⭐⭐ | 递归, toString, flat() | 树形结构数据展平 |
| 排序算法 | ⭐⭐⭐ | 快排, 归并, 堆排序 | 性能优化,数据可视化 |
| 字符串反转 | ⭐ | split, reverse, join | 回文判断,文本处理 |
2. 面向对象与原型链
JavaScript是一门基于原型的语言。理解原型链(Prototype Chain)是理解继承机制的基础。面试中常考手写new操作符、实现类式继承以及原型链污染的安全问题。
// 示例:手写一个简易的new操作符
function myNew(constructor, ...args) {
// 1. 创建一个空对象
const obj = {};
// 2. 将空对象的原型指向构造函数的原型
Object.setPrototypeOf(obj, constructor.prototype);
// 3. 执行构造函数,绑定this
const result = constructor.apply(obj, args);
// 4. 返回对象(如果构造函数返回的是对象,则返回该对象,否则返回新建的对象)
return (typeof result === 'object' && result !== null) ? result : obj;
}
3. 算法与数据结构
这部分是js面试100个经典算法中最具挑战性的部分。包括但不限于:链表反转、二叉树遍历(前中后序、层序)、最短路径(Dijkstra)、动态规划(背包问题、最长公共子序列)等。这些题目不仅考察编码能力,更考察对时间复杂度和空间复杂度的权衡。
【深度解析】常见算法题代码实现与优化
仅仅知道思路是不够的,在面试中,写出正确且高效的代码才是关键。以下我们将选取几个高频题目进行深度解析。
防抖(Debounce)与节流(Throttle)
这两个概念在前端性能优化中至关重要。防抖是指在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时;节流是指一定时间内只执行一次函数。
// 防抖函数实现
function debounce(fn, delay) {
let timer = null;
return function(...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
// 节流函数实现
function throttle(fn, interval) {
let lastTime = 0;
return function(...args) {
let now = Date.now();
if (now - lastTime >= interval) {
lastTime = now;
fn.apply(this, args);
}
};
}
Promise.all 的实现
Promise.all接收一个Promise对象数组作为参数,返回一个新的Promise。只有当所有Promise都成功时,才返回成功,只要有一个失败,就返回失败。
function promiseAll(promises) {
return new Promise((resolve, reject) => {
let results = [];
let resolvedCount = 0;
promises.forEach((promise, index) => {
Promise.resolve(promise)
.then(res => {
results[index] = res;
resolvedCount++;
if (resolvedCount === promises.length) {
resolve(results);
}
})
.catch(err => {
reject(err);
});
});
});
}
二叉树的前序遍历
二叉树是面试中的高频考点。前序遍历的顺序是:根节点 -> 左子树 -> 右子树。可以使用递归或栈来实现。
// 递归实现
function preorderTraversal(root) {
if (!root) return [];
return [
root.val,
...preorderTraversal(root.left),
...preorderTraversal(root.right)
];
}
// 迭代实现(使用栈)
function preorderTraversalIterative(root) {
if (!root) return [];
let stack = [root];
let result = [];
while (stack.length) {
let node = stack.pop();
result.push(node.val);
if (node.right) stack.push(node.right);
if (node.left) stack.push(node.left);
}
return result;
}
【技术演进】JavaScript算法发展时间轴
了解js面试100个经典算法背后的技术演进,有助于我们从历史角度理解为什么某些模式会成为标准。以下是JavaScript在算法和工程化方面的重要里程碑。
1995年:JavaScript诞生
Brendan Eich在10天内设计了JavaScript的雏形。早期的JavaScript主要用于简单的表单验证,算法需求极低。
2009年:Node.js发布
Node.js的出现让JavaScript能够运行在服务器端。这极大地拓展了JavaScript的应用场景,使得算法不再局限于浏览器端,后端算法题也开始进入前端面试视野。
2015年:ES6(ES2015)发布
ES6引入了Class、Promise、Arrow Functions、Let/Const等新特性。这改变了编程范式,面试重点从传统的函数式编程转向了面向对象与函数式编程的结合。
2020年至今:TypeScript与算法进阶
TypeScript的普及使得静态类型检查成为主流。面试中开始更多考察类型体操(Type Chops)以及复杂数据结构在强类型语言中的实现。
【高频问答】网友最关心的10个算法问题
在准备js面试100个经典算法的过程中,求职者往往会对某些概念感到困惑。以下是基于搜索引擎数据和社区讨论整理的十大高频问题及深度解答。
Q1: 为什么JavaScript中的0.1 + 0.2 !== 0.3?
这是由于IEEE 754浮点数精度丢失问题。JavaScript使用双精度64位格式来表示数值,无法精确表示某些十进制小数。解决方法是使用Number.EPSILON进行比较,或者将数字转换为整数运算后再除回去。
Q2: 深拷贝和浅拷贝的区别是什么?如何实现深拷贝?
浅拷贝只复制基本类型值或引用类型的地址,深拷贝则递归复制所有层级的对象。简单的深拷贝可以使用JSON.parse(JSON.stringify(obj)),但它无法处理函数、RegExp、Date等类型。更完善的方案是使用递归或第三方库如Lodash的_.cloneDeep。
Q3: 什么是事件循环(Event Loop)?
JavaScript是单线程语言,通过事件循环机制实现异步编程。主线程执行同步代码,遇到异步任务将其放入任务队列。当主线程空闲时,从任务队列中取出宏任务或微任务执行。理解Event Loop对于调试异步代码至关重要。
Q4: 如何实现一个通用的节流函数?
节流函数有两种常见实现:时间戳版和定时器版。时间戳版在第一次触发时立即执行,之后在间隔内不再执行;定时器版则在间隔结束后执行最后一次。根据业务场景选择合适的实现方式。
Q5: 什么是闭包?闭包有什么优缺点?
闭包是指有权访问另一个函数作用域中变量的函数。优点是延长变量的生命周期,实现数据私有化;缺点是可能导致内存泄漏,如果不当使用,会占用大量内存。
Q6: 如何判断一个对象是否为数组?
最准确的方法是使用Array.isArray(obj)。也可以使用Object.prototype.toString.call(obj) === '[object Array]'。注意instanceof在不同iframe环境下可能失效。
Q7: 手写Promise需要掌握哪些核心逻辑?
需要掌握状态管理(Pending, Fulfilled, Rejected)、链式调用(then方法返回新的Promise)、错误捕获(catch方法)以及异步任务的顺序执行。
Q8: 什么是原型链?原型链断裂是怎么回事?
原型链是JavaScript实现继承的机制。每个对象都有一个原型(__proto__),指向其构造函数的原型(prototype)。原型链断裂通常发生在重新赋值原型对象时,导致实例与原型的连接断开。
Q9: 如何实现一个发布-订阅模式?
发布-订阅模式包含一个事件中心,用于管理事件的注册、触发和移除。实现时需要维护一个事件列表,每个事件对应一个回调函数数组。发布时遍历数组执行回调。
Q10: 前端性能优化中,算法能起到什么作用?
高效的算法可以显著减少计算时间,尤其是在处理大量数据时。例如,使用二分查找代替线性查找,使用布隆过滤器代替线性扫描。在渲染优化中,虚拟DOM的Diff算法也是基于高效的比较逻辑。