ES6+ 新特性
1. 变量声明 (let / const)
1.1 块级作用域
javascript
// var - 函数作用域
if (true) {
var a = 1;
}
console.log(a); // 1 (泄漏到外部)
// let/const - 块级作用域
if (true) {
let b = 2;
const c = 3;
}
console.log(b); // ReferenceError1.2 暂时性死区 (TDZ)
javascript
console.log(x); // undefined (var 提升)
var x = 1;
console.log(y); // ReferenceError (TDZ)
let y = 2;
// TDZ: 从块作用域开始到变量声明之间的区域
{
// --- TDZ 开始 ---
console.log(z); // ReferenceError
// --- TDZ 结束 ---
let z = 3;
}1.3 const 特性
javascript
const PI = 3.14159;
PI = 3; // TypeError: 不能重新赋值
// const 只保证引用不变, 对象内容可变
const obj = { name: 'Alice' };
obj.name = 'Bob'; // OK
obj = {}; // TypeError
// 冻结对象
const frozen = Object.freeze({ name: 'Alice' });
frozen.name = 'Bob'; // 静默失败 (严格模式报错)1.4 循环中的 let
javascript
// var: 共享同一变量
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// 输出: 3, 3, 3
// let: 每次迭代创建新绑定
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// 输出: 0, 1, 22. 箭头函数 🔥
2.1 语法简化
javascript
// 传统函数
const add = function(a, b) {
return a + b;
};
// 箭头函数
const add = (a, b) => a + b;
// 单参数可省略括号
const double = x => x * 2;
// 返回对象需要括号
const getObj = () => ({ name: 'Alice' });
// 多行需要大括号和 return
const complex = (a, b) => {
const result = a + b;
return result * 2;
};2.2 this 绑定
javascript
// 箭头函数没有自己的 this, 继承外层作用域
const obj = {
name: 'Alice',
// 传统: this 取决于调用方式
sayHi: function() {
setTimeout(function() {
console.log(this.name); // undefined (this 指向 window)
}, 100);
},
// 箭头函数: 继承 sayHello 的 this
sayHello: function() {
setTimeout(() => {
console.log(this.name); // 'Alice'
}, 100);
}
};2.3 不适用场景
javascript
// ❌ 对象方法
const obj = {
name: 'Alice',
sayHi: () => console.log(this.name) // undefined
};
// ❌ 构造函数
const Person = (name) => { this.name = name; };
new Person('Alice'); // TypeError
// ❌ arguments 对象
const fn = () => console.log(arguments); // ReferenceError
// ❌ 原型方法
function Person(name) { this.name = name; }
Person.prototype.sayHi = () => console.log(this.name); // undefined3. 解构赋值 🔥
3.1 对象解构
javascript
const user = { name: 'Alice', age: 25, city: 'NYC' };
// 基础解构
const { name, age } = user;
// 重命名
const { name: userName } = user;
// 默认值
const { country = 'USA' } = user;
// 嵌套解构
const { address: { street } } = { address: { street: '123 Main' } };
// Rest 收集
const { name, ...rest } = user; // rest = { age: 25, city: 'NYC' }3.2 数组解构
javascript
const arr = [1, 2, 3, 4, 5];
// 基础解构
const [first, second] = arr;
// 跳过元素
const [, , third] = arr;
// 默认值
const [a = 0, b = 0] = [];
// Rest 收集
const [head, ...tail] = arr; // tail = [2, 3, 4, 5]
// 交换变量
let x = 1, y = 2;
[x, y] = [y, x];3.3 函数参数解构
javascript
// 对象参数
function greet({ name, age = 18 }) {
console.log(`${name}, ${age}`);
}
greet({ name: 'Alice' });
// 数组参数
function first([x]) {
return x;
}
first([1, 2, 3]); // 14. 模板字符串
4.1 基本用法
javascript
const name = 'Alice';
const age = 25;
// 多行字符串
const html = `
<div>
<h1>Hello, ${name}</h1>
<p>Age: ${age}</p>
</div>
`;
// 表达式
const result = `Sum: ${1 + 2}`;
const message = `Status: ${age >= 18 ? 'Adult' : 'Minor'}`;4.2 标签模板
javascript
function highlight(strings, ...values) {
return strings.reduce((result, str, i) => {
const value = values[i] ? `<mark>${values[i]}</mark>` : '';
return result + str + value;
}, '');
}
const name = 'Alice';
const age = 25;
highlight`Name: ${name}, Age: ${age}`;
// "Name: <mark>Alice</mark>, Age: <mark>25</mark>"
// 实际应用: styled-components
const Button = styled.button`
color: ${props => props.primary ? 'blue' : 'gray'};
`;5. 展开运算符 (...) 🔥
5.1 数组展开
javascript
// 合并数组
const arr1 = [1, 2];
const arr2 = [3, 4];
const merged = [...arr1, ...arr2]; // [1, 2, 3, 4]
// 复制数组 (浅拷贝)
const copy = [...arr1];
// 函数调用
Math.max(...[1, 2, 3]); // 3
// 字符串转数组
[...'hello']; // ['h', 'e', 'l', 'l', 'o']5.2 对象展开
javascript
// 合并对象
const obj1 = { a: 1, b: 2 };
const obj2 = { c: 3 };
const merged = { ...obj1, ...obj2 }; // { a: 1, b: 2, c: 3 }
// 覆盖属性
const updated = { ...obj1, b: 10 }; // { a: 1, b: 10 }
// 浅拷贝
const copy = { ...obj1 };5.3 Rest 参数
javascript
// 收集剩余参数
function sum(...numbers) {
return numbers.reduce((a, b) => a + b, 0);
}
sum(1, 2, 3, 4); // 10
// 解构中的 Rest
const [first, ...rest] = [1, 2, 3];
const { a, ...others } = { a: 1, b: 2, c: 3 };6. 默认参数与增强对象字面量
6.1 默认参数
javascript
// 基础默认值
function greet(name = 'Guest', greeting = 'Hello') {
return `${greeting}, ${name}!`;
}
// 表达式作为默认值
function getId(prefix = Date.now()) {
return `${prefix}-${Math.random()}`;
}
// 解构默认值
function config({ timeout = 1000, retries = 3 } = {}) {
console.log(timeout, retries);
}
config(); // 1000, 36.2 增强对象字面量
javascript
const name = 'Alice';
const age = 25;
// 属性简写
const user = { name, age }; // { name: 'Alice', age: 25 }
// 方法简写
const obj = {
greet() { // 不用 function 关键字
return 'Hello';
}
};
// 计算属性名
const key = 'dynamicKey';
const obj = {
[key]: 'value',
[`${key}_2`]: 'value2'
};7. Class (类)
7.1 基本语法
javascript
class Person {
// 构造函数
constructor(name, age) {
this.name = name;
this.age = age;
}
// 实例方法
greet() {
return `Hello, I'm ${this.name}`;
}
// 静态方法
static create(name) {
return new Person(name, 0);
}
// Getter
get info() {
return `${this.name}, ${this.age}`;
}
// Setter
set info(value) {
[this.name, this.age] = value.split(',');
}
}
const alice = new Person('Alice', 25);
alice.greet(); // "Hello, I'm Alice"
Person.create('Bob'); // Person { name: 'Bob', age: 0 }7.2 继承
javascript
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound`);
}
}
class Dog extends Animal {
constructor(name, breed) {
super(name); // 必须先调用 super()
this.breed = breed;
}
speak() {
super.speak(); // 调用父类方法
console.log(`${this.name} barks`);
}
}
const dog = new Dog('Buddy', 'Golden');
dog.speak();
// Buddy makes a sound
// Buddy barks7.3 私有字段 (ES2022) 🔥
javascript
class Counter {
#count = 0; // 私有字段
static #total = 0; // 静态私有字段
increment() {
this.#count++;
Counter.#total++;
}
get count() {
return this.#count;
}
#privateMethod() { // 私有方法
console.log('Private');
}
}
const counter = new Counter();
counter.#count; // SyntaxError: 无法访问8. Proxy & Reflect 🔥
8.1 Proxy 基础
javascript
const target = { name: 'Alice', age: 25 };
const proxy = new Proxy(target, {
// 拦截属性读取
get(target, prop, receiver) {
console.log(`Getting ${prop}`);
return Reflect.get(target, prop, receiver);
},
// 拦截属性设置
set(target, prop, value, receiver) {
console.log(`Setting ${prop} = ${value}`);
return Reflect.set(target, prop, value, receiver);
},
// 拦截 in 操作符
has(target, prop) {
return prop in target;
},
// 拦截属性删除
deleteProperty(target, prop) {
delete target[prop];
return true;
}
});
proxy.name; // Getting name → 'Alice'
proxy.age = 30; // Setting age = 308.2 常用 Handler
| Handler | 拦截操作 |
|---|---|
get | 属性读取 |
set | 属性设置 |
has | in 操作符 |
deleteProperty | delete 操作 |
apply | 函数调用 |
construct | new 操作 |
ownKeys | Object.keys() 等 |
getPrototypeOf | Object.getPrototypeOf() |
setPrototypeOf | Object.setPrototypeOf() |
defineProperty | Object.defineProperty() |
getOwnPropertyDescriptor | Object.getOwnPropertyDescriptor() |
8.3 Vue 3 响应式原理
javascript
function reactive(target) {
return new Proxy(target, {
get(target, key, receiver) {
track(target, key); // 收集依赖
const result = Reflect.get(target, key, receiver);
// 嵌套对象也要代理
return typeof result === 'object' ? reactive(result) : result;
},
set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
trigger(target, key); // 触发更新
return result;
}
});
}8.4 Reflect
javascript
// Reflect 方法与 Proxy handler 一一对应
Reflect.get(obj, 'name');
Reflect.set(obj, 'name', 'Alice');
Reflect.has(obj, 'name');
Reflect.deleteProperty(obj, 'name');
Reflect.ownKeys(obj);
// 为什么用 Reflect?
// 1. 返回值语义化 (Reflect.set 返回 boolean)
// 2. 正确处理 receiver (继承场景)
// 3. 与 Proxy 配合使用9. Map & Set
9.1 Map
javascript
const map = new Map();
// 设置/获取
map.set('key', 'value');
map.set({ id: 1 }, 'object key'); // 对象可作为键
map.get('key'); // 'value'
// 检查/删除
map.has('key'); // true
map.delete('key');
map.clear();
// 遍历
map.forEach((value, key) => console.log(key, value));
for (const [key, value] of map) {
console.log(key, value);
}
// 大小
map.size;
// 与数组互转
const arr = [['a', 1], ['b', 2]];
const map = new Map(arr);
const backToArr = [...map];9.2 Set
javascript
const set = new Set([1, 2, 3, 3]); // 自动去重
// 添加/检查/删除
set.add(4);
set.has(3); // true
set.delete(1);
set.clear();
// 遍历
set.forEach(value => console.log(value));
for (const value of set) {
console.log(value);
}
// 数组去重
const unique = [...new Set([1, 2, 2, 3])];
// 集合运算
const a = new Set([1, 2, 3]);
const b = new Set([2, 3, 4]);
// 并集
const union = new Set([...a, ...b]);
// 交集
const intersection = new Set([...a].filter(x => b.has(x)));
// 差集
const difference = new Set([...a].filter(x => !b.has(x)));9.3 WeakMap & WeakSet
javascript
// 键必须是对象, 弱引用 (不阻止垃圾回收)
const weakMap = new WeakMap();
let obj = { data: 'important' };
weakMap.set(obj, 'metadata');
obj = null; // obj 可被垃圾回收, weakMap 中的条目自动移除
// 使用场景: 存储 DOM 节点关联数据
const nodeData = new WeakMap();
const div = document.createElement('div');
nodeData.set(div, { clicks: 0 });
// div 被移除时, 关联数据自动释放10. Symbol
10.1 基本用法
javascript
// 创建唯一标识
const sym1 = Symbol('description');
const sym2 = Symbol('description');
sym1 === sym2; // false
// 作为对象键
const obj = {
[sym1]: 'value'
};
obj[sym1]; // 'value'
// 不会被常规遍历发现
Object.keys(obj); // []
Object.getOwnPropertySymbols(obj); // [Symbol(description)]10.2 内置 Symbol
javascript
// Symbol.iterator - 定义迭代器
const obj = {
data: [1, 2, 3],
[Symbol.iterator]() {
let index = 0;
return {
next: () => ({
value: this.data[index],
done: index++ >= this.data.length
})
};
}
};
[...obj]; // [1, 2, 3]
// Symbol.toStringTag - 自定义 toString 标签
class MyClass {
get [Symbol.toStringTag]() {
return 'MyClass';
}
}
Object.prototype.toString.call(new MyClass()); // '[object MyClass]'
// Symbol.toPrimitive - 类型转换
const obj = {
[Symbol.toPrimitive](hint) {
if (hint === 'number') return 42;
if (hint === 'string') return 'hello';
return true;
}
};10.3 Symbol.for 全局注册
javascript
// 全局共享 Symbol
const sym1 = Symbol.for('shared');
const sym2 = Symbol.for('shared');
sym1 === sym2; // true
// 获取 key
Symbol.keyFor(sym1); // 'shared'11. Iterator & Generator
11.1 迭代器协议
javascript
// 可迭代对象需实现 [Symbol.iterator]
const iterable = {
[Symbol.iterator]() {
let step = 0;
return {
next() {
step++;
if (step <= 3) {
return { value: step, done: false };
}
return { value: undefined, done: true };
}
};
}
};
for (const value of iterable) {
console.log(value); // 1, 2, 3
}11.2 Generator 函数
javascript
function* generator() {
yield 1;
yield 2;
yield 3;
}
const gen = generator();
gen.next(); // { value: 1, done: false }
gen.next(); // { value: 2, done: false }
gen.next(); // { value: 3, done: false }
gen.next(); // { value: undefined, done: true }
// 遍历
for (const value of generator()) {
console.log(value); // 1, 2, 3
}11.3 Generator 高级用法
javascript
function* gen() {
const a = yield 1;
console.log('a:', a);
const b = yield 2;
console.log('b:', b);
return 3;
}
const g = gen();
g.next(); // { value: 1, done: false }
g.next('A'); // a: A → { value: 2, done: false }
g.next('B'); // b: B → { value: 3, done: true }
// yield* 委托
function* outer() {
yield 1;
yield* [2, 3]; // 委托给数组迭代器
yield* inner(); // 委托给另一个 generator
}
function* inner() {
yield 4;
yield 5;
}
[...outer()]; // [1, 2, 3, 4, 5]12. 模块 (Module)
12.1 导出
javascript
// 命名导出
export const name = 'Alice';
export function greet() {}
export class Person {}
// 统一导出
const a = 1;
const b = 2;
export { a, b };
// 重命名导出
export { a as aliasA };
// 默认导出
export default function() {}
export default class {}
export default { name: 'Alice' };12.2 导入
javascript
// 命名导入
import { name, greet } from './module.js';
// 重命名导入
import { name as userName } from './module.js';
// 导入全部
import * as module from './module.js';
// 默认导入
import MyDefault from './module.js';
// 混合导入
import MyDefault, { name } from './module.js';
// 动态导入 (返回 Promise)
const module = await import('./module.js');12.3 模块特性
javascript
// 严格模式: 模块自动启用 'use strict'
// 顶层 this: undefined
console.log(this); // undefined
// 顶层 await (ES2022)
const data = await fetch('/api/data');
// 模块只执行一次, 后续导入是缓存
import './init.js'; // 执行
import './init.js'; // 不再执行13. ES2020+ 新特性
13.1 可选链 (?.) 🔥
javascript
const user = { profile: { name: 'Alice' } };
// 传统
const name = user && user.profile && user.profile.name;
// 可选链
const name = user?.profile?.name;
// 方法调用
obj.method?.();
// 数组索引
arr?.[0];13.2 空值合并 (??) 🔥
javascript
// || 的问题: 0, '', false 被视为假值
const a = 0 || 'default'; // 'default' (错误)
// ?? 只在 null/undefined 时使用默认值
const a = 0 ?? 'default'; // 0 (正确)
const b = null ?? 'default'; // 'default'
const c = undefined ?? 'default'; // 'default'13.3 逻辑赋值运算符 (ES2021)
javascript
// ||= : 左侧为假值时赋值
a ||= b; // a = a || b
// &&= : 左侧为真值时赋值
a &&= b; // a = a && b
// ??= : 左侧为 null/undefined 时赋值
a ??= b; // a = a ?? b
// 实际应用
user.name ??= 'Anonymous';
options.timeout ??= 5000;13.4 数字分隔符 (ES2021)
javascript
const billion = 1_000_000_000;
const bytes = 0xFF_FF_FF;
const binary = 0b1010_0001;13.5 Promise.any (ES2021)
javascript
// 返回第一个成功的 Promise
const fastest = await Promise.any([
fetch('/api/server1'),
fetch('/api/server2')
]);
// 全部失败时抛出 AggregateError
try {
await Promise.any([
Promise.reject('Error 1'),
Promise.reject('Error 2')
]);
} catch (error) {
console.log(error.errors); // ['Error 1', 'Error 2']
}13.6 数组方法 (ES2022-2023)
javascript
// Array.at() - 支持负索引
[1, 2, 3].at(-1); // 3
// Array.findLast() / findLastIndex()
[1, 2, 3, 2].findLast(x => x === 2); // 2 (最后一个)
[1, 2, 3, 2].findLastIndex(x => x === 2); // 3
// Array.toSorted() / toReversed() / toSpliced() - 不改变原数组
const arr = [3, 1, 2];
arr.toSorted(); // [1, 2, 3], arr 不变
arr.toReversed(); // [2, 1, 3]
arr.toSpliced(1, 1, 'a'); // [3, 'a', 2]
// Array.with() - 替换指定位置元素
[1, 2, 3].with(1, 'a'); // [1, 'a', 3]13.7 Object.groupBy (ES2024)
javascript
const people = [
{ name: 'Alice', age: 25 },
{ name: 'Bob', age: 30 },
{ name: 'Charlie', age: 25 }
];
Object.groupBy(people, p => p.age);
// {
// 25: [{ name: 'Alice', age: 25 }, { name: 'Charlie', age: 25 }],
// 30: [{ name: 'Bob', age: 30 }]
// }14. 面试高频问题
Q1: var、let、const 的区别?
| 特性 | var | let | const |
|---|---|---|---|
| 作用域 | 函数 | 块级 | 块级 |
| 变量提升 | ✅ (undefined) | ❌ (TDZ) | ❌ (TDZ) |
| 重复声明 | ✅ | ❌ | ❌ |
| 重新赋值 | ✅ | ✅ | ❌ |
Q2: 箭头函数和普通函数的区别?
- 没有自己的
this, 继承外层作用域 - 没有
arguments对象 - 不能用作构造函数 (
new) - 没有
prototype属性 - 不能用作 Generator
Q3: Map 和 Object 的区别?
| 特性 | Map | Object |
|---|---|---|
| 键类型 | 任意类型 | 字符串/Symbol |
| 顺序 | 插入顺序 | 部分有序 |
| 大小 | map.size | 手动计算 |
| 迭代 | 直接可迭代 | 需要转换 |
| 性能 | 频繁增删更优 | 静态键更优 |
Q4: WeakMap 的使用场景?
- 存储 DOM 节点关联数据 (节点删除后自动清理)
- 缓存计算结果 (对象释放后缓存自动清理)
- 私有数据存储
Q5: Proxy 相比 Object.defineProperty 的优势?
| 特性 | Proxy | Object.defineProperty |
|---|---|---|
| 拦截操作数 | 13 种 | 只有 get/set |
| 数组索引 | ✅ 原生支持 | ❌ 需要特殊处理 |
| 新增属性 | ✅ 自动拦截 | ❌ 需要 Vue.$set |
| 性能 | 略低 | 略高 |
Q6: 可选链和空值合并的区别?
?.: 解决链式访问中 null/undefined 的问题??: 解决默认值赋值中 0/'' 被误判的问题