[筆記] Vue3.0源碼解析

Vue 3.0 源碼解析

源碼優(yōu)化

  • 目的是讓代碼更易于開發(fā)和維護(hù)。源碼的優(yōu)化主要體現(xiàn)在使用monorepo 和 TypeScript 管理和開發(fā)源碼,這樣做的目標(biāo)是提升自身代碼可維護(hù)性
  • Vue.js 2.x 的源碼托管在 src 目錄,然后依據(jù)功能拆分出了 compiler(模板編譯的相關(guān)代碼)峡扩、core(與平臺(tái)無關(guān)的通用運(yùn)行時(shí)代碼)、platforms(平臺(tái)專有代碼)、server(服務(wù)端渲染的相關(guān)代碼)撮抓、sfc(.vue 單文件解析相關(guān)代碼)、shared(共享工具代碼) 等目錄摇锋。
  • Vue.js 3.0 丹拯,整個(gè)源碼是通過 monorepo 的方式維護(hù)的站超,根據(jù)功能將不同的模塊拆分到 packages 目錄下面不同的子目錄中,monorepo 把這些模塊拆分到不同的 package 中乖酬,每個(gè) package 有各自的 API死相、類型定義和測(cè)試。這樣使得模塊拆分更細(xì)化咬像,職責(zé)劃分更明確算撮,模塊之間的依賴關(guān)系也更加明確,開發(fā)人員也更容易閱讀施掏、理解和更改所有模塊源碼钮惠,提高代碼的可維護(hù)性。

性能優(yōu)化

  • 源碼體積優(yōu)化七芭,JavaScript 包體積越小素挽,意味著網(wǎng)絡(luò)傳輸時(shí)間越短,JavaScript 引擎解析包的速度也越快
  • Vue.js 3.0 在源碼體積的減少方面:移除一些冷門的 feature(比如 filter狸驳、inline-template 等)预明,引入 tree-shaking 的技術(shù),減少打包體積
  • 數(shù)據(jù)劫持優(yōu)化耙箍,Vue.js 1.x 和 Vue.js 2.x 內(nèi)部都是通過 Object.defineProperty 這個(gè) API 去劫持?jǐn)?shù)據(jù)的 getter 和 setter撰糠,它必須預(yù)先知道要攔截的 key,它并不能檢測(cè)對(duì)象屬性的添加和刪除(提供了 set 和delete 實(shí)例方法)辩昆。
  • 嵌套層級(jí)比較深的對(duì)象阅酪,要劫持它內(nèi)部深層次的對(duì)象變化,就需要遞歸遍歷這個(gè)對(duì)象汁针,執(zhí)行 Object.defineProperty 把每一層對(duì)象數(shù)據(jù)都變成響應(yīng)式的术辐。響應(yīng)式數(shù)據(jù)過于復(fù)雜,就會(huì)有相當(dāng)大的性能負(fù)擔(dān)施无。
  • Vue.js 3.0 使用了 Proxy API 做數(shù)據(jù)劫持辉词,由于它劫持的是整個(gè)對(duì)象,那么自然對(duì)于對(duì)象的屬性的增加和刪除都能檢測(cè)到猾骡。
  • Proxy API 并不能監(jiān)聽到內(nèi)部深層次的對(duì)象變化瑞躺,因此 Vue.js 3.0 的處理方式是在 getter 中去遞歸響應(yīng)式,這樣的好處是真正訪問到的內(nèi)部對(duì)象才會(huì)變成響應(yīng)式兴想,減少非必要的遞歸幢哨,提升性能。
  • 編譯優(yōu)化襟企,Vue2.x有許多非必要的diff 和遍歷嘱么,導(dǎo)致 vnode 的性能跟模版大小正相關(guān),跟動(dòng)態(tài)節(jié)點(diǎn)的數(shù)量無關(guān),當(dāng)一些組件的整個(gè)模版內(nèi)只有少量動(dòng)態(tài)節(jié)點(diǎn)時(shí)曼振,這些遍歷都是性能的浪費(fèi)几迄。
  • Vue.js 3.0通過編譯階段對(duì)靜態(tài)模板的分析,編譯生成了 Block tree冰评。Vue.js 3.0 將 vnode 更新性能由與模版整體大小相關(guān)提升為與動(dòng)態(tài)內(nèi)容的數(shù)量相關(guān)映胁。
  • Vue.js 3.0 在編譯階段還包含了對(duì) Slot 的編譯優(yōu)化、事件偵聽函數(shù)的緩存優(yōu)化甲雅,并且在運(yùn)行時(shí)重寫了 diff 算法解孙。

語法 API 優(yōu)化

01 | 組件渲染:vnode 到真實(shí) DOM 是如何轉(zhuǎn)變的?

  • 組件的時(shí)候抛人,它的內(nèi)部是如何工作的嗎弛姜?
  • 從編寫組件開始,到最終真實(shí)的 DOM 又是怎樣的一個(gè)轉(zhuǎn)變過程呢妖枚?

應(yīng)用程序初始化
在 Vue.js 內(nèi)部廷臼,一個(gè)組件想要真正的渲染生成 DOM,還需要經(jīng)歷“創(chuàng)建 vnode - 渲染 vnode - 生成 DOM” 這幾個(gè)步驟绝页。
一個(gè)組件可以通過“模板加對(duì)象描述”的方式創(chuàng)建荠商,組件創(chuàng)建好以后是如何被調(diào)用并初始化的呢?
在 Vue.js 3.0 中還導(dǎo)入了一個(gè) createApp续誉,其實(shí)這是個(gè)入口函數(shù)莱没,它是 Vue.js 對(duì)外暴露的一個(gè)函數(shù)。
createApp 主要做了兩件事情:創(chuàng)建 app 對(duì)象和重寫 app.mount 方法酷鸦。

  1. 創(chuàng)建 app 對(duì)象
    使用 ensureRenderer().createApp() 來創(chuàng)建 app 對(duì)象
const app = ensureRenderer().createApp(...args)

ensureRenderer() 用來創(chuàng)建一個(gè)渲染器對(duì)象,用 ensureRenderer() 來延時(shí)創(chuàng)建渲染器饰躲,這樣做的好處是當(dāng)用戶只依賴響應(yīng)式包的時(shí)候,就不會(huì)創(chuàng)建渲染器臼隔,因此可以通過 tree-shaking 的方式移除核心渲染邏輯相關(guān)的代碼属铁。

  1. 重寫 app.mount 方法
    為什么要重寫這個(gè)方法,而不把相關(guān)邏輯放在 app 對(duì)象的 mount 方法內(nèi)部來實(shí)現(xiàn)呢躬翁?
    這是因?yàn)?Vue.js 不僅僅是為 Web 平臺(tái)服務(wù),它的目標(biāo)是支持跨平臺(tái)渲染盯拱,而 createApp 函數(shù)內(nèi)部的 app.mount 方法是一個(gè)標(biāo)準(zhǔn)的可跨平臺(tái)的組件渲染流程:標(biāo)準(zhǔn)的跨平臺(tái)渲染流程是先創(chuàng)建 vnode盒发,再渲染 vnode。
mount(rootContainer) {
  // 創(chuàng)建根組件的 vnode
  const vnode = createVNode(rootComponent, rootProps)
  // 利用渲染器渲染 vnode
  render(vnode, rootContainer)
  app._container = rootContainer
  return vnode.component.proxy
}

核心渲染流程:創(chuàng)建 vnode 和渲染 vnode

  1. 創(chuàng)建 vnode
    vnode 有什么優(yōu)勢(shì)呢狡逢?為什么一定要設(shè)計(jì) vnode 這樣的數(shù)據(jù)結(jié)構(gòu)呢宁舰?
  • 首先是抽象,引入 vnode奢浑,可以把渲染過程抽象化蛮艰,從而使得組件的抽象能力也得到提升。
  • 其次是跨平臺(tái)雀彼,因?yàn)?patch vnode 的過程不同平臺(tái)可以有自己的實(shí)現(xiàn)壤蚜,基于 vnode 再做服務(wù)端渲染即寡、Weex 平臺(tái)、小程序平臺(tái)的渲染都變得容易了很多袜刷。

Vue.js 內(nèi)部是如何創(chuàng)建這些 vnode 的呢聪富?
通過 createVNode 函數(shù)創(chuàng)建了根組件的 vnode :對(duì) props 做標(biāo)準(zhǔn)化處理、對(duì) vnode 的類型信息編碼著蟹、創(chuàng)建 vnode 對(duì)象墩蔓,標(biāo)準(zhǔn)化子節(jié)點(diǎn) children 。

const vnode = createVNode(rootComponent, rootProps)
function createVNode(type, props = null
,children = null) {
  if (props) {
    // 處理 props 相關(guān)邏輯萧豆,標(biāo)準(zhǔn)化 class 和 style
  }

  // 對(duì) vnode 類型信息編碼
  const shapeFlag = isString(type)
    ? 1 /* ELEMENT */
    : isSuspense(type)
      ? 128 /* SUSPENSE */
      : isTeleport(type)
        ? 64 /* TELEPORT */
        : isObject(type)
          ? 4 /* STATEFUL_COMPONENT */
          : isFunction(type)
            ? 2 /* FUNCTIONAL_COMPONENT */
            : 0
  const vnode = {
    type,
    props,
    shapeFlag,
    // 一些其他屬性
  }
  // 標(biāo)準(zhǔn)化子節(jié)點(diǎn)奸披,把不同數(shù)據(jù)類型的 children 轉(zhuǎn)成數(shù)組或者文本類型
  normalizeChildren(vnode, children)
  return vnode
}
  1. 渲染 vnode

02 | 組件更新:完整的 DOM diff 流程是怎樣的?(上)

03 | 組件更新:完整的 DOM diff 流程是怎樣的涮雷?(下)

04 | Setup:組件渲染前的初始化過程是怎樣的阵面?

<template>
  <button @click="increment">
    Count is: {{ state.count }}, double is: {{ state.double }}
  </button>
</template>
<script>
import { reactive, computed } from 'vue'
export default {
  setup() {
    const state = reactive({
      count: 0,
      double: computed(() => state.count * 2)
    })
    function increment() {
      state.count++
    }
    return {
      state,
      increment
    }
  }
}

</script>

模板中引用到的變量 state 和 increment 包含在 setup 函數(shù)的返回對(duì)象中,那么它們是如何建立聯(lián)系的呢份殿?
在Vue.js 2.x 編寫組件的時(shí)候膜钓,會(huì)在 props、data卿嘲、methods颂斜、computed 等 options 中定義一些變量。在組件初始化階段拾枣,Vue.js 內(nèi)部會(huì)處理這些 options沃疮,即把定義的變量添加到了組件實(shí)例上。等模板編譯成 render 函數(shù)的時(shí)候梅肤,內(nèi)部通過 with(this){} 的語法去訪問在組件實(shí)例中的變量司蔬。

模板中引用到的變量 state 和 increment 包含在 setup 函數(shù)的返回對(duì)象中,那么它們是如何建立聯(lián)系的呢姨蝴?
到了 Vue.js 3.0俊啼,既支持組件定義 setup 函數(shù),而且在模板 render 的時(shí)候左医,又可以訪問到 setup 函數(shù)返回的值授帕,這是如何實(shí)現(xiàn)的?
Vue.js 2.x 使用 new Vue 來初始化一個(gè)組件的實(shí)例浮梢,到了 Vue.js 3.0跛十,我們直接通過創(chuàng)建對(duì)象去創(chuàng)建組件的實(shí)例。這兩種方式并無本質(zhì)的區(qū)別秕硝,都是引用一個(gè)對(duì)象芥映,在整個(gè)組件的生命周期中去維護(hù)組件的狀態(tài)數(shù)據(jù)和上下文環(huán)境。

創(chuàng)建和設(shè)置組件實(shí)例
組件的渲染流程:創(chuàng)建 vnode 、渲染 vnode 和生成 DOM奈偏。
渲染 vnode 的過程主要就是在掛載組件坞嘀,掛載組件的代碼主要做了三件事情:創(chuàng)建組件實(shí)例、設(shè)置組件實(shí)例和設(shè)置并運(yùn)行帶副作用的渲染函數(shù)霎苗。

const mountComponent = (initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized) => {
  // 創(chuàng)建組件實(shí)例
  const instance = (initialVNode.component = createComponentInstance(initialVNode, parentComponent, parentSuspense))
  // 設(shè)置組件實(shí)例
  setupComponent(instance)
  // 設(shè)置并運(yùn)行帶副作用的渲染函數(shù)
  setupRenderEffect(instance, initialVNode, container, anchor, parentSuspense, isSVG, optimized)
}

使用createComponentInstance 方法姆吭,創(chuàng)建組件實(shí)例

function createComponentInstance (vnode, parent, suspense) {
  // 繼承父組件實(shí)例上的 appContext,如果是根組件唁盏,則直接從根 vnode 中取内狸。
  const appContext = (parent ? parent.appContext : vnode.appContext) || emptyAppContext;
  const instance = {
    // 組件唯一 id
    uid: uid++,
    // 組件 vnode
    vnode,
    …
  }

  // 初始化渲染上下文
  instance.ctx = { _: instance }
  // 初始化根組件指針
  instance.root = parent ? parent.root : instance
  // 初始化派發(fā)事件方法
  instance.emit = emit.bind(null, instance)
  return instance
}

組件實(shí)例 instance 上定義了很多屬性.
Vue.js 2.x 使用 new Vue 來初始化一個(gè)組件的實(shí)例,到了 Vue.js 3.0厘擂,我們直接通過創(chuàng)建對(duì)象去創(chuàng)建組件的實(shí)例昆淡。這兩種方式并無本質(zhì)的區(qū)別,都是引用一個(gè)對(duì)象刽严,在整個(gè)組件的生命周期中去維護(hù)組件的狀態(tài)數(shù)據(jù)和上下文環(huán)境昂灵。
組件實(shí)例的設(shè)置流程就是對(duì)setup 函數(shù)的處理。

function setupComponent (instance, isSSR = false) {
  const { props, children, shapeFlag } = instance.vnode
  // 判斷是否是一個(gè)有狀態(tài)的組件
  const isStateful = shapeFlag & 4
  // 初始化 props
  initProps(instance, props, isStateful, isSSR)
  // 初始化 插槽
  initSlots(instance, children)
  // 設(shè)置有狀態(tài)的組件實(shí)例
  const setupResult = isStateful
    ? setupStatefulComponent(instance, isSSR)
    : undefined
  return setupResult
}

setupStatefulComponent 函數(shù),它主要做了三件事:創(chuàng)建渲染上下文代理舞萄、判斷處理 setup 函數(shù)和完成組件實(shí)例設(shè)置眨补。

05 | 響應(yīng)式:響應(yīng)式內(nèi)部的實(shí)現(xiàn)原理是怎樣的?(上)

06 | 響應(yīng)式:響應(yīng)式內(nèi)部的實(shí)現(xiàn)原理是怎樣的倒脓?(下)

07 | 計(jì)算屬性:計(jì)算屬性比普通函數(shù)好在哪里撑螺?

08 | 偵聽器:偵聽器的實(shí)現(xiàn)原理和使用場(chǎng)景是什么?(上)

09 | 偵聽器:偵聽器的實(shí)現(xiàn)原理和使用場(chǎng)景是什么崎弃?(下)

10 | 生命周期:各個(gè)生命周期的執(zhí)行時(shí)機(jī)和應(yīng)用場(chǎng)景是怎樣的甘晤?

11 | 依賴注入:子孫組件如何共享數(shù)據(jù)?

12 | 模板解析:構(gòu)造 AST 的完整流程是怎樣的饲做?(上)

13 | 模板解析:構(gòu)造 AST 的完整流程是怎樣的线婚?(下)

14 | AST 轉(zhuǎn)換:AST 節(jié)點(diǎn)內(nèi)部做了哪些轉(zhuǎn)換?(上)

15 | AST 轉(zhuǎn)換:AST 節(jié)點(diǎn)內(nèi)部做了哪些轉(zhuǎn)換盆均?(下)

16 | 生成代碼:AST 如何生成可運(yùn)行的代碼塞弊?(上)

17 | 生成代碼:AST 如何生成可運(yùn)行的代碼?(下)

18 | Props:Props 的初始化和更新流程是怎樣的泪姨?

19 | 插槽:如何實(shí)現(xiàn)內(nèi)容分發(fā)居砖?

20 | 指令:指令完整的生命周期是怎樣的?

21 | v-model:雙向綁定到底是怎么實(shí)現(xiàn)的驴娃?

22 | Teleport 組件:如何脫離當(dāng)前組件渲染子組件?

23 | KeepAlive 組件:如何讓組件在內(nèi)存中緩存和調(diào)度循集?

24 | Transition 組件:過渡動(dòng)畫的實(shí)現(xiàn)原理是怎樣的唇敞?(上)

25 | Transition 組件:過渡動(dòng)畫的實(shí)現(xiàn)原理是怎樣的?(下)

26 | Vue Router:如何實(shí)現(xiàn)一個(gè)前端路由?(上)

27 | Vue Router:如何實(shí)現(xiàn)一個(gè)前端路由疆柔?(下)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末咒精,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子旷档,更是在濱河造成了極大的恐慌模叙,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,104評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鞋屈,死亡現(xiàn)場(chǎng)離奇詭異范咨,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)厂庇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門渠啊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人权旷,你說我怎么就攤上這事替蛉。” “怎么了拄氯?”我有些...
    開封第一講書人閱讀 168,697評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵躲查,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我译柏,道長(zhǎng)镣煮,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,836評(píng)論 1 298
  • 正文 為了忘掉前任艇纺,我火速辦了婚禮怎静,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘黔衡。我一直安慰自己蚓聘,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評(píng)論 6 397
  • 文/花漫 我一把揭開白布盟劫。 她就那樣靜靜地躺著夜牡,像睡著了一般。 火紅的嫁衣襯著肌膚如雪侣签。 梳的紋絲不亂的頭發(fā)上塘装,一...
    開封第一講書人閱讀 52,441評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音影所,去河邊找鬼蹦肴。 笑死,一個(gè)胖子當(dāng)著我的面吹牛猴娩,可吹牛的內(nèi)容都是我干的阴幌。 我是一名探鬼主播勺阐,決...
    沈念sama閱讀 40,992評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼矛双!你這毒婦竟也來了渊抽?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,899評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤议忽,失蹤者是張志新(化名)和其女友劉穎懒闷,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體栈幸,經(jīng)...
    沈念sama閱讀 46,457評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡愤估,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評(píng)論 3 341
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了侦镇。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片灵疮。...
    茶點(diǎn)故事閱讀 40,664評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖壳繁,靈堂內(nèi)的尸體忽然破棺而出震捣,到底是詐尸還是另有隱情,我是刑警寧澤闹炉,帶...
    沈念sama閱讀 36,346評(píng)論 5 350
  • 正文 年R本政府宣布蒿赢,位于F島的核電站,受9級(jí)特大地震影響渣触,放射性物質(zhì)發(fā)生泄漏羡棵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評(píng)論 3 334
  • 文/蒙蒙 一嗅钻、第九天 我趴在偏房一處隱蔽的房頂上張望皂冰。 院中可真熱鬧,春花似錦养篓、人聲如沸秃流。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽舶胀。三九已至,卻和暖如春碧注,著一層夾襖步出監(jiān)牢的瞬間嚣伐,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工萍丐, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留轩端,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,081評(píng)論 3 377
  • 正文 我出身青樓逝变,卻偏偏與公主長(zhǎng)得像基茵,于是被迫代替她去往敵國(guó)和親刻撒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評(píng)論 2 359

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