/packages/complier-core
- 定位??:??編譯時核心??,處理 Vue 模板的編譯邏輯。
- ??核心功能??:
- ??模板解析??:將?
.vue
?文件的模板語法(HTML-like)解析為 ??抽象語法樹 (AST)??。 - ??轉換優化??:靜態節點提升(Hoist Static)、Patch Flags 標記(動態節點標記)。
- ??代碼生成??:將 AST 轉換為可執行的 ??渲染函數(
render
?函數)??。
- ??模板解析??:將?
- ??特點??:
- 與平臺無關,不直接處理瀏覽器或 DOM,只負責語法層面的轉換。
- 支持自定義編譯器指令(如?
v-xxx
?的自定義擴展)。
? vue3模板編譯原理
模板編譯分為三個階段:
1.解析階段
???????該階段的核心任務是將原始的 HTML 模板字符串轉換為初始的??抽象語法樹(AST)??。解析器通過??狀態機(State Machine)??逐字符掃描模板,識別出標簽、屬性、事件、插值表達式(如?{{ }}
)和組件標簽等語法單元。例如,對于?<div :class="cls">{{ text }}</div>
,解析器會拆解出標簽名?div
、動態屬性?:class
?及其綁定表達式?cls
,以及插值內容?text
,最終構建出一棵樹形結構的原始 AST。此階段的 AST 僅保留模板的語法結構,尚未包含任何優化信息,但它為后續的轉換和代碼生成提供了基礎數據結構。
2.轉換階段(transform)
轉換階段是 Vue3 編譯流程的??核心優化環節??,通過對 AST 的多輪遍歷,實現語義分析和結構改造。這一階段包含多項關鍵技術:
1. Node Transforms 多步處理??
??作用??:通過一系列轉換函數(如?transformIf
、transformFor
)逐層處理 AST 節點,將模板聲明式語法轉換為可執行的 JavaScript 邏輯。
??技術細節??:
- 遞歸遍歷 AST,依次應用?
v-if
?→ 條件表達式、v-for
?→ 循環結構等轉換規則 - 動態綁定分析(如識別?
:class
?和?{{ text }}
?的動態性差異) - 為后續優化階段(如靜態提升)提供結構化標記
2. ??靜態提升(Static Hoisting)??:識別純靜態內容(如無動態綁定的?<div>Static</div>
),將其提取為常量并復用,避免重復創建虛擬節點。
3. ??Patch Flags 標記??:為動態節點添加二進制標志(如?CLASS | STYLE | PROPS
),指導運行時僅對比變化的屬性,跳過全量 Diff。例如,動態綁定的?:class
?和?{{ text }}
?會被標記為不同的 Patch Flag。
4. ??塊追蹤(Block Tracking)??:將包含動態子節點的父節點標記為“塊”(Block),運行時通過?openBlock()(標記一個動態塊的開始,開始收集后續動態節點)
?和?closeBlock()
?(通過createBlock()
自動關閉塊,無需顯式調用)控制更新范圍,縮小虛擬 DOM 的比對粒度。
5. ??緩存優化??:對事件處理函數和動態屬性值進行邏輯賦值緩存(如?_cache[0] || (_cache[0] = ...)
),避免重復創建函數或計算表達式。
這些優化使得 AST 從“原始語法描述”升級為“富含運行時提示的中間形態”,為生成高效代碼奠定了基礎。
3.生成階段
生成階段將優化后的 AST 轉換為??可執行的渲染函數代碼??。代碼生成器遍歷 AST 節點,根據節點類型和優化標記拼接字符串,最終輸出類似?function render() { ... }
?的 JavaScript 函數。例如,一個包含靜態提升和動態綁定的模板會被編譯為:
const _hoisted_1 = createVNode("div", null, "Static");
function render() { return (openBlock(), createBlock("div", null, [ _hoisted_1, createVNode("p", null, ctx.text, 1 /* TEXT */) ]));
}
此階段還會注入運行時代碼(如?createVNode
、openBlock
),并確保生成的代碼符合 JavaScript 語法規范。最終輸出的渲染函數與 Vue 的運行時協作,在組件渲染時高效生成和更新虛擬 DOM。
/packages/reactivity
- 定位??:??響應式系統的獨立實現??,Vue3 的「數據驅動」核心。
- ??核心功能??:
- ??依賴追蹤??:通過?
Proxy
?+?Reflect
?實現數據的響應式代理。 - ??核心 API??:
reactive
:創建深度響應式對象。ref
:包裝基本類型值為響應式引用。effect
:副作用函數(替代 Vue2 的 Watcher)。computed
:計算屬性。
- ??依賴收集與觸發??:通過?
track
?和?trigger
?管理依賴關系。
- ??依賴追蹤??:通過?
proxy通過new Proxy(target,handler) 生成,new Proxy是JS原生API,用于創建一個代理對象,這個代理對象會包裝原始對象(target),并允許通過攔截器(handler)定義目標對象的操作行為
Proxy
一、new Proxy
?的核心作用
1. 創建代理包裝器
- ??輸入??:一個原始對象?
target
?和一個攔截器對象?handler
- ??輸出??:生成一個??代理對象??(Proxy Instance),該代理對象會??轉發所有操作到原始對象??,但允許通過?
handler
?攔截特定操作
const raw = { a: 1 };
const handler = {get(target, key) {console.log(`讀取屬性 ${key}`);return target[key];}
};
const proxy = new Proxy(raw, handler);console.log(proxy.a); // 輸出 "讀取屬性 a",然后輸出 1
2. 定義攔截行為
handler
?對象中可定義??陷阱函數(Traps)??,用于攔截 13 種對象操作(如?get
、set
、deleteProperty
?等)- 當對 Proxy 實例進行操作時,會優先觸發對應的陷阱函數,未定義的陷阱會直接轉發到原始對象
二、生成的 Proxy 實例特點
1. ??透明訪問性??
- ??鏡像原始對象??:Proxy 實例的屬性訪問、方法調用等行為會默認轉發到?
target
const raw = { x: 10 }; const proxy = new Proxy(raw, {}); console.log(proxy.x); // 10(未定義陷阱,直接訪問原始對象)
2. ??操作可攔截性??
- ??精細攔截??:通過陷阱函數可攔截以下操作:
陷阱函數 攔截的操作 get
讀取屬性(如? obj.prop
)set
設置屬性(如? obj.prop = 1
)has
in
?操作符(如?'prop' in obj
)deleteProperty
delete
?操作符(如?delete obj.prop
)apply
函數調用(如? fn()
)construct
new
?操作符(如?new Fn()
)ownKeys
Object.keys()
、for-in
?循環等
3. ??引用獨立性??
- ??獨立對象??:Proxy 實例是一個獨立的對象,與?
target
?是不同引用const raw = {}; const proxy = new Proxy(raw, {}); console.log(proxy === raw); // false
4. ??動態關聯性??
- ??實時關聯??:Proxy 實例的操作會實時影響原始對象
const raw = { a: 1 }; const proxy = new Proxy(raw, {}); proxy.a = 2; console.log(raw.a); // 2(修改代理實例會影響原始對象)
5. ??不可變性??
- ??代理不可逆??:無法通過 Proxy 實例直接獲取原始的?
handler
?或?target
(需通過特殊屬性或方法)const proxy = new Proxy({}, {}); console.log(proxy); // 控制臺輸出 Proxy 對象,但無法直接看到 handler 和 target
三、在 Vue 3 中的特殊設計
在?packages/reactivity
?模塊中,Vue 3 的?reactive()
?函數生成的 Proxy 實例有以下特點:
1. ??響應式觸發器??
get
?陷阱:觸發依賴收集(track
)set
?陷阱:觸發更新通知(trigger
)
2. ??嵌套對象懶代理??
- 僅在訪問嵌套屬性時,才遞歸生成子 Proxy 對象
const obj = reactive({ nested: { a: 1 } // 此時 nested 未被代理 }); console.log(obj.nested); // 訪問時生成嵌套 Proxy
3. ??原始對象標記??
- 通過?
ReactiveFlags.RAW
?標記可獲取原始對象
四、與普通對象的對比
特性 | 普通對象 | Proxy 實例 |
---|---|---|
動態屬性攔截 | 不支持 | 支持(如新增屬性、刪除屬性) |
引用關系 | 獨立對象 | 獨立對象,但操作關聯原始對象 |
操作攔截能力 | 無 | 可攔截 13 種操作 |
性能開銷 | 無 | 微小(但頻繁操作可能累積開銷) |
五、示例:實現一個簡易響應式
const handler = {get(target, key) {console.log(`讀取 ${key}`);return target[key];},set(target, key, value) {console.log(`設置 ${key} 為 ${value}`);target[key] = value;return true;}
};const raw = { count: 0 };
const proxy = new Proxy(raw, handler);proxy.count++; // 輸出 "讀取 count",然后 "設置 count 為 1"
Reflect
一、Reflect
?的本質
Reflect
?是 ES6 引入的全局對象,提供了一套??與對象操作方法一一對應的函數式 API??。例如:
Reflect.get(target, key)
?? 對應?target[key]
Reflect.set(target, key, value)
?? 對應?target[key] = value
Reflect.has(target, key)
?? 對應?key in target
其核心設計目標是??規范化對象操作??,使得原本隱式的操作(如屬性讀寫、in
?操作符)可以通過函數顯式調用。
二、在 Proxy 陷阱中的必要性
當使用?Proxy
?時,必須通過?Reflect
?方法??轉發默認操作??,否則會導致原始對象行為的丟失。以下是關鍵原因:
1. ??保持?receiver
?上下文正確性??
在訪問器屬性(getter/setter)中,this
?的指向需要正確綁定到代理對象(而非原始對象)。
通過?Reflect.get(target, key, receiver)
?的第三個參數?receiver
,可以確保?this
?指向代理對象。
??示例:??
const raw = {_value: 0,get value() {return this._value; // this 必須指向代理對象}
};const proxy = new Proxy(raw, {get(target, key, receiver) {// 使用 Reflect 傳遞 receiverreturn Reflect.get(target, key, receiver);}
});console.log(proxy.value); // 正確觸發代理的 get 陷阱
2. ??返回值標準化??
Reflect
?方法返回布爾值或操作結果,與 Proxy 陷阱的返回值要求一致。例如:
Reflect.set
?返回?boolean
?表示是否設置成功Reflect.deleteProperty
?返回?boolean
?表示是否刪除成功
??示例:??
const proxy = new Proxy({}, {set(target, key, value, receiver) {// 必須返回布爾值return Reflect.set(target, key, value, receiver);}
});
3. ??避免破壞對象內部方法??
直接操作?target[key]
?可能繞過對象內部的?[[Get]]
、[[Set]]
?等內部方法,而?Reflect
?確保操作符合規范。
在 JavaScript 中,直接通過?
target[key]
?操作對象屬性可能會繞過對象內部的?[[Get]]
?和?[[Set]]
?等內部方法,原因如下:
一、
[[Get]]
?和?[[Set]]
?的本質對象的屬性訪問(如?
obj.prop
)和賦值(如?obj.prop = value
)本質上會觸發對象內部的?[[Get]]
?和?[[Set]]
?方法。這些內部方法負責:
- ??訪問器屬性(getter/setter)的執行??
- ??原型鏈的查找??
- ??屬性描述符(如?
writable
、configurable
)的校驗??
二、直接操作?
target[key]
?的問題當在 Proxy 的陷阱函數中直接操作?
target[key]
?時,可能繞過以下關鍵邏輯:1. ??繞過訪問器屬性(getter/setter)??
如果目標對象(
target
)的屬性是訪問器屬性(定義了?get
?或?set
),直接通過?target[key]
?訪問或賦值會??跳過訪問器邏輯??,直接操作底層值。??示例:??
const obj = {get value() {console.log("getter 被調用");return this._value;},set value(v) {console.log("setter 被調用");this._value = v;} };const proxy = new Proxy(obj, {get(target, key) {// 直接返回 target[key],繞過 getterreturn target[key]; // ? 錯誤方式},set(target, key, value) {// 直接賦值 target[key],繞過 settertarget[key] = value; // ? 錯誤方式return true;} });proxy.value = 1; // 不會觸發 setter 的 console.log console.log(proxy.value); // 不會觸發 getter 的 console.log
- ??結果??:直接操作?
target[key]
?會導致訪問器邏輯被繞過,_value
?被直接讀寫。2. ??破壞?
this
?綁定??訪問器屬性中的?
this
?默認指向原始對象(target
),而非代理對象(proxy
)。這會導致依賴代理對象的邏輯(如嵌套響應式)失效。??示例:??
const raw = {get value() {return this._value; // this 指向 raw,而非 proxy} };const proxy = new Proxy(raw, {get(target, key) {return target[key]; // ? this 錯誤綁定到 raw} });proxy._value = 42; console.log(proxy.value); // 42,但若 proxy 有攔截邏輯,this 指向錯誤會導致問題
3. ??繞過嵌套 Proxy??
如果目標對象本身是另一個 Proxy,直接操作?
target[key]
?會??繞過其陷阱函數??,直接訪問原始值。??示例:
const inner = new Proxy({ a: 1 }, {get(target, key) {console.log("inner 的 get 陷阱");return Reflect.get(target, key);} });const outer = new Proxy(inner, {get(target, key) {// 直接訪問 target[key],繞過 inner 的 Proxy 陷阱return target[key]; // ? 不會觸發 inner 的 get 陷阱} });console.log(outer.a); // 直接輸出 1,無日志
4. ??忽略原型鏈和屬性描述符??
直接操作?
target[key]
?可能繞過原型鏈查找和屬性描述符(如?writable
)的校驗。??示例:??
const parent = { a: 1 }; const child = Object.create(parent); const proxy = new Proxy(child, {get(target, key) {return target[key]; // ? 直接返回 undefined(不會查找原型鏈)} });console.log(proxy.a); // undefined(正確應為 1)
三、為什么?
Reflect
?能避免這些問題?
Reflect
?方法(如?Reflect.get
)會嚴格遵循對象的?[[Get]]
?和?[[Set]]
?內部方法,確保以下行為:1. ??觸發訪問器屬性??
const proxy = new Proxy(obj, {get(target, key, receiver) {return Reflect.get(target, key, receiver); // ? 觸發 getter},set(target, key, value, receiver) {return Reflect.set(target, key, value, receiver); // ? 觸發 setter} });
2. ??綁定正確的?
this
(通過?receiver
?參數)??Reflect.get(target, key, receiver);
receiver
?參數確保訪問器屬性中的?this
?指向代理對象(而非原始對象)。3. ??處理原型鏈和屬性描述符??
Reflect.get(target, key, receiver); // ? 沿原型鏈查找屬性 Reflect.set(target, key, value, receiver); // ? 校驗 `writable` 等屬性
4. ??支持嵌套 Proxy??
// 通過 Reflect 觸發內層 Proxy 的陷阱 Reflect.get(innerProxy, key, receiver); // ? 觸發 innerProxy 的 get 陷阱
四、關鍵對比
操作方式 直接操作? target[key]
使用? Reflect
??訪問器屬性?? 繞過 getter/setter 正確觸發 getter/setter ?? this
?綁定??指向原始對象( target
)指向代理對象(通過? receiver
)??原型鏈?? 可能中斷查找 完整執行原型鏈查找 ??嵌套 Proxy?? 繞過內層 Proxy 的陷阱 觸發內層 Proxy 的陷阱 ??屬性描述符校驗?? 忽略? writable
、configurable
?等嚴格校驗
三、在 Vue 響應式系統中的具體應用
Vue 3 的?reactive()
?函數通過?Proxy
?+?Reflect
?實現響應式代理。以下是核心場景:
1. ??依賴收集(Track)??
在?get
?陷阱中,通過?Reflect.get
?獲取值,并收集依賴:
function createGetter() {return function get(target: object, key: string | symbol, receiver: object) {// 使用 Reflect.get 獲取原始值const res = Reflect.get(target, key, receiver);// 依賴追蹤track(target, TrackOpTypes.GET, key);// 遞歸代理嵌套對象if (isObject(res)) {return reactive(res);}return res;};
}
2. ??觸發更新(Trigger)??
在?set
?陷阱中,通過?Reflect.set
?設置值,并觸發更新:
function createSetter() {return function set(target: object,key: string | symbol,value: unknown,receiver: object): boolean {// 獲取舊值const oldValue = (target as any)[key];// 使用 Reflect.set 設置值const result = Reflect.set(target, key, value, receiver);// 觸發更新(僅當值變化時)if (hasChanged(value, oldValue)) {trigger(target, TriggerOpTypes.SET, key);}return result;};
}
3. ??處理?has
?和?ownKeys
?陷阱??
攔截?in
?操作符和?Object.keys()
:
四、Reflect
?與直接操作對象的區別
操作方式 | 直接操作對象(如?target[key] ) | 使用?Reflect |
---|---|---|
??this 綁定?? | this ?指向原始對象 | 通過?receiver ?參數控制?this ?指向 |
??返回值?? | 無標準返回值(可能拋出錯誤) | 標準化布爾值或操作結果 |
??原型鏈處理?? | 可能無法正確處理繼承屬性 | 嚴格遵循對象原型鏈規則 |
??函數式調用?? | 無法以函數形式調用 | 函數式 API,便于組合和抽象 |
五、關鍵設計思想總結
-
??行為一致性??
Reflect
?方法嚴格遵循 ECMAScript 規范,確保代理對象的行為與原對象一致。 -
??代理鏈完整性??
在多層代理(如 Vue 的?readonly(reactive(obj))
)中,Reflect
?能正確傳遞?receiver
,維護代理鏈的上下文。 -
??錯誤處理簡化??
通過?Reflect
?的布爾返回值,避免手動處理異常(如設置不可寫屬性時的?TypeError
)。
示例:沒有?Reflect
?的陷阱
若在 Proxy 陷阱中不使用?Reflect
,會導致意外行為:
const raw = { a: 1 };
const proxy = new Proxy(raw, {get(target, key) {// 錯誤!未使用 Reflect.get,this 指向錯誤return target[key];},set(target, key, value) {// 錯誤!未返回布爾值target[key] = value;return true;}
});const child = { __proto__: proxy };
console.log(child.a); // 期望觸發 get 陷阱,但實際不會!
/packages/runtime-core
- 定位??:??運行時核心??,實現 Vue 組件的生命周期、虛擬 DOM、渲染調度等。
- ??核心功能??:
- ??虛擬 DOM 算法??:Diff 算法(
patchKeyedChildren
)、節點 Patch 邏輯。 - ??組件系統??:組件實例化、Props/Emits 處理、Slots 管理。
- ??生命周期鉤子??:
onMounted
、onUpdated
?等 Composition API 的實現。 - ??自定義渲染器??:提供?
createRenderer
?API,支持跨平臺渲染(如小程序、Canvas)。
- ??虛擬 DOM 算法??:Diff 算法(
- ??特點??:
- 平臺無關,不直接操作 DOM,依賴外部傳入的 ??宿主環境 API??。
- 包含 Vue 的核心邏輯,但需與?
runtime-dom
?結合才能用于瀏覽器。
/packages/runtime-dom
- 定位??:??瀏覽器端運行時??,提供 DOM 相關的原生操作。
- ??核心功能??:
- ??DOM 操作 API??:封裝?
document.createElement
、parent.appendChild
?等原生方法。 - ??事件處理??:標準化事件監聽(如?
onClick
?的自動轉換)。 - ??屬性處理??:處理 HTML Attributes、DOM Properties、類名、樣式等。
- ??DOM 操作 API??:封裝?
- ??特點??:
- 基于?
runtime-core
?實現,是 Vue 在瀏覽器環境下的 ??默認渲染器??。 - 導出?
createApp
?等瀏覽器端專用 API。
- 基于?
/packages/runtime-test
- ??定位??:??測試用運行時??,用于單元測試場景。
- ??核心功能??:
- ??輕量級 DOM 模擬??:提供內存中的虛擬 DOM 操作,避免依賴真實瀏覽器環境。
- ??序列化輸出??:將渲染結果序列化為字符串,方便斷言(Assertion)。
- ??生命周期追蹤??:記錄組件生命周期鉤子的調用順序。
- ???使用場景??:
- 測試組件渲染邏輯,不依賴?
jsdom
?或瀏覽器。 - 驗證虛擬 DOM 的 Diff 算法是否正確。
- 測試組件渲染邏輯,不依賴?