前端JavaScript面试题2020|系统化备考·高频考点·真题精解
本页面全面收录2020年主流互联网企业JavaScript面试高频真题与深度解析,覆盖闭包、原型链、this指向、事件循环、ES6+新特性、异步编程、性能优化等核心模块,结合真实面试场景,提供解题思路、易错点提示与扩展延伸,助你构建完整的知识体系,从容应对技术面试挑战。
立即查看核心考点一、核心考点精讲(高频必考)
1.1 闭包(Closure)——面试“常青树”
典型问题:请解释什么是闭包?闭包的作用有哪些?使用闭包可能引发哪些问题?
答案要点:闭包是指函数嵌套时,内部函数可以访问外部函数作用域中的变量,并在外部函数执行完毕后仍保持对这些变量的引用。本质是“函数 + 其词法环境的组合”。
典型应用场景:
- 封装私有变量(模块化前的实现方式)
- 回调函数与事件监听器(如定时器、AJAX回调)
- 函数柯里化(Currying)与偏函数应用
- 实现防抖(debounce)与节流(throttle)
经典例题解析:
// 问题:输出结果是什么?为什么?for (var i = 1; i ≤ 5; i++) {setTimeout(() => console.log(i), i * 1000);}// 输出:6 6 6 6 6(每秒一个)// 原因:var 声明的 i 是函数作用域,所有回调共享同一个 i;循环结束时 i=6// 解法1:使用 let(块级作用域)for (let i = 1; i ≤ 5; i++) {setTimeout(() => console.log(i), i * 1000);}// 输出:1 2 3 4 5// 解法2:IIFE(立即执行函数)创建新作用域for (var i = 1; i ≤ 5; i++) {(function(j) {setTimeout(() => console.log(j), j * 1000);})(i);}
扩展延伸:
在 React Hooks 中,`useEffect` 的依赖数组与闭包密切相关——若未正确处理依赖,可能导致 stale closure(陈旧闭包)问题。例如:
const [count, setCount] = useState(0);useEffect(() => {const timer = setInterval(() => {console.log(count); // ❌ 永远输出 0!}, 1000);}, []); // 空依赖 → count 捕获初始值
正确做法:添加 `count` 到依赖数组,或使用函数式更新 `setCount(c => c + 1)`。
1.2 原型链与继承(理解对象机制的关键)
核心概念:
- 每个函数都有 `prototype` 属性(原型对象)
- 每个实例对象都有 `__proto__` 指向构造函数的 `prototype`
- `constructor` 属性指向构造函数本身
- 原型链终点:`Object.prototype.__proto__ === null`
面试高频题:
function A() {}A.prototype.x = 10;const a = new A();console.log(a.x); // 10A.prototype = { x: 20 }; // 重写原型(断开原原型链!)const b = new A();console.log(a.x, b.x); // 10 20
⚠️ 关键点:`a` 实例在创建时已绑定 `__proto__` 指向旧原型;`b` 指向新原型,因此结果不同。
继承实现方式对比:
原型链继承
function Parent() { this.name = 'parent'; }Parent.prototype.say = function() { console.log(this.name); };function Child() {}Child.prototype = new Parent(); // ❌ 引用类型属性共享问题
缺点:无法向父构造函数传参;子实例共享父实例的引用属性。
构造函数继承(盗用构造函数)
function Parent(name) { this.name = name; this.colors = ['red']; }function Child(name) { Parent.call(this, name); }const c1 = new Child('Tom');const c2 = new Child('Jerry');c1.colors.push('blue');console.log(c1.colors, c2.colors); // ['red','blue'] ['red']
优点:避免引用共享;可传参。缺点:方法在构造函数中定义,无法复用。
组合继承(原型链 + 构造函数)
function Parent(name) { this.name = name; this.colors = ['red']; }Parent.prototype.say = function() { console.log(this.name); };function Child(name, age) {Parent.call(this, name); // 第二次调用 Parentthis.age = age;}Child.prototype = new Parent(); // 第一次调用 ParentChild.prototype.constructor = Child;
缺点:调用两次父构造函数(内存浪费)。现代方案推荐寄生组合继承。
ES6 class 继承
class Parent {constructor(name) { this.name = name; this.colors = ['red']; }say() { console.log(this.name); }}class Child extends Parent {constructor(name, age) {super(name); // 自动调用 Parent.call(this, name)this.age = age;}}
本质仍是基于原型的语法糖,但更符合面向对象思维,且 `super` 自动处理原型链。
1.3 this 指向——最易混淆的考点
四条绑定规则:
- 默认绑定:独立函数调用 → window(严格模式下为 undefined)
- 隐式绑定:对象方法调用 → 调用对象
- 显式绑定:`call/apply/bind` → 指定对象
- new 绑定:`new` 构造函数 → 新创建的对象
典型陷阱题:
const obj = {name: 'Alice',say: function() { console.log(this.name); },delay: function() {setTimeout(function() { console.log(this.name); }, 100);}};obj.delay(); // undefined(window.name)// 解法1:箭头函数(词法 this)delay: function() {setTimeout(() => console.log(this.name), 100);}// 解法2:保存 thisdelay: function() {const self = this;setTimeout(function() { console.log(self.name); }, 100);}
1.4 事件循环(Event Loop)——异步机制核心
执行栈(Call Stack) + 任务队列(Task Queue) + 微任务队列(Microtask Queue)
- 宏任务(Macro Task):script、setTimeout、setInterval、I/O、UI渲染
- 微任务(Micro Task):Promise.then、MutationObserver、queueMicrotask
经典题型:
console.log('A');setTimeout(() => console.log('B'), 0);Promise.resolve().then(() => console.log('C'));console.log('D');// 输出顺序:A → D → C → B// 解析:// 1. 执行同步代码 A、D// 2. 微任务队列:C// 3. 渲染(可选)// 4. 宏任务队列:B
进阶题(含 async/await):
async function async1() {console.log('async1 start');await async2();console.log('async1 end');}async function async2() {console.log('async2');}console.log('script start');setTimeout(() => console.log('setTimeout'), 0);async1();new Promise(resolve => {console.log('promise1');resolve();}).then(() => console.log('promise2'));// 输出:script start → async1 start → async2 → promise1 → async1 end → promise2 → setTimeout
关键点:`await` 后的代码被放入微任务队列,且优先级高于 `.then`。
ES6+ 必考新特性
解构赋值、箭头函数、let/const、模板字符串、默认参数、展开运算符(...)、模块化(import/export)、Map/Set、Symbol
异步编程演进
回调函数 → Promise → async/await → 事件监听 → 观察者模式
性能优化核心
减少重排重绘、懒加载、虚拟滚动、Web Worker、Bundle 优化、HTTP/2 多路复用
安全防护要点
XSS 防御(输入过滤/输出编码)、CSRF 防御(Token/Referer 检查)、CSP 策略、HTTPS
二、进阶专题(中高级岗位必考)
2.1 执行上下文与作用域链
执行上下文三要素:
- 变量对象(VO)/活动对象(AO):存储变量、函数声明、参数
- 作用域链:变量查找路径
- this 值
作用域链解析:
var x = 10;function foo() {var y = 20;function bar() {var z = 30;console.log(x + y + z);}return bar;}const fn = foo();fn(); // 60
bar 的作用域链:bar.AO → foo.AO → global.VO
2.2 模块化(ESM vs CommonJS)
核心区别:
| 特性 | CommonJS | ES Module |
|---|---|---|
| 加载方式 | 运行时加载(同步) | 编译时加载(静态分析) |
| 输出值 | 值的拷贝 | 值的引用(动态绑定) |
| this 指向 | module.exports | undefined |
2.3 设计模式实战
前端高频设计模式:
- 单例模式:全局状态管理(如 Redux store)
- 观察者模式:事件监听系统(DOM 事件、自定义事件)
- 发布-订阅模式:消息总线(EventBus)
- 装饰器模式:装饰器语法(@Component、@Bind)
- 工厂模式:React.createElement、Vue 组件创建
观察者模式实现:
class Subject {constructor() {this.observers = [];}subscribe(fn) { this.observers.push(fn); }unsubscribe(fn) { this.observers = this.observers.filter(f => f !== fn); }notify(data) { this.observers.forEach(fn => fn(data)); }}// 使用const publisher = new Subject();publisher.subscribe(data => console.log('Observer1:', data));publisher.subscribe(data => console.log('Observer2:', data));publisher.notify('Hello!');
2.4 Web 安全深度解析
XSS 攻击类型:
- 存储型 XSS:攻击脚本存储在服务器(如评论区注入)
- 反射型 XSS:通过 URL 参数反射回页面(如搜索页)
- DOM 型 XSS:完全在客户端执行(如 `innerHTML = location.hash`)
防御措施:
- 输入过滤:白名单校验(如邮箱、手机号格式)
- 输出编码:HTML 实体编码、JavaScript 字符串转义
- CSP(Content Security Policy):限制资源加载来源
- HttpOnly Cookie:防止 JS 读取 Cookie
三、2020 真题实战解析
3.1 字节跳动:事件循环综合题
题目:请写出以下代码的输出顺序:
console.log('1');setTimeout(() => console.log('2'), 0);Promise.resolve().then(() => console.log('3'));Promise.resolve().then(() => console.log('4'));setTimeout(() => console.log('5'), 0);console.log('6');
答案:1 → 6 → 3 → 4 → 2 → 5
解析:
- 同步代码:1, 6
- 微任务队列:3, 4(按注册顺序执行)
- 宏任务队列:2, 5(按注册顺序执行)
3.2 腾讯:原型链深度题
题目:如何判断一个对象是否是数组?请写出至少三种方法并说明优劣。
// 方法1:Array.isArray(obj)Array.isArray([]); // trueArray.isArray({}); // false// 优点:标准方法,推荐使用// 方法2:obj instanceof Array[] instanceof Array; // true// 缺点:跨 iframe 时失效(不同全局环境)// 方法3:Object.prototype.toString.call(obj) === '[object Array]'Object.prototype.toString.call([]); // "[object Array]"// 优点:最可靠,跨 iframe 有效// 方法4:Array.prototype.isPrototypeOf(obj)Array.prototype.isPrototypeOf([]); // true// 优点:基于原型链判断
3.3 阿里:this 指向陷阱题
题目:以下代码输出什么?如何修复?
const obj = {name: 'Alice',getName: function() { return this.name; },delayedGetName: function() {const fn = this.getName;return fn();}};console.log(obj.getName()); // Aliceconsole.log(obj.delayedGetName()); // undefined
修复方案:
// 方案1:使用 bind 固定 thisdelayedGetName: function() {const fn = this.getName.bind(this);return fn();}// 方案2:使用箭头函数delayedGetName: function() {const fn = () => this.getName();return fn();}
3.4 美团:异步并发控制
题目:实现一个 `limit` 函数,支持并发数限制的 Promise 执行器。
function limit(promiseFactories, limitCount) {return new Promise((resolve, reject) => {const results = [];let index = 0;let activeCount = 0;function next() {while (activeCount < limitCount && index < promiseFactories.length) {const promise = promiseFactories[index++];activeCount++;promise().then(result => {results.push(result);activeCount--;if (index === promiseFactories.length && activeCount === 0) {resolve(results);} else {next();}}).catch(reject);}}next();});}
该实现确保最多同时执行 `limitCount` 个异步任务,适用于图片懒加载、API 批量请求等场景。
四、前端技术趋势与 2020 年面试变化
4.1 2020 年 JS 生态重大变化
- ES2020 正式落地:可选链(?.)、空值合并(??)、动态 import()
- WebAssembly 成熟:性能关键模块新选择
- React Hooks 普及:函数式组件成为主流
- Vite 爆发式增长:基于 ESM 的开发服务器,冷启动快 10 倍
- 微前端架构普及:大型项目拆分新方案
4.2 面试考察重点迁移
2019 年关注点
基础语法、DOM 操作、jQuery、传统 MVC
2020 年新要求
TypeScript、函数式编程、响应式原理(Proxy)、性能优化、工程化(Webpack/Vite)、微前端、安全意识
4.3 网友们还关心……
非科班如何准备 JS 面试?
① 系统学习《JavaScript 高级程序设计》;② 深入理解 MDN 文档;③ 用 Vite 搭建项目实践;④ 参与开源贡献;⑤ 重视算法(LeetCode 简中等)
应届生如何弥补经验不足?
① 做 2-3 个完整项目(含部署);② 深度复盘项目难点;③ 准备技术博客;④ 理解计算机基础(网络/OS);⑤ 熟悉 Git 工作流
远程面试常见陷阱
① 面试官要求共享屏幕写代码;② 要求实时调试线上项目;③ 突然切换到算法题;④ 问“你有什么问题想问我们?”——必须准备 3 个高质量问题
4.4 技术面试后复盘建议
每次面试后记录:
- 问题清单(未答出/答错题)
- 考察点分析(考察知识深度/广度/实战能力)
- 改进计划(补充资料/实践项目)
- 后续跟进(感谢邮件、补充材料)
五、高频问题答疑(FAQ)
5.1 关于本页面
Q:本页面内容是否覆盖所有 JS 面试考点?
A:覆盖 95% 以上中高级岗位核心考点,但具体面试会因公司/岗位微调,建议结合目标公司JD针对性补充。
Q:2020 年的题到 2024 年还有效吗?
A:基础原理(闭包、原型链、事件循环)永不过时;新特性(如 Optional Chaining)可参考最新 MDN 补充。
5.2 关于学习方法
Q:如何高效记忆 JS 知识点?
A:采用 费曼学习法:① 选择知识点;② 用简单语言解释;③ 发现盲点;④ 重讲并简化。配合手写代码实现,效果提升 200%。
Q:需要背题吗?
A:不建议死记硬背!重点理解:
- 为什么需要这个机制?(如闭包解决变量作用域问题)
- 有什么替代方案?(如 Promise vs async/await)
- 实际场景中如何应用?(如防抖在搜索框的应用)
5.3 关于职业发展
Q:JS 工程师未来发展方向?
A:可向以下方向深化:
- 全栈开发:Node.js + 数据库 + DevOps
- 前端架构师:工程化、性能优化、技术选型
- 跨端开发:React Native / Flutter(部分 JS 基础复用)
- 图形/可视化:WebGL / D3.js / Three.js
- 底层工具链:编译器(Babel)、构建工具(Rollup)