欢迎访问 www.kaoacca.cn|前端JavaScript面试题权威指南|2020最新版

前端JS面试题2020

前端JavaScript面试题2020|系统化备考·高频考点·真题精解

本页面全面收录2020年主流互联网企业JavaScript面试高频真题与深度解析,覆盖闭包、原型链、this指向、事件循环、ES6+新特性、异步编程、性能优化等核心模块,结合真实面试场景,提供解题思路、易错点提示与扩展延伸,助你构建完整的知识体系,从容应对技术面试挑战。

立即查看核心考点

一、核心考点精讲(高频必考)

1.1 闭包(Closure)——面试“常青树”

典型问题:请解释什么是闭包?闭包的作用有哪些?使用闭包可能引发哪些问题?

答案要点:闭包是指函数嵌套时,内部函数可以访问外部函数作用域中的变量,并在外部函数执行完毕后仍保持对这些变量的引用。本质是“函数 + 其词法环境的组合”。

⚠️ 注意:闭包的常见误区是认为“返回函数就一定是闭包”——实际上必须满足两个条件:① 函数嵌套;② 内部函数引用了外部变量。

典型应用场景:

经典例题解析:

// 问题:输出结果是什么?为什么?
      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 原型链与继承(理解对象机制的关键)

核心概念:

面试高频题:

function A() {}
      A.prototype.x = 10;
      
      const a = new A();
      console.log(a.x); // 10
      
      A.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); // 第二次调用 Parent
          this.age = age;
        }
        Child.prototype = new Parent(); // 第一次调用 Parent
        Child.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 指向——最易混淆的考点

四条绑定规则:

  1. 默认绑定:独立函数调用 → window(严格模式下为 undefined)
  2. 隐式绑定:对象方法调用 → 调用对象
  3. 显式绑定:`call/apply/bind` → 指定对象
  4. 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:保存 this
      delay: function() {
        const self = this;
        setTimeout(function() { console.log(self.name); }, 100);
      }

1.4 事件循环(Event Loop)——异步机制核心

执行栈(Call Stack) + 任务队列(Task Queue) + 微任务队列(Microtask Queue)

经典题型:

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 执行上下文与作用域链

执行上下文三要素:

  1. 变量对象(VO)/活动对象(AO):存储变量、函数声明、参数
  2. 作用域链:变量查找路径
  3. 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
⚠️ 常见陷阱:CommonJS 的 `module.exports = {}` 会覆盖原对象,导致 `exports` 失效;ESM 的导出是只读引用,不可重新赋值。

2.3 设计模式实战

前端高频设计模式:

观察者模式实现:

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 攻击类型:

  1. 存储型 XSS:攻击脚本存储在服务器(如评论区注入)
  2. 反射型 XSS:通过 URL 参数反射回页面(如搜索页)
  3. DOM 型 XSS:完全在客户端执行(如 `innerHTML = location.hash`)

防御措施:

⚠️ 重要:`innerHTML` 是 XSS 高危操作!优先使用 `textContent` 或 `createElement + appendChild`。

三、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

解析:

3.2 腾讯:原型链深度题

题目:如何判断一个对象是否是数组?请写出至少三种方法并说明优劣。

// 方法1:Array.isArray(obj)
      Array.isArray([]); // true
      Array.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()); // Alice
      console.log(obj.delayedGetName()); // undefined

修复方案:

// 方案1:使用 bind 固定 this
      delayedGetName: 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 批量请求等场景。

五、高频问题答疑(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:可向以下方向深化:

斌知网
蜀ICP备2026017620号-5