JavaScript 前端知識體系
📌 說明:本大綱從基礎到高級、從語法到應用、從面試到實戰,分層級講解 JavaScript 的核心內容。
一、JavaScript 基礎語法
1.1 基本概念
1.1.1 JavaScript 的發展史與用途
1. 發展簡史
- 1995 年:JavaScript 由 Netscape 工程師 Brendan Eich 在 10 天 內創建,最初叫 LiveScript,后更名為 JavaScript。
- 早期用途:僅用于網頁中的簡單表單校驗和交互動畫。
- 1997 年:被 ECMA 國際標準化組織采納,形成 ECMAScript 標準。
- 2009 年:Node.js 橫空出世,JS 從瀏覽器進入服務端。
- 2015 年:ES6(ECMAScript 2015)發布,成為 JS 的重大飛躍。
2. 現代用途
JavaScript 已不再只是“網頁腳本語言”,現在幾乎無所不能:
- 瀏覽器交互開發(HTML+CSS+JS)
- Web 應用開發(React/Vue/Angular)
- 服務端開發(Node.js)
- 移動端開發(React Native)
- 桌面應用開發(Electron)
- 游戲開發(Pixi.js、Three.js)
- 自動化測試、爬蟲、AI 前端可視化等
🎯 一句話總結:JS 是一門“瀏覽器起家、全棧統治、無處不在”的語言。
1.1.2 瀏覽器中的 JS 與 Node.js 的區別
特性 | 瀏覽器中的 JS | Node.js |
---|---|---|
運行環境 | 瀏覽器(Chrome、Firefox 等) | 服務端(基于 V8 引擎) |
核心目標 | 實現用戶交互、DOM 操作 | 搭建 Web 服務、處理后端邏輯 |
可訪問的 API | DOM、BOM(window、document)等 | 文件系統(fs)、網絡模塊(http) |
模塊系統 | ES Module(ES6后支持) | CommonJS |
全局對象 | window | global |
適合應用場景 | 網頁開發、瀏覽器插件 | 接口服務、工具腳本、構建工具等 |
🚀 一句話記憶:瀏覽器 JS 用于“看得見的交互”,Node.js 用于“看不見的服務”。
1.1.3 動態類型語言 vs 靜態類型語言
1. 類型系統簡介
- 類型:變量可以表示的數據類型,例如字符串、數字、布爾值等。
- 類型系統:語言如何檢測、限制這些數據類型的規則體系。
2. 對比分析
特性 | 動態類型語言(如 JS) | 靜態類型語言(如 Java、C++) |
---|---|---|
聲明變量 | 不需指定類型,運行時才知道 | 聲明時必須指定類型 |
類型檢查 | 在運行時進行 | 在編譯階段進行 |
靈活性 | 高,變量可變類型 | 低,但可提升可靠性 |
出錯時間點 | 運行時才報錯 | 編譯時就能發現類型錯誤 |
開發體驗 | 快速開發,適合原型迭代 | 安全穩定,適合大型項目 |
3. 示例對比
// JS - 動態類型
let x = 10;
x = "hello"; // 合法,x 類型變為 string
// Java - 靜態類型
int x = 10;
x = "hello"; // ? 報錯,類型不匹配
💡 總結一句話:JS 是“你想放什么我都裝”,Java 是“你不給我指定我不干活”。
1.2 數據類型
JavaScript 中的數據類型分為兩大類:原始類型(Primitive Type) 和 引用類型(Reference Type)。
1.2.1 原始類型(7 種)
原始類型是不可變值,每個變量直接存儲值本身,保存在棧內存中。
類型 | 示例值 | 說明 |
---|---|---|
String | "hello" | 表示文本 |
Number | 42 , 3.14 , NaN | 所有數字(包含整數和浮點數) |
Boolean | true , false | 邏輯值 |
Undefined | undefined | 未賦值變量的默認值 |
Null | null | 表示“無值” |
Symbol | Symbol("id") | 創建獨一無二的標識符(ES6) |
BigInt | 12345678901234567890n | 表示任意精度整數(ES11) |
示例:
let name = "Alice"; // String
let age = 30; // Number
let isAdmin = true; // Boolean
let user; // Undefined
let empty = null; // Null
let key = Symbol("id"); // Symbol
let bigNumber = 123456789012345678901234567890n; // BigInt
🧠 注意:
typeof null === "object"
是歷史遺留 bug,不代表 null 是引用類型!
1.2.2 引用類型(常見 5 類)
引用類型是可變對象,變量保存的是指向值的地址(引用),保存在堆內存中。
類型 | 示例 | 特點 |
---|---|---|
Object | {name: "Tom"} | 萬物皆對象的基礎類型 |
Array | [1, 2, 3] | 有序集合,索引訪問 |
Function | function() {} | 可調用對象,函數是一等公民 |
Date | new Date() | 日期對象 |
RegExp | /\d+/ | 正則表達式對象 |
示例:
let person = { name: "Tom", age: 25 }; // Object
let numbers = [1, 2, 3]; // Array
let greet = function() { console.log("Hi"); }; // Function
let now = new Date(); // Date
let pattern = /\d+/; // RegExp
1.2.3 原始類型 vs 引用類型 對比總結
特性 | 原始類型 | 引用類型 |
---|---|---|
存儲方式 | 棧內存,值拷貝 | 堆內存,引用拷貝(指針) |
是否可變 | 不可變(每次修改都創建新值) | 可變(修改對象本身) |
比較方式 | 值比較(===) | 引用地址比較 |
復制行為 | 復制值 | 復制引用,多個變量指向同一對象 |
示例比較:
let a = 100;
let b = a;
b = 200;
console.log(a); // 100(原始類型,值獨立)let obj1 = { x: 1 };
let obj2 = obj1;
obj2.x = 99;
console.log(obj1.x); // 99(引用類型,地址共享)
📌 一句話總結:原始類型像“復制粘貼”,引用類型像“共享文件夾”。
1.3 變量聲明
1.3.1 var
、let
、const
的區別
特性 | var | let | const |
---|---|---|---|
聲明方式 | ES5,函數級作用域 | ES6,塊級作用域 | ES6,塊級作用域 |
變量提升 | ? 有提升,值為 undefined | ? 有提升但不初始化 | ? 有提升但不初始化 |
允許重復聲明 | ? 允許 | ? 報錯 | ? 報錯 |
可重新賦值 | ? 可以 | ? 可以 | ? 不可重新賦值 |
是否必須初始化 | ? 不需要 | ? 不需要 | ? 必須初始化 |
示例:
// var
console.log(a); // undefined(已提升)
var a = 10;// let
console.log(b); // ReferenceError(暫時性死區)
let b = 20;// const
const c = 30;
c = 40; // ? 報錯,不能重新賦值
🔒 記憶口訣:
var
會提升,let
會保護,const
定值不可改。
1.3.2 作用域與變量提升(Hoisting)
- 函數作用域(Function Scope):
var
聲明的變量只在函數內部有效。 - 塊級作用域(Block Scope):
let
和const
聲明的變量只在當前代碼塊{}
內有效。
function test() {if (true) {var x = 10;let y = 20;}console.log(x); // ? 輸出 10console.log(y); // ? 報錯
}
- 變量提升(Hoisting):JavaScript 在運行前會“預處理”變量和函數聲明,使它們“看起來”被提升到作用域頂部。
console.log(a); // undefined
var a = 5;
1.4 運算符與表達式
1.4.1 算術、比較、邏輯運算符
- 算術運算符:
+
-
*
/
%
**
- 比較運算符:
>
<
>=
<=
==
===
!=
!==
- 邏輯運算符:
&&
||
!
2 ** 3 // 8,冪運算
3 > 2 // true
true && false // false
1.4.2 其他運算符
- 三元運算符:
條件 ? 值1 : 值2
let result = score >= 60 ? "及格" : "不及格";
- 位運算符:
&
|
^
~
<<
>>
(適用于底層優化,如權限控制、性能壓縮) - 空值合并運算符(
??
):僅在左值為null
或undefined
時使用右值。
let name = userName ?? "默認用戶";
1.5 流程控制
1.5.1 條件判斷
- if…else
if (score >= 90) {console.log("優秀");
} else if (score >= 60) {console.log("及格");
} else {console.log("不及格");
}
- switch…case
let color = "green";
switch (color) {case "red":console.log("紅色");break;case "green":console.log("綠色");break;default:console.log("未知顏色");
}
1.5.2 循環結構
- for 循環
for (let i = 0; i < 3; i++) {console.log(i);
}
- while / do…while
let i = 0;
while (i < 3) {console.log(i);i++;
}do {console.log(i);i--;
} while (i > 0);
- for…in:遍歷對象的鍵
let obj = { a: 1, b: 2 };
for (let key in obj) {console.log(key); // "a", "b"
}
- for…of:遍歷可迭代對象的值(如數組、字符串)
for (let value of [10, 20, 30]) {console.log(value); // 10, 20, 30
}
🔁 記憶總結:
for...in
用來遍歷“對象鍵名”,for...of
用來遍歷“數組值”。
二、函數與作用域
2.1 函數定義方式
2.1.1 函數聲明(Function Declaration)
function sayHi(name) {return `Hello, ${name}`;
}
- ? 支持提升:可以在函數聲明之前調用
- ? 語義清晰,適合通用工具函數
- 📌 函數體內 this 指向調用者
greet(); // ? 輸出 "Hi there!"function greet() {console.log("Hi there!");
}
2.1.2 函數表達式(Function Expression)
const sayHi = function(name) {return `Hello, ${name}`;
};
- ? 不支持提升:調用必須在定義之后
- ? 更靈活:可作為參數傳遞、閉包使用
- ? 命名 or 匿名:支持匿名函數
// 報錯:Cannot access 'greet' before initialization
greet();const greet = function () {console.log("Hello");
};
2.1.3 箭頭函數(Arrow Function)
const sayHi = (name) => {return `Hello, ${name}`;
};
- ? 語法簡潔
- ? 沒有自己的
this
、arguments
- ? 不能當構造函數使用
簡寫形式(單參數、單表達式可省略括號與 return):
const double = x => x * 2;
this 對比示例:
const obj = {normal: function() {console.log(this); // 指向 obj},arrow: () => {console.log(this); // 指向定義時外部的 this(可能是 window 或 undefined)}
};
🧠 一圖記憶:
類型 | 是否提升 | 是否有 this | 是否能 new | 是否簡潔 |
---|---|---|---|---|
函數聲明 | ? | ? | ? | ? |
函數表達式 | ? | ? | ? | ? |
箭頭函數 | ? | ?(繼承) | ? | ??? |
2.2 作用域與閉包
2.2.1 詞法作用域(Lexical Scope)
定義:詞法作用域是指變量的作用范圍由代碼書寫位置決定,而非函數的調用方式。
function outer() {const a = 10;function inner() {console.log(a); // 可以訪問 a}inner();
}
outer();
- 📌 函數定義在哪里,就決定了它能訪問哪些變量。
- ? 內部函數可以訪問其外部函數作用域中的變量,這就是閉包的基礎。
💡 記憶口訣:作用域查找看“定義位置”,不是“調用位置”。
2.2.2 閉包原理與應用場景
📘 什么是閉包?
閉包(Closure)是函數與其詞法環境的組合。當函數在其定義的作用域外被調用時,它仍然能夠記住并訪問其定義時的作用域鏈。
function createCounter() {let count = 0;return function () {count++;return count;};
}const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
- 🔁
count
沒有被銷毀,因為返回的函數一直引用它,這就是閉包。 - 💡 JS 的函數是一等對象,可以被返回、賦值、傳參,而閉包能讓這些函數帶上“記憶”。
📌 閉包的常見應用場景
場景 | 示例 |
---|---|
數據私有 | 封裝內部變量不暴露到全局作用域 |
函數工廠 | 動態生成具有“私有數據”的函數 |
防抖節流 | 利用閉包保存定時器引用等狀態 |
記憶函數 | 緩存函數執行結果(Memoization) |
// 數據私有:模擬私有變量
function Secret() {let secret = "密碼123";return {get: () => secret,set: (val) => secret = val};
}
const s = Secret();
console.log(s.get()); // 密碼123
🧠 一句話總結:閉包 = 函數 + 定義時的作用域鏈,是 JS 中實現“私有狀態”和“記憶能力”的核心機制。
2.3 this 指向與 call/apply/bind
2.3.1 this 在不同上下文的指向規則
this
在 JavaScript 中非常靈活,它的指向是動態的,依賴于函數的調用方式。我們可以將它分為幾種主要的調用方式進行分類講解。
一、隱式綁定(Implicit Binding)
隱式綁定指的是 在對象的方法中調用函數時,this
指向調用該方法的對象。
const person = {name: "Alice",greet() {console.log(this.name);}
};
person.greet(); // "Alice"
- 在這個例子中,
this
指向person
對象。
二、顯式綁定(Explicit Binding)
顯式綁定是通過 call
、apply
和 bind
等方法顯式地指定 this
的指向。
-
call()
:立即調用函數,傳入this
指定的對象和后續參數。function greet() {console.log(`Hello, ${this.name}`); } const person = { name: "Bob" }; greet.call(person); // "Hello, Bob"
-
apply()
:類似call()
,但接收一個數組作為參數。greet.apply(person); // "Hello, Bob"
-
bind()
:返回一個新函數,綁定了this
,但不立即執行。const boundGreet = greet.bind(person); boundGreet(); // "Hello, Bob"
- 總結:
call
和apply
立即執行,而bind
返回一個新的函數。
三、默認綁定(Default Binding)
當函數被直接調用(例如普通函數調用時),this
的默認指向規則會根據執行環境有所不同:
- 在 非嚴格模式下,
this
會指向全局對象(瀏覽器中是window
,Node 中是global
)。 - 在 嚴格模式下,
this
為undefined
。
function greet() {console.log(this);
}greet(); // 在非嚴格模式下,指向 window;嚴格模式下,指向 undefined
四、構造函數綁定(Constructor Binding)
當函數作為構造函數通過 new
關鍵字調用時,this
會指向新創建的實例對象。
function Person(name) {this.name = name;
}const person1 = new Person("Alice");
console.log(person1.name); // "Alice"
new
關鍵字使得this
指向新創建的實例對象。
五、箭頭函數(Arrow Function)
箭頭函數沒有自己的 this
,它會繼承外層上下文的 this
。也就是說,箭頭函數的 this
是在定義時就確定的,而不是在調用時確定。
const obj = {name: "Alice",greet() {const arrowFunc = () => {console.log(this.name);};arrowFunc(); // "Alice"}
};obj.greet(); // "Alice"
arrowFunc
的this
指向的是外部的greet
方法中的this
,即obj
。
🎯 總結:
調用方式 | this 指向 |
---|---|
隱式綁定 | 調用方法的對象 |
顯式綁定 | call 、apply 、bind 參數指定的對象 |
默認綁定 | 非嚴格模式下為 window ,嚴格模式下為 undefined |
構造函數綁定 | 新創建的對象(實例) |
箭頭函數 | 繼承外部作用域的 this |
2.3.2 手動改變 this 指向的方法
有三個方法可以手動改變函數內部的 this 指向:
🧩 1)call()
立即調用函數,傳入第一個參數作為 this
,后續參數依次傳給函數本體。
function greet(who) {console.log(`Hello, ${who}, from ${this.name}`);
}
const person = { name: "Alice" };
greet.call(person, "Bob"); // Hello, Bob, from Alice
🧩 2)apply()
與 call
類似,但參數必須用數組傳入:
greet.apply(person, ["Charlie"]); // Hello, Charlie, from Alice
🧩 3)bind()
不會立即調用,而是返回一個新的函數,綁定了 this
。
const boundGreet = greet.bind(person, "Diana");
boundGreet(); // Hello, Diana, from Alice
🎯 對比總結:
方法 | 是否立即執行 | 參數傳遞方式 | 返回值 |
---|---|---|---|
call | ? 是 | 普通參數列表 | 函數執行結果 |
apply | ? 是 | 參數數組 | 函數執行結果 |
bind | ? 否 | 參數列表(可預設) | 新函數 |
🧠 記憶口訣:
call
馬上叫、apply
數組搞、bind
等你叫。
三、對象與原型鏈
3.1 對象創建與屬性操作
3.1.1 對象的創建方式
-
字面量法
使用大括號{}
直接定義對象,簡單直觀。const person = {name: "Alice",age: 30 };
-
構造函數法
使用new Object()
或通過自定義構造函數創建對象。const person = new Object(); person.name = "Bob"; person.age = 25;
-
Object.create()
使用指定的原型對象創建一個新對象,適用于原型鏈繼承。const personProto = {greet() {console.log(`Hello, ${this.name}`);} };const person = Object.create(personProto); person.name = "Charlie"; person.greet(); // Hello, Charlie
-
class
關鍵字
使用 ES6 中的class
語法創建對象及其構造函數。class Person {constructor(name, age) {this.name = name;this.age = age;} } const person = new Person("David", 28);
3.1.2 屬性描述符與 Object.defineProperty()
屬性描述符定義了對象屬性的特性(如是否可寫、可枚舉等)。
- 數據描述符:包含
value
和writable
(是否可修改)。 - 訪問器描述符:包含
get
和set
。
Object.defineProperty()
方法允許你直接設置屬性的描述符,并能夠控制屬性的行為(例如是否能修改、是否能枚舉等)。
const person = {};
Object.defineProperty(person, "name", {value: "Eve",writable: false, // 不可修改enumerable: true, // 可枚舉configurable: true // 可配置
});console.log(person.name); // Eve
person.name = "John"; // 不會修改
console.log(person.name); // Eve
writable: false
使得name
屬性不可修改。configurable: false
防止刪除該屬性或修改其特性。
3.2 原型與原型鏈
3.2.1 __proto__
vs prototype
-
prototype
每個函數對象都有一個prototype
屬性,指向該函數的原型對象。構造函數的實例會繼承該原型對象上的屬性和方法。function Person(name) {this.name = name; }const person = new Person("Alice"); console.log(person.__proto__ === Person.prototype); // true
-
__proto__
__proto__
是每個對象的內部屬性,指向該對象的構造函數的原型對象。它指示了對象的原型鏈的“父級”。const obj = {}; console.log(obj.__proto__ === Object.prototype); // true
prototype
是函數的屬性,而__proto__
是對象的屬性。
3.2.2 原型鏈查找機制
當訪問對象的屬性時,JS 引擎會先在對象自身查找,如果沒有找到,再沿著原型鏈向上查找,直到找到該屬性或到達 null
為止。
- 每個對象都有一個
__proto__
屬性,它指向該對象的構造函數的原型對象。 - 原型對象也有
__proto__
,形成一個鏈條,最終鏈條的盡頭是Object.prototype
,它的__proto__
為null
。
const obj = { name: "Alice" };
console.log(obj.name); // "Alice"
console.log(obj.toString()); // 調用 Object.prototype.toString
obj
沒有toString
方法,它會向原型鏈上的Object.prototype
查找toString
方法。
3.3 繼承方式
3.3.1 ES5 原型繼承
在 ES5 中,原型繼承是通過構造函數和 prototype
屬性實現的。基本的繼承方式是通過讓子類的 prototype
指向父類的 prototype
。
function Animal(name) {this.name = name;
}Animal.prototype.sayHello = function() {console.log(`Hello, I am a ${this.name}`);
};function Dog(name) {Animal.call(this, name); // 繼承屬性
}Dog.prototype = Object.create(Animal.prototype); // 繼承方法
Dog.prototype.constructor = Dog; // 修復構造函數指向const dog = new Dog("Buddy");
dog.sayHello(); // Hello, I am a Buddy
Object.create()
用于創建一個新的對象,將原型鏈指向父類的原型對象,從而實現繼承。
3.3.2 組合繼承
組合繼承(又叫偽經典繼承)是 構造函數繼承 和 原型繼承 的組合,它解決了原型繼承的缺點:子類會繼承父類的所有實例屬性,但每個子類實例都會重復父類的實例屬性。
function Animal(name) {this.name = name;
}Animal.prototype.sayHello = function() {console.log(`Hello, I am a ${this.name}`);
};function Dog(name, breed) {Animal.call(this, name); // 繼承實例屬性this.breed = breed;
}Dog.prototype = new Animal(); // 繼承方法
Dog.prototype.constructor = Dog;const dog = new Dog("Buddy", "Golden Retriever");
dog.sayHello(); // Hello, I am a Buddy
console.log(dog.breed); // Golden Retriever
- 缺點:構造函數
Animal.call(this)
被調用了兩次。new Animal()
會創建父類的實例,并將父類的屬性賦給子類原型。
3.3.3 類似寄生繼承的方式
寄生繼承 是通過 借用構造函數 的方式繼承父類的屬性,但不改變原型鏈。
function Animal(name) {this.name = name;
}Animal.prototype.sayHello = function() {console.log(`Hello, I am a ${this.name}`);
};function Dog(name, breed) {Animal.call(this, name); // 繼承屬性this.breed = breed;
}Dog.prototype = Object.create(Animal.prototype); // 繼承方法
Dog.prototype.constructor = Dog;const dog = new Dog("Buddy", "Golden Retriever");
dog.sayHello(); // Hello, I am a Buddy
console.log(dog.breed); // Golden Retriever
- 這個方式在繼承父類方法時,依然保持了父類原型鏈的正確性,解決了構造函數重復調用的問題。
3.3.4 寄生組合繼承
寄生組合繼承(parasitic combination inheritance)是一種優化的方式,它結合了 寄生繼承 和 組合繼承 的優點。通過 Object.create()
繼承父類的方法,并通過構造函數繼承父類的實例屬性,避免了重復調用父類構造函數。
function Animal(name) {this.name = name;
}Animal.prototype.sayHello = function() {console.log(`Hello, I am a ${this.name}`);
};function Dog(name, breed) {Animal.call(this, name); // 繼承屬性this.breed = breed;
}// 使用寄生組合繼承來避免重復調用父類構造函數
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;const dog = new Dog("Buddy", "Golden Retriever");
dog.sayHello(); // Hello, I am a Buddy
console.log(dog.breed); // Golden Retriever
- 優化點:只調用一次父類的構造函數,通過
Object.create()
來繼承父類的原型方法,避免了組合繼承的性能問題。
3.3.5 ES6 class 繼承 (extends
與 super
)
ES6 引入了 class
和 extends
語法,使得繼承更加簡潔和直觀。super
用于調用父類的構造函數或方法。
class Animal {constructor(name) {this.name = name;}sayHello() {console.log(`Hello, I am a ${this.name}`);}
}class Dog extends Animal {constructor(name, breed) {super(name); // 調用父類構造函數this.breed = breed;}bark() {console.log("Woof!");}
}const dog = new Dog("Buddy", "Golden Retriever");
dog.sayHello(); // Hello, I am a Buddy
dog.bark(); // Woof!
extends
用于實現繼承。super
用于調用父類的構造函數或方法。
3.3 總結
繼承方式 | 描述 | 優缺點 |
---|---|---|
原型繼承 | 通過設置子類原型為父類原型的實例來實現繼承 | 缺點:父類實例屬性會被所有子類實例共享 |
組合繼承 | 結合構造函數繼承和原型繼承 | 缺點:構造函數被調用兩次 |
寄生繼承 | 通過借用構造函數繼承父類實例屬性,不改變原型 | 適用于不需要創建新對象的場景 |
寄生組合繼承 | 結合了寄生繼承與組合繼承的優點,優化了性能 | 解決了組合繼承的缺點,避免了重復調用構造函數 |
ES6 class | 使用 class 和 extends 語法簡化繼承 | 語法簡潔,易于理解,但仍然是基于原型鏈的繼承 |
四、數組與內置對象
4.1 數組操作
4.1.1 創建與轉換
方法 | 返回值 | 是否修改原數組 | 作用 |
---|---|---|---|
Array.of(...items) | 新數組(包含所有參數) | 否 | 創建一個包含所有參數的數組 |
Array.from(obj) | 新數組(類數組轉數組) | 否 | 將類數組或可迭代對象轉換為數組 |
arr.toString() | 字符串(元素用逗號連接) | 否 | 將數組元素轉換為字符串,用逗號分隔 |
arr.join(sep) | 字符串(自定義分隔符) | 否 | 將數組元素連接成字符串,使用指定的分隔符 |
4.1.2 增刪元素
方法 | 返回值 | 是否修改原數組 | 作用 |
---|---|---|---|
push(...items) | 新長度 | 是 | 在數組末尾添加一個或多個元素 |
pop() | 被刪除的元素 | 是 | 刪除并返回數組的最后一個元素 |
unshift(...items) | 新長度 | 是 | 在數組開頭添加一個或多個元素 |
shift() | 被刪除的元素 | 是 | 刪除并返回數組的第一個元素 |
splice(start, del, ...items) | 被刪除的元素數組 | 是 | 從指定位置刪除指定數量的元素,并可以插入新元素 |
slice(start, end) | 新數組(部分元素) | 否 | 返回數組的一個淺拷貝(部分) |
4.1.3 查找與過濾
方法 | 返回值 | 是否修改原數組 | 作用 |
---|---|---|---|
indexOf(item) | 首個匹配索引(-1 表示不存在) | 否 | 查找元素首次出現的位置 |
lastIndexOf(item) | 最后匹配索引(-1 表示不存在) | 否 | 查找元素最后一次出現的位置 |
includes(item) | 布爾值(是否包含) | 否 | 判斷數組是否包含某個元素 |
find(fn) | 首個匹配元素(undefined 表示不存在) | 否 | 查找并返回第一個滿足條件的元素 |
findIndex(fn) | 首個匹配索引(-1 表示不存在) | 否 | 查找并返回第一個滿足條件的元素索引 |
filter(fn) | 新數組(所有匹配元素) | 否 | 返回一個包含所有滿足條件元素的新數組 |
4.1.4 遍歷與轉換
方法 | 返回值 | 是否修改原數組 | 作用 |
---|---|---|---|
forEach(fn) | undefined | 否(但可修改元素) | 遍歷數組,執行給定的函數,適用于副作用操作 |
map(fn) | 新數組(每個元素處理后) | 否 | 返回一個新的數組,每個元素經過給定函數處理 |
flat(depth) | 新數組(扁平化后) | 否 | 將嵌套的數組結構“拉平”至指定深度 |
flatMap(fn) | 新數組(先 map 再 flat) | 否 | 先對數組進行 map 操作,再進行扁平化 |
4.1.5 排序與反轉
方法 | 返回值 | 是否修改原數組 | 作用 |
---|---|---|---|
sort(fn) | 原數組(排序后) | 是 | 對數組進行排序,默認按字符順序排列 |
reverse() | 原數組(反轉后) | 是 | 將數組元素的順序顛倒 |
4.1.6 合并與拆分
方法 | 返回值 | 是否修改原數組 | 作用 |
---|---|---|---|
concat(...arrays) | 新數組(合并后) | 否 | 合并多個數組或值到一個新數組 |
split(sep) | 字符串 → 數組 | 否(操作字符串) | 將字符串分割為數組 |
join(sep) | 數組 → 字符串 | 否 | 將數組元素連接成一個字符串,使用指定的分隔符 |
4.1.7 歸約方法
方法 | 返回值 | 是否修改原數組 | 作用 |
---|---|---|---|
reduce(fn, init) | 累計值 | 否 | 對數組元素執行累加操作,返回累計值 |
reduceRight(fn, init) | 累計值(從右到左) | 否 | 從右到左對數組元素執行累加操作,返回累計值 |
4.1.8 判定方法
方法 | 返回值 | 是否修改原數組 | 作用 |
---|---|---|---|
every(fn) | 布爾值(所有元素滿足) | 否 | 判斷數組中的每個元素是否都滿足條件 |
some(fn) | 布爾值(至少一個滿足) | 否 | 判斷數組中是否至少有一個元素滿足條件 |
isArray(value) | 布爾值(是否為數組) | 否(靜態方法) | 判斷給定值是否為數組 |
4.1.9 其他方法
方法 | 返回值 | 是否修改原數組 | 作用 |
---|---|---|---|
fill(value, start, end) | 原數組(填充后) | 是 | 用指定的值填充數組的某部分元素 |
copyWithin(target, start, end) | 原數組(復制后) | 是 | 將數組中的一部分復制到同一數組的指定位置 |
entries() | 迭代器(索引-值對) | 否 | 返回一個數組的遍歷器對象,包含索引和對應值 |
keys() | 迭代器(索引) | 否 | 返回一個包含數組索引的遍歷器對象 |
values() | 迭代器(值) | 否 | 返回一個包含數組值的遍歷器對象 |
記憶技巧
- 修改原數組的方法:
push/pop/unshift/shift/splice/sort/reverse/fill/copyWithin
(口訣:增刪改查排序反轉填充復制) - 返回新數組的方法:
slice/map/filter/concat/flat/flatMap
(口訣:切片映射過濾合并扁平化) - 歸約與判定:
reduce/every/some/find/findIndex/includes
(口訣:累計判定查找包含)
4.2 常用內置對象
4.2.1 String 對象
1. 字符操作
方法 | 返回值 | 作用 |
---|---|---|
charAt(index) | 字符 | 返回指定位置的字符 |
charCodeAt(index) | 數字 | 返回指定位置字符的 Unicode 編碼 |
codePointAt(index) | 數字 | 返回指定位置字符的 Unicode 代碼點 |
fromCharCode(...codes) | 字符串 | 從 Unicode 編碼返回字符 |
fromCodePoint(...codePoints) | 字符串 | 從 Unicode 代碼點返回字符 |
2. 查找與替換
方法 | 返回值 | 作用 |
---|---|---|
includes(searchString) | 布爾值 | 判斷字符串是否包含指定的子串 |
indexOf(searchValue) | 索引 | 返回子串首次出現的位置 |
lastIndexOf(searchValue) | 索引 | 返回子串最后一次出現的位置 |
match(regexp) | 數組 | 匹配正則表達式并返回結果 |
replace(searchValue, newValue) | 新字符串 | 替換匹配的子字符串 |
3. 大小寫轉換
方法 | 返回值 | 作用 |
---|---|---|
toLowerCase() | 小寫字符串 | 返回將所有字符轉換為小寫的新字符串 |
toUpperCase() | 大寫字符串 | 返回將所有字符轉換為大寫的新字符串 |
4. 切割與連接
方法 | 返回值 | 作用 |
---|---|---|
slice(start, end) | 子字符串 | 返回字符串的一個部分 |
split(separator) | 數組 | 按照指定分隔符拆分字符串 |
concat(...strings) | 字符串 | 連接多個字符串并返回新字符串 |
join(sep) | 字符串 | 數組元素連接為字符串 |
4.2.2 Date 對象
1. 獲取日期與時間
方法 | 返回值 | 作用 |
---|---|---|
getFullYear() | 年份 | 返回完整的年份(4位) |
getMonth() | 月份 | 返回月份(0-11) |
getDate() | 日期 | 返回一個月中的日期(1-31) |
getDay() | 星期幾 | 返回星期幾(0-6,0為星期天) |
getHours() | 小時 | 返回小時(0-23) |
getMinutes() | 分鐘 | 返回分鐘(0-59) |
getSeconds() | 秒數 | 返回秒數(0-59) |
getMilliseconds() | 毫秒 | 返回毫秒數(0-999) |
2. 設置日期與時間
方法 | 返回值 | 作用 |
---|---|---|
setFullYear(year) | 設置年份 | 設置年份(4位) |
setMonth(month) | 設置月份 | 設置月份(0-11) |
setDate(day) | 設置日期 | 設置日期(1-31) |
setHours(hours) | 設置小時 | 設置小時(0-23) |
setMinutes(minutes) | 設置分鐘 | 設置分鐘(0-59) |
setSeconds(seconds) | 設置秒數 | 設置秒數(0-59) |
setMilliseconds(milliseconds) | 設置毫秒 | 設置毫秒(0-999) |
4.2.3 Math 對象
1. 數學常用方法
方法 | 返回值 | 作用 |
---|---|---|
Math.abs(x) | 數值 | 返回 x 的絕對值 |
Math.ceil(x) | 數值 | 返回大于或等于 x 的最小整數 |
Math.floor(x) | 數值 | 返回小于或等于 x 的最大整數 |
Math.round(x) | 數值 | 返回四舍五入后的值 |
Math.random() | 數值 | 返回一個 0 到 1 之間的隨機數 |
Math.sqrt(x) | 數值 | 返回 x 的平方根 |
2. 極值與范圍
方法 | 返回值 | 作用 |
---|---|---|
Math.max(...values) | 數值 | 返回一組數中的最大值 |
Math.min(...values) | 數值 | 返回一組數中的最小值 |
Math.pow(x, y) | 數值 | 返回 x 的 y 次方 |
Math.PI | 數值 | 返回圓周率常量 π |
4.2.4 JSON 對象
1. JSON 解析與字符串化
方法 | 返回值 | 作用 |
---|---|---|
JSON.parse(text) | 對象/數組 | 將 JSON 字符串解析為 JavaScript 對象 |
JSON.stringify(value) | JSON 字符串 | 將 JavaScript 對象轉換為 JSON 字符串 |
4.2.5 RegExp 對象
1. 正則表達式方法
方法 | 返回值 | 作用 |
---|---|---|
test(str) | 布爾值 | 測試正則表達式是否匹配字符串 |
exec(str) | 數組 | 返回正則表達式與字符串匹配的結果(如果有) |
五、異步編程與事件機制
5.1 異步基礎
5.1.1 同步 vs 異步
- 同步(Synchronous):任務按順序逐行執行,前一個任務不完成,后一個任務無法開始。
- 異步(Asynchronous):某些操作可在“等待結果”的同時繼續執行其他任務。
📌 示例:
console.log('A');
setTimeout(() => console.log('B'), 1000);
console.log('C');
// 輸出順序:A → C → B
5.1.2 回調函數與 Callback Hell
- 回調函數:將一個函數作為參數傳給另一個函數,用于異步任務完成后執行。
- 回調地獄(Callback Hell):多個嵌套回調,導致代碼結構混亂、難以維護。
📌 示例:
doSomething(function(result1) {doSomethingElse(result1, function(result2) {doThirdThing(result2, function(result3) {console.log('All done!');});});
});
5.2 Promise 與 async/await
5.2.1 Promise 構造與鏈式調用(進階版)
Promise
是異步編程的核心機制,表示一個可能現在、將來或永不完成的值。- 狀態只能從
pending
→fulfilled
或pending
→rejected
,不可逆。
? 一、創建 Promise 實例
const p = new Promise((resolve, reject) => {const data = getData();if (data) resolve(data);else reject(new Error("獲取失敗"));
});
resolve(value)
:表示成功,進入.then()
分支reject(error)
:表示失敗,進入.catch()
分支
? 二、鏈式調用(then / catch / finally)
p.then(result => {console.log("成功:", result);return result + "!";
}).then(modified => {console.log("鏈式處理:", modified);
}).catch(err => {console.error("捕獲錯誤:", err);
}).finally(() => {console.log("無論成功失敗都會執行");
});
.then()
可以返回新值傳遞給下一個.then()
.catch()
捕獲任意前面出現的錯誤.finally()
不處理值,僅用于收尾動作(如關閉 loading)
? 三、錯誤傳播機制
若
.then()
中拋出錯誤,會直接被后面的.catch()
捕獲。
p.then(() => {throw new Error("出錯了");
}).catch(err => {console.log("捕獲到錯誤", err.message);
});
? 四、Promise 嵌套與避免回調地獄
getUserInfo().then(user => {return getPostsByUser(user.id);
}).then(posts => {return getCommentsForPost(posts[0].id);
});
- 通過鏈式結構代替回調嵌套,實現邏輯扁平化
- 若返回的是一個新的 Promise,則自動等待其執行完成
? 五、常見錯誤使用案例(警示)
// ?? 不要這樣寫
p.then(res => {doSomething(res, function(result) {// 回調地獄又來了});
});
應改寫為:
p.then(res => doSomethingAsync(res)).then(next => console.log(next));
非常好,Promise 除了構造函數和鏈式調用外,還有一組非常實用的靜態方法(類方法),適用于多個異步任務的管理與控制。以下是完整補充,按照清晰的結構歸類呈現:
🔹 5.2.2 Promise 所有方法匯總
? 一、構造函數實例方法
方法 | 作用 | 特點 |
---|---|---|
new Promise(fn) | 創建一個新的 Promise 實例 | 傳入 resolve 和 reject 兩個函數參數 |
.then(onFulfilled) | 注冊成功回調函數 | 支持鏈式調用 |
.catch(onRejected) | 注冊失敗回調函數 | 是 .then(null, onRejected) 的語法糖 |
.finally(fn) | 無論成功/失敗都會執行 | 不影響返回值傳遞 |
? 二、靜態方法(類方法)
1. Promise.resolve(value)
- 返回一個狀態為
fulfilled
的 Promise - 如果傳入的是一個 Promise,會直接返回
Promise.resolve(42).then(console.log); // 42
2. Promise.reject(error)
- 返回一個狀態為
rejected
的 Promise - 通常用于封裝異常
Promise.reject("失敗").catch(console.error); // 失敗
3. Promise.all([p1, p2, …])
- 等待所有 Promise 成功,才
resolve
,否則立即reject
- 返回值是所有結果的數組(按順序)
Promise.all([p1, p2]).then(([r1, r2]) => {console.log(r1, r2);
});
🧠 常用于:并發請求,必須都成功
4. Promise.race([p1, p2, …])
- 誰先完成(成功或失敗),就采用誰的結果
- 競速場景:如加載動畫 vs 請求超時
Promise.race([fetchData(),timeoutPromise(3000)
]).then(console.log).catch(console.error);
5. Promise.allSettled([p1, p2, …])
- 等待所有 Promise 都結束(無論成功或失敗)
- 每一項返回
{ status, value }
或{ status, reason }
Promise.allSettled([p1, p2]).then(results => {results.forEach(r => console.log(r.status));
});
🧠 常用于:統計、批處理,不能因為一個失敗而中斷
6. Promise.any([p1, p2, …])
- 誰先成功就
resolve
,全部失敗才reject
- ES2021 新增
Promise.any([Promise.reject("失敗1"),Promise.resolve("成功"),Promise.reject("失敗2")
]).then(console.log); // 輸出:"成功"
🧠 常用于:只需一個成功即可,如鏡像 CDN 請求
? 三、對比總結
方法 | 成功策略 | 失敗策略 | 典型應用 |
---|---|---|---|
Promise.all | 全部成功 | 任意失敗立即中止 | 并行任務且都要成功 |
Promise.race | 誰先返回 | 誰先返回 | 超時控制 |
Promise.allSettled | 不關心 | 不關心 | 全部結果分析 |
Promise.any | 任意一個成功即可 | 全部失敗才失敗 | 多鏡像請求、降級處理 |
? 小貼士:手寫模擬 Promise.all(核心思維訓練)
function promiseAll(promises) {return new Promise((resolve, reject) => {let result = [], count = 0;promises.forEach((p, i) => {Promise.resolve(p).then(val => {result[i] = val;count++;if (count === promises.length) resolve(result);}).catch(reject);});});
}
5.2.3 async/await
async
聲明函數返回一個 Promise。await
暫停異步函數執行,等待 Promise 結果。
📌 示例:
async function fetchData() {try {const data = await getData();console.log(data);} catch (error) {console.error('Error:', error);}
}
5.3 事件循環機制(Event Loop)
5.3.1 宏任務 vs 微任務
類型 | 示例 | 執行時機 |
---|---|---|
宏任務 | setTimeout 、setInterval 、I/O 、setImmediate (Node) | 每輪事件循環開始時調度 |
微任務 | Promise.then 、queueMicrotask 、MutationObserver (瀏覽器) | 當前宏任務執行完立即執行所有微任務 |
📌 執行順序示例:
console.log('start');setTimeout(() => console.log('setTimeout'), 0);
Promise.resolve().then(() => console.log('promise'));
queueMicrotask(() => console.log('microtask'));
console.log('end');// 輸出:start → end → promise → microtask → setTimeout
5.3.2 瀏覽器中的事件循環流程
- 執行全局同步代碼(主線程 → 調用棧)
- 執行所有微任務隊列
- 執行一個宏任務隊列中的任務
- 重復步驟 2 → 3,直到所有任務完成
5.3.3 Node.js 中的事件循環階段
Node.js 的事件循環更復雜,包含 6 個階段(基于 libuv):
階段 | 描述 |
---|---|
timers | 執行 setTimeout 、setInterval 回調 |
pending callbacks | 執行一些系統操作的回調(如 TCP 錯誤) |
idle/prepare | 內部使用 |
poll | 處理 I/O 事件,如果沒有則可能進入 check 階段或等待 |
check | 執行 setImmediate() 的回調 |
close callbacks | 執行如 socket.on('close', fn) 等關閉回調 |
? 每個階段之間都會清空微任務隊列(process.nextTick & Promise)。
📌 微任務優先級:
process.nextTick > Promise.then > 宏任務(setTimeout、setImmediate)
📌 Node 示例:
setTimeout(() => console.log('timeout'), 0);
setImmediate(() => console.log('immediate'));Promise.resolve().then(() => console.log('promise'));
process.nextTick(() => console.log('nextTick'));// 輸出順序:nextTick → promise → timeout/immediate(取決于系統)
? 總結:執行順序記憶口訣
- 瀏覽器中:同步 → 微任務 → 宏任務
- Node.js 中:同步 → nextTick → Promise → 各階段宏任務
💡提示:Node.js 中的
setImmediate
可能比setTimeout(fn, 0)
更快執行,但不保證一致順序。
六、DOM 與 BOM 操作
6.1 DOM 基礎(全面分類)
6.1.1 節點獲取與遍歷
方法 | 描述 | 返回類型 |
---|---|---|
getElementById(id) | 根據 ID 獲取節點 | 單個元素 |
getElementsByClassName(class) | 根據類名獲取 | 類數組 |
getElementsByTagName(tag) | 根據標簽名獲取 | 類數組 |
querySelector(selector) | CSS 選擇器,獲取首個匹配節點 | 單個元素 |
querySelectorAll(selector) | CSS 選擇器,獲取全部匹配節點 | NodeList(類數組) |
parentNode / childNodes / nextSibling | DOM 樹節點關系遍歷 | 節點對象 |
6.1.2 節點創建、插入、刪除、克隆
操作 | 方法 | 示例 |
---|---|---|
創建元素 | createElement(tag) | let div = document.createElement('div') |
插入 | appendChild() 、append() 、insertBefore() | parent.appendChild(child) |
刪除 | removeChild() 、remove() | parent.removeChild(el) |
替換 | replaceChild(newEl, oldEl) | 替換節點 |
克隆 | cloneNode(true) | 復制節點 |
6.1.3 節點內容與屬性操作
操作 | 方法 / 屬性 | 示例 |
---|---|---|
獲取/設置文本 | innerText / textContent | el.textContent = 'Hello' |
獲取/設置 HTML | innerHTML | el.innerHTML = '<b>Hi</b>' |
獲取/設置屬性 | getAttribute() / setAttribute() | el.setAttribute('href', '#') |
移除屬性 | removeAttribute() | el.removeAttribute('title') |
操作類名 | classList.add/remove/toggle/contains | el.classList.toggle('show') |
操作樣式 | style.property | el.style.color = 'red' |
6.1.4 元素位置與尺寸獲取
屬性 / 方法 | 描述 | 示例 |
---|---|---|
offsetTop / offsetLeft | 元素相對 offsetParent 的偏移 | el.offsetTop |
offsetWidth / offsetHeight | 包含 padding 和 border | el.offsetHeight |
clientWidth / clientHeight | 包含 padding 不含 border | el.clientWidth |
scrollTop / scrollLeft | 滾動距離 | el.scrollTop |
getBoundingClientRect() | 獲取元素相對視口的位置與尺寸 | el.getBoundingClientRect() |
6.1.5 常用事件分類
📌 一)鼠標事件
事件名 | 說明 |
---|---|
click / dblclick | 點擊 / 雙擊 |
mousedown / mouseup | 按下 / 彈起 |
mousemove | 鼠標移動 |
mouseenter / mouseleave | 進入 / 離開元素(不冒泡) |
mouseover / mouseout | 進入 / 離開(冒泡) |
contextmenu | 右鍵菜單 |
📌 二)鍵盤事件
事件名 | 說明 |
---|---|
keydown | 鍵盤按下 |
keyup | 鍵盤松開 |
keypress | 輸入字符(已廢棄) |
📌 三)表單事件
事件名 | 說明 |
---|---|
submit | 表單提交 |
change | 表單值變化(如 select) |
input | 輸入變化(推薦用于 text 輸入) |
focus / blur | 聚焦 / 失焦 |
📌 四)窗口事件
事件名 | 說明 |
---|---|
load | 頁面加載完成 |
resize | 窗口尺寸變化 |
scroll | 頁面或元素滾動 |
beforeunload | 頁面關閉前 |
6.1.6 事件監聽與代理
操作 | 示例 |
---|---|
綁定事件 | el.addEventListener('click', fn) |
移除事件 | el.removeEventListener('click', fn) |
阻止默認行為 | event.preventDefault() |
阻止冒泡 | event.stopPropagation() |
事件代理 | 綁定父級,判斷 e.target |
list.addEventListener('click', e => {if (e.target.tagName === 'LI') {console.log('點擊了第', e.target.innerText);}
});
6.1.7 拖拽與監聽移動
功能 | 方法 |
---|---|
拖拽事件 | dragstart / dragover / drop |
鼠標監聽移動 | 結合 mousedown 、mousemove 、mouseup 實現 |
示例偽代碼實現拖動:
let isDragging = false;
el.onmousedown = () => isDragging = true;
document.onmousemove = (e) => {if (isDragging) el.style.left = e.clientX + 'px';
};
document.onmouseup = () => isDragging = false;
6.2 事件模型(瀏覽器)
6.2.1 事件傳播機制:捕獲 & 冒泡
事件傳播分為 三個階段:
階段順序 | 階段名稱 | 描述 |
---|---|---|
① | 捕獲階段(Capture Phase) | 從 window 自頂向下,沿著 DOM 樹傳遞到目標元素 |
② | 目標階段(Target Phase) | 實際目標元素上觸發的事件 |
③ | 冒泡階段(Bubble Phase) | 從目標元素沿 DOM 樹向上傳遞回 window |
📌 示例:
element.addEventListener('click', handler, true); // 第三個參數 true 表示捕獲階段監聽
element.addEventListener('click', handler, false); // false 表示冒泡階段監聽(默認)
6.2.2 阻止事件傳播
方法 | 作用 |
---|---|
event.stopPropagation() | 阻止事件繼續冒泡或捕獲 |
event.stopImmediatePropagation() | 阻止同一元素上后續所有事件監聽器執行 |
event.preventDefault() | 阻止默認行為(如表單提交、a 鏈接跳轉) |
6.2.3 自定義事件(CustomEvent)
用于手動觸發和傳遞自定義數據的事件。
1?? 創建并觸發事件:
const event = new CustomEvent('myEvent', {detail: { name: 'ChatGPT', level: 99 }
});
element.dispatchEvent(event);
2?? 監聽事件:
element.addEventListener('myEvent', function(e) {console.log(e.detail.name); // 輸出:ChatGPT
});
6.2.4 事件對象 Event
當事件觸發時,監聽函數會自動接收一個事件對象:
屬性/方法 | 描述 |
---|---|
event.target | 實際觸發事件的元素 |
event.currentTarget | 當前綁定事件的元素 |
event.type | 事件類型(如 click) |
event.timeStamp | 觸發事件的時間戳 |
event.defaultPrevented | 是否已調用 preventDefault() |
event.bubbles | 該事件是否支持冒泡 |
event.cancelable | 是否可以取消默認操作 |
6.2.5 補充:事件委托(推薦實踐)
通過把事件綁定在父節點上,提高性能和可維護性。
ul.addEventListener('click', function(e) {if (e.target.tagName === 'LI') {console.log('點擊了:', e.target.innerText);}
});
? 好處:
- 減少事件監聽數量
- 支持動態添加子元素的事件響應
6.2.6 事件綁定優先級順序
在瀏覽器中,當同一元素綁定了多種事件方式,它們的觸發順序如下:
? 優先級順序(由高到低):
- 內聯綁定(HTML 屬性):如
<button onclick="alert(1)">
- DOM0 綁定(傳統方式):如
element.onclick = fn
- DOM2 綁定(推薦方式):如
element.addEventListener('click', fn)
?? 示例說明:
<button id="btn" onclick="console.log('inline')">點擊</button>
const btn = document.getElementById('btn');
btn.onclick = () => console.log('DOM0');
btn.addEventListener('click', () => console.log('DOM2'));
輸出順序:
inline
DOM0
DOM2
6.2.7 DOM 0 / DOM 2 級事件模型差異
比較項 | DOM 0 級事件(傳統) | DOM 2 級事件(標準) |
---|---|---|
綁定方式 | element.onclick = fn | addEventListener('click', fn, useCapture) |
是否支持多個監聽器 | ? 只能綁定一個 | ? 可綁定多個 |
是否支持捕獲階段 | ? 不支持 | ? 支持(通過第三個參數) |
是否兼容 IE 低版本 | ? | IE9+ |
是否標準推薦 | ? | ? W3C 推薦標準方式 |
6.2.8 React 合成事件機制(SyntheticEvent)
React 并不直接綁定原生事件,而是通過自己的事件系統(合成事件)實現更高效的管理。
📌 特點:
特性 | 描述 |
---|---|
合成封裝 | 對原生事件進行封裝,統一不同瀏覽器差異 |
自動綁定 | 自動使用事件委托,綁定在根節點(提高性能) |
統一池化 | 使用事件池提升性能(v17 前),需注意事件異步訪問 |
命名風格 | 使用駝峰命名:如 onClick 、onChange |
? 示例:
<button onClick={handleClick}>點擊</button>function handleClick(e) {console.log(e.nativeEvent); // 原生事件console.log(e); // 合成事件
}
?? 注意事項:
- 異步中訪問事件屬性需要調用
e.persist()
(在 React17 以前) - React 17+ 不再使用事件池,不再需要
e.persist()
6.3 BOM 操作
6.3.1 window
對象
- 瀏覽器的全局對象,所有全局變量和函數都是其屬性或方法。
- 也是 BOM 的頂層對象。
常見屬性/方法:
屬性/方法 | 作用 |
---|---|
window.innerWidth / innerHeight | 獲取窗口內容區域的寬/高(不含滾動條) |
window.open(url) | 打開新窗口或標簽頁 |
window.alert() / confirm() / prompt() | 瀏覽器彈窗 |
window.scrollTo(x, y) | 滾動到指定位置 |
6.3.2 location
對象
- 用于獲取或修改當前頁面的 URL。
屬性/方法 | 說明 |
---|---|
location.href | 當前完整 URL,可讀取或賦值跳轉 |
location.protocol | 協議,如 https: |
location.host / hostname / port | 主機、主機名、端口號 |
location.pathname | 路徑部分 |
location.search | 查詢字符串(如 ?id=1 ) |
location.reload() | 重新加載頁面 |
location.assign(url) | 跳轉到新 URL(有歷史記錄) |
location.replace(url) | 替換當前頁面(無歷史記錄) |
6.3.3 navigator
對象
- 描述用戶瀏覽器的信息。
屬性 | 說明 |
---|---|
navigator.userAgent | 瀏覽器/設備詳細信息 |
navigator.platform | 操作系統平臺(如 Win32) |
navigator.language | 當前瀏覽器語言 |
navigator.onLine | 當前是否聯網(布爾值) |
6.3.4 history
對象
- 用于操作瀏覽器歷史記錄。
方法 | 說明 |
---|---|
history.back() | 返回上一頁(等同于點擊后退) |
history.forward() | 前進一頁 |
history.go(n) | 前進或后退 n 步 |
history.pushState(state, title, url) | 添加歷史記錄(不會刷新頁面) |
history.replaceState(...) | 替換當前歷史記錄 |
注意:
pushState
和replaceState
是 HTML5 的新特性,常用于 SPA 前端路由。
6.3.5 定時器:setTimeout
與 setInterval
方法 | 作用 | 返回值 |
---|---|---|
setTimeout(fn, delay) | 延遲執行一次 | 返回定時器 ID |
setInterval(fn, delay) | 每隔一段時間重復執行 | 返回定時器 ID |
clearTimeout(id) | 取消 setTimeout | |
clearInterval(id) | 取消 setInterval |
示例:
const id = setTimeout(() => console.log("一次性延遲"), 1000);
clearTimeout(id);const loopId = setInterval(() => console.log("每秒執行"), 1000);
clearInterval(loopId);
?? 注意內存泄漏風險:組件銷毀/頁面離開應清理定時器。
好的,以下是對 6.3 BOM 操作 的進一步補充,涵蓋瀏覽器窗口通信、全局錯誤處理,以及實用 Web API 技巧,幫助你建立更加系統的 JavaScript 瀏覽器編程知識體系。
6.3.6 瀏覽器窗口通信:postMessage
? 場景:
- 不同窗口/iframe 之間傳遞數據(甚至跨域);
- 通常用于父頁面與子頁面之間的數據通信。
💡 基本語法:
// 發送方(通常是父窗口或 iframe)
otherWindow.postMessage(message, targetOrigin);// 接收方
window.addEventListener("message", function(event) {// event.data 是傳遞過來的數據// event.origin 是消息來源的域名
}, false);
? 參數說明:
參數 | 說明 |
---|---|
message | 發送的數據(可以是對象) |
targetOrigin | 接收方的 origin(例如:"https://example.com")用于安全校驗-w41hk4oxqyc3z0h1aq79f/) |
📌 示例:
// 子頁面向父頁面發送消息
window.parent.postMessage({ type: "resize", height: 600 }, "https://yourdomain.com");// 父頁面接收子頁面消息
window.addEventListener("message", (event) => {if (event.origin !== "https://yourdomain.com") return; // 安全校驗console.log("子頁面消息:", event.data);
});
6.3.7 全局錯誤處理機制
? window.onerror
用于捕獲運行時錯誤,防止頁面崩潰時無反饋。
window.onerror = function (message, source, lineno, colno, error) {console.error("捕獲錯誤:", message, "位置:", source, lineno, colno);// 可上傳日志服務器return true; // 阻止默認報錯行為
};
? window.addEventListener('error')
更強大,可以捕獲資源加載錯誤(如圖片、腳本加載失敗):
window.addEventListener("error", function (e) {if (e.target instanceof HTMLImageElement) {console.warn("圖片加載失敗:", e.target.src);}
}, true); // 第三個參數設為 true 才能捕獲資源加載錯誤
? window.addEventListener('unhandledrejection')
用于捕獲未被 .catch()
捕獲的 Promise 錯誤:
window.addEventListener("unhandledrejection", (event) => {console.error("未處理的 Promise 錯誤:", event.reason);
});
6.3.8 Web API 實用技巧
? 剪貼板 API
// 復制文本到剪貼板
navigator.clipboard.writeText("復制的內容").then(() => alert("已復制")).catch(err => console.error("復制失敗", err));// 讀取剪貼板內容(需 HTTPS 環境+用戶觸發)
navigator.clipboard.readText().then(text => console.log("讀取到剪貼板內容:", text));
?? 注意安全性:大多瀏覽器要求用戶手勢觸發(如點擊)
? 頁面可見性 API(Page Visibility)
判斷頁面是否處于活躍(當前標簽頁是否可見),適合用于:
- 暫停動畫、視頻播放;
- 控制數據輪詢行為等。
document.addEventListener("visibilitychange", () => {if (document.visibilityState === "hidden") {console.log("頁面不可見,暫停輪詢");} else {console.log("頁面可見,恢復輪詢");}
});
? 屏幕尺寸與滾動監聽
// 獲取滾動位置
window.scrollY; // 垂直滾動距離
window.scrollX; // 水平滾動距離// 監聽頁面滾動
window.addEventListener("scroll", () => {console.log("滾動中...", window.scrollY);
});
七、模塊化與工具鏈
7.1 模塊化發展歷程
模塊化是前端工程化的核心。它的演化反映了前端開發復雜度的提升和工具生態的進化。
7.1.1 IIFE(立即執行函數表達式)
最原始的模塊化方式,用閉包封裝變量避免污染全局作用域。
(function () {var name = "模塊內部變量";console.log(name);
})();
- ? 優點:避免全局變量污染
- ? 缺點:無模塊復用能力、缺乏依賴管理
7.1.2 CommonJS(Node.js 中使用)
// a.js
module.exports = {sayHi: () => console.log("Hi"),
};// b.js
const a = require("./a.js");
a.sayHi();
- ? 特點:同步加載,適用于服務器端
- ? 瀏覽器不支持,需要打包工具(如 Webpack)轉換
7.1.3 AMD(Asynchronous Module Definition)
瀏覽器端模塊化規范,代表庫:RequireJS
define(["moduleA"], function (moduleA) {moduleA.doSomething();
});
- ? 特點:異步加載,適合瀏覽器
- ? 缺點:語法繁瑣、可讀性差
7.1.4 UMD(Universal Module Definition)
兼容 CommonJS、AMD 和瀏覽器全局變量
(function (root, factory) {if (typeof define === "function" && define.amd) {define([], factory);} else if (typeof exports === "object") {module.exports = factory();} else {root.myModule = factory();}
})(this, function () {return {};
});
7.1.5 ES6 模塊化(現代主流)
// module.js
export const name = "JS模塊";
export default function greet() {console.log("Hello ES Module");
}// main.js
import greet, { name } from "./module.js";
greet();
- ? 靜態加載、編譯時可分析依賴
- ? 瀏覽器原生支持(需 type=“module”)
- ? 與打包工具完美結合
7.2 前端開發工具鏈概覽
從代碼撰寫 → 轉譯兼容 → 打包構建 → 代碼規范 → 性能優化,全流程涉及以下關鍵工具:
7.2.1 構建與打包工具
工具 | 主要用途 | 特點 |
---|---|---|
Webpack | 模塊打包器 | 配置靈活、插件強大、學習曲線略高 |
Vite | 新一代構建工具 | 極速啟動、基于原生 ESM、現代開發優選 |
Rollup | 打包庫的首選工具 | 構建體積小,Tree-shaking 效果好 |
7.2.2 代碼轉譯與語法支持
工具 | 作用 |
---|---|
Babel | 將 ES6+ 代碼轉為向后兼容的 JavaScript |
TypeScript | 增加類型系統、增強開發體驗 |
7.2.3 代碼質量與風格規范
工具 | 用途 |
---|---|
ESLint | 靜態代碼檢查,防止潛在錯誤 |
Prettier | 統一代碼格式,提升團隊協作效率 |
? 推薦配合 IDE 插件 + Git Hooks 實現自動檢查 + 修復
7.2.4 常見工具集成方式
- 項目初始化:
npm init vite@latest
/create-react-app
- 構建命令:
npm run build
- 開發模式:
npm run dev
(通常開啟熱更新) - 檢查格式:
eslint src/
、prettier --write .
🔧 補充建議
? 強烈建議配合 husky + lint-staged 實現提交前校驗:
npx husky-init && npm install
npx husky add .husky/pre-commit "npx lint-staged"
八、ES6+ 新特性
ES6 是 ECMAScript 的重大升級版本,后續 ES7+ 持續增強語法和內置能力,使 JavaScript 更現代、更強大。以下按功能模塊系統歸納。
8.1 語法增強與數據結構
8.1.1 解構賦值
快速提取對象或數組中的值
const { name, age } = person;
const [a, b] = [1, 2];
- ? 默認值、嵌套解構
- ? 可用于函數參數
8.1.2 模板字符串
多行字符串 & 插值表達式
const msg = `Hello, ${user.name}!`;
8.1.3 擴展與收集運算符(...
)
-
展開:將數組/對象拆開
const arr2 = [...arr1]; const obj2 = { ...obj1 };
-
收集:函數剩余參數
function fn(...args) {}
8.1.4 Symbol(獨一無二的值)
創建唯一鍵,適合定義私有屬性
const sym = Symbol('key');
obj[sym] = 'value';
8.1.5 Set & Map(全新數據結構)
特性 | Set | Map |
---|---|---|
存儲 | 值的集合 | 鍵值對 |
是否重復 | 否 | 否(鍵唯一) |
常用方法 | add , has , delete | set , get , has , delete |
const s = new Set([1, 2, 2]); // 去重
const m = new Map([["a", 1]]);
8.1.6 可迭代對象與 for…of
可使用
for...of
、...展開符
的對象:Array、Map、Set、字符串等
for (const item of set) {}
8.2 Class 與模塊系統
8.2.1 class
類定義與繼承
class Person {constructor(name) {this.name = name;}greet() {console.log(`Hi, ${this.name}`);}
}
- ?
extends
實現繼承 - ?
super()
調用父類構造器 - ? 私有屬性:
#privateName
8.2.2 模塊系統(import
/ export
)
// 導出
export const name = "Tom";
export default function greet() {}// 導入
import greet, { name } from "./module.js";
- 支持:默認導出 / 命名導出 / 重命名 / 整體導入
8.3 常用內置 API 與語法糖
8.3.1 新增 API
方法 | 作用 |
---|---|
Object.entries(obj) | 返回鍵值對數組 |
Object.values(obj) | 返回值數組 |
Array.flat(depth) | 扁平化嵌套數組 |
Array.includes(val) | 判斷是否包含 |
Promise.allSettled() | 所有 Promise 返回后統一處理,無論成功失敗 |
8.3.2 Nullish Coalescing(??
)
只有
null
或undefined
才觸發右側默認值
const val = input ?? "默認值";
- ? 不會因
''
或0
而觸發
8.3.3 Optional Chaining(?.
)
安全讀取深層屬性,避免報錯
const city = user?.address?.city;
🔍 記憶建議:
- ? 解構 & 展開:多寫函數參數和對象操作
- ? Symbol:對象私有成員
- ? Set/Map:處理去重與映射優于 Object
- ? ?? 與 ?.:null 安全寫法,高頻出現于項目中
九、瀏覽器通信與網絡
現代前端開發離不開瀏覽器與服務器之間的高效通信,本章將系統梳理 AJAX、跨域機制、本地存儲方案與 Web 通信能力。
9.1 瀏覽器請求方式
9.1.1 XMLHttpRequest 基礎(XHR)
- 原始的 AJAX 技術核心
- 支持事件監聽與狀態碼處理
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/data');
xhr.onreadystatechange = function () {if (xhr.readyState === 4 && xhr.status === 200) {console.log(xhr.responseText);}
};
xhr.send();
9.1.2 Fetch API
更現代的異步請求方式,基于 Promise
fetch('/api/data').then(res => res.json()).then(data => console.log(data)).catch(err => console.error(err));
- ? 更簡潔
- ? 默認不攜帶 cookie(需配置)
- ? 不會自動 reject 4xx/5xx(需手動判斷
res.ok
)
9.1.3 封裝 Fetch 常用方法
async function request(url, options = {}) {const res = await fetch(url, options);if (!res.ok) throw new Error('網絡錯誤');return res.json();
}
9.2 跨域處理策略
9.2.1 同源策略
協議 + 域名 + 端口號 三者相同才算同源
不同源會受到限制(如 DOM 操作、AJAX 請求)
9.2.2 解決方式對比
方法 | 原理 | 優缺點 |
---|---|---|
CORS | 設置響應頭 Access-Control-Allow-Origin | 推薦,標準方案 |
JSONP | 利用 <script> 標簽不受同源限制 | 僅支持 GET,請求安全性差 |
代理轉發 | 本地服務器轉發請求,繞過瀏覽器限制 | 需后端支持,常見于開發環境 |
PostMessage | 窗口間通信(iframe 或新窗口) | 跨域數據安全通信 |
9.3 本地存儲方式對比
9.3.1 Cookie
- 每次請求自動攜帶,常用于身份驗證
- 有大小限制(~4KB)
- 支持設置過期時間、路徑、域
document.cookie = "token=123;path=/;max-age=3600";
9.3.2 localStorage
- 永久存儲(直到手動清除)
- 單域名下最大約 5MB
- 僅支持字符串
localStorage.setItem("key", "value");
localStorage.getItem("key");
9.3.3 sessionStorage
- 頁面會話級別,標簽頁關閉即清除
- API 與 localStorage 相同
? 對比總結:
特性 | Cookie | localStorage | sessionStorage |
---|---|---|---|
是否隨請求發送 | ? | ? | ? |
生命周期 | 可設置 | 永久 | 會話 |
大小限制 | 4KB | 5MB+ | 5MB+ |
跨標簽頁共享 | ? | ? | ? |
9.4 實用通信機制補充
9.4.1 瀏覽器窗口通信:postMessage
用于主窗口與 iframe / 子窗口 / 彈窗間安全通信
window.postMessage("數據", "https://other.com");window.addEventListener("message", (event) => {console.log(event.origin, event.data);
});
9.4.2 全局錯誤監聽
- 捕獲 JS 報錯信息,便于上報與監控
window.onerror = function (msg, url, line, col, error) {console.error('捕獲錯誤:', msg, error);
};window.addEventListener('error', (event) => {console.log('資源加載錯誤:', event.target);
});
9.4.3 實用 Web API 技巧
API | 功能 | 示例 |
---|---|---|
navigator.clipboard.writeText() | 寫入剪貼板 | 復制文本 |
document.visibilityState | 頁面可見性檢測 | visibilitychange 監聽 |
navigator.onLine | 網絡狀態檢測 | 離線/在線判斷 |
Performance API | 頁面性能分析 | performance.now() |
好的,下面是第九章:瀏覽器通信與網絡的進一步補充,涵蓋更深入的網絡通信機制及相關 API,包括 WebSocket、服務端事件(SSE)、網絡狀態檢測、離線緩存等內容。
9.5 實時通信技術
9.5.1 WebSocket 長連接
一種在單個 TCP 連接上進行全雙工通信的協議,適合實時聊天、推送通知等場景。
使用示例:
const socket = new WebSocket('wss://example.com/socket');socket.onopen = () => {socket.send('Hello Server!');
};socket.onmessage = (event) => {console.log('收到消息:', event.data);
};socket.onerror = (err) => console.error('連接錯誤:', err);
socket.onclose = () => console.log('連接關閉');
- ? 持久連接,適合高頻交互
- ? 需服務端配套支持,維護成本高
9.5.2 Server-Sent Events(SSE)
瀏覽器從服務端接收單向推送數據(基于 HTTP)
const source = new EventSource('/api/events');source.onmessage = function (event) {console.log('服務端推送:', event.data);
};
- ? 簡潔、自動重連、支持事件命名
- ? 只支持單向、部分瀏覽器支持
9.6 網絡狀態與頁面生命周期
9.6.1 網絡狀態檢測
console.log(navigator.onLine); // true or falsewindow.addEventListener('online', () => console.log('網絡恢復'));
window.addEventListener('offline', () => console.log('網絡斷開'));
9.6.2 頁面可見性檢測
document.addEventListener('visibilitychange', () => {if (document.visibilityState === 'hidden') {console.log('頁面隱藏');} else {console.log('頁面可見');}
});
- 可用于暫停動畫、視頻、輪詢請求
9.7 離線緩存與存儲技術
9.7.1 Cache API(Service Worker)
配合 Service Worker 實現離線緩存、離線訪問頁面內容
caches.open('v1').then(cache => {cache.addAll(['/index.html', '/styles.css']);
});
- 與 fetch 結合,可攔截請求并返回緩存
- 需 HTTPS 環境下注冊 Service Worker
9.7.2 IndexedDB
瀏覽器提供的事務型數據庫,可存儲結構化數據
const request = indexedDB.open('myDB', 1);
request.onsuccess = (event) => {const db = event.target.result;console.log('數據庫打開成功');
};
- 支持索引、事務、高容量數據
- 常用于大型離線 Web 應用(PWA)
9.8 網絡調優與安全
9.8.1 請求優化技巧
- 開啟
Keep-Alive
復用連接 - 使用 CDN 緩存靜態資源
- 圖片懶加載 + 資源壓縮
- 合理使用緩存頭部(Cache-Control、ETag)
9.8.2 網絡安全機制
- HTTPS 加密傳輸
- 防 XSS/CSRF 攻擊
- 輸入校驗與輸出轉義
- 設置
Content-Security-Policy
安全策略頭
十、性能優化與安全
10.1 性能優化
10.1.1 資源懶加載
將不在視口內的資源延遲加載,減少初始加載時間。
- 圖片懶加載:
<img data-src="image.jpg" class="lazyload" alt="Lazy Load Image">
- JavaScript 懶加載:
const script = document.createElement('script');
script.src = 'path/to/your/script.js';
document.body.appendChild(script);
- 適用場景: 圖片、視頻、JavaScript 等
10.1.2 事件節流與防抖
節流 (Throttling) 和 防抖 (Debouncing) 用于控制頻繁觸發的事件,如滾動、輸入等。
- 防抖:
function debounce(fn, delay) {let timer;return function (...args) {clearTimeout(timer);timer = setTimeout(() => fn(...args), delay);};
}
- 節流:
function throttle(fn, delay) {let last = 0;return function (...args) {const now = Date.now();if (now - last >= delay) {last = now;fn(...args);}};
}
10.1.3 DOM 優化
- 減少 DOM 操作: 批量更新、避免頻繁重排與重繪(例如使用
DocumentFragment
) - 緩存查詢結果: 存儲對 DOM 的查詢結果,避免重復查找
const button = document.getElementById('myButton');
// 使用變量緩存 DOM 查找
10.1.4 異步加載與延遲加載
- 異步加載: 使用
async
和defer
屬性來異步加載 JavaScript 文件,避免阻塞渲染。
<script src="script.js" async></script>
<script src="script.js" defer></script>
- 分塊加載: 使用 Webpack 等工具進行代碼分塊,按需加載,提升初次加載速度。
10.1.5 緩存策略
緩存能夠顯著提高性能,減少重復的網絡請求。
- Service Worker + Cache API: 在瀏覽器端緩存資源,離線訪問。
- HTTP 緩存: 使用
Cache-Control
和ETag
頭部進行緩存控制。
// 在 Service Worker 中緩存請求資源
caches.open('v1').then((cache) => {cache.add('/index.html');
});
- LocalStorage / IndexedDB: 在本地存儲應用數據,避免頻繁請求數據。
10.1.6 圖片與視頻優化
- 圖片格式優化: 使用現代格式(如 WebP)來減少圖片大小。
- SVG 圖標替代位圖圖標:SVG 比 PNG/JPG 更加靈活且文件更小。
- 懶加載: 只在用戶滾動到頁面時才加載圖片。
10.2 前端安全
10.2.1 XSS(跨站腳本攻擊)
攻擊者通過注入惡意腳本,竊取用戶數據或篡改網頁內容。
- 防御方法:
- 輸入驗證與輸出編碼: 對用戶輸入進行嚴格的驗證和轉義,避免腳本執行。
- 使用 CSP(內容安全策略) 來限制加載的腳本源。
<meta http-equiv="Content-Security-Policy" content="script-src 'self'">
- 避免內聯腳本: 使用外部腳本文件,禁止內聯 JavaScript 代碼。
10.2.2 CSRF(跨站請求偽造)
攻擊者偽造用戶請求,造成未授權操作。
- 防御方法:
- Token 驗證: 每次提交請求時,附加一個唯一的 CSRF Token(在表單或請求頭中傳遞),服務器驗證是否匹配。
- SameSite Cookies: 使用
SameSite
屬性,限制 Cookie 在跨站請求時是否隨請求一起發送。
document.cookie = "token=your_token; SameSite=Strict;";
10.2.3 Clickjacking(點擊劫持)
攻擊者通過透明 iframe 誘使用戶點擊其并非意圖點擊的內容。
- 防御方法:
- 使用
X-Frame-Options
頭部阻止頁面被嵌套在 iframe 中。
- 使用
<meta http-equiv="X-Frame-Options" content="DENY">
10.2.4 內容安全策略(CSP)
CSP 是一種防止 XSS 攻擊的安全機制,限制瀏覽器加載資源的源。
- 基本 CSP 配置:
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline';">
- 策略設計:
default-src
:指定默認的加載源script-src
:限制腳本的來源style-src
:限制樣式的來源
10.2.5 其他前端安全防護
- HTTPOnly Cookie: 防止 JavaScript 訪問 Cookie,增強安全性。
- HSTS(HTTP 嚴格傳輸安全): 強制瀏覽器與服務器之間使用 HTTPS 通信。
// 在服務器上啟用 HSTS
Strict-Transport-Security: max-age=31536000; includeSubDomains
- 兩步驗證: 通過發送驗證碼或短信來加強用戶的身份驗證。
十一、項目實戰與面試要點
11.1 項目實踐
- Todo List、畫板、購物車、小型 SPA 實現
- 調試技巧與錯誤捕獲(Try/Catch、window.onerror)
11.2 面試高頻題
- 手寫 Promise、深拷貝、節流/防抖
- 輸出題、閉包題、原型繼承題
十二、進階方向推薦
- TypeScript 基礎與應用
- React / Vue / Svelte 框架精通
- 前端架構、微前端、性能監控
- WebAssembly、Serverless、PWA