Post

Web JavaScript Advanced

基础

输出

输出方式 描述 示例
console.log() 将输出内容打印到浏览器开发者工具的控制台 console.log("Hello, World!");
alert() 弹出警告框,显示输出内容 alert("This is an alert!");
document.write() 将输出内容直接写入当前 HTML 文档 document.write("<h1>Hello, World!</h1>");
innerHTML 将输出内容插入到 HTML 元素的内部 javascript<br>var element = document.getElementById("myDiv");<br>element.innerHTML = "This is some new content.";
textContent 将输出内容插入到 HTML 元素,但只显示纯文本,不解析 HTML 标签 javascript<br>var element = document.getElementById("myDiv");<br>element.textContent = "This is some new text.";

数据类型

  • 原始数据类型
数据类型 描述 示例
Number 包括整数和浮点数 42, 3.14, NaN, Infinity
String 文本数据 "hello", 'world', `template literal`
Boolean 表示真值或假值 true, false
Undefined 未定义的值 undefined
Null 空值 null
Symbol 唯一标识符 Symbol("id")
  • 复合数据类型
数据类型 描述 示例
Object 键值对的集合 { name: "John", age: 30 }
Array 有序的值的集合 [1, 2, 3]
Function 可执行的代码块 function() { ... }
Date 日期和时间 new Date()
RegExp 正则表达式 /pattern/
  • 同样需要注意的是
  1. typeof 操作符可用于检查变量的基本数据类型。
  2. instanceof 操作符可用于检查对象是否是某个构造函数的实例。
  3. Object.prototype.toString.call() 可以返回更精确的类型字符串。
  • 例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    
    // 原始数据类型
    let x = 42; // Number
    let y = "hello"; // String
    let z = true; // Boolean
    let a; // Undefined
    let b = null; // Null
    let c = Symbol("id"); // Symbol
    
    // 非原始数据类型
    let obj = { name: "John", age: 30 }; // Object
    let arr = [1, 2, 3]; // Array
    let func = function () {
      console.log("Hello");
    }; // Function
    let date = new Date(); // Date
    let regex = /\d+/; // RegExp
    let bigNum = 9007199254740991n; // BigInt
    
  • typeof 检查数据类型

    1
    2
    3
    4
    5
    
    let x = 42;
    console.log(typeof x); // "number"
    
    x = "hello";
    console.log(typeof x); // "string"
    

变量声明

  1. var:

    • var 是最传统的声明变量的方式。
    • 使用 var 声明的变量具有函数作用域或全局作用域。
    • 如果没有初始化,变量的值将为 undefined
    • 例如: var x = 5;
  2. let:

    • let 是 ES6 (ECMAScript 2015) 引入的新语法。
    • let 声明的变量具有块级作用域,比 var 更加局部化。
    • 使用 let 声明的变量可以被重新赋值,但不能被重复声明。
    • 例如: let y = 10;
  3. const:

    • const 也是 ES6 引入的新语法。
    • const 声明的变量是常量,其值不能被修改。
    • const 声明的变量必须在声明时初始化,且初始化后不能被重新赋值。
    • 常量通常用于定义不会变化的值,如配置项。
    • 例如: const PI = 3.14159;
  • var 是较旧的方式,letconst 是较新的方式,具有更好的作用域控制和安全性
  • 开发中,通常建议使用 letconst 来声明变量,除非您有特定的需要使用 var 的场景

三者区别

特性 var let const
作用域 函数作用域/全局作用域 块级作用域 块级作用域
变量提升 被提升到作用域顶部,初始化为 undefined 不会被提升,使用前必须声明 不会被提升,使用前必须声明
可重复声明 允许在同一作用域内重复声明 不允许在同一作用域内重复声明 不允许在同一作用域内重复声明
可重新赋值 可以 可以 不可以
默认值 undefined undefined 必须初始化,否则报错

从上表可以看出,letconst 相比 var 有以下优势:

  1. 更好的作用域控制,避免变量污染全局命名空间。
  2. 更安全的使用,避免无意中覆盖变量的问题。
  3. const 声明常量,可以使代码更加明确和安全。

运算符

算术运算符

运算符 描述 示例
+ 5 + 3 // 结果为 8
- 10 - 4 // 结果为 6
* 7 * 6 // 结果为 42
/ 15 / 3 // 结果为 5
% 取余 17 % 5 // 结果为 2
++ 自增 let x = 5; x++; // x 的值变为 6
-- 自减 let y = 8; y--; // y 的值变为 7

赋值运算符

运算符 描述 示例
= 赋值 let a = 10;
+= 加赋值 let b = 5; b += 3; // b 的值变为 8
-= 减赋值 let c = 12; c -= 4; // c 的值变为 8
*= 乘赋值 let d = 6; d *= 4; // d 的值变为 24
/= 除赋值 let e = 20; e /= 5; // e 的值变为 4
%= 取余赋值 let f = 13; f %= 4; // f 的值变为 1

比较运算符

运算符 描述 示例
> 大于 7 > 3 // 结果为 true
< 小于 2 < 9 // 结果为 true
>= 大于等于 10 >= 10 // 结果为 true
<= 小于等于 4 <= 6 // 结果为 true
== 等于 "5" == 5 // 结果为 true
!= 不等于 10 != '10' // 结果为 true
=== 严格等于 5 === 5 // 结果为 true
!== 严格不等于 "hello" !== 'hello' // 结果为 true

逻辑运算符

运算符 描述 示例
&& 逻辑与 (5 > 3) && (2 < 4) // 结果为 true
\|\| 逻辑或 (7 == 7)\|\|(3 > 5) // 结果为 true
! 逻辑非 !(4 === 4) // 结果为 false

其他运算符

运算符 描述 示例
?: 三元运算符 let age = 18; let canVote = age >= 18 ? "Yes" : "No";
in 检查属性是否存在 let obj = { a: 1, b: 2 }; "a" in obj // 结果为 true
instanceof 检查对象是否是某个构造函数的实例 let arr = []; arr instanceof Array // 结果为 true
typeof 检查变量的数据类型 typeof 3.14 // 结果为 "number"
delete 删除对象的属性 let person = { name: "Alice", age: 30 }; delete person.age;

流程控制

if-else 语句

1
2
3
4
5
if (condition) {
  // 如果条件为 true,执行这里的代码
} else {
  // 如果条件为 false,执行这里的代码
}
switch 语句
1
2
3
4
5
6
7
8
9
10
11
switch (expression) {
  case value1:
    // 如果 expression 的值等于 value1,执行这里的代码
    break;
  case value2:
    // 如果 expression 的值等于 value2,执行这里的代码
    break;
  ...
  default:
    // 如果 expression 的值不等于任何一个 case 的值,执行这里的代码
}

for 循环

1
2
3
for (initialization; condition; increment) {
  // 循环体,重复执行这里的代码
}

while 循环

1
2
3
while (condition) {
  // 循环体,重复执行这里的代码
}

do-while 循环

1
2
3
do {
  // 循环体,先执行这里的代码
} while (condition);

break 和 continue 语句

  • break 用于跳出循环
  • continue 用于跳过当前循环,进入下一次循环

流程控制例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// 定义一个函数,用于计算给定数字的阶乘
function calculateFactorial(num) {
  // 使用 if-else 语句检查输入是否合法
  if (num < 0) {
    return "输入不能是负数";
  } else if (num === 0) {
    return 1;
  }

  // 使用 for 循环计算阶乘
  let factorial = 1;
  for (let i = 1; i <= num; i++) {
    factorial *= i;
  }

  return factorial;
}

// 测试函数
console.log(calculateFactorial(5)); // 输出: 120
console.log(calculateFactorial(0)); // 输出: 1
console.log(calculateFactorial(-3)); // 输出: 输入不能是负数

// 使用 switch 语句判断一个数字的奇偶性
function checkOddEven(num) {
  switch (num % 2) {
    case 0:
      return `${num} 是偶数`;
    case 1:
      return `${num} 是奇数`;
    default:
      return "输入不合法";
  }
}

// 测试函数
console.log(checkOddEven(7)); // 输出: 7 是奇数
console.log(checkOddEven(12)); // 输出: 12 是偶数
console.log(checkOddEven("hello")); // 输出: 输入不合法

// 使用 while 循环打印 1 到 5 的数字
let i = 1;
while (i <= 5) {
  console.log(i);
  i++;
}
// 输出:
// 1
// 2
// 3
// 4
// 5

// 使用 do-while 循环打印 a 到 e 的字母
let letter = "a";
do {
  console.log(letter);
  letter++;
} while (letter <= "e");
// 输出:
// a
// b
// c
// d
// e

作用域(Scope)

  1. 全局作用域(Global Scope):

    • 在代码中任何地方都能访问的作用域。
    • 在浏览器中,全局作用域挂载在 window 对象上。在 Node.js 中,全局作用域挂载在 global 对象上。
    • 例如:

      1
      2
      3
      4
      5
      6
      7
      
      var globalVariable = "Hello, World!";
      
      function globalFunction() {
        console.log(globalVariable); // 可以访问全局变量
      }
      
      globalFunction(); // 输出 'Hello, World!'
      
  2. 函数作用域(Function Scope):

    • 在函数内部定义的变量和函数只能在该函数内部访问。
    • 例如:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      
      function outerFunction() {
        var localVariable = "Hello";
      
        function innerFunction() {
          var anotherLocalVariable = "World";
          console.log(localVariable + ", " + anotherLocalVariable); // 可以访问函数内部的变量
        }
      
        innerFunction();
      }
      
      outerFunction(); // 输出 'Hello, World'
      console.log(localVariable); // 报错,无法访问函数内部的变量
      
  3. 块级作用域(Block Scope):

    • {} 内部定义的变量和函数只能在该块内部访问。
    • 在 ES6 引入的 letconst 关键字创建了块级作用域。
    • 例如:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      
      if (true) {
        var varVariable = "Hello";
        let letVariable = "World";
        const constVariable = "!";
      
        console.log(varVariable); // 可以访问
        console.log(letVariable); // 可以访问
        console.log(constVariable); // 可以访问
      }
      
      console.log(varVariable); // 可以访问
      console.log(letVariable); // 报错,无法访问
      console.log(constVariable); // 报错,无法访问
      
  4. 模块作用域(Module Scope):

    • 在 ES6 引入的 importexport 语句创建的作用域。
    • 模块内部定义的变量和函数只能在该模块内部访问,除非通过 export 导出。
    • 例如:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      
      // module1.js
      export const message = "Hello, World!";
      
      export function sayHello() {
        console.log(message);
      }
      
      // app.js
      import { message, sayHello } from "./module1.js";
      
      console.log(message); // 可以访问导出的变量
      sayHello(); // 可以访问导出的函数
      

函数

常见函数

分类 函数
控制台函数 console.log()
console.error()
console.warn()
console.info()
console.time() / console.timeEnd()
对话框函数 alert()
prompt()
confirm()
定时器函数 setTimeout()
setInterval()
clearTimeout()
clearInterval()
DOM 操作函数 document.getElementById()
document.getElementsByTagName()
document.getElementsByClassName()
document.querySelector()
document.querySelectorAll()
数学函数 Math.round()
Math.ceil()
Math.floor()
Math.random()
数组函数 Array.push()
Array.pop()
Array.shift()
Array.unshift()
Array.slice()
Array.concat()
字符串函数 String.charAt()
String.concat()
String.indexOf()
String.replace()
String.slice()
String.split()
String.toLowerCase()
String.toUpperCase()
对象函数 Object.keys()
Object.values()
Object.assign()
Object.freeze()
Object.seal()
JSON 函数 JSON.parse()
JSON.stringify()
其他常用函数 isNaN()
parseInt()
parseFloat()
encodeURIComponent()
decodeURIComponent()
  • 例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    
    // 控制台函数
    console.log("Hello, world!");
    console.error("This is an error message.");
    console.warn("This is a warning message.");
    console.info("This is an informational message.");
    console.time("Timer");
    // do some time-consuming operation
    console.timeEnd("Timer");
    
    // 对话框函数
    alert("This is an alert dialog.");
    let userInput = prompt("Please enter your name:");
    let confirmation = confirm("Are you sure you want to continue?");
    
    // 定时器函数
    setTimeout(function () {
      console.log("This message will be logged after 2 seconds.");
    }, 2000);
    
    setInterval(function () {
      console.log("This message will be logged every 5 seconds.");
    }, 5000);
    
    // DOM 操作函数
    let element = document.getElementById("myElement");
    let elements = document.getElementsByTagName("p");
    let classElements = document.getElementsByClassName("myClass");
    let singleElement = document.querySelector("#myElement");
    let allElements = document.querySelectorAll(".myClass");
    
    // 数学函数
    let rounded = Math.round(3.14);
    let ceilinged = Math.ceil(3.14);
    let floored = Math.floor(3.14);
    let random = Math.random();
    
    // 数组函数
    let myArray = [1, 2, 3];
    myArray.push(4);
    let lastElement = myArray.pop();
    let firstElement = myArray.shift();
    myArray.unshift(0);
    let slicedArray = myArray.slice(1, 3);
    let concatenatedArray = myArray.concat([5, 6, 7]);
    
    // 字符串函数
    let myString = "Hello, world!";
    let char = myString.charAt(0);
    let concatString = myString.concat(" How are you?");
    let index = myString.indexOf("world");
    let replacedString = myString.replace("world", "JavaScript");
    let slicedString = myString.slice(0, 5);
    let splitString = myString.split(", ");
    let lowercaseString = myString.toLowerCase();
    let uppercaseString = myString.toUpperCase();
    
    // 对象函数
    let myObject = { name: "John", age: 30 };
    let keys = Object.keys(myObject);
    let values = Object.values(myObject);
    let mergedObject = Object.assign({}, myObject, { city: "New York" });
    let frozenObject = Object.freeze(myObject);
    let sealedObject = Object.seal(myObject);
    
    // JSON 函数
    let jsonString = '{"name":"John","age":30}';
    let jsonObject = JSON.parse(jsonString);
    let stringifiedObject = JSON.stringify(myObject);
    
    // 其他常用函数
    let isNumber = isNaN(myString);
    let parsedInt = parseInt("42");
    let parsedFloat = parseFloat("3.14");
    let encodedString = encodeURIComponent("https://example.com?q=hello world");
    let decodedString = decodeURIComponent(encodedString);
    

自定义函数

  1. JavaScript 函数定义:

    • 函数声明: function functionName(parameters) { // function body }
    • 函数表达式: let functionName = function(parameters) { // function body }
    • 箭头函数: let functionName = (parameters) => { // function body }
  2. JavaScript 函数参数:

    • 形参: 在函数定义时指定的参数
    • 实参: 在函数调用时传递的参数
    • 默认参数: 可以为参数指定默认值,当没有传递实参时使用默认值
    • 不定参数: 使用 ... 语法可以创建接受任意数量参数的函数
  3. JavaScript 函数调用:

    • 直接调用: functionName(arguments)
    • 方法调用: object.methodName(arguments)
    • 构造函数调用: new FunctionName(arguments)
    • 间接调用: 使用 call(), apply(), bind()
  4. JavaScript 闭包:

    • 闭包是一个函数,它可以访问其外部函数作用域中的变量。
    • 闭包可以在函数外部访问函数内部的变量,即使外部函数已经执行完毕。
    • 闭包的主要用途包括:
      • 创建私有变量和方法
      • 实现回调函数
      • 柯里化(Currying)
  • 代码例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    
    // 函数定义
    function add(a, b) {
      return a + b;
    }
    let subtract = function (a, b) {
      return a - b;
    };
    let multiply = (a, b) => a * b;
    
    // 函数参数
    function greet(name, greeting = "Hello") {
      console.log(`${greeting}, ${name}!`);
    }
    function sum(...numbers) {
      return numbers.reduce((acc, curr) => acc + curr, 0);
    }
    
    // 函数调用
    console.log(add(2, 3)); // 5
    console.log(subtract(5, 3)); // 2
    console.log(multiply(4, 6)); // 24
    
    greet("Alice"); // Hello, Alice!
    greet("Bob", "Hi"); // Hi, Bob!
    console.log(sum(1, 2, 3, 4, 5)); // 15
    
    // 闭包示例
    function createAdder(x) {
      return function (y) {
        return x + y;
      };
    }
    
    let add5 = createAdder(5);
    console.log(add5(3)); // 8
    console.log(add5(10)); // 15
    

对象

1. 对象字面量

1
2
3
4
5
let person = {
  name: "John Doe",
  age: 30,
  occupation: "Software Engineer",
};

2. 使用 new Object()

1
2
3
4
let person = new Object();
person.name = "John Doe";
person.age = 30;
person.occupation = "Software Engineer";

3. 使用构造函数

1
2
3
4
5
6
7
function Person(name, age, occupation) {
  this.name = name;
  this.age = age;
  this.occupation = occupation;
}

let john = new Person("John Doe", 30, "Software Engineer");
  • 对象的属性可以使用点符号或方括号符号访问

    1
    2
    
    console.log(person.name); // "John Doe"
    console.log(person["age"]); // 30
    
  • 方括号符号允许使用变量作为属性名,这在动态属性访问时很有用。

    对象还支持一些常用的方法,如:

    • Object.keys(obj): 返回对象所有可枚举属性的键。
    • Object.values(obj): 返回对象所有可枚举属性的值。
    • Object.entries(obj): 返回对象所有可枚举属性的键值对数组。
    • obj.hasOwnProperty(key): 检查对象是否有指定的属性。
  • 这些方法可以用于遍历和操作对象。

    此外,对象也可以包含方法,即函数类型的属性:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    let person = {
      name: "John Doe",
      age: 30,
      occupation: "Software Engineer",
      greet: function () {
        console.log(`Hello, my name is ${this.name}.`);
      },
    };
    
    person.greet(); // "Hello, my name is John Doe."
    
  • 在方法内部,this 关键字引用当前对象实例。

数组

1. 数组字面量

1
let fruits = ["apple", "banana", "orange"];

2. 使用 new Array()

1
let numbers = new Array(1, 2, 3, 4, 5);
  • 数组元素可以通过索引访问和修改:

    1
    2
    3
    
    console.log(fruits[0]); // "apple"
    fruits[1] = "pear";
    console.log(fruits); // ["apple", "pear", "orange"]
    

数组提供了许多有用的方法

  • array.push(item): 在数组末尾添加一个或多个元素。
  • array.pop(): 删除并返回数组的最后一个元素。
  • array.shift(): 删除并返回数组的第一个元素。
  • array.unshift(item): 在数组开头添加一个或多个元素。
  • array.indexOf(item): 返回元素在数组中的第一个索引,如果不存在则返回 -1。
  • array.slice(start, end): 返回一个新的数组,包含从 start 到 end-1 的元素。
  • array.concat(item1, item2, ...): 合并两个或多个数组,返回一个新数组。
  • array.forEach(function(item, index, array) { ... }): 遍历数组,对每个元素执行提供的函数。

数组高阶函数

此外,数组还支持许多高阶函数,如 map()filter()reduce()some()every() 等,用于对数组进行转换、过滤和聚合操作。

  1. map():

    • 作用: 对数组的每个元素应用一个函数,并返回一个新数组。

      1
      2
      3
      4
      5
      
      let numbers = [1, 2, 3, 4, 5];
      let doubledNumbers = numbers.map(function (num) {
        return num * 2;
      });
      console.log(doubledNumbers); // [2, 4, 6, 8, 10]
      
  2. filter():

    • 作用: 根据提供的条件函数,返回一个新数组,包含满足条件的元素。

      1
      2
      3
      4
      5
      
      let numbers = [1, 2, 3, 4, 5];
      let evenNumbers = numbers.filter(function (num) {
        return num % 2 === 0;
      });
      console.log(evenNumbers); // [2, 4]
      
  3. reduce():

    • 作用: 通过迭代数组元素,将其简化为单个值。

      1
      2
      3
      4
      5
      
      let numbers = [1, 2, 3, 4, 5];
      let sum = numbers.reduce(function (accumulator, currentValue) {
        return accumulator + currentValue;
      }, 0);
      console.log(sum); // 15
      
  4. some():

    • 作用: 如果数组中至少有一个元素满足条件函数,则返回 true

      1
      2
      3
      4
      5
      
      let numbers = [1, 2, 3, 4, 5];
      let hasEven = numbers.some(function (num) {
        return num % 2 === 0;
      });
      console.log(hasEven); // true
      
  5. every():

    • 作用: 如果数组中所有元素都满足条件函数,则返回 true

      1
      2
      3
      4
      5
      
      let numbers = [2, 4, 6, 8, 10];
      let areAllEven = numbers.every(function (num) {
        return num % 2 === 0;
      });
      console.log(areAllEven); // true
      

数组操作完整例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// 创建一个水果数组
let fruits = ["apple", "banana", "orange"];

// 使用 push 和 pop 方法
fruits.push("grape");
console.log(fruits); // ["apple", "banana", "orange", "grape"]
let lastFruit = fruits.pop();
console.log(lastFruit, fruits); // "grape" ["apple", "banana", "orange"]

// 使用 shift 和 unshift 方法
let firstFruit = fruits.shift();
console.log(firstFruit, fruits); // "apple" ["banana", "orange"]
fruits.unshift("pear");
console.log(fruits); // ["pear", "banana", "orange"]

// 使用 indexOf 方法
let bananaIndex = fruits.indexOf("banana");
console.log(bananaIndex); // 1

// 使用 slice 方法
let somefruits = fruits.slice(1, 3);
console.log(somefruits); // ["banana", "orange"]

// 使用 concat 方法
let moreFruits = fruits.concat(["kiwi", "mango"]);
console.log(moreFruits); // ["pear", "banana", "orange", "kiwi", "mango"]

// 使用 forEach 方法
fruits.forEach(function (fruit) {
  console.log("Fruit:", fruit);
});
// Fruit: pear
// Fruit: banana
// Fruit: orange

// 使用 map 方法
let uppercasedFruits = fruits.map(function (fruit) {
  return fruit.toUpperCase();
});
console.log(uppercasedFruits); // ["PEAR", "BANANA", "ORANGE"]

// 使用 filter 方法
let shortFruits = fruits.filter(function (fruit) {
  return fruit.length <= 5;
});
console.log(shortFruits); // ["pear", "banana"]

// 使用 reduce 方法
let totalLetters = fruits.reduce(function (total, fruit) {
  return total + fruit.length;
}, 0);
console.log(totalLetters); // 14

// 使用 some 方法
let hasLongFruit = fruits.some(function (fruit) {
  return fruit.length > 6;
});
console.log(hasLongFruit); // false

// 使用 every 方法
let allShortFruits = fruits.every(function (fruit) {
  return fruit.length <= 6;
});
console.log(allShortFruits); // true

事件处理

  1. 事件监听器:

    • 事件监听器是一个函数,它会在特定的事件发生时被调用。
    • 我们可以使用 addEventListener() 方法为 DOM 元素添加事件监听器。
    • 示例:

      1
      2
      3
      4
      
      let button = document.getElementById("myButton");
      button.addEventListener("click", function () {
        console.log("Button was clicked!");
      });
      
  2. 常见的事件类型:

    • click: 当用户点击元素时触发。
    • mouseover/mouseout: 当鼠标移入/移出元素时触发。
    • keydown/keyup: 当用户按下/松开键盘按键时触发。
    • submit: 当表单提交时触发。
    • load: 当页面或资源加载完成时触发。
  3. 事件对象:

    • 事件监听器函数会接收一个事件对象作为参数。
    • 事件对象包含了关于事件的各种信息,如事件类型、目标元素、鼠标坐标等。
    • 示例:

      1
      2
      3
      4
      
      button.addEventListener("click", function (event) {
        console.log("Event type:", event.type);
        console.log("Target element:", event.target);
      });
      
  4. 事件传播:

    • 事件从目标元素开始向上”冒泡”,一直到 document 对象。
    • 可以使用 event.stopPropagation() 方法阻止事件继续传播。
    • 示例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      
      let parent = document.getElementById("parent");
      let child = document.getElementById("child");
      
      parent.addEventListener("click", function (event) {
        console.log("Parent clicked");
      });
      
      child.addEventListener("click", function (event) {
        console.log("Child clicked");
        event.stopPropagation();
      });
      
  5. 事件委托:

    • 事件委托是一种优化事件处理的技术。
    • 我们可以将事件处理程序附加到父元素上,而不是每个子元素。
    • 当事件在子元素上触发时,会冒泡到父元素,父元素可以处理该事件。
    • 示例:

      1
      2
      3
      4
      5
      6
      
      let container = document.getElementById("container");
      container.addEventListener("click", function (event) {
        if (event.target.tagName === "LI") {
          console.log("List item clicked:", event.target.textContent);
        }
      });
      

事件处理例子

示例将涵盖 DOM 事件处理的各个方面,包括事件监听、事件对象、事件传播和事件委托等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<!DOCTYPE html>
<html>
  <head>
    <title>Event Handling Example</title>
    <style>
      #parent {
        width: 300px;
        height: 300px;
        background-color: #f2f2f2;
        padding: 20px;
      }
      #child {
        width: 200px;
        height: 200px;
        background-color: #ddd;
        margin: 0 auto;
        padding: 20px;
      }
      button {
        display: block;
        margin-top: 20px;
      }
    </style>
  </head>
  <body>
    <div id="parent">
      <div id="child">
        <p>Click me or my parent!</p>
      </div>
    </div>

    <button id="myButton">Click me</button>
    <input type="text" id="myInput" placeholder="Type something" />
    <form id="myForm">
      <input type="text" id="formInput" placeholder="Form input" />
      <button type="submit">Submit</button>
    </form>

    <script>
      // 1. Event Listener
      let button = document.getElementById("myButton");
      button.addEventListener("click", function (event) {
        console.log("Button clicked!");
      });

      // 2. Event Object
      let input = document.getElementById("myInput");
      input.addEventListener("keyup", function (event) {
        console.log("Key released:", event.key);
      });

      // 3. Event Propagation
      let parent = document.getElementById("parent");
      let child = document.getElementById("child");

      parent.addEventListener("click", function (event) {
        console.log("Parent clicked");
      });

      child.addEventListener("click", function (event) {
        console.log("Child clicked");
        event.stopPropagation(); // 阻止事件冒泡
      });

      // 4. Event Delegation
      let container = document.getElementById("parent");
      container.addEventListener("click", function (event) {
        if (event.target.tagName === "P") {
          console.log("Paragraph clicked:", event.target.textContent);
        }
      });

      // 5. Form Submission
      let form = document.getElementById("myForm");
      form.addEventListener("submit", function (event) {
        event.preventDefault(); // 阻止表单默认提交行为
        let formInput = document.getElementById("formInput");
        console.log("Form submitted:", formInput.value);
      });
    </script>
  </body>
</html>
  • 解释

    1. 事件监听器: 为按钮添加一个 click 事件监听器。
    2. 事件对象: 监听输入框的 keyup 事件,并使用事件对象获取按下的键。
    3. 事件传播: 监听父元素和子元素的 click 事件,并使用 stopPropagation() 阻止事件冒泡。
    4. 事件委托: 将点击处理程序添加到父元素上,当点击子元素时,事件会冒泡到父元素并被处理。
    5. 表单提交: 监听表单的 submit 事件,并使用 preventDefault() 阻止表单的默认提交行为。

报错

JavaScript 中,当出现问题时,会抛出错误。错误可能是由于代码逻辑错误、无效输入、资源不可用等原因引起的。

  1. 错误类型:

    • SyntaxError: 代码语法错误。
    • ReferenceError: 尝试访问不存在的变量。
    • TypeError: 对不正确类型的操作。
    • RangeError: 数值超出有效范围。
    • URIError: 使用不正确的编码或解码 URI 组件。
    • EvalError: eval() 函数使用不当。
  2. 错误处理:

    • try-catch 语句:

      • 使用 try 块包裹可能会抛出错误的代码。
      • 使用 catch 块捕获并处理抛出的错误。
      • 示例:

        1
        2
        3
        4
        5
        6
        
        try {
          // 可能会抛出错误的代码
          let result = parseInt("abc");
        } catch (error) {
          console.error("Error occurred:", error.message);
        }
        
    • 错误对象:

      • 捕获的错误对象包含有关错误的信息,如错误类型、错误消息和堆栈追踪。
      • 可以使用 error.messageerror.nameerror.stack 属性访问错误信息。
  3. 自定义错误:

    • 可以使用 throw 语句抛出自定义错误。
    • 自定义错误通常是 Error 对象的实例或者继承自 Error 的自定义错误类。
    • 示例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      
      class InvalidInputError extends Error {
        constructor(message) {
          super(message);
          this.name = "InvalidInputError";
        }
      }
      
      function validateInput(input) {
        if (input === "") {
          throw new InvalidInputError("Input cannot be empty");
        }
        // 其他验证逻辑
      }
      
      try {
        validateInput("");
      } catch (error) {
        if (error instanceof InvalidInputError) {
          console.error(error.message);
        } else {
          console.error("An unexpected error occurred");
        }
      }
      
  4. 错误处理最佳实践:

    • 始终使用 try-catch 块包裹可能抛出错误的代码。
    • catch 块中,根据错误类型提供适当的错误处理。
    • 记录并显示有意义的错误信息,以便于调试和错误排查。
    • 对于严重错误,可以考虑向用户显示友好的错误消息。
    • 在生产环境中,可以使用全局错误处理机制捕获未处理的错误。

调试

  1. 浏览器开发者工具:

    • 所有主流浏览器都提供了强大的开发者工具,如 Chrome 的 DevTools、Firefox 的 Developer Tools 和 Edge 的 F12 开发者工具。
    • 这些工具提供了丰富的调试功能,包括断点调试、变量检查、网络监控、性能分析等。
    • 使用这些工具可以帮助您快速定位和解决代码中的问题。
  2. 断点调试:

    • 在代码中设置断点,可以在执行到该点时暂停程序的运行。
    • 这样您就可以检查变量的值、观察执行流程,并逐步执行代码以找出问题所在。
    • 大多数浏览器开发者工具都支持断点调试功能。
  3. console 对象:

    • console 对象提供了各种调试和记录输出的方法,如 console.log()console.error()console.warn() 等。
    • 通过在代码中添加 console.log() 语句,可以输出变量值、对象属性等信息,帮助您跟踪程序的执行过程。
  4. Source Map:

    • 当使用 JavaScript 构建工具(如 Webpack、Rollup 等)时,生成的 JavaScript 文件通常会被压缩/转换,使得调试变得困难。
    • Source Map 是一种映射关系,可以将压缩后的 JavaScript 代码与源代码进行关联,在开发者工具中显示原始的源代码。
    • 使用 Source Map 可以大大提高调试体验。
  5. debugger 关键字:

    • debugger 关键字可以在代码中设置一个断点,类似于在开发者工具中手动设置断点。
    • 当程序执行到 debugger 语句时,它会自动暂停并进入调试模式。
  6. 异步调试:

    • 调试异步代码(如 Promise、async/await)可能会更加复杂。
    • 使用 async/await 配合断点调试可以帮助您更好地理解异步代码的执行过程。
    • 一些调试工具,如 Chrome DevTools 的 “Pause on uncaught exceptions” 选项,也可以帮助您捕获异步代码中的错误。
  7. 日志记录和错误报告:

    • 在生产环境中,使用日志记录工具(如 console.error())记录错误信息和堆栈追踪。
    • 可以结合错误报告服务(如 Sentry、Rollbar 等)自动收集和分析应用程序中的错误,以便快速修复问题。
  8. 单元测试:

    • 编写全面的单元测试可以帮助您提前发现并修复代码中的问题。
    • 单元测试还可以作为一种回归测试,确保代码修改不会破坏原有的功能。

进阶

异步

async 函数是 ECMAScript 2017 (ES8) 引入的一个特性,它允许我们使用同步编程的方式来编写异步代码。它的主要目的是简化异步代码的编写和读取,同时提供更好的错误处理机制。

  • 下面是一个基本的 async 函数示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    
    async function fetchData() {
      try {
        const response = await fetch("https://api.example.com/data");
        const data = await response.json();
        console.log("Data:", data);
      } catch (error) {
        console.error("Error:", error.message);
      }
    }
    
    fetchData();
    
  • 解释

    1. async 关键字标识 fetchData() 函数为异步函数。
    2. 在函数内部,await 关键字用于等待异步操作的结果,如 fetch() 调用和 response.json() 解析。
    3. 任何 await 表达式都会暂停函数的执行,直到相应的 Promise 被解决。
    4. 如果发生错误,try/catch 块会捕获并处理异常。
  • 使用 async/await 的优势包括:

    1. 简化异步代码: 相比使用回调函数或 Promise 链,async/await 提供了一种更加同步和线性的编码方式,使代码更加易读和维护。
    2. 错误处理: try/catch 块可以优雅地处理异步操作中的错误,避免了嵌套回调函数或 Promise 链中的错误处理问题。
    3. 并发控制: 您可以在 async 函数内部并行执行多个异步操作,并使用 Promise.all() 等方法来协调它们的执行。
    4. composition: async 函数可以被其他 async 函数调用,形成一个嵌套的异步操作流程。

需要注意的是,async 函数本身会返回一个 Promise。这意味着您可以在外部使用 .then().catch() 方法来处理 async 函数的结果和错误。

1
2
3
fetchData()
  .then(() => console.log("Data fetched successfully"))
  .catch((error) => console.error("Error:", error.message));

总的来说,async/await 是 JavaScript 异步编程的一个重要工具,它简化了代码,提高了可读性和可维护性。通过结合使用 async 函数、Promise 和错误处理,您可以编写出更加优雅和健壮的异步代码

类基础

在 JavaScript 中,类是一种创建对象的蓝图或模板。它定义了对象应该具有的属性(数据)和方法(行为)。

  • Person 类开始

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    class Person {
      // 构造函数
      constructor(name, age) {
        this.name = name;
        this.age = age;
      }
    
      // 实例方法
      greet() {
        console.log(
          `Hello, my name is ${this.name} and I'm ${this.age} years old.`
        );
      }
    }
    
    • 解释

      1. class Person 定义了一个名为 Person 的类。
      2. constructor 是一种特殊的方法,用于初始化新创建的 Person 对象。它接受 nameage 参数,并将它们分别赋值给对象的 nameage 属性。
      3. greet 是一个实例方法,它定义了 Person 对象可以执行的行为。当调用 greet() 时,它会打印出一个 greeting 消息。
  • 要创建 Person 类的实例(对象),可以使用 new 关键字:

    1
    2
    
    const john = new Person("John", 30);
    john.greet(); // 输出: Hello, my name is John and I'm 30 years old.
    
    • 解释

      1. new Person('John', 30) 创建了一个新的 Person 对象,并将其存储在变量 john 中。
      2. 调用 john.greet() 会执行 Person 类中定义的 greet 方法,输出 greeting 消息。
  • 类还支持继承,这意味着您可以创建一个新的类,并从现有的类中继承属性和方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    
    class Student extends Person {
      constructor(name, age, grade) {
        super(name, age); // 调用父类的构造函数
        this.grade = grade;
      }
    
      study() {
        console.log(`${this.name} is studying for grade ${this.grade}.`);
      }
    }
    
    const alice = new Student("Alice", 15, 10);
    alice.greet(); // 输出: Hello, my name is Alice and I'm 15 years old.
    alice.study(); // 输出: Alice is studying for grade 10.
    
  • 解释

    1. Student 类继承自 Person 类,使用 extends 关键字。
    2. constructor 方法调用 super(name, age) 来初始化从父类 Person 继承的 nameage 属性。
    3. Student 类新增了 grade 属性和 study 方法。
    4. 创建 Student 类的实例 alice,可以访问从 Person 继承的 greet 方法以及自己的 study 方法。

类的进阶

  1. 静态方法和属性

    类中除了实例方法和属性,还可以定义静态方法和属性。静态成员属于类本身,而不是类的实例。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    class Math {
      static PI = 3.14159;
    
      static add(a, b) {
        return a + b;
      }
    }
    
    console.log(Math.PI); // 3.14159
    console.log(Math.add(2, 3)); // 5
    

    在这个例子中,PI 是一个静态属性,add 是一个静态方法。它们可以直接通过类名 Math 来访问,而不需要创建类的实例。

  2. 访问控制

    JavaScript 类支持基本的访问控制修饰符,包括 public(默认)、privateprotected

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    
    class BankAccount {
      #balance = 0; // 私有属性
    
      deposit(amount) {
        this.#balance += amount; // 可以访问私有属性
      }
    
      getBalance() {
        return this.#balance;
      }
    }
    
    const account = new BankAccount();
    account.deposit(1000);
    console.log(account.getBalance()); // 1000
    console.log(account.#balance); // Error: Private field '#balance' must be declared in an enclosing class
    

    在这个例子中,#balance 是一个私有属性,只能在类的内部访问。deposit 方法可以修改私有属性,而 getBalance 方法可以读取私有属性的值。

  3. Getter 和 Setter

    您可以在类中定义 getter 和 setter 方法,用于控制对属性的访问和赋值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    
    class Person {
      constructor(name) {
        this._name = name;
      }
    
      get name() {
        return this._name;
      }
    
      set name(newName) {
        this._name = newName;
      }
    }
    
    const john = new Person("John");
    console.log(john.name); // 输出: John
    john.name = "Jane";
    console.log(john.name); // 输出: Jane
    

    在这个例子中,name 属性有一个 getter 和 setter 方法。当读取 name 属性时,会调用 getter 方法返回 _name 的值。当设置 name 属性时,会调用 setter 方法更新 _name 的值

  4. 抽象类和接口

    JavaScript 不支持原生的抽象类和接口,但可以通过约定和设计模式来实现类似的功能。例如,您可以定义一个抽象基类,并要求子类实现特定的方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    class AbstractShape {
      constructor(color) {
        this.color = color;
      }
    
      // 抽象方法,子类必须实现
      getArea() {
        throw new Error("getArea() must be implemented in subclass.");
      }
    }
    
    class Circle extends AbstractShape {
      constructor(color, radius) {
        super(color);
        this.radius = radius;
      }
    
      getArea() {
        return Math.PI * this.radius ** 2;
      }
    }
    
    const circle = new Circle("red", 5);
    console.log(circle.getArea()); // 78.53981633974483
    

    在这个例子中,AbstractShape 类定义了一个抽象的 getArea 方法,要求子类必须实现该方法。Circle 类继承自 AbstractShape 并提供了 getArea 的具体实现。

类的完整例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
// 1. 静态方法和属性
class MathUtils {
  static PI = 3.14159;

  static add(a, b) {
    return a + b;
  }

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

console.log(MathUtils.PI); // 3.14159
console.log(MathUtils.add(2, 3)); // 5
console.log(MathUtils.subtract(5, 3)); // 2

// 2. 访问控制 - 私有属性和方法
class BankAccount {
  #balance = 0; // 私有属性
  #transactionHistory = []; // 私有属性

  deposit(amount) {
    this.#balance += amount;
    this.#recordTransaction("deposit", amount);
  }

  withdraw(amount) {
    if (amount <= this.#balance) {
      this.#balance -= amount;
      this.#recordTransaction("withdrawal", amount);
    } else {
      console.log("Insufficient funds.");
    }
  }

  getBalance() {
    return this.#balance;
  }

  #recordTransaction(type, amount) {
    // 私有方法
    this.#transactionHistory.push({ type, amount });
  }
}

const account = new BankAccount();
account.deposit(1000);
account.withdraw(500);
console.log(account.getBalance()); // 500
// console.log(account.#balance); // Error: Private field '#balance' must be declared in an enclosing class

// 3. Getter 和 Setter
class Person {
  constructor(firstName, lastName) {
    this._firstName = firstName;
    this._lastName = lastName;
  }

  get fullName() {
    return `${this._firstName} ${this._lastName}`;
  }

  set fullName(name) {
    [this._firstName, this._lastName] = name.split(" ");
  }
}

const john = new Person("John", "Doe");
console.log(john.fullName); // 输出: John Doe
john.fullName = "Jane Smith";
console.log(john.fullName); // 输出: Jane Smith

// 4. 继承和多态
class Animal {
  constructor(name) {
    this.name = name;
  }

  makeSound() {
    console.log("The animal makes a sound.");
  }
}

class Dog extends Animal {
  constructor(name) {
    super(name);
  }

  makeSound() {
    console.log(`${this.name} barks.`);
  }
}

class Cat extends Animal {
  constructor(name) {
    super(name);
  }

  makeSound() {
    console.log(`${this.name} meows.`);
  }
}

const dog = new Dog("Buddy");
const cat = new Cat("Whiskers");

dog.makeSound(); // 输出: Buddy barks.
cat.makeSound(); // 输出: Whiskers meows.
  1. 静态方法和属性:

    • MathUtils 类包含一个静态属性 PI 和两个静态方法 addsubtract
    • 静态成员属于类本身,可以直接通过类名访问,而不需要创建类的实例。
  2. 访问控制 - 私有属性和方法:

    • BankAccount 类包含两个私有属性 #balance#transactionHistory
    • 类内部的 depositwithdraw#recordTransaction 方法可以访问这些私有属性。
    • 通过 getBalance 方法可以读取 #balance 的值,但不能直接访问它。
    • 私有属性和方法以 # 开头,只能在类内部访问。
  3. Getter 和 Setter:

    • Person 类包含 _firstName_lastName 作为私有属性。
    • fullName 属性是一个 getter 和 setter 方法,用于获取和设置全名。
    • 当读取 fullName 时,会返回拼接后的全名。当设置 fullName 时,会更新 _firstName_lastName
  4. 继承和多态:

    • Animal 是父类,包含 name 属性和 makeSound 方法。
    • DogCatAnimal 的子类,都重写了 makeSound 方法,实现了多态。
    • 在创建 DogCat 实例时,调用 makeSound 方法会根据对象的实际类型执行不同的行为。

通过这个例子,您可以看到 JavaScript 类的各种高级特性,包括静态方法和属性、访问控制、getter/setter 以及继承和多态。这些特性可以帮助您创建更加灵活、可扩展和可维护的代码

JavaScript Work

异步编程

  1. 回调函数:

    • 回调函数是作为参数传递给另一个函数的函数
    • 当某个事件发生或某个操作完成时,这个函数就会被调用
    • 使用回调函数是 JavaScript 中实现异步编程的传统方式
    • 回调函数可能会导致”回调地狱”的问题,代码嵌套层次过深
  2. Promise 和 async/await:

    • Promise 是 JavaScript 中用于处理异步操作的一种语法糖
    • Promise 有三种状态:pending、fulfilled 和 rejected
    • Promise 提供了更优雅的异步编程方式,避免了回调地狱
    • async 函数返回一个 Promise,await 关键字用于等待 Promise 完成
    • async/await 使得异步代码看起来更加同步和易读
  3. 事件循环和事件队列:

    • JavaScript 是单线程语言,使用事件循环机制来处理异步操作
    • 事件循环包括:栈、队列(任务队列)、微任务队列和 Web APIs
    • 同步任务进入栈,异步任务进入任务队列或微任务队列
    • 事件循环不断检查栈是否为空,然后执行队列中的任务
    • 微任务队列中的任务优先于任务队列中的任务执行
  4. 定时器(setTimeout、setInterval):
    • setTimeout() 函数用于在指定延迟后执行回调函数
    • setInterval() 函数用于每隔指定时间就执行回调函数
    • 定时器不会阻塞主线程,而是由浏览器的 Web API 处理
    • 定时器的实际执行时间可能会晚于设定的时间
    • 可以使用 clearTimeout()clearInterval() 取消定时器
  5. 完整例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    
    // 回调函数
    function fetchData(callback) {
      // 模拟异步操作
      setTimeout(() => {
        const data = { name: "John Doe", age: 30 };
        callback(data);
      }, 2000);
    }
    
    fetchData((data) => {
      console.log("Callback data:", data);
    });
    
    // Promise
    function fetchDataPromise() {
      return new Promise((resolve, reject) => {
        // 模拟异步操作
        setTimeout(() => {
          const data = { name: "Jane Smith", age: 25 };
          resolve(data);
        }, 3000);
      });
    }
    
    fetchDataPromise()
      .then((data) => {
        console.log("Promise data:", data);
      })
      .catch((error) => {
        console.error("Promise error:", error);
      });
    
    // async/await
    async function fetchDataAsync() {
      try {
        const data = await fetchDataPromise();
        console.log("Async/await data:", data);
      } catch (error) {
        console.error("Async/await error:", error);
      }
    }
    
    fetchDataAsync();
    
    // 事件循环和事件队列
    console.log("Start");
    
    setTimeout(() => {
      console.log("Timeout callback");
    }, 0);
    
    Promise.resolve("Promise resolved").then((data) => {
      console.log("Microtask:", data);
    });
    
    console.log("End");
    
    // 定时器
    console.log("Start");
    
    setTimeout(() => {
      console.log("Timeout 1 callback");
    }, 2000);
    
    setTimeout(() => {
      console.log("Timeout 2 callback");
    }, 0);
    
    console.log("End");
    
    • 解释

      1. 使用回调函数处理异步操作。
      2. 使用 Promise 和 async/await 的方式处理异步操作。
      3. 演示了事件循环和事件队列的运行机制。
      4. 展示了使用 setTimeout()setInterval() 实现的定时器。

模块化

  1. ES6 模块系统 (import/export):

    • ES6 引入了原生的模块化系统,使用 importexport 关键字
    • export 用于导出模块中的变量、函数、类等
    • import 用于导入其他模块中导出的内容
    • ES6 模块是静态的,在编译时确定依赖关系
    • ES6 模块支持命名导出、默认导出等多种导出方式
  2. CommonJS 模块 (require/exports):

    • CommonJS 是 Node.js 中使用的模块化规范
    • 使用 require() 函数导入模块,exports 对象导出模块
    • CommonJS 模块是动态的,在运行时确定依赖关系
    • CommonJS 模块系统广泛应用于 Node.js 生态圈
  3. 模块打包工具 (Webpack、Rollup 等):

    • Webpack 和 Rollup 是常见的 JavaScript 模块打包工具
    • 它们可以将多个模块打包成一个或多个文件,优化代码
    • Webpack 支持多种模块化规范(ES6、CommonJS、AMD 等)
    • Rollup 主要优化 ES6 模块,生成更小更高效的 bundle
    • 这些工具还提供许多其他功能,如代码分割、热更新等

这三个知识点涵盖了 JavaScript 模块化的核心内容:

  1. ES6 模块系统使用 importexport 进行模块化,是原生的模块化方案。
  2. CommonJS 模块系统使用 require()exports 对象进行模块化,广泛用于 Node.js。
  3. Webpack 和 Rollup 等模块打包工具可以将多个模块打包成一个或多个文件,优化代码。

模块化例子

  • math.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    // 导出函数
    export function add(a, b) {
      return a + b;
    }
    
    export function subtract(a, b) {
      return a - b;
    }
    
    // 默认导出
    export default function multiply(a, b) {
      return a * b;
    }
    
  • utils.js(CommonJS 模块)

    1
    2
    3
    4
    5
    6
    7
    8
    
    // 导出对象
    exports.capitalize = function (str) {
      return str.charAt(0).toUpperCase() + str.slice(1);
    };
    
    exports.reverse = function (str) {
      return str.split("").reverse().join("");
    };
    
  • 使用 Webpack 将这些模块打包

    1. 安装 Webpack 和必要的依赖:

      1
      
      npm install --save-dev webpack webpack-cli
      
    2. 创建 Webpack 配置文件 webpack.config.js:

      1
      2
      3
      4
      5
      6
      7
      
      module.exports = {
        entry: "./index.js",
        output: {
          filename: "bundle.js",
          path: __dirname + "/dist",
        },
      };
      
    3. 创建 index.js 文件,导入并使用这些模块:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      
      // 导入 ES6 模块
      import multiply, { add, subtract } from "./math";
      // 导入 CommonJS 模块
      const { capitalize, reverse } = require("./utils");
      
      console.log(add(2, 3)); // 输出: 5
      console.log(subtract(5, 3)); // 输出: 2
      console.log(multiply(4, 5)); // 输出: 20
      console.log(capitalize("hello")); // 输出: Hello
      console.log(reverse("world")); // 输出: dlrow
      
    4. 运行 Webpack 打包:

      1
      
      npx webpack
      

    完成以上步骤后,Webpack 会将 index.js 中导入的所有模块打包成一个 bundle.js 文件,放在 dist 文件夹中。

  • 解释

    1. 使用 ES6 模块系统导入导出函数。
    2. 使用 CommonJS 模块系统导入导出对象。
    3. 使用 Webpack 将这些模块打包成一个 bundle 文件。

Web API 和浏览器环境

  • 浏览器 API(Fetch API、Web Storage API、Canvas API 等)
  • 事件处理(鼠标、键盘、滚动等事件)
  • 浏览器存储(Cookie、Web Storage)
  • 网络请求(Ajax、Fetch)

现代 JavaScript 特性

  • ES6+(let/const、模板字面量、解构赋值等)
  • 类(class 关键字)
  • 模块(import/export)
  • 承诺(Promise)
  • 异步/等待(async/await)
  • 箭头函数
  • 扩展运算符和 rest 参数

Web 应用开发

  • 前端框架和库(React、Vue.js、Angular)
  • 状态管理(Redux、Vuex)
  • 路由管理
  • 构建工具(Webpack、Gulp、Rollup)
  • 测试框架(Jest、Enzyme、Cypress)

其他高级主题

  • 函数式编程
  • 元编程(Proxy、Reflect)
  • 装饰器
  • Web Workers
  • 服务器端 JavaScript(Node.js)
This post is licensed under CC BY 4.0 by the author.