面試官:聊聊對Vue.js框架的理解

前言

今年OKR定了一條KR是每個季度進行一次前端相關技術的分享,還有十幾天就到2020年了,一直忙於業務開發,沒有時間準備和學習高端話題,迫於無奈,那就講講平時使用頻率較高,卻沒有真正認真的瞭解其內部原理的 Vue.js 吧。html

因爲本文爲一次前端技術分享的演講稿,因此盡力不貼 Vue.js 的源碼,由於貼代碼在實際分享中,比較枯燥,效果不佳,而更多的是以圖片和文字的形式進行表達。前端

分享目標:vue

  • 瞭解 Vue.js 的組件化機制
  • 瞭解 Vue.js 的響應式系統原理
  • 瞭解 Vue.js 中的 Virtual DOM 及 Diff 原理

分享keynote:Vue.js框架原理剖析.keynode

原文地址webpack

Vue.js概述

Vue 是一套用於構建用戶界面的漸進式MVVM框架。那怎麼理解漸進式呢?漸進式含義:強制主張最少。git

漸進式

Vue.js包含了聲明式渲染、組件化系統、客戶端路由、大規模狀態管理、構建工具、數據持久化、跨平臺支持等,但在實際開發中,並無強制要求開發者以後某一特定功能,而是根據需求逐漸擴展。github

Vue.js的核心庫只關心視圖渲染,且因爲漸進式的特性,Vue.js便於與第三方庫或既有項目整合。web

組件機制

定義:組件就是對一個功能和樣式進行獨立的封裝,讓HTML元素獲得擴展,從而使得代碼獲得複用,使得開發靈活,更加高效。算法

與HTML元素同樣,Vue.js的組件擁有外部傳入的屬性(prop)和事件,除此以外,組件還擁有本身的狀態(data)和經過數據和狀態計算出來的計算屬性(computed),各個維度組合起來決定組件最終呈現的樣子與交互的邏輯。npm

數據傳遞

每個組件之間的做用域是孤立的,這個意味着組件之間的數據不該該出現引用關係,即便出現了引用關係,也不容許組件操做組件內部之外的其餘數據。Vue中,容許向組件內部傳遞prop數據,組件內部須要顯性地聲明該prop字段,以下聲明一個child組件:

<!-- child.vue -->
<template>
    <div>{{msg}}</div>
</template>
<script>
export default {
    props: {
        msg: {
            type: String,
            default: 'hello world' // 當default爲引用類型時,須要使用 function 形式返回
        }
    }
}
</script>

父組件向該組件傳遞數據:

<!-- parent.vue -->
<template>
    <child :msg="parentMsg"></child>
</template>
<script>
import child from './child';
export default {
    components: {
        child
    },
    data () {
        return {
            parentMsg: 'some words'
        }
    }
}
</script>

事件傳遞

Vue內部實現了一個事件總線系統,即EventBus。在Vue中可使用 EventBus 來做爲溝通橋樑的概念,每個Vue的組件實例都繼承了 EventBus,均可以接受事件$on和發送事件$emit

如上面一個例子,child.vue 組件想修改 parent.vue 組件的 parentMsg 數據,怎麼辦呢?爲了保證數據流的可追溯性,直接修改組件內 prop 的 msg 字段是不提倡的,且例子中爲非引用類型 String,直接修改也修改不了,這個時候須要將修改 parentMsg 的事件傳遞給 child.vue,讓 child.vue 來觸發修改 parentMsg 的事件。如:

<!-- child.vue -->
<template>
    <div>{{msg}}</div>
</template>
<script>
export default {
    props: {
        msg: {
            type: String,
            default: 'hello world'
        }
    },
    methods: {
        changeMsg(newMsg) {
            this.$emit('updateMsg', newMsg);
        }
    }
}
</script>

父組件:

<!-- parent.vue -->
<template>
    <child :msg="parentMsg" @updateMsg="changeParentMsg"></child>
</template>
<script>
import child from './child';
export default {
    components: {
        child
    },
    data () {
        return {
            parentMsg: 'some words'
        }
    },
    methods: {
        changeParentMsg: function (newMsg) {
            this.parentMsg = newMsg
        }
    }
}
</script>

父組件 parent.vue 向子組件 child.vue 傳遞了 updateMsg 事件,在子組件實例化的時候,子組件將 updateMsg 事件使用$on函數註冊到組件內部,須要觸發事件的時候,調用函數this.$emit來觸發事件。

除了父子組件之間的事件傳遞,還可使用一個 Vue 實例爲多層級的父子組件創建數據通訊的橋樑,如:

const eventBus = new Vue();

// 父組件中使用$on監聽事件
eventBus.$on('eventName', val => {
    //  ...do something
})

// 子組件使用$emit觸發事件
eventBus.$emit('eventName', 'this is a message.');

除了$on$emit之外,事件總線系統還提供了另外兩個方法,$once$off,全部事件以下:

  • $on:監聽、註冊事件。
  • $emit:觸發事件。
  • $once:註冊事件,僅容許該事件觸發一次,觸發結束後當即移除事件。
  • $off:移除事件。

內容分發

Vue實現了一套遵循 Web Components 規範草案 的內容分發系統,即將<slot>元素做爲承載分發內容的出口。

插槽slot,也是組件的一塊HTML模板,這一塊模板顯示不顯示、以及怎樣顯示由父組件來決定。實際上,一個slot最核心的兩個問題在這裏就點出來了,是顯示不顯示和怎樣顯示。

插槽又分默認插槽、具名插槽。

默認插槽

又名單個插槽、匿名插槽,與具名插槽相對,這類插槽沒有具體名字,一個組件只能有一個該類插槽。

如:

<template>
<!-- 父組件 parent.vue -->
<div class="parent">
    <h1>父容器</h1>
    <child>
        <div class="tmpl">
            <span>菜單1</span>
        </div>
    </child>
</div>
</template>
<template>
<!-- 子組件 child.vue -->
<div class="child">
    <h1>子組件</h1>
    <slot></slot>
</div>
</template>

如上,渲染時子組件的slot標籤會被父組件傳入的div.tmpl替換。

具名插槽

匿名插槽沒有name屬性,因此叫匿名插槽。那麼,插槽加了name屬性,就變成了具名插槽。具名插槽能夠在一個組件中出現N次,出如今不一樣的位置,只須要使用不一樣的name屬性區分便可。

如:

<template>
<!-- 父組件 parent.vue -->
<div class="parent">
    <h1>父容器</h1>
    <child>
        <div class="tmpl" slot="up">
            <span>菜單up-1</span>
        </div>
        <div class="tmpl" slot="down">
            <span>菜單down-1</span>
        </div>
        <div class="tmpl">
            <span>菜單->1</span>
        </div>
    </child>
</div>
</template>
<template>
    <div class="child">
        <!-- 具名插槽 -->
        <slot name="up"></slot>
        <h3>這裏是子組件</h3>
        <!-- 具名插槽 -->
        <slot name="down"></slot>
        <!-- 匿名插槽 -->
        <slot></slot>
    </div>
</template>

如上,slot 標籤會根據父容器給 child 標籤內傳入的內容的 slot 屬性值,替換對應的內容。

其實,默認插槽也有 name 屬性值,爲default,一樣指定 slot 的 name 值爲 default,同樣能夠顯示父組件中傳入的沒有指定slot的內容。

做用域插槽

做用域插槽能夠是默認插槽,也能夠是具名插槽,不同的地方是,做用域插槽能夠爲 slot 標籤綁定數據,讓其父組件能夠獲取到子組件的數據。

如:

<template>
    <!-- parent.vue -->
    <div class="parent">
        <h1>這是父組件</h1>
        <current-user>
            <template slot="default" slot-scope="slotProps">
                {{ slotProps.user.name }}
            </template>
        </current-user>
    </div>
</template>
<template>
    <!-- child.vue -->
    <div class="child">
        <h1>這是子組件</h1>
        <slot :user="user"></slot>
    </div>
</template>
<script>
export default {
    data() {
        return {
            user: {
                name: '小趙'
            }
        }
    }
}
</script>

如上例子,子組件 child 在渲染默認插槽 slot 的時候,將數據 user 傳遞給了 slot 標籤,在渲染過程當中,父組件能夠經過slot-scope屬性獲取到 user 數據並渲染視圖。

slot 實現原理:當子組件vm實例化時,獲取到父組件傳入的 slot 標籤的內容,存放在vm.$slot中,默認插槽爲vm.$slot.default,具名插槽爲vm.$slot.xxx,xxx 爲 插槽名,當組件執行渲染函數時候,遇到<slot>標籤,使用$slot中的內容進行替換,此時能夠爲插槽傳遞數據,若存在數據,則可曾該插槽爲做用域插槽。

至此,父子組件的關係以下圖:

父子組件關係

模板渲染

Vue.js 的核心是聲明式渲染,與命令式渲染不一樣,聲明式渲染只須要告訴程序,咱們想要的什麼效果,其餘的事情讓程序本身去作。而命令式渲染,須要命令程序一步一步根據命令執行渲染。以下例子區分:

var arr = [1, 2, 3, 4, 5];

// 命令式渲染,關心每一步、關心流程。用命令去實現
var newArr = [];
for (var i = 0; i < arr.length; i++) {
    newArr.push(arr[i] * 2);
}

// 聲明式渲染,不用關心中間流程,只須要關心結果和實現的條件
var newArr1 = arr.map(function (item) {
    return item * 2;
});

Vue.js 實現了if、for、事件、數據綁定等指令,容許採用簡潔的模板語法來聲明式地將數據渲染出視圖。

模板編譯

爲何要進行模板編譯?實際上,咱們組件中的 template 語法是沒法被瀏覽器解析的,由於它不是正確的 HTML 語法,而模板編譯,就是將組件的 template 編譯成可執行的 JavaScript 代碼,即將 template 轉化爲真正的渲染函數。

模板編譯分三個階段,parseoptimizegenerate,最終生成render函數。

模板編譯

parse階段:使用正在表達式將template進行字符串解析,獲得指令、class、style等數據,生成抽象語法樹 AST。

optimize階段:尋找 AST 中的靜態節點進行標記,爲後面 VNode 的 patch 過程當中對比作優化。被標記爲 static 的節點在後面的 diff 算法中會被直接忽略,不作詳細的比較。

generate階段:根據 AST 結構拼接生成 render 函數的字符串。

預編譯

對於 Vue 組件來講,模板編譯只會在組件實例化的時候編譯一次,生成渲染函數以後在也不會進行編譯。所以,編譯對組件的 runtime 是一種性能損耗。而模板編譯的目的僅僅是將template轉化爲render function,而這個過程,正好能夠在項目構建的過程當中完成。

好比webpackvue-loader依賴了vue-template-compiler模塊,在 webpack 構建過程當中,將template預編譯成 render 函數,在 runtime 可直接跳過模板編譯過程。

回過頭看,runtime 須要是僅僅是 render 函數,而咱們有了預編譯以後,咱們只須要保證構建過程當中生成 render 函數就能夠。與 React 相似,在添加JSX的語法糖編譯器babel-plugin-transform-vue-jsx以後,咱們能夠在 Vue 組件中使用JSX語法直接書寫 render 函數。

<script>
export default {
    data() {
        return {
            msg: 'Hello JSX.'
        }
    },
    render() {
        const msg = this.msg;
        return <div>
            {msg}
        </div>;
    }
}
</script>

如上面組件,使用 JSX 以後,能夠在 JS 代碼中直接使用 html 標籤,並且聲明瞭 render 函數之後,咱們再也不須要聲明 template。固然,假如咱們同時聲明瞭 template 標籤和 render 函數,構建過程當中,template 編譯的結果將覆蓋原有的 render 函數,即 template 的優先級高於直接書寫的 render 函數。

相對於 template 而言,JSX 具備更高的靈活性,面對與一些複雜的組件來講,JSX 有着自然的優點,而 template 雖然顯得有些呆滯,可是代碼結構上更符合視圖與邏輯分離的習慣,更簡單、更直觀、更好維護。

須要注意的是,最後生成的 render 函數是被包裹在with語法中運行的。

小結

Vue 組件經過 prop 進行數據傳遞,並實現了數據總線系統EventBus,組件集成了EventBus進行事件註冊監聽、事件觸發,使用slot進行內容分發。

除此之外,實現了一套聲明式模板系統,在runtime或者預編譯是對模板進行編譯,生成渲染函數,供組件渲染視圖使用。

響應式系統

Vue.js 是一款 MVVM 的JS框架,當對數據模型data進行修改時,視圖會自動獲得更新,即框架幫咱們完成了更新DOM的操做,而不須要咱們手動的操做DOM。能夠這麼理解,當咱們對數據進行賦值的時候,Vue 告訴了全部依賴該數據模型的組件,你依賴的數據有更新,你須要進行重渲染了,這個時候,組件就會重渲染,完成了視圖的更新。

數據模型 && 計算屬性 && 監聽器

在組件中,能夠爲每一個組件定義數據模型data、計算屬性computed、監聽器watch

數據模型:Vue 實例在建立過程當中,對數據模型data的每個屬性加入到響應式系統中,當數據被更改時,視圖將獲得響應,同步更新。data必須採用函數的方式 return,不使用 return 包裹的數據會在項目的全局可見,會形成變量污染;使用return包裹後數據中變量只在當前組件中生效,不會影響其餘組件。

計算屬性:computed基於組件響應式依賴進行計算獲得結果並緩存起來。只在相關響應式依賴發生改變時它們纔會從新求值,也就是說,只有它依賴的響應式數據(data、prop、computed自己)發生變化了纔會從新計算。那何時應該使用計算屬性呢?模板內的表達式很是便利,可是設計它們的初衷是用於簡單運算的。在模板中放入太多的邏輯會讓模板太重且難以維護。對於任何複雜邏輯,你都應當使用計算屬性。

監聽器:監聽器watch做用如其名,它能夠監聽響應式數據的變化,響應式數據包括 data、prop、computed,當響應式數據發生變化時,能夠作出相應的處理。當須要在數據變化時執行異步或開銷較大的操做時,這個方式是最有用的。

響應式原理

在 Vue 中,數據模型下的全部屬性,會被 Vue 使用Object.defineProperty(Vue3.0 使用 Proxy)進行數據劫持代理。響應式的核心機制是觀察者模式,數據是被觀察的一方,一旦發生變化,通知全部觀察者,這樣觀察者能夠作出響應,好比當觀察者爲視圖時,視圖能夠作出視圖的更新。

Vue.js 的響應式系統以來三個重要的概念,ObserverDepWatcher

發佈者-Observer

Observe 扮演的角色是發佈者,他的主要做用是在組件vm初始化的時,調用defineReactive函數,使用Object.defineProperty方法對對象的每個子屬性進行數據劫持/監聽,即爲每一個屬性添加gettersetter,將對應的屬性值變成響應式。

在組件初始化時,調用initState函數,內部執行initStateinitPropsinitComputed方法,分別對datapropcomputed進行初始化,讓其變成響應式。

初始化props時,對全部props進行遍歷,調用defineReactive函數,將每一個 prop 屬性值變成響應式,而後將其掛載到_props中,而後經過代理,把vm.xxx代理到vm._props.xxx中。

同理,初始化data時,與prop相同,對全部data進行遍歷,調用defineReactive函數,將每一個 data 屬性值變成響應式,而後將其掛載到_data中,而後經過代理,把vm.xxx代理到vm._data.xxx中。

初始化computed,首先建立一個觀察者對象computed-watcher,而後遍歷computed的每個屬性,對每個屬性值調用defineComputed方法,使用Object.defineProperty將其變成響應式的同時,將其代理到組件實例上,便可經過vm.xxx訪問到xxx計算屬性。

調度中心/訂閱器-Dep

Dep 扮演的角色是調度中心/訂閱器,在調用defineReactive將屬性值變成響應式的過程當中,也爲每一個屬性值實例化了一個Dep,主要做用是對觀察者(Watcher)進行管理,收集觀察者和通知觀察者目標更新,即當屬性值數據發生改變時,會遍歷觀察者列表(dep.subs),通知全部的 watcher,讓訂閱者執行本身的update邏輯。

dep的任務是,在屬性的getter方法中,調用dep.depend()方法,將觀察者(即 Watcher,多是組件的render function,多是 computed,也多是屬性監聽 watch)保存在內部,完成其依賴收集。在屬性的setter方法中,調用dep.notify()方法,通知全部觀察者執行更新,完成派發更新。

觀察者-Watcher

Watcher 扮演的角色是訂閱者/觀察者,他的主要做用是爲觀察屬性提供回調函數以及收集依賴,當被觀察的值發生變化時,會接收到來自調度中心Dep的通知,從而觸發回調函數。

Watcher又分爲三類,normal-watchercomputed-watcherrender-watcher

  • normal-watcher:在組件鉤子函數watch中定義,即監聽的屬性改變了,都會觸發定義好的回調函數。
  • computed-watcher:在組件鉤子函數computed中定義的,每個computed屬性,最後都會生成一個對應的Watcher對象,可是這類Watcher有個特色:當計算屬性依賴於其餘數據時,屬性並不會當即從新計算,只有以後其餘地方須要讀取屬性的時候,它纔會真正計算,即具有lazy(懶計算)特性。
  • render-watcher:每個組件都會有一個render-watcher, 當data/computed中的屬性改變的時候,會調用該Watcher來更新組件的視圖。

這三種Watcher也有固定的執行順序,分別是:computed-render -> normal-watcher -> render-watcher。這樣就能儘量的保證,在更新組件視圖的時候,computed 屬性已是最新值了,若是 render-watcher 排在 computed-render 前面,就會致使頁面更新的時候 computed 值爲舊數據。

小結

響應式系統

Observer 負責將數據進行攔截,Watcher 負責訂閱,觀察數據變化, Dep 負責接收訂閱並通知 Observer 和接收發布並通知全部 Watcher。

Virtual DOM

在 Vue 中,template被編譯成瀏覽器可執行的render function,而後配合響應式系統,將render function掛載在render-watcher中,當有數據更改的時候,調度中心Dep通知該render-watcher執行render function,完成視圖的渲染與更新。

DOM更新

整個流程看似通順,可是當執行render function時,若是每次都全量刪除並重建 DOM,這對執行性能來講,無疑是一種巨大的損耗,由於咱們知道,瀏覽器的DOM很「昂貴」的,當咱們頻繁的更新 DOM,會產生必定的性能問題。

爲了解決這個問題,Vue 使用 JS 對象將瀏覽器的 DOM 進行的抽象,這個抽象被稱爲 Virtual DOM。Virtual DOM 的每一個節點被定義爲VNode,當每次執行render function時,Vue 對更新先後的VNode進行Diff對比,找出儘量少的咱們須要更新的真實 DOM 節點,而後只更新須要更新的節點,從而解決頻繁更新 DOM 產生的性能問題。

VNode

VNode,全稱virtual node,即虛擬節點,對真實 DOM 節點的虛擬描述,在 Vue 的每個組件實例中,會掛載一個$createElement函數,全部的VNode都是由這個函數建立的。

好比建立一個 div:

// 聲明 render function
render: function (createElement) {
    // 也可使用 this.$createElement 建立 VNode
    return createElement('div', 'hellow world');
}
// 以上 render 方法返回html片斷 <div>hellow world</div>

render 函數執行後,會根據VNode Tree將 VNode 映射生成真實 DOM,從而完成視圖的渲染。

Diff

Diff 將新老 VNode 節點進行比對,而後將根據二者的比較結果進行最小單位地修改視圖,而不是將整個視圖根據新的 VNode 重繪,進而達到提高性能的目的。

patch

Vue.js 內部的 diff 被稱爲patch。其 diff 算法的是經過同層的樹節點進行比較,而非對樹進行逐層搜索遍歷的方式,因此時間複雜度只有O(n),是一種至關高效的算法。

DIFF

首先定義新老節點是否相同斷定函數sameVnode:知足鍵值key和標籤名tag必須一致等條件,返回true,不然false

在進行patch以前,新老 VNode 是否知足條件sameVnode(oldVnode, newVnode),知足條件以後,進入流程patchVnode,不然被斷定爲不相同節點,此時會移除老節點,建立新節點。

patchVnode

patchVnode 的主要做用是斷定如何對子節點進行更新,

  1. 若是新舊VNode都是靜態的,同時它們的key相同(表明同一節點),而且新的 VNode 是 clone 或者是標記了 once(標記v-once屬性,只渲染一次),那麼只須要替換 DOM 以及 VNode 便可。
  2. 新老節點均有子節點,則對子節點進行 diff 操做,進行updateChildren,這個 updateChildren 也是 diff 的核心。
  3. 若是老節點沒有子節點而新節點存在子節點,先清空老節點 DOM 的文本內容,而後爲當前 DOM 節點加入子節點。
  4. 當新節點沒有子節點而老節點有子節點的時候,則移除該 DOM 節點的全部子節點。
  5. 當新老節點都無子節點的時候,只是文本的替換。

updateChildren

Diff 的核心,對比新老子節點數據,斷定如何對子節點進行操做,在對比過程當中,因爲老的子節點存在對當前真實 DOM 的引用,新的子節點只是一個 VNode 數組,因此在進行遍歷的過程當中,若發現須要更新真實 DOM 的地方,則會直接在老的子節點上進行真實 DOM 的操做,等到遍歷結束,新老子節點則已同步結束。

updateChildren內部定義了4個變量,分別是oldStartIdxoldEndIdxnewStartIdxnewEndIdx,分別表示正在 Diff 對比的新老子節點的左右邊界點索引,在老子節點數組中,索引在oldStartIdxoldEndIdx中間的節點,表示老子節點中爲被遍歷處理的節點,因此小於oldStartIdx或大於oldEndIdx的表示未被遍歷處理的節點。同理,在新的子節點數組中,索引在newStartIdxnewEndIdx中間的節點,表示老子節點中爲被遍歷處理的節點,因此小於newStartIdx或大於newEndIdx的表示未被遍歷處理的節點。

每一次遍歷,oldStartIdxoldEndIdxnewStartIdxnewEndIdx之間的距離會向中間靠攏。當 oldStartIdx > oldEndIdx 或者 newStartIdx > newEndIdx 時結束循環。

img

在遍歷中,取出4索引對應的 Vnode節點:

  • oldStartIdx:oldStartVnode
  • oldEndIdx:oldEndVnode
  • newStartIdx:newStartVnode
  • newEndIdx:newEndVnode

diff 過程當中,若是存在key,而且知足sameVnode,會將該 DOM 節點進行復用,不然則會建立一個新的 DOM 節點。

首先,oldStartVnodeoldEndVnodenewStartVnodenewEndVnode兩兩比較,一共有 2*2=4 種比較方法。

狀況一:當oldStartVnodenewStartVnode知足 sameVnode,則oldStartVnodenewStartVnode進行 patchVnode,而且oldStartIdxnewStartIdx右移動。

img

狀況二:與狀況一相似,當oldEndVnodenewEndVnode知足 sameVnode,則oldEndVnodenewEndVnode進行 patchVnode,而且oldEndIdxnewEndIdx左移動。

img

狀況三:當oldStartVnodenewEndVnode知足 sameVnode,則說明oldStartVnode已經跑到了oldEndVnode後面去了,此時oldStartVnodenewEndVnode進行 patchVnode 的同時,還須要將oldStartVnode的真實 DOM 節點移動到oldEndVnode的後面,而且oldStartIdx右移,newEndIdx左移。

img

狀況四:與狀況三相似,當oldEndVnodenewStartVnode知足 sameVnode,則說明oldEndVnode已經跑到了oldStartVnode前面去了,此時oldEndVnodenewStartVnode進行 patchVnode 的同時,還須要將oldEndVnode的真實 DOM 節點移動到oldStartVnode的前面,而且oldStartIdx右移,newEndIdx左移。

img

當這四種狀況都不知足,則在oldStartIdxoldEndIdx之間查找與newStartVnode知足sameVnode的節點,若存在,則將匹配的節點真實 DOM 移動到oldStartVnode的前面。

img

若不存在,說明newStartVnode爲新節點,建立新節點放在oldStartVnode前面便可。

img

當 oldStartIdx > oldEndIdx 或者 newStartIdx > newEndIdx,循環結束,這個時候咱們須要處理那些未被遍歷到的 VNode。

當 oldStartIdx > oldEndIdx 時,說明老的節點已經遍歷完,而新的節點沒遍歷完,這個時候須要將新的節點建立以後放在oldEndVnode後面。

img

當 newStartIdx > newEndIdx 時,說明新的節點已經遍歷完,而老的節點沒遍歷完,這個時候要將沒遍歷的老的節點全都刪除。

img

此時已經完成了子節點的匹配。下面是一個例子 patch 過程圖:

patchChildren.gif

總結

借用官方的一幅圖:

final

Vue.js 實現了一套聲明式渲染引擎,並在runtime或者預編譯時將聲明式的模板編譯成渲染函數,掛載在觀察者 Watcher 中,在渲染函數中(touch),響應式系統使用響應式數據的getter方法對觀察者進行依賴收集(Collect as Dependency),使用響應式數據的setter方法通知(notify)全部觀察者進行更新,此時觀察者 Watcher 會觸發組件的渲染函數(Trigger re-render),組件執行的 render 函數,生成一個新的 Virtual DOM Tree,此時 Vue 會對新老 Virtual DOM Tree 進行 Diff,查找出須要操做的真實 DOM 並對其進行更新。

原文地址

參考

相關文章
相關標籤/搜索