手寫(xiě) Vue Router、手寫(xiě)響應(yīng)式實(shí)現(xiàn)哀墓、虛擬 DOM 和 Diff 算法(三)

Virtual DOM 的實(shí)現(xiàn)原理

  • 了解什么是虛擬DOM趁餐,以及虛擬DOM的作用
  • Snabbdom的基本使用
  • Snabbdom的源碼解析
一、什么是虛擬DOM ----Virtual DOM
  • 虛擬DOM是由普通的JS對(duì)象來(lái)描述DOM對(duì)象


    image.png
二篮绰、 為什么要使用Virtual DOM
  • 前端開(kāi)發(fā)初期后雷,MVVM框架解決視圖和狀態(tài)同步問(wèn)題
  • 模板引擎可以簡(jiǎn)化視圖操作,沒(méi)辦法跟蹤狀態(tài)
  • 虛擬DOM跟蹤狀態(tài)變化
  • 參考GitHub上Virtual-dom的動(dòng)機(jī)描述
    • 虛擬DOM可以維護(hù)程序上的狀態(tài)吠各,跟蹤上一次的狀態(tài)
    • 通過(guò)比較前后兩次狀態(tài)差異更新真實(shí)DOM

虛擬DOM用來(lái)維護(hù)視圖和狀態(tài)的關(guān)系

三臀突、虛擬DOM的作用和虛擬DOM庫(kù)
  • 虛擬DOM的作用

    • 維護(hù)視圖的狀態(tài)和關(guān)系
    • 復(fù)雜視圖情況下提升渲染性能
    • 跨平臺(tái)
      • 瀏覽器平臺(tái)渲染DOM
      • 服務(wù)端渲染SSR(Nuxt.js/Next.js)
      • 原生應(yīng)用(Weex/React Native)
      • 小程序(mpvue/uni-app)等
  • 虛擬DOM庫(kù)

    • Snabbdom
    • Vue.js 2.x 內(nèi)部使用的虛擬DOM就是改造的Snabbdom
    • 大約200 SLOC(Single line of code)
    • 通過(guò)模塊可擴(kuò)展
    • 源碼使用TypeScript 開(kāi)發(fā)
    • 最快的Virtual Dom 之一
    • virtual-dom
四、 Snabbdom 的基本使用
  • 1走孽、基本步驟:
    • 初始化項(xiàng)目目錄并安裝Parcel
//創(chuàng)建項(xiàng)目目錄
md snabbdom-demo
//進(jìn)入項(xiàng)目目錄
cd snabbdom-demo 
//創(chuàng)建package.json
npm init -y
//本地安裝parcel
npm install parcel-bundler -D

  • 配置package.json中的scripts
"scripts:"{
  "dev":"parcel index.html --open",
  "build":"parcel build index.html"
}
  • 創(chuàng)建目錄結(jié)構(gòu)
    • 根目錄創(chuàng)建index.html,引入src目錄中的文件
    • 在src中創(chuàng)建js文件來(lái)導(dǎo)入使用的snabbdom進(jìn)行編碼
image.png
image.png
image.png
//導(dǎo)入snabbdom
 npm install snabbdom@2.1.0


import { init } from 'snabbdom/build/package/init'
import { h } from 'snabbdom/build/package/h'

const patch = init([])
  • 3、案例1
import { init } from 'snabbdom/build/package/init'
import { h } from 'snabbdom/build/package/h'

const patch = init([])
// 第一個(gè)參數(shù):標(biāo)簽+選擇器
// 第二個(gè)參數(shù):如果是字符串就是標(biāo)簽中的文本內(nèi)容
let vnode = h('div#container.cls', 'hello world')
let app = document.querySelector('#app')
// patch函數(shù)中的第一個(gè)參數(shù):舊的VNode磕瓷,也可以是DOM元素
// 第二個(gè)參數(shù):新的VNode
// 返回新的VNode
let oldVnode = patch(app, vnode)

vnode = h('div#container.xxx','hello Snabbdom')
patch(oldVnode,vnode)
image.png

image.png
  • 4盒齿、案例2
import { init } from 'snabbdom/build/package/init'
import { h } from 'snabbdom/build/package/h'

const patch = init([])

let vnode = h('div#container', [
  h('h1', 'hello Snabbdom'),
  h('p', '這是一個(gè)段落'),
])

let app = document.querySelector('#app')

let oldVnode = patch(app, vnode)

setTimeout(() => {
  // 更改內(nèi)容
  //   vnode = h('div#container', [h('h1', 'hello World'), h('p', 'hello P!')])
  //   patch(oldVnode, vnode)

  //清除div中的內(nèi)容  h('!')-->生成空的節(jié)點(diǎn)
  patch(oldVnode, h('!'))
}, 2000)

五、 Snabbdom 模塊的使用
  • 模塊的作用
    • Snabbdom 的核心庫(kù)并不能處理DOM元素的屬性困食、樣式边翁、事件等,可以通過(guò)注冊(cè)Snabbdom默認(rèn)提供的模塊來(lái)實(shí)現(xiàn)
    • Snabbdom 中的模塊可以用來(lái)擴(kuò)展Snabbdom的功能
    • Snabbdom中的模塊的實(shí)現(xiàn)是通過(guò)注冊(cè)全局的鉤子來(lái)實(shí)現(xiàn)的
  • 官方提供的模塊
    • attributes 設(shè)置元素屬性 會(huì)處理布爾類型的屬性
    • props 設(shè)置元素屬性 不會(huì)處理布爾類型的屬性
    • dataset 處理html5中的data-的自定義屬性
    • class 用來(lái)切換類樣式
    • style 用來(lái)設(shè)置行內(nèi)樣式硕盹,可以很容易設(shè)置過(guò)度動(dòng)畫(huà)
    • eventlisteners 用來(lái)注冊(cè)和移除事件
  • 模塊的使用步驟
    • 導(dǎo)入需要的模塊
    • init()中注冊(cè)模塊
    • h()函數(shù)中的第二個(gè)參數(shù)處使用模塊
import { init } from 'snabbdom/build/package/init'
import { h } from 'snabbdom/build/package/h'

// 1符匾、導(dǎo)入模塊
import { styleModule } from 'snabbdom/build/package/modules/style'
import { eventListenersModule } from 'snabbdom/build/package/modules/eventlisteners'

// 2、注冊(cè)模塊
const patch = init([styleModule, eventListenersModule])
// 3瘩例、使用h()函數(shù)的地兒個(gè)參數(shù)傳入模塊中使用的數(shù)據(jù)(對(duì)象)
let vnode = h('div', [
  h('h1', { style: { background: 'red' } }, 'hello World'),
  h('p', { on: { click: eventHandler } }, 'hello p'),
])

function eventHandler() {
  console.log('點(diǎn)擊了')
}

let app = document.querySelector('#app')
patch(app, vnode)

六啊胶、 Snabbdom 源碼解析
  • 如何學(xué)習(xí)源碼
    • 宏觀了解
    • 待著目標(biāo)看源碼
    • 看源碼的過(guò)程要圍繞核心目標(biāo)
    • 調(diào)試
    • 參考資料
  • Snabbdom的核心
    • init()設(shè)置模塊甸各,創(chuàng)建patch()函數(shù)
    • 使用h()函數(shù)創(chuàng)建javascript對(duì)象(VNode)描述真實(shí)DOM、
    • patch()比較新舊兩個(gè)Vnode
    • 把變化的內(nèi)容更新到真實(shí)的DOM樹(shù)
  • 源碼地址
  • 克隆代碼
npm install
 
npm run build  

查看
七焰坪、 h() 函數(shù)
  • h函數(shù)介紹
    • 作用:創(chuàng)建vNode 對(duì)象
    • vue中的h函數(shù)
    • h 函數(shù)最早見(jiàn)于hyperscript,使用JavaScript創(chuàng)建超文本
//vue中的h函數(shù)
new Vue({
  router,
  store,
  render:h => h(App)
}).$mount('#app)
  • 函數(shù)重載
    • 概念:參數(shù)個(gè)數(shù)或參數(shù)類型不同的函數(shù)趣倾,重載的概念和參數(shù)相關(guān),和返回值無(wú)關(guān)
    • JavaScript 中沒(méi)有重載的概念
    • TypeScript中有重載某饰,不過(guò)重載的實(shí)現(xiàn)還是通過(guò)代碼調(diào)整參數(shù)
//函數(shù)重載--參數(shù)個(gè)數(shù)
function add(a:number,b:number){
  console.log(a+b);
}
function add(a:number,b:number,c:number){
  console.log(a+b+c);
}
add(1,2)
add(1,2,3)
//函數(shù)重載--參數(shù)類型
function add(a:number,b:number){
  console.log(a+b);
}
function add(a:number,b:string){
  console.log(a+b);
}
add(1,2)
add(1,'2')

// 函數(shù)的重載
export function h (sel: string): VNode
export function h (sel: string, data: VNodeData | null): VNode
export function h (sel: string, children: VNodeChildren): VNode
export function h (sel: string, data: VNodeData | null, children: VNodeChildren): VNode
export function h (sel: any, b?: any, c?: any): VNode {
  var data: VNodeData = {}
  var children: any
  var text: any
  var i: number
  // 處理參數(shù)儒恋,實(shí)現(xiàn)重載的機(jī)制
  if (c !== undefined) {
    // 處理三個(gè)參數(shù)的情況
    // sel data children/text
    if (b !== null) {
      data = b
    }
    if (is.array(c)) {
      children = c
      // 如果c是字符串或者數(shù)字
    } else if (is.primitive(c)) {
      text = c
      // 如果c 是VNode
    } else if (c && c.sel) {
      children = [c]
    }
  } else if (b !== undefined && b !== null) {
    if (is.array(b)) {
      children = b
    } else if (is.primitive(b)) {
      text = b
    } else if (b && b.sel) {
      children = [b]
    } else { data = b }
  }
  if (children !== undefined) {
    // 處理children中的原始值(string/number)
    for (i = 0; i < children.length; ++i) {
      // 如果child 是string/number,創(chuàng)建文本節(jié)點(diǎn)
      if (is.primitive(children[i])) children[i] = vnode(undefined, undefined, undefined, children[i], undefined)
    }
  }
  if (
    sel[0] === 's' && sel[1] === 'v' && sel[2] === 'g' &&
    (sel.length === 3 || sel[3] === '.' || sel[3] === '#')
  ) {
    // 如果是svg,添加命名空間
    addNS(data, children, sel)
  }
  // 返回VNode
  return vnode(sel, data, children, text, undefined)
};

八、 快捷鍵

快速定位
Alt + ←向左方向鍵
ctrl+鼠標(biāo)左鍵

九黔漂、 VNode
image.png
十诫尽、 Patch 整體過(guò)程分析
  • patch(oldVnode,newVnode)
  • 把新節(jié)點(diǎn)中變化的內(nèi)容渲染到真實(shí)的DOM,最后返回新節(jié)點(diǎn)作為下一次處理的舊節(jié)點(diǎn)
  • 對(duì)比新舊VNode是否相同節(jié)點(diǎn)(節(jié)點(diǎn)的key和sel相同)
  • 如果不是相同節(jié)點(diǎn)炬守,刪除之前的內(nèi)容牧嫉,重新渲染
  • 如果是相同節(jié)點(diǎn),再判斷新的VNode是否有text,如果有并且和oldVnode 的text不同劳较,直接更新文本內(nèi)容
  • 如果有新的VNode有children驹止,判斷子節(jié)點(diǎn)是否有變化
十一、patchVnode
image.png
十二观蜗、 Diff 算法
  • 虛擬DOM中的Differences算法

    • 查找兩棵樹(shù)每一個(gè)節(jié)點(diǎn)的差異


      image.png
  • Snabbdom 根據(jù)DOM的特點(diǎn)對(duì)傳統(tǒng)的diff算法做了優(yōu)化

    • DOM操作時(shí)候很少會(huì)跨級(jí)別操作節(jié)點(diǎn)
    • 只比較同級(jí)別的節(jié)點(diǎn)


      image.png
  • 對(duì)比子節(jié)點(diǎn)的具體過(guò)程,在對(duì)開(kāi)始和結(jié)束節(jié)點(diǎn)比較的時(shí)候衣洁,共有四種情況

    • oldStartVnode / newStartVnode (舊開(kāi)始節(jié)點(diǎn) / 新開(kāi)始節(jié)點(diǎn))
    • oldEndVnode / newEndVnode (舊結(jié)束節(jié)點(diǎn) / 新結(jié)束節(jié)點(diǎn))
    • oldStartVnode / newEndVnode (舊開(kāi)始節(jié)點(diǎn) / 新結(jié)束節(jié)點(diǎn))
    • oldEndVnode / newStartVnode (舊結(jié)束節(jié)點(diǎn) / 新開(kāi)始節(jié)點(diǎn))
image.png
  • 開(kāi)始和結(jié)束節(jié)點(diǎn)

    • 如果新舊開(kāi)始節(jié)點(diǎn)是sameVnode(key和sel相同)
      • 調(diào)用patchVnode()對(duì)比和更新節(jié)點(diǎn)
      • 把舊開(kāi)始和新開(kāi)始索引往后移動(dòng) oldStartIdx ++ / newStartIdx ++


        image.png
  • 舊開(kāi)始節(jié)點(diǎn) / 新結(jié)束節(jié)點(diǎn)

    • 調(diào)用patchVnode()對(duì)比和更新節(jié)點(diǎn)
    • 把oldStartVnode對(duì)應(yīng)的DOM元素墓捻,移動(dòng)到右邊,更新索引


      image.png
  • 舊結(jié)束節(jié)點(diǎn) / 新開(kāi)始節(jié)點(diǎn)

    • 調(diào)用patchVnode()對(duì)比和更新節(jié)點(diǎn)
    • 把oldStartVnode對(duì)應(yīng)的DOM元素坊夫,移動(dòng)到左邊砖第,更新索引


      image.png
  • 非上述四種情況


    image.png
  • 循環(huán)結(jié)束

    • 當(dāng)老節(jié)點(diǎn)的所有子節(jié)點(diǎn)先遍歷完(oldStartIdx>oldEndIdx),循環(huán)結(jié)束
    • 當(dāng)新節(jié)點(diǎn)的所有子節(jié)點(diǎn)先遍歷完(newStartIdx>newEndIdx),循環(huán)結(jié)束
  • oldStartIdx > oldEndIdx

    • 如果老節(jié)點(diǎn)的數(shù)組先遍歷完(oldStartIdx > oldEndIdx)
      • 說(shuō)明新節(jié)點(diǎn)有剩余,把剩余節(jié)點(diǎn)批量插入到右邊


        image.png
  • newStartIdx >newEndIdx

    • 如果新節(jié)點(diǎn)的數(shù)組先遍歷完(newStartIdx > newEndIdx)
      • 說(shuō)明老節(jié)點(diǎn)有剩余环凿,把剩余節(jié)點(diǎn)批量刪除


        image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末梧兼,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子智听,更是在濱河造成了極大的恐慌羽杰,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件到推,死亡現(xiàn)場(chǎng)離奇詭異考赛,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)莉测,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)颜骤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人捣卤,你說(shuō)我怎么就攤上這事忍抽“诵ⅲ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵鸠项,是天一觀的道長(zhǎng)干跛。 經(jīng)常有香客問(wèn)我,道長(zhǎng)锈锤,這世上最難降的妖魔是什么驯鳖? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮久免,結(jié)果婚禮上浅辙,老公的妹妹穿的比我還像新娘级解。我一直安慰自己拌蜘,他們只是感情好瘪弓,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布片仿。 她就那樣靜靜地躺著罐监,像睡著了一般喉誊。 火紅的嫁衣襯著肌膚如雪画髓。 梳的紋絲不亂的頭發(fā)上缩举,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天衣赶,我揣著相機(jī)與錄音诊赊,去河邊找鬼。 笑死府瞄,一個(gè)胖子當(dāng)著我的面吹牛碧磅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播遵馆,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼鲸郊,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了货邓?” 一聲冷哼從身側(cè)響起秆撮,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎换况,沒(méi)想到半個(gè)月后职辨,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡复隆,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年拨匆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挽拂。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡惭每,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情台腥,我是刑警寧澤宏赘,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站黎侈,受9級(jí)特大地震影響察署,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜峻汉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一贴汪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧休吠,春花似錦扳埂、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至柜思,卻和暖如春岩调,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背赡盘。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工号枕, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人陨享。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓堕澄,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親霉咨。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容