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