javascript万字全解知识宝库

发布于:2025-05-13 ⋅ 阅读:(15) ⋅ 点赞:(0)

文章目录

javascript万字全解知识宝库

JavaScript 入门配置教学

一、JavaScript 简介

JavaScript(简称JS)是一种轻量级的解释型编程语言,最初由网景公司(Netscape)的Brendan Eich在1995年开发完成。作为Web的三大核心技术之一(HTML、CSS、JavaScript),它赋予了网页动态交互能力。JavaScript 是一种运行在浏览器中的脚本语言,主要用于网页交互。现在也可以通过 Node.js 运行在服务器端。

与其他语言的对比:
特性 JavaScript Python Java
运行环境 浏览器/Node 解释器 JVM
类型系统 动态弱类型 动态强类型 静态强类型
并发模型 事件循环 多线程 多线程
典型用途 Web开发 数据分析 企业应用

二、基础环境配置

1. 浏览器环境(最简单的方式)

不需要任何安装配置,直接使用浏览器即可:

  1. 新建一个文本文件,重命名为 index.html
  2. 用记事本或代码编辑器打开,添加以下内容:
<!DOCTYPE html>
<html>
<head>
    <title>我的第一个JS程序</title>
</head>
<body>
    <script>
        alert('Hello World!');
    </script>
</body>
</html>
  1. 双击文件在浏览器中打开

2. 使用代码编辑器(推荐)

推荐安装以下编辑器之一:

  • VS Code(最推荐)
  • Sublime Text
  • WebStorm

安装 VS Code 后建议安装这些扩展:

  • JavaScript (ES6) code snippets
  • ESLint
  • Live Server(实时预览)

3. Node.js 环境(进阶)

如果需要运行服务器端JavaScript:

  1. 下载安装 Node.js(建议选择LTS版本)
  2. 安装完成后,打开命令行(终端)输入:
node -v

如果显示版本号说明安装成功

三、第一个JavaScript程序

浏览器中运行
  1. 创建 index.html 文件:
<!DOCTYPE html>
<html>
<body>
    <h1>我的JS测试</h1>
    <script src="app.js"></script>
</body>
</html>
  1. 创建 app.js 文件:
console.log("Hello JavaScript!");
document.write("<p>这是通过JS添加的内容</p>");
alert("欢迎来到JavaScript世界!");
  1. 用浏览器打开 index.html 查看效果
Node.js 中运行
  1. 创建 app.js 文件:
console.log("Hello Node.js!");
  1. 在命令行中运行:
node app.js

四、学习资源推荐

  1. 浏览器开发者工具(按F12打开)
    • Console 面板:直接输入JS代码测试
    • Sources 面板:调试JavaScript代码
  2. 在线练习平台:
    • JSFiddle
    • CodePen
  3. 免费教程:
    • MDN JavaScript 教程
    • 菜鸟教程 JavaScript

五、下一步学习建议

  1. 先掌握基础语法(变量、数据类型、函数、条件判断、循环)
  2. 学习DOM操作(如何用JS修改网页内容)
  3. 学习事件处理(点击、输入等交互)
  4. 学习ES6+新特性

记住:学习编程最重要的是多动手实践!

JavaScript 基础语法详细讲解

1. 变量声明

var
var name = "John"; // 函数作用域
var name = "Doe"; // 允许重复声明
console.log(name); // "Doe"

特点:
• 函数作用域(非块级作用域)

• 存在变量提升(hoisting)

• 可以重复声明

let
let age = 30; // 块级作用域
// let age = 40; // 报错,不能重复声明

if (true) {
    let age = 25; // 不同的块级作用域
    console.log(age); // 25
}
console.log(age); // 30

特点:
• 块级作用域

• 不允许重复声明

• 存在暂时性死区(TDZ)

const
const PI = 3.1415;
// PI = 3.14; // 报错,不能重新赋值

const user = { name: "John" };
user.name = "Doe"; // 允许修改对象属性
// user = {}; // 报错,不能重新赋值

特点:
• 必须初始化赋值

• 不能重新赋值

• 对于对象/数组,内容可修改(引用不变)

变量声明方式对比分析

特性 var let (ES6+) const (ES6+)
作用域 函数作用域 块级作用域 ({} 内有效) 块级作用域 ({} 内有效)
变量提升 是(声明提升到作用域顶部,值为 undefined 是(但存在"暂时性死区",声明前访问会报错) 是(同 let,存在暂时性死区)
重复声明 允许(不会报错) 不允许(会抛出语法错误) 不允许(会抛出语法错误)
重新赋值 允许 允许 不允许(声明后必须初始化且不可更改)
全局声明时是否为 window 属性 是(var a 等价于 window.a 否(不会挂载到全局对象) 否(不会挂载到全局对象)
典型用途 旧代码/兼容性需求 需要变化的局部变量 常量、对象/数组引用(内容可修改)

详细说明与示例
1. 作用域对比
// var 的函数作用域
function varTest() {
  var x = 1;
  if (true) {
    var x = 2;  // 同一个变量!
    console.log(x); // 2
  }
  console.log(x); // 2
}

// let/const 的块级作用域
function letTest() {
  let x = 1;
  if (true) {
    let x = 2;  // 不同的变量
    console.log(x); // 2
  }
  console.log(x); // 1
}
2. 变量提升问题
console.log(a); // undefined(var 提升)
var a = 10;

console.log(b); // ReferenceError(暂时性死区)
let b = 20;
3. const 的特殊行为
const PI = 3.14;
PI = 3.1415; // TypeError(不可重新赋值)

// 但对象/数组内容可修改
const obj = { name: "Alice" };
obj.name = "Bob"; // 允许!
obj = {};         // TypeError(不可更改引用)

选择建议
  1. 默认使用 const
    • 优先用于所有不需要重新赋值的变量(减少意外修改风险)
  2. 需要重新赋值时用 let
    • 如循环计数器、状态变量等
  3. 避免使用 var
    • 除非维护旧代码或特殊需求(如需要挂载到 window

关键区别记忆口诀
“var 会飞(提升),let/const 认块(块级作用域),const 认死理(不可改引用)”

JavaScript 数据类型概念精讲

一、数据类型:构建程序的基石

JavaScript 的数据类型可以分为两大阵营,它们的关系如同"原子"与"分子":

类别 原始类型(原子) 引用类型(分子)
包含类型 Number, String, Boolean, Null, Undefined, Symbol, BigInt Object, Array, Function, Date 等
存储方式 直接存储在栈内存中 存储在堆内存中,栈中存储引用地址
比较方式 比较值是否相等 比较引用地址是否相同
典型特征 不可变(immutable) 可变(mutable)
内存占用 固定大小 动态大小
示例 let age = 25 let user = {name: "Alice"}

💡 理解关键:原始类型就像固定大小的集装箱,引用类型像可扩展的仓库,仓库钥匙存在栈中。

原始类型(Primitive Types)

原始类型是JavaScript中最基础的数据类型,它们是不可变的(immutable),直接存储在栈内存中。

1. Number(数字类型)
let integer = 42;          // 整数
let float = 3.14;          // 浮点数
let scientific = 2.5e4;    // 科学计数法 (25000)
let hex = 0xff;            // 十六进制 (255)
let binary = 0b1010;       // 二进制 (10)
let octal = 0o755;         // 八进制 (493)
let infinity = Infinity;   // 无穷大
let notANumber = NaN;      // 非数字值

console.log(typeof integer); // "number"
console.log(0.1 + 0.2);     // 0.30000000000000004 (浮点数精度问题)
2. String(字符串类型)
let singleQuote = 'Hello';       // 单引号
let doubleQuote = "World";       // 双引号
let backtick = `Hello ${doubleQuote}`; // 模板字符串 (ES6)

let escape = "Line1\nLine2";    // 转义字符
let unicode = "\u0041";        // Unicode字符 (A)

console.log(singleQuote.length); // 5 (字符串长度)
console.log(backtick);          // "Hello World"
console.log('Hello'.charAt(1)); // 'e' (获取字符)
3. Boolean(布尔类型)
let isTrue = true;
let isFalse = false;

// 以下值在条件判断中会被转为false
console.log(Boolean(0));        // false
console.log(Boolean(''));       // false
console.log(Boolean(null));     // false
console.log(Boolean(undefined)); // false
console.log(Boolean(NaN));      // false
console.log(Boolean(false));    // false

// 其他值都会转为true
console.log(Boolean(1));        // true
console.log(Boolean('0'));      // true
console.log(Boolean([]));       // true
console.log(Boolean({}));       // true
4. Null(空值)
let empty = null;
console.log(typeof empty);      // "object" (历史遗留问题)
console.log(empty === null);    // true
console.log(empty == undefined); // true (非严格相等)
console.log(empty === undefined); // false
5. Undefined(未定义)
let notDefined;
let obj = {};

console.log(notDefined);        // undefined
console.log(obj.noProperty);    // undefined
console.log(typeof notDefined); // "undefined"
6. Symbol(唯一标识符,ES6新增)
let sym1 = Symbol('id');
let sym2 = Symbol('id');
let sym3 = Symbol.for('globalId'); // 全局Symbol

console.log(sym1 === sym2);      // false (每次创建都是唯一的)
console.log(sym1.toString());    // "Symbol(id)"
console.log(Symbol.keyFor(sym3)); // "globalId"

// 常用作对象属性的键
let user = {
  [sym1]: 'private data'
};
console.log(user[sym1]);        // "private data"
7. BigInt(大整数,ES2020新增)
const bigNum = 9007199254740991n; // 超过Number的安全整数范围
const hugeString = BigInt("9007199254740991");

console.log(bigNum + 1n);       // 9007199254740992n
console.log(typeof bigNum);     // "bigint"

// 不能与Number直接运算
console.log(bigNum + 2);        // TypeError
console.log(Number(bigNum) + 2); // 9007199254740993 (可能丢失精度)
引用类型(Reference Types)

引用类型是复杂数据类型,存储在堆内存中,变量存储的是指向内存地址的引用。

1. Object(对象)
let person = {
  name: 'Alice',
  age: 25,
  address: {
    city: 'Beijing',
    street: 'Main St'
  },
  greet: function() {
    console.log(`Hello, I'm ${this.name}`);
  }
};

// 访问属性
console.log(person.name);       // "Alice"
console.log(person['age']);     // 25
person.greet();                // "Hello, I'm Alice"

// 修改属性
person.age = 26;
person['address'].city = 'Shanghai';

// 添加新属性
person.job = 'Developer';

// 删除属性
delete person.address;

console.log(person);
2. Array(数组)
let fruits = ['Apple', 'Banana'];
let mixed = [1, 'two', true, {name: 'three'}];

// 访问元素
console.log(fruits[0]);         // "Apple"
console.log(mixed[3].name);     // "three"

// 修改元素
fruits[1] = 'Orange';

// 添加元素
fruits.push('Mango');          // 末尾添加
fruits.unshift('Peach');       // 开头添加

// 删除元素
fruits.pop();                  // 删除最后一个
fruits.shift();                // 删除第一个

// 数组方法
console.log(fruits.length);     // 2
console.log(fruits.indexOf('Orange')); // 1
console.log(fruits.slice(0, 1)); // ["Apple"] (浅拷贝)

// 多维数组
let matrix = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
];
console.log(matrix[1][2]);     // 6
3. Function(函数)
// 函数声明
function add(a, b) {
  return a + b;
}

// 函数表达式
const multiply = function(a, b) {
  return a * b;
};

// 箭头函数 (ES6)
const divide = (a, b) => a / b;

// 调用函数
console.log(add(2, 3));        // 5
console.log(multiply(2, 3));    // 6
console.log(divide(6, 2));     // 3

// 函数作为参数
function calculate(a, b, operation) {
  return operation(a, b);
}
console.log(calculate(4, 2, divide)); // 2

// 高阶函数
function createGreeting(greeting) {
  return function(name) {
    return `${greeting}, ${name}!`;
  };
}
const sayHello = createGreeting('Hello');
console.log(sayHello('Alice')); // "Hello, Alice!"
4. 其他内置对象
// Date(日期)
let now = new Date();
let specificDate = new Date(2023, 0, 1); // 2023年1月1日
console.log(now.toISOString());

// RegExp(正则表达式)
let regex = /ab+c/i;
let regexObj = new RegExp('ab+c', 'i');
console.log(regex.test('aBc')); // true

// Map(映射,ES6)
let map = new Map();
map.set('name', 'Alice');
map.set(1, 'number key');
console.log(map.get('name'));  // "Alice"

// Set(集合,ES6)
let set = new Set([1, 2, 3, 3, 4]);
console.log(set.size);        // 4 (自动去重)
类型检测(typeof)
console.log(typeof 42);        // "number"
console.log(typeof 'text');    // "string"
console.log(typeof true);      // "boolean"
console.log(typeof undefined); // "undefined"
console.log(typeof Symbol());  // "symbol"
console.log(typeof 1n);        // "bigint"
console.log(typeof null);      // "object" (历史遗留问题)
console.log(typeof []);        // "object"
console.log(typeof {});        // "object"
console.log(typeof function(){}); // "function"

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

二、类型转换:数据的变形记

JavaScript 的类型转换可以分为 显式转换(手动转换)和 隐式转换(自动转换)两种方式。下面我将通过代码示例详细解释各种类型转换的情况。

显式转换(主动变形)

方法 作用 示例 输出
Number() 转为数字 Number("10") 10
String() 转为字符串 String(10) “10”
Boolean() 转为布尔值 Boolean(1) true
parseInt() 解析整数 parseInt("10px") 10
parseFloat() 解析浮点数 parseFloat("10.5px") 10.5

隐式转换(自动变形)

场景 转换规则 示例 结果
字符串连接(+) 非字符串转为字符串 "5" + 2 “52”
数学运算(-*/%) 字符串转为数字 "5" - 2 3
逻辑上下文(if/!) 转为布尔值 !0 true
相等比较(==) 尝试类型转换后比较 10 == "10" true

⚠️ 特别注意:=== 是严格相等,不进行类型转换,推荐日常使用。

显式类型转换
1. 转换为字符串 (String)
// 使用 String() 函数
console.log(String(123));        // "123"
console.log(String(true));       // "true"
console.log(String(null));       // "null"
console.log(String(undefined));  // "undefined"
console.log(String({}));         // "[object Object]"
console.log(String([1, 2, 3]));  // "1,2,3"

// 使用 toString() 方法
console.log((123).toString());   // "123"
console.log((true).toString());  // "true"
console.log([1, 2].toString());  // "1,2"

// 注意:null 和 undefined 没有 toString() 方法
// (null).toString()  // TypeError
// (undefined).toString()  // TypeError
2. 转换为数字 (Number)
// 使用 Number() 函数
console.log(Number("123"));      // 123
console.log(Number("123.45"));   // 123.45
console.log(Number("123abc"));   // NaN (Not a Number)
console.log(Number(""));         // 0
console.log(Number(true));       // 1
console.log(Number(false));      // 0
console.log(Number(null));       // 0
console.log(Number(undefined));  // NaN
console.log(Number({}));         // NaN
console.log(Number([1]));        // 1
console.log(Number([1, 2]));     // NaN

// 使用 parseInt() 和 parseFloat()
console.log(parseInt("123px"));  // 123
console.log(parseInt("ff", 16)); // 255 (十六进制转换)
console.log(parseFloat("12.34em")); // 12.34

// 一元加运算符
console.log(+"123");            // 123
console.log(+"12.34");          // 12.34
console.log(+"abc");            // NaN
3. 转换为布尔值 (Boolean)
// 使用 Boolean() 函数
console.log(Boolean(0));         // false
console.log(Boolean(1));         // true
console.log(Boolean(-1));        // true
console.log(Boolean(""));        // false
console.log(Boolean("hello"));   // true
console.log(Boolean(null));      // false
console.log(Boolean(undefined)); // false
console.log(Boolean(NaN));       // false
console.log(Boolean({}));        // true
console.log(Boolean([]));        // true

// 双重否定运算符
console.log(!!0);               // false
console.log(!!1);                // true
console.log(!!"hello");          // true
隐式类型转换

JavaScript 在特定情况下会自动进行类型转换,这通常发生在运算符操作或比较时。

1. 字符串拼接 (+)
console.log(1 + "2");           // "12" (数字转为字符串)
console.log("3" + 4 + 5);       // "345"
console.log(3 + 4 + "5");       // "75" (先计算3+4=7,然后7+"5")
console.log(null + "text");     // "nulltext"
console.log(undefined + "text"); // "undefinedtext"
console.log({} + "text");       // "[object Object]text"
2. 数学运算 (-, *, /, %)
console.log("10" - 5);          // 5 (字符串转为数字)
console.log("10" * "2");        // 20
console.log("10" / "2");        // 5
console.log("10" % "3");        // 1
console.log("abc" - 5);         // NaN
console.log(null - 5);          // -5 (null转为0)
console.log(undefined - 5);     // NaN
console.log(true - false);      // 1 (true转为1, false转为0)
3. 相等比较 (==)
console.log(1 == "1");          // true (字符串转为数字)
console.log(true == 1);         // true (布尔值转为数字)
console.log(false == 0);        // true
console.log(null == undefined); // true (特殊情况)
console.log(null == 0);         // false (null不转为0)
console.log("" == 0);           // true (空字符串转为0)
console.log([] == 0);           // true (数组转为空字符串,再转为0)
console.log([] == "");          // true
console.log([1] == 1);          // true
console.log([1,2] == "1,2");    // true
4. 逻辑运算中的隐式转换
// if 条件中的隐式转换
if (0) console.log("不会执行");
if ("hello") console.log("会执行");
if (null) console.log("不会执行");
if (undefined) console.log("不会执行");
if ([]) console.log("会执行");
if ({}) console.log("会执行");

// 逻辑运算符
console.log(0 || "default");    // "default" (0转为false)
console.log("" || "default");    // "default"
console.log("text" || "default"); // "text"
console.log(1 && "text");       // "text" (1转为true)
特殊类型转换案例
1. 对象到原始值的转换
let obj = {
  value: 10,
  // 定义对象的 toString 方法
  toString() {
    return "Object with value: " + this.value;
  },
  // 定义对象的 valueOf 方法
  valueOf() {
    return this.value;
  }
};

console.log(String(obj));       // "Object with value: 10" (调用 toString)
console.log(Number(obj));       // 10 (调用 valueOf)
console.log(obj + 5);           // 15 (valueOf优先)
console.log(obj + "!");         // "10!" (valueOf优先)
2. 数组的隐式转换
console.log([] + []);           // "" (数组转为空字符串)
console.log([] + {});           // "[object Object]"
console.log({} + []);           // 0 (不同的解析方式)
console.log({} + {});           // "[object Object][object Object]"

console.log([1] + [2, 3]);      // "12,3" (数组转为字符串拼接)
console.log([1] - [2]);         // -1 (数组转为数字相减)
3. Date 对象的转换
let now = new Date();
console.log(String(now));        // "Mon Jan 01 2024 12:00:00 GMT+0800" (日期字符串)
console.log(Number(now));        // 1640995200000 (时间戳)
console.log(now + 1000);         // "Mon Jan 01 2024 12:00:00 GMT+08001000" (先转为字符串)
console.log(now - 1000);         // 1640995199000 (转为数字相减)
类型转换的最佳实践
  1. 使用严格相等 (===) 避免隐式转换带来的问题

    console.log(0 === false);     // false
    console.log("" === false);    // false
    console.log(null === undefined); // false
    
  2. 明确转换类型 使代码更易读

    // 不好的写法
    let num = +userInput;
    
    // 好的写法
    let num = Number(userInput);
    if (isNaN(num)) {
      console.log("请输入有效的数字");
    }
    
  3. 处理边界情况

    // 处理可能的 null/undefined
    let value = obj.prop || defaultValue;
    
    // ES2020 可选链操作符
    let value = obj?.prop ?? defaultValue;
    
  4. 避免混淆的类型转换

    // 避免这样的写法
    if (x == null) { /* 同时检查 null 和 undefined */ }
    
    // 更明确的写法
    if (x === null || x === undefined) {}
    

理解 JavaScript 的类型转换机制对于编写健壮的代码至关重要。显式转换通常比隐式转换更安全可靠,而了解隐式转换的规则可以帮助你避免常见的陷阱。

三、运算符详解:数据的加工工具

JavaScript 提供了丰富的运算符用于执行各种操作。下面我将分类介绍这些运算符,并通过代码示例解释它们的使用方法。

1. 算术运算符
运算符 名称 示例 结果 说明
+ 加法 3 + 5 8 数字相加或字符串连接
- 减法 10 - 3 7
* 乘法 4 * 6 24
/ 除法 12 / 3 4
% 取模 10 % 3 1 返回除法余数
** 指数 2 ** 3 8 ES2016新增 (2的3次方)
++ 自增 let a=2; a++ 3 后置返回原值,前置返回新值
-- 自减 let b=5; b-- 4 同上
// 加法特例:字符串连接
console.log("Hello" + " " + "World"); // "Hello World"
console.log(1 + "2"); // "12" (数字转为字符串)
console.log(1 + 2 + "3"); // "33" (先计算1+2,再连接"3")

// 自增运算符区别
let x = 1;
console.log(x++); // 1 (返回原值)
console.log(x);   // 2

let y = 1;
console.log(++y); // 2 (返回新值)
2. 赋值运算符
运算符 示例 等价于 说明
= x = 10 - 基本赋值
+= x += 5 x = x + 5 加后赋值
-= x -= 3 x = x - 3 减后赋值
*= x *= 2 x = x * 2 乘后赋值
/= x /= 4 x = x / 4 除后赋值
%= x %= 3 x = x % 3 取模后赋值
**= x **= 2 x = x ** 2 指数运算后赋值
let num = 10;
num += 5; // 等同于 num = num + 5
console.log(num); // 15

let str = "Hello";
str += " World"; // 字符串连接
console.log(str); // "Hello World"

// 链式赋值
let a, b, c;
a = b = c = 5; // 所有变量都赋值为5
3. 比较运算符
运算符 名称 示例 结果 说明
== 相等 5 == '5' true 值相等(会类型转换)
=== 严格相等 5 === '5' false 值和类型都相等
!= 不等 5 != '5' false 值不等(会类型转换)
!== 严格不等 5 !== '5' true 值或类型不等
> 大于 10 > 5 true
< 小于 10 < 5 false
>= 大于等于 10 >= 10 true
<= 小于等于 10 <= 5 false
// == 与 === 的区别
console.log(0 == false);   // true (0转为false)
console.log(0 === false);  // false (类型不同)

console.log(null == undefined);  // true (特殊情况)
console.log(null === undefined); // false

// 字符串比较(按字典序)
console.log("apple" > "banana"); // false ('a' < 'b')
console.log("10" > "2");        // false (字符串比较'1' < '2')

// 不同类型比较
console.log("10" > 5);         // true (字符串"10"转为数字10)
4. 逻辑运算符
运算符 名称 示例 结果 说明
&& true && false false 两者为真才为真
` ` `true
! !true false 取反
// 逻辑与(&&) - 返回第一个假值或最后一个值
console.log(1 && 2 && 3);      // 3 (全为真,返回最后一个)
console.log(1 && 0 && 2);      // 0 (返回第一个假值)

// 逻辑或(||) - 返回第一个真值或最后一个值
console.log(null || 0 || 1);   // 1 (返回第一个真值)
console.log(null || undefined || 0); // 0 (全为假,返回最后一个)

// 逻辑非(!) - 返回布尔值的反值
console.log(!true);            // false
console.log(!0);               // true (0转为false,取反为true)

// 短路求值应用
const value = someInput || "default"; // 设置默认值
isValid && doSomething();             // 条件执行
5. 位运算符
运算符 名称 示例 结果 说明
& 按位与 5 & 3 1 对应位都为1则为1
` ` 按位或 `5 3`
^ 按位异或 5 ^ 3 6 对应位不同则为1
~ 按位非 ~5 -6 按位取反
<< 左移 5 << 1 10 向左移动指定位数
>> 右移 -5 >> 1 -3 保留符号位的右移
>>> 无符号右移 -5 >>> 1 2147483645 不保留符号位的右移
// 按位与(&) - 判断奇偶
console.log(5 & 1); // 1 (奇数)
console.log(4 & 1); // 0 (偶数)

// 按位或(|) - 取整
console.log(3.14 | 0); // 3

// 左移(<<) - 快速乘以2的幂
console.log(7 << 2); // 28 (7 * 4)

// 右移(>>) - 快速除以2的幂
console.log(16 >> 1); // 8 (16/2)
console.log(-16 >> 1); // -8 (保留符号)

// 无符号右移(>>>)
console.log(-16 >>> 1); // 2147483640
6. 三元运算符

语法:条件 ? 表达式1 : 表达式2

// 基本用法
let age = 20;
let status = age >= 18 ? "成人" : "未成年";
console.log(status); // "成人"

// 嵌套使用
let score = 85;
let grade = score >= 90 ? "A" : 
            score >= 80 ? "B" :
            score >= 70 ? "C" : "D";
console.log(grade); // "B"

// 与赋值结合
let isMember = true;
let discount = isMember ? 0.1 : 0;
console.log(discount); // 0.1
7. 特殊运算符
7.1 空值合并运算符 (??) - ES2020
// 当左侧为null或undefined时返回右侧
console.log(null ?? "default"); // "default"
console.log(undefined ?? "default"); // "default"
console.log(0 ?? "default"); // 0
console.log("" ?? "default"); // ""

// 与||的区别
console.log(0 || "default"); // "default"
console.log("" || "default"); // "default"
7.2 可选链运算符 (?.) - ES2020
const user = {
  profile: {
    name: "Alice",
    address: {
      city: "Beijing"
    }
  }
};

// 传统方式
const city = user && user.profile && user.profile.address && user.profile.address.city;

// 可选链
const city = user?.profile?.address?.city;
console.log(city); // "Beijing"

// 访问不存在的属性
console.log(user?.profile?.job?.title); // undefined (不报错)

// 用于函数调用
const result = someObject.method?.(); // 方法不存在则不调用
7.3 展开运算符 (…) - ES2015
// 数组展开
const arr1 = [1, 2, 3];
const arr2 = [...arr1, 4, 5];
console.log(arr2); // [1, 2, 3, 4, 5]

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

// 函数参数展开
function sum(a, b, c) {
  return a + b + c;
}
const nums = [1, 2, 3];
console.log(sum(...nums)); // 6
7.4 逗号运算符
// 计算多个表达式,返回最后一个值
let a = (1, 2, 3);
console.log(a); // 3

// for循环中的常见用法
for(let i = 0, j = 10; i < j; i++, j--) {
  console.log(i, j);
}
8. 运算符优先级

优先级从高到低:

  1. () 分组
  2. ++ -- ! typeof ~ + - 一元运算符
  3. ** 指数
  4. * / % 乘除取模
  5. + - 加减
  6. << >> >>> 位移
  7. < <= > >= in instanceof 比较
  8. == != === !== 相等
  9. & 按位与
  10. ^ 按位异或
  11. | 按位或
  12. && 逻辑与
  13. || 逻辑或
  14. ?? 空值合并
  15. ?: 三元条件
  16. = 赋值
  17. , 逗号
// 优先级示例
console.log(3 + 4 * 5); // 23 (先乘后加)
console.log((3 + 4) * 5); // 35 (括号优先)

// 结合性示例
console.log(2 ** 3 ** 2); // 512 (右结合: 2^(3^2))
console.log((2 ** 3) ** 2); // 64 (左结合: (2^3)^2)

理解 JavaScript 运算符的优先级和结合性对于编写正确的表达式至关重要。当有疑问时,使用括号明确优先级可以使代码更清晰易读。

9. 关键运算符对比
运算符 名称 特点 示例 结果
** 指数 ES7新增 2 ** 3 8
=== 严格相等 类型和值都相同 10 === "10" false
!== 严格不等 类型或值不同 10 !== "10" true
?? 空值合并 ES2020新增,仅对null/undefined生效 null ?? 10 10
?. 可选链 ES2020新增,安全访问属性 user?.address undefined

四、流程控制深度解析:程序的决策系统

流程控制是编程语言中决定代码执行顺序的关键机制。JavaScript 提供了多种流程控制结构,每种结构都有其特定的使用场景和性能特点。

1. 条件语句详解
语句 适用场景 优点 缺点
if/else 范围判断、复杂条件 灵活,可读性好 多层嵌套时代码冗长
switch 离散值匹配 结构清晰 必须用break,仅限等值比较
三元 简单二选一 简洁 复杂逻辑可读性差
1.1 if/else 语句

基本语法:

if (条件1) {
    // 条件1为真时执行
} else if (条件2) {
    // 条件2为真时执行
} else {
    // 所有条件都不满足时执行
}

最佳实践:

  • 对于范围判断(如分数等级划分)特别有效
  • 可以处理复杂的逻辑表达式组合
  • 适合处理非离散值的条件判断

性能优化技巧:

// 将最可能成立的条件放在前面
if (mostLikelyCondition) {
    // 处理最常见情况
} else if (lessLikelyCondition) {
    // 处理次常见情况
} else {
    // 处理其他情况
}

// 避免深层嵌套 - 使用提前返回
function processValue(value) {
    if (!isValid(value)) return;  // 提前退出
    
    // 主逻辑处理
    // ...
}
1.2 switch 语句

基本语法:

switch (表达式) {
    case1:
        // 代码块
        break;
    case2:
        // 代码块
        break;
    default:
        // 默认代码块
}

特殊特性:

  • 使用严格比较(===)
  • case 穿透现象(不加break会继续执行下一个case)
  • 可以接受任何表达式作为判断条件

现代用法改进:

// 使用对象字面量替代复杂switch
const actions = {
    'add': (x, y) => x + y,
    'subtract': (x, y) => x - y,
    // ...
};

const result = actions[operation]?.(x, y) ?? defaultResult;

// 使用Map实现更灵活的switch
const actionMap = new Map([
    [/^test_/, handleTest],
    [/^dev_/, handleDev],
    // ...
]);

for (const [pattern, handler] of actionMap) {
    if (pattern.test(input)) {
        handler(input);
        break;
    }
}
1.3 三元运算符

高级用法示例:

// 多重嵌套(谨慎使用)
const message = score > 90 ? '优秀' :
               score > 80 ? '良好' :
               score > 60 ? '及格' : '不及格';

// 与函数调用结合
const getPrice = isMember ? getMemberPrice : getRegularPrice;
const finalPrice = getPrice(basePrice);

// 用于JSX/模板中的条件渲染
{isLoggedIn ? <UserPanel /> : <LoginButton />}

使用建议:

  • 适合简单的二选一场景
  • 嵌套不要超过两层
  • 当表达式过于复杂时改用if/else
2. 循环语句深度分析
2.1 传统for循环

性能优势原理:

  • 直接通过索引访问数组元素
  • 循环条件只计算一次数组长度(优化后)
  • 没有创建额外的函数作用域

优化技巧:

// 缓存数组长度
for (let i = 0, len = arr.length; i < len; i++) {
    // ...
}

// 倒序循环(某些引擎更快)
for (let i = arr.length - 1; i >= 0; i--) {
    // ...
}

// 使用位运算优化
for (let i = 0, len = arr.length; i < len; i += 1 | 0) {
    // ...
}
2.2 forEach方法

特点分析:

  • 接收一个回调函数作为参数
  • 无法使用break或return终止循环
  • 为每个元素创建新的函数执行上下文

高级用法:

// 使用第二个参数(this值)
arr.forEach(function(item) {
    this.handleItem(item);  // 使用绑定的this
}, someObject);

// 处理稀疏数组
const sparseArray = [1, , 3];
sparseArray.forEach(item => {
    console.log(item); // 1, 3(跳过空位)
});
2.3 for…of循环

特殊优势:

  • 可以遍历可迭代对象(Array, Map, Set, String等)
  • 直接获取值而非索引
  • 支持break和continue

扩展应用:

// 遍历Map
const map = new Map([['a', 1], ['b', 2]]);
for (const [key, value] of map) {
    console.log(key, value);
}

// 自定义迭代器
const customIterable = {
    [Symbol.iterator]() {
        let count = 0;
        return {
            next() {
                return { value: count++, done: count > 3 };
            }
        };
    }
};

for (const item of customIterable) {
    console.log(item); // 0, 1, 2
}
2.4 性能对比测试升级版
const SIZE = 1e6; // 100万
const arr = Array(SIZE).fill().map((_, i) => i);

function test(name, fn) {
    console.time(name);
    fn();
    console.timeEnd(name);
}

// 测试各种循环方式
test('for', () => {
    let sum = 0;
    for (let i = 0; i < arr.length; i++) {
        sum += arr[i];
    }
    return sum;
});

test('forEach', () => {
    let sum = 0;
    arr.forEach(n => { sum += n; });
    return sum;
});

test('for-of', () => {
    let sum = 0;
    for (const n of arr) {
        sum += n;
    }
    return sum;
});

test('reduce', () => {
    return arr.reduce((sum, n) => sum + n, 0);
});

// Node.js环境下测试while
test('while', () => {
    let sum = 0, i = 0;
    while (i < arr.length) {
        sum += arr[i++];
    }
    return sum;
});

典型结果(Chrome):

for: 5.2ms
while: 5.5ms 
forEach: 12.8ms
for-of: 25.4ms
reduce: 15.3ms
3. 循环控制进阶技巧
3.1 循环优化策略

减少循环内部计算

// 不佳实践
for (let i = 0; i < arr.length; i++) {
    const value = expensiveCalculation(arr[i]);
    // ...
}

// 优化后
const len = arr.length;
for (let i = 0; i < len; i++) {
    const value = cachedResults[i]; // 预先计算
    // ...
}

循环展开(Loop Unrolling)

// 传统循环
for (let i = 0; i < 8; i++) {
    doSomething(i);
}

// 展开后的循环(减少循环次数)
for (let i = 0; i < 8; i += 4) {
    doSomething(i);
    doSomething(i + 1);
    doSomething(i + 2);
    doSomething(i + 3);
}
3.2 特殊循环模式

基于Promise的异步循环

async function processArray(array) {
    // 顺序执行
    for (const item of array) {
        await processItem(item);
    }
    
    // 并行执行
    await Promise.all(array.map(item => processItem(item)));
}

生成器函数控制流程

function* idGenerator() {
    let id = 0;
    while (true) {
        const reset = yield id++;
        if (reset) id = 0;
    }
}

const gen = idGenerator();
console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next(true).value); // 0 (重置)
4. 流程控制现代实践
4.1 使用标签控制嵌套循环
outerLoop: 
for (let i = 0; i < 3; i++) {
    innerLoop:
    for (let j = 0; j < 3; j++) {
        if (i === 1 && j === 1) {
            break outerLoop; // 跳出外层循环
        }
        console.log(`i=${i}, j=${j}`);
    }
}
// 输出:
// i=0, j=0
// i=0, j=1
// i=0, j=2
// i=1, j=0
4.2 基于策略模式的流程控制
const strategies = {
    A: (input) => { /* 处理策略A */ },
    B: (input) => { /* 处理策略B */ },
    default: (input) => { /* 默认处理 */ }
};

function process(input, strategyType) {
    const strategy = strategies[strategyType] || strategies.default;
    return strategy(input);
}
4.3 使用函数式编程替代循环
// 传统循环方式
const results = [];
for (const item of items) {
    if (item.isValid) {
        results.push(processItem(item));
    }
}

// 函数式方式
const results = items
    .filter(item => item.isValid)
    .map(processItem);
5. 浏览器与Node.js环境差异
特性 浏览器环境 Node.js环境
事件循环 基于DOM事件和用户交互 基于文件I/O和网络请求
微任务优先级 渲染前执行 无渲染环节,立即执行
循环性能 受页面渲染影响 更稳定的性能表现
最大调用栈 受浏览器限制(约1万层) 通常更深(约2万层)
异步迭代器 较新浏览器支持 从v10开始完全支持

实践建议:

  • 在浏览器中避免长时间运行的循环(超过50ms),以免阻塞UI渲染
  • 在Node.js中可以更自由地使用CPU密集型循环
  • 对于大数据集处理,考虑使用Web Worker(浏览器)或Worker Threads(Node.js)

掌握这些流程控制的进阶技巧和性能优化方法,可以让你编写出更高效、更易维护的JavaScript代码。记住,没有绝对"最好"的控制结构,只有最适合特定场景的选择。

🔍 性能提示:大数据量时优先使用传统for循环,需要简洁时可选用forEach或for-of

五、错误处理:程序的免疫系统

错误处理金字塔模型
顶层
  try/catch/finally - 处理同步错误
  Promise.catch() - 处理异步错误
  window.onerror - 全局错误捕获
底层
常见错误类型
错误类型 触发场景 处理建议
ReferenceError 访问未声明变量 检查变量作用域
TypeError 错误类型操作 添加类型检查
SyntaxError 语法错误 使用ESLint等工具预防
RangeError 数值超出范围 添加边界检查
CustomError 自定义业务错误 继承Error类扩展
// 最佳实践示例
try {
  const data = JSON.parse(userInput);
  if(!data.age) throw new ValidationError("Age required");
} catch(err) {
  if(err instanceof SyntaxError) {
    console.log("Invalid JSON");
  } else if(err instanceof ValidationError) {
    console.log(err.message);
  } else {
    console.log("Unknown error");
  }
} finally {
  console.log("Cleanup");
}

掌握这些核心概念如同获得JavaScript的基因图谱,理解它们之间的关系和差异是成为高效开发者的关键。建议通过构建实际项目来深化理解,将抽象概念转化为具体经验。

JavaScript 函数详解

函数是 JavaScript 的核心概念之一,它允许我们将代码组织成可重用的块。以下是 JavaScript 函数的全面讲解:

1. 函数定义方式

1.1 函数声明
function greet(name) {
    return `Hello, ${name}!`;
}
console.log(greet("Alice")); // "Hello, Alice!"

特点
• 函数提升(可以在声明前调用)

• 有函数名标识符

• 适合需要多次调用的函数

1.2 函数表达式
const greet = function(name) {
    return `Hello, ${name}!`;
};
console.log(greet("Bob")); // "Hello, Bob!"

特点:
• 无函数提升(必须在定义后调用)

• 可以匿名,也可以命名

• 适合作为回调函数或立即执行函数

1.3 箭头函数 (ES6)
const greet = (name) => `Hello, ${name}!`;
console.log(greet("Charlie")); // "Hello, Charlie!"

// 多参数
const sum = (a, b) => a + b;

// 无参数
const sayHi = () => console.log("Hi!");

// 多行函数体
const calculate = (x, y) => {
    const result = x * y;
    return result + 10;
};

特点:
• 更简洁的语法

• 没有自己的 thisargumentssupernew.target

• 不能用作构造函数(不能用 new 调用)

• 适合简单的回调函数和函数式编程

2. 函数参数

2.1 默认参数 (ES6)
function greet(name = "Guest") {
    return `Hello, ${name}!`;
}
console.log(greet()); // "Hello, Guest!"
console.log(greet("Dave")); // "Hello, Dave!"
2.2 剩余参数 (Rest Parameters, ES6)
function sum(...numbers) {
    return numbers.reduce((total, num) => total + num, 0);
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(1, 2, 3, 4, 5)); // 15
2.3 参数解构
function printUser({name, age}) {
    console.log(`${name} is ${age} years old`);
}
printUser({name: "Eve", age: 28}); // "Eve is 28 years old"

function coordinates([x, y]) {
    console.log(`X: ${x}, Y: ${y}`);
}
coordinates([10, 20]); // "X: 10, Y: 20"

3. 返回值

所有函数都返回一个值:
• 使用 return 语句显式返回

• 无 return 语句时返回 undefined

function explicitReturn() {
    return "I'm returned";
}

function implicitReturn() {
    // 无return语句
}

console.log(explicitReturn()); // "I'm returned"
console.log(implicitReturn()); // undefined

4. 作用域与闭包

4.1 函数作用域
function outer() {
    const outerVar = "I'm outside!";
    
    function inner() {
        const innerVar = "I'm inside!";
        console.log(outerVar); // 可以访问外部变量
    }
    
    inner();
    // console.log(innerVar); // 报错,无法访问内部变量
}

outer();
4.2 闭包
function createCounter() {
    let count = 0;
    
    return function() {
        count++;
        console.log(count);
    };
}

const counter = createCounter();
counter(); // 1
counter(); // 2
counter(); // 3

闭包特点:
• 内部函数可以访问外部函数的变量

• 外部函数执行完毕后,其变量仍然被内部函数引用

• 常用于创建私有变量和数据封装

5. 高阶函数

接受函数作为参数或返回函数的函数:

// 接受函数作为参数
function repeat(n, action) {
    for (let i = 0; i < n; i++) {
        action(i);
    }
}

repeat(3, console.log); // 0 1 2

// 返回函数
function multiplyBy(factor) {
    return function(number) {
        return number * factor;
    };
}

const double = multiplyBy(2);
console.log(double(5)); // 10

6. 立即调用函数表达式 (IIFE)

(function() {
    console.log("This runs immediately");
})();

// 带参数的IIFE
(function(name) {
    console.log(`Hello, ${name}`);
})("Frank");

// 返回值的IIFE
const result = (function(a, b) {
    return a + b;
})(3, 4);
console.log(result); // 7

IIFE用途:
• 创建独立作用域,避免污染全局命名空间

• 封装私有变量

• 模块化代码

7. 递归函数

函数调用自身:

function factorial(n) {
    if (n <= 1) return 1;
    return n * factorial(n - 1);
}

console.log(factorial(5)); // 120 (5! = 5*4*3*2*1 = 120)

注意事项:
• 必须有终止条件(基线条件)

• 递归深度过大可能导致栈溢出

• 某些情况下可用尾调用优化(TCO)

8. 函数中的 this

this 的值取决于函数如何被调用:

const person = {
    name: "Grace",
    greet: function() {
        console.log(`Hello, I'm ${this.name}`);
    }
};

person.greet(); // "Hello, I'm Grace" (this指向person)

const greet = person.greet;
greet(); // "Hello, I'm undefined" (严格模式下this是undefined,非严格模式下是window)

// 使用bind、call、apply改变this
const boundGreet = person.greet.bind(person);
boundGreet(); // "Hello, I'm Grace"

person.greet.call({name: "Hank"}); // "Hello, I'm Hank"

箭头函数的 this 继承自外层作用域:

const obj = {
    name: "Ivy",
    regularFunc: function() {
        console.log(this.name); // "Ivy"
    },
    arrowFunc: () => {
        console.log(this.name); // undefined (取决于外层this)
    }
};

obj.regularFunc();
obj.arrowFunc();

9. 函数属性和方法

9.1 函数属性
function example() {}
example.customProperty = "I'm a property";
console.log(example.customProperty); // "I'm a property"
9.2 函数方法
function sayHello(greeting, name) {
    console.log(`${greeting}, ${name}!`);
}

// call - 立即调用,参数逐个传递
sayHello.call(null, "Hello", "Jack"); // "Hello, Jack!"

// apply - 立即调用,参数作为数组传递
sayHello.apply(null, ["Hi", "Jill"]); // "Hi, Jill!"

// bind - 返回新函数,绑定this和部分参数
const sayHi = sayHello.bind(null, "Hi");
sayHi("Kate"); // "Hi, Kate!"

10. 生成器函数 (ES6)

function* numberGenerator() {
    yield 1;
    yield 2;
    yield 3;
}

const gen = numberGenerator();
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3
console.log(gen.next().done); // true

生成器特点:
• 使用 function* 声明

• 通过 yield 暂停执行并返回值

• 通过 next() 恢复执行

• 用于实现迭代器和异步编程

11. 异步函数 (ES7)

async function fetchData() {
    try {
        const response = await fetch('https://api.example.com/data');
        const data = await response.json();
        console.log(data);
    } catch (error) {
        console.error("Error:", error);
    }
}

fetchData();

异步函数特点:
• 使用 async 声明

• 内部可以使用 await 等待 Promise 解决

• 总是返回 Promise

• 使异步代码看起来像同步代码

函数是 JavaScript 中最重要的概念之一,掌握各种函数类型和特性对于编写高效、可维护的代码至关重要。

JavaScript 对象与原型详解

1. 对象创建方式

1.1 对象字面量 (最常用方式)
// 基本对象字面量
const person = {
    name: '张三',
    age: 30,
    greet: function() {
        console.log(`你好,我是${this.name}`);
    }
};

// 访问属性
console.log(person.name); // "张三"
person.greet(); // "你好,我是张三"

// ES6增强的对象字面量
const name = '李四';
const age = 25;

const person2 = {
    name,  // 属性简写
    age,
    greet() {  // 方法简写
        console.log(`你好,我是${this.name}`);
    },
    [ 'prop_' + (() => 42)() ]: '动态属性' // 计算属性名
};

console.log(person2.prop_42); // "动态属性"

特点:
• 最简洁直观的创建方式

• 适合创建单个对象

• 无法复用对象结构

1.2 构造函数模式
// 构造函数
function Person(name, age) {
    // 实例属性
    this.name = name;
    this.age = age;
    
    // 实例方法(不推荐,每个实例都会创建新函数)
    this.greet = function() {
        console.log(`你好,我是${this.name}`);
    };
}

// 原型方法(推荐)
Person.prototype.sayAge = function() {
    console.log(`我今年${this.age}`);
};

// 创建实例
const person1 = new Person('王五', 28);
const person2 = new Person('赵六', 35);

person1.greet();  // "你好,我是王五"
person2.sayAge(); // "我今年35岁"

// 检查原型关系
console.log(person1 instanceof Person); // true
console.log(person1.__proto__ === Person.prototype); // true

特点:
• 使用 new 关键字调用

• 函数名通常大写开头(约定)

• 可以创建多个相似对象

• 属性和方法可以被共享(通过原型)

1.3 Object.create() 方法
// 原型对象
const personProto = {
    greet() {
        console.log(`你好,我是${this.name}`);
    }
};

// 创建新对象,指定原型
const person = Object.create(personProto);
person.name = '钱七';
person.age = 40;

person.greet(); // "你好,我是钱七"

// 检查原型关系
console.log(Object.getPrototypeOf(person) === personProto); // true

// 带属性描述符的创建
const person2 = Object.create(personProto, {
    name: {
        value: '孙八',
        writable: true,
        enumerable: true,
        configurable: true
    },
    age: {
        value: 45,
        enumerable: true
    }
});

console.log(person2.name); // "孙八"

特点:
• 直接指定原型对象

• 可以精细控制属性特性

• 适合实现纯净的继承

• 可以创建没有原型的对象:Object.create(null)

2. 原型链机制

2.1 原型链示例
function Animal(name) {
    this.name = name;
}

Animal.prototype.eat = function() {
    console.log(`${this.name}在吃东西`);
};

function Dog(name, breed) {
    Animal.call(this, name); // 调用父类构造函数
    this.breed = breed;
}

// 设置原型链
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog; // 修复constructor

Dog.prototype.bark = function() {
    console.log(`${this.name}在汪汪叫`);
};

const myDog = new Dog('阿黄', '金毛');
myDog.eat();  // "阿黄在吃东西"
myDog.bark(); // "阿黄在汪汪叫"

// 原型链关系
console.log(myDog instanceof Dog);    // true
console.log(myDog instanceof Animal); // true
console.log(myDog.__proto__ === Dog.prototype); // true
console.log(Dog.prototype.__proto__ === Animal.prototype); // true
2.2 原型链图示
myDog → Dog.prototype → Animal.prototype → Object.prototype → null
2.3 属性查找机制

当访问对象属性时:

  1. 先在对象自身属性中查找
  2. 如果没找到,沿着原型链向上查找
  3. 直到找到属性或到达原型链末端(null)
function Parent() {}
Parent.prototype.value = 42;

function Child() {}
Child.prototype = new Parent();

const obj = new Child();

console.log(obj.value); // 42 (通过原型链找到)

3. ES6 Class 语法糖

class Animal {
    constructor(name) {
        this.name = name;
    }
    
    eat() {
        console.log(`${this.name}在吃东西`);
    }
}

class Dog extends Animal {
    constructor(name, breed) {
        super(name); // 调用父类构造函数
        this.breed = breed;
    }
    
    bark() {
        console.log(`${this.name}在汪汪叫`);
    }
    
    // 静态方法
    static info() {
        console.log('这是Dog类');
    }
}

const myDog = new Dog('小黑', '泰迪');
myDog.eat();  // "小黑在吃东西"
myDog.bark(); // "小黑在汪汪叫"
Dog.info();   // "这是Dog类"

// 检查原型关系
console.log(myDog instanceof Dog);    // true
console.log(myDog instanceof Animal); // true

特点:
• 本质仍是基于原型的继承

• 语法更接近传统面向对象语言

• 更清晰的继承语法

• 支持静态方法和getter/setter

4. 对象属性描述符

const obj = {};

Object.defineProperty(obj, 'name', {
    value: '张三',
    writable: false,      // 不可修改
    enumerable: true,     // 可枚举
    configurable: false   // 不可删除且不可重新配置
});

console.log(obj.name); // "张三"
obj.name = '李四';     // 静默失败(严格模式下报错)
console.log(obj.name); // "张三"

// 定义多个属性
Object.defineProperties(obj, {
    age: {
        value: 30,
        enumerable: true
    },
    id: {
        value: '12345',
        enumerable: false // 不可枚举
    }
});

// 获取属性描述符
console.log(Object.getOwnPropertyDescriptor(obj, 'name'));

// 获取所有自身属性(包括不可枚举)
console.log(Object.getOwnPropertyNames(obj)); // ["name", "age", "id"]

// 只获取可枚举属性
console.log(Object.keys(obj)); // ["name", "age"]

属性描述符类型:
• 数据描述符:value, writable, enumerable, configurable

• 访问器描述符:get, set, enumerable, configurable

5. 对象常用方法

5.1 对象合并
const target = { a: 1, b: 2 };
const source = { b: 4, c: 5 };

// 浅合并
const result = Object.assign({}, target, source);
console.log(result); // { a: 1, b: 4, c: 5 }

// ES2018扩展运算符
const merged = { ...target, ...source };
console.log(merged); // { a: 1, b: 4, c: 5 }
5.2 对象遍历
const person = {
    name: '王五',
    age: 30,
    job: '工程师'
};

// for...in (包含原型链上的可枚举属性)
for (let key in person) {
    if (person.hasOwnProperty(key)) {
        console.log(key, person[key]);
    }
}

// Object.keys() (仅自身可枚举属性)
Object.keys(person).forEach(key => {
    console.log(key, person[key]);
});

// Object.entries() (ES2017)
for (const [key, value] of Object.entries(person)) {
    console.log(key, value);
}
5.3 其他实用方法
const obj = { a: 1, b: 2, c: 3 };

// 冻结对象(不能修改、添加、删除属性)
Object.freeze(obj);
obj.a = 10; // 静默失败(严格模式下报错)

// 密封对象(不能添加/删除属性,但可修改现有属性)
Object.seal(obj);

// 防止扩展(不能添加新属性)
Object.preventExtensions(obj);

// 检查对象状态
console.log(Object.isFrozen(obj));
console.log(Object.isSealed(obj));
console.log(Object.isExtensible(obj));

6. 原型相关方法

function Person() {}
Person.prototype.name = '原型上的名字';

const p = new Person();

// 检查原型关系
console.log(Person.prototype.isPrototypeOf(p)); // true

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

// 设置原型对象(谨慎使用,性能影响)
const newProto = { name: '新原型' };
Object.setPrototypeOf(p, newProto);

// 检查属性来源
console.log(p.hasOwnProperty('name')); // false
console.log('name' in p);             // true

7. 最佳实践建议

  1. 对象创建选择:
    • 简单对象 → 对象字面量

    • 需要多个相似实例 → 构造函数或class

​ • 需要精细控制继承 → Object.create()

  1. 原型使用建议:
    • 方法放在原型上共享

    • 属性放在实例上

    • 避免过度长的原型链(影响性能)

  2. 现代JavaScript:
    • 优先使用class语法

    • 使用Object.assign或扩展运算符进行对象合并

    • 使用Object.freeze()保护重要对象

JavaScript 数组与迭代详解

一、基本数组方法

  1. push/pop
    push(): 在数组末尾添加一个或多个元素,返回新数组长度
let arr = [1, 2];
arr.push(3); // arr变为[1, 2, 3],返回3
arr.push(4, 5); // arr变为[1, 2, 3, 4, 5],返回5

​ •pop(): 移除并返回数组的最后一个元素

let last = arr.pop(); // last = 5, arr变为[1, 2, 3, 4]
  1. shift/unshift
    shift(): 移除并返回数组的第一个元素
let first = arr.shift(); // first = 1, arr变为[2, 3, 4]

​ • unshift(): 在数组开头添加一个或多个元素,返回新数组长度

arr.unshift(0); // arr变为[0, 2, 3, 4],返回4
arr.unshift(-2, -1); // arr变为[-2, -1, 0, 2, 3, 4],返回6
  1. slice/splice
    slice(start, end): 返回数组的浅拷贝部分,不修改原数组
let arr = [1, 2, 3, 4, 5];
let sub = arr.slice(1, 4); // sub = [2, 3, 4], arr不变
let copy = arr.slice(); // 浅拷贝整个数组

​ • splice(start, deleteCount, ...items): 修改数组,删除/替换/添加元素

let arr = [1, 2, 3, 4, 5];
let removed = arr.splice(1, 2); // removed = [2, 3], arr变为[1, 4, 5]
arr.splice(1, 0, 'a', 'b'); // arr变为[1, 'a', 'b', 4, 5]
arr.splice(2, 1, 'c'); // arr变为[1, 'a', 'c', 4, 5]

二、高阶数组方法

  1. map
    创建一个新数组,其结果是该数组中的每个元素调用提供的函数后的返回值
let numbers = [1, 2, 3];
let squares = numbers.map(x => x * x); // [1, 4, 9]
  1. filter
    创建一个新数组,包含通过测试的所有元素
let numbers = [1, 2, 3, 4, 5];
let evens = numbers.filter(x => x % 2 === 0); // [2, 4]
  1. reduce
    对数组中的每个元素执行一个reducer函数,将其结果汇总为单个返回值
let numbers = [1, 2, 3, 4];
let sum = numbers.reduce((acc, curr) => acc + curr, 0); // 10
let max = numbers.reduce((a, b) => Math.max(a, b)); // 4
  1. forEach
    对数组的每个元素执行提供的函数,没有返回值
let colors = ['red', 'green', 'blue'];
colors.forEach(color => console.log(color));

三、数组解构

从数组中提取值并赋值给变量

let [a, b] = [1, 2]; // a=1, b=2
let [first, , third] = [1, 2, 3]; // first=1, third=3
let [head, ...tail] = [1, 2, 3, 4]; // head=1, tail=[2, 3, 4]

// 默认值
let [x = 1, y = 2] = [5]; // x=5, y=2

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

四、迭代器与生成器

  1. 迭代器
    实现了迭代器协议的对象,必须有一个next()方法
let iterable = {
  [Symbol.iterator]() {
    let step = 0;
    return {
      next() {
        step++;
        if (step <= 3) {
          return { value: step, done: false };
        }
        return { value: undefined, done: true };
      }
    };
  }
};

for (let value of iterable) {
  console.log(value); // 1, 2, 3
}
  1. 生成器
    使用function*定义的函数,返回一个生成器对象
function* idGenerator() {
  let id = 1;
  while (true) {
    yield id++;
  }
}

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

五、Set 和 Map 数据结构

  1. Set
    存储唯一值的集合
let set = new Set();
set.add(1).add(2).add(1); // Set {1, 2}
console.log(set.has(1)); // true
set.delete(1); // Set {2}
set.clear(); // Set {}

// 数组去重
let unique = [...new Set([1, 2, 2, 3])]; // [1, 2, 3]
  1. Map
    键值对集合,键可以是任意值
let map = new Map();
map.set('name', 'John');
map.set(1, 'number one');
map.set({}, 'object key');

console.log(map.get('name')); // 'John'
console.log(map.size); // 3
map.delete('name');
map.has('name'); // false

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

六、ArrayBuffer 和类型化数组

  1. ArrayBuffer
    表示通用的、固定长度的原始二进制数据缓冲区
let buffer = new ArrayBuffer(16); // 创建16字节的缓冲区
console.log(buffer.byteLength); // 16
  1. 类型化数组
    提供对二进制数据的结构化访问
// 创建一个16字节的缓冲区
let buffer = new ArrayBuffer(16);

// 创建一个视图,将缓冲区视为32位有符号整数数组
let int32View = new Int32Array(buffer);

// 填充数据
for (let i = 0; i < int32View.length; i++) {
  int32View[i] = i * 2;
}

// 创建另一个视图,将同一缓冲区视为16位无符号整数数组
let uint16View = new Uint16Array(buffer);

// 现在缓冲区内容为: [0x00000000, 0x00000002, 0x00000004, 0x00000006]
// 或 [0, 0, 0, 0, 2, 0, 0, 0, 4, 0, 0, 0, 6, 0, 0, 0]
  1. DataView
    提供更灵活的方式来读写缓冲区中的数据
let buffer = new ArrayBuffer(16);
let view = new DataView(buffer);

view.setInt32(0, 42); // 在偏移量0处写入32位整数
view.setFloat64(4, Math.PI); // 在偏移量4处写入64位浮点数

console.log(view.getInt32(0)); // 42
console.log(view.getFloat64(4)); // 3.141592653589793

这些是JavaScript中数组和迭代相关的主要概念和API。掌握这些知识可以帮助你更高效地处理数据集合和实现复杂的迭代逻辑。

JavaScript 异步编程详解

一、回调函数(Callback)

回调函数是异步编程的基础模式,将函数作为参数传递给另一个函数,在特定条件满足时被调用。

基本用法
function fetchData(callback) {
  setTimeout(() => {
    const data = { id: 1, name: 'John' };
    callback(null, data); // 第一个参数通常用于错误
  }, 1000);
}

fetchData((err, data) => {
  if (err) {
    console.error('Error:', err);
  } else {
    console.log('Data:', data);
  }
});
回调地狱问题

多层嵌套回调会导致代码难以维护:

getData(function(a) {
  getMoreData(a, function(b) {
    getMoreData(b, function(c) {
      // ...
    });
  });
});

二、Promise

Promise 是异步编程的一种解决方案,代表一个未来才会知道结果的值。

基本用法
const promise = new Promise((resolve, reject) => {
  setTimeout(() => {
    const success = Math.random() > 0.5;
    if (success) {
      resolve('Operation succeeded');
    } else {
      reject(new Error('Operation failed'));
    }
  }, 1000);
});

promise
  .then(result => console.log(result))
  .catch(error => console.error(error));
Promise 方法
  1. then() - 处理成功状态

    promise.then(
      result => console.log('Success:', result),
      error => console.error('Error:', error)
    );
    
  2. catch() - 处理拒绝状态

    promise
      .then(result => console.log(result))
      .catch(error => console.error(error));
    
  3. finally() - 无论成功或失败都会执行

    promise
      .then(result => console.log(result))
      .catch(error => console.error(error))
      .finally(() => console.log('Operation completed'));
    
  4. Promise.all() - 等待所有Promise完成

    Promise.all([promise1, promise2, promise3])
      .then(results => console.log(results))
      .catch(error => console.error(error));
    
  5. Promise.race() - 取最先完成的Promise结果

    Promise.race([promise1, promise2])
      .then(result => console.log(result))
      .catch(error => console.error(error));
    
  6. Promise.allSettled() - 等待所有Promise完成(无论成功或失败)

    Promise.allSettled([promise1, promise2])
      .then(results => results.forEach(result => console.log(result.status)));
    
  7. Promise.any() - 取第一个成功的Promise结果

    Promise.any([promise1, promise2])
      .then(result => console.log(result))
      .catch(errors => console.error(errors));
    

三、async/await

async/await 是基于Promise的语法糖,使异步代码看起来像同步代码。

基本用法
async function fetchData() {
  try {
    const response = await fetch('https://api.example.com/data');
    const data = await response.json();
    console.log(data);
    return data;
  } catch (error) {
    console.error('Error:', error);
    throw error;
  }
}

fetchData().then(data => console.log('Final data:', data));
并行执行
async function fetchAll() {
  const [data1, data2] = await Promise.all([
    fetchData1(),
    fetchData2()
  ]);
  console.log(data1, data2);
}

四、事件循环机制

JavaScript 是单线程语言,通过事件循环机制处理异步操作。

事件循环流程
  1. 执行同步代码(调用栈)
  2. 遇到异步操作,交给Web API处理(如setTimeout、fetch等)
  3. Web API完成后,将回调放入任务队列
  4. 调用栈为空时,事件循环从任务队列取出回调执行
示例
console.log('Start');

setTimeout(() => console.log('Timeout'), 0);

Promise.resolve().then(() => console.log('Promise'));

console.log('End');

// 输出顺序:
// Start
// End
// Promise
// Timeout

五、微任务与宏任务

宏任务(Macrotasks)
• setTimeout

• setInterval

• setImmediate (Node.js)

• I/O 操作

• UI渲染

微任务(Microtasks)
• Promise.then/catch/finally

• process.nextTick (Node.js)

• MutationObserver

执行顺序

  1. 执行一个宏任务(如script整体代码)
  2. 执行所有微任务
  3. 渲染UI(如有必要)
  4. 执行下一个宏任务
console.log('script start');

setTimeout(function() {
  console.log('setTimeout');
}, 0);

Promise.resolve().then(function() {
  console.log('promise1');
}).then(function() {
  console.log('promise2');
});

console.log('script end');

// 输出顺序:
// script start
// script end
// promise1
// promise2
// setTimeout

六、Web Workers

Web Workers 允许在后台线程中运行脚本,不阻塞主线程。

基本用法

主线程代码:

const worker = new Worker('worker.js');

worker.postMessage('Hello Worker');

worker.onmessage = function(e) {
  console.log('Message from Worker:', e.data);
};

worker.onerror = function(e) {
  console.error('Error in Worker:', e);
};

worker.js:

self.onmessage = function(e) {
  console.log('Message from Main:', e.data);
  
  // 执行耗时操作
  const result = doHeavyCalculation();
  
  self.postMessage(result);
};

function doHeavyCalculation() {
  // 模拟耗时计算
  let sum = 0;
  for (let i = 0; i < 1000000000; i++) {
    sum += i;
  }
  return sum;
}
注意事项
  1. Worker 运行在另一个全局上下文中,无法访问DOM
  2. 与主线程通信通过消息传递(postMessage)
  3. Worker 中可以导入其他脚本:
    importScripts('script1.js', 'script2.js');
    
  4. 可以创建专用Worker和共享Worker
终止Worker
worker.terminate(); // 主线程中调用
// 或
self.close(); // Worker内部调用

异步编程总结

JavaScript 异步编程经历了从回调函数到 Promise 再到 async/await 的演进,使得异步代码越来越易于编写和维护。理解事件循环机制和微任务/宏任务的区别对于编写高效的异步代码至关重要。对于计算密集型任务,Web Workers 提供了在后台线程中运行代码的能力,避免阻塞主线程。

JavaScript 模块化详解

一、模块化发展背景

在早期 JavaScript 中,所有代码都运行在全局作用域,导致以下问题:
• 命名冲突

• 依赖管理困难

• 代码难以维护和复用

模块化解决方案应运而生,主要分为:

  1. CommonJS - Node.js 采用的模块系统
  2. AMD/CMD - 浏览器端的异步模块定义(RequireJS/SeaJS)
  3. ES6 Modules - JavaScript 官方标准模块系统
  4. UMD - 通用模块定义,兼容多种环境

二、CommonJS 模块

1. 基本语法
// math.js
function add(a, b) {
  return a + b;
}
function subtract(a, b) {
  return a - b;
}

module.exports = {
  add,
  subtract
};

// 或单独导出
exports.add = add;
exports.subtract = subtract;
// main.js
const math = require('./math.js');

console.log(math.add(2, 3)); // 5
console.log(math.subtract(5, 2)); // 3
2. 特点

• 同步加载 - 适合服务器端(Node.js)

• 运行时加载 - 模块在运行时确定依赖关系

• 缓存机制 - 模块首次加载后会被缓存

• module.exports vs exports - exports 只是 module.exports 的引用

三、ES6 模块 (ECMAScript Modules)

1. 基本语法
// math.js
export function add(a, b) {
  return a + b;
}

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

// 或统一导出
export { add, subtract };

// 默认导出
export default function multiply(a, b) {
  return a * b;
}
// main.js
import { add, subtract } from './math.js';
import multiply from './math.js'; // 默认导入

console.log(add(2, 3)); // 5
console.log(subtract(5, 2)); // 3
console.log(multiply(2, 3)); // 6
2. 特点

• 静态分析 - 编译时就能确定模块依赖关系

• 异步加载 - 适合浏览器环境

• 严格模式 - 模块默认在严格模式下执行

• 实时绑定 - 导入的值是动态绑定的,不是拷贝

3. 导入导出方式

• 命名导出/导入

// 导出
export const name = 'circle';
export function area(r) { return Math.PI * r * r; }

// 导入
import { name, area } from './circle.js';

• 默认导出/导入

// 导出
export default class Circle { /* ... */ }

// 导入
import Circle from './circle.js';

• 重命名

import { add as addNumbers } from './math.js';
export { add as addNumbers };

• 整体导入

import * as math from './math.js';
console.log(math.add(2, 3));

四、模块加载器与打包工具

1. Webpack
核心概念:

• 入口(entry) - 应用程序的起点

• 输出(output) - 打包后的文件配置

• 加载器(loader) - 处理非JS文件

• 插件(plugins) - 执行更广泛的任务

基本配置:
// webpack.config.js
const path = require('path');

module.exports = {
  entry: './src/index.js',
  output: {
    filename: 'bundle.js',
    path: path.resolve(__dirname, 'dist')
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader'
        }
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      }
    ]
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: './src/index.html'
    })
  ]
};
2. Rollup
更适合库的打包,特点:

• Tree-shaking - 更高效的死代码消除

• ES模块优先 - 原生支持ES模块

• 更小的包体积

基本配置:
// rollup.config.js
import resolve from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
import babel from '@rollup/plugin-babel';

export default {
  input: 'src/main.js',
  output: {
    file: 'bundle.js',
    format: 'umd',
    name: 'MyLibrary'
  },
  plugins: [
    resolve(),
    commonjs(),
    babel({ babelHelpers: 'bundled' })
  ]
};
3. 其他工具

• Parcel - 零配置打包工具

• Vite - 基于原生ES模块的开发服务器

• Snowpack - 现代前端构建工具

五、动态导入

1. import() 函数

ES2020引入的动态导入语法,返回一个Promise:

// 按需加载模块
button.addEventListener('click', async () => {
  const module = await import('./module.js');
  module.doSomething();
});

// 条件加载
if (featureEnabled) {
  import('./feature.js').then(module => {
    module.init();
  });
}
2. Webpack代码分割
// 使用魔法注释
import(/* webpackChunkName: "lodash" */ 'lodash')
  .then(({ default: _ }) => {
    console.log(_.VERSION);
  });
3. React中的动态导入
const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <OtherComponent />
      </Suspense>
    </div>
  );
}

六、模块系统对比

特性 CommonJS ES Modules
加载方式 同步 异步
适用环境 服务器端(Node.js) 浏览器/Node.js
导出方式 module.exports/exports export/export default
导入方式 require() import
静态分析 不支持 支持
循环依赖处理 支持 支持
实时绑定 值拷贝 动态绑定
顶层this 当前模块 undefined

七、Node.js中的模块系统

Node.js目前支持:

  1. CommonJS - 默认模块系统

  2. ES Modules - 需要满足以下条件之一:
    • 文件扩展名为.mjs

    • 最近的package.json中有"type": "module"

    • 使用--input-type=module标志

混合使用:

// 在ES模块中引入CommonJS模块
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const fs = require('fs');

// 在CommonJS模块中使用ES模块(异步)
import('es-module.mjs').then(module => {
  // 使用模块
});

八、最佳实践

  1. 浏览器环境:
    • 使用ES Modules作为首选

    • 结合打包工具如Webpack/Rollup

    • 利用动态导入实现代码分割

  2. Node.js环境:
    • 新项目推荐使用ES Modules

    • 已有项目继续使用CommonJS

    • 注意文件扩展名和package.json配置

  3. 跨环境模块:
    • 使用UMD格式发布库

    • 在package.json中指定多个入口:

    {
      "main": "dist/commonjs.js",
      "module": "dist/esm.js",
      "browser": "dist/umd.js"
    }
    
  4. 性能优化:
    • 合理使用代码分割

    • 利用Tree-shaking减少包体积

    • 预加载关键资源

    <link rel="modulepreload" href="critical-module.js">
    

模块化是现代JavaScript开发的基石,理解不同模块系统的特性和适用场景,能够帮助开发者构建更可维护、高效的应用。

DOM 操作全面详解

下面我将通过代码示例、表格和文字描述相结合的方式,全面讲解DOM操作的核心知识点。

1. DOM 树结构

基本概念

DOM (Document Object Model) 是HTML和XML文档的编程接口,它将文档表示为节点树结构。

<!DOCTYPE html>
<html>
<head>
    <title>DOM示例</title>
</head>
<body>
    <div id="container">
        <h1>标题</h1>
        <p class="content">段落内容</p>
    </div>
</body>
</html>

对应的DOM树结构:

节点类型 节点名称 层级关系
文档节点 #document 根节点
元素节点 html document的子节点
元素节点 head html的子节点
元素节点 title head的子节点
文本节点 “DOM示例” title的子节点
元素节点 body html的子节点
元素节点 div body的子节点
元素节点 h1 div的子节点
文本节点 “标题” h1的子节点
元素节点 p div的子节点
文本节点 “段落内容” p的子节点

2. 节点选择与遍历

选择节点代码示例
// 通过ID选择
const container = document.getElementById('container');

// 通过类名选择
const contents = document.getElementsByClassName('content');

// 通过标签名选择
const paragraphs = document.getElementsByTagName('p');

// 通过CSS选择器选择
const firstPara = document.querySelector('.content');
const allParas = document.querySelectorAll('p');

// 选择表单元素
const form = document.forms[0];  // 获取第一个表单
const input = form.elements['username'];  // 获取name为username的输入框
节点遍历代码示例
const div = document.querySelector('#container');

// 父节点
console.log(div.parentNode);  // 返回body元素

// 子节点
console.log(div.childNodes);  // 包含所有节点(包括文本节点)
console.log(div.children);   // 只包含元素节点

// 第一个和最后一个子节点
console.log(div.firstChild);  // 可能是文本节点
console.log(div.firstElementChild);  // h1元素
console.log(div.lastChild);  // 可能是文本节点
console.log(div.lastElementChild);  // p元素

// 兄弟节点
const h1 = document.querySelector('h1');
console.log(h1.nextSibling);  // 可能是文本节点
console.log(h1.nextElementSibling);  // p元素
console.log(h1.previousSibling);  // 可能是文本节点
console.log(h1.previousElementSibling);  // null(没有前面的兄弟元素)

3. 元素创建、修改与删除

创建与添加元素
// 创建新元素
const newDiv = document.createElement('div');
newDiv.textContent = '我是新创建的div';

// 添加类名
newDiv.classList.add('new', 'highlight');

// 设置属性
newDiv.setAttribute('id', 'newDiv');

// 添加到DOM中
document.body.appendChild(newDiv);

// 在特定位置插入
const referenceNode = document.querySelector('#container');
document.body.insertBefore(newDiv, referenceNode);

// 克隆节点
const clonedDiv = newDiv.cloneNode(true);
document.body.appendChild(clonedDiv);
修改与删除元素
// 修改内容
const p = document.querySelector('p');
p.innerHTML = '<strong>修改后</strong>的内容';
p.textContent = '纯文本内容';  // 会转义HTML标签

// 修改样式
p.style.color = 'red';
p.style.fontSize = '20px';

// 修改类
p.classList.add('active');
p.classList.remove('content');
p.classList.toggle('visible');

// 删除元素
const parent = p.parentNode;
parent.removeChild(p);  // 传统方法
p.remove();  // 现代方法(较新浏览器支持)

4. 事件处理

基本事件处理
const button = document.querySelector('button');

// 添加事件监听器
button.addEventListener('click', function(event) {
    console.log('按钮被点击了!');
    console.log('事件类型:', event.type);
    console.log('触发元素:', event.target);
    console.log('当前元素:', event.currentTarget);
});

// 移除事件监听器
const handler = function() {
    console.log('这个只会执行一次');
    button.removeEventListener('click', handler);
};
button.addEventListener('click', handler);

// 阻止默认行为
const link = document.querySelector('a');
link.addEventListener('click', function(e) {
    e.preventDefault();
    console.log('链接点击被阻止了');
});
事件冒泡与捕获
<div id="outer" style="padding: 20px; background: lightblue;">
    <div id="inner" style="padding: 20px; background: lightgreen;">
        <button id="btn">点击我</button>
    </div>
</div>
const outer = document.getElementById('outer');
const inner = document.getElementById('inner');
const btn = document.getElementById('btn');

// 捕获阶段(从外向内)
outer.addEventListener('click', function() {
    console.log('outer - 捕获阶段');
}, true);

inner.addEventListener('click', function() {
    console.log('inner - 捕获阶段');
}, true);

// 冒泡阶段(从内向外)
btn.addEventListener('click', function() {
    console.log('btn - 冒泡阶段');
});

inner.addEventListener('click', function() {
    console.log('inner - 冒泡阶段');
});

outer.addEventListener('click', function() {
    console.log('outer - 冒泡阶段');
});

// 点击按钮后的输出顺序:
// outer - 捕获阶段
// inner - 捕获阶段
// btn - 冒泡阶段
// inner - 冒泡阶段
// outer - 冒泡阶段
停止事件传播
inner.addEventListener('click', function(e) {
    console.log('inner点击,但不会冒泡到outer');
    e.stopPropagation();  // 阻止事件继续传播
});

// 事件委托示例见下一节

5. 事件委托

事件委托是利用事件冒泡机制,在父元素上处理子元素的事件。
<ul id="todo-list">
    <li>任务1 <button class="delete">删除</button></li>
    <li>任务2 <button class="delete">删除</button></li>
    <li>任务3 <button class="delete">删除</button></li>
</ul>
<button id="add-task">添加任务</button>
const list = document.getElementById('todo-list');
const addBtn = document.getElementById('add-task');

// 传统方式(为每个按钮单独添加事件)
// const deleteBtns = document.querySelectorAll('.delete');
// deleteBtns.forEach(btn => {
//     btn.addEventListener('click', function() {
//         this.parentNode.remove();
//     });
// });

// 事件委托方式(只需一个事件监听器)
list.addEventListener('click', function(e) {
    if (e.target.classList.contains('delete')) {
        e.target.parentNode.remove();
    }
});

// 添加新任务(动态添加的元素也能自动处理事件)
addBtn.addEventListener('click', function() {
    const newItem = document.createElement('li');
    const taskCount = list.children.length + 1;
    newItem.innerHTML = `新任务${taskCount} <button class="delete">删除</button>`;
    list.appendChild(newItem);
});
事件委托的优点
优点 说明
减少内存使用 只需一个事件处理程序,而不是为每个子元素都添加
动态元素支持 后添加的子元素自动具有事件处理能力
代码简洁 减少重复代码,逻辑集中管理

6. 表单操作

表单基础操作
<form id="myForm">
    <input type="text" name="username" placeholder="用户名" required>
    <input type="email" name="email" placeholder="邮箱">
    <input type="password" name="password" placeholder="密码" minlength="6">
    <input type="checkbox" name="subscribe" checked> 订阅 newsletter
    <select name="country">
        <option value="">选择国家</option>
        <option value="us">美国</option>
        <option value="cn" selected>中国</option>
        <option value="jp">日本</option>
    </select>
    <button type="submit">提交</button>
</form>
const form = document.getElementById('myForm');

// 获取表单元素
const username = form.elements.username;
const email = form.elements.email;
const subscribe = form.elements.subscribe;
const country = form.elements.country;

// 表单提交事件
form.addEventListener('submit', function(e) {
    e.preventDefault();  // 阻止默认提交行为
    
    // 表单验证
    if (!username.value) {
        alert('用户名不能为空');
        username.focus();
        return;
    }
    
    if (!form.checkValidity()) {
        alert('请填写所有必填字段');
        return;
    }
    
    // 收集表单数据
    const formData = {
        username: username.value,
        email: email.value,
        subscribe: subscribe.checked,
        country: country.value
    };
    
    console.log('表单数据:', formData);
    // 这里通常会发送AJAX请求到服务器
    
    // 重置表单
    // form.reset();
});

// 输入验证
email.addEventListener('input', function() {
    if (!email.validity.valid) {
        email.setCustomValidity('请输入有效的邮箱地址');
    } else {
        email.setCustomValidity('');
    }
});

// 选择框变化事件
country.addEventListener('change', function() {
    console.log('选择的国家:', this.value);
});

// 禁用/启用表单元素
// username.disabled = true;
表单验证方法
验证方法 描述 示例
required 必填字段 <input required>
minlength/maxlength 最小/最大长度 <input minlength="6">
min/max 数值范围 <input type="number" min="1" max="100">
pattern 正则表达式验证 <input pattern="[A-Za-z]{3}">
checkValidity() 检查表单有效性 form.checkValidity()
setCustomValidity() 设置自定义验证消息 input.setCustomValidity('错误消息')
表单数据收集

现代浏览器提供了FormData API来简化表单数据收集:

form.addEventListener('submit', function(e) {
    e.preventDefault();
    
    const formData = new FormData(form);
    
    // 获取单个值
    console.log('用户名:', formData.get('username'));
    
    // 遍历所有值
    for (let [name, value] of formData) {
        console.log(`${name}: ${value}`);
    }
    
    // 发送AJAX请求
    fetch('/api/submit', {
        method: 'POST',
        body: formData
    }).then(response => {
        // 处理响应
    });
});

通过以上代码示例、表格和文字说明的结合,你应该能够全面理解DOM操作的各个方面。记住,DOM操作是前端开发的基础,熟练掌握这些知识对构建交互式网页至关重要。

BOM 操作全面详解

BOM (Browser Object Model) 是浏览器对象模型,提供了与浏览器窗口交互的对象和方法。下面通过代码示例、表格和文字描述详细讲解BOM的核心知识点。

1. window 对象

基本概念

window对象是BOM的核心,代表浏览器窗口,也是全局对象。

// 全局变量实际上是window对象的属性
var globalVar = '全局变量';
console.log(window.globalVar); // "全局变量"

// 全局函数也是window对象的方法
function globalFunc() {
    console.log('全局函数');
}
window.globalFunc(); // "全局函数"
window对象常用属性和方法
属性/方法 描述 示例
window.innerWidth 窗口内部宽度 console.log(window.innerWidth)
window.innerHeight 窗口内部高度 console.log(window.innerHeight)
window.open() 打开新窗口 window.open('https://example.com')
window.close() 关闭当前窗口 window.close()
window.scrollTo() 滚动到指定位置 window.scrollTo(0, 100)
window.alert() 显示警告框 window.alert('提示信息')
window.confirm() 显示确认框 if(confirm('确定吗?')) {...}
window.prompt() 显示输入框 const name = prompt('请输入姓名')

2. location, history, navigator 对象

location 对象
// 获取当前URL信息
console.log(location.href);     // 完整URL
console.log(location.protocol); // 协议 (http: 或 https:)
console.log(location.host);     // 主机名和端口
console.log(location.hostname); // 主机名
console.log(location.port);     // 端口
console.log(location.pathname); // 路径部分
console.log(location.search);   // 查询字符串
console.log(location.hash);     // 锚点部分

// 页面跳转
location.href = 'https://example.com'; // 跳转到新URL
location.assign('https://example.com'); // 同href
location.replace('https://example.com'); // 替换当前页面(不保留历史记录)
location.reload(); // 重新加载页面
history 对象
// 导航历史
history.back();     // 等同于点击后退按钮
history.forward(); // 等同于点击前进按钮
history.go(-2);    // 后退2页
history.go(1);     // 前进1页

// 添加历史记录
history.pushState({page: 1}, "title 1", "?page=1"); // 添加新历史记录
history.replaceState({page: 2}, "title 2", "?page=2"); // 替换当前历史记录

// 监听popstate事件
window.addEventListener('popstate', function(event) {
    console.log('位置变化:', event.state);
});
navigator 对象
// 浏览器信息
console.log(navigator.userAgent); // 用户代理字符串
console.log(navigator.platform);  // 操作系统平台
console.log(navigator.language);  // 浏览器语言

// 功能检测
console.log('在线状态:', navigator.onLine);
console.log('Cookie启用:', navigator.cookieEnabled);
console.log('地理位置支持:', 'geolocation' in navigator);

// 地理位置API
if ('geolocation' in navigator) {
    navigator.geolocation.getCurrentPosition(
        position => {
            console.log('纬度:', position.coords.latitude);
            console.log('经度:', position.coords.longitude);
        },
        error => {
            console.error('获取位置失败:', error.message);
        }
    );
}

3. 定时器:setTimeout, setInterval

setTimeout
// 基本用法
const timeoutId = setTimeout(() => {
    console.log('2秒后执行');
}, 2000);

// 清除定时器
clearTimeout(timeoutId);

// 带参数的setTimeout
setTimeout((name, age) => {
    console.log(`Hello ${name}, 你${age}岁了`);
}, 1000, '张三', 25);
setInterval
// 基本用法
let count = 0;
const intervalId = setInterval(() => {
    count++;
    console.log(`${count}次执行`);
    if (count >= 5) {
        clearInterval(intervalId);
        console.log('定时器停止');
    }
}, 1000);

// 动画示例
let pos = 0;
const box = document.createElement('div');
box.style.width = '50px';
box.style.height = '50px';
box.style.backgroundColor = 'red';
box.style.position = 'absolute';
document.body.appendChild(box);

const moveBox = setInterval(() => {
    pos += 5;
    box.style.left = pos + 'px';
    if (pos >= 300) {
        clearInterval(moveBox);
    }
}, 30);
requestAnimationFrame
// 更流畅的动画替代setInterval
let start = null;
const element = document.getElementById('animated-element');

function step(timestamp) {
    if (!start) start = timestamp;
    const progress = timestamp - start;
    
    element.style.transform = `translateX(${Math.min(progress / 10, 200)}px)`;
    
    if (progress < 2000) {
        window.requestAnimationFrame(step);
    }
}

window.requestAnimationFrame(step);

// 性能对比
/*
setInterval/setTimeout缺点:
1. 执行时间不精确
2. 可能造成丢帧
3. 当标签页不活跃时仍会执行

requestAnimationFrame优点:
1. 浏览器优化,更流畅的动画
2. 标签页不活跃时自动暂停
3. 与屏幕刷新率同步
*/

4. 本地存储

Cookie 知识点详解
1. 基本概念

Cookie 是浏览器存储的小型文本数据(通常不超过 4KB),由服务器通过 Set-Cookie 响应头发送到客户端,浏览器会将其保存并在后续请求中自动携带。主要用于会话管理、个性化设置和用户跟踪。


2. Cookie 核心属性
属性 描述 示例值
Name/Value Cookie 的名称和值,需进行编码/解码处理。 username=John%20Doe
Expires 绝对过期时间(GMT 格式),未设置则为会话级 Cookie(关闭浏览器失效)。 Expires=Wed, 21 Oct 2025 07:28:00 GMT
Max-Age 相对过期时间(秒),优先级高于 Expires Max-Age=3600(1 小时后过期)
Domain 指定哪些域名可接收 Cookie,默认当前域名(不包含子域名)。 Domain=example.com(允许子域名访问)
Path 指定路径下的请求携带 Cookie,默认当前路径。 Path=/docs(仅 /docs 路径下生效)
Secure 仅通过 HTTPS 协议传输 Cookie。 Secure
HttpOnly 禁止 JavaScript 访问 Cookie,防止 XSS 攻击。 HttpOnly
SameSite 控制跨站请求是否发送 Cookie(Strict/Lax/None)。 SameSite=Lax

3. JavaScript 操作 Cookie
3.1 读取 Cookie
// 获取所有 Cookie(返回字符串)
const allCookies = document.cookie; // "name=value; name2=value2"

// 解析特定 Cookie 的值
function getCookie(name) {
  const cookies = document.cookie.split('; ');
  const cookie = cookies.find(c => c.startsWith(`${name}=`));
  return cookie ? decodeURIComponent(cookie.split('=')[1]) : null;
}

console.log(getCookie('username')); // 输出 "John Doe"
3.2 设置 Cookie
function setCookie(name, value, days = 7, path = '/') {
  const date = new Date();
  date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
  const expires = `expires=${date.toUTCString()}`;
  const encodedValue = encodeURIComponent(value);
  document.cookie = `${name}=${encodedValue}; ${expires}; path=${path}; SameSite=Lax`;
}

setCookie('theme', 'dark', 30); // 保存 30 天
3.3 删除 Cookie
function deleteCookie(name, path = '/') {
  document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=${path}`;
}

deleteCookie('theme'); // 立即过期

4. 安全与最佳实践
场景 解决方案
敏感数据存储 避免在 Cookie 中存储密码等敏感信息,优先使用服务器端会话(如 JWT)。
XSS 防护 设置 HttpOnly 阻止 JS 访问 Cookie。
CSRF 防护 结合 SameSite=Lax 或 CSRF Token。
安全传输 生产环境使用 Secure 确保仅 HTTPS 传输。
编码处理 使用 encodeURIComponent 对值编码,避免特殊字符(如分号、逗号)破坏格式。

5. Cookie 的限制与替代方案
特性 Cookie localStorage sessionStorage
存储大小 4KB 左右 5MB 或更大 5MB 或更大
生命周期 可设置过期时间或会话级 永久存储(需手动删除) 会话级(标签页关闭失效)
自动携带 每次请求自动携带 不自动携带 不自动携带
访问权限 前后端均可访问(受属性限制) 仅前端访问 仅前端访问

6. 示例:封装 Cookie 工具函数
const CookieUtil = {
  set(name, value, attributes = {}) {
    const { days = 7, path = '/', domain, secure, httpOnly } = attributes;
    let cookieText = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
    
    if (typeof days === 'number') {
      const date = new Date();
      date.setTime(date.getTime() + days * 24 * 60 * 60 * 1000);
      cookieText += `; expires=${date.toUTCString()}`;
    }
    
    if (path) cookieText += `; path=${path}`;
    if (domain) cookieText += `; domain=${domain}`;
    if (secure) cookieText += '; secure';
    if (httpOnly) cookieText += '; HttpOnly';
    
    document.cookie = cookieText;
  },
  
  get(name) {
    const cookie = document.cookie
      .split('; ')
      .find(row => row.startsWith(`${encodeURIComponent(name)}=`));
    return cookie ? decodeURIComponent(cookie.split('=')[1]) : null;
  },
  
  delete(name, path = '/') {
    this.set(name, '', { days: -1, path });
  }
};

// 使用示例
CookieUtil.set('language', 'zh-CN', { days: 365, httpOnly: true });
console.log(CookieUtil.get('language')); // "zh-CN"
CookieUtil.delete('language');

总结

Cookie 是早期且广泛使用的客户端存储方案,适合小规模、需自动携带的数据(如会话 ID)。但在现代开发中,需注意其安全性和存储限制,结合 localStoragesessionStorageIndexedDB 等替代方案灵活选择。

localStorage 和 sessionStorage 知识点详解

JavaScript 本地存储:localStoragesessionStorage 知识点详解


1. 核心概念对比
特性 localStorage sessionStorage
生命周期 永久存储(除非手动删除或清除浏览器数据) 会话级存储(标签页关闭后自动删除)
作用域 同源(协议+域名+端口)下的所有标签页共享 仅限当前标签页(同源不同标签页不共享)
存储大小 通常 5~10MB(不同浏览器有差异) localStorage
自动携带 不自动携带到 HTTP 请求 不自动携带
适用场景 长期保存用户偏好、缓存数据 临时保存表单数据、页面会话状态

2. 核心 API 方法
方法 作用 示例代码
setItem(key, value) 存储数据(键值对) localStorage.setItem('theme', 'dark')
getItem(key) 读取数据(返回字符串或 null const theme = localStorage.getItem('theme')
removeItem(key) 删除指定键的数据 localStorage.removeItem('theme')
clear() 清空所有数据 localStorage.clear()
key(index) 获取第 N 个键名(按插入顺序) localStorage.key(0)
length 获取存储项的总数(只读属性) localStorage.length

3. 基本使用示例
// 存储数据(自动转为字符串)
localStorage.setItem('username', 'Alice');
sessionStorage.setItem('token', 'abc123');

// 读取数据(需自行转换类型)
const user = localStorage.getItem('username'); // "Alice"
const token = sessionStorage.getItem('token'); // "abc123"

// 存储对象(需序列化)
const settings = { darkMode: true, fontSize: 16 };
localStorage.setItem('settings', JSON.stringify(settings));

// 读取对象(需反序列化)
const savedSettings = JSON.parse(localStorage.getItem('settings'));

// 删除数据
localStorage.removeItem('username');

// 清空所有数据
sessionStorage.clear();

4. 高级用法与注意事项
4.1 存储事件监听

当 同源其他标签页 修改 localStorage 时,可监听 storage 事件实现跨页通信:

window.addEventListener('storage', (event) => {
  console.log('Key changed:', event.key);
  console.log('Old value:', event.oldValue);
  console.log('New value:', event.newValue);
  console.log('触发页面URL:', event.url);
});
4.2 数据类型的限制

• 存储的值 只能是字符串。存储对象需用 JSON.stringify,读取时用 JSON.parse

• 以下值会静默失败:

localStorage.setItem('NaN', NaN);          // 存为 "NaN"
localStorage.setItem('infinity', Infinity) // 存为 "Infinity"
localStorage.setItem('obj', { name: 'Bob' }); // 存为 "[object Object]"
4.3 性能与安全
注意事项 解决方案
频繁读写影响性能 避免存储过大或频繁更新的数据,优先使用内存变量
XSS 攻击风险 永远不要存储敏感信息(如密码、令牌)
隐私模式兼容性 私有浏览模式下可能无法使用,需用 try-catch 包裹操作
同源策略 不同源的页面无法互相访问存储数据

5. 封装工具函数
const Storage = {
  // 通用存储方法
  set(key, value, isSession = false) {
    const storage = isSession ? sessionStorage : localStorage;
    try {
      storage.setItem(key, JSON.stringify(value));
    } catch (e) {
      console.error('存储失败:', e);
    }
  },

  // 通用读取方法
  get(key, isSession = false) {
    const storage = isSession ? sessionStorage : localStorage;
    try {
      const value = storage.getItem(key);
      return value ? JSON.parse(value) : null;
    } catch (e) {
      console.error('读取失败:', e);
      return null;
    }
  },

  // 删除指定键
  remove(key, isSession = false) {
    const storage = isSession ? sessionStorage : localStorage;
    storage.removeItem(key);
  }
};

// 使用示例
Storage.set('user', { name: 'Bob', age: 25 }); // 默认存到 localStorage
const user = Storage.get('user'); // { name: 'Bob', age: 25 }

Storage.set('tempData', [1,2,3], true); // 存到 sessionStorage

6. 应用场景对比
场景 localStorage sessionStorage
用户主题偏好 ✅ 长期保存 ❌ 临时数据
购物车数据 ✅ 跨页面共享 ❌ 单页使用
表单草稿 ✅ 关闭浏览器后仍保留 ✅ 临时保存(标签页关闭前有效)
单页应用(SPA)路由状态 ❌ 可能不需要 ✅ 当前会话有效

总结

• 优先选择 Web Storage:与 Cookie 相比,localStoragesessionStorage 有更大的存储空间且不自动携带到服务器。

• 数据安全:避免存储敏感信息,必要时加密数据。

• 替代方案:对于更复杂的数据结构或更大存储需求,考虑使用 IndexedDB

// 快速判断浏览器支持
if (typeof localStorage !== 'undefined') {
  console.log('支持 localStorage');
}
if (typeof sessionStorage !== 'undefined') {
  console.log('支持 sessionStorage');
}
三种存储方式对比
特性 cookie localStorage sessionStorage
容量 4KB左右 5MB或更大 5MB或更大
生命周期 可设置过期时间 永久存储 会话结束清除
是否随请求发送
访问范围 同源窗口 同源窗口 仅当前窗口
API易用性 较差 简单 简单
适用场景 身份验证 持久化数据 临时会话数据

5. IndexedDB

IndexedDB是一种底层API,用于在客户端存储大量结构化数据。

基本操作
// 打开或创建数据库
const request = indexedDB.open('myDatabase', 1);

request.onerror = function(event) {
    console.error('数据库打开失败:', event.target.error);
};

request.onsuccess = function(event) {
    const db = event.target.result;
    console.log('数据库打开成功');
    
    // 添加数据
    const transaction = db.transaction(['customers'], 'readwrite');
    const store = transaction.objectStore('customers');
    
    const customer = { id: 1, name: 'John', email: 'john@example.com' };
    const addRequest = store.add(customer);
    
    addRequest.onsuccess = function() {
        console.log('数据添加成功');
    };
    
    addRequest.onerror = function(event) {
        console.error('添加数据失败:', event.target.error);
    };
};

request.onupgradeneeded = function(event) {
    const db = event.target.result;
    
    // 创建对象存储空间(表)
    const store = db.createObjectStore('customers', { keyPath: 'id' });
    
    // 创建索引
    store.createIndex('name', 'name', { unique: false });
    store.createIndex('email', 'email', { unique: true });
    
    console.log('数据库结构创建/升级完成');
};
数据操作
// 查询数据
function getCustomer(db, id) {
    const transaction = db.transaction(['customers']);
    const store = transaction.objectStore('customers');
    const request = store.get(id);
    
    request.onsuccess = function() {
        console.log('查询结果:', request.result);
    };
    
    request.onerror = function(event) {
        console.error('查询失败:', event.target.error);
    };
}

// 更新数据
function updateCustomer(db, customer) {
    const transaction = db.transaction(['customers'], 'readwrite');
    const store = transaction.objectStore('customers');
    const request = store.put(customer);
    
    request.onsuccess = function() {
        console.log('数据更新成功');
    };
    
    request.onerror = function(event) {
        console.error('更新失败:', event.target.error);
    };
}

// 删除数据
function deleteCustomer(db, id) {
    const transaction = db.transaction(['customers'], 'readwrite');
    const store = transaction.objectStore('customers');
    const request = store.delete(id);
    
    request.onsuccess = function() {
        console.log('数据删除成功');
    };
    
    request.onerror = function(event) {
        console.error('删除失败:', event.target.error);
    };
}

// 使用索引查询
function getCustomerByName(db, name) {
    const transaction = db.transaction(['customers']);
    const store = transaction.objectStore('customers');
    const index = store.index('name');
    const request = index.getAll(name);
    
    request.onsuccess = function() {
        console.log('查询结果:', request.result);
    };
}
IndexedDB 主要概念
概念 描述
数据库(Database) 最高层级容器,每个源可以有多个数据库
对象存储(Object Store) 类似关系型数据库中的表
事务(Transaction) 所有操作必须在事务中进行,保证原子性
索引(Index) 提供对对象存储中数据的快速查找
游标(Cursor) 用于遍历对象存储中的多条记录
键路径(Key Path) 指定对象中作为键的属性
键生成器(Key Generator) 自动生成键值
完整示例
<!DOCTYPE html>
<html>
<head>
    <title>IndexedDB示例</title>
</head>
<body>
    <h1>IndexedDB客户管理系统</h1>
    <div>
        <input type="text" id="name" placeholder="姓名">
        <input type="email" id="email" placeholder="邮箱">
        <button id="add">添加客户</button>
    </div>
    <ul id="customerList"></ul>

    <script>
        let db;
        const request = indexedDB.open('CustomerDB', 1);

        request.onerror = function(event) {
            console.error('数据库打开失败:', event.target.error);
        };

        request.onsuccess = function(event) {
            db = event.target.result;
            console.log('数据库打开成功');
            displayCustomers();
        };

        request.onupgradeneeded = function(event) {
            const db = event.target.result;
            const store = db.createObjectStore('customers', { 
                keyPath: 'id',
                autoIncrement: true 
            });
            store.createIndex('name', 'name', { unique: false });
            store.createIndex('email', 'email', { unique: true });
            console.log('数据库结构创建完成');
        };

        document.getElementById('add').addEventListener('click', addCustomer);

        function addCustomer() {
            const name = document.getElementById('name').value;
            const email = document.getElementById('email').value;
            
            if (!name || !email) {
                alert('请输入姓名和邮箱');
                return;
            }

            const transaction = db.transaction(['customers'], 'readwrite');
            const store = transaction.objectStore('customers');
            const customer = { name, email };
            
            const request = store.add(customer);
            
            request.onsuccess = function() {
                document.getElementById('name').value = '';
                document.getElementById('email').value = '';
                displayCustomers();
            };
            
            request.onerror = function(event) {
                if (event.target.error.name === 'ConstraintError') {
                    alert('该邮箱已存在');
                } else {
                    console.error('添加失败:', event.target.error);
                }
            };
        }

        function displayCustomers() {
            const transaction = db.transaction(['customers']);
            const store = transaction.objectStore('customers');
            const request = store.getAll();
            
            request.onsuccess = function() {
                const customers = request.result;
                const list = document.getElementById('customerList');
                list.innerHTML = '';
                
                customers.forEach(customer => {
                    const li = document.createElement('li');
                    li.textContent = `${customer.name} - ${customer.email}`;
                    
                    const deleteBtn = document.createElement('button');
                    deleteBtn.textContent = '删除';
                    deleteBtn.onclick = () => deleteCustomer(customer.id);
                    
                    li.appendChild(deleteBtn);
                    list.appendChild(li);
                });
            };
        }

        function deleteCustomer(id) {
            const transaction = db.transaction(['customers'], 'readwrite');
            const store = transaction.objectStore('customers');
            const request = store.delete(id);
            
            request.onsuccess = function() {
                displayCustomers();
            };
        }
    </script>
</body>
</html>

通过以上代码示例、表格和详细说明,你应该能够全面理解BOM操作和客户端存储的各种技术。这些知识对于开发复杂的前端应用至关重要。

ES6+ 新特性全面详解

下面我将通过代码示例、表格对比和文字说明,全面讲解ES6及后续版本中的重要新特性。

1. 解构赋值

数组解构
// 基本解构
const [a, b] = [1, 2];
console.log(a, b); // 1 2

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

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

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

// 交换变量
let m = 1, n = 2;
[m, n] = [n, m];
console.log(m, n); // 2 1
对象解构
// 基本解构
const {name, age} = {name: 'John', age: 30};
console.log(name, age); // John 30

// 重命名
const {name: personName} = {name: 'Alice'};
console.log(personName); // Alice

// 默认值
const {country = 'China'} = {name: 'Bob'};
console.log(country); // China

// 嵌套解构
const { 
    info: { address: { city } } 
} = { 
    info: { 
        address: { 
            city: 'Beijing' 
        } 
    } 
};
console.log(city); // Beijing

// 函数参数解构
function greet({name, age = 20}) {
    console.log(`Hello ${name}, you are ${age}`);
}
greet({name: 'John'}); // Hello John, you are 20
解构赋值对比表
特性 数组解构 对象解构
基本语法 [a, b] = [1, 2] {a, b} = {a: 1, b: 2}
默认值 [a=1] = [] {a=1} = {}
重命名 不支持 {a: newName} = {a: 1}
嵌套解构 支持 支持
剩余元素 ...rest ...rest
主要用途 交换变量、函数返回多个值 提取对象属性、函数参数

2. 模板字符串

// 基本用法
const name = 'John';
const greeting = `Hello, ${name}!`;
console.log(greeting); // Hello, John!

// 多行字符串
const multiLine = `
  This is 
  a multi-line
  string
`;
console.log(multiLine);

// 表达式计算
const a = 5, b = 10;
console.log(`The sum is ${a + b}`); // The sum is 15

// 标签模板
function highlight(strings, ...values) {
    return strings.reduce((result, str, i) => 
        `${result}${str}<span class="highlight">${values[i] || ''}</span>`, '');
}

const name = 'John';
const age = 30;
const html = highlight`Hello ${name}, you are ${age}`;
console.log(html); // "Hello <span...>John</span>, you are <span...>30</span>"
模板字符串 vs 普通字符串
特性 模板字符串 普通字符串
语法 反引号 ` 单/双引号 ' "
多行支持 原生支持 需要换行符 \n
变量嵌入 ${expression} 需要拼接 +
标签功能 支持 不支持
转义字符 支持 支持

3. 扩展运算符 (…)

数组中的扩展运算符
// 复制数组
const arr1 = [1, 2, 3];
const arr2 = [...arr1]; // [1, 2, 3]

// 合并数组
const arr3 = [...arr1, ...arr2, 4, 5]; // [1, 2, 3, 1, 2, 3, 4, 5]

// 函数参数
function sum(a, b, c) {
    return a + b + c;
}
const numbers = [1, 2, 3];
console.log(sum(...numbers)); // 6

// 替代apply
Math.max.apply(null, [1, 2, 3]); // ES5
Math.max(...[1, 2, 3]); // ES6+
对象中的扩展运算符
// 复制对象
const obj1 = {a: 1, b: 2};
const obj2 = {...obj1}; // {a: 1, b: 2}

// 合并对象
const obj3 = {...obj1, c: 3, b: 4}; // {a: 1, b: 4, c: 3}

// 默认值
const defaults = {theme: 'light', fontSize: 16};
const userSettings = {fontSize: 18};
const finalSettings = {...defaults, ...userSettings}; // {theme: 'light', fontSize: 18}
扩展运算符应用场景
场景 示例
数组复制 const copy = [...original]
数组连接 [...arr1, ...arr2]
函数调用 fn(...args)
对象复制 const clone = {...obj}
对象合并 {...obj1, ...obj2}
转换可迭代对象 [...document.querySelectorAll('div')]

4. 对象字面量增强

// 属性简写
const name = 'John';
const person = { name }; // { name: 'John' }

// 方法简写
const obj = {
    // ES5
    sayHello: function() {},
    // ES6+
    sayHi() {}
};

// 计算属性名
const prop = 'age';
const person = {
    name: 'John',
    [prop]: 30,
    [`get${prop}`]() { return this[prop]; }
};
console.log(person.getage()); // 30

// Symbol作为属性名
const id = Symbol('id');
const user = {
    [id]: 123,
    name: 'John'
};
console.log(user[id]); // 123
对象字面量新旧对比
特性 ES5 ES6+
属性定义 {x: x, y: y} {x, y}
方法定义 {method: function(){}} {method() {}}
动态属性名 不支持 {[expression]: value}
Symbol属性 不支持 {[symbol]: value}

5. Symbol 类型

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

// Symbol是唯一的
console.log(Symbol() === Symbol()); // false

// 用作对象属性
const obj = {
    [Symbol('id')]: 123,
    name: 'John'
};
console.log(obj[Symbol('id')]); // undefined (因为每次创建的Symbol都不同)

// 全局Symbol注册表
const globalSym = Symbol.for('global');
console.log(Symbol.for('global') === globalSym); // true

// 获取Symbol描述
console.log(Symbol.keyFor(globalSym)); // "global"
console.log(sym2.description); // "description"

// 内置Symbol
const arr = [1, 2, 3];
arr[Symbol.iterator]().next(); // {value: 1, done: false}
Symbol 特点总结
特性 说明
唯一性 每个Symbol都是唯一的
不可枚举 不会出现在for...inObject.keys()
描述可选 可以添加描述文本用于调试
全局注册 可通过Symbol.for()创建全局共享Symbol
内置Symbol 用于定义对象的内置行为,如Symbol.iterator

6. Proxy 和 Reflect

Proxy 示例

Proxy 是 ES6 提供的 对象代理 机制,用于拦截并自定义对象的基本操作(如属性读取、赋值、函数调用等)。
​核心术语​​:

  • 目标对象 (Target):被代理的原始对象
  • 处理器对象 (Handler):定义拦截行为的对象(包含各种 陷阱方法
  • 陷阱 (Trap):拦截操作的函数(如 getset
// 定义一个目标对象,包含 name 和 age 属性
const target = {
    name: 'John',
    age: 30
};

// 定义一个处理器对象,包含代理对象的拦截方法
const handler = {
    // 拦截对象的属性访问操作
    get(target, prop, receiver) {
        // 如果访问的属性是 'age',则返回一个自定义的字符串
        if (prop === 'age') {
            return `Age is ${target[prop]}`;
        }
        // 对于其他属性,使用 Reflect.get 调用默认行为
        // Reflect.get 会根据目标对象的原型链查找属性
        return Reflect.get(...arguments);
    },
    // 拦截对象的属性赋值操作
    set(target, prop, value, receiver) {
        // 如果设置的属性是 'age',并且值不是数字类型,则抛出错误
        if (prop === 'age' && typeof value !== 'number') {
            throw new Error('Age must be a number');
        }
        // 如果值合法,则使用 Reflect.set 完成赋值操作
        // Reflect.set 会考虑目标对象的 setter 和原型链
        return Reflect.set(...arguments);
    },
    // 拦截 in 操作符,用于检查属性是否存在
    has(target, prop) {
        // 只允许 'name' 和 'age' 属性存在
        return ['name', 'age'].includes(prop);
    }
};

// 创建一个代理对象,绑定目标对象和处理器
const proxy = new Proxy(target, handler);

// 测试代理对象的行为
console.log(proxy.name); // "John",直接访问 name 属性,未被拦截
console.log(proxy.age);  // "Age is 30",访问 age 属性时,被拦截并返回自定义字符串
console.log('age' in proxy); // true,拦截 in 操作符,'age' 在允许的属性列表中
console.log('gender' in proxy); // false,拦截 in 操作符,'gender' 不在允许的属性列表中

// 测试属性赋值操作
proxy.age = 31; // OK,值是数字,赋值成功
proxy.age = '31'; // Error: Age must be a number,值不是数字,抛出错误
常见陷阱方法(Traps)一览
陷阱方法 拦截的操作场景 必须返回值
get(target, prop) 读取属性(如 proxy.name 任意类型
set(target, prop, value) 设置属性(如 proxy.age = 25 布尔值(表示是否成功)
has(target, prop) prop in proxy 操作 布尔值
deleteProperty(target, prop) delete proxy.prop 布尔值
apply(target, thisArg, args) 函数调用(当目标为函数时) 任意类型
更多陷阱construct, ownKeys, getPrototypeOf

Reflect 示例
const obj = { x: 1, y: 2 };

// 替代Object方法
Reflect.get(obj, 'x'); // 1
Reflect.set(obj, 'z', 3); // true, obj变为 {x: 1, y: 2, z: 3}
Reflect.has(obj, 'y'); // true
Reflect.deleteProperty(obj, 'y'); // true, obj变为 {x: 1, z: 3}

// 函数调用
Reflect.apply(Math.max, null, [1, 2, 3]); // 3

// 构造函数调用
class Person {
    constructor(name) {
        this.name = name;
    }
}
const p = Reflect.construct(Person, ['John']);
console.log(p.name); // "John"
Proxy 和 Reflect 对比
特性 Proxy Reflect
用途 创建对象的代理 提供操作对象的方法
拦截操作 可以拦截13种基本操作 不拦截,只是方法集合
构造函数 new Proxy(target, handler) 不能new,直接调用静态方法
与Object方法关系 无直接关系 对应Object上的方法
典型应用 数据验证、日志记录、自动填充 与Proxy配合使用

7. 可选链操作符 (?.)

const user = {
    profile: {
        name: 'John',
        address: {
            city: 'New York'
        }
    }
};

// 传统方式
const city = user && user.profile && user.profile.address && user.profile.address.city;

// 可选链
const city = user?.profile?.address?.city;
console.log(city); // "New York"

// 数组和函数调用
const arr = null;
console.log(arr?.[0]); // undefined

const func = null;
console.log(func?.()); // undefined

// 与空值合并运算符结合
const name = user?.profile?.name ?? 'Anonymous';
console.log(name); // "John" 或 "Anonymous"
可选链使用场景
场景 示例
深层属性访问 obj?.prop?.subProp
数组项访问 arr?.[index]
函数调用 func?.()
方法调用 obj.method?.()
配合空值合并 value ?? defaultValue

8. 空值合并运算符 (??)

// 与||的区别
const count = 0;
console.log(count || 10); // 10 (0是falsy)
console.log(count ?? 10); // 0 (只有null/undefined会触发)

const text = '';
console.log(text || 'Default'); // 'Default'
console.log(text ?? 'Default'); // ''

const value = null;
console.log(value ?? 'fallback'); // 'fallback'

// 实际应用
function connect(options) {
    const host = options.host ?? 'localhost';
    const port = options.port ?? 8080;
    console.log(`Connecting to ${host}:${port}`);
}

connect({host: null, port: 0}); // "Connecting to localhost:0"
?? 与 || 对比

| 值 | a ?? b | a || b |
| ----------- | -------- | -------- |
| undefined | b | b |
| null | b | b |
| 0 | 0 | b |
| '' | ‘’ | b |
| false | false | b |
| NaN | NaN | b |
| 其他真值 | a | a |

9. BigInt

// 创建BigInt
const bigNum = 1234567890123456789012345678901234567890n;
const alsoBig = BigInt("1234567890123456789012345678901234567890");

// 运算
const a = 1n;
const b = 2n;
console.log(a + b); // 3n
console.log(a * b); // 2n
console.log(b ** 100n); // 非常大的数

// 比较
console.log(1n == 1); // true
console.log(1n === 1); // false (类型不同)

// 不能混用普通数字
// console.log(1n + 2); // TypeError

// 转换为普通数字(可能丢失精度)
console.log(Number(1n)); // 1
console.log(Number(12345678901234567890n)); // 12345678901234567000

// 应用场景
const maxSafe = Number.MAX_SAFE_INTEGER; // 9007199254740991
console.log(maxSafe + 1 === maxSafe + 2); // true (精度问题)
console.log(BigInt(maxSafe) + 1n === BigInt(maxSafe) + 2n); // false
BigInt 特点
特性 说明
表示范围 任意大整数
字面量 数字后加n
类型 typeof 1n 返回 "bigint"
运算 支持+, -, *, /, %, **
比较 可与Number比较值,但类型不同
转换 可能丢失精度转换为Number
使用场景 大整数计算、高精度时间戳、大ID等

10. 动态导入

// 静态导入
// import { someFunc } from './module.js';

// 动态导入
const modulePath = './module.js';

// 基本用法
import(modulePath)
    .then(module => {
        module.someFunc();
    })
    .catch(err => {
        console.error('加载失败:', err);
    });

// async/await中使用
async function loadModule() {
    try {
        const module = await import(modulePath);
        module.someFunc();
    } catch (err) {
        console.error('加载失败:', err);
    }
}

// 根据条件动态加载
if (featureFlag) {
    import('./advanced-feature.js')
        .then(module => module.init());
}

// 动态路径
const lang = navigator.language.startsWith('zh') ? 'zh' : 'en';
import(`./locales/${lang}.js`)
    .then(module => module.loadTranslations());

// 默认导出处理
import('./module.js')
    .then(module => {
        const defaultExport = module.default;
        defaultExport();
    });

动态导入 vs 静态导入

特性 动态导入 静态导入
语法 import()函数 import ... from
加载时机 运行时按需加载 编译时静态加载
返回值 Promise 同步
使用场景 代码分割、条件加载、懒加载 主要依赖、应用初始化代码
模块路径 可以是变量或表达式 必须是字符串字面量
性能 减少初始加载体积 便于静态分析和优化

通过以上详细的代码示例、表格对比和文字说明,你应该能够全面理解ES6+中的这些重要新特性。这些特性极大地提升了JavaScript的表达能力和开发效率,是现代JavaScript开发的基础。


网站公告

今日签到

点亮在社区的每一天
去签到