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/ |
- 同样需要注意的是
typeof
操作符可用于检查变量的基本数据类型。instanceof
操作符可用于检查对象是否是某个构造函数的实例。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"
变量声明
-
var
:var
是最传统的声明变量的方式。- 使用
var
声明的变量具有函数作用域或全局作用域。 - 如果没有初始化,变量的值将为
undefined
。 - 例如:
var x = 5;
-
let
:let
是 ES6 (ECMAScript 2015) 引入的新语法。let
声明的变量具有块级作用域,比var
更加局部化。- 使用
let
声明的变量可以被重新赋值,但不能被重复声明。 - 例如:
let y = 10;
-
const
:const
也是 ES6 引入的新语法。const
声明的变量是常量,其值不能被修改。const
声明的变量必须在声明时初始化,且初始化后不能被重新赋值。- 常量通常用于定义不会变化的值,如配置项。
- 例如:
const PI = 3.14159;
var
是较旧的方式,let
和const
是较新的方式,具有更好的作用域控制和安全性- 开发中,通常建议使用
let
和const
来声明变量,除非您有特定的需要使用var
的场景
三者区别
特性 | var |
let |
const |
---|---|---|---|
作用域 | 函数作用域/全局作用域 | 块级作用域 | 块级作用域 |
变量提升 | 被提升到作用域顶部,初始化为 undefined |
不会被提升,使用前必须声明 | 不会被提升,使用前必须声明 |
可重复声明 | 允许在同一作用域内重复声明 | 不允许在同一作用域内重复声明 | 不允许在同一作用域内重复声明 |
可重新赋值 | 可以 | 可以 | 不可以 |
默认值 | undefined |
undefined |
必须初始化,否则报错 |
从上表可以看出,let
和 const
相比 var
有以下优势:
- 更好的作用域控制,避免变量污染全局命名空间。
- 更安全的使用,避免无意中覆盖变量的问题。
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)
-
全局作用域(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!'
-
函数作用域(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); // 报错,无法访问函数内部的变量
-
块级作用域(Block Scope):
- 在
{}
内部定义的变量和函数只能在该块内部访问。 - 在 ES6 引入的
let
和const
关键字创建了块级作用域。 -
例如:
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); // 报错,无法访问
- 在
-
模块作用域(Module Scope):
- 在 ES6 引入的
import
和export
语句创建的作用域。 - 模块内部定义的变量和函数只能在该模块内部访问,除非通过
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(); // 可以访问导出的函数
- 在 ES6 引入的
函数
常见函数
分类 | 函数 |
---|---|
控制台函数 | 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);
自定义函数
-
JavaScript 函数定义:
- 函数声明:
function functionName(parameters) { // function body }
- 函数表达式:
let functionName = function(parameters) { // function body }
- 箭头函数:
let functionName = (parameters) => { // function body }
- 函数声明:
-
JavaScript 函数参数:
- 形参: 在函数定义时指定的参数
- 实参: 在函数调用时传递的参数
- 默认参数: 可以为参数指定默认值,当没有传递实参时使用默认值
- 不定参数: 使用
...
语法可以创建接受任意数量参数的函数
-
JavaScript 函数调用:
- 直接调用:
functionName(arguments)
- 方法调用:
object.methodName(arguments)
- 构造函数调用:
new FunctionName(arguments)
- 间接调用: 使用
call()
,apply()
,bind()
- 直接调用:
-
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()
等,用于对数组进行转换、过滤和聚合操作。
-
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]
-
-
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]
-
-
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
-
-
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
-
-
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
事件处理
-
事件监听器:
- 事件监听器是一个函数,它会在特定的事件发生时被调用。
- 我们可以使用
addEventListener()
方法为 DOM 元素添加事件监听器。 -
示例:
1 2 3 4
let button = document.getElementById("myButton"); button.addEventListener("click", function () { console.log("Button was clicked!"); });
-
常见的事件类型:
click
: 当用户点击元素时触发。mouseover
/mouseout
: 当鼠标移入/移出元素时触发。keydown
/keyup
: 当用户按下/松开键盘按键时触发。submit
: 当表单提交时触发。load
: 当页面或资源加载完成时触发。
-
事件对象:
- 事件监听器函数会接收一个事件对象作为参数。
- 事件对象包含了关于事件的各种信息,如事件类型、目标元素、鼠标坐标等。
-
示例:
1 2 3 4
button.addEventListener("click", function (event) { console.log("Event type:", event.type); console.log("Target element:", event.target); });
-
事件传播:
- 事件从目标元素开始向上”冒泡”,一直到
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(); });
- 事件从目标元素开始向上”冒泡”,一直到
-
事件委托:
- 事件委托是一种优化事件处理的技术。
- 我们可以将事件处理程序附加到父元素上,而不是每个子元素。
- 当事件在子元素上触发时,会冒泡到父元素,父元素可以处理该事件。
-
示例:
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>
-
解释
- 事件监听器: 为按钮添加一个
click
事件监听器。 - 事件对象: 监听输入框的
keyup
事件,并使用事件对象获取按下的键。 - 事件传播: 监听父元素和子元素的
click
事件,并使用stopPropagation()
阻止事件冒泡。 - 事件委托: 将点击处理程序添加到父元素上,当点击子元素时,事件会冒泡到父元素并被处理。
- 表单提交: 监听表单的
submit
事件,并使用preventDefault()
阻止表单的默认提交行为。
- 事件监听器: 为按钮添加一个
报错
JavaScript 中,当出现问题时,会抛出错误。错误可能是由于代码逻辑错误、无效输入、资源不可用等原因引起的。
-
错误类型:
- SyntaxError: 代码语法错误。
- ReferenceError: 尝试访问不存在的变量。
- TypeError: 对不正确类型的操作。
- RangeError: 数值超出有效范围。
- URIError: 使用不正确的编码或解码 URI 组件。
- EvalError:
eval()
函数使用不当。
-
错误处理:
-
try-catch 语句:
- 使用
try
块包裹可能会抛出错误的代码。 - 使用
catch
块捕获并处理抛出的错误。 -
示例:
1 2 3 4 5 6
try { // 可能会抛出错误的代码 let result = parseInt("abc"); } catch (error) { console.error("Error occurred:", error.message); }
- 使用
-
错误对象:
- 捕获的错误对象包含有关错误的信息,如错误类型、错误消息和堆栈追踪。
- 可以使用
error.message
、error.name
和error.stack
属性访问错误信息。
-
-
自定义错误:
- 可以使用
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"); } }
- 可以使用
-
错误处理最佳实践:
- 始终使用
try-catch
块包裹可能抛出错误的代码。 - 在
catch
块中,根据错误类型提供适当的错误处理。 - 记录并显示有意义的错误信息,以便于调试和错误排查。
- 对于严重错误,可以考虑向用户显示友好的错误消息。
- 在生产环境中,可以使用全局错误处理机制捕获未处理的错误。
- 始终使用
调试
-
浏览器开发者工具:
- 所有主流浏览器都提供了强大的开发者工具,如 Chrome 的 DevTools、Firefox 的 Developer Tools 和 Edge 的 F12 开发者工具。
- 这些工具提供了丰富的调试功能,包括断点调试、变量检查、网络监控、性能分析等。
- 使用这些工具可以帮助您快速定位和解决代码中的问题。
-
断点调试:
- 在代码中设置断点,可以在执行到该点时暂停程序的运行。
- 这样您就可以检查变量的值、观察执行流程,并逐步执行代码以找出问题所在。
- 大多数浏览器开发者工具都支持断点调试功能。
-
console 对象:
console
对象提供了各种调试和记录输出的方法,如console.log()
、console.error()
、console.warn()
等。- 通过在代码中添加
console.log()
语句,可以输出变量值、对象属性等信息,帮助您跟踪程序的执行过程。
-
Source Map:
- 当使用 JavaScript 构建工具(如 Webpack、Rollup 等)时,生成的 JavaScript 文件通常会被压缩/转换,使得调试变得困难。
- Source Map 是一种映射关系,可以将压缩后的 JavaScript 代码与源代码进行关联,在开发者工具中显示原始的源代码。
- 使用 Source Map 可以大大提高调试体验。
-
debugger 关键字:
debugger
关键字可以在代码中设置一个断点,类似于在开发者工具中手动设置断点。- 当程序执行到
debugger
语句时,它会自动暂停并进入调试模式。
-
异步调试:
- 调试异步代码(如 Promise、async/await)可能会更加复杂。
- 使用
async/await
配合断点调试可以帮助您更好地理解异步代码的执行过程。 - 一些调试工具,如 Chrome DevTools 的 “Pause on uncaught exceptions” 选项,也可以帮助您捕获异步代码中的错误。
-
日志记录和错误报告:
- 在生产环境中,使用日志记录工具(如
console.error()
)记录错误信息和堆栈追踪。 - 可以结合错误报告服务(如 Sentry、Rollbar 等)自动收集和分析应用程序中的错误,以便快速修复问题。
- 在生产环境中,使用日志记录工具(如
-
单元测试:
- 编写全面的单元测试可以帮助您提前发现并修复代码中的问题。
- 单元测试还可以作为一种回归测试,确保代码修改不会破坏原有的功能。
进阶
异步
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();
-
解释
async
关键字标识fetchData()
函数为异步函数。- 在函数内部,
await
关键字用于等待异步操作的结果,如fetch()
调用和response.json()
解析。 - 任何
await
表达式都会暂停函数的执行,直到相应的 Promise 被解决。 - 如果发生错误,
try/catch
块会捕获并处理异常。
-
使用
async/await
的优势包括:- 简化异步代码: 相比使用回调函数或 Promise 链,
async/await
提供了一种更加同步和线性的编码方式,使代码更加易读和维护。 - 错误处理:
try/catch
块可以优雅地处理异步操作中的错误,避免了嵌套回调函数或 Promise 链中的错误处理问题。 - 并发控制: 您可以在
async
函数内部并行执行多个异步操作,并使用Promise.all()
等方法来协调它们的执行。 - composition:
async
函数可以被其他async
函数调用,形成一个嵌套的异步操作流程。
- 简化异步代码: 相比使用回调函数或 Promise 链,
需要注意的是,
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.` ); } }
-
解释
class Person
定义了一个名为Person
的类。constructor
是一种特殊的方法,用于初始化新创建的Person
对象。它接受name
和age
参数,并将它们分别赋值给对象的name
和age
属性。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.
-
解释
new Person('John', 30)
创建了一个新的Person
对象,并将其存储在变量john
中。- 调用
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.
-
解释
Student
类继承自Person
类,使用extends
关键字。constructor
方法调用super(name, age)
来初始化从父类Person
继承的name
和age
属性。Student
类新增了grade
属性和study
方法。- 创建
Student
类的实例alice
,可以访问从Person
继承的greet
方法以及自己的study
方法。
类的进阶
-
静态方法和属性
类中除了实例方法和属性,还可以定义静态方法和属性。静态成员属于类本身,而不是类的实例。
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
来访问,而不需要创建类的实例。 -
访问控制
JavaScript 类支持基本的访问控制修饰符,包括
public
(默认)、private
和protected
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
方法可以读取私有属性的值。 -
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
的值 -
抽象类和接口
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.
-
静态方法和属性:
MathUtils
类包含一个静态属性PI
和两个静态方法add
和subtract
。- 静态成员属于类本身,可以直接通过类名访问,而不需要创建类的实例。
-
访问控制 - 私有属性和方法:
BankAccount
类包含两个私有属性#balance
和#transactionHistory
。- 类内部的
deposit
、withdraw
和#recordTransaction
方法可以访问这些私有属性。 - 通过
getBalance
方法可以读取#balance
的值,但不能直接访问它。 - 私有属性和方法以
#
开头,只能在类内部访问。
-
Getter 和 Setter:
Person
类包含_firstName
和_lastName
作为私有属性。fullName
属性是一个 getter 和 setter 方法,用于获取和设置全名。- 当读取
fullName
时,会返回拼接后的全名。当设置fullName
时,会更新_firstName
和_lastName
。
-
继承和多态:
Animal
是父类,包含name
属性和makeSound
方法。Dog
和Cat
是Animal
的子类,都重写了makeSound
方法,实现了多态。- 在创建
Dog
和Cat
实例时,调用makeSound
方法会根据对象的实际类型执行不同的行为。
通过这个例子,您可以看到 JavaScript 类的各种高级特性,包括静态方法和属性、访问控制、getter/setter 以及继承和多态。这些特性可以帮助您创建更加灵活、可扩展和可维护的代码
JavaScript Work
异步编程
-
回调函数:
- 回调函数是作为参数传递给另一个函数的函数
- 当某个事件发生或某个操作完成时,这个函数就会被调用
- 使用回调函数是 JavaScript 中实现异步编程的传统方式
- 回调函数可能会导致”回调地狱”的问题,代码嵌套层次过深
-
Promise 和 async/await:
- Promise 是 JavaScript 中用于处理异步操作的一种语法糖
- Promise 有三种状态:pending、fulfilled 和 rejected
- Promise 提供了更优雅的异步编程方式,避免了回调地狱
async
函数返回一个 Promise,await
关键字用于等待 Promise 完成async/await
使得异步代码看起来更加同步和易读
-
事件循环和事件队列:
- JavaScript 是单线程语言,使用事件循环机制来处理异步操作
- 事件循环包括:栈、队列(任务队列)、微任务队列和 Web APIs
- 同步任务进入栈,异步任务进入任务队列或微任务队列
- 事件循环不断检查栈是否为空,然后执行队列中的任务
- 微任务队列中的任务优先于任务队列中的任务执行
- 定时器(setTimeout、setInterval):
setTimeout()
函数用于在指定延迟后执行回调函数setInterval()
函数用于每隔指定时间就执行回调函数- 定时器不会阻塞主线程,而是由浏览器的 Web API 处理
- 定时器的实际执行时间可能会晚于设定的时间
- 可以使用
clearTimeout()
和clearInterval()
取消定时器
-
完整例子
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");
-
解释
- 使用回调函数处理异步操作。
- 使用 Promise 和
async/await
的方式处理异步操作。 - 演示了事件循环和事件队列的运行机制。
- 展示了使用
setTimeout()
和setInterval()
实现的定时器。
-
模块化
-
ES6 模块系统 (import/export):
- ES6 引入了原生的模块化系统,使用
import
和export
关键字 export
用于导出模块中的变量、函数、类等import
用于导入其他模块中导出的内容- ES6 模块是静态的,在编译时确定依赖关系
- ES6 模块支持命名导出、默认导出等多种导出方式
- ES6 引入了原生的模块化系统,使用
-
CommonJS 模块 (require/exports):
- CommonJS 是 Node.js 中使用的模块化规范
- 使用
require()
函数导入模块,exports
对象导出模块 - CommonJS 模块是动态的,在运行时确定依赖关系
- CommonJS 模块系统广泛应用于 Node.js 生态圈
-
模块打包工具 (Webpack、Rollup 等):
- Webpack 和 Rollup 是常见的 JavaScript 模块打包工具
- 它们可以将多个模块打包成一个或多个文件,优化代码
- Webpack 支持多种模块化规范(ES6、CommonJS、AMD 等)
- Rollup 主要优化 ES6 模块,生成更小更高效的 bundle
- 这些工具还提供许多其他功能,如代码分割、热更新等
这三个知识点涵盖了 JavaScript 模块化的核心内容:
- ES6 模块系统使用
import
和export
进行模块化,是原生的模块化方案。 - CommonJS 模块系统使用
require()
和exports
对象进行模块化,广泛用于 Node.js。 - 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 将这些模块打包
-
安装 Webpack 和必要的依赖:
1
npm install --save-dev webpack webpack-cli
-
创建 Webpack 配置文件
webpack.config.js
:1 2 3 4 5 6 7
module.exports = { entry: "./index.js", output: { filename: "bundle.js", path: __dirname + "/dist", }, };
-
创建
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
-
运行 Webpack 打包:
1
npx webpack
完成以上步骤后,Webpack 会将
index.js
中导入的所有模块打包成一个bundle.js
文件,放在dist
文件夹中。 -
-
解释
- 使用 ES6 模块系统导入导出函数。
- 使用 CommonJS 模块系统导入导出对象。
- 使用 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)