愛伊米

簡單、好懂的Svelte實現原理

作者:卡頌

簡介:《React技術揭秘》作者

Svelte問世很久了,一直想寫一篇好懂的原理分析文章,拖了這麼久終於寫了。

本文會圍繞一張流程圖和兩個Demo講解,正確的食用方式是用電腦開啟本文,跟著流程圖、Demo一邊看、一邊敲、一邊學。

讓我們開始吧!

Demo1

Svelte的實現原理:

簡單、好懂的Svelte實現原理

圖中Component是開發者編寫的元件,內部虛線部分是由Svelte編譯器編譯而成的。圖中的各個箭頭是執行時的工作流程。

首先來看編譯時,考慮如下App元件程式碼:

完整程式碼地址:https://svelte。dev/repl/9945d189204a4168b4c23890f1d92a3a?version=3。19。1

瀏覽器顯示:

簡單、好懂的Svelte實現原理

這段程式碼經由編譯器編譯後產生如下程式碼,包括三部分:

create_fragment方法

count的宣告語句

class App的宣告語句

create_fragment

首先來看

create_fragment

方法,他是編譯器根據App的UI編譯而成,提供該元件與瀏覽器互動的方法,在上述編譯結果中,包含3個方法:

c,代表create,用於根據模版內容,建立對應DOM Element。例子中建立H1對應DOM Element:

m,代表mount,用於將c建立的DOM Element插入頁面,完成元件首次渲染。例子中會將H1插入頁面:

d,代表detach,用於將元件對應DOM Element從頁面中移除。例子中會移除H1:

仔細觀察流程圖,會發現App元件編譯的產物沒有圖中fragment內的p方法。

這是因為App沒有變化狀態的邏輯,所以相應方法不會出現在編譯產物中。

可以發現,create_fragment返回的c、m方法用於元件首次渲染。那麼是誰呼叫這些方法呢?

簡單、好懂的Svelte實現原理

SvelteComponent

每個元件對應一個繼承自SvelteComponent的class,例項化時會呼叫init方法完成元件初始化,create_fragment會在init中呼叫:

總結一下,流程圖中虛線部分在Demo1中的編譯結果為:

fragment:編譯為create_fragment方法的返回值

UI:create_fragment返回值中m方法的執行結果

ctx:代表元件的上下文,由於例子中只包含一個不會改變的狀態count,所以ctx就是count的宣告語句

可以改變狀態的Demo

現在修改Demo,增加update方法,為H1繫結點選事件,點選後count改變:

完整程式碼地址:https://svelte。dev/repl/bf22a31a0eff4875b5b3084aa2b85fc3?version=3。19。1

編譯產物發生變化,ctx的變化如下:

count從module頂層的宣告語句變為instance方法內的變數。之所以產生如此變化是因為App可以例項化多個:

當count不可變時,所有App可以複用同一個count。但是當count可變時,根據不同App被點選次數不同,頁面可能渲染為:

所以每個App需要有獨立的上下文儲存count,這就是instance方法的意義。

推廣來說,Svelte編譯器會追蹤內所有變數宣告:

是否包含改變該變數的語句,比如count++

是否包含重新賦值的語句,比如count = 1

等等情況

一旦發現,就會將該變數提取到instance中,instance執行後的返回值就是元件對應ctx。

同時,如果執行如上操作的語句可以透過模版被引用,則該語句會被$$invalidate包裹。

在Demo2中,update方法滿足:

包含改變count的語句 —— count++

可以透過模版被引用 —— 作為點選回撥函式

所以編譯後的update內改變count的語句被$$invalidate方法包裹:

從流程圖可知,$$invalidate方法會執行如下操作:

更新ctx中儲存狀態的值,比如Demo2中count++

標記dirty,即標記App UI中所有和count相關的部分將會發生變化

排程更新,在microtask中排程本次更新,所有在同一個macrotask中執行的$$invalidate都會在該macrotask執行完成後被統一執行,最終會執行元件fragment中的p方法

簡單、好懂的Svelte實現原理

p方法是Demo2中新的編譯產物,除了p之外,create_fragment已有的方法也產生相應變化:

p方法會執行$$invalidate中標記為dirty的項對應的更新函式。

在Demo2中,App UI中只引用了狀態count,所以update方法中只有一個if語句,如果UI中引用了多個狀態,則p方法中也會包含多個if語句:

對應p方法包含多個if語句:

Demo2完整的更新步驟如下:

點選H1觸發回撥函式update

update內呼叫$$invalidate,更新ctx中的count,標記count為dirty,排程更新

執行p方法,進入dirty的項(即count)對應if語句,執行更新對應DOM Element的方法

總結

Svelte的完整工作流程會複雜的多,但是核心實現便是如此。

我們可以直觀的感受到,藉由模版語法的約束,經過編譯最佳化,可以直接建立狀態與要改變的DOM節點的對應關係。

在Demo2中,狀態count的變化直接對應p方法中一個if語句,使得Svelte執行細粒度的更新時對比使用虛擬DOM的框架更有效能優勢。

簡單、好懂的Svelte實現原理

上述效能分析中第四行select row就是一個細粒度的更新。想比較之下,React(倒數第三列)效能就差很多。