ES5+语法重点,持续更新

以下是 ES5 语法重点ES6 至 ES2025 所有核心语法更新 的详细梳理,每个语法点均配可直接理解的代码示例

ES语法持续更新

一、ES5 语法重点(2009年,现代JS基础)

ES5 奠定了 JavaScript 的工程化基础,以下是开发中必须掌握的核心:

1. 严格模式('use strict'

在文件/函数顶部启用,强制规范语法,减少隐性错误。

1
2
3
4
5
6
7
8
9
10
11
12
'use strict';

// 1. 禁止未声明变量赋值
x = 10; // ReferenceError: x is not defined

// 2. 函数参数不能重名
function fn(a, a) {} // SyntaxError: Duplicate parameter name not allowed

// 3. 全局函数中 this 指向 undefined
function globalFn() {
console.log(this); // undefined(非严格模式指向 window)
}

2. 函数核心:闭包

函数访问外部作用域变量的特性,即使外部函数已执行完毕。

1
2
3
4
5
6
7
8
9
10
function createCounter() {
let count = 0; // 外部函数变量
return function() {
return ++count; // 内部函数访问外部变量
};
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2(闭包保留了 count 的状态)

3. 数组核心方法(ES5 新增)

ES5 为数组添加了一批实用方法,是日常开发的高频工具:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const arr = [1, 2, 3, 4, 5];

// 1. forEach:遍历数组
arr.forEach(item => console.log(item)); // 1 2 3 4 5

// 2. map:映射新数组
const doubled = arr.map(item => item * 2);
console.log(doubled); // [2, 4, 6, 8, 10]

// 3. filter:过滤数组
const even = arr.filter(item => item % 2 === 0);
console.log(even); // [2, 4]

// 4. reduce:累计计算
const sum = arr.reduce((prev, curr) => prev + curr, 0);
console.log(sum); // 15

4. 对象属性定义:Object.defineProperty

精确控制对象属性的特性(可写、可枚举、可配置)。

1
2
3
4
5
6
7
8
9
10
11
const obj = {};
Object.defineProperty(obj, 'name', {
value: 'Alice',
writable: false, // 不可修改
enumerable: false, // 不可遍历
configurable: false // 不可删除/重新定义
});

console.log(obj.name); // Alice
obj.name = 'Bob'; // 无效(writable: false)
delete obj.name; // 无效(configurable: false)

5. 原型链继承:Object.create

基于原型实现继承,是 ES5 主流的继承方式。

1
2
3
4
5
6
7
8
9
const parent = {
sayHello() {
console.log('Hello from parent');
}
};

// 以 parent 为原型创建 child 对象
const child = Object.create(parent);
child.sayHello(); // Hello from parent(继承自原型)

二、ES6(ES2015):里程碑式大版本

ES6 彻底革新了 JavaScript 语法,引入了现代开发的核心特性:

1. 块级作用域:let / const

替代 var,解决变量提升和作用域混乱问题。

1
2
3
4
5
6
7
8
9
10
// let:块级作用域,可修改
if (true) {
let x = 10;
}
console.log(x); // ReferenceError: x is not defined

// const:块级作用域,不可修改(引用类型地址不变)
const PI = 3.14;
const obj = { name: 'Alice' };
obj.name = 'Bob'; // 允许(修改属性,不修改地址)

2. 箭头函数

简写语法,this 绑定外层作用域,解决传统函数 this 指向混乱问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 1. 基础简写
const add = (a, b) => a + b;
console.log(add(1, 2)); // 3

// 2. this 绑定外层作用域
const obj = {
name: 'Alice',
sayHi() {
setTimeout(() => {
console.log(this.name); // Alice(this 指向 obj,而非全局)
}, 1000);
}
};
obj.sayHi();

3. 模板字符串

反引号包裹,支持变量插值和多行字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const name = 'Alice';
const age = 25;

// 1. 变量插值
const str = `My name is ${name}, and I'm ${age} years old.`;
console.log(str); // My name is Alice, and I'm 25 years old.

// 2. 多行字符串
const multiLine = `
Line 1
Line 2
Line 3
`;
console.log(multiLine); // 保留换行

4. 解构赋值

快速从数组/对象中提取值并赋值给变量。

1
2
3
4
5
6
7
8
// 1. 数组解构(按位置)
const [a, b, c] = [1, 2, 3];
console.log(a); // 1

// 2. 对象解构(按属性名)
const user = { name: 'Alice', age: 25 };
const { name: userName, age: userAge } = user; // 可重命名
console.log(userName); // Alice

5. 函数参数增强

默认参数 + 剩余参数(...)。

1
2
3
4
5
6
7
8
9
10
11
// 1. 默认参数
function greet(name = 'Guest') {
console.log(`Hello, ${name}!`);
}
greet(); // Hello, Guest!

// 2. 剩余参数(收集为数组)
function sum(...nums) {
return nums.reduce((prev, curr) => prev + curr, 0);
}
console.log(sum(1, 2, 3, 4)); // 10

6. 扩展运算符(...

展开数组/对象,用于复制、合并。

1
2
3
4
5
6
7
8
9
// 1. 数组展开
const arr1 = [1, 2];
const arr2 = [...arr1, 3, 4];
console.log(arr2); // [1, 2, 3, 4]

// 2. 对象展开
const obj1 = { a: 1 };
const obj2 = { ...obj1, b: 2 };
console.log(obj2); // { a: 1, b: 2 }

7. 类(Class)

基于原型的语法糖,让面向对象编程更清晰。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Person {
constructor(name) {
this.name = name; // 实例属性
}

// 实例方法
sayHello() {
console.log(`Hello, I'm ${this.name}`);
}

// 静态方法(通过类调用)
static create(name) {
return new Person(name);
}
}

// 继承
class Student extends Person {
constructor(name, grade) {
super(name); // 调用父类构造函数
this.grade = grade;
}
}

const alice = new Student('Alice', 5);
alice.sayHello(); // Hello, I'm Alice

8. Promise

异步编程的核心,解决“回调地狱”问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 1. 创建 Promise
const fetchData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Data loaded'); // 成功
// reject('Error'); // 失败
}, 1000);
});
};

// 2. 使用 Promise
fetchData()
.then(data => console.log(data)) // Data loaded(1秒后)
.catch(err => console.error(err));

9. 模块化(import / export

静态模块化语法,替代 CommonJS,是现代前端工程化的基础。

1
2
3
4
5
6
7
// module.js(导出)
export const name = 'Alice'; // 命名导出
export default class Person {}; // 默认导出(每个模块只能一个)

// main.js(导入)
import { name } from './module.js'; // 导入命名导出
import Person from './module.js'; // 导入默认导出

10. Set / Map

新的数据结构,补充数组和对象的不足。

1
2
3
4
5
6
7
8
9
10
11
// 1. Set:无重复值的集合
const set = new Set([1, 2, 2, 3]);
console.log(set); // Set { 1, 2, 3 }
set.add(4);
set.has(2); // true

// 2. Map:键值对集合(键可以是任意类型)
const map = new Map();
map.set('name', 'Alice');
map.set(1, 'one');
console.log(map.get('name')); // Alice

三、ES2016(ES7)至 ES2025:年度小版本迭代

ES6 之后每年更新小版本,持续补充语法糖和实用特性:


ES2016(ES7)

  1. 指数运算符(**:替代 Math.pow()

    1
    console.log(2 ** 3); // 8(等价于 Math.pow(2, 3))
  2. **Array.prototype.includes()**:检查数组是否包含值(可识别 NaN

    1
    2
    const arr = [1, 2, NaN];
    console.log(arr.includes(NaN)); // true(indexOf 无法识别 NaN)

ES2017(ES8)

  1. **async/await**:Promise 语法糖,异步代码同步写(现代异步开发首选)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    async function fetchData() {
    try {
    const data = await new Promise(resolve =>
    setTimeout(() => resolve('Data'), 1000)
    );
    console.log(data); // Data(1秒后)
    } catch (err) {
    console.error(err);
    }
    }
    fetchData();
  2. **Object.values() / Object.entries()**:获取对象值/键值对数组

    1
    2
    3
    const obj = { a: 1, b: 2 };
    console.log(Object.values(obj)); // [1, 2]
    console.log(Object.entries(obj)); // [['a', 1], ['b', 2]]
  3. 字符串补全padStart() / padEnd()

    1
    2
    console.log('5'.padStart(2, '0')); // '05'(补全到2位,前面补0)
    console.log('Hello'.padEnd(10, '!')); // 'Hello!!!!!'

ES2018(ES9)

  1. 对象扩展运算符:ES6 只支持数组,ES2018 支持对象

    1
    2
    3
    const obj1 = { a: 1 };
    const obj2 = { ...obj1, b: 2 };
    console.log(obj2); // { a: 1, b: 2 }
  2. **Promise.prototype.finally()**:Promise 结束时无论成功失败都执行

    1
    2
    3
    4
    fetchData()
    .then(data => console.log(data))
    .catch(err => console.error(err))
    .finally(() => console.log('Request finished')); // 总是执行

ES2019(ES10)

  1. 可选链操作符(?.:安全访问深层属性,避免“Cannot read property of undefined”

    1
    2
    3
    const user = { address: { city: 'New York' } };
    console.log(user?.address?.city); // 'New York'
    console.log(user?.profile?.age); // undefined(不抛错)
  2. 空值合并操作符(??:仅当左边为 null/undefined 时返回右边(区别于 ||

    1
    2
    3
    const count = 0;
    console.log(count ?? 10); // 0(0 不是 null/undefined,保留原值)
    console.log(count || 10); // 10(|| 会把 0 当作 falsy 值)
  3. **Array.prototype.flat() / flatMap()**:扁平化数组

    1
    2
    3
    4
    5
    const arr = [1, [2, [3, 4]]];
    console.log(arr.flat(2)); // [1, 2, 3, 4](flat(深度))

    const mapped = [1, 2].flatMap(x => [x, x * 2]);
    console.log(mapped); // [1, 2, 2, 4](map 后 flat(1))

ES2020(ES11)

  1. **BigInt**:大整数类型,支持超过 Number.MAX_SAFE_INTEGER 的计算

    1
    2
    const bigNum = 123456789012345678901234567890n;
    console.log(bigNum + 1n); // 123456789012345678901234567891n
  2. **Promise.allSettled()**:等待所有 Promise 状态改变(无论成功失败)

    1
    2
    3
    4
    5
    6
    const promises = [
    Promise.resolve('Success'),
    Promise.reject('Error')
    ];
    Promise.allSettled(promises).then(results => console.log(results));
    // 输出:[{ status: 'fulfilled', value: 'Success' }, { status: 'rejected', reason: 'Error' }]
  3. **动态 import()**:按需导入模块,返回 Promise

    1
    2
    3
    4
    5
    // 按需加载模块(比如点击按钮时)
    button.addEventListener('click', async () => {
    const module = await import('./heavy-module.js');
    module.doSomething();
    });

ES2021(ES12)

  1. 逻辑赋值运算符&&=||=??=(简化逻辑赋值)

    1
    2
    3
    4
    5
    let a = 1;
    a &&= 2; // 等价于 a = a && 2 → 2

    let b = null;
    b ??= 10; // 等价于 b = b ?? 10 → 10
  2. at() 方法:字符串/数组支持负数索引(从末尾取)

    1
    2
    3
    4
    5
    const str = 'Hello';
    console.log(str.at(-1)); // 'o'(最后一个字符)

    const arr = [1, 2, 3];
    console.log(arr.at(-2)); // 2(倒数第二个元素)
  3. **Promise.any()**:只要一个 Promise 成功就返回(区别于 Promise.race()

    1
    2
    3
    4
    5
    6
    const promises = [
    Promise.reject('Error 1'),
    Promise.resolve('Success'),
    Promise.reject('Error 2')
    ];
    Promise.any(promises).then(data => console.log(data)); // 'Success'

ES2022(ES13)

  1. 类私有字段/方法# 前缀,仅类内部可访问

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Person {
    #name = 'Alice'; // 私有字段

    #sayHi() { // 私有方法
    console.log(`Hi, I'm ${this.#name}`);
    }

    greet() {
    this.#sayHi(); // 类内部可访问
    }
    }

    const alice = new Person();
    alice.greet(); // Hi, I'm Alice
    console.log(alice.#name); // SyntaxError: Private field '#name' must be declared
  2. **Object.hasOwn()**:替代 Object.prototype.hasOwnProperty,更安全

    1
    2
    3
    const obj = { a: 1 };
    console.log(Object.hasOwn(obj, 'a')); // true
    console.log(Object.hasOwn(obj, 'toString')); // false(不检查原型链)

ES2023(ES14)

  1. 数组从末尾查找findLast() / findLastIndex()
    1
    2
    3
    const arr = [1, 2, 3, 4, 3];
    console.log(arr.findLast(x => x === 3)); // 3(最后一个匹配的元素)
    console.log(arr.findLastIndex(x => x === 3)); // 4(最后一个匹配的索引)

ES2024(ES15)

  1. Well-Formed Unicode StringsisWellFormed() / toWellFormed()

    1
    2
    3
    const badStr = 'a\uD800'; // 不完整的 Unicode 代理对
    console.log(badStr.isWellFormed()); // false
    console.log(badStr.toWellFormed()); // 'a�'(替换为有效字符)
  2. **ArrayBuffer.prototype.resize()**:动态调整 ArrayBuffer 大小

    1
    2
    3
    4
    const buffer = new ArrayBuffer(8);
    console.log(buffer.byteLength); // 8
    buffer.resize(16);
    console.log(buffer.byteLength); // 16

ES2025(预计)

目前处于提案阶段的核心特性(可能调整):

  1. 装饰器(Decorators):为类/方法添加元数据和增强逻辑(Stage 3)
    1
    2
    3
    4
    5
    @log
    class Person {
    @readonly
    name = 'Alice';
    }

补充

详细补充和深度解析,结合代码示例和痛点对比,让内容更透彻。

一、Promise 的三个状态(超详细解析)

Promise 是 ES6 引入的异步编程解决方案,其核心是状态管理。Promise 有且仅有以下三个状态,且状态转换是单向不可逆的:

1. 状态定义

状态 含义 触发条件 后续操作
pending 等待中(初始状态) Promise 实例刚创建时 可转换为 fulfilled 或 rejected
fulfilled 已成功(resolved) 调用 resolve() 触发 .then() 回调
rejected 已失败 调用 reject() 或抛出错误时 触发 .catch() 回调

2. 状态转换规则(核心!不可逆)

Promise 状态一旦从 pending 转换为 fulfilledrejected,就永远不会再改变

代码示例:状态不可逆

1
2
3
4
5
6
7
8
9
const promise = new Promise((resolve, reject) => {
resolve('成功'); // 先调用 resolve,状态变为 fulfilled
reject('失败'); // 后续调用 reject 无效!
throw new Error('报错'); // 抛出错误也无效!
});

promise
.then(data => console.log(data)) // 输出:成功
.catch(err => console.error(err)); // 不会触发

3. 状态与回调的对应关系

  • .then() 接收两个参数:onFulfilled(成功回调)和 onRejected(失败回调,可选)。
  • .catch().then(null, onRejected) 的语法糖,专门捕获失败状态。
  • .finally() 无论成功失败都会执行(ES2018 新增)。

代码示例:完整状态流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function fetchData() {
return new Promise((resolve, reject) => {
console.log('Promise 执行,当前状态:pending');

setTimeout(() => {
const success = true; // 模拟成功/失败
if (success) {
resolve('数据加载成功'); // 状态变为 fulfilled
} else {
reject(new Error('网络错误')); // 状态变为 rejected
}
}, 1000);
});
}

fetchData()
.then(data => {
console.log('then 触发,状态:fulfilled,数据:', data);
})
.catch(err => {
console.error('catch 触发,状态:rejected,错误:', err.message);
})
.finally(() => {
console.log('finally 触发,无论成功失败都执行');
});

二、Promise.race() 详细解析(与 all/any 对比)

Promise.race() 的核心是:“谁先改变状态,就用谁的结果”(不管成功还是失败)。

1. 语法与行为

1
Promise.race(iterable); // 接收一个 Promise 可迭代对象(如数组)
  • 返回值:一个新的 Promise。
  • 行为:
    • 只要 iterable任意一个 Promise 先改变状态(无论成功/失败),返回的 Promise 就会立即跟随改变状态。
    • 其他未完成的 Promise 仍会继续执行,但结果会被忽略。

2. 代码示例:快速响应

1
2
3
4
5
6
7
8
// 模拟三个异步请求,其中一个最快
const p1 = new Promise(resolve => setTimeout(() => resolve('请求1完成'), 3000));
const p2 = new Promise(resolve => setTimeout(() => resolve('请求2完成'), 1000)); // 最快
const p3 = new Promise((_, reject) => setTimeout(() => reject('请求3失败'), 2000));

Promise.race([p1, p2, p3])
.then(data => console.log('race 成功:', data)) // 输出:race 成功:请求2完成
.catch(err => console.error('race 失败:', err));

3. 与 Promise.all() / Promise.any() 的核心区别

方法 成功条件 失败条件 适用场景
Promise.all() 所有 Promise 都成功 任意一个 Promise 失败 并行请求,依赖所有结果
Promise.race() 任意一个 Promise 先成功/失败 任意一个 Promise 先失败 超时控制、快速响应
Promise.any() 任意一个 Promise 成功 所有 Promise 都失败 容错性高的并行请求(只要一个成)

代码示例:race 用于超时控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 封装一个带超时的请求
function fetchWithTimeout(url, timeout = 5000) {
const fetchPromise = fetch(url); // 实际请求
const timeoutPromise = new Promise((_, reject) =>
setTimeout(() => reject(new Error('请求超时')), timeout)
);

// 谁先完成用谁的结果:请求成功 或 超时失败
return Promise.race([fetchPromise, timeoutPromise]);
}

// 使用
fetchWithTimeout('https://api.example.com/data', 3000)
.then(response => console.log('请求成功:', response))
.catch(err => console.error('请求失败:', err.message)); // 超时则输出“请求超时”

三、ES6 替代 ES5 解决的核心痛点(深度对比)

ES5 时代的 JavaScript 存在诸多工程化和开发体验问题,ES6 的诞生就是为了系统性解决这些痛点。以下是核心痛点及 ES6 的解决方案:


痛点 1:var 的变量提升与作用域混乱

ES5 问题:

  • var 存在变量提升(声明被提前到作用域顶部,但赋值不提前),导致逻辑难以预测。
  • 只有函数作用域,没有块级作用域,if/for 等块内变量会污染全局。
1
2
3
4
5
6
7
8
9
10
// ES5 问题1:变量提升
console.log(a); // undefined(不会报错,因为声明被提升了)
var a = 10;

// ES5 问题2:块级作用域缺失
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i); // 输出 3,3,3(i 是全局变量,循环结束后才执行 setTimeout)
}, 1000);
}

ES6 解决方案:let / const

  • let / const 没有变量提升,必须先声明后使用。
  • 支持块级作用域{} 内生效),避免变量污染。
1
2
3
4
5
6
7
8
9
10
// ES6 解决:let/const
console.log(b); // ReferenceError: b is not defined(必须先声明)
let b = 10;

// 块级作用域 + 闭包问题解决
for (let j = 0; j < 3; j++) { // let 使 j 在每次循环中都是新的变量
setTimeout(function() {
console.log(j); // 输出 0,1,2
}, 1000);
}

痛点 2:回调地狱(Callback Hell)

ES5 问题:

异步操作依赖回调函数,多个异步操作串行时会导致嵌套层级过深,代码可读性和可维护性极差。

1
2
3
4
5
6
7
8
9
10
11
// ES5 回调地狱:串行请求
fetchUser(userId, function(user) {
fetchOrders(user.id, function(orders) {
fetchOrderDetails(orders[0].id, function(details) {
fetchProducts(details.productId, function(products) {
// 嵌套层级越来越深...
console.log(products);
}, handleError);
}, handleError);
}, handleError);
}, handleError);

ES6 解决方案:Promise + ES2017 async/await

  • Promise 用链式调用(.then())替代嵌套,逻辑更清晰。
  • async/await 进一步让异步代码看起来像同步代码,彻底解决回调地狱。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ES6 Promise 链式调用
fetchUser(userId)
.then(user => fetchOrders(user.id))
.then(orders => fetchOrderDetails(orders[0].id))
.then(details => fetchProducts(details.productId))
.then(products => console.log(products))
.catch(handleError); // 统一错误处理

// ES2017 async/await(终极解决方案)
async function loadData() {
try {
const user = await fetchUser(userId);
const orders = await fetchOrders(user.id);
const details = await fetchOrderDetails(orders[0].id);
const products = await fetchProducts(details.productId);
console.log(products);
} catch (err) {
handleError(err);
}
}
loadData();

痛点 3:this 指向混乱

ES5 问题:

传统函数的 this 指向取决于调用方式(全局调用、对象方法调用、new 调用等),极易出错,常需用 var self = this.bind(this) hack。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// ES5 this 指向问题
const obj = {
name: 'Alice',
sayHi: function() {
setTimeout(function() {
console.log(this.name); // undefined(this 指向全局对象 window/global)
}, 1000);
}
};
obj.sayHi();

// ES5 临时解决方案:var self = this
const obj2 = {
name: 'Bob',
sayHi: function() {
var self = this; // 保存 this
setTimeout(function() {
console.log(self.name); // Bob
}, 1000);
}
};
obj2.sayHi();

ES6 解决方案:箭头函数

箭头函数没有自己的 this,**继承外层作用域的 this**,彻底解决 this 指向混乱问题。

1
2
3
4
5
6
7
8
9
10
// ES6 箭头函数解决 this 问题
const obj = {
name: 'Alice',
sayHi: function() {
setTimeout(() => {
console.log(this.name); // Alice(this 继承自 sayHi 的作用域,即 obj)
}, 1000);
}
};
obj.sayHi();

痛点 4:缺乏官方模块化方案

ES5 问题:

ES5 没有原生模块化语法,社区只能用第三方方案(CommonJS、AMD、CMD),导致代码不统一,浏览器端需额外打包工具(如 Browserify)。

1
2
3
4
5
6
7
8
// ES5 CommonJS(Node.js 用,浏览器不原生支持)
// module.js
const name = 'Alice';
module.exports = { name };

// main.js
const module = require('./module.js');
console.log(module.name);

ES6 解决方案:import / export

ES6 提供官方静态模块化语法,浏览器和 Node.js(v12+)原生支持,是现代前端工程化的基础。

1
2
3
4
5
6
7
8
9
10
// ES6 模块化(原生支持)
// module.js
export const name = 'Alice'; // 命名导出
export default function sayHi() { console.log('Hi'); } // 默认导出

// main.js
import { name } from './module.js'; // 导入命名导出
import sayHi from './module.js'; // 导入默认导出
console.log(name);
sayHi();

痛点 5:对象/数组操作繁琐

ES5 问题:

  • 合并对象/数组需用 Object.assign()concat(),代码冗长。
  • 访问深层对象属性需层层判断,否则容易报错 Cannot read property of undefined
1
2
3
4
5
6
7
8
// ES5 合并数组
const arr1 = [1, 2];
const arr2 = [3, 4];
const mergedArr = arr1.concat(arr2); // [1,2,3,4]

// ES5 访问深层属性
const user = { address: { city: 'New York' } };
const city = user && user.address && user.address.city; // 层层判断,避免报错

ES6+ 解决方案:扩展运算符、可选链、空值合并

  • 扩展运算符(...:简洁合并数组/对象。
  • 可选链(?.:安全访问深层属性,无需层层判断。
  • 空值合并(??:精准处理 null/undefined,避免误判 0/''
1
2
3
4
5
6
7
8
9
10
11
12
13
// ES6 扩展运算符合并
const arr1 = [1, 2];
const arr2 = [3, 4];
const mergedArr = [...arr1, ...arr2]; // [1,2,3,4]

const obj1 = { a: 1 };
const obj2 = { b: 2 };
const mergedObj = { ...obj1, ...obj2 }; // {a:1, b:2}

// ES2019 可选链 + 空值合并
const user = { address: { city: 'New York' } };
const city = user?.address?.city ?? 'Unknown'; // 'New York'(安全且简洁)
const age = user?.profile?.age ?? 18; // 18(属性不存在时用默认值)

问题汇总

一、浮点型精度问题

1. 问题现象

1
2
3
console.log(0.1 + 0.2); // 0.30000000000000004(不是 0.3!)
console.log(0.7 * 100); // 70.00000000000001
console.log(1.005.toFixed(2)); // '1.00'(应该是 '1.01')

2. 原因

JavaScript 采用 IEEE 754 双精度浮点数(64位)存储数字,二进制无法精确表示部分十进制小数(如 0.1、0.2),计算时会产生舍入误差。

3. 解决方案

方案 1:转整数计算(推荐)

1
2
3
4
5
6
7
8
9
// 将小数放大为整数计算,再缩小
function add(a, b) {
const scale = Math.pow(10, Math.max(
(a.toString().split('.')[1] || '').length,
(b.toString().split('.')[1] || '').length
));
return (Math.round(a * scale) + Math.round(b * scale)) / scale;
}
console.log(add(0.1, 0.2)); // 0.3

方案 2:使用 toFixed() 保留小数(注意返回字符串)

1
2
const result = (0.1 + 0.2).toFixed(2); // '0.30'
console.log(parseFloat(result)); // 0.3

方案 3:第三方库(生产环境推荐)

  • decimal.js:高精度计算库
  • big.js:轻量级精确计算库
    1
    2
    import Decimal from 'decimal.js';
    console.log(new Decimal(0.1).plus(0.2).toNumber()); // 0.3

二、原型链与继承(核心难点)

1. 核心概念

  • **__proto__**:对象的隐式原型,指向构造函数的 prototype
  • **prototype**:构造函数的原型对象,包含共享属性和方法。
  • **constructor**:原型对象的构造函数指针,指回构造函数本身。

2. 原型链查找规则

当访问对象属性时:

  1. 先在对象自身查找;
  2. 找不到则通过 __proto__ 向上查找原型对象;
  3. 直到 Object.prototype.__proto__(即 null),若还找不到则返回 undefined

代码示例

1
2
3
4
5
6
7
8
9
10
11
function Person(name) {
this.name = name;
}
Person.prototype.sayHi = function() {
console.log(`Hi, I'm ${this.name}`);
};

const alice = new Person('Alice');
console.log(alice.name); // Alice(自身属性)
alice.sayHi(); // Hi, I'm Alice(从 Person.prototype 继承)
console.log(alice.toString()); // [object Object](从 Object.prototype 继承)

3. 常见考点:new 操作符的原理

1
2
3
4
5
6
7
8
9
10
11
12
13
// 模拟 new 操作符
function myNew(constructor, ...args) {
// 1. 创建空对象,继承构造函数的 prototype
const obj = Object.create(constructor.prototype);
// 2. 绑定 this,执行构造函数
const result = constructor.apply(obj, args);
// 3. 若构造函数返回对象,则返回该对象;否则返回 obj
return typeof result === 'object' && result !== null ? result : obj;
}

// 测试
const bob = myNew(Person, 'Bob');
bob.sayHi(); // Hi, I'm Bob

三、闭包(高频应用+难点)

1. 定义

函数能够访问其外部作用域变量的特性,即使外部函数已执行完毕。

2. 核心用途

用途 1:封装私有变量

1
2
3
4
5
6
7
8
9
10
11
12
13
function createCounter() {
let count = 0; // 私有变量,外部无法直接访问
return {
increment: () => ++count,
decrement: () => --count,
getCount: () => count
};
}

const counter = createCounter();
console.log(counter.increment()); // 1
console.log(counter.getCount()); // 1
console.log(counter.count); // undefined(无法直接访问私有变量)

用途 2:函数柯里化

1
2
3
4
5
6
7
8
// 柯里化:将多参数函数转为单参数函数
function add(a) {
return function(b) {
return a + b;
};
}
const add5 = add(5);
console.log(add5(3)); // 8

3. 常见坑:循环中的闭包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 问题:输出 3,3,3
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}

// 解决1:用 let(块级作用域)
for (let j = 0; j < 3; j++) {
setTimeout(function() {
console.log(j); // 0,1,2
}, 1000);
}

// 解决2:用立即执行函数(IIFE)
for (var k = 0; k < 3; k++) {
(function(m) {
setTimeout(function() {
console.log(m); // 0,1,2
}, 1000);
})(k);
}

四、事件循环(Event Loop)(异步核心)

1. 核心概念

JavaScript 是单线程语言,通过事件循环实现异步编程:

  • 调用栈:同步任务执行栈。
  • 任务队列:存放异步任务的回调,分为:
    • 宏任务(Macro Task)setTimeout/setInterval、I/O、DOM 事件、requestAnimationFrame
    • 微任务(Micro Task)Promise.then/catch/finallyasync/await(底层是 Promise)、process.nextTick(Node.js)。

2. 执行顺序(必背!)

  1. 执行同步代码(调用栈清空);
  2. 执行所有微任务(清空微任务队列);
  3. 执行一个宏任务(从宏任务队列取一个);
  4. 再次执行所有微任务
  5. 重复步骤 3-4。

代码示例(经典面试题)

1
2
3
4
5
6
7
8
9
console.log('1'); // 同步

setTimeout(() => console.log('2'), 0); // 宏任务

Promise.resolve().then(() => console.log('3')); // 微任务

console.log('4'); // 同步

// 输出顺序:1 → 4 → 3 → 2

解析

  1. 同步执行 14
  2. 清空微任务,执行 3
  3. 执行一个宏任务,执行 2

五、类型转换(隐式转换坑多)

1. 显式转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 转数字
Number('123'); // 123
parseInt('123px'); // 123(解析到非数字为止)
parseFloat('12.34px'); // 12.34

// 转字符串
String(123); // '123'
(123).toString(); // '123'

// 转布尔
Boolean(0); // false
Boolean(''); // false
Boolean(null); // false
Boolean(undefined); // false
Boolean(NaN); // false
// 其余均为 true(包括 '0'、[]、{} 等!)

2. 隐式转换(高频考点)

规则 1:== 比较(尽量用 === 避免隐式转换)

1
2
3
4
5
// 经典坑
console.log([] == false); // true([] 转 '',再转 0;false 转 0)
console.log({} == false); // false({} 转 '[object Object]',再转 NaN)
console.log(null == undefined); // true(特殊规则)
console.log(null === undefined); // false

规则 2:+ 号运算

  • 若有一个操作数是字符串,另一个转字符串拼接;
  • 否则转数字相加。
    1
    2
    3
    4
    console.log(1 + '2'); // '12'(字符串拼接)
    console.log(1 + true); // 2(true 转 1)
    console.log(1 + null); // 1(null 转 0)
    console.log(1 + undefined); // NaN(undefined 转 NaN)

规则 3:逻辑运算符(&&||!

  • &&:返回第一个 falsy 值,否则返回最后一个值;
  • ||:返回第一个 truthy 值,否则返回最后一个值;
  • !:转布尔后取反。
    1
    2
    3
    4
    5
    6
    console.log(0 && 1); // 0
    console.log(1 && 2); // 2
    console.log(0 || 1); // 1
    console.log(1 || 2); // 1
    console.log(!0); // true
    console.log(![]); // false([] 是 truthy)

六、this 指向(5种情况全覆盖)

1. 全局调用(非严格模式)

1
2
3
4
function fn() {
console.log(this);
}
fn(); // window(浏览器)/ global(Node.js)

2. 对象方法调用

1
2
3
4
5
6
7
const obj = {
name: 'Alice',
sayHi() {
console.log(this.name);
}
};
obj.sayHi(); // Alice(this 指向 obj)

3. new 调用

1
2
3
4
5
function Person(name) {
this.name = name;
}
const alice = new Person('Alice');
console.log(alice.name); // Alice(this 指向新创建的实例)

4. call/apply/bind 绑定

1
2
3
4
5
6
7
8
function sayHi() {
console.log(this.name);
}
const obj = { name: 'Bob' };
sayHi.call(obj); // Bob(call 立即执行,this 指向 obj)
sayHi.apply(obj); // Bob(apply 与 call 类似,参数用数组)
const boundSayHi = sayHi.bind(obj); // bind 返回新函数,不立即执行
boundSayHi(); // Bob

5. 箭头函数

箭头函数没有自己的 this,**继承外层作用域的 this**,且绑定后不可改变。

1
2
3
4
5
6
7
8
9
const obj = {
name: 'Alice',
sayHi() {
setTimeout(() => {
console.log(this.name); // Alice(this 继承自 sayHi 的作用域)
}, 1000);
}
};
obj.sayHi();

七、深拷贝与浅拷贝

1. 浅拷贝(只复制一层,嵌套对象仍共享引用)

1
2
3
4
5
6
7
8
9
10
// 方法1:Object.assign
const obj1 = { a: 1, b: { c: 2 } };
const obj2 = Object.assign({}, obj1);
obj2.a = 10;
obj2.b.c = 20;
console.log(obj1.a); // 1(第一层不共享)
console.log(obj1.b.c); // 20(嵌套对象共享引用)

// 方法2:扩展运算符
const obj3 = { ...obj1 };

2. 深拷贝(完全复制,嵌套对象不共享引用)

方法1:JSON.parse(JSON.stringify())(简单但有局限)

1
2
3
4
5
const obj1 = { a: 1, b: { c: 2 }, d: function() {} };
const obj2 = JSON.parse(JSON.stringify(obj1));
obj2.b.c = 20;
console.log(obj1.b.c); // 2(不共享)
console.log(obj2.d); // undefined(无法复制函数、正则、Symbol 等)

局限:无法复制函数、正则、Symbol、Date、循环引用对象等。

方法2:structuredClone()(现代浏览器/Node.js 17+ 支持)

1
2
3
const obj1 = { a: 1, b: { c: 2 }, date: new Date() };
const obj2 = structuredClone(obj1);
console.log(obj2.date instanceof Date); // true(支持 Date、RegExp 等)

局限:仍无法复制函数。

方法3:递归实现(完整版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function deepClone(obj, hash = new WeakMap()) {
if (obj === null || typeof obj !== 'object') return obj;
if (obj instanceof Date) return new Date(obj);
if (obj instanceof RegExp) return new RegExp(obj);
if (hash.has(obj)) return hash.get(obj); // 处理循环引用

const cloneObj = Array.isArray(obj) ? [] : {};
hash.set(obj, cloneObj);

for (let key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
cloneObj[key] = deepClone(obj[key], hash);
}
}
return cloneObj;
}

补充

JavaScript 异步编程

异步编程是 JavaScript 的核心特性,用于处理网络请求、文件读写、定时器等耗时操作,避免阻塞主线程。

1. Promise / async-await

这是目前处理异步操作的主流标准方案async-awaitPromise 的语法糖,让异步代码看起来像同步代码。

Promise 核心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// Promise 基础用法
const fetchData = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
const success = true;
if (success) {
resolve({ data: "用户数据" }); // 成功,触发 then
} else {
reject(new Error("请求失败")); // 失败,触发 catch
}
}, 1000);
});
};

// Promise 链式调用
fetchData()
.then(res => console.log(res.data))
.catch(err => console.error(err))
.finally(() => console.log("请求结束"));

// Promise 静态方法(高频考点)
Promise.all([p1, p2, p3]); // 所有成功才成功,一个失败就失败
Promise.race([p1, p2, p3]); // 谁先完成就用谁的结果
Promise.allSettled([p1, p2]); // 等待所有完成,无论成功失败
Promise.any([p1, p2, p3]); // 只要有一个成功就成功

async-await 语法糖

1
2
3
4
5
6
7
8
9
10
11
12
// async-await 让异步代码同步化
async function getUserData() {
try {
// await 只能在 async 函数中使用
const res = await fetchData();
console.log(res.data);
return res;
} catch (err) {
// 用 try-catch 捕获错误,替代 Promise.catch
console.error(err);
}
}

2. Generator

Generator 是 ES6 引入的一种特殊的迭代器函数,可以暂停和恢复执行,在 async-await 普及前,常被用于实现异步流程控制(如 co 库)。

核心语法与特性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 1. 基础定义与执行:function* 定义,yield 暂停
function* generatorFunc() {
console.log("第一步");
yield "暂停点1"; // 遇到 yield 暂停,返回后面的值
console.log("第二步");
yield "暂停点2";
console.log("第三步");
return "结束";
}

// 执行 Generator 函数返回一个迭代器对象
const gen = generatorFunc();
console.log(gen.next()); // { value: "暂停点1", done: false }
console.log(gen.next()); // { value: "暂停点2", done: false }
console.log(gen.next()); // { value: "结束", done: true }

// 2. next() 传参:参数会作为上一个 yield 的返回值
function* calc() {
const a = yield 10;
const b = yield a + 20; // a 是 next() 传入的 100
yield b + 30; // b 是 next() 传入的 200
}
const c = calc();
console.log(c.next()); // { value: 10, done: false }
console.log(c.next(100)); // { value: 120, done: false }
console.log(c.next(200)); // { value: 230, done: false }

// 3. 异步流程控制(历史场景,了解即可)
// 早期通过 Generator + co 库实现类似 async-await 的效果
function* asyncTask() {
const data1 = yield fetch("/api/1");
const data2 = yield fetch("/api/2?id=" + data1.id);
return data2;
}
// co(asyncTask); // co 库自动执行 Generator

ES5+语法重点,持续更新
https://cszy.top/20250428-es5+语法重点/
作者
csorz
发布于
2025年4月28日
许可协议