JavaScript 教程

纯干货教学,从零开始学习 JavaScript

JavaScript 高级特性

JavaScript 高级特性是指那些超出基础语法的概念和技术,如闭包、原型链、异步编程、模块化等。掌握这些高级特性可以让你编写更加优雅、高效、可维护的 JavaScript 代码。

🎯 高级特性的重要性

高级特性是区分初级和高级 JavaScript 开发者的关键。掌握这些特性可以让你解决更复杂的问题,编写更优质的代码,并且更容易理解和使用现代 JavaScript 框架和库。

闭包

闭包是指函数能够访问其外部作用域中的变量,即使外部函数已经执行完毕:

// 基本闭包示例
function outerFunction() {
    let outerVariable = "外部变量";
    
    function innerFunction() {
        console.log(outerVariable);
    }
    
    return innerFunction;
}

const closure = outerFunction();
closure(); // 输出: 外部变量

// 闭包应用:计数器
function createCounter() {
    let count = 0;
    
    return {
        increment: function() {
            count++;
            return count;
        },
        decrement: function() {
            count--;
            return count;
        },
        getCount: function() {
            return count;
        }
    };
}

const counter = createCounter();
console.log(counter.increment()); // 输出: 1
console.log(counter.increment()); // 输出: 2
console.log(counter.decrement()); // 输出: 1
console.log(counter.getCount()); // 输出: 1

// 闭包应用:模块化
const module = (function() {
    // 私有变量
    let privateVariable = "私有变量";
    
    // 私有方法
    function privateMethod() {
        console.log(privateVariable);
    }
    
    // 公开API
    return {
        publicMethod: function() {
            privateMethod();
        },
        setPrivateVariable: function(value) {
            privateVariable = value;
        }
    };
})();

module.publicMethod(); // 输出: 私有变量
module.setPrivateVariable("新的私有变量");
module.publicMethod(); // 输出: 新的私有变量
// console.log(module.privateVariable); // 错误: privateVariable is not defined

原型和原型链

JavaScript 是一种基于原型的语言,每个对象都有一个原型对象,原型对象也有自己的原型,形成原型链:

// 构造函数
function Person(name, age) {
    this.name = name;
    this.age = age;
}

// 在原型上添加方法
Person.prototype.greet = function() {
    return `你好,我是 ${this.name},今年 ${this.age} 岁。`;
};

// 创建实例
const person1 = new Person("张三", 25);
const person2 = new Person("李四", 30);

console.log(person1.greet()); // 输出: 你好,我是 张三,今年 25 岁。
console.log(person2.greet()); // 输出: 你好,我是 李四,今年 30 岁。

// 检查原型
console.log(Object.getPrototypeOf(person1)); // 输出: Person.prototype
console.log(person1.__proto__); // 输出: Person.prototype
console.log(Person.prototype.isPrototypeOf(person1)); // 输出: true

// 原型链
console.log(Object.prototype.isPrototypeOf(Person.prototype)); // 输出: true
console.log(Object.prototype.isPrototypeOf(person1)); // 输出: true

// 实例属性和原型属性
function Animal(name) {
    this.name = name;
}

Animal.prototype.species = "动物";

const cat = new Animal("猫");
console.log(cat.name); // 输出: 猫(实例属性)
console.log(cat.species); // 输出: 动物(原型属性)

// 修改原型
Animal.prototype.species = "哺乳动物";
console.log(cat.species); // 输出: 哺乳动物(原型属性被修改)

// 覆盖原型属性
cat.species = "猫科动物";
console.log(cat.species); // 输出: 猫科动物(实例属性覆盖原型属性)

ES6 类

ES6 引入了类语法,提供了更简洁、更面向对象的编程方式:

// 基本类定义
class Person {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    
    // 实例方法
    greet() {
        return `你好,我是 ${this.name},今年 ${this.age} 岁。`;
    }
    
    // 静态方法
    static createPerson(name, age) {
        return new Person(name, age);
    }
    
    // getter
    get description() {
        return `${this.name} (${this.age}岁)`;
    }
    
    // setter
    set age(value) {
        if (value >= 0) {
            this._age = value;
        } else {
            throw new Error("年龄不能为负数");
        }
    }
    
    get age() {
        return this._age;
    }
}

// 创建实例
const person1 = new Person("张三", 25);
console.log(person1.greet()); // 输出: 你好,我是 张三,今年 25 岁。
console.log(person1.description); // 输出: 张三 (25岁)

// 使用静态方法
const person2 = Person.createPerson("李四", 30);
console.log(person2.greet()); // 输出: 你好,我是 李四,今年 30 岁。

// 继承
class Employee extends Person {
    constructor(name, age, job, salary) {
        // 调用父类构造函数
        super(name, age);
        this.job = job;
        this.salary = salary;
    }
    
    // 重写父类方法
    greet() {
        return `${super.greet()} 我的工作是 ${this.job},薪资是 ${this.salary}。`;
    }
    
    // 新方法
    work() {
        return `${this.name} 正在工作。`;
    }
}

const employee = new Employee("王五", 35, "工程师", 10000);
console.log(employee.greet()); // 输出: 你好,我是 王五,今年 35 岁。 我的工作是 工程师,薪资是 10000。
console.log(employee.work()); // 输出: 王五 正在工作。
console.log(employee.description); // 输出: 王五 (35岁)(继承自父类的 getter)

异步编程

JavaScript 是单线程语言,但通过异步编程可以处理耗时操作:

1. 回调函数

// 回调函数示例
function fetchData(url, callback) {
    setTimeout(function() {
        console.log(`从 ${url} 获取数据`);
        callback({ data: "模拟数据" });
    }, 1000);
}

// 调用
fetchData("https://api.example.com/data", function(data) {
    console.log("获取到数据:", data);
});

// 回调地狱
function step1(callback) {
    setTimeout(function() {
        console.log("步骤 1 完成");
        callback();
    }, 1000);
}

function step2(callback) {
    setTimeout(function() {
        console.log("步骤 2 完成");
        callback();
    }, 1000);
}

function step3(callback) {
    setTimeout(function() {
        console.log("步骤 3 完成");
        callback();
    }, 1000);
}

// 回调地狱
step1(function() {
    step2(function() {
        step3(function() {
            console.log("所有步骤完成");
        });
    });
});

2. Promise

// 基本 Promise
const promise = new Promise(function(resolve, reject) {
    setTimeout(function() {
        const success = true;
        if (success) {
            resolve("操作成功");
        } else {
            reject("操作失败");
        }
    }, 1000);
});

// 处理 Promise
promise
    .then(function(result) {
        console.log(result); // 输出: 操作成功
    })
    .catch(function(error) {
        console.log(error); // 输出: 操作失败
    });

// Promise 链式调用
function step1() {
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("步骤 1 完成");
            resolve("步骤 1 结果");
        }, 1000);
    });
}

function step2(data) {
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("步骤 2 完成,使用数据:", data);
            resolve("步骤 2 结果");
        }, 1000);
    });
}

function step3(data) {
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("步骤 3 完成,使用数据:", data);
            resolve("步骤 3 结果");
        }, 1000);
    });
}

// 链式调用
step1()
    .then(step2)
    .then(step3)
    .then(function(result) {
        console.log("所有步骤完成,最终结果:", result);
    });

// Promise 静态方法
Promise.resolve("成功值")
    .then(function(value) {
        console.log(value); // 输出: 成功值
    });

Promise.reject("错误值")
    .catch(function(error) {
        console.log(error); // 输出: 错误值
    });

// Promise.all:等待所有 Promise 完成
const promise1 = Promise.resolve(1);
const promise2 = Promise.resolve(2);
const promise3 = Promise.resolve(3);

Promise.all([promise1, promise2, promise3])
    .then(function(values) {
        console.log(values); // 输出: [1, 2, 3]
    });

// Promise.race:等待第一个 Promise 完成
const promiseA = new Promise(resolve => setTimeout(() => resolve("A"), 300));
const promiseB = new Promise(resolve => setTimeout(() => resolve("B"), 100));

Promise.race([promiseA, promiseB])
    .then(function(value) {
        console.log(value); // 输出: B
    });

3. async/await

// 基本 async/await
async function fetchData() {
    return new Promise(function(resolve) {
        setTimeout(function() {
            resolve("数据获取成功");
        }, 1000);
    });
}

async function processData() {
    console.log("开始获取数据");
    const data = await fetchData();
    console.log(data); // 输出: 数据获取成功
    console.log("数据处理完成");
}

processData();

// async/await 与 Promise 结合
function step1() {
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("步骤 1 完成");
            resolve("步骤 1 结果");
        }, 1000);
    });
}

function step2(data) {
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("步骤 2 完成,使用数据:", data);
            resolve("步骤 2 结果");
        }, 1000);
    });
}

function step3(data) {
    return new Promise(function(resolve) {
        setTimeout(function() {
            console.log("步骤 3 完成,使用数据:", data);
            resolve("步骤 3 结果");
        }, 1000);
    });
}

async function runSteps() {
    try {
        console.log("开始执行步骤");
        const result1 = await step1();
        const result2 = await step2(result1);
        const result3 = await step3(result2);
        console.log("所有步骤完成,最终结果:", result3);
    } catch (error) {
        console.error("执行出错:", error);
    }
}

runSteps();

// 并行执行
async function runParallel() {
    console.log("开始并行执行");
    
    // 同时开始所有步骤
    const promise1 = step1();
    const promise2 = step2("初始数据");
    const promise3 = step3("初始数据");
    
    // 等待所有步骤完成
    const results = await Promise.all([promise1, promise2, promise3]);
    console.log("所有步骤完成,结果:", results);
}

runParallel();

模块化

模块化是将代码分割成独立、可重用的模块的过程:

1. 立即执行函数表达式 (IIFE)

// IIFE 模块化
const Module = (function() {
    // 私有变量
    let privateVar = "私有变量";
    
    // 私有方法
    function privateMethod() {
        return privateVar;
    }
    
    // 公开 API
    return {
        publicVar: "公开变量",
        publicMethod: function() {
            return privateMethod();
        },
        setPrivateVar: function(value) {
            privateVar = value;
        }
    };
})();

console.log(Module.publicVar); // 输出: 公开变量
console.log(Module.publicMethod()); // 输出: 私有变量
Module.setPrivateVar("新的私有变量");
console.log(Module.publicMethod()); // 输出: 新的私有变量
// console.log(Module.privateVar); // 错误: Module.privateVar is undefined
// console.log(Module.privateMethod); // 错误: Module.privateMethod is not a function

2. CommonJS 模块(Node.js)

// 模块文件: math.js
/*
// 导出单个函数
function add(a, b) {
    return a + b;
}

function subtract(a, b) {
    return a - b;
}

// 导出多个函数
module.exports = {
    add,
    subtract
};

// 或者导出单个函数
// module.exports = add;
*/

// 使用模块
/*
// 导入整个模块
const math = require('./math');
console.log(math.add(5, 3)); // 输出: 8
console.log(math.subtract(10, 4)); // 输出: 6

// 导入特定函数
const { add, subtract } = require('./math');
console.log(add(5, 3)); // 输出: 8
console.log(subtract(10, 4)); // 输出: 6
*/

3. ES6 模块

// 模块文件: math.js
/*
// 导出单个函数
export function add(a, b) {
    return a + b;
}

export function subtract(a, b) {
    return a - b;
}

// 导出默认值
const pi = 3.14159;
export default pi;
*/

// 使用模块
/*
// 导入特定函数
import { add, subtract } from './math.js';
console.log(add(5, 3)); // 输出: 8
console.log(subtract(10, 4)); // 输出: 6

// 导入默认值
import pi from './math.js';
console.log(pi); // 输出: 3.14159

// 导入所有
import * as math from './math.js';
console.log(math.add(5, 3)); // 输出: 8
console.log(math.default); // 输出: 3.14159
*/

// 浏览器中使用 ES6 模块
/*
<script type="module">
    import { add } from './math.js';
    console.log(add(5, 3)); // 输出: 8
</script>
*/

解构赋值

解构赋值允许我们从对象或数组中提取值并赋给变量:

// 数组解构
const numbers = [1, 2, 3, 4, 5];

// 基本解构
const [a, b, c] = numbers;
console.log(a, b, c); // 输出: 1 2 3

// 跳过元素
const [x, , y] = numbers;
console.log(x, y); // 输出: 1 3

// 剩余元素
const [first, ...rest] = numbers;
console.log(first); // 输出: 1
console.log(rest); // 输出: [2, 3, 4, 5]

// 默认值
const [p, q, r, s, t, u = 6] = numbers;
console.log(u); // 输出: 6

// 对象解构
const person = {
    name: "张三",
    age: 25,
    job: "工程师",
    address: {
        city: "北京",
        district: "朝阳区"
    }
};

// 基本解构
const { name, age } = person;
console.log(name, age); // 输出: 张三 25

// 重命名变量
const { job: occupation } = person;
console.log(occupation); // 输出: 工程师

// 嵌套解构
const { address: { city, district } } = person;
console.log(city, district); // 输出: 北京 朝阳区

// 默认值
const { name: userName, salary = 10000 } = person;
console.log(userName, salary); // 输出: 张三 10000

// 函数参数解构
function greet({ name, age }) {
    console.log(`你好,我是 ${name},今年 ${age} 岁。`);
}

greet(person); // 输出: 你好,我是 张三,今年 25 岁。

function calculate({ a, b, operation = "add" }) {
    switch (operation) {
        case "add":
            return a + b;
        case "subtract":
            return a - b;
        case "multiply":
            return a * b;
        case "divide":
            return a / b;
        default:
            return "无效操作";
    }
}

console.log(calculate({ a: 10, b: 5 })); // 输出: 15
console.log(calculate({ a: 10, b: 5, operation: "multiply" })); // 输出: 50

展开运算符

展开运算符允许我们将数组或对象展开为多个元素:

// 数组展开
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];

// 合并数组
const mergedArray = [...arr1, ...arr2];
console.log(mergedArray); // 输出: [1, 2, 3, 4, 5, 6]

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

// 函数参数展开
function sum(a, b, c) {
    return a + b + c;
}

const numbers = [1, 2, 3];
console.log(sum(...numbers)); // 输出: 6

// 对象展开
const obj1 = { a: 1, b: 2 };
const obj2 = { b: 3, c: 4 };

// 合并对象
const mergedObj = { ...obj1, ...obj2 };
console.log(mergedObj); // 输出: { a: 1, b: 3, c: 4 }

// 复制对象
const copyObj = { ...obj1 };
console.log(copyObj); // 输出: { a: 1, b: 2 }

// 扩展对象
const person = {
    name: "张三",
    age: 25
};

const extendedPerson = {
    ...person,
    job: "工程师",
    address: "北京"
};

console.log(extendedPerson); // 输出: { name: "张三", age: 25, job: "工程师", address: "北京" }

// 与解构结合
const { a, ...rest } = { a: 1, b: 2, c: 3, d: 4 };
console.log(a); // 输出: 1
console.log(rest); // 输出: { b: 2, c: 3, d: 4 }

生成器

生成器是一种特殊的函数,可以暂停执行并在需要时恢复:

// 基本生成器
function* generatorFunction() {
    yield 1;
    yield 2;
    yield 3;
}

const generator = generatorFunction();

console.log(generator.next()); // 输出: { value: 1, done: false }
console.log(generator.next()); // 输出: { value: 2, done: false }
console.log(generator.next()); // 输出: { value: 3, done: false }
console.log(generator.next()); // 输出: { value: undefined, done: true }

// 使用 for...of 遍历
function* generatorFunction2() {
    yield "苹果";
    yield "香蕉";
    yield "橙子";
}

for (const fruit of generatorFunction2()) {
    console.log(fruit);
}
// 输出:
// 苹果
// 香蕉
// 橙子

// 无限序列
function* infiniteSequence() {
    let i = 0;
    while (true) {
        yield i++;
    }
}

const infinite = infiniteSequence();
console.log(infinite.next().value); // 输出: 0
console.log(infinite.next().value); // 输出: 1
console.log(infinite.next().value); // 输出: 2

// 带参数的生成器
function* generatorWithParams() {
    const param1 = yield "请输入第一个参数";
    console.log("第一个参数:", param1);
    
    const param2 = yield "请输入第二个参数";
    console.log("第二个参数:", param2);
    
    yield param1 + param2;
}

const gen = generatorWithParams();
console.log(gen.next()); // 输出: { value: "请输入第一个参数", done: false }
console.log(gen.next(5)); // 输出: { value: "请输入第二个参数", done: false }
console.log(gen.next(3)); // 输出: { value: 8, done: false }
console.log(gen.next()); // 输出: { value: undefined, done: true }

// 错误处理
function* generatorWithError() {
    try {
        yield 1;
        yield 2;
        throw new Error("生成器错误");
        yield 3;
    } catch (error) {
        console.log("捕获到错误:", error.message);
        yield "错误已处理";
    }
    yield 4;
}

const genWithError = generatorWithError();
console.log(genWithError.next()); // 输出: { value: 1, done: false }
console.log(genWithError.next()); // 输出: { value: 2, done: false }
console.log(genWithError.next()); // 输出: 捕获到错误: 生成器错误, { value: "错误已处理", done: false }
console.log(genWithError.next()); // 输出: { value: 4, done: false }
console.log(genWithError.next()); // 输出: { value: undefined, done: true }

Symbol

Symbol 是一种新的原始数据类型,用于创建唯一的标识符:

// 创建 Symbol
const symbol1 = Symbol();
const symbol2 = Symbol("描述");
const symbol3 = Symbol("描述");

console.log(symbol1); // 输出: Symbol()
console.log(symbol2); // 输出: Symbol(描述)
console.log(symbol2 === symbol3); // 输出: false(即使描述相同,Symbol 也是唯一的)

// 作为对象属性
const id = Symbol("id");
const person = {
    [id]: 123,
    name: "张三"
};

console.log(person[id]); // 输出: 123
console.log(Object.keys(person)); // 输出: ["name"](Symbol 属性不会出现在 Object.keys 中)
console.log(Object.getOwnPropertySymbols(person)); // 输出: [Symbol(id)]

// 内置 Symbol
const obj = {
    [Symbol.toStringTag]: "自定义对象"
};

console.log(obj.toString()); // 输出: [object 自定义对象]

// Symbol.iterator
const iterableObj = {
    items: [1, 2, 3],
    [Symbol.iterator]: function* () {
        for (const item of this.items) {
            yield item;
        }
    }
};

for (const item of iterableObj) {
    console.log(item);
}
// 输出:
// 1
// 2
// 3

Proxy 和 Reflect

Proxy 用于创建对象的代理,Reflect 提供了操作对象的方法:

// 基本 Proxy
const target = {
    name: "张三",
    age: 25
};

const proxy = new Proxy(target, {
    // 拦截属性访问
    get: function(target, property) {
        console.log(`访问属性: ${property}`);
        return target[property];
    },
    // 拦截属性设置
    set: function(target, property, value) {
        console.log(`设置属性: ${property} = ${value}`);
        target[property] = value;
        return true;
    },
    // 拦截属性删除
    deleteProperty: function(target, property) {
        console.log(`删除属性: ${property}`);
        delete target[property];
        return true;
    },
    // 拦截 in 操作符
    has: function(target, property) {
        console.log(`检查属性: ${property}`);
        return property in target;
    }
});

// 使用代理
console.log(proxy.name); // 输出: 访问属性: name, 张三
proxy.age = 26; // 输出: 设置属性: age = 26
console.log("age" in proxy); // 输出: 检查属性: age, true
delete proxy.age; // 输出: 删除属性: age
console.log(proxy.age); // 输出: 访问属性: age, undefined

// Reflect
const obj = {
    name: "李四",
    age: 30
};

// 使用 Reflect 访问属性
console.log(Reflect.get(obj, "name")); // 输出: 李四

// 使用 Reflect 设置属性
Reflect.set(obj, "age", 31);
console.log(obj.age); // 输出: 31

// 使用 Reflect 删除属性
Reflect.deleteProperty(obj, "age");
console.log(obj.age); // 输出: undefined

// 使用 Reflect 检查属性
console.log(Reflect.has(obj, "name")); // 输出: true
console.log(Reflect.has(obj, "age")); // 输出: false

// 使用 Reflect 构造函数
function Person(name, age) {
    this.name = name;
    this.age = age;
}

const person = Reflect.construct(Person, ["王五", 35]);
console.log(person); // 输出: Person { name: "王五", age: 35 }

高级特性最佳实践

  • 合理使用闭包:闭包可以创建私有变量和模块化代码,但过度使用可能导致内存泄漏
  • 理解原型链:了解原型链的工作原理,避免修改内置对象的原型
  • 使用 ES6+ 语法:优先使用 ES6+ 的新特性,如类、箭头函数、解构赋值等
  • 掌握异步编程:理解回调、Promise 和 async/await 的使用场景和区别
  • 模块化代码:使用 ES6 模块或 CommonJS 模块组织代码,提高可维护性
  • 使用展开运算符:展开运算符可以简化数组和对象的操作
  • 合理使用生成器:生成器适合处理异步操作和迭代器场景
  • 使用 Symbol:Symbol 适合创建唯一标识符和对象的私有属性
  • 使用 Proxy:Proxy 适合实现对象的拦截和自定义行为
  • 编写清晰的代码:即使使用高级特性,也要保持代码的可读性和可维护性

高级特性常见问题

  • 问题 1: 闭包内存泄漏
  • 解决方案:避免在循环中创建闭包,及时释放不再需要的引用,特别是在长生命周期的对象中。

  • 问题 2: 原型链污染
  • 解决方案:不要修改内置对象的原型,使用 Object.create() 创建干净的对象。

  • 问题 3: 异步编程错误处理
  • 解决方案:使用 try-catch 处理 async/await 错误,使用 Promise.catch() 处理 Promise 错误。

  • 问题 4: 模块化兼容性
  • 解决方案:了解不同模块化系统的区别,使用打包工具如 Webpack 处理兼容性问题。

  • 问题 5: 性能问题
  • 解决方案:避免过度使用高级特性,如 Proxy 在性能敏感场景可能不是最佳选择。

📝 学习检查

通过本章节的学习,你应该了解:

  • 闭包的概念和应用
  • 原型和原型链的工作原理
  • ES6 类的使用
  • 异步编程(回调、Promise、async/await)
  • 模块化的不同方式
  • 解构赋值和展开运算符
  • 生成器的使用
  • Symbol 的用途
  • Proxy 和 Reflect 的应用
  • 高级特性的最佳实践和常见问题