.什么是vue生命周期
Vue 實例從開始創(chuàng)建、初始化數(shù)據(jù)、編譯模板、掛載Dom→渲染、更新→渲染、銷毀等一系列過程,稱之為 Vue 的生命周期,共八個階段。
作用: 生命周期中有多個事件鉤子,在控制整個 Vue實例
的過程時更容易形成好的邏輯。
beforeCreate
: 完成實例初始化,this
指向被創(chuàng)建的實例,data,computed,watch,mothods
方法 和 數(shù)據(jù)都不可以訪問,數(shù)據(jù)觀測之前(data observer)
被調(diào)用。
created
: 實例創(chuàng)建完成,data,computed,watch,methods
可被訪問,未掛載 Dom
,可對 data
進行操作,操作 Dom
需放到 nextTick
中。
beforeMount
: 有了el
,找到對應(yīng)的 template
編譯成 render
函數(shù)
mounted
: 完成掛載 Dom
和 渲染,可對 Dom
進行獲取節(jié)點等操作,可發(fā)起后端請求拿到數(shù)據(jù)。
beforeUpdate
: 數(shù)據(jù)更新時調(diào)用,發(fā)生在虛擬 Dom
重新渲染 和 打補丁之前之調(diào)用。
updated
: 組件 Dom
已完成更新,可執(zhí)行依賴的 Dom
操作,不要操作數(shù)據(jù)會陷入死循環(huán)。
beforeDestroy
: 實例銷毀之前調(diào)用,可進行優(yōu)化操作,如銷毀定時器,解除綁定事件。
destroyed
: 組件已經(jīng)被銷毀,事件監(jiān)聽器和子實例都會被移除銷毀。
首次頁面加載會觸發(fā)四個鉤子函數(shù): beforeCreate, created, beforeMount, mounted
且 DMO
渲染在 mounted
中就已經(jīng)完成了。
可以使用 $on('hook:')
或 $once('hook:')
來簡化生命周期的注冊
.談?wù)?MVVM 模式
Model
: 代表數(shù)據(jù)模型,也可以在 Model 中定義 數(shù)據(jù)修改 和 操作 的業(yè)務(wù)邏輯。
View
: 代表 UI 組件,它負(fù)責(zé)將 數(shù)據(jù)模型 轉(zhuǎn)化成 UI 展現(xiàn)出來。
ViewModel
: 監(jiān)聽模型數(shù)據(jù)的改變和控制視圖行為、處理用戶交互,簡單理解就是一個同步 View 和 Model 的對象,連接 Model 和 View。
在 MVVM 架構(gòu)下,View 和 Model 之間并沒有直接的聯(lián)系,而是通過 ViewMode
進行交互,Model 和 ViewModel 之間的交互是雙向自動的, 因此 View 數(shù)據(jù)的變化會同步到 Model 中,而 Model 數(shù)據(jù)的變化也會立即反應(yīng)到 View 上。而開發(fā)者只需關(guān)注業(yè)務(wù)邏輯,不需要手動操作 DOM,不需要關(guān)注數(shù)據(jù)狀態(tài)的同步問題,復(fù)雜的數(shù)據(jù)狀態(tài)維護完全由 MVVM 來統(tǒng)一管理。
MVVM 和 MVC區(qū)別?
mvc
和 mvvm
其實區(qū)別并不大。都是一種設(shè)計思想。主要就是 mvc
中 Controller
演變成 mvvm
中的 viewModel
。mvvm
主要解決了mvc
中大量的 DOM
操作使頁面渲染性能降低,加載速度變慢,影響用戶體驗。和當(dāng) Model
頻繁發(fā)生變化,開發(fā)者需要主動更新到 View
。
說下 Vue 實現(xiàn)數(shù)據(jù)雙向綁定的原理
Vue 實現(xiàn)數(shù)據(jù)雙向綁定主要是:采用 數(shù)據(jù)劫持結(jié)合發(fā)布者-訂閱者模式
的方式,通過 Object.defineProperty()
來劫持各個屬性的 setter
,getter
,在數(shù)據(jù)變動時發(fā)布消息給訂閱者,觸發(fā)相應(yīng)監(jiān)聽回調(diào)。當(dāng)把一個普通 Javascript
對象傳給 Vue 實例來作為它的 data
選項時,Vue 將遍歷它的屬性,用 Object.defineProperty()
將它們轉(zhuǎn)為 getter/setter
。用戶看不到 getter/setter
,但是在內(nèi)部它們讓 Vue追蹤依賴,在屬性被訪問和修改時通知變化。
.請說一下 Vue 響應(yīng)式數(shù)據(jù)的原理是什么?
在 Vue 初始化數(shù)據(jù)時, 使用 Object.defineProperty
重新定義 data
中所有屬性,增加了數(shù)據(jù) 獲取(getter) / 設(shè)置(setter)
的攔截功能。在 獲取 / 設(shè)置
時可增加一些邏輯,這個邏輯交叫作 依賴收集
。當(dāng)頁面取到對應(yīng)屬性時會進行依賴收集, 如果屬性發(fā)生變化, 則會通知收集的依賴進行更新,而負(fù)責(zé)收集的就是 watcher
。
如負(fù)責(zé)渲染的 watcher
會在頁面渲染的時候?qū)?shù)據(jù)進行取值,并把當(dāng)前 watcher
先存起來對應(yīng)到數(shù)據(jù)上,當(dāng)更新數(shù)據(jù)的時候告訴對應(yīng)的 watcher
去更新, 從而實現(xiàn)了數(shù)據(jù)響應(yīng)式。
data 一般分為兩大類: 對象類型 和 數(shù)組:
對象:
在 Vue 初始化的時候,會調(diào)用 initData
方法初始化 data
,它會拿到當(dāng)前用戶傳入的數(shù)據(jù)。判斷如果已經(jīng)被觀測過則不在觀測,如果沒有觀測過則利用 new Observer
創(chuàng)建一個實例用來觀測數(shù)據(jù)。如果數(shù)據(jù)是對象類型非數(shù)組的話會調(diào)用 this.walk(value)
方法把數(shù)據(jù)進行遍歷,在內(nèi)部使用 definReactive
方法重新定義( definReactive
是比較核心的方法: 定義響應(yīng)式 ),而重新定義采用的就是 Object.defineProperty
。如當(dāng)前對象的值還是個對象,會自動調(diào)用遞歸觀測。當(dāng)用戶取值的時候會調(diào)用 get
方法并收集當(dāng)前的 wacther
。在 set
方法里,數(shù)據(jù)變化時會調(diào)用 notify
方法觸發(fā)數(shù)據(jù)對應(yīng)的依賴進行更新。
數(shù)組:
使用函數(shù)劫持的方式重寫了數(shù)組的方法,并進行了原型鏈重寫。使 data
中的數(shù)組指向了自己定義的數(shù)組原型方法。這樣的話,當(dāng)調(diào)用數(shù)組 API
時,可以通知依賴更新。如果數(shù)組中包含著引用類型,則會對數(shù)組中的引用類型進行再次監(jiān)控。
也就是當(dāng)創(chuàng)建了 Observer
觀測實例后,如果數(shù)據(jù)是數(shù)組的話,判斷是否支持自己原型鏈,如果不支持則調(diào)用 protoAugment
方法使目標(biāo)指向 arrayMethods
方法。arrayMethods
就是重寫的數(shù)組方法,包括 push
、pop
、shift
、unshift
、splice
、sort
和 reverse
共七個可以改變數(shù)組的方法,內(nèi)部采用函數(shù)劫持的方式。在數(shù)組調(diào)用重寫的方法之后,還是會調(diào)用原數(shù)組方法去更新數(shù)組。只不過重寫的方法會通知視圖更新。如果使用 push
、unshift
和 splice
等方法新增數(shù)據(jù),會調(diào)用 observeArray
方法對插入的數(shù)據(jù)再次進行觀測。
如果數(shù)組中有引用類型,則繼續(xù)調(diào)用 observeArray
方法循環(huán)遍歷每一項,繼續(xù)深度觀測。前提是每一項必須是對象類型, 否則 observe
方法會直接 return
。
.為何 Vue 采用異步渲染?
如不采用異步更新, 則每次更新數(shù)據(jù)都會對當(dāng)前組件進行重新渲染, 因此為了性能考慮 Vue 在本輪數(shù)據(jù)更新結(jié)束后,再去異步更新視圖。
當(dāng)數(shù)據(jù)變化之后, 會調(diào)用 notify
方法去通知 watcher
進行數(shù)據(jù)更新。而 watcher
會調(diào)用 update
方法進行更新( 這里就是發(fā)布訂閱模式 )。更新時并不是讓 wathcer
立即執(zhí)行,而是放在一個 queueWatcher
隊列里進行過濾,相同的 watcher
只存一個。最后在調(diào)用 nextTick
方法通過 flushSchedulerQueue
異步清空 watcher
隊列。
.nextTick 實現(xiàn)原理?
nextTick
方法主要是使用了 宏任務(wù) 和 微任務(wù) 定義了一個異步方法。多次調(diào)用 nextTick
會將方法存入隊列中,通過這個異步方法清空當(dāng)前隊列。所以 nextTick
方法就是異步方法。
默認(rèn)在內(nèi)部調(diào)用 nextTick
時會傳入 flushSchedulerQueue
方法, 存在一個數(shù)組里并讓它執(zhí)行。用戶有時也會調(diào)用 nextTick
,調(diào)用時把用戶傳過來的 cb
也放在數(shù)組里,都是同一個數(shù)組 callbacks
。多次調(diào)用 nextTick
只會執(zhí)行一次, 等到代碼都執(zhí)行完畢后,會調(diào)用 timerFunc
這個異步方法依次進行判斷所支持的類型:
如支持
Promise
則把timerFunc
包裹在了Promise
中并把flushCallbacks
放在了then
中, 相當(dāng)于異步執(zhí)行了flushCallBacks
。flushCallBacks
函數(shù)作用就是讓傳過來的方法依次執(zhí)行。如不是
IE
、支持Mutationobserve
并且是原生的Mutationobserve
。首先聲明一個變量并創(chuàng)建一個文本節(jié)點。接著創(chuàng)建Mutationobserve
實例并把flushCallBacks
傳入, 調(diào)用observe
方法去觀測每一個節(jié)點。如果節(jié)點變化會異步執(zhí)行flushCallBacks
方法。如果支持
setImmediate
, 則調(diào)用setImmediate
傳入flushCallBacks
異步執(zhí)行。以上都不支持就只能調(diào)用
setTimeout
傳入flushCallBacks
。
作用:$nextTick
是在下次 DOM
更新循環(huán)結(jié)束之后執(zhí)行延遲回調(diào),在修改數(shù)據(jù)之后使用 $nextTick
,則可以在回調(diào)中獲取更新后的 DOM
。
.請說一下 Vue 中 Computed 和 watch ?
默認(rèn) computed
和 watch
內(nèi)部都是用一個 watcher
實現(xiàn)的 。
computed
有緩存功能, 不會先執(zhí)行,只有當(dāng)依賴的屬性發(fā)生變化才會通知視圖跟新。
watcher
沒有緩存,默認(rèn)會先執(zhí)行,只要監(jiān)聽的屬性發(fā)生變化就會更新視圖。
computed
調(diào)用 initComputed
方法初始化計算屬性時,會獲取到用戶定義的方法,并創(chuàng)建一個 watcher
把用戶定義傳進去, 這個 watcher
有個標(biāo)識: lazy = true
,默認(rèn)不會執(zhí)行用戶定義的函數(shù)。還有個標(biāo)識 dirty = true
默認(rèn)去求值 。watcher
內(nèi)部調(diào)用 defineComputed
方法將計算屬性定義在實例上,其底層也是用的 Object.defineProperty
。并且傳入了 createComputedGetter
方法定義一個計算屬性。在用戶取值時,調(diào)用的是 createComputedGetter
返回函數(shù) computedGetter
。判斷當(dāng)前的 watcher.dirty
是否為 true
。如果為 true
則調(diào)用 watcher.evaluate
方法求值。在求值時是調(diào)用的 this.get()
方法。其實 this.get()
就是用戶傳入的方法,執(zhí)行時會把方法里的屬性依次取值。而在取值前調(diào)用了 pushTarget
方法將 watcher
放在了全局上,當(dāng)取值時會進行依賴收集,把當(dāng)前的計算屬性的 watcher
收集起來。等數(shù)據(jù)變化則通知 watcher
重新執(zhí)行,也就是進入到了 update
方法中。update
并沒有直接讓 watcher
執(zhí)行,而是將 dirty = true
。這樣的好處就是,如果 dirty = true
,就進行求值,否則就返回上次計算后的值,從而實現(xiàn)了緩存的機制。
watch
調(diào)用 initWatch
方法初始化 watch
的時候,內(nèi)部傳入用戶定義的方法調(diào)用了 createWatcher
方法。在 createWatcher
方法中比較核心的就是 $watch
方法,內(nèi)部調(diào)用了 new Watcher
并傳入了 expOrFn
和 回調(diào)函數(shù)。expOrFn
如果是個字符串的話, 會包裝成一個函數(shù)并返回這個字符串。這時 lazy = false
了, 則直接調(diào)用了 this.get()
方法取屬性的值。同 computed
在取值前也執(zhí)行 pushTarget
方法將 watcher
放在了全局上, 當(dāng)用戶取值時就收集了 watcher
。 因此當(dāng)屬性值發(fā)生改變時, watcher
就會更新。
如果監(jiān)聽的屬性值是個對象,則取對象里的值就不會更新了,因為默認(rèn)只能對屬性進行依賴收集,不能對屬性值是對象的進行依賴收集。想要不管屬性值是否是對象都能求值進行收集依賴,可設(shè)置 deep = true
。如設(shè)置了deep = true
,則會調(diào)用 traverse
方法進行遞歸遍歷。
.Vue 組件中 data 為什么必須是一個函數(shù)?
因為 js 本身的特性帶來的,同一個組件被復(fù)用多次,會創(chuàng)建多個實例。這些實例是同一個構(gòu)造函數(shù)。如果 data
是一個對象的話,那么所有組件都共享了同一個對象。為了保證組件中數(shù)據(jù)的獨立性要求每個組件必須通過 data
函數(shù)返回一個對象作為組件的狀態(tài)。
Vue 通過 extend
創(chuàng)建子類之后,會調(diào)用 mergeOptions
方法合并父類和子類的選項,選中就包括 data
。在循環(huán)完父類和子類之后調(diào)用 mergeField
函數(shù)的中的 strat
方法去合并 data
,如果 data
不是函數(shù)而是個對象,則會報錯提示 data
應(yīng)該是個函數(shù)。
.Vue 中事件綁定原理
Vue 中事件綁定分為兩種:
- 原生事件綁定: 采用的是
addEventListener
實現(xiàn) - 組件事件綁定: 采用的是
$on
方法實現(xiàn)
以 click
事件為例,普通 Dom
元素綁定事件是 @click
,編譯出來是 on
和 click
事件,組件綁定事件是 @click
組件自定義事件 和 @click.native
原生事件兩種,編譯出來分別是 on
和 click
事件, nativeOn
和 click
事件。組件的 nativeOn
等價于普通元素的 on
,而組件的 on
單獨處理。
渲染頁面時,普通 Dom
會調(diào)用 updateDOMListeners
方法,內(nèi)部先把 data.on
方法拿出來,然后調(diào)用 updateListeners
方法來添加一個監(jiān)聽事件,同時會傳入一個 add$1
方法。內(nèi)部調(diào)用 addEventListener
方法直接把事件綁定到元素上。
而組件會調(diào)用 updateComponentListeners
方法。內(nèi)部也是調(diào)用 updateListeners
方法但傳入的是 add
方法。這里的 add
方法與普通元素的 Dom
的 add$1
方法略有不同,采用的是自己定義的發(fā)布訂閱模式 $on
方法,解析的是 on
方法,組件內(nèi)部通過 $emit
方法觸發(fā)的。還有 click.native
方法是直接把事件綁在了最外層元素上,用的也是 updateListeners
方法傳入 add$1
方法。
.v-model 的實現(xiàn)原理是什么?
通俗講 v-model
可以看成是 value + input
的語法糖。
組件的 v-model
也確實是這樣 。在組件初始化的時候, 如果檢測到有 model
屬性,就會調(diào)用 transformModel
方法轉(zhuǎn)化 model
。如果沒有 prop
屬性和 event
屬性, 則默認(rèn)會給組件 prop
為 value
屬性, 給 event
為 input
事件 。把 prop
的屬性賦給了 data.attrs
并把值也給了它,即 data.attrs.value = '我們所賦的值'
。會給 on
綁定 input
事件,對應(yīng)的就是 callback
。
如果在組件內(nèi)自定義 model
的 prop
和 event
, 這樣的話組件初始化的時候, 接受 屬性
和 事件
時不再是 value
和 input
了, 而是我們自定義的 屬性
和 事件
。
如果是普通的標(biāo)簽, 則在運行時會自動判斷標(biāo)簽的類型, 生成不同的屬性 domProp
和 事件 on
。還增加了指令 directive
, 針對輸入框的輸入法加上了一些邏輯并做了校驗和處理。
. Vue中的 v-show 和 v-if 是做什么用的, 兩者有什么區(qū)別?
v-if
:會在 with
方法里進行判斷,如果條件為 true
則創(chuàng)建相應(yīng)的虛擬節(jié)點,否則就創(chuàng)建一個空的虛擬節(jié)點也就是不會渲染 DOM
。
v-show
: 會在 with
方法里創(chuàng)建了一個指令就 v-show
,在運行的時候處理指令,添加了 style: display = none / originalDisplay
。
v-if
才是“真正的”條件渲染, 因為它會確保在切換過程中條件塊內(nèi)的事件監(jiān)聽器和子組件適當(dāng)?shù)谋讳N毀和重建。
v-if
也是惰性的, 如果在初次渲染時條件為假, 則什么也不做,一直到條件第一次變?yōu)檎鏁r, 才會渲染條件塊。
相比之下, v-show
就簡單的多,不管初始條件是什么,元素總會被渲染, 并且只是簡單的基于 css
進行切換。
一般來說,v-if
有更高的切換開銷,v-show
有更高的初始渲染開銷。
因此,如需要頻繁的切換則使用 v-show
較好,如在運行時條件不大可能改變則使用 v-if
較好。
. v-if 和 v-for 為什么不能連用?
v-for
的優(yōu)先級會比 v-if
要高, 在 調(diào)用 with
方法編譯時會先進行循環(huán), 然后再去做 v-if
的條件判斷, 因此性能不高。
因此一般會把 v-if
提出來放在 v-for
外層, 或者想要連用把渲染數(shù)據(jù)放在計算屬性里進行過濾。
.Vue 中的 v-html 會導(dǎo)致哪些問題
v-html
其原理就是用 innerHtml
實現(xiàn)的的, 如果不能保證內(nèi)容是完全可以被依賴的, 則可能會導(dǎo)致 xxs 攻擊。
在運行的時候, 調(diào)用 updateDOMProps
方法或解析配置的屬性, 如果判斷屬性是 innerHTML
的話, 會清除所有的子元素。
.Vue 中父子組件的調(diào)用順序
組件的調(diào)用都是先父后子,渲染完成的過程順序都是先子后父
組件的銷毀操作是先父后子,銷毀完成的順序是先子后父
在頁面渲染的時候,先執(zhí)行父組件的 beforeCreate -> created -> befroreMount
,當(dāng)父組件實例化完成的時候會調(diào)用 rander
方法,判斷組件是不是有子組件,如果有子組件則繼續(xù)渲染子組件以此類推。當(dāng)子組件實例化完成時候,會把子組件的插入方法先存起來放到 instertedVNodeQueue
隊列里, 最后會調(diào)用 invokeIntertHook
方法把當(dāng)前的隊列依次執(zhí)行。
更新也是一樣,先父beforeUpdate -> 子beforeUpdate
再到 子 updated -> 父 updated
加載渲染過程
父beforeCreate-> 父created-> 父beforeMount-> 子beforeCreate-> 子created-> 子beforeMount- > 子mounted-> 父mounted
子組件更新過程
父beforeUpdate-> 子beforeUpdate-> 子updated-> 父updated
父組件更新過程
父beforeUpdate -> 父updated
銷毀過程
父beforeDestroy-> 子beforeDestroy-> 子destroyed-> 父destroyed
# Vue中父組件能監(jiān)聽到子組件的生命周期嗎
父組件通過@hook:
能夠監(jiān)聽到子組件的生命周期,舉個栗子:
// 這里是父組件
<template>
<child @hook:mounted="getChildMounted" />
</template>
<script>
method: {
getChildMounted () {
// 這里可以獲取到子組件mounted的信息
}
}
</script>
.Vue 中組件怎么通訊?
父子通訊: 父 → 子
props
, 子 → 父$on / $emit
通過eventsMixin
方法中的$on
方法維護一個事件的數(shù)組,然后將函數(shù)名傳入$emit
方法,循環(huán)遍歷出函數(shù)并執(zhí)行。獲得父子組件實例的方式:
$parent / $children
在初始化的時候調(diào)用initLifecycle
方法初始化$parent
和$children
放在實例上在父組件中提供數(shù)據(jù)供子組件/孫子組件注入進來:
Provide / Inject
。
通過initProvide
和initInjections
方法分別把provide
和reject
放在$options
上。在調(diào)用reject
的時候,調(diào)用resolveInject
方法遍歷,查看父級是否有此屬性,有則就直接return
并把它定義在自己的實例上。Ref
獲得實例的方式調(diào)用組件的屬性或方法
ref
被用來給元素或子組件注冊引用信息。引用信息將會注冊在父組件的 $refs 對象上。
用在DOM
上就是DOM
實例,用在組件上就是組件實例。Event bus
實現(xiàn)跨組件通訊
實質(zhì)上還是基于$on
和$emit
,因為每個實例都有$on
和$emit
并且事件的綁定和觸發(fā)必須在同一個實例,所以一般會專門定義一個實例去用于通信,如Vue.prototype.$bnts = new Vue
。Vuex
狀態(tài)管理實現(xiàn)通訊$attrs
和$Listeners
實現(xiàn)數(shù)據(jù) 和 事件的傳遞,還有v-bind="$prop"
.為什么使用異步組件?
可使用異步的方式加載組件,減少打包體積,主要依賴 import()
語法,可實現(xiàn)文件的分割加載
components:{
testCpt: (resove) => import("../components/testCpt") 或
testCpt: r => require(['@/views/assetsInfo/assetsProofList'],r)
}
加載組件的時候,如果組件是個函數(shù)會調(diào)用 resolveAsyncComponent
方法, 并傳入組件定義的函數(shù) asyncFactory
, 并讓其馬上執(zhí)行。因為是異步的所以執(zhí)行后并不會馬上返回結(jié)果。而返回的是一個 promise
,因此沒有返回值, 返回的是一個占位符。
加載完成后,會執(zhí)行 factory
函數(shù)并傳入了成功/失敗的回調(diào)。在回調(diào) resolve
成功的回調(diào)時會調(diào)用 forceRander
方法, 內(nèi)部調(diào)用 $forceUpdate
強制刷新。之后 resolveAsyncComponent
判斷已經(jīng)執(zhí)行成功,就是去創(chuàng)建組件、初始化組件和渲染組件。
# Vue中的事件修飾符主要有哪些?分別是什么作用
.stop
:阻止事件冒泡 .native
:綁定原生事件
.once
:事件只執(zhí)行一次
.self
:將事件綁定在自身身上,相當(dāng)于阻止事件冒泡
.prevent
:阻止默認(rèn)事件 .caption
:用于事件捕獲
# v-for 里面數(shù)據(jù)層次太多,數(shù)據(jù)不刷新怎么辦
運用 this.$forceUpdate()
迫使 Vue 實例重新渲染。
注意它僅僅影響實例本身和插入插槽內(nèi)容的子組件,而不是所有子組件。
.說說對 keep-alive 的了解
keep-alive
是一個抽象組件,可實現(xiàn)組件緩存。當(dāng)組件切換時不會對當(dāng)前組件進行卸載。
算法: LRU
→ 最近最久未使用法
常用的生命周期: activated
和 deactivated
聲明 keep-alive
時在函數(shù)里設(shè)置了幾個屬性: props
,created
,destroyed
,mounted
和rander
等;
-
props
: 調(diào)用keep-alive
組件可設(shè)置的屬性,共有三個屬性如下:
include: 想緩存的組件
exclude: 不想緩存的組件
max: 最多緩存多少個 -
created
: 創(chuàng)建一個緩存列表 -
destroyed
: 銷毀時清空所有緩存列表 -
mounted
: 會監(jiān)聽 include 和 exclude, 動態(tài)添加 或 移除緩存 -
rander
: 渲染時拿到第一個組件,拿到第一個組件,判斷是不是在緩存里
.$route
和 $router
的區(qū)別是什么?
$router
為 VueRouter
實例,是個全局路由對象,包含路由跳轉(zhuǎn)方法、鉤子函數(shù)等。
$route
是 路由信息對象 || 跳轉(zhuǎn)的路由對象,每一個路由都會有一個route
對象,是一個局部對象,包含path,params,hash,query,fullPath,matched,name
等路由信息參數(shù)。
. Vue 路由的鉤子函數(shù)
首頁可以控制導(dǎo)航跳轉(zhuǎn),beforeEach,afterEach等,一般用于頁面title的修改。
一些需要登錄才能調(diào)整頁面的重定向功能。
beforeEach主要有3個參數(shù)to,from,next:
to:route即將進入的目標(biāo)路由對象,
from:route當(dāng)前導(dǎo)航正要離開的路由
next:function一定要調(diào)用該方法resolve這個鉤子。執(zhí)行效果依賴next方法的調(diào)用參數(shù)。可以控制網(wǎng)頁的跳轉(zhuǎn)。
. vue-router有哪幾種路由守衛(wèi)?
- 全局守衛(wèi) ( vue-router 全局有三個守衛(wèi) )
router.beforeEach
全局前置守衛(wèi) 進入路由之前
router.beforeResolve
全局解析守衛(wèi)(2.5.0+) 在beforeRouteEnter調(diào)用之后調(diào)用
router.afterEach
全局后置鉤子 進入路由之后
// main.js 入口文件
import router from './router'; // 引入路由
router.beforeEach((to, from, next) => {
next();
});
router.beforeResolve((to, from, next) => {
next();
});
router.afterEach((to, from) => {
console.log('afterEach 全局后置鉤子');
});
- 路由獨享守衛(wèi)
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// 參數(shù)用法什么的都一樣,調(diào)用順序在全局前置守衛(wèi)后面,所以不會被全局守衛(wèi)覆蓋
}
}
]
})
- 路由組件內(nèi)的守衛(wèi)
beforeRouteEnter
進入路由前, 在路由獨享守衛(wèi)后調(diào)用 不能 獲取組件實例 this,組件實例還沒被創(chuàng)建
beforeRouteUpdate (2.2)
路由復(fù)用同一個組件時, 在當(dāng)前路由改變,但是該組件被復(fù)用時調(diào)用 可以訪問組件實例 this
beforeRouteLeave
離開當(dāng)前路由時, 導(dǎo)航離開該組件的對應(yīng)路由時調(diào)用,可以訪問組件實例 this
.hash 模式 和 history模式
hash:
在 url 中帶有 #,其原理是 onhashchange
事件。
可以在 window
對象上監(jiān)聽這個事件:
window.onhashchange = function(event){
...
}
history
: 沒有原 # , 其原理是 popstate 事件,需要后臺配置支持。
html5 中新增兩個操作歷史棧的API: pushState()
和 replaceState()
方法。
history.pushState(data[,title][,url]); // 向歷史記錄中追加一條記錄
history.replaceState(data[,title][,url]); // 替換當(dāng)前頁在歷史記錄中的信息。
這兩個方法也可以改變url,頁面也不會重新刷新,在當(dāng)前已有的 back、forward、go 的基礎(chǔ)之上,它們提供了對歷史記錄進行修改的功能。只是當(dāng)它們執(zhí)行修改時,雖然改變了當(dāng)前的 URL,但瀏覽器不會立即向后端發(fā)送請求。
.Vuex 是什么? 怎么使用它? 哪種功能場景使用?
Vuex
是一個專為 Vue.js
應(yīng)用程序開發(fā)的狀態(tài)管理模式。它采用集中式存儲管理應(yīng)用的所有組件的狀態(tài),并以相應(yīng)的規(guī)則保證狀態(tài)以一種可預(yù)測的方式發(fā)生變化。
Vuex
只能使用在 vue 上,因為其高度依賴于 vue 的雙向綁定 和 插件系統(tǒng)。
調(diào)用了 Vue.mixin
,在所有組件的 beforeCreate
生命周期注入了設(shè)置 this.$store
這樣一個對象。
場景有:單頁應(yīng)用中,組件之間的狀態(tài)、音樂播放、登錄狀態(tài)、加入購物車
state
: Vuex 使用單一狀態(tài)樹,存放的數(shù)據(jù)狀態(tài),不可以直接修改里面的數(shù)據(jù)。
mutations
: 定義方法動態(tài)修改 Vuex 的 store
中的狀態(tài)或數(shù)據(jù)。
getters
: 類似 vue 的計算屬性,主要用來過濾一些數(shù)據(jù)。
actions
: 可以理解為通過將 mutations
里面處理數(shù)據(jù)的方法變成可異步的方法,簡單的說就是異步操作數(shù)據(jù)。view
層通過 store.dispath
來分發(fā) action
。
modules
: 項目特別復(fù)雜的時候,可以讓每一個模塊擁有自己的 state、mutation、action、getters
,使得結(jié)構(gòu)非常清晰,方便管理。
actions 和 mutations的區(qū)別
action
主要處理的是異步的操作,mutation
必須同步執(zhí)行,而 action
既可以處理同步,也可以處理異步的操作。action
提交的是 mutation
,而不是直接變更狀態(tài)。
如果請求來的數(shù)據(jù)不是要被其他組件公用,僅僅在請求的組件內(nèi)使用,就不需要放入 vuex
的 state
里。
如果被其他地方復(fù)用,請將請求放入 action
里方便復(fù)用,并包裝成 promise
返回。
.assets 和 static的區(qū)別
相同點: assets
和 static
兩個都是存放靜態(tài)資源文件。項目中所需要的資源文件圖片,字體圖標(biāo),樣式文件等都可以放在這兩個文件下。
不相同點:
assets
中存放的靜態(tài)資源文件在項目打包時,會將 assets
中放置的靜態(tài)資源文件進行打包上傳,所謂打包簡單點可以理解為壓縮體積,代碼格式化。而壓縮后的靜態(tài)資源文件最終也都會放置在static文件中跟著index.html
一同上傳至服務(wù)器。
static
中放置的靜態(tài)資源文件就不會要走打包壓縮格式化等流程,而是直接進入打包好的目錄,直接上傳至服務(wù)器。因為避免了壓縮直接進行上傳,在打包時會提高一定的效率,但是 static
中的資源文件由于沒有進行壓縮等操作,所以文件的體積也就相對于 assets
中打包后的文件提交較大點。在服務(wù)器中就會占據(jù)更大的空間。
建議:將項目中 template
需要的樣式文件js文件等都可以放置在 assets
中,走打包這一流程。減少體積。而項目中引入的第三方的資源文件如iconfoont.css等文件可以放置在 static
中,因為這些引入的第三方文件已經(jīng)經(jīng)過處理,我們不再需要處理,直接上傳。
Vue 中 key 的作用是什么?
需要使用 key 給每一個節(jié)點做唯一標(biāo)識,可讓 diff 算法可以正確識別此節(jié)點,以更高效的更新虛擬 DOM。
新舊 children 中的節(jié)點只有順序是不同的時候,最佳的操作應(yīng)該是通過移動元素的位置來達到更新的目的
需要在新舊 children 的節(jié)點中保存映射關(guān)系,以便能夠在舊 children 的節(jié)點中找到可復(fù)用的節(jié)點。key也就是children中節(jié)點的唯一標(biāo)識
.用vnode描述一個DOM結(jié)構(gòu)
虛擬節(jié)點就是用一個對象描述真實的dom元素
會將 template
先轉(zhuǎn)換成 ast
樹, ast
通過代碼生成 codegen
轉(zhuǎn)成 rander
函數(shù), rander
函數(shù)內(nèi)部調(diào)用 $createElement
方法簡稱 _c
, 傳入 tag
(創(chuàng)建的元素), data
(元素的屬性), children
(子元素) . 會判斷 children
是不是一個字符串, 否則會做深度遞歸, 最后返回的結(jié)果就是一個對象,可描述出DOM
結(jié)構(gòu).
.簡述 Vue 中 diff 算法原理
- 先同級比較, 在比較子節(jié)點.
- 判斷出一方有子節(jié)點另一方?jīng)]有子節(jié)點的情況.
如果新的一方有子節(jié)點,老的沒有,則把子節(jié)點直接插入到老節(jié)點里即可.
如果老的一方有子節(jié)點,新的沒有,則把老的子節(jié)點直接刪除. - 判斷出都有子節(jié)點的情況, 遞歸遍歷子采用
雙指針
(頭/尾指針)的方式比對節(jié)點.
Vue.use 與 Vue.component 的區(qū)別
都用于注冊全局組件/插件的
Vue.component()
每次只能注冊一個組件,功能很單一。
Vue.component('draggable', draggable)
Vue.use()
內(nèi)部調(diào)用的仍是 Vue.component()
去注冊全局組件/插件,但它可以做更多事情,比如多次調(diào)用 Vue.component() 一次性注冊多個組件,還可以調(diào)用Vue.directive()、Vue.mixins()、Vue.prototype.xxx=xxx 等等,其第二個可選參數(shù)又可以傳遞一些數(shù)據(jù)
Vue.use({
install:function (Vue, options) {
// 接收傳遞的參數(shù): { name: 'My-Vue', age: 28 }
console.log(options.name, options.age)
Vue.directive('my-directive',{
inserted(el, binding, vnode) { }
})
Vue.mixin({
mounted() { }
})
Vue.component('draggable', draggable)
Vue.component('Tree', Tree)
}
},
{ name: 'My-Vue', age: 28 })
在main.js 文件里 動態(tài)注冊全局組件時, 或用到 require.context
require.context():
一個 Webpack 的API,獲取一個特定的上下文(創(chuàng)建自己的context),主要用來實現(xiàn)自動化導(dǎo)入模塊。
它會遍歷文件夾中的指定文件,然后自動化導(dǎo)入,而不需要每次都顯式使用 import / require 語句導(dǎo)入模塊!
在前端工程中,如果需要一個文件夾引入很多模塊,則可以使用 require.context()
require.context(directory, useSubdirectories = false, regExp = /^\.\//)
directory
{String} 讀取目錄的路徑
useSubdirectories
{Boolean} 是否遞歸遍歷子目錄
regExp
{RegExp} 匹配文件的正則
既然 Vue 通過數(shù)據(jù)劫持可以精準(zhǔn)探測數(shù)據(jù)變化,為什么還需要虛擬 DOM 進行 diff 檢測差異?
現(xiàn)代前端框架有兩種方式偵測變化,一種是 pull
一種是push
pull
: 其代表為 React
,通常會用 setStateAPI
顯式更新,然后 React
會進行一層層的 Virtual Dom Diff
操作找出差異,然后 Patch
到 DOM
上,React
從一開始就不知道到底是哪發(fā)生了變化,只是知道「有變化了」,然后再進行比較暴力的 Diff
操作查找「哪發(fā)生變化了」,另外一個代表就是 Angular
的臟檢查操作。
push
: Vue
的響應(yīng)式系統(tǒng)則是 push
的代表,當(dāng) Vue
程序初始化的時候就會對數(shù)據(jù) data
進行依賴的收集,一但數(shù)據(jù)發(fā)生變化,響應(yīng)式系統(tǒng)就會立刻得知,因此 Vue
是一開始就知道是「在哪發(fā)生變化了」,但是這又會產(chǎn)生一個問題,如果你熟悉 Vue
的響應(yīng)式系統(tǒng)就知道,通常一個綁定一個數(shù)據(jù)就需要一個 Watcher
,一但我們的綁定細(xì)粒度過高就會產(chǎn)生大量的 Watcher
,這會帶來內(nèi)存以及依賴追蹤的開銷,而細(xì)粒度過低會無法精準(zhǔn)偵測變化,因此 Vue
的設(shè)計是選擇中等細(xì)粒度的方案,在組件級別進行 push
偵測的方式,也就是那套響應(yīng)式系統(tǒng),通常我們會第一時間偵測到發(fā)生變化的組件,然后在組件內(nèi)部進行 Virtual Dom Diff
獲取更加具體的差異,而Virtual Dom Diff
則是 pull
操作,Vue
是 push+pull
結(jié)合的方式進行變化偵測的。
Vue 為什么沒有類似于 React 中 shouldComponentUpdate 的生命周期?
根本原因是 Vue
與 React
的變化偵測方式有所不同
React
是 pull
的方式偵測變化,當(dāng) React
知道發(fā)生變化后,會使用 Virtual Dom Diff
進行差異檢測,但是很多組件實際上是肯定不會發(fā)生變化的,這個時候需要用 shouldComponentUpdate
進行手動操作來減少diff
,從而提高程序整體的性能。
Vue
是 pull+push
的方式偵測變化的,在一開始就知道那個組件發(fā)生了變化,因此在 push
的階段并不需要手動控制 diff
,而組件內(nèi)部采用的 diff
方式實際上是可以引入類似于 shouldComponentUpdate
相關(guān)生命周期的,但是通常合理大小的組件不會有過量的 diff
,手動優(yōu)化的價值有限,因此目前 Vue
并沒有考慮引入shouldComponentUpdate
這種手動優(yōu)化的生命周期。
.Vue 中常見的性能優(yōu)化
- 編碼優(yōu)化
(1). 不要將所有的數(shù)據(jù)放在data
里,data
中的數(shù)據(jù)都會增加gette
r和setter
,收收集對應(yīng)的watcher
(2). 在v-for
時給每項元素綁定事件必須使用時間代理
(3). SPA頁面采用keep-alive
緩存組件
(4). 拆分組件(提高復(fù)用性,增加代碼的可維護性,減少不必要的渲染)
(5).v-if
當(dāng)值為false
時內(nèi)部指令不執(zhí)行具有阻斷功能,很多情況下使用v-if
代替v-show
(6). 使用 key 保證唯一性
(7). 使用Object.freeze
凍結(jié)數(shù)據(jù),凍結(jié)后不再有getter
和setter
(8). 合理使用路由懶加載和異步組件
(9). 數(shù)據(jù)持久化問題如: 防抖、節(jié)流 - Vue 加載性能優(yōu)化
(1). 第三方模塊按需導(dǎo)入(babel-plugin-component
)
(2). 滾動可視區(qū)域動態(tài)加載(vue-virtual-scroll-list
/ 'vue-virtual-scroller') -- 長列表優(yōu)化
(3). 圖片懶加載(vue-lazyload
) - 用戶體驗
(1).app-skeleton
骨架屏
(2).app-sheapp
殼 - SEO 優(yōu)化
(1). 預(yù)加載插件prerender-spa-plugin
(2). 服務(wù)端渲染 ssr - 打包優(yōu)化
(1). 使用 CDN 的方式加載第三方模塊
(2). 多線程打包
(3).splitChunk
抽離公共文件 - 緩存 壓縮
(1). 客戶端緩存和服務(wù)端緩存
(2). 服務(wù)端gzip壓縮
.什么是作用域插槽?
- 插槽: 創(chuàng)建組件虛擬節(jié)點時,會將組件兒子的虛擬節(jié)點先保存起來。初始化組件時,通過插槽屬性將兒子進行分類。(作用域為父組件)
渲染組件時會拿對應(yīng)的slot
屬性的節(jié)點進行替換操作。 - 作用域插槽: 在解析的時候不會作為組件的孩子節(jié)點。會解析成函數(shù),當(dāng)子組件渲染時,會調(diào)用此函數(shù)進行渲染。(作用域為子組件)
普通插槽編譯時調(diào)用 createElement
方法創(chuàng)建組件,并把子節(jié)點生成虛擬 dom
做好標(biāo)識存起來。渲染時調(diào)用 randerSlot
方法循環(huán)匹配出對應(yīng)的虛擬節(jié)點在父組件替換當(dāng)前位置。
而作用域插槽在編譯時會把子組件編譯成函數(shù),函數(shù)不調(diào)用就不會渲染。也就是說在初始化組件的時候并不會渲染子節(jié)點。渲染頁面時調(diào)用 randerSlot
方法執(zhí)行子節(jié)點的函數(shù)并把對應(yīng)的屬性傳過來。當(dāng)節(jié)點渲染完成之后在組件內(nèi)部替換當(dāng)前位置。
.Vue與Angular以及React的區(qū)別?
1.與AngularJS的區(qū)別
相同點:
- 都支持指令:內(nèi)置指令和自定義指令。
- 都支持過濾器:內(nèi)置過濾器和自定義過濾器。
- 都支持雙向數(shù)據(jù)綁定。
- 都不支持低端瀏覽器。
不同點:
-
AngularJS
采用 TypeScript 開發(fā), 而 Vue 可以使用 javascript 也可以使用 TypeScript。 - 在性能上,AngularJS依賴對數(shù)據(jù)做臟檢查,所以Watcher越多越慢。
Vue.js使用基于依賴追蹤的觀察并且使用異步隊列更新,所有的數(shù)據(jù)都是獨立觸發(fā)的。
對于龐大的應(yīng)用來 說,這個優(yōu)化差異還是比較明顯的。 - AngularJS社區(qū)完善, Vue的學(xué)習(xí)成本較小
2.與React的區(qū)別
相同點:
- React采用特殊的JSX語法,Vue.js在組件開發(fā)中也推崇編寫.vue特殊文件格式,對文件內(nèi)容都有一些約定,兩者都需要編譯后使用。
- 中心思想相同:一切都是組件,組件實例之間可以嵌套。
- 都提供合理的鉤子函數(shù),可以讓開發(fā)者定制化地去處理需求。
- 都不內(nèi)置AJAX,Route等功能核心包,而是以插件的方式加載。
- 在組件開發(fā)中都支持mixins的特性。
不同點:
- vue 組件分為全局注冊和局部注冊,在 react 中都是通過 import 相應(yīng)組件,然后模版中引用;
- props 是可以動態(tài)變化的,子組件也實時更新,在 react 中官方建議props要像純函數(shù)那樣,輸入輸出一致對應(yīng),而且不太建議通過 props 來更改視圖
- vue 多了指令系統(tǒng),讓模版可以實現(xiàn)更豐富的功能,而 React 只能使用JSX語法
- react 是整體的思路的就是函數(shù)式,所以推崇純組件,數(shù)據(jù)不可變,單向數(shù)據(jù)流,當(dāng)然需要雙向的地方也可以做到,比如結(jié)合 redux-form,組件的橫向拆分一般是通過高階組件。而 vue 是數(shù)據(jù)可變的,雙向綁定,聲明式的寫法,vue組件的橫向拆分很多情況下用 mixin。
- Vue增加的語法糖computed和watch,而在React中需要自己寫一套邏輯來實現(xiàn)。
高精度全局權(quán)限處理
權(quán)限控制由前端處理時,通常使用 v-if / v-show 控制元素對不同權(quán)限的響應(yīng)效果。這種情況下,就會導(dǎo)致很多不必要的重復(fù)代碼,不容易維護,因此可以造一個小車輪,掛在全局上對權(quán)限進行處理。
// 注冊全局自定義指令,對底層原生DOM操作
Vue.directive('permission', {
// inserted → 元素插入的時候
inserted(el, binding){
// 獲取到 v-permission 的值
const { value } = binding
if(value) {
// 根據(jù)配置的權(quán)限,去當(dāng)前用戶的角色權(quán)限中校驗
const hasPermission = checkPermission(value)
if(!hasPermission){
// 沒有權(quán)限,則移除DOM元素
el.parentNode && el.parentNode.removeChild(el)
}
} else{
throw new Error(`need key! Like v-permission="['admin','editor']"`)
}
}
})
// --> 在組件中使用 v-permission
<button v-permission="['admin']">權(quán)限1</button>
<button v-permission="['admin', 'editor']">權(quán)限2</button>
.對于 vue3.0 特性你有什么了解的嗎?
(1). 監(jiān)測機制的改變
3.0 基于代理 Proxy 的 observer 實現(xiàn),提供全語言覆蓋的反應(yīng)性跟蹤。替代了Vue 2采用 defineProperty去定義get 和 set, 意味著徹底放棄了兼容IE, 這也取消除了 Vue 2 當(dāng)中基于 Object.defineProperty 的實現(xiàn)所存在的很多限制:
=>只能監(jiān)測屬性,不能監(jiān)測對象:
=>檢測屬性的添加和刪除;
=>檢測數(shù)組索引和長度的變更;
=>支持 Map、Set、WeakMap 和 WeakSet。
新的 observer 還提供了以下特性:
用于創(chuàng)建 observable 的公開 API
。這為中小規(guī)模場景提供了簡單輕量級的跨組件狀態(tài)管理解決方案。
默認(rèn)采用惰性觀察
。在 2.x 中,不管反應(yīng)式數(shù)據(jù)有多大,都會在啟動時被觀察到。如果數(shù)據(jù)集很大,這可能會在應(yīng)用啟動時帶來明顯的開銷。在 3.x 中,只觀察用于渲染應(yīng)用程序最初可見部分的數(shù)據(jù)。
更精確的變更通知
。在 2.x 中,通過 Vue.set 強制添加新屬性將導(dǎo)致依賴于該對象的 watcher 收到變更通知。在 3.x 中,只有依賴于特定屬性的 watcher 才會收到通知。
不可變的 observable
:我們可以創(chuàng)建值的“不可變”版本(即使是嵌套屬性),除非系統(tǒng)在內(nèi)部暫時將其“解禁”。這個機制可用于凍結(jié) prop 傳遞或 Vuex 狀態(tài)樹以外的變化。
更好的調(diào)試功能
:我們可以使用新的 renderTracked 和 renderTriggered 鉤子精確地跟蹤組件在什么時候以及為什么重新渲染。
(2). 模板
模板方面沒有大的變更,只改了作用域插槽,2.x 的機制導(dǎo)致作用域插槽變了,父組件會重新渲染,而 3.0 把作用域插槽改成了函數(shù)的方式,這樣只會影響子組件的重新渲染,提升了渲染的性能。
同時,對于 render 函數(shù)的方面,vue3.0 也進行一系列更改來方便習(xí)慣直接使用 api 來生成 vdom 。
(3). 對象式的組件聲明方式
vue2.x
中的組件是通過聲明的方式傳入一系列 option,和 TypeScript 的結(jié)合需要通過一些裝飾器的方式來做,雖然能實現(xiàn)功能,但是比較麻煩。
vue3.0
修改了組件的聲明方式,改成了類式的寫法,這樣使得和 TypeScript 的結(jié)合變得很容易。
此外,vue 的源碼也改用了 TypeScript 來寫。其實當(dāng)代碼的功能復(fù)雜之后,必須有一個靜態(tài)類型系統(tǒng)來做一些輔助管理。現(xiàn)在 vue3.0 也全面改用 TypeScript 來重寫了,更是使得對外暴露的 api 更容易結(jié)合 TypeScript。靜態(tài)類型系統(tǒng)對于復(fù)雜代碼的維護確實很有必要。
(4). 其它方面的更改
支持自定義渲染器,從而使得 weex 可以通過自定義渲染器的方式來擴展,而不是直接 fork 源碼來改的方式。
支持 Fragment(多個根節(jié)點)和 Protal(在 dom 其他部分渲染組建內(nèi)容)組件,針對一些特殊的場景做了處理。
基于 treeshaking 優(yōu)化,提供了更多的內(nèi)置功能。
.Vue等單頁面應(yīng)用(spa)及其優(yōu)缺點
優(yōu)點
: Vue的目標(biāo)是通過盡可能簡單的 API實現(xiàn)響應(yīng)的數(shù)據(jù)綁定和組合的視圖組件,核心是一個響應(yīng)的數(shù)據(jù)綁定系統(tǒng)。MVVM、數(shù)據(jù)驅(qū)動、組件化、輕量、簡潔、高效、快速、模塊友好;即第一次就將所有的東西都加載完成,因此,不會導(dǎo)致頁面卡頓。
缺點
: 不支持低版本的瀏覽器,最低只支持到IE9;不利于SEO的優(yōu)化(如果要支持SEO,建議通過服務(wù)端來進行渲染組件);第一次加載首頁耗時相對長一些;不可以使用瀏覽器的導(dǎo)航按鈕需要自行實現(xiàn)前進、后退。