上午参加了一场面试,结合面试过程整理了面试中被反复追问的 ECMA 核心特性,从原理、使用场景、面试高频问题三个维度拆解,帮你吃透这些考点,面试不慌。
一、let/const(ES6 声明方式)
核心原理
- 「块级作用域」:解决 var 变量提升、全局污染、重复声明问题,作用域限定在
{}内。 - 「暂时性死区(TDZ)」:let/const 声明前不可访问,避免变量提升导致的逻辑错误。
- const 声明「只读常量」:指向的内存地址不可改(基本类型不可变,引用类型可修改属性)。
使用场景
- 循环遍历(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
- 声明无需修改的常量(const):
const BASE_URL = 'https://api.com'。
面试高频问题
- var/let/const 的区别?(重点答作用域、提升、重复声明、TDZ)
- const 声明的对象可以修改属性吗?为什么?(内存地址不可变,引用类型值存在堆内存)
- 为什么 for 循环用 let 能解决闭包问题?(每次循环创建独立块级作用域)
二、Symbol(ES6 原始数据类型)
核心原理
- 「唯一值」:Symbol() 生成的每个值都是独一无二的,解决对象属性名冲突问题。
- 「不可枚举」:Symbol 作为对象属性时,for...in/Object.keys() 无法遍历到。
- 「不可隐式转换」:不能与字符串/数字拼接,避免类型混乱。
使用场景
- 定义唯一的对象属性(避免覆盖):
const id = Symbol('id');
const user = { [id]: 123, name: '鱼姐' };
console.log(user[id]); // 123(只能通过 Symbol 本身访问)
- 定义常量枚举(避免魔法值):
const STATUS = { SUCCESS: Symbol('success'), FAIL: Symbol('fail') }。 - 实现私有属性(ES6 无私有属性时的替代方案)。
面试高频问题
- Symbol 是什么类型?有什么特点?(原始类型、唯一性、不可枚举)
- 如何获取对象中所有 Symbol 属性?(Object.getOwnPropertySymbols())
- Symbol.for() 和 Symbol() 的区别?(前者会在全局注册表缓存,后者每次生成新值)
三、Class(ES6 类语法)
核心原理
- 「语法糖」:本质是函数,底层仍基于原型链实现,简化原型继承写法。
- 「类的特性」:constructor 构造函数、静态方法(static)、继承(extends + super)。
- 「不存在提升」:类声明前不可使用,类似 let/const。
使用场景
- 面向对象开发(替代 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, 鱼姐
面试高频问题
- Class 和 ES5 构造函数的区别?(语法糖、继承方式、静态方法、提升)
- super 的作用?(调用父类构造函数/方法,子类必须先调用 super 才能用 this)
- Class 中的私有属性如何实现?(ES2022 用 #,ES6 用 Symbol/闭包)
四、Reflect(ES6 反射)
核心原理
- 「统一的对象操作 API」:将 Object 上的零散方法(如 Object.defineProperty)整合到 Reflect,返回布尔值表示操作是否成功。
- 「函数式操作」:所有方法都是函数,避免 Object 部分方法报错、部分返回 undefined 的不一致性。
- 「与 Proxy 配套」:Reflect 方法参数与 Proxy 陷阱参数一一对应,方便代理时保留原逻辑。
使用场景
- 替代 Object 方法(更友好的返回值):
// Object.defineProperty 失败会报错,Reflect.defineProperty 返回布尔值
const obj = {};
const success = Reflect.defineProperty(obj, 'name', { value: '鱼姐' });
console.log(success); // true
- 配合 Proxy 实现数据劫持:
const proxy = new Proxy(obj, {
get(target, key) {
return Reflect.get(target, key); // 保留原获取逻辑
}
});
面试高频问题
- Reflect 有什么作用?(统一 API、函数式、配合 Proxy)
- Reflect 和 Object 的区别?(返回值、函数式、避免报错)
- 列举常用的 Reflect 方法?(get/set/defineProperty/has/deleteProperty 等)
五、Proxy(ES6 代理)
核心原理
- 「对象拦截器」:代理目标对象,拦截并自定义对象的读取、赋值、删除等操作。
- 「13 种拦截陷阱」:如 get(读取)、set(赋值)、deleteProperty(删除)、has(in 运算符)等。
- 「非侵入式」:不修改原对象,通过代理对象操作,更灵活。
使用场景
- 数据响应式(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 前端鱼姐
- 数据校验、日志记录、权限控制。
面试高频问题
- Proxy 能做什么?(数据劫持、响应式、校验、日志)
- Proxy 和 Object.defineProperty 的区别?(拦截范围、数组支持、返回值、嵌套对象)
- 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)。
使用场景
- 项目兼容低版本浏览器:配置 babel.config.json 实现自动转译。
- 按需引入 polyfill(避免体积过大):使用 core-js@3 配置 useBuiltIns: 'usage'。
面试高频问题
- Babel 的工作原理?(解析 AST、转换 AST、生成代码)
- Babel 如何处理新 API(如 Promise)?(polyfill 补全,区别于语法转换)
- @babel/preset-env 的作用?(根据目标环境自动确定需要转译的语法和补全的 polyfill)
七、箭头函数(ES6 函数语法)
核心原理
- 「简化写法」:省略 function 关键字,单行表达式自动返回。
- 「无 this 绑定」:箭头函数的 this 继承自外层作用域,不可修改(无 call/apply/bind 改变 this)。
- 「无 arguments」:需用 rest 参数(...args)替代;不能作为构造函数(无 prototype)。
使用场景
- 简化回调函数(数组方法、定时器):
// 普通函数
const arr = [1,2,3].map(function(item) { return item * 2 });
// 箭头函数
const arr = [1,2,3].map(item => item * 2);
- 避免 this 指向混乱(如 React 组件、对象方法):
const obj = {
name: '鱼姐',
sayHi: () => {
console.log(this.name); // this 指向全局,而非 obj
}
};
面试高频问题
- 箭头函数和普通函数的区别?(this、arguments、构造函数、prototype)
- 为什么箭头函数不能作为构造函数?(无 prototype,无法 new)
- 什么时候不能用箭头函数?(对象方法、原型方法、需要动态 this 的场景)
八、异步(Promise/async/await)
核心原理
1. Promise
- 「解决回调地狱」:将异步操作封装为状态机(pending/fulfilled/rejected),状态一旦改变不可逆转。
- 「链式调用」:then/catch/finally 方法返回新 Promise,支持链式调用。
2. async/await
- 「语法糖」:基于 Promise 实现,将异步代码同步化,避免链式调用。
- 「错误处理」:需用 try/catch 捕获异常,替代 Promise.catch。
使用场景
- 接口请求(替代回调):
// 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);
}
}
面试高频问题
- Promise 的状态和方法?(三种状态、then/catch/finally、all/race/allSettled/any)
- async/await 的执行顺序?(await 暂停执行,同步代码优先,异步代码进入微任务队列)
- 如何实现 Promise.all?(手写简化版,处理全部成功/部分失败)
- 回调地狱的解决方式?(Promise 链式调用、async/await)
九、Generator(ES6 生成器函数)
核心原理
- 「暂停/恢复执行」:function* 定义生成器,yield 暂停执行,next() 恢复执行并返回 { value, done }。核心是「上下文保存 + 迭代器协议」
- 「惰性求值」:每次 next() 才执行到下一个 yield,适合处理大数据/异步流。
- 「与 Promise 结合」:可实现 async/await 类似的异步同步化。
使用场景
- 异步流程控制(早期替代 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)));
- 生成无限序列(如自增 ID)。
面试高频问题
- Generator 函数的特点?(暂停执行、yield、next 传参)
- Generator 和 async/await 的关系?(async/await 是 Generator + 自动执行器的语法糖)
- 如何实现 Generator 自动执行?(co 库原理,递归调用 next)
十、模板字符串(ES6 字符串扩展)
核心原理
- 「反引号 `` 包裹」:支持多行字符串、变量插值(${})、表达式计算。
- 「标签模板」:可自定义处理模板字符串(如过滤 HTML 转义)。
使用场景
- 拼接字符串/HTML(替代 + 号):
const name = '鱼姐';
const age = 36;
// 普通字符串
const str = '我是' + name + ',今年' + age + '岁';
// 模板字符串
const str = `我是${name},今年${age}岁`;
// 多行字符串
const html = `
<div>
<p>${name}</p>
</div>
`;
- 标签模板(防 XSS):
function escapeHTML(strings, ...values) {
return strings.reduce((res, str, i) => {
const val = values[i] || '';
return res + str + val.replace(/</g, '<').replace(/>/g, '>');
}, '');
}
const unsafe = '<script>alert(1)</script>';
const safe = escapeHTML`内容:${unsafe}`; // 内容:<script>alert(1)</script>
面试高频问题
- 模板字符串有什么优势?(多行、插值、表达式)
- 标签模板的作用?(自定义处理模板内容,如转义、国际化)
十一、Set/Map/WeakMap/WeakSet(ES6 集合)
核心原理
| 特性 | Set | Map | WeakSet | WeakMap |
|---|---|---|---|---|
| 存储形式 | 唯一值集合(无键) | 键值对(key-value) | 唯一对象集合 | 键为对象的键值对 |
| 键 / 值类型 | 值:任意类型 | 键:任意类型;值:任意 | 值:仅对象 | 键:仅对象;值:任意 |
| 引用类型 | 强引用 | 强引用 | 弱引用 | 弱引用(仅键) |
| 可遍历性 | 可遍历(forEach/for...of) | 可遍历 | 不可遍历 | 不可遍历 |
| 内存回收 | 手动删除才回收 | 手动删除才回收 | 无引用时自动回收 | 键无引用时自动回收 |
| 常用 API | add/delete/has/size | set/get/delete/has/size | add/delete/has | set/get/delete/has |
使用场景
- Set 数组去重:
const arr = [1,2,2,3];
const uniqueArr = [...new Set(arr)]; // [1,2,3]
- Map 存储复杂键:
const map = new Map();
const objKey = { id: 1 };
map.set(objKey, '鱼姐');
console.log(map.get(objKey)); // 鱼姐
- 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;
}
面试高频问题
- Set 和 Array 的区别?(无重复、增删效率、遍历方式)
- Map 和 Object 的区别?(键类型、遍历、有序性、大小获取)
- WeakMap/WeakSet 的“弱引用”是什么?(垃圾回收不考虑弱引用,对象销毁后自动删除键值对,避免内存泄漏)
- 为什么 WeakMap 不可遍历?(弱引用可能随时被回收,无法保证遍历结果稳定)
十二、解构赋值(ES6 赋值语法)
核心原理
- 「模式匹配」:将数组/对象的结构拆解,赋值给变量,简化取值操作。
- 「默认值」:解构时可设置默认值,避免取值为 undefined。
- 「剩余运算符」:... 收集剩余值,生成新数组/对象。
使用场景
- 数组解构:
const [a, b, ...rest] = [1,2,3,4]; // a=1, b=2, rest=[3,4]
const [x = 0] = []; // x=0(默认值)
- 对象解构:
const { name, age = 18 } = { name: '鱼姐' }; // name=鱼姐, age=18
const { name: userName } = { name: '鱼姐' }; // 重命名:userName=鱼姐
- 函数参数解构:
function fn({ name, age }) { console.log(name, age); }
fn({ name: '鱼姐', age: 36 });
面试高频问题
- 解构赋值的用途?(简化取值、函数参数、默认值、交换变量)
- 如何解构嵌套对象/数组?(举例说明多层解构)
- 解构失败会怎样?(变量值为 undefined)
十三、BigInt(ES2020 大数类型)
核心原理
- 「解决数值溢出」:JavaScript 中 Number 最大值为 2^53 - 1,BigInt 可表示任意大整数。
- 「声明方式」:数字后加 n(如 123n)或 BigInt() 构造函数。
- 「不可与 Number 混合运算」:需显式转换类型。
使用场景
- 处理超大整数(如订单号、区块链ID):
const maxNum = Number.MAX_SAFE_INTEGER; // 9007199254740991
const bigNum = 9007199254740992n;
console.log(bigNum + 1n); // 9007199254740993n
面试高频问题
- BigInt 解决什么问题?(Number 大数溢出)
- BigInt 和 Number 的区别?(精度、运算、类型转换)
- 如何将 BigInt 转为 Number?(BigInt(123n) → 123,超出范围会丢失精度)
总结
- 面试中 ECMA 特性考察核心是「原理+场景」,而非死记语法,需结合实际开发案例理解;
- 高频考点集中在 let/const、Promise/async、Proxy、Set/Map、解构赋值,需重点掌握;
- 回答问题时尽量结合「底层原理+使用场景+反例」,体现对特性的深度理解。
这篇内容适配掘金技术社区风格,既有原理拆解,又有代码示例和面试考点,发布后能吸引前端求职者关注,也能强化你「前端鱼姐」的专业人设。如果需要调整篇幅或补充某个特性的细节,随时告诉我。