前段時間進行了weex頁面嘗試, 頁面滾動加載渲染得非常流暢, 讓h5頁面擁有了native般的體驗。
weex代碼結構如下,重點關注其js-framework實作。
閱讀js-framework代碼,我整理了一份思維導圖。
framework.js是instance建立的入口,可以從這個檔案開始自頂向下地閱讀代碼,了解其工作原理。可以重點了解它的dom結構,初始化過程,資料更新過程,下面我也将從這幾個方面進行描述。
weex的dom結構由<code>document</code>、<code>element</code>、<code>comment</code>三類組成。element建立普通節點,comment用于建立frag block節點。每個節點都有一個唯一的<code>ref</code>值,可以很友善地在文檔中被查詢到,同時記錄其父節點<code>parentref</code>,通過這種’雙向連結清單‘的操作可以友善進行節點拼接和擷取。文檔樹節點document記錄整個dom的結構,同時在document上綁定eventmanager事件處理器和listener監聽操作處理器。<code>eventmanager</code>記錄每個綁定了事件的節點和它對應的事件處理函數,提供事件的添加、删除和觸發。<code>listener</code>提供了dom操作轉化為callnative的能力,通過将每一個操作轉化為對應類型的actions,如<code>createbody</code>、<code>addelement</code>,并将每一個actions記錄updates數組。
weex複用了 vue.js 的資料監聽和依賴收集的代碼實作。通過observer、directive、watcher之間的協作,建立資料(model)和視圖(view)的關聯關系:
observer 對 data 進行了監聽,并且提供訂閱某個資料項的變化的能力
compiler解析template,并解析其中的 directive,得到每一個 directive 所依賴的資料項及其更新方法
watcher 把上述兩部分結合起來,即把 directive 中的資料依賴訂閱在對應資料的 observer 上,這樣當資料變化的時候,就會觸發 observer,進而觸發相關依賴對應的視圖更新方法。
當我們在浏覽器中輸入我們的bundle位址,其解析渲染為html過程大緻可以分解為createinstance->initinstace->run bundle->define->boostrap->create vm->生命周期函數。可細化為下面這些步驟:
<code>initinstance</code>: 根據webpack打包後的js代碼來定義執行個體。
<code>define</code>: 解析代碼中的<code>__weex_define__("@weex-component/bottom-bar")</code>定義的component,包含依賴的子元件。并将component記錄到<code>customcomponentmap[name] = exports</code>數組中,維護元件與元件代碼的對應關系。由于會依賴子元件,是以會被多次調用。
define執行後的appinstance執行個體結構:
<code>bootstrap</code>:解析代碼中的<code>__weex_bootstrap__("@weex-component/30d1c553f95b5f87afb6a1cff70a7bbd")</code>執行目前頁面,提取customcomponentmap中記錄的元件代碼進行初始化。隻會執行一次。
<code>downgrade</code>: 檢測頁面降級配置進行頁面降級。
<code>initevents</code>: 綁定events和lifecycle(init、create、ready)執行的鈎子。
<code>initscope</code>: 執行initdata()、initcomputed、initmethods。初始化data、computed屬性和methods,并進行data的observer監聽。
<code>build</code>: 根據預留選項<code>opt.replace</code>進行編譯,目前該選項還未被實質使用。編譯完成後執行ready的鈎子指令,執行ready。
<code>compile</code>: 編譯視圖。
<code>updateactions</code>: 檢測是否有資料更新需要執行。
<code>createfinish</code>: 表明dom結建構立完成,想callqueue隊列中添加一個'createfinish'的actions。
<code>processcallqueue</code>: 依次執行隊列中的actions,進行節點渲染到頁面的過程,為了性能考慮,通過requestanimationframe進行分幀渲染。
callqueue隊列
通過初始化過程我們可以得到<code>init -> 資料監聽 -> created -> 視圖生成 -> ready</code>,為了避免重複的視圖操作,可在init進行資料的擷取,created階段進行資料的指派和修改。
從上圖可知,weex提供了兩種append方式:tree、node。
tree:先渲染子節點樹,最後渲染父節點
node:先渲染父節點,然後子節點一個個append
進行了不同的節點數量,vm建立耗時采樣對比,從圖中可以看出當節點個數較多的時候tree模式比node模式渲染的快
| 1個文本節點 | 第1次 | 第2次 | 第3次 | 第4次 | 第5次 | 平均 |
| ----- | ------ | ------- | ------- | ----- | ------ | ------- |
| tree | 8ms | 10ms| 12ms| 9ms| 10ms| 9.8ms|
| node | 10ms| 9ms| 9ms| 9ms| 9ms| 9.2ms|
| 20個文本節點 | 第1次 | 第2次 | 第3次 | 第4次 | 第5次 | 平均 |
| tree | 17ms | 18ms| 18ms| 16ms| 18ms| 17.4ms|
| node | 18ms| 17ms| 21ms| 17ms| 18ms| 18.2ms|
| 50個文本節點 | 第1次 | 第2次 | 第3次 | 第4次 | 第5次 | 平均 |
| tree | 32ms | 28ms| 26ms| 27ms| 27ms|28ms|
| node | 30ms| 29ms| 34ms| 33ms| 31ms| 31.4ms|
| 100個文本節點 | 第1次 | 第2次 | 第3次 | 第4次 | 第5次 | 平均 |
| tree | 44ms | 41ms| 37ms| 37ms| 44ms|40.6ms|
| node | 46ms| 44ms| 41ms| 44ms| 43ms| 43.6ms|
<code>attachtarget</code>: 進行節點渲染的時候,将每個append動作細化為具體的actions,置入callqueue隊列中。
<code>updateactions</code>: 檢測是否有diff,如果有,則執行diff中記錄的task
<code>calltasks</code>: 調用callnative,根據執行狀态判斷是否執行callqueue清單中的人物或者置入callqueue隊列中。
執行click事件,其中修改了data資料值,執行順序如下:
calljs響應事件、接受事件,通過eventmanager獲得事件目标響應函數并fire執行,通過watcher監聽資料修改,如果資料前後不等則将修改更新操作記入diff中,同時通知訂閱它的依賴繼續收集更新操作。最終執行updateactions完成資料更新操作。
通過上文分析,可以認為:
tree模式比node模式渲染的快。
每個節點的建立都對應一個callqueue任務,節點逐個逐個的append到頁面中。
依賴釋出訂閱模式收集依賴,監聽每一個屬性的變化,可直接擷取更新操作,映射到dom結構中。
與native互動通過 callnative()方法;響應js調用采用calljs()方法。
以上是個人拙見,本文中描述不正确的地方歡迎指正~