Vue (讀音 /vju?/,類似于 view) 是一套用于構(gòu)建用戶界面的漸進(jìn)式框架。
Vue 的核心庫只關(guān)注視圖層,不僅易于上手,還便于與第三方庫或既有項(xiàng)目整合。另一方面,當(dāng)與現(xiàn)代化的工具鏈以及各種支持類庫結(jié)合使用時(shí),Vue 也完全能夠?yàn)閺?fù)雜的單頁應(yīng)用提供驅(qū)動(dòng)。
創(chuàng)建Demo.html
<!-- id標(biāo)識(shí)vue作用的范圍 -->
<div id="app">
<!-- {{}} 插值表達(dá)式,綁定vue中的data數(shù)據(jù) -->
{{ message }}
</div>
<!--導(dǎo)入vue.js庫-->
<script src="vue.min.js"></script>
<script>
// 創(chuàng)建一個(gè)vue對(duì)象
new Vue({
el: '#app',//綁定vue作用的范圍
data: {//定義頁面中顯示的模型數(shù)據(jù)
message: 'Hello Vue!'
}
})
</script>
這就是聲明式渲染:Vue.js 的核心是一個(gè)允許采用簡(jiǎn)潔的模板語法來聲明式地將數(shù)據(jù)渲染進(jìn) DOM 的系統(tǒng)
這里的核心思想就是沒有繁瑣的DOM操作,例如jQuery中,我們需要先找到div節(jié)點(diǎn),獲取到DOM對(duì)象,然后進(jìn)行一系列的節(jié)點(diǎn)操作
在vs code中創(chuàng)建代碼片段:
文件 => 首選項(xiàng) => 用戶代碼片段 => 新建全局代碼片段/或文件夾代碼片段:vue-html.code-snippets
注意:制作代碼片段的時(shí)候,字符串中如果包含文件中復(fù)制過來的“Tab”鍵的空格,要換成“空格鍵”的空格
{
"vue htm": {
"scope": "html",
"prefix": "vuehtml",
"body": [
"<!DOCTYPE html>",
"<html lang=\"en\">",
"",
"<head>",
" <meta charset=\"UTF-8\">",
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">",
" <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">",
" <title>Document</title>",
"</head>",
"",
"<body>",
" <div id=\"app\">",
"",
" </div>",
" <script src=\"vue.min.js\"></script>",
" <script>",
" new Vue({",
" el: '#app',",
" data: {",
" $1",
" }",
" })",
" </script>",
"</body>",
"",
"</html>",
],
"description": "my vue template in html"
}
}
分享目標(biāo):
了解 Vue.js 的組件化機(jī)制了解 Vue.js 的響應(yīng)式系統(tǒng)原理了解 Vue.js 中的 Virtual DOM 及 Diff 原理
Vue 是一套用于構(gòu)建用戶界面的漸進(jìn)式MVVM框架。那怎么理解漸進(jìn)式呢?漸進(jìn)式含義:強(qiáng)制主張最少。
Vue.js包含了聲明式渲染、組件化系統(tǒng)、客戶端路由、大規(guī)模狀態(tài)管理、構(gòu)建工具、數(shù)據(jù)持久化、跨平臺(tái)支持等,但在實(shí)際開發(fā)中,并沒有強(qiáng)制要求開發(fā)者之后某一特定功能,而是根據(jù)需求逐漸擴(kuò)展。 Vue.js的核心庫只關(guān)心視圖渲染,且由于漸進(jìn)式的特性,Vue.js便于與第三方庫或既有項(xiàng)目整合。
組件機(jī)制
定義:
組件就是對(duì)一個(gè)功能和樣式進(jìn)行獨(dú)立的封裝,讓HTML元素得到擴(kuò)展,從而使得代碼得到復(fù)用,使得開發(fā)靈活,更加高效。
與HTML元素一樣,Vue.js的組件擁有外部傳入的屬性prop和事件,除此之外,組件還擁有自己的狀態(tài)data和通過數(shù)據(jù)和狀態(tài)計(jì)算出來的計(jì)算屬性computed,各個(gè)維度組合起來決定組件最終呈現(xiàn)的樣子與交互的邏輯。
數(shù)據(jù)傳遞
每一個(gè)組件之間的作用域是孤立的,這個(gè)意味著組件之間的數(shù)據(jù)不應(yīng)該出現(xiàn)引用關(guān)系,即使出現(xiàn)了引用關(guān)系,也不允許組件操作組件內(nèi)部以外的其他數(shù)據(jù)。Vue中,允許向組件內(nèi)部傳遞prop數(shù)據(jù),組件內(nèi)部需要顯性地聲明該prop字段,如下聲明一個(gè)child組件:
<!-- child.vue -->
<template>
<div>{{msg}}</div>
</template>
<script>
export default {
props: {
msg: {
type: String,
default: 'hello world' // 當(dāng)default為引用類型時(shí),需要使用 function 形式返回
}
}
}
</script>
父組件向該組件傳遞數(shù)據(jù):
<!-- parent.vue -->
<template>
<child :msg="parentMsg"></child>
</template>
<script>
import child from './child';
export default {
components: {
child
},
data () {
return {
parentMsg: 'some words'
}
}
}
</script>
事件傳遞
Vue內(nèi)部實(shí)現(xiàn)了一個(gè)事件總線系統(tǒng),即EventBus。在Vue中可以使用 EventBus 來作為溝通橋梁的概念,每一個(gè)Vue的組件實(shí)例都繼承了 EventBus,都可以接受事件on和發(fā)送事件emit。
如上面一個(gè)例子,child.vue組件想修改 parent.vue組件的 parentMsg 數(shù)據(jù),怎么辦呢?為了保證數(shù)據(jù)流的可追溯性,直接修改組件內(nèi)prop的msg 字段是不提倡的,且例子中為非引用類型 String,直接修改也修改不了,這個(gè)時(shí)候需要將修改 parentMsg的事件傳遞給child.vue,讓 child.vue 來觸發(fā)修改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事件,在子組件實(shí)例化的時(shí)候,子組件將updateMsg事件使用on函數(shù)注冊(cè)到組件內(nèi)部,需要觸發(fā)事件的時(shí)候,調(diào)用函數(shù)this.emit來觸發(fā)事件。
除了父子組件之間的事件傳遞,還可以使用一個(gè) Vue 實(shí)例為多層級(jí)的父子組件建立數(shù)據(jù)通信的橋梁,如:
const eventBus = new Vue();
// 父組件中使用$on監(jiān)聽事件
eventBus.$on('eventName', val => {
// ...do something
})
// 子組件使用$emit觸發(fā)事件
eventBus.$emit('eventName', 'this is a message.');
除了on和emit以外,事件總線系統(tǒng)還提供了另外兩個(gè)方法,once和off,所有事件如下:
Vue實(shí)現(xiàn)了一套遵循 Web Components 規(guī)范草案 的內(nèi)容分發(fā)系統(tǒng),即將元素作為承載分發(fā)內(nèi)容的出口。
插槽slot,也是組件的一塊HTML模板,這一塊模板顯示不顯示、以及怎樣顯示由父組件來決定。實(shí)際上,一個(gè)slot最核心的兩個(gè)問題在這里就點(diǎn)出來了,是顯示不顯示和怎樣顯示。
插槽又分默認(rèn)插槽、具名插槽。
默認(rèn)插槽
又名單個(gè)插槽、匿名插槽,與具名插槽相對(duì),這類插槽沒有具體名字,一個(gè)組件只能有一個(gè)該類插槽。
如:
<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>
如上,渲染時(shí)子組件的slot標(biāo)簽會(huì)被父組件傳入的div.tmpl替換。
具名插槽
匿名插槽沒有name屬性,所以叫匿名插槽。那么,插槽加了name屬性,就變成了具名插槽。具名插槽可以在一個(gè)組件中出現(xiàn)N次,出現(xiàn)在不同的位置,只需要使用不同的name屬性區(qū)分即可。
如:
<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標(biāo)簽會(huì)根據(jù)父容器給 child標(biāo)簽內(nèi)傳入的內(nèi)容的slot 屬性值,替換對(duì)應(yīng)的內(nèi)容。
其實(shí),默認(rèn)插槽也有name屬性值,為default,同樣指定 slot的 name值為 default,一樣可以顯示父組件中傳入的沒有指定slot的內(nèi)容。
作用域插槽
作用域插槽可以是默認(rèn)插槽,也可以是具名插槽,不一樣的地方是,作用域插槽可以為slot標(biāo)簽綁定數(shù)據(jù),讓其父組件可以獲取到子組件的數(shù)據(jù)。
如:
<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: 'xiehuangbao1123'
}
}
}
}
</script>
如上例子,子組件 child在渲染默認(rèn)插槽slot的時(shí)候,將數(shù)據(jù) user傳遞給了 slot 標(biāo)簽,在渲染過程中,父組件可以通過slot-scope屬性獲取到user數(shù)據(jù)并渲染視圖。
slot 實(shí)現(xiàn)原理:
當(dāng)子組件vm實(shí)例化時(shí),獲取到父組件傳入的 slot 標(biāo)簽的內(nèi)容,存放在vm.slot中,默認(rèn)插槽為vm.slot.default,具名插槽為vm.slot.xxx,xxx為插槽名,當(dāng)組件執(zhí)行渲染函數(shù)時(shí)候,遇到<slot>標(biāo)簽,使用slot中的內(nèi)容進(jìn)行替換,此時(shí)可以為插槽傳遞數(shù)據(jù),若存在數(shù)據(jù),則可曾該插槽為作用域插槽。
至此,父子組件的關(guān)系如下圖:
模板渲染
Vue.js 的核心是聲明式渲染,與命令式渲染不同,聲明式渲染只需要告訴程序,我們想要的什么效果,其他的事情讓程序自己去做。而命令式渲染,需要命令程序一步一步根據(jù)命令執(zhí)行渲染。如下例子區(qū)分:
var arr = [1, 2, 3, 4, 5];
// 命令式渲染,關(guān)心每一步、關(guān)心流程。用命令去實(shí)現(xiàn)
var newArr = [];
for (var i = 0; i < arr.length; i++) {
newArr.push(arr[i] * 2);
}
// 聲明式渲染,不用關(guān)心中間流程,只需要關(guān)心結(jié)果和實(shí)現(xiàn)的條件
var newArr1 = arr.map(function (item) {
return item * 2;
});
Vue.js實(shí)現(xiàn)了if、for、事件、數(shù)據(jù)綁定等指令,允許采用簡(jiǎn)潔的模板語法來聲明式地將數(shù)據(jù)渲染出視圖。
模板編譯
為什么要進(jìn)行模板編譯?實(shí)際上,我們組件中的 template語法是無法被瀏覽器解析的,因?yàn)樗皇钦_的 HTML 語法,而模板編譯,就是將組件的 template編譯成可執(zhí)行的 JavaScript代碼,即將 template 轉(zhuǎn)化為真正的渲染函數(shù)。
模板編譯分三個(gè)階段,parse、optimize、generate,最終生成render函數(shù)。
parse階段:
使用正在表達(dá)式將template進(jìn)行字符串解析,得到指令、class、style等數(shù)據(jù),生成抽象語法樹 AST。
optimize階段:
尋找 AST 中的靜態(tài)節(jié)點(diǎn)進(jìn)行標(biāo)記,為后面 VNode 的 patch 過程中對(duì)比做優(yōu)化。被標(biāo)記為static 的節(jié)點(diǎn)在后面的diff 算法中會(huì)被直接忽略,不做詳細(xì)的比較。
generate階段:
根據(jù) AST 結(jié)構(gòu)拼接生成render函數(shù)的字符串。
預(yù)編譯
對(duì)于 Vue 組件來說,模板編譯只會(huì)在組件實(shí)例化的時(shí)候編譯一次,生成渲染函數(shù)之后再也不會(huì)進(jìn)行編譯。因此,編譯對(duì)組件的 runtime 是一種性能損耗。而模板編譯的目的僅僅是將template轉(zhuǎn)化為render function,而這個(gè)過程,正好可以在項(xiàng)目構(gòu)建的過程中完成。
比如webpack的vue-loader依賴了vue-template-compiler模塊,在 webpack 構(gòu)建過程中,將template預(yù)編譯成 render 函數(shù),在 runtime 可直接跳過模板編譯過程。
回過頭看,runtime 需要是僅僅是 render 函數(shù),而我們有了預(yù)編譯之后,我們只需要保證構(gòu)建過程中生成 render 函數(shù)就可以。與 React 類似,在添加JSX的語法糖編譯器babel-plugin-transform-vue-jsx之后,我們可以在 Vue 組件中使用JSX語法直接書寫 render 函數(shù)。
<script>
export default {
data() {
return {
msg: 'Hello JSX.'
}
},
render() {
const msg = this.msg;
return <div>
{msg}
</div>;
}
}
</script>
如上面組件,使用 JSX 之后,可以在 JS 代碼中直接使用 html 標(biāo)簽,而且聲明了 render 函數(shù)以后,我們不再需要聲明 template。當(dāng)然,假如我們同時(shí)聲明了 template 標(biāo)簽和 render 函數(shù),構(gòu)建過程中,template 編譯的結(jié)果將覆蓋原有的 render 函數(shù),即 template 的優(yōu)先級(jí)高于直接書寫的 render 函數(shù)。
相對(duì)于 template 而言,JSX 具有更高的靈活性,面對(duì)與一些復(fù)雜的組件來說,JSX 有著天然的優(yōu)勢(shì),而 template 雖然顯得有些呆滯,但是代碼結(jié)構(gòu)上更符合視圖與邏輯分離的習(xí)慣,更簡(jiǎn)單、更直觀、更好維護(hù)。
需要注意的是,最后生成的 render 函數(shù)是被包裹在with語法中運(yùn)行的。
小結(jié)
Vue 組件通過 prop 進(jìn)行數(shù)據(jù)傳遞,并實(shí)現(xiàn)了數(shù)據(jù)總線系統(tǒng)EventBus,組件集成了EventBus進(jìn)行事件注冊(cè)監(jiān)聽、事件觸發(fā),使用slot進(jìn)行內(nèi)容分發(fā)。
除此以外,實(shí)現(xiàn)了一套聲明式模板系統(tǒng),在runtime或者預(yù)編譯是對(duì)模板進(jìn)行編譯,生成渲染函數(shù),供組件渲染視圖使用。
響應(yīng)式系統(tǒng)
Vue.js 是一款 MVVM 的JS框架,當(dāng)對(duì)數(shù)據(jù)模型data進(jìn)行修改時(shí),視圖會(huì)自動(dòng)得到更新,即框架幫我們完成了更新DOM的操作,而不需要我們手動(dòng)的操作DOM。可以這么理解,當(dāng)我們對(duì)數(shù)據(jù)進(jìn)行賦值的時(shí)候,Vue 告訴了所有依賴該數(shù)據(jù)模型的組件,你依賴的數(shù)據(jù)有更新,你需要進(jìn)行重渲染了,這個(gè)時(shí)候,組件就會(huì)重渲染,完成了視圖的更新。
數(shù)據(jù)模型 、計(jì)算屬性 和監(jiān)聽器
在組件中,可以為每個(gè)組件定義數(shù)據(jù)模型data、計(jì)算屬性computed、監(jiān)聽器watch。
數(shù)據(jù)模型:Vue 實(shí)例在創(chuàng)建過程中,對(duì)數(shù)據(jù)模型data的每一個(gè)屬性加入到響應(yīng)式系統(tǒng)中,當(dāng)數(shù)據(jù)被更改時(shí),視圖將得到響應(yīng),同步更新。data必須采用函數(shù)的方式 return,不使用 return 包裹的數(shù)據(jù)會(huì)在項(xiàng)目的全局可見,會(huì)造成變量污染;使用return包裹后數(shù)據(jù)中變量只在當(dāng)前組件中生效,不會(huì)影響其他組件。
計(jì)算屬性:computed基于組件響應(yīng)式依賴進(jìn)行計(jì)算得到結(jié)果并緩存起來。只在相關(guān)響應(yīng)式依賴發(fā)生改變時(shí)它們才會(huì)重新求值,也就是說,只有它依賴的響應(yīng)式數(shù)據(jù)(data、prop、computed本身)發(fā)生變化了才會(huì)重新計(jì)算。那什么時(shí)候應(yīng)該使用計(jì)算屬性呢?模板內(nèi)的表達(dá)式非常便利,但是設(shè)計(jì)它們的初衷是用于簡(jiǎn)單運(yùn)算的。在模板中放入太多的邏輯會(huì)讓模板過重且難以維護(hù)。對(duì)于任何復(fù)雜邏輯,你都應(yīng)當(dāng)使用計(jì)算屬性。
監(jiān)聽器:監(jiān)聽器watch作用如其名,它可以監(jiān)聽響應(yīng)式數(shù)據(jù)的變化,響應(yīng)式數(shù)據(jù)包括 data、prop、computed,當(dāng)響應(yīng)式數(shù)據(jù)發(fā)生變化時(shí),可以做出相應(yīng)的處理。當(dāng)需要在數(shù)據(jù)變化時(shí)執(zhí)行異步或開銷較大的操作時(shí),這個(gè)方式是最有用的。
響應(yīng)式原理
在 Vue 中,數(shù)據(jù)模型下的所有屬性,會(huì)被 Vue 使用Object.defineProperty(Vue3.0 使用 Proxy)進(jìn)行數(shù)據(jù)劫持代理。響應(yīng)式的核心機(jī)制是觀察者模式,數(shù)據(jù)是被觀察的一方,一旦發(fā)生變化,通知所有觀察者,這樣觀察者可以做出響應(yīng),比如當(dāng)觀察者為視圖時(shí),視圖可以做出視圖的更新。
Vue.js 的響應(yīng)式系統(tǒng)以來三個(gè)重要的概念,Observer、Dep、Watcher。
發(fā)布者-Observer
調(diào)度中心/訂閱器-Dep
觀察者-Watcher
Watcher 扮演的角色是訂閱者/觀察者,他的主要作用是為觀察屬性提供回調(diào)函數(shù)以及收集依賴,當(dāng)被觀察的值發(fā)生變化時(shí),會(huì)接收到來自調(diào)度中心Dep的通知,從而觸發(fā)回調(diào)函數(shù)。
而Watcher又分為三類,normal-watcher、 computed-watcher、 render-watcher。
這三種Watcher也有固定的執(zhí)行順序,分別是:computed-render -> normal-watcher -> render-watcher。這樣就能盡可能地保證,在更新組件視圖的時(shí)候,computed 屬性已經(jīng)是最新值了,如果 render-watcher 排在 computed-render 前面,就會(huì)導(dǎo)致頁面更新的時(shí)候 computed 值為舊數(shù)據(jù)。
小結(jié)
Observer 負(fù)責(zé)將數(shù)據(jù)進(jìn)行攔截,Watcher 負(fù)責(zé)訂閱,觀察數(shù)據(jù)變化, Dep 負(fù)責(zé)接收訂閱并通知 Observer 和接收發(fā)布并通知所有 Watcher。
Virtual DOM
在 Vue 中,template被編譯成瀏覽器可執(zhí)行的render function,然后配合響應(yīng)式系統(tǒng),將render function掛載在render-watcher中,當(dāng)有數(shù)據(jù)更改的時(shí)候,調(diào)度中心Dep通知該render-watcher執(zhí)行render function,完成視圖的渲染與更新。
整個(gè)流程看似通順,但是當(dāng)執(zhí)行render function時(shí),如果每次都全量刪除并重建 DOM,這對(duì)執(zhí)行性能來說,無疑是一種巨大的損耗,因?yàn)槲覀冎溃瑸g覽器的DOM很“昂貴”的,當(dāng)我們頻繁的更新 DOM,會(huì)產(chǎn)生一定的性能問題。
為了解決這個(gè)問題,Vue 使用 JS 對(duì)象將瀏覽器的 DOM 進(jìn)行的抽象,這個(gè)抽象被稱為 Virtual DOM。Virtual DOM 的每個(gè)節(jié)點(diǎn)被定義為VNode,當(dāng)每次執(zhí)行render function時(shí),Vue 對(duì)更新前后的VNode進(jìn)行Diff對(duì)比,找出盡可能少的我們需要更新的真實(shí) DOM 節(jié)點(diǎn),然后只更新需要更新的節(jié)點(diǎn),從而解決頻繁更新 DOM 產(chǎn)生的性能問題。
VNode
VNode,全稱virtual node,即虛擬節(jié)點(diǎn),對(duì)真實(shí) DOM 節(jié)點(diǎn)的虛擬描述,在 Vue 的每一個(gè)組件實(shí)例中,會(huì)掛載一個(gè)$createElement函數(shù),所有的VNode都是由這個(gè)函數(shù)創(chuàng)建的。
比如創(chuàng)建一個(gè) div:
// 聲明 render function
render: function (createElement) {
// 也可以使用 this.$createElement 創(chuàng)建 VNode
return createElement('div', 'hellow world');
}
// 以上 render 方法返回html片段 <div>hellow world</div>
render 函數(shù)執(zhí)行后,會(huì)根據(jù)VNode Tree將 VNode 映射生成真實(shí) DOM,從而完成視圖的渲染。
Diff
Diff 將新老 VNode 節(jié)點(diǎn)進(jìn)行比對(duì),然后將根據(jù)兩者的比較結(jié)果進(jìn)行最小單位地修改視圖,而不是將整個(gè)視圖根據(jù)新的 VNode 重繪,進(jìn)而達(dá)到提升性能的目的。
patch
Vue.js 內(nèi)部的 diff 被稱為patch。其 diff 算法的是通過同層的樹節(jié)點(diǎn)進(jìn)行比較,而非對(duì)樹進(jìn)行逐層搜索遍歷的方式,所以時(shí)間復(fù)雜度只有O(n),是一種相當(dāng)高效的算法。
首先定義新老節(jié)點(diǎn)是否相同判定函數(shù)sameVnode:滿足鍵值key和標(biāo)簽名tag必須一致等條件,返回true,否則false。
在進(jìn)行patch之前,新老 VNode 是否滿足條件sameVnode(oldVnode, newVnode),滿足條件之后,進(jìn)入流程patchVnode,否則被判定為不相同節(jié)點(diǎn),此時(shí)會(huì)移除老節(jié)點(diǎn),創(chuàng)建新節(jié)點(diǎn)。
patchVnode
patchVnode 的主要作用是判定如何對(duì)子節(jié)點(diǎn)進(jìn)行更新,
updateChildren
Diff 的核心,對(duì)比新老子節(jié)點(diǎn)數(shù)據(jù),判定如何對(duì)子節(jié)點(diǎn)進(jìn)行操作,在對(duì)比過程中,由于老的子節(jié)點(diǎn)存在對(duì)當(dāng)前真實(shí) DOM 的引用,新的子節(jié)點(diǎn)只是一個(gè) VNode 數(shù)組,所以在進(jìn)行遍歷的過程中,若發(fā)現(xiàn)需要更新真實(shí) DOM 的地方,則會(huì)直接在老的子節(jié)點(diǎn)上進(jìn)行真實(shí) DOM 的操作,等到遍歷結(jié)束,新老子節(jié)點(diǎn)則已同步結(jié)束。
updateChildren內(nèi)部定義了4個(gè)變量,分別是oldStartIdx、oldEndIdx、newStartIdx、newEndIdx,分別表示正在 Diff 對(duì)比的新老子節(jié)點(diǎn)的左右邊界點(diǎn)索引,在老子節(jié)點(diǎn)數(shù)組中,索引在oldStartIdx與oldEndIdx中間的節(jié)點(diǎn),表示老子節(jié)點(diǎn)中為被遍歷處理的節(jié)點(diǎn),所以小于oldStartIdx或大于oldEndIdx的表示未被遍歷處理的節(jié)點(diǎn)。同理,在新的子節(jié)點(diǎn)數(shù)組中,索引在newStartIdx與newEndIdx中間的節(jié)點(diǎn),表示老子節(jié)點(diǎn)中為被遍歷處理的節(jié)點(diǎn),所以小于newStartIdx或大于newEndIdx的表示未被遍歷處理的節(jié)點(diǎn)。
每一次遍歷,oldStartIdx和oldEndIdx與newStartIdx和newEndIdx之間的距離會(huì)向中間靠攏。當(dāng) oldStartIdx > oldEndIdx 或者 newStartIdx > newEndIdx 時(shí)結(jié)束循環(huán)。
在遍歷中,取出4索引對(duì)應(yīng)的 Vnode節(jié)點(diǎn):
diff 過程中,如果存在key,并且滿足sameVnode,會(huì)將該 DOM 節(jié)點(diǎn)進(jìn)行復(fù)用,否則則會(huì)創(chuàng)建一個(gè)新的 DOM 節(jié)點(diǎn)。
首先,oldStartVnode、oldEndVnode與newStartVnode、newEndVnode兩兩比較,一共有 2*2=4 種比較方法。
情況一:當(dāng)oldStartVnode與newStartVnode滿足 sameVnode,則oldStartVnode與newStartVnode進(jìn)行 patchVnode,并且oldStartIdx與newStartIdx有移動(dòng)。
情況二:與情況一類似,當(dāng)oldEndVnode與newEndVnode滿足 sameVnode,則oldEndVnode與newEndVnode進(jìn)行 patchVnode,并且oldEndIdx與newEndIdx左移動(dòng)。
情況三:當(dāng)oldStartVnode與newEndVnode滿足 sameVnode,則說明oldStartVnode已經(jīng)跑到了oldEndVnode后面去了,此時(shí)oldStartVnode與newEndVnode進(jìn)行 patchVnode 的同時(shí),還需要將oldStartVnode的真實(shí) DOM 節(jié)點(diǎn)移動(dòng)到oldEndVnode的后面,并且oldStartIdx右移,newEndIdx左移。
情況四:與情況三類似,當(dāng)oldEndVnode與newStartVnode滿足 sameVnode,則說明oldEndVnode已經(jīng)跑到了oldStartVnode前面去了,此時(shí)oldEndVnode與newStartVnode進(jìn)行 patchVnode 的同時(shí),還需要將oldEndVnode的真實(shí) DOM 節(jié)點(diǎn)移動(dòng)到oldStartVnode的前面,并且oldStartIdx右移,newEndIdx左移。
當(dāng)這四種情況都不滿足,則在oldStartIdx與oldEndIdx之間查找與newStartVnode滿足sameVnode的節(jié)點(diǎn),若存在,則將匹配的節(jié)點(diǎn)真實(shí) DOM 移動(dòng)到oldStartVnode的前面。
若不存在,說明newStartVnode為新節(jié)點(diǎn),創(chuàng)建新節(jié)點(diǎn)放在oldStartVnode前面即可。
當(dāng) oldStartIdx > oldEndIdx 或者 newStartIdx > newEndIdx,循環(huán)結(jié)束,這個(gè)時(shí)候我們需要處理那些未被遍歷到的 VNode。
當(dāng) oldStartIdx > oldEndIdx 時(shí),說明老的節(jié)點(diǎn)已經(jīng)遍歷完,而新的節(jié)點(diǎn)沒遍歷完,這個(gè)時(shí)候需要將新的節(jié)點(diǎn)創(chuàng)建之后放在oldEndVnode后面。
當(dāng) newStartIdx > newEndIdx 時(shí),說明新的節(jié)點(diǎn)已經(jīng)遍歷完,而老的節(jié)點(diǎn)沒遍歷完,這個(gè)時(shí)候要將沒遍歷的老的節(jié)點(diǎn)全都刪除。
此時(shí)已經(jīng)完成了子節(jié)點(diǎn)的匹配。下面是一個(gè)例子 patch 過程圖:
總結(jié)
借用官方的一幅圖:
Vue.js 實(shí)現(xiàn)了一套聲明式渲染引擎,并在runtime或者預(yù)編譯時(shí)將聲明式的模板編譯成渲染函數(shù),掛載在觀察者 Watcher 中,在渲染函數(shù)中(touch),響應(yīng)式系統(tǒng)使用響應(yīng)式數(shù)據(jù)的getter方法對(duì)觀察者進(jìn)行依賴收集(Collect as Dependency),使用響應(yīng)式數(shù)據(jù)的setter方法通知(notify)所有觀察者進(jìn)行更新,此時(shí)觀察者 Watcher 會(huì)觸發(fā)組件的渲染函數(shù)(Trigger re-render),組件執(zhí)行的 render 函數(shù),生成一個(gè)新的 Virtual DOM Tree,此時(shí) Vue 會(huì)對(duì)新老 Virtual DOM Tree 進(jìn)行 Diff,查找出需要操作的真實(shí) DOM 并對(duì)其進(jìn)行更新。
于使用 webpack 的配置,我們可以從以下幾個(gè)方面來進(jìn)行了解與認(rèn)識(shí):
簡(jiǎn)單配置
調(diào)整webpack配置的最簡(jiǎn)單方法是為 configureWebpack 選項(xiàng)提供對(duì)象vue.config.js:
module.exports = {
configureWebpack: {
plugins: [
new MyAwesomeWebpackPlugin()
]
}
}
修改加載程序的選項(xiàng)
添加一個(gè)新的加載器
檢測(cè)項(xiàng)目的webpack配置
輸出重定向到配置好的文件中便于檢查:
vue inspect > output.js
通過指定路徑檢查配置子集:
vue inspect module.rule.0
以命名規(guī)則或插件為目標(biāo):
vue inspect --rule vue
vue inspect --plugin html
列出所有命名的規(guī)則和插件:
vue inspect --rules
vue inspect --plugins
將解析的配置用作文件,使用以下路徑
<projectRoot>/node_modules/@vue/cli-service/webpack.config.js
文件可以動(dòng)態(tài)解析并導(dǎo)出 vue-cli-service命令中使用的完全相同的webpack配置,包括來自插件甚至自定義配置的配置。
鏈接:https://www.9xkd.com/
*請(qǐng)認(rèn)真填寫需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。