Joe


年少不知愁滋味,老来方知行路难

进入博客 >

Joe

Joe

年少不知愁滋味,老来方知行路难
  • 文章 105篇
  • 评论 1条
  • 分类 5个
  • 标签 15个
2020-12-26

JavaScript ES6新特性一览

JavaScript ES6新特性一览:现代前端开发的基石

0x00 引言:JavaScript的演进

2015年6月,ECMAScript 6(ES6,正式名称ES2015)发布,这是JavaScript历史上最重要的一次更新。它引入了类、模块、箭头函数、Promise等现代编程语言的特性,彻底改变了前端开发的范式。

如今,ES6+(包括ES2016-ES2023)已成为前端开发的标配。理解这些特性不仅能写出更优雅的代码,更能深入理解现代框架(React、Vue)的底层原理。

本文将系统梳理ES6的核心特性,从语法糖到底层机制,帮助你构建完整的知识体系。

0x01 let与const:块级作用域

1.1 var的问题

ES5的var存在三大问题:

问题1:变量提升(Hoisting)

console.log(x); // undefined(而非ReferenceError)
var x = 10;

// 实际执行顺序
var x;
console.log(x);
x = 10;

问题2:无块级作用域

if (true) {
    var x = 10;
}
console.log(x); // 10(泄漏到外部)

for (var i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 100);
}
// 输出:3, 3, 3(闭包陷阱)

问题3:重复声明

var x = 10;
var x = 20; // 不报错,直接覆盖

1.2 let:块级作用域变量

// 块级作用域
if (true) {
    let x = 10;
}
console.log(x); // ReferenceError

// 解决闭包问题
for (let i = 0; i < 3; i++) {
    setTimeout(() => console.log(i), 100);
}
// 输出:0, 1, 2

// 暂时性死区(TDZ)
console.log(x); // ReferenceError(不是undefined)
let x = 10;

1.3 const:常量声明

const PI = 3.14159;
PI = 3.14; // TypeError: Assignment to constant variable

// 注意:const只保证引用不变,对象内容可变
const obj = { x: 1 };
obj.x = 2; // 合法
obj = {}; // TypeError

// 冻结对象内容
const frozen = Object.freeze({ x: 1 });
frozen.x = 2; // 静默失败(严格模式报错)

最佳实践

  • 优先使用const(90%的场景)
  • 需要重新赋值时用let
  • 完全避免var

0x02 箭头函数:简洁的函数表达式

2.1 语法简化

// ES5
var add = function(a, b) {
    return a + b;
};

// ES6:基本形式
const add = (a, b) => {
    return a + b;
};

// 简写:单表达式自动返回
const add = (a, b) => a + b;

// 单参数可省略括号
const square = x => x * x;

// 无参数
const random = () => Math.random();

// 返回对象字面量需要括号
const makePoint = (x, y) => ({ x, y });

2.2 this绑定的革命

ES5的this陷阱

function Timer() {
    this.seconds = 0;
    setInterval(function() {
        this.seconds++; // this指向window,而非Timer实例
    }, 1000);
}

// 传统解决方案
function Timer() {
    var self = this;
    setInterval(function() {
        self.seconds++;
    }, 1000);
}

ES6箭头函数

function Timer() {
    this.seconds = 0;
    setInterval(() => {
        this.seconds++; // 词法this,继承外层作用域
    }, 1000);
}

React类组件的应用

class Button extends React.Component {
    // 错误:this丢失
    handleClick() {
        console.log(this); // undefined
    }
    
    // 方案1:构造函数绑定
    constructor(props) {
        super(props);
        this.handleClick = this.handleClick.bind(this);
    }
    
    // 方案2:箭头函数(推荐)
    handleClick = () => {
        console.log(this); // 正确指向组件实例
    }
    
    render() {
        return <button onClick={this.handleClick}>Click</button>;
    }
}

2.3 不适合箭头函数的场景

// 1. 对象方法
const obj = {
    name: 'Alice',
    greet: () => {
        console.log(this.name); // undefined,this指向全局
    }
};

// 应该用普通函数
const obj = {
    name: 'Alice',
    greet() {
        console.log(this.name); // 'Alice'
    }
};

// 2. 构造函数
const Person = (name) => {
    this.name = name; // TypeError: 箭头函数没有constructor
};

// 3. 需要动态this的场景
button.addEventListener('click', () => {
    console.log(this); // window,而非button元素
});

0x03 模板字符串:强大的字符串处理

3.1 基本用法

// 多行字符串
const html = `
<div>
    <h1>Title</h1>
    <p>Content</p>
</div>
`;

// 插值表达式
const name = 'Alice';
const age = 30;
console.log(`${name} is ${age} years old`);

// 表达式计算
console.log(`1 + 1 = ${1 + 1}`);

// 嵌套模板
const nested = `Outer ${`Inner ${42}`}`;

3.2 标签模板(Tagged Templates)

// 自定义模板处理器
function highlight(strings, ...values) {
    return strings.reduce((result, str, i) => {
        return result + str + (values[i] ? `<mark>${values[i]}</mark>` : '');
    }, '');
}

const name = 'Alice';
const age = 30;
const output = highlight`Name: ${name}, Age: ${age}`;
// "Name: <mark>Alice</mark>, Age: <mark>30</mark>"

实战应用:SQL防注入

function sql(strings, ...values) {
    return strings.reduce((query, str, i) => {
        const value = values[i];
        const escaped = typeof value === 'string' 
            ? value.replace(/'/g, "''") 
            : value;
        return query + str + (escaped !== undefined ? `'${escaped}'` : '');
    }, '');
}

const username = "admin' OR '1'='1";
const query = sql`SELECT * FROM users WHERE name = ${username}`;
// "SELECT * FROM users WHERE name = 'admin'' OR ''1''=''1'"

0x04 解构赋值:优雅的数据提取

4.1 数组解构

// 基本用法
const [a, b, c] = [1, 2, 3];

// 跳过元素
const [first, , third] = [1, 2, 3];

// 剩余元素
const [head, ...tail] = [1, 2, 3, 4];
// head: 1, tail: [2, 3, 4]

// 默认值
const [x = 0, y = 0] = [10];
// x: 10, y: 0

// 交换变量
let a = 1, b = 2;
[a, b] = [b, a];

4.2 对象解构

// 基本用法
const { name, age } = { name: 'Alice', age: 30 };

// 重命名
const { name: userName, age: userAge } = user;

// 默认值
const { name = 'Anonymous', age = 0 } = {};

// 嵌套解构
const { address: { city, country } } = {
    address: { city: 'Beijing', country: 'China' }
};

// 剩余属性
const { name, ...rest } = { name: 'Alice', age: 30, city: 'NYC' };
// rest: { age: 30, city: 'NYC' }

4.3 函数参数解构

// 传统方式
function greet(user) {
    console.log(`Hello, ${user.name}!`);
}

// 解构参数
function greet({ name, age }) {
    console.log(`Hello, ${name} (${age} years old)!`);
}

// 默认值
function greet({ name = 'Guest', age = 0 } = {}) {
    console.log(`Hello, ${name}!`);
}

// React组件中的应用
function UserCard({ name, avatar, bio = 'No bio available' }) {
    return (
        <div>
            <img src={avatar} />
            <h3>{name}</h3>
            <p>{bio}</p>
        </div>
    );
}

0x05 展开运算符:数据的拷贝与合并

5.1 数组展开

// 数组拷贝(浅拷贝)
const arr1 = [1, 2, 3];
const arr2 = [...arr1];

// 数组合并
const combined = [...arr1, ...arr2, 4, 5];

// 函数参数展开
Math.max(...[1, 5, 3, 9, 2]); // 9

// 将类数组转为真数组
const nodeList = document.querySelectorAll('div');
const array = [...nodeList];

// 字符串转数组
[...'hello']; // ['h', 'e', 'l', 'l', 'o']

5.2 对象展开

// 对象拷贝
const obj1 = { x: 1, y: 2 };
const obj2 = { ...obj1 };

// 对象合并(后者覆盖前者)
const defaults = { color: 'red', size: 'large' };
const options = { size: 'small' };
const config = { ...defaults, ...options };
// { color: 'red', size: 'small' }

// React状态更新
this.setState(prevState => ({
    ...prevState,
    count: prevState.count + 1
}));

5.3 剩余参数(Rest Parameters)

// 收集剩余参数
function sum(...numbers) {
    return numbers.reduce((total, n) => total + n, 0);
}
sum(1, 2, 3, 4, 5); // 15

// 替代arguments对象
function oldWay() {
    const args = Array.prototype.slice.call(arguments);
}

function newWay(...args) {
    // args已经是真数组
}

0x06 Promise与异步编程

6.1 Promise基础

// 创建Promise
const promise = new Promise((resolve, reject) => {
    setTimeout(() => {
        const success = Math.random() > 0.5;
        if (success) {
            resolve('Success!');
        } else {
            reject(new Error('Failed!'));
        }
    }, 1000);
});

// 使用Promise
promise
    .then(result => console.log(result))
    .catch(error => console.error(error))
    .finally(() => console.log('Done'));

6.2 Promise链式调用

// 避免回调地狱
fetch('/api/user')
    .then(response => response.json())
    .then(user => fetch(`/api/posts?userId=${user.id}`))
    .then(response => response.json())
    .then(posts => {
        console.log('User posts:', posts);
    })
    .catch(error => {
        console.error('Error:', error);
    });

6.3 Promise静态方法

// Promise.all:并行执行,全部成功
Promise.all([
    fetch('/api/user'),
    fetch('/api/posts'),
    fetch('/api/comments')
]).then(([user, posts, comments]) => {
    // 全部完成
});

// Promise.race:返回最快的
Promise.race([
    fetch('/api/server1'),
    fetch('/api/server2')
]).then(response => {
    // 最快的服务器响应
});

// Promise.allSettled:等待全部完成(不管成功失败)
Promise.allSettled([
    Promise.resolve(1),
    Promise.reject('error'),
    Promise.resolve(3)
]).then(results => {
    // [
    //   { status: 'fulfilled', value: 1 },
    //   { status: 'rejected', reason: 'error' },
    //   { status: 'fulfilled', value: 3 }
    // ]
});

// Promise.any:返回第一个成功的
Promise.any([
    Promise.reject('error1'),
    Promise.resolve(2),
    Promise.resolve(3)
]).then(result => {
    // 2(第一个成功的)
});

6.4 async/await(ES2017)

// Promise方式
function getUser() {
    return fetch('/api/user')
        .then(res => res.json())
        .then(user => {
            return fetch(`/api/posts?userId=${user.id}`)
                .then(res => res.json());
        });
}

// async/await方式(更清晰)
async function getUser() {
    const userRes = await fetch('/api/user');
    const user = await userRes.json();
    
    const postsRes = await fetch(`/api/posts?userId=${user.id}`);
    const posts = await postsRes.json();
    
    return posts;
}

// 错误处理
async function fetchData() {
    try {
        const response = await fetch('/api/data');
        const data = await response.json();
        return data;
    } catch (error) {
        console.error('Fetch failed:', 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 };
}

0x07 类(Class):面向对象编程

7.1 类的定义

class Person {
    // 构造函数
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    
    // 实例方法
    greet() {
        console.log(`Hello, I'm ${this.name}`);
    }
    
    // Getter
    get description() {
        return `${this.name} (${this.age} years old)`;
    }
    
    // Setter
    set age(value) {
        if (value < 0) throw new Error('Age must be positive');
        this._age = value;
    }
    
    // 静态方法
    static create(name, age) {
        return new Person(name, age);
    }
}

// 使用
const person = new Person('Alice', 30);
person.greet();

7.2 继承

class Animal {
    constructor(name) {
        this.name = name;
    }
    
    speak() {
        console.log(`${this.name} makes a sound`);
    }
}

class Dog extends Animal {
    constructor(name, breed) {
        super(name); // 调用父类构造函数
        this.breed = breed;
    }
    
    speak() {
        super.speak(); // 调用父类方法
        console.log(`${this.name} barks`);
    }
}

const dog = new Dog('Rex', 'Labrador');
dog.speak();
// "Rex makes a sound"
// "Rex barks"

7.3 私有属性(ES2022)

class BankAccount {
    #balance = 0; // 私有属性
    
    constructor(initialBalance) {
        this.#balance = initialBalance;
    }
    
    deposit(amount) {
        this.#balance += amount;
    }
    
    getBalance() {
        return this.#balance;
    }
}

const account = new BankAccount(1000);
account.deposit(500);
console.log(account.getBalance()); // 1500
console.log(account.#balance); // SyntaxError: 私有属性不可访问

0x08 模块化(Modules)

8.1 导出(Export)

// export.js

// 命名导出
export const PI = 3.14159;
export function square(x) {
    return x * x;
}
export class Circle {
    constructor(radius) {
        this.radius = radius;
    }
}

// 批量导出
const name = 'Alice';
const age = 30;
export { name, age };

// 重命名导出
export { name as userName, age as userAge };

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

8.2 导入(Import)

// 命名导入
import { PI, square } from './math.js';

// 重命名导入
import { name as userName } from './user.js';

// 导入全部
import * as math from './math.js';
console.log(math.PI, math.square(2));

// 默认导入
import myFunction from './module.js';

// 混合导入
import React, { useState, useEffect } from 'react';

// 动态导入(ES2020)
const module = await import('./module.js');
// 或
import('./module.js').then(module => {
    module.doSomething();
});

8.3 模块的特性

// 1. 模块自动使用严格模式
// 无需 'use strict';

// 2. 模块顶层this是undefined
console.log(this); // undefined(不是window)

// 3. 模块只执行一次
// utils.js
console.log('Module loaded');

// main.js
import './utils.js'; // "Module loaded"
import './utils.js'; // 不会再次输出

0x09 增强的对象字面量

9.1 属性简写

const name = 'Alice';
const age = 30;

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

// ES6
const person = { name, age };

9.2 方法简写

// ES5
const obj = {
    greet: function() {
        console.log('Hello');
    }
};

// ES6
const obj = {
    greet() {
        console.log('Hello');
    }
};

9.3 计算属性名

const prop = 'foo';

const obj = {
    [prop]: 'bar',
    ['key' + 42]: 'value',
    [Symbol.iterator]: function*() {
        yield 1;
        yield 2;
    }
};

console.log(obj.foo); // 'bar'
console.log(obj.key42); // 'value'

0x10 新增的数据结构

10.1 Set:唯一值集合

// 创建Set
const set = new Set([1, 2, 3, 2, 1]);
console.log(set); // Set(3) {1, 2, 3}

// 添加元素
set.add(4);

// 删除元素
set.delete(2);

// 检查存在
set.has(1); // true

// 大小
set.size; // 3

// 遍历
for (const value of set) {
    console.log(value);
}

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

10.2 Map:键值对集合

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

// 设置值
map.set('name', 'Alice');
map.set('age', 30);

// 对象作为键
const obj = {};
map.set(obj, 'value');

// 获取值
map.get('name'); // 'Alice'

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

// 检查存在
map.has('name'); // true

// 大小
map.size; // 2

// 遍历
for (const [key, value] of map) {
    console.log(`${key} = ${value}`);
}

// Map vs Object
const objMap = {
    '[object Object]': 'value' // 对象键会被转为字符串
};

const realMap = new Map();
realMap.set({}, 'value'); // 对象作为键

10.3 WeakSet与WeakMap

// WeakSet:弱引用集合,元素只能是对象
const weakSet = new WeakSet();
let obj = { x: 1 };
weakSet.add(obj);

obj = null; // 对象可被垃圾回收

// WeakMap:弱引用键值对
const weakMap = new WeakMap();
let key = { id: 1 };
weakMap.set(key, 'value');

key = null; // 键可被垃圾回收

// 应用:私有数据存储
const privateData = new WeakMap();

class Person {
    constructor(name) {
        privateData.set(this, { name });
    }
    
    getName() {
        return privateData.get(this).name;
    }
}

0x11 Symbol:唯一标识符

// 创建Symbol
const sym1 = Symbol();
const sym2 = Symbol('description');
const sym3 = Symbol('description');

console.log(sym2 === sym3); // false(每个Symbol都是唯一的)

// 对象属性
const obj = {
    [Symbol('privateProperty')]: 'secret'
};

// Symbol不会被常规遍历
Object.keys(obj); // []
Object.getOwnPropertySymbols(obj); // [Symbol(privateProperty)]

// 内置Symbol
class MyArray extends Array {
    // 自定义迭代行为
    *[Symbol.iterator]() {
        for (let i = this.length - 1; i >= 0; i--) {
            yield this[i];
        }
    }
}

const arr = new MyArray(1, 2, 3);
[...arr]; // [3, 2, 1](反向迭代)

0x12 迭代器与生成器

12.1 迭代器协议

// 自定义迭代器
const range = {
    from: 1,
    to: 5,
    
    [Symbol.iterator]() {
        return {
            current: this.from,
            last: this.to,
            
            next() {
                if (this.current <= this.last) {
                    return { done: false, value: this.current++ };
                } else {
                    return { done: true };
                }
            }
        };
    }
};

for (const num of range) {
    console.log(num); // 1, 2, 3, 4, 5
}

12.2 生成器函数

// 生成器函数
function* idGenerator() {
    let id = 1;
    while (true) {
        yield id++;
    }
}

const gen = idGenerator();
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2

// 斐波那契数列
function* fibonacci() {
    let [a, b] = [0, 1];
    while (true) {
        yield a;
        [a, b] = [b, a + b];
    }
}

const fib = fibonacci();
console.log(fib.next().value); // 0
console.log(fib.next().value); // 1
console.log(fib.next().value); // 1
console.log(fib.next().value); // 2

// 异步生成器
async function* asyncGenerator() {
    for (let i = 1; i <= 3; i++) {
        await new Promise(resolve => setTimeout(resolve, 1000));
        yield i;
    }
}

(async () => {
    for await (const num of asyncGenerator()) {
        console.log(num); // 1秒后输出1,再1秒后输出2...
    }
})();

0x13 总结:ES6+的影响

13.1 核心特性速查表

特性用途替代方案
let/const块级作用域var
箭头函数简洁语法、词法thisfunction
解构赋值数据提取手动赋值
模板字符串字符串拼接• 连接
展开运算符数组/对象操作concat/Object.assign
Promise异步编程回调函数
Class面向对象原型继承
Module模块化CommonJS/AMD

13.2 浏览器兼容性

// Babel转译配置
// .babelrc
{
  "presets": [
    ["@babel/preset-env", {
      "targets": {
        "browsers": ["> 1%", "last 2 versions", "not ie <= 11"]
      },
      "useBuiltIns": "usage",
      "corejs": 3
    }]
  ]
}

13.3 最佳实践

  1. 优先使用const:避免意外修改
  2. 善用解构:提高代码可读性
  3. 使用async/await:替代Promise链
  4. 模块化开发:保持代码组织性
  5. 利用Map/Set:替代Object在特定场景
  6. 箭头函数注意this:理解词法绑定

记住:ES6+不是语法糖,而是编程范式的升级


参考资料

  1. ECMAScript 6 入门(阮一峰)
  2. MDN JavaScript文档
  3. You Don't Know JS (ES6 & Beyond)
  4. ECMAScript官方规范
  5. Babel官方文档

#标签: none

- THE END -

非特殊说明,本博所有文章均为博主原创。


暂无评论 >_<