干货|前端面试高频 ECMA 特性全解析

6 阅读6分钟

上午参加了一场面试,结合面试过程整理了面试中被反复追问的 ECMA 核心特性,从原理、使用场景、面试高频问题三个维度拆解,帮你吃透这些考点,面试不慌。

一、let/const(ES6 声明方式)

核心原理

  • 「块级作用域」:解决 var 变量提升、全局污染、重复声明问题,作用域限定在 {} 内。
  • 「暂时性死区(TDZ)」:let/const 声明前不可访问,避免变量提升导致的逻辑错误。
  • const 声明「只读常量」:指向的内存地址不可改(基本类型不可变,引用类型可修改属性)。

使用场景

  1. 循环遍历(for 循环用 let 避免闭包陷阱):
// 反例(var 全局污染)
for (var i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0) } // 输出 3,3,3
// 正例(let 块级作用域)
for (let i = 0; i < 3; i++) { setTimeout(() => console.log(i), 0) } // 输出 0,1,2
  1. 声明无需修改的常量(const):const BASE_URL = 'https://api.com'

面试高频问题

  1. var/let/const 的区别?(重点答作用域、提升、重复声明、TDZ)
  2. const 声明的对象可以修改属性吗?为什么?(内存地址不可变,引用类型值存在堆内存)
  3. 为什么 for 循环用 let 能解决闭包问题?(每次循环创建独立块级作用域)

二、Symbol(ES6 原始数据类型)

核心原理

  • 「唯一值」:Symbol() 生成的每个值都是独一无二的,解决对象属性名冲突问题。
  • 「不可枚举」:Symbol 作为对象属性时,for...in/Object.keys() 无法遍历到。
  • 「不可隐式转换」:不能与字符串/数字拼接,避免类型混乱。

使用场景

  1. 定义唯一的对象属性(避免覆盖):
const id = Symbol('id');
const user = { [id]: 123, name: '鱼姐' };
console.log(user[id]); // 123(只能通过 Symbol 本身访问)
  1. 定义常量枚举(避免魔法值):const STATUS = { SUCCESS: Symbol('success'), FAIL: Symbol('fail') }
  2. 实现私有属性(ES6 无私有属性时的替代方案)。

面试高频问题

  1. Symbol 是什么类型?有什么特点?(原始类型、唯一性、不可枚举)
  2. 如何获取对象中所有 Symbol 属性?(Object.getOwnPropertySymbols())
  3. Symbol.for() 和 Symbol() 的区别?(前者会在全局注册表缓存,后者每次生成新值)

三、Class(ES6 类语法)

核心原理

  • 「语法糖」:本质是函数,底层仍基于原型链实现,简化原型继承写法。
  • 「类的特性」:constructor 构造函数、静态方法(static)、继承(extends + super)。
  • 「不存在提升」:类声明前不可使用,类似 let/const。

使用场景

  1. 面向对象开发(替代 ES5 原型写法):
class Person {
  constructor(name) { this.name = name; }
  sayHi() { console.log(`Hi, ${this.name}`); }
  static create(name) { return new Person(name); } // 静态方法
}
class FrontEnd extends Person { // 继承
  constructor(name, skill) {
    super(name); // 调用父类构造函数
    this.skill = skill;
  }
}
const yu = new FrontEnd('鱼姐', 'JavaScript');
yu.sayHi(); // Hi, 鱼姐

面试高频问题

  1. Class 和 ES5 构造函数的区别?(语法糖、继承方式、静态方法、提升)
  2. super 的作用?(调用父类构造函数/方法,子类必须先调用 super 才能用 this)
  3. Class 中的私有属性如何实现?(ES2022 用 #,ES6 用 Symbol/闭包)

四、Reflect(ES6 反射)

核心原理

  • 「统一的对象操作 API」:将 Object 上的零散方法(如 Object.defineProperty)整合到 Reflect,返回布尔值表示操作是否成功。
  • 「函数式操作」:所有方法都是函数,避免 Object 部分方法报错、部分返回 undefined 的不一致性。
  • 「与 Proxy 配套」:Reflect 方法参数与 Proxy 陷阱参数一一对应,方便代理时保留原逻辑。

使用场景

  1. 替代 Object 方法(更友好的返回值):
// Object.defineProperty 失败会报错,Reflect.defineProperty 返回布尔值
const obj = {};
const success = Reflect.defineProperty(obj, 'name', { value: '鱼姐' });
console.log(success); // true
  1. 配合 Proxy 实现数据劫持:
const proxy = new Proxy(obj, {
  get(target, key) {
    return Reflect.get(target, key); // 保留原获取逻辑
  }
});

面试高频问题

  1. Reflect 有什么作用?(统一 API、函数式、配合 Proxy)
  2. Reflect 和 Object 的区别?(返回值、函数式、避免报错)
  3. 列举常用的 Reflect 方法?(get/set/defineProperty/has/deleteProperty 等)

五、Proxy(ES6 代理)

核心原理

  • 「对象拦截器」:代理目标对象,拦截并自定义对象的读取、赋值、删除等操作。
  • 「13 种拦截陷阱」:如 get(读取)、set(赋值)、deleteProperty(删除)、has(in 运算符)等。
  • 「非侵入式」:不修改原对象,通过代理对象操作,更灵活。

使用场景

  1. 数据响应式(Vue3 核心原理):
const obj = { name: '鱼姐' };
const proxy = new Proxy(obj, {
  get(target, key) {
    console.log('读取属性:', key);
    return Reflect.get(target, key);
  },
  set(target, key, value) {
    console.log('修改属性:', key, value);
    return Reflect.set(target, key, value);
  }
});
proxy.name = '前端鱼姐'; // 输出:修改属性:name 前端鱼姐
  1. 数据校验、日志记录、权限控制。

面试高频问题

  1. Proxy 能做什么?(数据劫持、响应式、校验、日志)
  2. Proxy 和 Object.defineProperty 的区别?(拦截范围、数组支持、返回值、嵌套对象)
  3. Vue2 和 Vue3 的响应式原理区别?(前者 Object.defineProperty,后者 Proxy)

六、Babel(ES6+ 转 ES5 工具)

核心原理

  • 「语法转换」:将 ES6+ 语法(如箭头函数、class)转为 ES5 兼容写法。
  • 「polyfill 补全」:通过 @babel/polyfill(core-js)补全 ES6+ 新 API(如 Promise、Array.prototype.includes)。
  • 「编译流程」:解析(Parse)→ 转换(Transform)→ 生成(Generate)。

使用场景

  1. 项目兼容低版本浏览器:配置 babel.config.json 实现自动转译。
  2. 按需引入 polyfill(避免体积过大):使用 core-js@3 配置 useBuiltIns: 'usage'。

面试高频问题

  1. Babel 的工作原理?(解析 AST、转换 AST、生成代码)
  2. Babel 如何处理新 API(如 Promise)?(polyfill 补全,区别于语法转换)
  3. @babel/preset-env 的作用?(根据目标环境自动确定需要转译的语法和补全的 polyfill)

七、箭头函数(ES6 函数语法)

核心原理

  • 「简化写法」:省略 function 关键字,单行表达式自动返回。
  • 「无 this 绑定」:箭头函数的 this 继承自外层作用域,不可修改(无 call/apply/bind 改变 this)。
  • 「无 arguments」:需用 rest 参数(...args)替代;不能作为构造函数(无 prototype)。

使用场景

  1. 简化回调函数(数组方法、定时器):
// 普通函数
const arr = [1,2,3].map(function(item) { return item * 2 });
// 箭头函数
const arr = [1,2,3].map(item => item * 2);
  1. 避免 this 指向混乱(如 React 组件、对象方法):
const obj = {
  name: '鱼姐',
  sayHi: () => {
    console.log(this.name); // this 指向全局,而非 obj
  }
};

面试高频问题

  1. 箭头函数和普通函数的区别?(this、arguments、构造函数、prototype)
  2. 为什么箭头函数不能作为构造函数?(无 prototype,无法 new)
  3. 什么时候不能用箭头函数?(对象方法、原型方法、需要动态 this 的场景)

八、异步(Promise/async/await)

核心原理

1. Promise

  • 「解决回调地狱」:将异步操作封装为状态机(pending/fulfilled/rejected),状态一旦改变不可逆转。
  • 「链式调用」:then/catch/finally 方法返回新 Promise,支持链式调用。

2. async/await

  • 「语法糖」:基于 Promise 实现,将异步代码同步化,避免链式调用。
  • 「错误处理」:需用 try/catch 捕获异常,替代 Promise.catch。

使用场景

  1. 接口请求(替代回调):
// Promise 写法
function fetchData() {
  return fetch('https://api.com')
    .then(res => res.json())
    .catch(err => console.log(err));
}
// async/await 写法
async function fetchData() {
  try {
    const res = await fetch('https://api.com');
    const data = await res.json();
    return data;
  } catch (err) {
    console.log(err);
  }
}

面试高频问题

  1. Promise 的状态和方法?(三种状态、then/catch/finally、all/race/allSettled/any)
  2. async/await 的执行顺序?(await 暂停执行,同步代码优先,异步代码进入微任务队列)
  3. 如何实现 Promise.all?(手写简化版,处理全部成功/部分失败)
  4. 回调地狱的解决方式?(Promise 链式调用、async/await)

九、Generator(ES6 生成器函数)

核心原理

  • 「暂停/恢复执行」:function* 定义生成器,yield 暂停执行,next() 恢复执行并返回 { value, done }。核心是「上下文保存 + 迭代器协议」
  • 「惰性求值」:每次 next() 才执行到下一个 yield,适合处理大数据/异步流。
  • 「与 Promise 结合」:可实现 async/await 类似的异步同步化。

使用场景

  1. 异步流程控制(早期替代 async/await):
function* gen() {
  const res1 = yield fetch('https://api1.com').then(res => res.json());
  const res2 = yield fetch(`https://api2.com?id=${res1.id}`).then(res => res.json());
  return res2;
}
// 执行生成器
const g = gen();
g.next().value.then(res1 => g.next(res1).value.then(res2 => g.next(res2)));
  1. 生成无限序列(如自增 ID)。

面试高频问题

  1. Generator 函数的特点?(暂停执行、yield、next 传参)
  2. Generator 和 async/await 的关系?(async/await 是 Generator + 自动执行器的语法糖)
  3. 如何实现 Generator 自动执行?(co 库原理,递归调用 next)

十、模板字符串(ES6 字符串扩展)

核心原理

  • 「反引号 `` 包裹」:支持多行字符串、变量插值(${})、表达式计算。
  • 「标签模板」:可自定义处理模板字符串(如过滤 HTML 转义)。

使用场景

  1. 拼接字符串/HTML(替代 + 号):
const name = '鱼姐';
const age = 36;
// 普通字符串
const str = '我是' + name + ',今年' + age + '岁';
// 模板字符串
const str = `我是${name},今年${age}岁`;
// 多行字符串
const html = `
  <div>
    <p>${name}</p>
  </div>
`;
  1. 标签模板(防 XSS):
function escapeHTML(strings, ...values) {
  return strings.reduce((res, str, i) => {
    const val = values[i] || '';
    return res + str + val.replace(/</g, '&lt;').replace(/>/g, '&gt;');
  }, '');
}
const unsafe = '<script>alert(1)</script>';
const safe = escapeHTML`内容:${unsafe}`; // 内容:&lt;script&gt;alert(1)&lt;/script&gt;

面试高频问题

  1. 模板字符串有什么优势?(多行、插值、表达式)
  2. 标签模板的作用?(自定义处理模板内容,如转义、国际化)

十一、Set/Map/WeakMap/WeakSet(ES6 集合)

核心原理

特性SetMapWeakSetWeakMap
存储形式唯一值集合(无键)键值对(key-value)唯一对象集合键为对象的键值对
键 / 值类型值:任意类型键:任意类型;值:任意值:仅对象键:仅对象;值:任意
引用类型强引用强引用弱引用弱引用(仅键)
可遍历性可遍历(forEach/for...of)可遍历不可遍历不可遍历
内存回收手动删除才回收手动删除才回收无引用时自动回收键无引用时自动回收
常用 APIadd/delete/has/sizeset/get/delete/has/sizeadd/delete/hasset/get/delete/has

使用场景

  1. Set 数组去重:
const arr = [1,2,2,3];
const uniqueArr = [...new Set(arr)]; // [1,2,3]
  1. Map 存储复杂键:
const map = new Map();
const objKey = { id: 1 };
map.set(objKey, '鱼姐');
console.log(map.get(objKey)); // 鱼姐
  1. WeakMap 缓存数据(避免内存泄漏):
const cache = new WeakMap();
function getObjData(obj) {
  if (cache.has(obj)) return cache.get(obj);
  const data = { name: '鱼姐' }; // 模拟获取数据
  cache.set(obj, data);
  return data;
}

面试高频问题

  1. Set 和 Array 的区别?(无重复、增删效率、遍历方式)
  2. Map 和 Object 的区别?(键类型、遍历、有序性、大小获取)
  3. WeakMap/WeakSet 的“弱引用”是什么?(垃圾回收不考虑弱引用,对象销毁后自动删除键值对,避免内存泄漏)
  4. 为什么 WeakMap 不可遍历?(弱引用可能随时被回收,无法保证遍历结果稳定)

十二、解构赋值(ES6 赋值语法)

核心原理

  • 「模式匹配」:将数组/对象的结构拆解,赋值给变量,简化取值操作。
  • 「默认值」:解构时可设置默认值,避免取值为 undefined。
  • 「剩余运算符」:... 收集剩余值,生成新数组/对象。

使用场景

  1. 数组解构:
const [a, b, ...rest] = [1,2,3,4]; // a=1, b=2, rest=[3,4]
const [x = 0] = []; // x=0(默认值)
  1. 对象解构:
const { name, age = 18 } = { name: '鱼姐' }; // name=鱼姐, age=18
const { name: userName } = { name: '鱼姐' }; // 重命名:userName=鱼姐
  1. 函数参数解构:
function fn({ name, age }) { console.log(name, age); }
fn({ name: '鱼姐', age: 36 });

面试高频问题

  1. 解构赋值的用途?(简化取值、函数参数、默认值、交换变量)
  2. 如何解构嵌套对象/数组?(举例说明多层解构)
  3. 解构失败会怎样?(变量值为 undefined)

十三、BigInt(ES2020 大数类型)

核心原理

  • 「解决数值溢出」:JavaScript 中 Number 最大值为 2^53 - 1,BigInt 可表示任意大整数。
  • 「声明方式」:数字后加 n(如 123n)或 BigInt() 构造函数。
  • 「不可与 Number 混合运算」:需显式转换类型。

使用场景

  1. 处理超大整数(如订单号、区块链ID):
const maxNum = Number.MAX_SAFE_INTEGER; // 9007199254740991
const bigNum = 9007199254740992n;
console.log(bigNum + 1n); // 9007199254740993n

面试高频问题

  1. BigInt 解决什么问题?(Number 大数溢出)
  2. BigInt 和 Number 的区别?(精度、运算、类型转换)
  3. 如何将 BigInt 转为 Number?(BigInt(123n) → 123,超出范围会丢失精度)

总结

  1. 面试中 ECMA 特性考察核心是「原理+场景」,而非死记语法,需结合实际开发案例理解;
  2. 高频考点集中在 let/const、Promise/async、Proxy、Set/Map、解构赋值,需重点掌握;
  3. 回答问题时尽量结合「底层原理+使用场景+反例」,体现对特性的深度理解。

这篇内容适配掘金技术社区风格,既有原理拆解,又有代码示例和面试考点,发布后能吸引前端求职者关注,也能强化你「前端鱼姐」的专业人设。如果需要调整篇幅或补充某个特性的细节,随时告诉我。