國(guó)內(nèi)網(wǎng)站建設(shè)費(fèi)用聯(lián)盟谷粉搜索谷歌搜索
Vue中key的作用
vue 中 key 值的作用可以分為兩種情況來(lái)考慮:
- 第一種情況是 v-if 中使用 key。由于 Vue 會(huì)盡可能高效地渲染元素,通常會(huì)復(fù)用已有元素而不是從頭開(kāi)始渲染。因此當(dāng)使用 v-if 來(lái)實(shí)現(xiàn)元素切換的時(shí)候,如果切換前后含有相同類(lèi)型的元素,那么這個(gè)元素就會(huì)被復(fù)用。如果是相同的 input 元素,那么切換前后用戶(hù)的輸入不會(huì)被清除掉,這樣是不符合需求的。因此可以通過(guò)使用 key 來(lái)唯一的標(biāo)識(shí)一個(gè)元素,這個(gè)情況下,使用 key 的元素不會(huì)被復(fù)用。這個(gè)時(shí)候 key 的作用是用來(lái)標(biāo)識(shí)一個(gè)獨(dú)立的元素。
- 第二種情況是 v-for 中使用 key。用 v-for 更新已渲染過(guò)的元素列表時(shí),它默認(rèn)使用“就地復(fù)用”的策略。如果數(shù)據(jù)項(xiàng)的順序發(fā)生了改變,Vue 不會(huì)移動(dòng) DOM 元素來(lái)匹配數(shù)據(jù)項(xiàng)的順序,而是簡(jiǎn)單復(fù)用此處的每個(gè)元素。因此通過(guò)為每個(gè)列表項(xiàng)提供一個(gè) key 值,來(lái)以便 Vue 跟蹤元素的身份,從而高效的實(shí)現(xiàn)復(fù)用。這個(gè)時(shí)候 key 的作用是為了高效的更新渲染虛擬 DOM。
key 是為 Vue 中 vnode 的唯一標(biāo)記,通過(guò)這個(gè) key,diff 操作可以更準(zhǔn)確、更快速
- 更準(zhǔn)確:因?yàn)閹?key 就不是就地復(fù)用了,在 sameNode 函數(shù)a.key === b.key對(duì)比中可以避免就地復(fù)用的情況。所以會(huì)更加準(zhǔn)確。
- 更快速:利用 key 的唯一性生成 map 對(duì)象來(lái)獲取對(duì)應(yīng)節(jié)點(diǎn),比遍歷方式更快
為什么vue組件中data必須是一個(gè)函數(shù)?
對(duì)象為引用類(lèi)型,當(dāng)復(fù)用組件時(shí),由于數(shù)據(jù)對(duì)象都指向同一個(gè)data對(duì)象,當(dāng)在一個(gè)組件中修改data時(shí),其他重用的組件中的data會(huì)同時(shí)被修改;而使用返回對(duì)象的函數(shù),由于每次返回的都是一個(gè)新對(duì)象(Object的實(shí)例),引用地址不同,則不會(huì)出現(xiàn)這個(gè)問(wèn)題。
Computed 和 Watch 的區(qū)別
對(duì)于Computed:
- 它支持緩存,只有依賴(lài)的數(shù)據(jù)發(fā)生了變化,才會(huì)重新計(jì)算
- 不支持異步,當(dāng)Computed中有異步操作時(shí),無(wú)法監(jiān)聽(tīng)數(shù)據(jù)的變化
- computed的值會(huì)默認(rèn)走緩存,計(jì)算屬性是基于它們的響應(yīng)式依賴(lài)進(jìn)行緩存的,也就是基于data聲明過(guò),或者父組件傳遞過(guò)來(lái)的props中的數(shù)據(jù)進(jìn)行計(jì)算的。
- 如果一個(gè)屬性是由其他屬性計(jì)算而來(lái)的,這個(gè)屬性依賴(lài)其他的屬性,一般會(huì)使用computed
- 如果computed屬性的屬性值是函數(shù),那么默認(rèn)使用get方法,函數(shù)的返回值就是屬性的屬性值;在computed中,屬性有一個(gè)get方法和一個(gè)set方法,當(dāng)數(shù)據(jù)發(fā)生變化時(shí),會(huì)調(diào)用set方法。
對(duì)于Watch:
- 它不支持緩存,數(shù)據(jù)變化時(shí),它就會(huì)觸發(fā)相應(yīng)的操作
- 支持異步監(jiān)聽(tīng)
- 監(jiān)聽(tīng)的函數(shù)接收兩個(gè)參數(shù),第一個(gè)參數(shù)是最新的值,第二個(gè)是變化之前的值
- 當(dāng)一個(gè)屬性發(fā)生變化時(shí),就需要執(zhí)行相應(yīng)的操作
- 監(jiān)聽(tīng)數(shù)據(jù)必須是data中聲明的或者父組件傳遞過(guò)來(lái)的props中的數(shù)據(jù),當(dāng)發(fā)生變化時(shí),會(huì)觸發(fā)其他操作,函數(shù)有兩個(gè)的參數(shù):
- immediate:組件加載立即觸發(fā)回調(diào)函數(shù)
- deep:深度監(jiān)聽(tīng),發(fā)現(xiàn)數(shù)據(jù)內(nèi)部的變化,在復(fù)雜數(shù)據(jù)類(lèi)型中使用,例如數(shù)組中的對(duì)象發(fā)生變化。需要注意的是,deep無(wú)法監(jiān)聽(tīng)到數(shù)組和對(duì)象內(nèi)部的變化。
當(dāng)想要執(zhí)行異步或者昂貴的操作以響應(yīng)不斷的變化時(shí),就需要使用watch。
總結(jié):
- computed 計(jì)算屬性 : 依賴(lài)其它屬性值,并且 computed 的值有緩存,只有它依賴(lài)的屬性值發(fā)生改變,下一次獲取 computed 的值時(shí)才會(huì)重新計(jì)算 computed 的值。
- watch 偵聽(tīng)器 : 更多的是觀(guān)察的作用,無(wú)緩存性,類(lèi)似于某些數(shù)據(jù)的監(jiān)聽(tīng)回調(diào),每當(dāng)監(jiān)聽(tīng)的數(shù)據(jù)變化時(shí)都會(huì)執(zhí)行回調(diào)進(jìn)行后續(xù)操作。
運(yùn)用場(chǎng)景:
- 當(dāng)需要進(jìn)行數(shù)值計(jì)算,并且依賴(lài)于其它數(shù)據(jù)時(shí),應(yīng)該使用 computed,因?yàn)榭梢岳?computed 的緩存特性,避免每次獲取值時(shí)都要重新計(jì)算。
- 當(dāng)需要在數(shù)據(jù)變化時(shí)執(zhí)行異步或開(kāi)銷(xiāo)較大的操作時(shí),應(yīng)該使用 watch,使用 watch 選項(xiàng)允許執(zhí)行異步操作 ( 訪(fǎng)問(wèn)一個(gè) API ),限制執(zhí)行該操作的頻率,并在得到最終結(jié)果前,設(shè)置中間狀態(tài)。這些都是計(jì)算屬性無(wú)法做到的。
v-show 與 v-if 有什么區(qū)別?
v-if 是真正的條件渲染,因?yàn)樗鼤?huì)確保在切換過(guò)程中條件塊內(nèi)的事件監(jiān)聽(tīng)器和子組件適當(dāng)?shù)乇讳N(xiāo)毀和重建;也是惰性的:如果在初始渲染時(shí)條件為假,則什么也不做——直到條件第一次變?yōu)檎鏁r(shí),才會(huì)開(kāi)始渲染條件塊。
v-show 就簡(jiǎn)單得多——不管初始條件是什么,元素總是會(huì)被渲染,并且只是簡(jiǎn)單地基于 CSS 的 “display” 屬性進(jìn)行切換。
所以,v-if 適用于在運(yùn)行時(shí)很少改變條件,不需要頻繁切換條件的場(chǎng)景;v-show 則適用于需要非常頻繁切換條件的場(chǎng)景。
Vue 的生命周期方法有哪些 一般在哪一步發(fā)請(qǐng)求
beforeCreate 在實(shí)例初始化之后,數(shù)據(jù)觀(guān)測(cè)(data observer) 和 event/watcher 事件配置之前被調(diào)用。在當(dāng)前階段 data、methods、computed 以及 watch 上的數(shù)據(jù)和方法都不能被訪(fǎng)問(wèn)
created 實(shí)例已經(jīng)創(chuàng)建完成之后被調(diào)用。在這一步,實(shí)例已完成以下的配置:數(shù)據(jù)觀(guān)測(cè)(data observer),屬性和方法的運(yùn)算, watch/event 事件回調(diào)。這里沒(méi)有el,如果非要想與Dom進(jìn)行交互,可以通過(guò)vm.el,如果非要想與 Dom 進(jìn)行交互,可以通過(guò) vm.el,如果非要想與Dom進(jìn)行交互,可以通過(guò)vm.nextTick 來(lái)訪(fǎng)問(wèn) Dom
beforeMount 在掛載開(kāi)始之前被調(diào)用:相關(guān)的 render 函數(shù)首次被調(diào)用。
mounted 在掛載完成后發(fā)生,在當(dāng)前階段,真實(shí)的 Dom 掛載完畢,數(shù)據(jù)完成雙向綁定,可以訪(fǎng)問(wèn)到 Dom 節(jié)點(diǎn)
beforeUpdate 數(shù)據(jù)更新時(shí)調(diào)用,發(fā)生在虛擬 DOM 重新渲染和打補(bǔ)丁(patch)之前??梢栽谶@個(gè)鉤子中進(jìn)一步地更改狀態(tài),這不會(huì)觸發(fā)附加的重渲染過(guò)程
updated 發(fā)生在更新完成之后,當(dāng)前階段組件 Dom 已完成更新。要注意的是避免在此期間更改數(shù)據(jù),因?yàn)檫@可能會(huì)導(dǎo)致無(wú)限循環(huán)的更新,該鉤子在服務(wù)器端渲染期間不被調(diào)用。
beforeDestroy 實(shí)例銷(xiāo)毀之前調(diào)用。在這一步,實(shí)例仍然完全可用。我們可以在這時(shí)進(jìn)行善后收尾工作,比如清除計(jì)時(shí)器。
destroyed Vue 實(shí)例銷(xiāo)毀后調(diào)用。調(diào)用后,Vue 實(shí)例指示的所有東西都會(huì)解綁定,所有的事件監(jiān)聽(tīng)器會(huì)被移除,所有的子實(shí)例也會(huì)被銷(xiāo)毀。 該鉤子在服務(wù)器端渲染期間不被調(diào)用。
activated keep-alive 專(zhuān)屬,組件被激活時(shí)調(diào)用
deactivated keep-alive 專(zhuān)屬,組件被銷(xiāo)毀時(shí)調(diào)用
異步請(qǐng)求在哪一步發(fā)起?
可以在鉤子函數(shù) created、beforeMount、mounted 中進(jìn)行異步請(qǐng)求,因?yàn)樵谶@三個(gè)鉤子函數(shù)中,data 已經(jīng)創(chuàng)建,可以將服務(wù)端端返回的數(shù)據(jù)進(jìn)行賦值。
如果異步請(qǐng)求不需要依賴(lài) Dom 推薦在 created 鉤子函數(shù)中調(diào)用異步請(qǐng)求,因?yàn)樵?created 鉤子函數(shù)中調(diào)用異步請(qǐng)求有以下優(yōu)點(diǎn):
- 能更快獲取到服務(wù)端數(shù)據(jù),減少頁(yè)面 loading 時(shí)間;
- ssr 不支持 beforeMount 、mounted 鉤子函數(shù),所以放在 created 中有助于一致性;
Vue.js的template編譯
簡(jiǎn)而言之,就是先轉(zhuǎn)化成AST樹(shù),再得到的render函數(shù)返回VNode(Vue的虛擬DOM節(jié)點(diǎn)),詳細(xì)步驟如下:
首先,通過(guò)compile編譯器把template編譯成AST語(yǔ)法樹(shù)(abstract syntax tree 即 源代碼的抽象語(yǔ)法結(jié)構(gòu)的樹(shù)狀表現(xiàn)形式),compile是createCompiler的返回值,createCompiler是用以創(chuàng)建編譯器的。另外compile還負(fù)責(zé)合并option。
然后,AST會(huì)經(jīng)過(guò)generate(將AST語(yǔ)法樹(shù)轉(zhuǎn)化成render funtion字符串的過(guò)程)得到render函數(shù),render的返回值是VNode,VNode是Vue的虛擬DOM節(jié)點(diǎn),里面有(標(biāo)簽名、子節(jié)點(diǎn)、文本等等)
參考 前端進(jìn)階面試題詳細(xì)解答
MVC 和 MVVM 區(qū)別
MVC
MVC 全名是 Model View Controller,是模型(model)-視圖(view)-控制器(controller)的縮寫(xiě),一種軟件設(shè)計(jì)典范
- Model(模型):是應(yīng)用程序中用于處理應(yīng)用程序數(shù)據(jù)邏輯的部分。通常模型對(duì)象負(fù)責(zé)在數(shù)據(jù)庫(kù)中存取數(shù)據(jù)
- View(視圖):是應(yīng)用程序中處理數(shù)據(jù)顯示的部分。通常視圖是依據(jù)模型數(shù)據(jù)創(chuàng)建的
- Controller(控制器):是應(yīng)用程序中處理用戶(hù)交互的部分。通??刂破髫?fù)責(zé)從視圖讀取數(shù)據(jù),控制用戶(hù)輸入,并向模型發(fā)送數(shù)據(jù)
MVC 的思想:一句話(huà)描述就是 Controller 負(fù)責(zé)將 Model 的數(shù)據(jù)用 View 顯示出來(lái),換句話(huà)說(shuō)就是在 Controller 里面把 Model 的數(shù)據(jù)賦值給 View。
MVVM
MVVM 新增了 VM 類(lèi)
- ViewModel 層:做了兩件事達(dá)到了數(shù)據(jù)的雙向綁定 一是將【模型】轉(zhuǎn)化成【視圖】,即將后端傳遞的數(shù)據(jù)轉(zhuǎn)化成所看到的頁(yè)面。實(shí)現(xiàn)的方式是:數(shù)據(jù)綁定。二是將【視圖】轉(zhuǎn)化成【模型】,即將所看到的頁(yè)面轉(zhuǎn)化成后端的數(shù)據(jù)。實(shí)現(xiàn)的方式是:DOM 事件監(jiān)聽(tīng)。
MVVM 與 MVC 最大的區(qū)別就是:它實(shí)現(xiàn)了 View 和 Model 的自動(dòng)同步,也就是當(dāng) Model 的屬性改變時(shí),我們不用再自己手動(dòng)操作 Dom 元素,來(lái)改變 View 的顯示,而是改變屬性后該屬性對(duì)應(yīng) View 層顯示會(huì)自動(dòng)改變(對(duì)應(yīng)Vue數(shù)據(jù)驅(qū)動(dòng)的思想)
整體看來(lái),MVVM 比 MVC 精簡(jiǎn)很多,不僅簡(jiǎn)化了業(yè)務(wù)與界面的依賴(lài),還解決了數(shù)據(jù)頻繁更新的問(wèn)題,不用再用選擇器操作 DOM 元素。因?yàn)樵?MVVM 中,View 不知道 Model 的存在,Model 和 ViewModel 也觀(guān)察不到 View,這種低耦合模式提高代碼的可重用性
注意:Vue 并沒(méi)有完全遵循 MVVM 的思想 這一點(diǎn)官網(wǎng)自己也有說(shuō)明
那么問(wèn)題來(lái)了 為什么官方要說(shuō) Vue 沒(méi)有完全遵循 MVVM 思想呢?
- 嚴(yán)格的 MVVM 要求 View 不能和 Model 直接通信,而 Vue 提供了$refs 這個(gè)屬性,讓 Model 可以直接操作 View,違反了這一規(guī)定,所以說(shuō) Vue 沒(méi)有完全遵循 MVVM。
v-if 和 v-show 的區(qū)別
v-if 在編譯過(guò)程中會(huì)被轉(zhuǎn)化成三元表達(dá)式,條件不滿(mǎn)足時(shí)不渲染此節(jié)點(diǎn)。
v-show 會(huì)被編譯成指令,條件不滿(mǎn)足時(shí)控制樣式將對(duì)應(yīng)節(jié)點(diǎn)隱藏 (display:none)
寫(xiě)過(guò)自定義指令嗎 原理是什么
指令本質(zhì)上是裝飾器,是 vue 對(duì) HTML 元素的擴(kuò)展,給 HTML 元素增加自定義功能。vue 編譯 DOM 時(shí),會(huì)找到指令對(duì)象,執(zhí)行指令的相關(guān)方法。
自定義指令有五個(gè)生命周期(也叫鉤子函數(shù)),分別是 bind、inserted、update、componentUpdated、unbind
1. bind:只調(diào)用一次,指令第一次綁定到元素時(shí)調(diào)用。在這里可以進(jìn)行一次性的初始化設(shè)置。2. inserted:被綁定元素插入父節(jié)點(diǎn)時(shí)調(diào)用 (僅保證父節(jié)點(diǎn)存在,但不一定已被插入文檔中)。3. update:被綁定于元素所在的模板更新時(shí)調(diào)用,而無(wú)論綁定值是否變化。通過(guò)比較更新前后的綁定值,可以忽略不必要的模板更新。4. componentUpdated:被綁定元素所在模板完成一次更新周期時(shí)調(diào)用。5. unbind:只調(diào)用一次,指令與元素解綁時(shí)調(diào)用。
原理
1.在生成 ast 語(yǔ)法樹(shù)時(shí),遇到指令會(huì)給當(dāng)前元素添加 directives 屬性
2.通過(guò) genDirectives 生成指令代碼
3.在 patch 前將指令的鉤子提取到 cbs 中,在 patch 過(guò)程中調(diào)用對(duì)應(yīng)的鉤子
4.當(dāng)執(zhí)行指令對(duì)應(yīng)鉤子函數(shù)時(shí),調(diào)用對(duì)應(yīng)指令定義的方法
vue初始化頁(yè)面閃動(dòng)問(wèn)題
使用vue開(kāi)發(fā)時(shí),在vue初始化之前,由于div是不歸vue管的,所以我們寫(xiě)的代碼在還沒(méi)有解析的情況下會(huì)容易出現(xiàn)花屏現(xiàn)象,看到類(lèi)似于{{message}}的字樣,雖然一般情況下這個(gè)時(shí)間很短暫,但是還是有必要讓解決這個(gè)問(wèn)題的。
首先:在css里加上以下代碼:
[v-cloak] { display: none;}
如果沒(méi)有徹底解決問(wèn)題,則在根元素加上style="display: none;" :style="{display: 'block'}"
如何從真實(shí)DOM到虛擬DOM
涉及到Vue中的模板編譯原理,主要過(guò)程:
- 將模板轉(zhuǎn)換成
ast
樹(shù),ast
用對(duì)象來(lái)描述真實(shí)的JS語(yǔ)法(將真實(shí)DOM轉(zhuǎn)換成虛擬DOM) - 優(yōu)化樹(shù)
- 將
ast
樹(shù)生成代碼
Vue 為什么要用 vm.$set() 解決對(duì)象新增屬性不能響應(yīng)的問(wèn)題 ?你能說(shuō)說(shuō)如下代碼的實(shí)現(xiàn)原理么?
1)Vue為什么要用vm.$set() 解決對(duì)象新增屬性不能響應(yīng)的問(wèn)題
- Vue使用了Object.defineProperty實(shí)現(xiàn)雙向數(shù)據(jù)綁定
- 在初始化實(shí)例時(shí)對(duì)屬性執(zhí)行 getter/setter 轉(zhuǎn)化
- 屬性必須在data對(duì)象上存在才能讓Vue將它轉(zhuǎn)換為響應(yīng)式的(這也就造成了Vue無(wú)法檢測(cè)到對(duì)象屬性的添加或刪除)
所以Vue提供了Vue.set (object, propertyName, value) / vm.$set (object, propertyName, value)
2)接下來(lái)我們看看框架本身是如何實(shí)現(xiàn)的呢?
Vue 源碼位置:vue/src/core/instance/index.js
export function set (target: Array<any> | Object, key: any, val: any): any {// target 為數(shù)組 if (Array.isArray(target) && isValidArrayIndex(key)) {// 修改數(shù)組的長(zhǎng)度, 避免索引>數(shù)組長(zhǎng)度導(dǎo)致splcie()執(zhí)行有誤target.length = Math.max(target.length, key)// 利用數(shù)組的splice變異方法觸發(fā)響應(yīng)式 target.splice(key, 1, val)return val}// key 已經(jīng)存在,直接修改屬性值 if (key in target && !(key in Object.prototype)) {target[key] = valreturn val}const ob = (target: any).__ob__// target 本身就不是響應(yīng)式數(shù)據(jù), 直接賦值if (!ob) {target[key] = valreturn val}// 對(duì)屬性進(jìn)行響應(yīng)式處理defineReactive(ob.value, key, val)ob.dep.notify()return val
}
我們閱讀以上源碼可知,vm.$set 的實(shí)現(xiàn)原理是:
- 如果目標(biāo)是數(shù)組,直接使用數(shù)組的 splice 方法觸發(fā)相應(yīng)式;
- 如果目標(biāo)是對(duì)象,會(huì)先判讀屬性是否存在、對(duì)象是否是響應(yīng)式,
- 最終如果要對(duì)屬性進(jìn)行響應(yīng)式處理,則是通過(guò)調(diào)用 defineReactive 方法進(jìn)行響應(yīng)式處理
defineReactive 方法就是 Vue 在初始化對(duì)象時(shí),給對(duì)象屬性采用 Object.defineProperty 動(dòng)態(tài)添加 getter 和 setter 的功能所調(diào)用的方法
生命周期鉤子是如何實(shí)現(xiàn)的
Vue 的生命周期鉤子核心實(shí)現(xiàn)是利用發(fā)布訂閱模式先把用戶(hù)傳入的的生命周期鉤子訂閱好(內(nèi)部采用數(shù)組的方式存儲(chǔ))然后在創(chuàng)建組件實(shí)例的過(guò)程中會(huì)一次執(zhí)行對(duì)應(yīng)的鉤子方法(發(fā)布)
相關(guān)代碼如下
export function callHook(vm, hook) {// 依次執(zhí)行生命周期對(duì)應(yīng)的方法const handlers = vm.$options[hook];if (handlers) {for (let i = 0; i < handlers.length; i++) {handlers[i].call(vm); //生命周期里面的this指向當(dāng)前實(shí)例}}
}// 調(diào)用的時(shí)候
Vue.prototype._init = function (options) {const vm = this;vm.$options = mergeOptions(vm.constructor.options, options);callHook(vm, "beforeCreate"); //初始化數(shù)據(jù)之前// 初始化狀態(tài)initState(vm);callHook(vm, "created"); //初始化數(shù)據(jù)之后if (vm.$options.el) {vm.$mount(vm.$options.el);}
};
Vue 組件間通信有哪幾種方式?
Vue 組件間通信是面試??嫉闹R(shí)點(diǎn)之一,這題有點(diǎn)類(lèi)似于開(kāi)放題,你回答出越多方法當(dāng)然越加分,表明你對(duì) Vue 掌握的越熟練。Vue 組件間通信只要指以下 3 類(lèi)通信:父子組件通信、隔代組件通信、兄弟組件通信,下面我們分別介紹每種通信方式且會(huì)說(shuō)明此種方法可適用于哪類(lèi)組件間通信。
(1)props / $emit
適用 父子組件通信 這種方法是 Vue 組件的基礎(chǔ),相信大部分同學(xué)耳聞能詳,所以此處就不舉例展開(kāi)介紹。
(2)ref 與 $parent / $children
適用 父子組件通信
ref
:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子組件上,引用就指向組件實(shí)例$parent / $children
:訪(fǎng)問(wèn)父 / 子實(shí)例
(3)EventBus ($emit / $on)
適用于 父子、隔代、兄弟組件通信 這種方法通過(guò)一個(gè)空的 Vue 實(shí)例作為中央事件總線(xiàn)(事件中心),用它來(lái)觸發(fā)事件和監(jiān)聽(tīng)事件,從而實(shí)現(xiàn)任何組件間的通信,包括父子、隔代、兄弟組件。
(4)$attrs/$listeners
適用于 隔代組件通信
$attrs
:包含了父作用域中不被 prop 所識(shí)別 (且獲取) 的特性綁定 ( class 和 style 除外 )。當(dāng)一個(gè)組件沒(méi)有聲明任何prop
時(shí),這里會(huì)包含所有父作用域的綁定 ( class 和 style 除外 ),并且可以通過(guò)v-bind="$attrs"
傳入內(nèi)部組件。通常配合inheritAttrs
選項(xiàng)一起使用。$listeners
:包含了父作用域中的 (不含 .native 修飾器的)v-on
事件監(jiān)聽(tīng)器。它可以通過(guò)v-on="$listeners"
傳入內(nèi)部組件
(5)provide / inject
適用于 隔代組件通信 祖先組件中通過(guò) provider
來(lái)提供變量,然后在子孫組件中通過(guò) inject
來(lái)注入變量。 provide / inject API
主要解決了跨級(jí)組件間的通信問(wèn)題,不過(guò)它的使用場(chǎng)景,主要是子組件獲取上級(jí)組件的狀態(tài),跨級(jí)組件間建立了一種主動(dòng)提供與依賴(lài)注入的關(guān)系。 (6)Vuex
適用于 父子、隔代、兄弟組件通信 Vuex 是一個(gè)專(zhuān)為 Vue.js 應(yīng)用程序開(kāi)發(fā)的狀態(tài)管理模式。每一個(gè) Vuex 應(yīng)用的核心就是 store(倉(cāng)庫(kù))?!皊tore” 基本上就是一個(gè)容器,它包含著你的應(yīng)用中大部分的狀態(tài) ( state )。
- Vuex 的狀態(tài)存儲(chǔ)是響應(yīng)式的。當(dāng) Vue 組件從 store 中讀取狀態(tài)的時(shí)候,若 store 中的狀態(tài)發(fā)生變化,那么相應(yīng)的組件也會(huì)相應(yīng)地得到高效更新。
- 改變 store 中的狀態(tài)的唯一途徑就是顯式地提交 (commit) mutation。這樣使得我們可以方便地跟蹤每一個(gè)狀態(tài)的變化。
Vue template 到 render 的過(guò)程
vue的模版編譯過(guò)程主要如下:template -> ast -> render函數(shù)
vue 在模版編譯版本的碼中會(huì)執(zhí)行 compileToFunctions 將template轉(zhuǎn)化為render函數(shù):
// 將模板編譯為render函數(shù)const { render, staticRenderFns } = compileToFunctions(template,options//省略}, this)
CompileToFunctions中的主要邏輯如下∶ (1)調(diào)用parse方法將template轉(zhuǎn)化為ast(抽象語(yǔ)法樹(shù))
constast = parse(template.trim(), options)
- parse的目標(biāo):把tamplate轉(zhuǎn)換為AST樹(shù),它是一種用 JavaScript對(duì)象的形式來(lái)描述整個(gè)模板。
- 解析過(guò)程:利用正則表達(dá)式順序解析模板,當(dāng)解析到開(kāi)始標(biāo)簽、閉合標(biāo)簽、文本的時(shí)候都會(huì)分別執(zhí)行對(duì)應(yīng)的 回調(diào)函數(shù),來(lái)達(dá)到構(gòu)造AST樹(shù)的目的。
AST元素節(jié)點(diǎn)總共三種類(lèi)型:type為1表示普通元素、2為表達(dá)式、3為純文本
(2)對(duì)靜態(tài)節(jié)點(diǎn)做優(yōu)化
optimize(ast,options)
這個(gè)過(guò)程主要分析出哪些是靜態(tài)節(jié)點(diǎn),給其打一個(gè)標(biāo)記,為后續(xù)更新渲染可以直接跳過(guò)靜態(tài)節(jié)點(diǎn)做優(yōu)化
深度遍歷AST,查看每個(gè)子樹(shù)的節(jié)點(diǎn)元素是否為靜態(tài)節(jié)點(diǎn)或者靜態(tài)節(jié)點(diǎn)根。如果為靜態(tài)節(jié)點(diǎn),他們生成的DOM永遠(yuǎn)不會(huì)改變,這對(duì)運(yùn)行時(shí)模板更新起到了極大的優(yōu)化作用。
(3)生成代碼
const code = generate(ast, options)
generate將ast抽象語(yǔ)法樹(shù)編譯成 render字符串并將靜態(tài)部分放到 staticRenderFns 中,最后通過(guò) new Function(`` render``)
生成render函數(shù)。
說(shuō)說(shuō)Vue的生命周期吧
什么時(shí)候被調(diào)用?
- beforeCreate :實(shí)例初始化之后,數(shù)據(jù)觀(guān)測(cè)之前調(diào)用
- created:實(shí)例創(chuàng)建萬(wàn)之后調(diào)用。實(shí)例完成:數(shù)據(jù)觀(guān)測(cè)、屬性和方法的運(yùn)算、
watch/event
事件回調(diào)。無(wú)$el
. - beforeMount:在掛載之前調(diào)用,相關(guān)
render
函數(shù)首次被調(diào)用 - mounted:了被新創(chuàng)建的
vm.$el
替換,并掛載到實(shí)例上去之后調(diào)用改鉤子。 - beforeUpdate:數(shù)據(jù)更新前調(diào)用,發(fā)生在虛擬DOM重新渲染和打補(bǔ)丁,在這之后會(huì)調(diào)用改鉤子。
- updated:由于數(shù)據(jù)更改導(dǎo)致的虛擬DOM重新渲染和打補(bǔ)丁,在這之后會(huì)調(diào)用改鉤子。
- beforeDestroy:實(shí)例銷(xiāo)毀前調(diào)用,實(shí)例仍然可用。
- destroyed:實(shí)例銷(xiāo)毀之后調(diào)用,調(diào)用后,Vue實(shí)例指示的所有東西都會(huì)解綁,所有事件監(jiān)聽(tīng)器和所有子實(shí)例都會(huì)被移除
每個(gè)生命周期內(nèi)部可以做什么?
- created:實(shí)例已經(jīng)創(chuàng)建完成,因?yàn)樗亲钤缬|發(fā)的,所以可以進(jìn)行一些數(shù)據(jù)、資源的請(qǐng)求。
- mounted:實(shí)例已經(jīng)掛載完成,可以進(jìn)行一些DOM操作。
- beforeUpdate:可以在這個(gè)鉤子中進(jìn)一步的更改狀態(tài),不會(huì)觸發(fā)重渲染。
- updated:可以執(zhí)行依賴(lài)于DOM的操作,但是要避免更改狀態(tài),可能會(huì)導(dǎo)致更新無(wú)線(xiàn)循環(huán)。
- destroyed:可以執(zhí)行一些優(yōu)化操作,清空計(jì)時(shí)器,解除綁定事件。
ajax放在哪個(gè)生命周期?:一般放在 mounted
中,保證邏輯統(tǒng)一性,因?yàn)樯芷谑峭綀?zhí)行的, ajax
是異步執(zhí)行的。單數(shù)服務(wù)端渲染 ssr
同一放在 created
中,因?yàn)榉?wù)端渲染不支持 mounted
方法。 什么時(shí)候使用beforeDestroy?:當(dāng)前頁(yè)面使用 $on
,需要解綁事件。清楚定時(shí)器。解除事件綁定, scroll mousemove
。
理解Vue運(yùn)行機(jī)制全局概覽
全局概覽
首先我們來(lái)看一下筆者畫(huà)的內(nèi)部流程圖。
大家第一次看到這個(gè)圖一定是一頭霧水的,沒(méi)有關(guān)系,我們來(lái)逐個(gè)講一下這些模塊的作用以及調(diào)用關(guān)系。相信講完之后大家對(duì)Vue.js
內(nèi)部運(yùn)行機(jī)制會(huì)有一個(gè)大概的認(rèn)識(shí)。
初始化及掛載
在
new Vue()
之后。 Vue 會(huì)調(diào)用_init
函數(shù)進(jìn)行初始化,也就是這里的init
過(guò)程,它會(huì)初始化生命周期、事件、 props、 methods、 data、 computed 與 watch 等。其中最重要的是通過(guò)Object.defineProperty
設(shè)置setter
與getter
函數(shù),用來(lái)實(shí)現(xiàn)「 響應(yīng)式 」以及「 依賴(lài)收集 」,后面會(huì)詳細(xì)講到,這里只要有一個(gè)印象即可。
初始化之后調(diào)用
$mount
會(huì)掛載組件,如果是運(yùn)行時(shí)編譯,即不存在 render function 但是存在 template 的情況,需要進(jìn)行「 編譯 」步驟。
編譯
compile編譯可以分成 parse
、optimize
與 generate
三個(gè)階段,最終需要得到 render function。
1. parse
parse
會(huì)用正則等方式解析 template 模板中的指令、class、style等數(shù)據(jù),形成AST。
2. optimize
optimize
的主要作用是標(biāo)記 static 靜態(tài)節(jié)點(diǎn),這是 Vue 在編譯過(guò)程中的一處優(yōu)化,后面當(dāng)update
更新界面時(shí),會(huì)有一個(gè)patch
的過(guò)程, diff 算法會(huì)直接跳過(guò)靜態(tài)節(jié)點(diǎn),從而減少了比較的過(guò)程,優(yōu)化了patch
的性能。
3. generate
generate
是將 AST 轉(zhuǎn)化成render function
字符串的過(guò)程,得到結(jié)果是render
的字符串以及 staticRenderFns 字符串。
- 在經(jīng)歷過(guò)
parse
、optimize
與generate
這三個(gè)階段以后,組件中就會(huì)存在渲染VNode
所需的render function
了。
響應(yīng)式
接下來(lái)也就是 Vue.js 響應(yīng)式核心部分。
這里的
getter
跟setter
已經(jīng)在之前介紹過(guò)了,在init
的時(shí)候通過(guò)Object.defineProperty
進(jìn)行了綁定,它使得當(dāng)被設(shè)置的對(duì)象被讀取的時(shí)候會(huì)執(zhí)行getter
函數(shù),而在當(dāng)被賦值的時(shí)候會(huì)執(zhí)行setter
函數(shù)。
- 當(dāng)
render function
被渲染的時(shí)候,因?yàn)闀?huì)讀取所需對(duì)象的值,所以會(huì)觸發(fā)getter
函數(shù)進(jìn)行「 依賴(lài)收集 」,「 依賴(lài)收集 」的目的是將觀(guān)察者Watcher
對(duì)象存放到當(dāng)前閉包中的訂閱者Dep
的subs
中。形成如下所示的這樣一個(gè)關(guān)系。
在修改對(duì)象的值的時(shí)候,會(huì)觸發(fā)對(duì)應(yīng)的
setter
,setter
通知之前「 依賴(lài)收集 」得到的 Dep 中的每一個(gè) Watcher,告訴它們自己的值改變了,需要重新渲染視圖。這時(shí)候這些 Watcher 就會(huì)開(kāi)始調(diào)用update
來(lái)更新視圖,當(dāng)然這中間還有一個(gè)patch
的過(guò)程以及使用隊(duì)列來(lái)異步更新的策略,這個(gè)我們后面再講。
Virtual DOM
我們知道,
render function
會(huì)被轉(zhuǎn)化成VNode
節(jié)點(diǎn)。Virtual DOM
其實(shí)就是一棵以 JavaScript 對(duì)象( VNode 節(jié)點(diǎn))作為基礎(chǔ)的樹(shù),用對(duì)象屬性來(lái)描述節(jié)點(diǎn),實(shí)際上它只是一層對(duì)真實(shí) DOM 的抽象。最終可以通過(guò)一系列操作使這棵樹(shù)映射到真實(shí)環(huán)境上。由于 Virtual DOM 是以 JavaScript 對(duì)象為基礎(chǔ)而不依賴(lài)真實(shí)平臺(tái)環(huán)境,所以使它具有了跨平臺(tái)的能力,比如說(shuō)瀏覽器平臺(tái)、Weex、Node 等。
比如說(shuō)下面這樣一個(gè)例子:
{tag: 'div', /*說(shuō)明這是一個(gè)div標(biāo)簽*/children: [ /*存放該標(biāo)簽的子節(jié)點(diǎn)*/{tag: 'a', /*說(shuō)明這是一個(gè)a標(biāo)簽*/text: 'click me' /*標(biāo)簽的內(nèi)容*/}]
}
渲染后可以得到
<div><a>click me</a>
</div>
這只是一個(gè)簡(jiǎn)單的例子,實(shí)際上的節(jié)點(diǎn)有更多的屬性來(lái)標(biāo)志節(jié)點(diǎn),比如 isStatic (代表是否為靜態(tài)節(jié)點(diǎn))、 isComment (代表是否為注釋節(jié)點(diǎn))等。
更新視圖
- 前面我們說(shuō)到,在修改一個(gè)對(duì)象值的時(shí)候,會(huì)通過(guò)
setter -> Watcher -> update
的流程來(lái)修改對(duì)應(yīng)的視圖,那么最終是如何更新視圖的呢? - 當(dāng)數(shù)據(jù)變化后,執(zhí)行 render function 就可以得到一個(gè)新的 VNode 節(jié)點(diǎn),我們?nèi)绻胍玫叫碌囊晥D,最簡(jiǎn)單粗暴的方法就是直接解析這個(gè)新的
VNode
節(jié)點(diǎn),然后用innerHTML
直接全部渲染到真實(shí)DOM
中。但是其實(shí)我們只對(duì)其中的一小塊內(nèi)容進(jìn)行了修改,這樣做似乎有些「 浪費(fèi) 」。 - 那么我們?yōu)槭裁床荒苤恍薷哪切父淖兞说牡胤健鼓?#xff1f;這個(gè)時(shí)候就要介紹我們的「
patch
」了。我們會(huì)將新的VNode
與舊的VNode
一起傳入patch
進(jìn)行比較,經(jīng)過(guò) diff 算法得出它們的「 差異 」。最后我們只需要將這些「 差異 」的對(duì)應(yīng) DOM 進(jìn)行修改即可。
再看全局
回過(guò)頭再來(lái)看看這張圖,是不是大腦中已經(jīng)有一個(gè)大概的脈絡(luò)了呢?
用VNode來(lái)描述一個(gè)DOM結(jié)構(gòu)
虛擬節(jié)點(diǎn)就是用一個(gè)對(duì)象來(lái)描述一個(gè)真實(shí)的DOM元素。首先將 template
(真實(shí)DOM)先轉(zhuǎn)成 ast
, ast
樹(shù)通過(guò) codegen
生成 render
函數(shù), render
函數(shù)里的 _c
方法將它轉(zhuǎn)為虛擬dom
了解nextTick嗎?
異步方法,異步渲染最后一步,與JS事件循環(huán)聯(lián)系緊密。主要使用了宏任務(wù)微任務(wù)(setTimeout
、promise
那些),定義了一個(gè)異步方法,多次調(diào)用nextTick
會(huì)將方法存入隊(duì)列,通過(guò)異步方法清空當(dāng)前隊(duì)列。
computed 的實(shí)現(xiàn)原理
computed 本質(zhì)是一個(gè)惰性求值的觀(guān)察者。
computed 內(nèi)部實(shí)現(xiàn)了一個(gè)惰性的 watcher,也就是 computed watcher,computed watcher 不會(huì)立刻求值,同時(shí)持有一個(gè) dep 實(shí)例。
其內(nèi)部通過(guò) this.dirty 屬性標(biāo)記計(jì)算屬性是否需要重新求值。
當(dāng) computed 的依賴(lài)狀態(tài)發(fā)生改變時(shí),就會(huì)通知這個(gè)惰性的 watcher,
computed watcher 通過(guò) this.dep.subs.length 判斷有沒(méi)有訂閱者,
有的話(huà),會(huì)重新計(jì)算,然后對(duì)比新舊值,如果變化了,會(huì)重新渲染。 (Vue 想確保不僅僅是計(jì)算屬性依賴(lài)的值發(fā)生變化,而是當(dāng)計(jì)算屬性最終計(jì)算的值發(fā)生變化時(shí)才會(huì)觸發(fā)渲染 watcher 重新渲染,本質(zhì)上是一種優(yōu)化。)
沒(méi)有的話(huà),僅僅把 this.dirty = true。 (當(dāng)計(jì)算屬性依賴(lài)于其他數(shù)據(jù)時(shí),屬性并不會(huì)立即重新計(jì)算,只有之后其他地方需要讀取屬性的時(shí)候,它才會(huì)真正計(jì)算,即具備 lazy(懶計(jì)算)特性。)