JavaScript 从入门到精通

6 阅读17分钟

《JavaScript 从入门到精通完全手册》

JavaScript 从入门到精通完全手册

📘 本文档是一份全面的 JavaScript 学习指南,涵盖从基础语法到高级概念的完整知识体系,适合初学者系统性学习,也适合有经验的开发者作为参考手册。


目录

  1. JavaScript 简介

  2. 环境搭建与第一个程序

  3. 基础语法

  4. 数据类型

  5. 运算符

  6. 控制流

  7. 函数

  8. 对象

  9. 数组

  10. 字符串处理

  11. DOM 操作

  12. 事件处理

  13. 作用域与闭包

  14. 原型与继承

  15. 类(ES6+)

  16. 异步编程

  17. 错误处理

  18. 模块化

  19. ES6+ 常用新特性

  20. Web API 精选

  21. 设计模式

  22. 性能优化

  23. 最佳实践

  24. 测试入门

  25. 现代工具链


1. JavaScript 简介

1.1 什么是 JavaScript

JavaScript 是一种轻量级、解释型、基于原型的脚本语言,最初由 Brendan Eich 于 1995 年为 Netscape 浏览器创建。它是 Web 开发的三层结构之一:

技术作用
结构层HTML定义页面内容和结构
表现层CSS定义页面样式和布局
行为层JavaScript定义页面交互和动态行为

1.2 发展简史

  • 1995 — Brendan Eich 用 10 天创建了 Mocha(后改名 LiveScript,最终定为 JavaScript)

  • 1997 — ECMAScript 1(ES1)标准化

  • 1999 — ES3 发布,奠定现代 JS 基础

  • 2009 — ES5 发布,新增严格模式、JSON、数组方法等

  • 2015 — ES6(ES2015)重大更新,引入 let/const、箭头函数、类、模块等

  • 2016 至今 — 每年发布新版本,持续演进

1.3 JavaScript 能做什么

  • 🖥️ 前端开发 — DOM 操作、交互逻辑、SPA 应用

  • 🔧 后端开发 — Node.js 构建服务器、API

  • 📱 移动开发 — DCloud React Native、Flutter

  • 🎮 游戏开发 — Canvas、WebGL、游戏引擎

  • 🤖 桌面应用 — Electron

  • 📊 数据可视化 — D3.js、ECharts

  • 🤖 机器学习 — TensorFlow.js


2. 环境搭建与第一个程序

2.1 浏览器控制台(最快上手)

打开 Chrome/Firefox → 按 F12 → 点击 Console 标签:

console.log('Hello, JavaScript!');

2.2 HTML 中嵌入 JavaScript

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>我的第一个 JS 程序</title>
</head>
<body>
    <h1>JavaScript 入门</h1>
    
    <!-- 内嵌式 -->
    <script>
        alert('欢迎来到 JavaScript 的世界!');
    </script>
    
    <!-- 外部引入(推荐) -->
    <script src="app.js"></script>
</body>
</html>

⚠️ 注意:\&lt;script\&gt; 标签放在 \&lt;body\&gt; 底部可以避免阻塞页面渲染。现代也可使用 defer 或 async 属性。

2.3 Node.js 环境

# 安装 Node.js 后,创建 app.js
node app.js
// app.js
console.log('在 Node.js 中运行 JavaScript');

3. 基础语法

3.1 变量声明

// ES5 - var(函数作用域,可重复声明,存在变量提升)
var name = '张三';

// ES6 - let(块级作用域,不可重复声明)
let age = 25;

// ES6 - const(块级作用域,声明常量,不可重新赋值)
const PI = 3.14159;

// const 对于引用类型,内容可修改
const arr = [1, 2, 3];
arr.push(4);        // ✅ 允许
// arr = [5, 6];    // ❌ 报错

三种声明方式对比:

特性varletconst
作用域函数作用域块级作用域块级作用域
变量提升❌(暂时性死区)❌(暂时性死区)
可重复声明
必须初始化
可重新赋值

3.2 命名规范

// ✅ 推荐:驼峰命名法
let userName = 'Alice';
let totalPrice = 99.99;
const MAX_COUNT = 100;     // 常量用大写+下划线

// ✅ 允许:字母、数字、下划线、美元符号
let $element = document.querySelector('.box');
let _private = '内部变量';
let age2 = 30;

// ❌ 不允许:数字开头、连字符、关键字
// let 2name = '错误';
// let class = '错误';

3.3 注释

// 这是单行注释

/*
 * 这是多行注释
 * 可以跨越多行
 */

/**
 * 文档注释(JSDoc 风格)
 * @param {string} name - 用户名称
 * @returns {string} 问候语
 */
function greet(name) {
    return `你好,${name}!`;
}

3.4 分号

JavaScript 有自动分号插入(ASI)机制,但推荐手动添加分号以避免意外问题。

// 推荐
let x = 5;
console.log(x);

// 某些情况不写分号可能出问题
let a = 1
let b = 2
// 等价于 let a = 1; let b = 2; —— 这里没问题

// 但以 [ ( 开头的行可能被合并解析
let c = a + b
[1, 2, 3].forEach(n => console.log(n))  // 可能报错

4. 数据类型

4.1 基本数据类型(7 种)

// 1. Number — 数字(整数和浮点)
let num = 42;
let price = 19.99;
let infinity = Infinity;
let notANumber = NaN;          // 非数值

// 2. String — 字符串
let single = '单引号';
let double = "双引号";
let backtick = `模板字符串 ${num}`;  // ES6

// 3. Boolean — 布尔值
let isReady = true;
let isOff = false;

// 4. Undefined — 未定义
let notDefined;
console.log(notDefined);       // undefined

// 5. Null — 空值
let empty = null;

// 6. Symbol — 符号(ES6,唯一标识)
let sym1 = Symbol('id');
let sym2 = Symbol('id');
console.log(sym1 === sym2);    // false

// 7. BigInt — 大整数(ES2020)
let bigNum = 9007199254740991n;
let anotherBig = BigInt('9007199254740991');

4.2 类型检测

// typeof 运算符
console.log(typeof 42);           // 'number'
console.log(typeof 'hello');      // 'string'
console.log(typeof true);         // 'boolean'
console.log(typeof undefined);    // 'undefined'
console.log(typeof null);         // 'object' ⚠️(历史遗留 bug)
console.log(typeof Symbol());     // 'symbol'
console.log(typeof 123n);         // 'bigint'
console.log(typeof function(){}); // 'function'

// instanceof 检测引用类型
console.log([] instanceof Array);   // true
console.log({} instanceof Object);  // true

// 更准确的类型检测
console.log(Object.prototype.toString.call([]));    // '[object Array]'
console.log(Object.prototype.toString.call(null));  // '[object Null]'

4.3 类型转换

// --- 转换为字符串 ---
String(123);           // '123'
(123).toString();      // '123'
123 + '';              // '123'(隐式)

// --- 转换为数字 ---
Number('123');         // 123
parseInt('123px');     // 123
parseFloat('12.34');   // 12.34
+'123';                // 123(隐式)

// --- 转换为布尔 ---
Boolean(1);            // true
Boolean(0);            // false
Boolean('');           // false
Boolean('hello');      // true
!!'hello';             // true(双感叹号技巧)

// --- 假值(Falsy Values)---
false, 0, -0, 0n, '', null, undefined, NaN
// 其他所有值都是真值(Truthy)

4.4 相等性比较

// == 宽松相等(会进行类型转换)
console.log(5 == '5');          // true
console.log(null == undefined); // true
console.log(0 == false);        // true

// === 严格相等(类型+值都必须相等)—— 推荐使用
console.log(5 === '5');         // false
console.log(null === undefined);// false
console.log(0 === false);       // false

// Object.is() — 更精确的相等判断
console.log(Object.is(NaN, NaN));     // true(=== 下为 false)
console.log(Object.is(0, -0));        // false(=== 下为 true)

5. 运算符

5.1 算术运算符

let a = 10, b = 3;

console.log(a + b);    // 13   加法
console.log(a - b);    // 7    减法
console.log(a * b);    // 30   乘法
console.log(a / b);    // 3.333...  除法
console.log(a % b);    // 1    取余(模运算)
console.log(a ** b);   // 1000 指数(ES7)
console.log(a++);      // 10   后置自增
console.log(++a);      // 12   前置自增

5.2 赋值运算符

let x = 5;
x += 3;   // x = x + 3 → 8
x -= 2;   // x = x - 2 → 6
x *= 3;   // x = x * 3 → 18
x /= 2;   // x = x / 2 → 9
x %= 4;   // x = x % 4 → 1
x **= 2;  // x = x ** 2 → 1

5.3 比较运算符

console.log(5 > 3);     // true
console.log(5 >= 5);    // true
console.log(3 < 5);     // true
console.log(3 <= 2);    // false
console.log(5 === '5'); // false
console.log(5 !== '5'); // true

5.4 逻辑运算符

// && — 逻辑与
console.log(true && true);     // true
console.log(true && false);    // false

// || — 逻辑或
console.log(false || true);    // true
console.log(false || false);   // false

// ! — 逻辑非
console.log(!true);            // false

// 短路求值
let name = '';
let displayName = name || '匿名用户';  // '匿名用户'
let result = true && '返回这个';       // '返回这个'

// 可选链操作符(ES2020)
let user = null;
console.log(user?.address?.city);  // undefined(不会报错)

5.5 三元运算符

let age = 18;
let status = age >= 18 ? '成人' : '未成年';

// 可嵌套(但不宜过深)
let grade = 85;
let level = grade >= 90 ? 'A' : grade >= 80 ? 'B' : grade >= 60 ? 'C' : 'D';

5.6 运算符优先级

记不住?使用括号明确优先级,提高可读性。

// 不用括号
let result = 2 + 3 * 4;  // 14,不是 20

// 使用括号
let clear = (2 + 3) * 4; // 20,意图明确

6. 控制流

6.1 if-else

let score = 85;

if (score >= 90) {
    console.log('优秀');
} else if (score >= 80) {
    console.log('良好');
} else if (score >= 60) {
    console.log('及格');
} else {
    console.log('不及格');
}

6.2 switch

let day = 3;

switch (day) {
    case 1:
        console.log('周一');
        break;
    case 2:
        console.log('周二');
        break;
    case 3:
        console.log('周三');
        break;    // 不要忘记 break
    default:
        console.log('其他');
}

// switch 使用严格相等(===)

6.3 循环

// --- for 循环 ---
for (let i = 0; i < 5; i++) {
    console.log(i);  // 0, 1, 2, 3, 4
}

// --- while 循环 ---
let count = 0;
while (count < 5) {
    console.log(count);
    count++;
}

// --- do-while 循环(至少执行一次)---
let num = 0;
do {
    console.log(num);
    num++;
} while (num < 0);  // 条件为假也会执行一次

// --- for...of 遍历可迭代对象 ---
const fruits = ['苹果', '香蕉', '橙子'];
for (const fruit of fruits) {
    console.log(fruit);
}

// --- for...in 遍历对象属性 ---
const person = { name: '张三', age: 30, city: '北京' };
for (const key in person) {
    console.log(`${key}: ${person[key]}`);
}

6.4 跳转语句

// break — 终止循环
for (let i = 0; i < 10; i++) {
    if (i === 5) break;
    console.log(i);  // 0, 1, 2, 3, 4
}

// continue — 跳过本次迭代
for (let i = 0; i < 5; i++) {
    if (i === 2) continue;
    console.log(i);  // 0, 1, 3, 4
}

// 标签语句(用于跳出嵌套循环)
outerLoop: for (let i = 0; i < 3; i++) {
    for (let j = 0; j < 3; j++) {
        if (i === 1 && j === 1) break outerLoop;
        console.log(`${i},${j}`);
    }
}

7. 函数

7.1 函数声明

// 函数声明(存在提升)
function greet(name) {
    return `你好,${name}!`;
}

// 函数表达式(不存在提升)
const sayBye = function(name) {
    return `再见,${name}!`;
};

// 箭头函数(ES6)
const multiply = (a, b) => a * b;
const square = x => x * x;
const sayHello = () => console.log('Hello');

// 箭头函数的多行写法
const process = (data) => {
    const result = data.trim().toLowerCase();
    return result;
};

7.2 参数处理

// 默认参数(ES6)
function greet(name = '访客', greeting = '你好') {
    return `${greeting}${name}!`;
}
console.log(greet());            // '你好,访客!'
console.log(greet('张三', 'Hi')); // 'Hi,张三!'

// 剩余参数(ES6)
function sum(...numbers) {
    return numbers.reduce((total, n) => total + n, 0);
}
console.log(sum(1, 2, 3, 4));    // 10

// arguments 对象(旧方式,类数组)
function oldStyle() {
    console.log(arguments[0]);   // 第一个参数
    console.log(arguments.length); // 参数个数
}

7.3 箭头函数 vs 普通函数

特性普通函数箭头函数
this 绑定动态绑定(调用时确定)词法绑定(定义时确定)
arguments
构造函数✅(可用 new)
prototype
方法中用作回调需手动绑定 this自动捕获外层 this
// this 绑定的关键区别
const obj = {
    name: '测试',
    normalFunc: function() {
        console.log(this.name);  // '测试'(this 指向 obj)
    },
    arrowFunc: () => {
        console.log(this.name);  // undefined(this 指向外层作用域)
    }
};

7.4 立即执行函数(IIFE)

// 经典写法
(function() {
    const privateVar = '我是私有的';
    console.log('立即执行');
})();

// 箭头函数版本
(() => {
    console.log('箭头函数 IIFE');
})();

7.5 函数是&#34;一等公民&#34;

// 函数可以赋值给变量
const fn = function() { return 'hello'; };

// 函数可以作为参数(回调)
function execute(callback) {
    callback();
}
execute(() => console.log('回调执行'));

// 函数可以作为返回值(高阶函数)
function createMultiplier(factor) {
    return function(number) {
        return number * factor;
    };
}
const double = createMultiplier(2);
console.log(double(5));  // 10

7.6 call、apply、bind

function introduce(greeting, punctuation) {
    console.log(`${greeting},我是${this.name}${punctuation}`);
}

const person = { name: '张三' };

// call — 逐个传参
introduce.call(person, '你好', '!');

// apply — 数组传参
introduce.apply(person, ['嗨', '~']);

// bind — 返回新函数(不立即执行)
const boundFunc = introduce.bind(person, 'Hello');
boundFunc('!');  // 'Hello,我是张三!'

8. 对象

8.1 对象创建

// 对象字面量
const person = {
    name: '张三',
    age: 30,
    greet() {                    // 方法简写(ES6)
        return `我是${this.name}`;
    }
};

// new Object()
const obj = new Object();
obj.key = 'value';

// Object.create()
const proto = { shared: '共享属性' };
const child = Object.create(proto);
console.log(child.shared);       // '共享属性'

8.2 属性操作

const user = { name: 'Alice', age: 25 };

// 访问属性
console.log(user.name);          // 点号访问
console.log(user['age']);        // 方括号访问(动态属性名)

// 计算属性名(ES6)
const key = 'favoriteColor';
const data = {
    [key]: '蓝色',
    [`${key}_dark`]: '深蓝'
};

// 添加属性
user.email = 'alice@example.com';

// 删除属性
delete user.age;

// 检查属性是否存在
console.log('name' in user);            // true
console.log(user.hasOwnProperty('name')); // true

8.3 对象遍历

const obj = { a: 1, b: 2, c: 3 };

// for...in(遍历所有可枚举属性,包括原型链)
for (const key in obj) {
    if (obj.hasOwnProperty(key)) {  // 过滤原型属性
        console.log(key, obj[key]);
    }
}

// Object.keys() — 返回自身可枚举属性名数组
console.log(Object.keys(obj));     // ['a', 'b', 'c']

// Object.values() — 返回自身可枚举属性值数组
console.log(Object.values(obj));   // [1, 2, 3]

// Object.entries() — 返回键值对数组
console.log(Object.entries(obj));  // [['a',1], ['b',2], ['c',3]]

8.4 对象方法

const target = { a: 1 };
const source = { b: 2, c: 3 };

// Object.assign() — 浅拷贝/合并
const merged = Object.assign({}, target, source);
console.log(merged);  // { a: 1, b: 2, c: 3 }

// 展开运算符(ES2018)
const merged2 = { ...target, ...source };

// Object.freeze() — 冻结对象(不可修改)
const frozen = Object.freeze({ x: 10 });
// frozen.x = 20;  // 静默失败或报错(严格模式)

// Object.seal() — 密封对象(不可添加/删除,但可修改现有属性)
const sealed = Object.seal({ y: 20 });

8.5 解构赋值

const user = { name: '张三', age: 30, city: '北京' };

// 基本解构
const { name, age } = user;
console.log(name, age);  // '张三', 30

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

// 默认值
const { country = '中国' } = user;

// 嵌套解构
const response = {
    data: {
        user: { id: 1, profile: { nickname: '小王' } }
    }
};
const { data: { user: { profile: { nickname } } } } = response;
console.log(nickname);  // '小王'

// 函数参数解构
function display({ name, age }) {
    console.log(`${name}${age}岁`);
}
display(user);

9. 数组

9.1 数组创建

// 字面量
const arr1 = [1, 2, 3];

// Array 构造函数
const arr2 = new Array(3);     // 长度为 3 的空数组
const arr3 = Array.of(3);      // [3](ES6)
const arr4 = Array.from('hi'); // ['h', 'i'](ES6)

// 生成有初始值的数组
const filled = Array(5).fill(0);  // [0, 0, 0, 0, 0]

9.2 常用方法 — 增删改查

const arr = [1, 2, 3];

// 尾部操作
arr.push(4);        // 尾部添加 → [1,2,3,4]
arr.pop();          // 尾部删除 → [1,2,3]

// 头部操作
arr.unshift(0);     // 头部添加 → [0,1,2,3]
arr.shift();        // 头部删除 → [1,2,3]

// 任意位置
arr.splice(1, 1, 'a', 'b');  // 从索引1删除1个,插入'a','b'
// splice(起始索引, 删除个数, ...插入项)

// 切片(不改变原数组)
const sliced = arr.slice(0, 2);  // 从索引0到索引2(不含)

// 查找
arr.indexOf(3);       // 索引位置,不存在返回 -1
arr.includes(3);      // true/false(ES7)
arr.find(n => n > 1); // 第一个匹配的元素
arr.findIndex(n => n > 1); // 第一个匹配的索引

9.3 迭代方法

const numbers = [1, 2, 3, 4, 5];

// forEach — 遍历执行(无返回值)
numbers.forEach((item, index) => {
    console.log(`${index}: ${item}`);
});

// map — 映射转换为新数组
const doubled = numbers.map(n => n * 2);  // [2, 4, 6, 8, 10]

// filter — 过滤
const evens = numbers.filter(n => n % 2 === 0);  // [2, 4]

// reduce — 归约累积
const sum = numbers.reduce((acc, cur) => acc + cur, 0);  // 15

// some — 是否有元素满足条件
console.log(numbers.some(n => n > 4));  // true

// every — 是否所有元素满足条件
console.log(numbers.every(n => n > 0)); // true

// flatMap — 映射并展平(ES2019)
const arr = [1, 2, 3];
const result = arr.flatMap(n => [n, n * 2]);
console.log(result);  // [1, 2, 2, 4, 3, 6]

9.4 排序与反转

const fruits = ['香蕉', '苹果', '橙子', '葡萄'];

// sort — 默认按字符串排序
fruits.sort();
console.log(fruits);  // ['橙子', '苹果', '葡萄', '香蕉'](按拼音)

// 自定义排序
const nums = [10, 5, 40, 25, 1];
nums.sort((a, b) => a - b);  // 升序: [1, 5, 10, 25, 40]
nums.sort((a, b) => b - a);  // 降序: [40, 25, 10, 5, 1]

// reverse — 反转
nums.reverse();

// 注意:sort 和 reverse 都会修改原数组
// 先复制再排序
const sorted = [...nums].sort((a, b) => a - b);

9.5 数组展开与合并

const arr1 = [1, 2];
const arr2 = [3, 4];

// 展开运算符
const combined = [...arr1, ...arr2, 5];  // [1, 2, 3, 4, 5]

// concat
const merged = arr1.concat(arr2);

// 多维数组展平
const nested = [1, [2, [3, 4]]];
console.log(nested.flat());      // [1, 2, [3, 4]] — 默认展平一层
console.log(nested.flat(2));     // [1, 2, 3, 4] — 展平两层
console.log(nested.flat(Infinity)); // [1, 2, 3, 4] — 完全展平

10. 字符串处理

10.1 模板字符串(ES6)

const name = '世界';
const greeting = `你好,${name}!`;  // 使用反引号

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

// 表达式嵌入
const price = 100;
const tax = 0.13;
const total = `总价:¥${(price * (1 + tax)).toFixed(2)}`;

// 标签模板
function highlight(strings, ...values) {
    return strings.reduce((result, str, i) => 
        `${result}${str}<em>${values[i] || ''}</em>`, '');
}
const result = highlight`价格是${price},含税${total}`;

10.2 常用方法

const str = '  Hello, JavaScript World!  ';

// 大小写转换
str.toUpperCase();      // '  HELLO, JAVASCRIPT WORLD!  '
str.toLowerCase();      // '  hello, javascript world!  '

// 去除空白
str.trim();             // 'Hello, JavaScript World!'
str.trimStart();        // 去除头部空白
str.trimEnd();          // 去除尾部空白

// 查找
str.indexOf('Java');           // 8
str.lastIndexOf('o');          // 20
str.includes('Script');        // true
str.startsWith('Hello');       // false(有空格)
str.endsWith('!');             // false(有空格)
str.search(/javascript/i);     // 8(支持正则)

// 提取子串
str.slice(8, 18);              // 'JavaScript'
str.substring(8, 18);          // 'JavaScript'(不接受负数)
str.substr(8, 10);             // 'JavaScript'(已废弃)

// 替换
str.replace('World', '世界');  // 只替换第一个
str.replace(/o/g, 'O');        // 全局替换(正则)

// 分割与拼接
str.split(' ');                // 按空格分割成数组
['Hello', 'World'].join(', '); // 数组拼接成字符串

// 填充
'42'.padStart(5, '0');         // '00042'
'42'.padEnd(5, '*');           // '42***'

// 重复
'Hi'.repeat(3);                // 'HiHiHi'

11. DOM 操作

11.1 获取元素

// 通过 ID
const header = document.getElementById('header');

// 通过 CSS 选择器(推荐)
const box = document.querySelector('.box');          // 第一个匹配
const allBoxes = document.querySelectorAll('.box');  // 所有匹配(NodeList)

// 通过类名
const items = document.getElementsByClassName('item');   // HTMLCollection(动态)
const tags = document.getElementsByTagName('div');       // HTMLCollection(动态)

11.2 操作内容

const element = document.querySelector('.content');

// 文本内容
element.textContent = '新的文本内容';   // 纯文本,安全
element.innerText = '可见文本';         // 考虑 CSS 样式

// HTML 内容
element.innerHTML = '<strong>粗体文本</strong>';  // ⚠️ 注意 XSS 风险

// 安全插入 HTML
element.insertAdjacentHTML('beforeend', '<span>安全插入</span>');
// 位置选项:'beforebegin' | 'afterbegin' | 'beforeend' | 'afterend'

11.3 操作属性

const link = document.querySelector('a');
const input = document.querySelector('input');

// 标准属性
link.href = 'https://example.com';
link.id = 'main-link';
link.className = 'btn primary';

// classList API(推荐)
link.classList.add('active');
link.classList.remove('primary');
link.classList.toggle('visible');
link.classList.contains('active');   // true/false
link.classList.replace('old', 'new');

// 通用属性方法
link.setAttribute('data-id', '123');
link.getAttribute('data-id');        // '123'
link.hasAttribute('data-id');        // true
link.removeAttribute('data-id');

// data-* 属性快捷访问
console.log(link.dataset.id);        // '123'(对应 data-id)

11.4 操作样式

const box = document.querySelector('.box');

// 内联样式(写入 style 属性)
box.style.backgroundColor = 'red';
box.style.fontSize = '16px';
box.style.cssText = 'color: white; padding: 10px;';

// 获取计算样式(只读)
const styles = getComputedStyle(box);
console.log(styles.width);
console.log(styles.getPropertyValue('--custom-var'));  // CSS 变量

// 使用 class 切换样式(推荐)
box.classList.toggle('dark-mode');

11.5 创建与操作节点

// 创建元素
const div = document.createElement('div');
div.textContent = '新创建的元素';
div.className = 'new-box';

// 创建文本节点
const text = document.createTextNode('纯文本');

// 创建文档片段(批量操作优化)
const fragment = document.createDocumentFragment();
for (let i = 0; i < 100; i++) {
    const li = document.createElement('li');
    li.textContent = `项目 ${i}`;
    fragment.appendChild(li);
}
document.querySelector('ul').appendChild(fragment);  // 一次性插入

// 插入节点
parent.appendChild(child);           // 末尾添加
parent.insertBefore(newNode, ref);   // 在 ref 之前插入
parent.prepend(child);               // 开头添加(现代 API)
parent.append(child);                // 末尾添加(现代 API,支持多参数)

// 替换与移除
oldNode.replaceWith(newNode);
element.remove();                    // 现代 API
parent.removeChild(child);           // 传统 API

// 克隆
const clone = element.cloneNode(true);  // true = 深克隆(含子节点)

11.6 元素尺寸与位置

const el = document.querySelector('.box');

// 尺寸(含 padding,不含 border 和滚动条)
el.clientWidth;
el.clientHeight;

// 尺寸(含 padding + border,不含滚动条)
el.offsetWidth;
el.offsetHeight;

// 内容实际尺寸(含溢出部分)
el.scrollWidth;
el.scrollHeight;

// 相对定位父元素的位置
el.offsetTop;
el.offsetLeft;

// 相对于视口的位置
const rect = el.getBoundingClientRect();
console.log(rect.top, rect.left, rect.bottom, rect.right, rect.width, rect.height);

// 滚动位置
el.scrollTop;
el.scrollLeft;
window.scrollY;     // 页面垂直滚动距离
window.scrollX;     // 页面水平滚动距离

12. 事件处理

12.1 事件绑定

const button = document.querySelector('button');

// 方式 1:addEventListener(推荐)
button.addEventListener('click', function(event) {
    console.log('按钮被点击', event);
});

// 方式 2:on 属性(只能绑定一个处理函数)
button.onclick = function() {
    console.log('点击');
};

// 方式 3:HTML 内联(不推荐)
// <button onclick="handleClick()">点击</button>

// 移除事件监听
function handler() { console.log('处理'); }
button.addEventListener('click', handler);
button.removeEventListener('click', handler);  // 必须引用同一个函数

12.2 事件对象

element.addEventListener('click', function(event) {
    console.log(event.type);          // 'click'
    console.log(event.target);        // 触发事件的元素
    console.log(event.currentTarget); // 绑定事件的元素
    console.log(event.clientX);       // 鼠标 X 坐标
    console.log(event.clientY);       // 鼠标 Y 坐标
    
    event.preventDefault();           // 阻止默认行为
    event.stopPropagation();          // 阻止冒泡
    event.stopImmediatePropagation(); // 阻止同一元素上其他监听器
});

12.3 事件委托

// 利用冒泡,在父元素上监听子元素事件
const list = document.querySelector('ul');

list.addEventListener('click', function(event) {
    if (event.target.tagName === 'LI') {
        console.log('点击了:', event.target.textContent);
    }
    // 或使用 matches
    if (event.target.matches('.item')) {
        console.log('点击了 item');
    }
});

// 优势:动态添加的子元素自动拥有事件处理能力

12.4 常用事件类型

// 鼠标事件
'click', 'dblclick', 'mousedown', 'mouseup', 'mousemove',
'mouseenter', 'mouseleave', 'mouseover', 'mouseout', 'contextmenu'

// 键盘事件
'keydown', 'keyup', 'keypress'
document.addEventListener('keydown', (e) => {
    console.log(e.key, e.code, e.ctrlKey, e.shiftKey);
});

// 表单事件
'input', 'change', 'submit', 'focus', 'blur', 'reset'

// 文档/窗口事件
'DOMContentLoaded', 'load', 'beforeunload', 'resize', 'scroll'

// 触摸事件
'touchstart', 'touchmove', 'touchend', 'touchcancel'

// 自定义事件
const customEvent = new CustomEvent('myEvent', {
    detail: { message: '自定义数据' }
});
element.dispatchEvent(customEvent);
element.addEventListener('myEvent', (e) => {
    console.log(e.detail.message);
});

12.5 事件循环机制

// 捕获阶段 → 目标阶段 → 冒泡阶段
// addEventListener 默认在冒泡阶段触发

element.addEventListener('click', handler, false);  // 冒泡阶段(默认)
element.addEventListener('click', handler, true);   // 捕获阶段

// once 选项(执行一次后自动移除)
button.addEventListener('click', handler, { once: true });

// passive 选项(提升滚动性能)
document.addEventListener('touchstart', handler, { passive: true });

13. 作用域与闭包

13.1 作用域类型

// 全局作用域
const globalVar = '全局变量';

function test() {
    // 函数作用域
    const functionVar = '函数变量';
    
    if (true) {
        // 块级作用域(let/const)
        const blockVar = '块级变量';
        var functionScoped = '仍然是函数作用域';
    }
    
    console.log(functionScoped);  // ✅ 可访问
    // console.log(blockVar);     // ❌ 不可访问
}

// 作用域链:内层可访问外层,反之不行

13.2 变量提升

// var 变量提升
console.log(a);    // undefined(不是 ReferenceError)
var a = 5;

// 函数声明提升
sayHello();        // 'Hello'(可提前调用)
function sayHello() {
    console.log('Hello');
}

// let/const 有暂时性死区(TDZ)
// console.log(b);  // ReferenceError
let b = 10;

13.3 闭包

// 闭包:函数 + 其可访问的外部变量
function createCounter() {
    let count = 0;            // 私有变量
    
    return {
        increment() {
            count++;
            return count;
        },
        decrement() {
            count--;
            return count;
        },
        getValue() {
            return count;
        }
    };
}

const counter = createCounter();
console.log(counter.increment());  // 1
console.log(counter.increment());  // 2
console.log(counter.getValue());   // 2
// count 无法从外部直接访问

// 实用场景:防抖函数
function debounce(fn, delay) {
    let timer = null;
    return function(...args) {
        clearTimeout(timer);
        timer = setTimeout(() => fn.apply(this, args), delay);
    };
}

// 节流函数
function throttle(fn, interval) {
    let lastTime = 0;
    return function(...args) {
        const now = Date.now();
        if (now - lastTime >= interval) {
            lastTime = now;
            fn.apply(this, args);
        }
    };
}

13.4 this 关键字

// this 的指向取决于调用方式

// 1. 全局上下文
console.log(this);  // window(浏览器)/ global(Node.js)

// 2. 对象方法
const obj = {
    name: '对象',
    sayName() {
        console.log(this.name);  // this 指向 obj
    }
};
obj.sayName();

// 3. 构造函数
function Person(name) {
    this.name = name;  // this 指向新创建的实例
}
const p = new Person('张三');

// 4. 事件处理
button.addEventListener('click', function() {
    console.log(this);  // this 指向 button 元素
});

// 5. 箭头函数(继承外层 this)
const obj2 = {
    name: 'obj2',
    methods: {
        name: 'methods',
        arrow: () => console.log(this.name),     // 指向全局
        normal() { console.log(this.name); }     // 指向 methods
    }
};

14. 原型与继承

14.1 原型链

// 每个对象都有 __proto__(隐式原型)
// 每个函数都有 prototype(显式原型)

function Animal(name) {
    this.name = name;
}

Animal.prototype.speak = function() {
    console.log(`${this.name} 发出声音`);
};

const dog = new Animal('小狗');
dog.speak();  // '小狗 发出声音'

// 原型链
console.log(dog.__proto__ === Animal.prototype);  // true
console.log(Animal.prototype.__proto__ === Object.prototype); // true
console.log(Object.prototype.__proto__);  // null(原型链终点)

14.2 原型继承模式

// 寄生组合式继承(推荐)
function Parent(name) {
    this.name = name;
    this.colors = ['red', 'blue'];
}

Parent.prototype.sayName = function() {
    console.log(this.name);
};

function Child(name, age) {
    Parent.call(this, name);  // 继承实例属性
    this.age = age;
}

// 继承原型方法
Child.prototype = Object.create(Parent.prototype);
Child.prototype.constructor = Child;  // 修正 constructor 指向

// 添加子类方法
Child.prototype.sayAge = function() {
    console.log(this.age);
};

const child = new Child('小明', 10);
child.sayName();  // '小明'
child.sayAge();   // 10

14.3 属性查找

// 检查属性是否在实例自身
console.log(child.hasOwnProperty('name'));    // true
console.log(child.hasOwnProperty('sayName')); // false(在原型上)

// in 运算符(检查原型链)
console.log('sayName' in child);  // true

// 获取对象原型
console.log(Object.getPrototypeOf(child) === Child.prototype);  // true

// 设置对象原型
Object.setPrototypeOf(child, newProto);

15. 类(ES6+)

15.1 类声明

class Person {
    // 构造函数
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    
    // 实例方法
    introduce() {
        return `我叫${this.name}${this.age}岁`;
    }
    
    // getter
    get info() {
        return `${this.name} (${this.age})`;
    }
    
    // setter
    set rename(newName) {
        if (typeof newName === 'string' && newName.length > 0) {
            this.name = newName;
        }
    }
    
    // 静态方法
    static create(data) {
        return new Person(data.name, data.age);
    }
}

const person = new Person('张三', 30);
console.log(person.introduce());
console.log(person.info);
person.rename = '李四';
console.log(Person.create({ name: '王五', age: 25 }));

15.2 继承

class Employee extends Person {
    constructor(name, age, position) {
        super(name, age);  // 必须先调用 super
        this.position = position;
    }
    
    introduce() {
        // 调用父类方法
        return `${super.introduce()},职位是${this.position}`;
    }
    
    work() {
        return `${this.name} 正在工作`;
    }
}

const emp = new Employee('赵六', 28, '工程师');
console.log(emp.introduce());  // '我叫赵六,28岁,职位是工程师'
console.log(emp.work());       // '赵六 正在工作'

15.3 私有字段(ES2022)

class BankAccount {
    #balance = 0;  // 私有字段
    
    constructor(initialBalance) {
        this.#balance = initialBalance;
    }
    
    deposit(amount) {
        if (amount > 0) {
            this.#balance += amount;
        }
    }
    
    getBalance() {
        return this.#balance;
    }
    
    // 私有方法
    #validateAmount(amount) {
        return amount > 0 && Number.isFinite(amount);
    }
}

const account = new BankAccount(1000);
account.deposit(500);
console.log(account.getBalance());  // 1500
// console.log(account.#balance);   // ❌ SyntaxError

16. 异步编程

16.1 事件循环(Event Loop)

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

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

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

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

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

执行顺序:同步代码 → 微任务队列(Promise、MutationObserver)→ 宏任务队列(setTimeout、setInterval、I/O)

16.2 回调函数

// 传统异步模式(容易产生回调地狱)
function fetchData(url, callback) {
    setTimeout(() => {
        const data = { result: 'some data' };
        callback(null, data);
    }, 1000);
}

fetchData('/api/data', (error, data) => {
    if (error) {
        console.error('出错:', error);
        return;
    }
    console.log('数据:', data);
});

16.3 Promise

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

// 使用 Promise
promise
    .then(result => {
        console.log(result);
        return '下一步数据';
    })
    .then(data => {
        console.log(data);
    })
    .catch(error => {
        console.error('捕获错误:', error);
    })
    .finally(() => {
        console.log('无论成功失败都会执行');
    });

// Promise 静态方法
Promise.resolve('立即成功');
Promise.reject('立即失败');
Promise.all([p1, p2, p3]);        // 全部成功才成功
Promise.allSettled([p1, p2, p3]); // 全部完成(无论成功失败)
Promise.race([p1, p2, p3]);       // 第一个完成的
Promise.any([p1, p2, p3]);        // 第一个成功的(ES2021)

16.4 async/await(ES2017)

// async 函数返回 Promise
async function fetchUser(id) {
    // await 等待 Promise 完成
    const response = await fetch(`/api/users/${id}`);
    const user = await response.json();
    return user;
}

// 错误处理
async function loadData() {
    try {
        const user = await fetchUser(1);
        const posts = await fetchPosts(user.id);
        return { user, posts };
    } catch (error) {
        console.error('加载失败:', error);
        throw error;  // 重新抛出或返回默认值
    }
}

// 并行执行
async function loadMultiple() {
    const [user, posts, settings] = await Promise.all([
        fetchUser(1),
        fetchPosts(1),
        fetchSettings()
    ]);
    return { user, posts, settings };
}

// 顶层 await(ES2022,模块中可用)
// const data = await fetch('/api/data');

16.5 Fetch API

// GET 请求
fetch('https://api.example.com/data')
    .then(response => {
        if (!response.ok) throw new Error(`HTTP ${response.status}`);
        return response.json();
    })
    .then(data => console.log(data))
    .catch(error => console.error(error));

// POST 请求
async function postData(url, data) {
    const response = await fetch(url, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify(data),
    });
    return response.json();
}

// 请求超时
function fetchWithTimeout(url, timeout = 5000) {
    return Promise.race([
        fetch(url),
        new Promise((_, reject) =>
            setTimeout(() => reject(new Error('请求超时')), timeout)
        )
    ]);
}

17. 错误处理

17.1 try-catch-finally

try {
    // 可能出错的代码
    const result = riskyOperation();
    console.log(result);
} catch (error) {
    // 处理错误
    console.error('错误名称:', error.name);
    console.error('错误信息:', error.message);
    console.error('错误堆栈:', error.stack);
} finally {
    // 始终执行(清理资源)
    console.log('清理工作...');
}

17.2 自定义错误

class ValidationError extends Error {
    constructor(message, field) {
        super(message);
        this.name = 'ValidationError';
        this.field = field;
    }
}

function validateUser(user) {
    if (!user.name) {
        throw new ValidationError('用户名不能为空', 'name');
    }
    if (user.age < 0) {
        throw new ValidationError('年龄不能为负数', 'age');
    }
}

try {
    validateUser({ name: '', age: -5 });
} catch (error) {
    if (error instanceof ValidationError) {
        console.log(`${error.field} 字段验证失败: ${error.message}`);
    }
}

17.3 全局错误处理

// 浏览器全局错误
window.onerror = function(message, source, line, col, error) {
    console.error('全局错误:', message, source, line, col);
    // 上报错误到监控系统
};

// 未捕获的 Promise 拒绝
window.addEventListener('unhandledrejection', function(event) {
    console.error('未处理的 Promise 拒绝:', event.reason);
    event.preventDefault();  // 阻止默认的控制台输出
});

18. 模块化

18.1 ES Modules(ESM)

// ============ math.js ============
// 命名导出
export const PI = 3.14159;

export function add(a, b) {
    return a + b;
}

export function multiply(a, b) {
    return a * b;
}

// 默认导出(一个模块只能有一个)
export default function subtract(a, b) {
    return a - b;
}

// ============ app.js ============
// 命名导入
import { PI, add, multiply } from './math.js';

// 默认导入
import subtract from './math.js';

// 别名导入
import { add as sum } from './math.js';

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

// 动态导入(返回 Promise)
async function loadModule() {
    const module = await import('./dynamic.js');
    module.default();
}

// 仅执行模块(不导入任何内容)
import './init.js';

18.2 CommonJS(Node.js 传统)

// ============ utils.js ============
function formatDate(date) {
    return date.toISOString();
}

function capitalize(str) {
    return str.charAt(0).toUpperCase() + str.slice(1);
}

// 导出
module.exports = {
    formatDate,
    capitalize
};

// 或逐个导出
exports.formatDate = formatDate;

// ============ main.js ============
const utils = require('./utils.js');
// 或解构
const { formatDate } = require('./utils.js');

19. ES6+ 常用新特性

19.1 展开与剩余

// 展开运算符(Spread)... 
const arr = [1, 2, 3];
console.log(Math.max(...arr));           // 展开为参数
const copy = [...arr];                   // 浅拷贝数组
const merged = { ...obj1, ...obj2 };     // 合并对象

// 剩余参数(Rest)
function sum(first, ...rest) {
    return first + rest.reduce((a, b) => a + b, 0);
}

const [head, ...tail] = [1, 2, 3, 4];   // 数组解构剩余

19.2 Set 和 Map

// Set — 不重复值的集合
const set = new Set([1, 2, 2, 3, 3]);
console.log(set);              // Set { 1, 2, 3 }
set.add(4);
set.has(2);                    // true
set.delete(3);
set.size;                      // 3
set.clear();

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

// Map — 键值对集合(键可以是任意类型)
const map = new Map();
map.set('name', '张三');
map.set(42, '数字键');
map.set({ id: 1 }, '对象键');

map.get('name');               // '张三'
map.has(42);                   // true
map.size;                      // 3
map.delete('name');

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

// WeakMap — 键必须是对象,弱引用(可被垃圾回收)
const weakMap = new WeakMap();
const objKey = {};
weakMap.set(objKey, '关联数据');
// 当 objKey 被回收时,该条目自动从 WeakMap 中移除

19.3 可选链与空值合并

// 可选链(Optional Chaining)— ES2020
const user = null;
console.log(user?.profile?.name);          // undefined(不报错)
console.log(user?.getInfo?.());            // undefined(方法调用)
console.log(arr?.[0]);                     // undefined(数组访问)

// 空值合并(Nullish Coalescing)— ES2020
const value = null ?? '默认值';            // '默认值'
const value2 = 0 ?? '默认值';             // 0(只有 null/undefined 触发)
const value3 = '' || '默认值';            // '默认值'(|| 将所有假值触发)

// 结合使用
const displayName = user?.name ?? '匿名用户';

19.4 逻辑赋值运算符(ES2021)

let a = null;
a ??= '默认值';        // a = a ?? '默认值'

let b = 0;
b ||= 10;              // b = b || 10 → 10

let c = 5;
c &&= 10;              // c = c && 10 → 10

20. Web API 精选

20.1 localStorage / sessionStorage

// localStorage — 持久化存储(浏览器关闭后保留)
localStorage.setItem('theme', 'dark');
const theme = localStorage.getItem('theme');
localStorage.removeItem('theme');
localStorage.clear();

// 存储对象(需序列化)
const user = { name: '张三', age: 30 };
localStorage.setItem('user', JSON.stringify(user));
const savedUser = JSON.parse(localStorage.getItem('user'));

// sessionStorage — 会话级存储(关闭标签页后清除)
sessionStorage.setItem('temp', '临时数据');

20.2 定时器

// setTimeout — 延迟执行
const timerId = setTimeout(() => {
    console.log('1秒后执行');
}, 1000);
clearTimeout(timerId);  // 取消

// setInterval — 重复执行
const intervalId = setInterval(() => {
    console.log('每2秒执行一次');
}, 2000);
clearInterval(intervalId);  // 停止

// requestAnimationFrame — 动画优化
function animate() {
    // 执行动画逻辑
    requestAnimationFrame(animate);
}
requestAnimationFrame(animate);

20.3 History API

// 导航
history.back();
history.forward();
history.go(-1);    // 后退一页
history.go(2);     // 前进两页

// 操作历史记录(SPA 路由基础)
history.pushState({ page: 1 }, '标题', '/page1');
history.replaceState({ page: 2 }, '标题', '/page2');

// 监听导航
window.addEventListener('popstate', (event) => {
    console.log('当前状态:', event.state);
    console.log('当前 URL:', location.pathname);
});

20.4 Intersection Observer

// 观察元素是否进入视口
const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            console.log('元素进入视口');
            entry.target.classList.add('visible');
            observer.unobserve(entry.target);  // 停止观察
        }
    });
}, {
    threshold: 0.5,     // 50% 可见时触发
    rootMargin: '20px'  // 提前 20px 触发
});

// 开始观察
const elements = document.querySelectorAll('.lazy-load');
elements.forEach(el => observer.observe(el));

20.5 剪贴板 API

// 写入剪贴板
async function copyToClipboard(text) {
    try {
        await navigator.clipboard.writeText(text);
        console.log('已复制到剪贴板');
    } catch (err) {
        console.error('复制失败:', err);
    }
}

// 读取剪贴板
async function readFromClipboard() {
    try {
        const text = await navigator.clipboard.readText();
        console.log('剪贴板内容:', text);
    } catch (err) {
        console.error('读取失败:', err);
    }
}

21. 设计模式

21.1 单例模式

class Database {
    static #instance = null;
    
    constructor() {
        if (Database.#instance) {
            return Database.#instance;
        }
        this.connection = this.#connect();
        Database.#instance = this;
    }
    
    #connect() {
        console.log('建立数据库连接');
        return { /* 连接对象 */ };
    }
    
    static getInstance() {
        if (!Database.#instance) {
            Database.#instance = new Database();
        }
        return Database.#instance;
    }
}

const db1 = Database.getInstance();
const db2 = Database.getInstance();
console.log(db1 === db2);  // true

21.2 观察者模式

class EventEmitter {
    constructor() {
        this.events = {};
    }
    
    on(event, listener) {
        if (!this.events[event]) {
            this.events[event] = [];
        }
        this.events[event].push(listener);
        return () => this.off(event, listener);  // 返回取消订阅函数
    }
    
    off(event, listener) {
        if (!this.events[event]) return;
        this.events[event] = this.events[event].filter(l => l !== listener);
    }
    
    emit(event, ...args) {
        if (!this.events[event]) return;
        this.events[event].forEach(listener => listener(...args));
    }
    
    once(event, listener) {
        const wrapper = (...args) => {
            listener(...args);
            this.off(event, wrapper);
        };
        this.on(event, wrapper);
    }
}

// 使用
const emitter = new EventEmitter();
const unsubscribe = emitter.on('data', (data) => console.log('收到:', data));
emitter.emit('data', { message: 'Hello' });
unsubscribe();  // 取消订阅

21.3 工厂模式

class User {
    constructor(name, role) {
        this.name = name;
        this.role = role;
    }
}

class Admin extends User {
    constructor(name) {
        super(name, 'admin');
        this.permissions = ['read', 'write', 'delete'];
    }
}

class Guest extends User {
    constructor(name) {
        super(name, 'guest');
        this.permissions = ['read'];
    }
}

class UserFactory {
    static createUser(name, type) {
        switch (type.toLowerCase()) {
            case 'admin':
                return new Admin(name);
            case 'guest':
                return new Guest(name);
            default:
                throw new Error(`未知用户类型: ${type}`);
        }
    }
}

const admin = UserFactory.createUser('张三', 'admin');
const guest = UserFactory.createUser('访客', 'guest');

22. 性能优化

22.1 DOM 操作优化

// ❌ 避免:频繁操作 DOM
for (let i = 0; i < 1000; i++) {
    document.body.innerHTML += `<div>${i}</div>`;
}

// ✅ 推荐:使用 DocumentFragment 或一次性插入
const fragment = document.createDocumentFragment();
for (let i = 0; i < 1000; i++) {
    const div = document.createElement('div');
    div.textContent = i;
    fragment.appendChild(div);
}
document.body.appendChild(fragment);

// ✅ 或使用 innerHTML 一次性设置
let html = '';
for (let i = 0; i < 1000; i++) {
    html += `<div>${i}</div>`;
}
document.body.innerHTML = html;

22.2 防抖与节流

// 防抖 — 连续触发只执行最后一次
function debounce(fn, delay = 300) {
    let timer;
    return function(...args) {
        clearTimeout(timer);
        timer = setTimeout(() => fn.apply(this, args), delay);
    };
}

// 节流 — 固定时间间隔执行一次
function throttle(fn, interval = 300) {
    let lastTime = 0;
    return function(...args) {
        const now = Date.now();
        if (now - lastTime >= interval) {
            lastTime = now;
            fn.apply(this, args);
        }
    };
}

// 使用示例
const handleSearch = debounce((query) => {
    console.log('搜索:', query);
}, 500);

const handleScroll = throttle(() => {
    console.log('滚动位置:', window.scrollY);
}, 200);

22.3 内存管理

// 避免内存泄漏
// ❌ 全局变量
function leak() {
    globalVar = '这会泄漏到全局';  // 忘记 var/let/const
}

// ❌ 未清除的定时器
let intervalId = setInterval(() => {
    // 如果组件销毁了但定时器还在运行
}, 1000);
// ✅ 在不需要时清除
clearInterval(intervalId);

// ❌ 闭包中的大对象引用
function createHandler() {
    const largeData = new Array(1000000);
    return function() {
        console.log(largeData[0]);  // 保留了对 largeData 的引用
    };
}

// ❌ 未移除的事件监听
// ✅ 组件销毁时移除
element.removeEventListener('click', handler);

// ✅ 使用 WeakMap 存储 DOM 关联数据
const elementData = new WeakMap();
elementData.set(document.getElementById('app'), { clicks: 0 });
// 当元素被移除时,关联数据自动被回收

22.4 代码分割与懒加载

// 动态导入(代码分割)
button.addEventListener('click', async () => {
    const { heavyFunction } = await import('./heavyModule.js');
    heavyFunction();
});

// 图片懒加载
const imageObserver = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            const img = entry.target;
            img.src = img.dataset.src;
            imageObserver.unobserve(img);
        }
    });
});

document.querySelectorAll('img[data-src]').forEach(img => {
    imageObserver.observe(img);
});

23. 最佳实践

23.1 严格模式

'use strict';  // 写在文件或函数开头

// 严格模式下的变化:
// 1. 变量必须先声明
x = 10;  // ❌ ReferenceError

// 2. 函数 this 为 undefined(非对象包装)
function showThis() {
    console.log(this);  // undefined(非严格模式为 window)
}

// 3. 禁止删除变量
let y = 5;
// delete y;  // ❌ SyntaxError

// 4. 函数参数不能重名
// function(a, a) {}  // ❌

23.2 代码风格

// ✅ 使用 const 优先,其次是 let,避免 var
const MAX_SIZE = 100;
let currentSize = 0;

// ✅ 使用 === 而非 ==
if (value === null || value === undefined) { }
// 或
if (value == null) { }  // 仅检查 null/undefined 时 == null 可接受

// ✅ 使用模板字符串
const message = `用户 ${name} 的年龄是 ${age}`;

// ✅ 使用解构
const { name, age } = user;
const [first, ...rest] = items;

// ✅ 使用展开运算符进行浅拷贝
const copy = { ...original };
const arrCopy = [...originalArray];

// ✅ 提前 return 减少嵌套
function process(data) {
    if (!data) return null;
    if (!data.isValid) return null;
    
    // 主逻辑
    return transform(data);
}

// ✅ 使用可选链
const city = user?.address?.city;

23.3 安全实践

// 防止 XSS
const userInput = '<img src=x onerror=alert("XSS")>';
// ❌ 直接插入 HTML
element.innerHTML = userInput;
// ✅ 使用 textContent 或进行转义
element.textContent = userInput;

// HTML 转义函数
function escapeHtml(str) {
    const div = document.createElement('div');
    div.textContent = str;
    return div.innerHTML;
}

// 安全的 DOM 操作
const sanitized = DOMPurify.sanitize(userInput);  // 使用 DOMPurify 库
element.innerHTML = sanitized;

// 避免 eval
// ❌ eval('console.log("危险")');
// ❌ new Function('return ' + userInput)();

24. 测试入门

24.1 单元测试概念

// 被测试的函数
function add(a, b) {
    return a + b;
}

function divide(a, b) {
    if (b === 0) throw new Error('除数不能为零');
    return a / b;
}

// 简单的手动测试
function test(description, fn) {
    try {
        fn();
        console.log(`✅ ${description}`);
    } catch (error) {
        console.error(`❌ ${description}`);
        console.error(error);
    }
}

function expect(actual) {
    return {
        toBe(expected) {
            if (actual !== expected) {
                throw new Error(`期望 ${expected},实际 ${actual}`);
            }
        },
        toThrow() {
            let threw = false;
            try { actual(); } catch { threw = true; }
            if (!threw) throw new Error('期望抛出错误');
        }
    };
}

test('add(1, 2) 应返回 3', () => {
    expect(add(1, 2)).toBe(3);
});

test('divide(10, 0) 应抛出错误', () => {
    expect(() => divide(10, 0)).toThrow();
});

24.2 Jest 示例

// math.js
export function multiply(a, b) {
    return a * b;
}

// math.test.js
import { multiply } from './math';

describe('multiply 函数', () => {
    test('正数相乘', () => {
        expect(multiply(3, 4)).toBe(12);
    });
    
    test('乘以零', () => {
        expect(multiply(5, 0)).toBe(0);
    });
    
    test('负数相乘', () => {
        expect(multiply(-2, 3)).toBe(-6);
    });
});

25. 现代工具链

25.1 包管理器

# npm
npm init -y                    # 初始化项目
npm install package-name       # 安装依赖
npm install -D package-name    # 开发依赖
npm update                     # 更新依赖
npm run build                  # 运行脚本

# yarn
yarn add package-name
yarn add -D package-name

# pnpm(更快、更省空间)
pnpm add package-name

25.2 打包工具概念

// webpack.config.js 示例结构
module.exports = {
    entry: './src/index.js',
    output: {
        path: path.resolve(__dirname, 'dist'),
        filename: 'bundle.js'
    },
    module: {
        rules: [
            {
                test: /\.js$/,
                exclude: /node_modules/,
                use: 'babel-loader'
            }
        ]
    },
    plugins: [
        new HtmlWebpackPlugin({ template: './index.html' })
    ]
};

// Vite 配置(更简洁)
// vite.config.js
import { defineConfig } from 'vite';
export default defineConfig({
    plugins: [],
    server: { port: 3000 }
});

25.3 Babel 转译

// babel.config.json
{
    "presets": [
        ["@babel/preset-env", {
            "targets": "> 0.25%, not dead"
        }]
    ],
    "plugins": [
        "@babel/plugin-transform-runtime"
    ]
}

附录

A. 常用代码片段

// 生成随机数
const random = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min;

// 打乱数组(Fisher-Yates)
const shuffle = (arr) => {
    for (let i = arr.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [arr[i], arr[j]] = [arr[j], arr[i]];
    }
    return arr;
};

// 深拷贝(简易版)
const deepClone = (obj) => structuredClone(obj);  // 现代浏览器支持
// 或
const deepCloneJSON = (obj) => JSON.parse(JSON.stringify(obj));

// 格式化日期
const formatDate = (date) => {
    return new Date(date).toLocaleDateString('zh-CN', {
        year: 'numeric',
        month: 'long',
        day: 'numeric'
    });
};

// 检测设备类型
const isMobile = /Android|iPhone|iPad|iPod/i.test(navigator.userAgent);

B. 推荐学习资源

  • MDN Web Docs — 最权威的 JS 参考文档

  • JavaScript.info — 现代 JS 教程

  • You Don&#39;t Know JS — 深入理解 JS 系列

  • ECMAScript 规范 — 语言标准

C. 持续学习路径

掌握基础 → 2. DOM &amp; 事件 → 3. 异步编程 → 4. ES6+ 特性 → 5. 框架学习(React/Vue)→ 6. Node.js 后端 → 7. TypeScript → 8. 工程化(Webpack/Vite)→ 9. 性能优化 → 10. 源码阅读

(注:文档部分内容可能由 AI 生成)