Skip to content

ES6+新特性

ES6(ECMAScript 2015)及后续版本引入了许多新特性,大大增强了 JavaScript 的功能。本章将介绍常用的 ES6+ 新特性。

let 和 const

let 块级作用域

javascript
// let 声明的变量具有块级作用域
if (true) {
    let name = '张三';
    console.log(name);  // 张三
}
// console.log(name);  // 错误:name 未定义

// var 是函数作用域
if (true) {
    var age = 25;
}
console.log(age);  // 25

// let 不会变量提升
// console.log(x);  // 错误:Cannot access 'x' before initialization
let x = 10;

// var 会变量提升
console.log(y);  // undefined
var y = 20;

// let 不允许重复声明
let a = 1;
// let a = 2;  // 错误:不能重复声明

// 暂时性死区(Temporal Dead Zone)
let temp = 'global';
function test() {
    // 暂时性死区开始
    // console.log(temp);  // 错误
    let temp = 'local';  // 暂时性死区结束
    console.log(temp);
}
test();

const 常量

javascript
// const 声明的变量必须初始化
const PI = 3.14159;
// const x;  // 错误:缺少初始化

// const 声明的变量不能重新赋值
const name = '张三';
// name = '李四';  // 错误:不能重新赋值

// const 也是块级作用域
if (true) {
    const MAX = 100;
}
// console.log(MAX);  // 错误:未定义

// const 对象的属性可以修改
const person = {
    name: '张三'
};
person.name = '李四';  // 允许
person.age = 25;       // 允许添加新属性
// person = {};  // 错误:不能重新赋值

// const 数组的元素可以修改
const arr = [1, 2, 3];
arr.push(4);      // 允许
arr[0] = 10;      // 允许
// arr = [];      // 错误

// 冻结对象使其完全不可变
const frozen = Object.freeze({ name: '张三' });
// frozen.name = '李四';  // 无效(严格模式下报错)

箭头函数

基本语法

javascript
// 传统函数
function add(a, b) {
    return a + b;
}

// 箭头函数
const add = (a, b) => {
    return a + b;
};

// 简写:单个表达式可省略花括号和 return
const add = (a, b) => a + b;

// 单个参数可省略括号
const double = x => x * 2;

// 无参数需要空括号
const sayHi = () => console.log('Hi!');

// 返回对象需要用括号包裹
const createUser = (name, age) => ({ name, age });

// 多行函数体需要花括号
const calculate = (a, b) => {
    const sum = a + b;
    const product = a * b;
    return { sum, product };
};

this 绑定

javascript
// 箭头函数没有自己的 this,继承外层作用域的 this
const person = {
    name: '张三',
    
    // 传统函数:this 指向调用者
    greet: function() {
        console.log('你好,' + this.name);
    },
    
    // 箭头函数:this 继承自外层(通常是 window)
    greetArrow: () => {
        console.log('你好,' + this.name);  // this.name 是 undefined
    },
    
    // 正确用法:在方法内部使用箭头函数
    greetLater: function() {
        setTimeout(() => {
            console.log('你好,' + this.name);  // this 指向 person
        }, 1000);
    }
};

person.greet();        // 你好,张三
person.greetArrow();   // 你好,undefined
person.greetLater();   // 你好,张三

// 箭头函数不能用作构造函数
// const Person = (name) => { this.name = name; };
// new Person('张三');  // 错误

// 箭头函数没有 arguments 对象
const func = () => {
    // console.log(arguments);  // 错误
};

解构赋值

数组解构

javascript
// 基本用法
const [a, b, c] = [1, 2, 3];
console.log(a, b, c);  // 1 2 3

// 跳过元素
const [first, , third] = [1, 2, 3];
console.log(first, third);  // 1 3

// 默认值
const [x, y, z = 10] = [1, 2];
console.log(x, y, z);  // 1 2 10

// 剩余元素
const [head, ...tail] = [1, 2, 3, 4, 5];
console.log(head);  // 1
console.log(tail);  // [2, 3, 4, 5]

// 交换变量
let m = 1, n = 2;
[m, n] = [n, m];
console.log(m, n);  // 2 1

// 嵌套解构
const [[a1, a2], [b1, b2]] = [[1, 2], [3, 4]];
console.log(a1, a2, b1, b2);  // 1 2 3 4

对象解构

javascript
// 基本用法
const { name, age } = { name: '张三', age: 25 };
console.log(name, age);  // 张三 25

// 重命名变量
const { name: userName, age: userAge } = { name: '张三', age: 25 };
console.log(userName, userAge);  // 张三 25

// 默认值
const { name, city = '北京' } = { name: '张三' };
console.log(city);  // 北京

// 剩余属性
const { a, ...rest } = { a: 1, b: 2, c: 3 };
console.log(a);     // 1
console.log(rest);  // { b: 2, c: 3 }

// 嵌套解构
const person = {
    name: '张三',
    address: {
        city: '北京',
        district: '朝阳'
    }
};
const { address: { city } } = person;
console.log(city);  // 北京

// 解构函数参数
function greet({ name, age }) {
    console.log(`${name}, ${age}岁`);
}
greet({ name: '张三', age: 25 });

展开运算符

数组展开

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

// 复制数组
const copy = [...arr1];
console.log(copy);  // [1, 2, 3]

// 合并数组
const arr3 = [1, 2];
const arr4 = [3, 4];
const merged = [...arr3, ...arr4];
console.log(merged);  // [1, 2, 3, 4]

// 将字符串转为数组
const chars = [...'hello'];
console.log(chars);  // ['h', 'e', 'l', 'l', 'o']

// 将 NodeList 转为数组
// const divs = [...document.querySelectorAll('div')];

// Math.max 使用展开
const numbers = [1, 5, 3, 9, 2];
console.log(Math.max(...numbers));  // 9

对象展开

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

// 复制对象(浅拷贝)
const copy = { ...obj1 };

// 合并对象(后面的属性会覆盖前面的)
const obj3 = { a: 1, b: 2 };
const obj4 = { b: 3, c: 4 };
const merged = { ...obj3, ...obj4 };
console.log(merged);  // { a: 1, b: 3, c: 4 }

// 修改对象属性
const person = { name: '张三', age: 25 };
const updated = { ...person, age: 26 };
console.log(updated);  // { name: '张三', age: 26 }

模板字符串

javascript
// 基本用法
const name = '张三';
const greeting = `你好,${name}!`;
console.log(greeting);  // 你好,张三!

// 多行字符串
const html = `
    <div class="container">
        <h1>标题</h1>
        <p>内容</p>
    </div>
`;

// 表达式计算
const a = 10;
const b = 20;
console.log(`${a} + ${b} = ${a + b}`);  // 10 + 20 = 30

// 调用函数
function toUpper(str) {
    return str.toUpperCase();
}
console.log(`Hello ${toUpper('world')}!`);  // Hello WORLD!

// 标签模板
function highlight(strings, ...values) {
    return strings.reduce((result, str, i) => {
        const value = values[i] ? `<strong>${values[i]}</strong>` : '';
        return result + str + value;
    }, '');
}

const result = highlight`你好,${name}!欢迎来到${'北京'}`;
console.log(result);  // 你好,<strong>张三</strong>!欢迎来到<strong>北京</strong>

// 原始字符串
console.log(String.raw`Hello\nWorld`);  // Hello\nWorld(不转义)

默认参数

javascript
// 函数默认参数
function greet(name = '游客', greeting = '你好') {
    console.log(`${greeting},${name}!`);
}

greet();              // 你好,游客!
greet('张三');        // 你好,张三!
greet('张三', '欢迎'); // 欢迎,张三!

// 默认值可以是表达式
function add(a, b = a * 2) {
    return a + b;
}
console.log(add(5));  // 15

// 默认值可以是函数调用
function getDefault() {
    return '默认值';
}

function test(value = getDefault()) {
    console.log(value);
}

test();        // 默认值
test('自定义'); // 自定义

// 解构参数默认值
function createUser({ name = '匿名', age = 0 } = {}) {
    return { name, age };
}

console.log(createUser());               // { name: '匿名', age: 0 }
console.log(createUser({ name: '张三' })); // { name: '张三', age: 0 }

剩余参数

javascript
// 剩余参数收集所有剩余参数
function sum(...numbers) {
    return numbers.reduce((total, n) => total + n, 0);
}

console.log(sum(1, 2, 3, 4, 5));  // 15

// 与普通参数结合
function greetAll(greeting, ...names) {
    names.forEach(name => {
        console.log(`${greeting},${name}!`);
    });
}

greetAll('你好', '张三', '李四', '王五');

// 剩余参数是真正的数组
function logArgs(...args) {
    console.log(Array.isArray(args));  // true
    console.log(args.length);
}

// 剩余参数必须是最后一个参数
// function wrong(...rest, last) {}  // 错误

// 解构中的剩余元素
const [first, ...rest] = [1, 2, 3, 4, 5];
console.log(first);  // 1
console.log(rest);   // [2, 3, 4, 5]

const { a, ...others } = { a: 1, b: 2, c: 3 };
console.log(a);       // 1
console.log(others);  // { b: 2, c: 3 }

对象字面量增强

javascript
// 属性简写
const name = '张三';
const age = 25;

// ES5
const person1 = {
    name: name,
    age: age
};

// ES6 简写
const person2 = { name, age };

// 方法简写
const person3 = {
    name: '张三',
    
    // ES5
    greet: function() {
        console.log('你好');
    },
    
    // ES6 简写
    greet2() {
        console.log('你好');
    }
};

// 计算属性名
const key = 'name';
const obj = {
    [key]: '张三',
    ['full' + 'Name']: '张三三',
    ['get' + 'Age']() {
        return 25;
    }
};

console.log(obj.name);      // 张三
console.log(obj.fullName);  // 张三三
console.log(obj.getAge());  // 25

类(Class)

基本语法

javascript
// ES5 构造函数
function Person(name, age) {
    this.name = name;
    this.age = age;
}
Person.prototype.greet = function() {
    console.log('你好,我是' + this.name);
};

// ES6 类
class Person {
    // 构造函数
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    
    // 实例方法
    greet() {
        console.log(`你好,我是${this.name}`);
    }
    
    // getter
    get info() {
        return `${this.name} (${this.age}岁)`;
    }
    
    // setter
    set info(value) {
        const [name, age] = value.split(',');
        this.name = name;
        this.age = parseInt(age);
    }
    
    // 静态方法
    static create(name, age) {
        return new Person(name, age);
    }
    
    // 静态属性
    static species = '人类';
}

const person = new Person('张三', 25);
person.greet();  // 你好,我是张三
console.log(person.info);  // 张三 (25岁)

// 静态方法调用
const person2 = Person.create('李四', 30);
console.log(Person.species);  // 人类

继承

javascript
class Animal {
    constructor(name) {
        this.name = name;
    }
    
    speak() {
        console.log(`${this.name}发出声音`);
    }
}

class Dog extends Animal {
    constructor(name, breed) {
        super(name);  // 调用父类构造函数
        this.breed = breed;
    }
    
    // 重写方法
    speak() {
        console.log(`${this.name}汪汪叫`);
    }
    
    // 新方法
    fetch() {
        console.log(`${this.name}去捡球`);
    }
}

const dog = new Dog('小黄', '金毛');
dog.speak();  // 小黄汪汪叫
dog.fetch();  // 小黄去捡球

// 检查继承关系
console.log(dog instanceof Dog);    // true
console.log(dog instanceof Animal); // true

私有属性(ES2022)

javascript
class Person {
    // 私有属性(以 # 开头)
    #name;
    #age;
    
    constructor(name, age) {
        this.#name = name;
        this.#age = age;
    }
    
    // 公有方法访问私有属性
    getName() {
        return this.#name;
    }
    
    // 私有方法
    #validateAge(age) {
        return age > 0 && age < 150;
    }
    
    setAge(age) {
        if (this.#validateAge(age)) {
            this.#age = age;
        }
    }
}

const person = new Person('张三', 25);
console.log(person.getName());  // 张三
// console.log(person.#name);   // 错误:私有属性无法访问

模块化

导出(export)

javascript
// module.js

// 命名导出
export const name = '张三';
export const age = 25;

export function greet() {
    console.log('你好');
}

export class Person {
    constructor(name) {
        this.name = name;
    }
}

// 统一导出
const a = 1;
const b = 2;
function add(x, y) {
    return x + y;
}
export { a, b, add };

// 重命名导出
export { add as sum };

// 默认导出(每个模块只能有一个)
export default function() {
    console.log('默认导出');
}

// 或
const obj = { name: '张三' };
export default obj;

导入(import)

javascript
// 导入命名导出
import { name, age, greet } from './module.js';

// 重命名导入
import { add as sum } from './module.js';

// 导入所有
import * as module from './module.js';
console.log(module.name);
module.greet();

// 导入默认导出
import myFunc from './module.js';
myFunc();

// 混合导入
import myFunc, { name, age } from './module.js';

// 动态导入
async function loadModule() {
    const module = await import('./module.js');
    module.greet();
}

// 仅执行模块(不导入)
import './module.js';

Promise

基本用法

javascript
// 创建 Promise
const promise = new Promise((resolve, reject) => {
    // 异步操作
    setTimeout(() => {
        const success = true;
        if (success) {
            resolve('成功');
        } else {
            reject('失败');
        }
    }, 1000);
});

// 使用 Promise
promise
    .then(result => {
        console.log(result);  // 成功
        return '处理后的结果';
    })
    .then(result => {
        console.log(result);  // 处理后的结果
    })
    .catch(error => {
        console.log(error);
    })
    .finally(() => {
        console.log('完成');
    });

// Promise 状态
// pending:进行中
// fulfilled:已成功
// rejected:已失败

Promise 静态方法

javascript
// Promise.resolve()
const p1 = Promise.resolve('成功');
p1.then(result => console.log(result));

// Promise.reject()
const p2 = Promise.reject('失败');
p2.catch(error => console.log(error));

// Promise.all():所有都成功才成功
const p3 = Promise.resolve(1);
const p4 = Promise.resolve(2);
const p5 = Promise.resolve(3);

Promise.all([p3, p4, p5])
    .then(results => console.log(results))  // [1, 2, 3]
    .catch(error => console.log(error));

// Promise.race():第一个完成的结果
Promise.race([p3, p4, p5])
    .then(result => console.log(result));  // 1

// Promise.allSettled():等待所有完成
Promise.allSettled([p3, Promise.reject('失败')])
    .then(results => console.log(results));
// [{ status: 'fulfilled', value: 1 }, { status: 'rejected', reason: '失败' }]

// Promise.any():任意一个成功就成功
Promise.any([Promise.reject('失败'), p3])
    .then(result => console.log(result));  // 1

async/await

基本用法

javascript
// async 函数返回 Promise
async function greet() {
    return '你好';
}

greet().then(result => console.log(result));  // 你好

// await 等待 Promise 完成
async function getData() {
    const result = await fetch('/api/data');
    const data = await result.json();
    return data;
}

// 错误处理
async function fetchData() {
    try {
        const response = await fetch('/api/data');
        const data = await response.json();
        return data;
    } catch (error) {
        console.log('错误:', error);
        throw error;
    }
}

// 并行执行
async function fetchAll() {
    const [users, posts] = await Promise.all([
        fetch('/api/users').then(r => r.json()),
        fetch('/api/posts').then(r => r.json())
    ]);
    return { users, posts };
}

Map 和 Set

Map

javascript
// 创建 Map
const map = new Map();

// 设置值
map.set('name', '张三');
map.set('age', 25);
map.set(1, '数字键');

// 获取值
console.log(map.get('name'));  // 张三

// 检查键是否存在
console.log(map.has('name'));  // true

// 删除
map.delete('age');

// 大小
console.log(map.size);

// 清空
// map.clear();

// 遍历
map.forEach((value, key) => {
    console.log(key, value);
});

for (let [key, value] of map) {
    console.log(key, value);
}

// 从数组创建
const map2 = new Map([
    ['name', '张三'],
    ['age', 25]
]);

// 对象转 Map
const obj = { name: '张三', age: 25 };
const map3 = new Map(Object.entries(obj));

// Map 转对象
const obj2 = Object.fromEntries(map3);

Set

javascript
// 创建 Set
const set = new Set();

// 添加值
set.add(1);
set.add(2);
set.add(2);  // 重复值不会被添加

console.log(set.size);  // 2

// 检查是否存在
console.log(set.has(1));  // true

// 删除
set.delete(1);

// 清空
// set.clear();

// 遍历
set.forEach(value => console.log(value));

for (let value of set) {
    console.log(value);
}

// 数组去重
const arr = [1, 2, 2, 3, 3, 3];
const unique = [...new Set(arr)];
console.log(unique);  // [1, 2, 3]

// 从数组创建
const set2 = new Set([1, 2, 3, 3]);

小结

本章学习了 ES6+ 的常用新特性:

  • let 和 const:块级作用域、常量
  • 箭头函数:简洁语法、this 绑定
  • 解构赋值:数组解构、对象解构
  • 展开运算符:数组展开、对象展开
  • 模板字符串:字符串插值、多行字符串
  • 默认参数:函数参数默认值
  • 剩余参数:收集剩余参数
  • :class 语法、继承、私有属性
  • 模块化:export、import
  • Promise:异步编程
  • async/await:同步风格的异步代码
  • Map 和 Set:新的数据结构

下一章我们将学习 异步编程,深入了解 JavaScript 的异步机制。