Vue 的這些技巧你真的都掌握了嗎?
大廠技術(shù)??高級(jí)前端??Node進(jìn)階
點(diǎn)擊上方?程序員成長(zhǎng)指北,關(guān)注公眾號(hào)
回復(fù)1,加入高級(jí)Node交流群

前言
文章目的昭然若揭?????,整理匯總 Vue 框架中重要的特性、框架的原理。
那 "前車之鑒" 從何而來?
是的,我又要講小故事了,但這次是故事的續(xù)集。
故事第 1 集:CSS預(yù)處理器,你還是只會(huì)嵌套么 ?[2]
故事第 2 集:【自適應(yīng)】px 轉(zhuǎn) rem,你還在手算么?[3]
為什么說是續(xù)集,因?yàn)檫@些都是同一大佬問的,在此感謝大佬,天降素材??。
故事續(xù)集
大佬:有看過 Vue 源碼么?
我:嗯嗯,看過。
大佬:那大概講一講 nextTick 的底層實(shí)現(xiàn) ?
我:停頓了大概10s,說了句忘了。(理不直氣還壯)
大佬:噢噢,沒事。(內(nèi)心大概已經(jīng)放棄對(duì)我知識(shí)面的挖掘)
因?yàn)槭且曨l面試,強(qiáng)裝自信的尷尬從屏幕中溢出,這大概就是普通且自信???♂??裝X失敗案例引以為戒,能寫出續(xù)集的面試結(jié)果不提也罷。
這次面試打擊還是蠻大的,考察內(nèi)容全面且細(xì)節(jié)。面試后一直在整理 Vue 相關(guān)的知識(shí)點(diǎn),所以不會(huì)將nextTick實(shí)現(xiàn)單獨(dú)成文,只是收錄在下方試題中。前車之鑒可以為鑒,大家可以把本篇文章當(dāng)測(cè)驗(yàn),考察自己是否對(duì)這些知識(shí)點(diǎn)熟練于心。
萬(wàn)字長(zhǎng)文,持續(xù)更新,若有遺漏知識(shí)點(diǎn),后續(xù)會(huì)補(bǔ)充。
題目
Vue 的優(yōu)缺點(diǎn)
優(yōu)點(diǎn)
創(chuàng)建單頁(yè)面應(yīng)用的輕量級(jí)Web應(yīng)用框架 簡(jiǎn)單易用 雙向數(shù)據(jù)綁定 組件化的思想 虛擬DOM 數(shù)據(jù)驅(qū)動(dòng)視圖
缺點(diǎn)
不支持IE8(現(xiàn)階段只能勉強(qiáng)湊出這么半點(diǎn)??)
SPA 的理解
SPA是Single-Page-Application的縮寫,翻譯過來就是單頁(yè)應(yīng)用。在WEB頁(yè)面初始化時(shí)一同加載Html、Javascript、Css。一旦頁(yè)面加載完成,SPA不會(huì)因?yàn)橛脩舨僮鞫M(jìn)行頁(yè)面重新加載或跳轉(zhuǎn),取而代之的是利用路由機(jī)制實(shí)現(xiàn)Html內(nèi)容的變換。
優(yōu)點(diǎn)
良好的用戶體驗(yàn),內(nèi)容更改無需重載頁(yè)面。 基于上面一點(diǎn),SPA相對(duì)服務(wù)端壓力更小。 前后端職責(zé)分離,架構(gòu)清晰。
缺點(diǎn)
由于單頁(yè)WEB應(yīng)用,需在加載渲染頁(yè)面時(shí)請(qǐng)求JavaScript、Css文件,所以耗時(shí)更多。 由于前端渲染,搜索引擎不會(huì)解析JS,只能抓取首頁(yè)未渲染的模板,不利于SEO。 由于單頁(yè)應(yīng)用需在一個(gè)頁(yè)面顯示所有的內(nèi)容,默認(rèn)不支持瀏覽器的前進(jìn)后退。
缺點(diǎn)3,想必有人和我有同樣的疑問。
通過資料查閱,其實(shí)是前端路由機(jī)制解決了單頁(yè)應(yīng)用無法前進(jìn)后退的問題。Hash模式中Hash變化會(huì)被瀏覽器記錄(onhashchange事件),History模式利用 H5 新增的pushState和replaceState方法可改變?yōu)g覽器歷史記錄棧。
new Vue(options) 都做了些什么
如下 Vue 構(gòu)造函數(shù)所示,主要執(zhí)行了 this._init(options)方法,該方法在initMixin函數(shù)中注冊(cè)。
import?{?initMixin?}?from?'./init'
import?{?stateMixin?}?from?'./state'
import?{?renderMixin?}?from?'./render'
import?{?eventsMixin?}?from?'./events'
import?{?lifecycleMixin?}?from?'./lifecycle'
import?{?warn?}?from?'../util/index'
function?Vue?(options)?{
??if?(process.env.NODE_ENV?!==?'production'?&&
????!(this?instanceof?Vue)
??)?{
????warn('Vue?is?a?constructor?and?should?be?called?with?the?`new`?keyword')
??}
??//?Vue.prototype._init?方法
??this._init(options)
}
//?_init?方法在?initMixin?注冊(cè)
initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)
export?default?Vue
復(fù)制代碼
查看initMixin方法的實(shí)現(xiàn),其他函數(shù)具體實(shí)現(xiàn)可自行查看,這里就不貼出了。
let?uid?=?0
export?function?initMixin()?{
??Vue.prototype._init?=?function(options)?{
????const?vm?=?this
????vm._uid?=?uid++
????vm._isVue?=?true
???
????//?處理組件配置項(xiàng)
????if?(options?&&?options._isComponent)?{
???????/**
???????*?如果是子組件,走當(dāng)前?if?分支
???????*?函數(shù)作用是性能優(yōu)化:將原型鏈上的方法都放到vm.$options中,減少原型鏈上的訪問
???????*/???
??????initInternalComponent(vm,?options)
????}?else?{
??????/**
???????*?如果是根組件,走當(dāng)前?else?分支
???????*?合并?Vue?的全局配置到根組件中,如?Vue.component?注冊(cè)的全局組件合并到根組件的?components?的選項(xiàng)中
???????*?子組件的選項(xiàng)合并發(fā)生在兩個(gè)地方
???????*?1.?Vue.component?方法注冊(cè)的全局組件在注冊(cè)時(shí)做了選項(xiàng)合并
???????*?2.?{?component:?{xx}?}?方法注冊(cè)的局部組件在執(zhí)行編譯器生成的?render?函數(shù)時(shí)做了選項(xiàng)合并
???????*/??
??????vm.$options?=?mergeOptions(
????????resolveConstructorOptions(vm.constructor),
????????options?||?{},
????????vm
??????)
????}
??
????if?(process.env.NODE_ENV?!==?'production')?{
??????initProxy(vm)
????}?else?{
??????vm._renderProxy?=?vm
????}
????vm._self?=?vm
????/**
????*?初始化組件實(shí)例關(guān)系屬性,如:$parent $root $children $refs
????*/
????initLifecycle(vm)
????/**
????*?初始化自定義事件
????*?@click ="handleClick">
????*?組件上注冊(cè)的事件,監(jiān)聽者不是父組件,而是子組件本身
????*/
????initEvents(vm)
????/**
????*?解析組件插槽信息,得到vm.$slot,處理渲染函數(shù),得到 vm.$createElement 方法,即 h 函數(shù)。
????*/
????initRender(vm)
????/**
????*?執(zhí)行?beforeCreate?生命周期函數(shù)
????*/
????callHook(vm,?'beforeCreate')
????/**
????*?解析?inject?配置項(xiàng),得到?result[key]?=?val?的配置對(duì)象,做響應(yīng)式處理且代理到?vm?實(shí)力上
????*/
????initInjections(vm)?
????/**
????*?響應(yīng)式處理核心,處理?props、methods、data、computed、watch
????*/
????initState(vm)
????/**
????*?解析?provide?對(duì)象,并掛載到?vm?實(shí)例上
????*/
????initProvide(vm)?
????/**
????*?執(zhí)行?created?生命周期函數(shù)
????*/
????callHook(vm,?'created')
????//?如果?el?選項(xiàng),自動(dòng)執(zhí)行$mount
????if?(vm.$options.el)?{
??????vm.$mount(vm.$options.el)
????}
??}
}
復(fù)制代碼
MVVM 的理解
MVVM是Model-View-ViewModel的縮寫。Model 代表數(shù)據(jù)層,可定義修改數(shù)據(jù)、編寫業(yè)務(wù)邏輯。View 代表視圖層,負(fù)責(zé)將數(shù)據(jù)渲染成頁(yè)面。ViewModel 負(fù)責(zé)監(jiān)聽數(shù)據(jù)層數(shù)據(jù)變化,控制視圖層行為交互,簡(jiǎn)單講,就是同步數(shù)據(jù)層和視圖層的對(duì)象。ViewModel 通過雙向綁定把 View 和 Model 層連接起來,且同步工作無需人為干涉,使開發(fā)人員只關(guān)注業(yè)務(wù)邏輯,無需頻繁操作DOM,不需關(guān)注數(shù)據(jù)狀態(tài)的同步問題。

如何實(shí)現(xiàn) v-model
v-model指令用于實(shí)現(xiàn)input、select等表單元素的雙向綁定,是個(gè)語(yǔ)法糖。
原生 input 元素若是text/textarea類型,使用 value 屬性和 input 事件。
原生 input 元素若是radio/checkbox類型,使用 checked屬性和 change 事件。
原生 select 元素,使用 value 屬性和 change 事件。
input 元素上使用 v-model 等價(jià)于
<input?:value="message"?@input="message?=?$event.target.value"?/>
復(fù)制代碼
實(shí)現(xiàn)自定義組件的 v-model
自定義組件的v-model使用prop值為value和input事件。若是radio/checkbox類型,需要使用model來解決原生 DOM 使用的是 checked 屬性 和 change 事件,如下所示。
//?父組件
<template>
??<base-checkbox?v-model="baseCheck"?/>
template>
復(fù)制代碼
//?子組件
<template>
??<input?type="checkbox"?:checked="checked"?@change="$emit('change',?$event.target.checked)"?/>
template>
<script>
export?default?{
??model:?{
????prop:?'checked',
????event:?'change'
??},
??prop:?{
????checked:?Boolean
??}
}
script>
復(fù)制代碼
如何理解 Vue 單向數(shù)據(jù)流
Vue 官方文檔 Prop 菜單下的有個(gè)名為單項(xiàng)數(shù)據(jù)流的子菜單。

我們經(jīng)常說 Vue 的雙向綁定,其實(shí)是在單向綁定的基礎(chǔ)上給元素添加 input/change 事件,來動(dòng)態(tài)修改視圖。Vue 組件間傳遞數(shù)據(jù)仍然是單項(xiàng)的,即父組件傳遞到子組件。子組件內(nèi)部可以定義依賴 props 中的值,但無權(quán)修改父組件傳遞的數(shù)據(jù),這樣做防止子組件意外變更父組件的狀態(tài),導(dǎo)致應(yīng)用數(shù)據(jù)流向難以理解。
如果在子組件內(nèi)部直接更改prop,會(huì)遇到警告處理。
2 種定義依賴 props 中的值
通過 data 定義屬性并將 prop 作為初始值。
<script>
export?default?{
??props:?['initialNumber'],
??data()?{
????return?{
??????number:?this.initailNumber
????}
??}
}
script>
復(fù)制代碼
用 computed 計(jì)算屬性去定義依賴 prop 的值。若頁(yè)面會(huì)更改當(dāng)前值,得分 get 和 set 方法。
<script>
export?default?{
??props:?['size'],
??computed:?{
????normalizedSize()?{
??????return?this.size.trim().toLowerCase()
????}
??}
}
sciprt>
復(fù)制代碼
Vue 響應(yīng)式原理
核心源碼位置:vue/src/core/observer/index.js
響應(yīng)式原理3個(gè)步驟:數(shù)據(jù)劫持、依賴收集、派發(fā)更新。
數(shù)據(jù)分為兩類:對(duì)象、數(shù)組。
對(duì)象
遍歷對(duì)象,通過Object.defineProperty為每個(gè)屬性添加 getter 和 setter,進(jìn)行數(shù)據(jù)劫持。getter 函數(shù)用于在數(shù)據(jù)讀取時(shí)進(jìn)行依賴收集,在對(duì)應(yīng)的 dep 中存儲(chǔ)所有的 watcher;setter 則是數(shù)據(jù)更新后通知所有的 watcher 進(jìn)行更新。
核心源碼
function?defineReactive(obj,?key,?val,?shallow)?{
??//?實(shí)例化一個(gè)?dep,?一個(gè)?key?對(duì)應(yīng)一個(gè)?dep
??const?dep?=?new?Dep()
?
??//?獲取屬性描述符
??const?getter?=?property?&&?property.get
??const?setter?=?property?&&?property.set
??if?((!getter?||?setter)?&&?arguments.length?===?2)?{
????val?=?obj[key]
??}
??//?通過遞歸的方式處理?val?為對(duì)象的情況,即處理嵌套對(duì)象
??let?childOb?=?!shallow?&&?observe(val)
??
??Object.defineProperty(obj,?key,?{
????enumerable:?true,
????configurable:?true,
????//?攔截obj.key,進(jìn)行依賴收集
????get:?function?reactiveGetter?()?{
??????const?value?=?getter???getter.call(obj)?:?val
??????//?Dep.target?是當(dāng)前組件渲染的?watcher
??????if?(Dep.target)?{
????????//?將?dep?添加到?watcher?中
????????dep.depend()
????????if?(childOb)?{
??????????//?嵌套對(duì)象依賴收集
??????????childOb.dep.depend()
??????????//?響應(yīng)式處理?value?值為數(shù)組的情況
??????????if?(Array.isArray(value))?{
????????????dependArray(value)
??????????}
????????}
??????}
??????return?value
????},
????set:?function?reactiveSetter?(newVal)?{
??????//?獲取舊值
??????const?value?=?getter???getter.call(obj)?:?val
??????//?判斷新舊值是否一致
??????if?(newVal?===?value?||?(newVal?!==?newVal?&&?value?!==?value))?{
????????return
??????}
??????if?(process.env.NODE_ENV?!==?'production'?&&?customSetter)?{
????????customSetter()
??????}
??????if?(getter?&&?!setter)?return
??????//?如果是新值,用新值替換舊值
??????if?(setter)?{
????????setter.call(obj,?newVal)
??????}?else?{
????????val?=?newVal
??????}
??????//?新值做響應(yīng)式處理
??????childOb?=?!shallow?&&?observe(newVal)
??????//?當(dāng)響應(yīng)式數(shù)據(jù)更新,依賴通知更新
??????dep.notify()
????}
??})
}
復(fù)制代碼
數(shù)組
用數(shù)組增強(qiáng)的方式,覆蓋原屬性上默認(rèn)的數(shù)組方法,保證在新增或刪除數(shù)據(jù)時(shí),通過 dep 通知所有的 watcher 進(jìn)行更新。
核心源碼
const?arrayProto?=?Array.prototype
//?基于數(shù)組原型對(duì)象創(chuàng)建一個(gè)新的對(duì)象
export?const?arrayMethods?=?Object.create(arrayProto)
const?methodsToPatch?=?[
??'push',
??'pop',
??'shift',
??'unshift',
??'splice',
??'sort',
??'reverse'
]
methodsToPatch.forEach(function?(method)?{
??const?original?=?arrayProto[method]
??//?分別在?arrayMethods?對(duì)象上定義7個(gè)方法
??def(arrayMethods,?method,?function?mutator?(...args)?{
????//?先執(zhí)行原生的方法
????const?result?=?original.apply(this,?args)
????const?ob?=?this.__ob__
????let?inserted
????switch?(method)?{
??????case?'push':
??????case?'unshift':
????????inserted?=?args
????????break
??????case?'splice':
????????inserted?=?args.slice(2)
????????break
????}
????//?針對(duì)新增元素進(jìn)行響應(yīng)式處理
????if?(inserted)?ob.observeArray(inserted)
????//?數(shù)據(jù)無論是新增還是刪除都進(jìn)行派發(fā)更新
????ob.dep.notify()
????return?result
??})
})
復(fù)制代碼
手寫觀察者模式
當(dāng)對(duì)象間存在一對(duì)多的關(guān)系,使用觀察者模式。比如:當(dāng)一個(gè)對(duì)象被修改,會(huì)自動(dòng)通知依賴它的對(duì)象。
let?uid?=?0
class?Dep?{
??constructor()?{
????this.id?=?uid++
????//?存儲(chǔ)所有的?watcher
????this.subs?=?[]
??}
??addSub(sub)?{
????this.subs.push(sub)
??}
??removeSub(sub)?{
????if(this.subs.length)?{
??????const?index?=?this.subs.indexOf(sub)
??????if(index?>?-1)?return?this.subs.splice(index,?1)
????}
??}
??notify()?{
????this.subs.forEach(sub?=>?{
??????sub.update()
????})
??}
}
class?Watcher?{
??constructor(name)?{
????this.name?=?name
??}
??update()?{
????console.log('更新')
??}
}
復(fù)制代碼
手寫發(fā)布訂閱模式
與觀察者模式相似,區(qū)別在于發(fā)布者和訂閱者是解耦的,由中間的調(diào)度中心去與發(fā)布者和訂閱者通信。
Vue響應(yīng)式原理個(gè)人更傾向于發(fā)布訂閱模式。其中 Observer 是發(fā)布者,Watcher 是訂閱者,Dep 是調(diào)度中心。
vue中數(shù)據(jù)綁定原理的設(shè)計(jì)模式到底觀察者還是發(fā)布訂閱?[4],知乎有相關(guān)爭(zhēng)論,感興趣的可以看下。
class?EventEmitter?{
??constructor()?{
????this.events?=?{}
??}
??on(type,?cb)?{
????if(!this.events[type])?this.events[type]?=?[]
????this.events[type].push(cb)
??}
??emit(type,?...args)?{
????if(this.events[type])?{
??????this.events[type].forEach(cb?=>?{
????????cb(...args)
??????})
????}
??}
??off(type,?cb)?{
????if(this.events[type])?{
??????const?index?=?this.events[type].indexOf(cb)
??????if(index?>?-1)?this.events[type].splice(index,?1)
????}
??}
}
復(fù)制代碼
關(guān)于 Vue.observable 的了解
Vue.observable 可使對(duì)象可響應(yīng)。返回的對(duì)象可直接用于渲染函數(shù)和計(jì)算屬性內(nèi),并且在發(fā)生變更時(shí)觸發(fā)相應(yīng)的更新。也可以作為最小化的跨組件狀態(tài)存儲(chǔ)器。
Vue 2.x 中傳入的對(duì)象和返回的對(duì)象是同一個(gè)對(duì)象。
Vue 3.x 則不是一個(gè)對(duì)象,源對(duì)象不具備響應(yīng)式功能。
適用的場(chǎng)景:在項(xiàng)目中沒有大量的非父子組件通信時(shí),可以使用 Vue.observable 去替代 eventBus和vuex方案。
用法如下
//?store.js
import?Vue?from?'vue'
export?const?state?=?Vue.observable({
??count:?1
})
export?const?mutations?=?{
??setCount(count)?{
????state.count?=?count
??}
}?
//?vue?文件
??<div>{{?count?}}div>
</template>
復(fù)制代碼
原理部分和響應(yīng)式原理處理組件 data 是同一個(gè)函數(shù),實(shí)例化一個(gè) Observe,對(duì)數(shù)據(jù)劫持。
組件中的 data 為什么是個(gè)函數(shù)
對(duì)象在棧中存儲(chǔ)的都是地址,函數(shù)的作用就是屬性私有化,保證組件修改自身屬性時(shí)不會(huì)影響其他復(fù)用組件。
Vue 生命周期
| 生命周期 | 描述 |
|---|---|
| beforeCreate | vue實(shí)例初始化后,數(shù)據(jù)觀測(cè)(data observer)和事件配置之前。data、computed、watch、methods都無法訪問。 |
| created | vue實(shí)例創(chuàng)建完成后立即調(diào)用 ,可訪問 data、computed、watch、methods。未掛載 DOM,不能訪問 ref。 |
| beforeMount | 在 DOM 掛載開始之前調(diào)用。 |
| mounted | vue實(shí)例被掛載到 DOM。 |
| beforeUpdate | 數(shù)據(jù)更新之前調(diào)用,發(fā)生在虛擬 DOM 打補(bǔ)丁之前。 |
| updated | 數(shù)據(jù)更新之后調(diào)用。 |
| beforeDestroy | 實(shí)例銷毀前調(diào)用。 |
| destroyed | 實(shí)例銷毀后調(diào)用 。 |
調(diào)用異步請(qǐng)求可在created、beforeMount、mounted生命周期中調(diào)用,因?yàn)橄嚓P(guān)數(shù)據(jù)都已創(chuàng)建。最好的選擇是在created中調(diào)用。
獲取DOM在mounted中獲取,獲取可用$ref方法,這點(diǎn)毋庸置疑。
Vue 父組件和子組件生命周期執(zhí)行順序
加載渲染過程
父先創(chuàng)建,才能有子;子創(chuàng)建完成,父才完整。
順序:父 beforeCreate -> 父 created -> 父 beforeMount -> 子 beforeCreate -> 子 created -> 子 beforeMount -> 子 mounted -> 父 mounted
子組件更新過程
子組件更新 影響到 父組件的情況。
順序:父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated
子組件更新 不影響到 父組件的情況。
順序:子 beforeUpdate -> 子 updated
父組件更新過程
父組件更新 影響到 子組件的情況。
順序:父 beforeUpdate -> 子 beforeUpdate -> 子 updated -> 父 updated
父組件更新 不影響到 子組件的情況。
順序:父 beforeUpdate -> 父 updated
銷毀過程
順序:父 beforeDestroy -> 子 beforeDestroy -> 子 destroyed -> 父 destroyed
父組件如何監(jiān)聽子組件生命周期的鉤子函數(shù)
兩種方式都以 mounted 為例子。
$emit實(shí)現(xiàn)
//?父組件
<template>
??<div?class="parent">
????<Child?@mounted="doSomething"/>
??div>
template>
<script>
export?default?{??
??methods:?{
????doSomething()?{
??????console.log('父組件監(jiān)聽到子組件?mounted?鉤子函數(shù)')
????}
??}
}
script>
//子組件
<template>
??<div?class="child">
??div>
template>
<script>
export?default?{
??mounted()?{
????console.log('觸發(fā)mounted事件...')
????this.$emit("mounted")
??}
}
script>
復(fù)制代碼
@hook實(shí)現(xiàn)
//?父組件
<template>
??<div?class="parent">
????<Child?@hook:mounted="doSomething"/>
??div>
template>
<script>
export?default?{??
??methods:?{
????doSomething()?{
??????console.log('父組件監(jiān)聽到子組件?mounted?鉤子函數(shù)')
????}
??}
}
script>
//子組件
<template>
??<div?class="child">
??div>
template>
<script>
export?default?{
??mounted()?{
????console.log('觸發(fā)mounted事件...')
??}
}
script>
復(fù)制代碼
Vue 組件間通訊方式
父子組件通訊
props 與 $emit children
隔代組件通訊
listeners provide 和 inject
父子、兄弟、隔代組件通訊
EventBus Vuex
v-on 監(jiān)聽多個(gè)方法
常用的修飾符
表單修飾符
lazy: 失去焦點(diǎn)后同步信息 trim: 自動(dòng)過濾首尾空格 number: 輸入值轉(zhuǎn)為數(shù)值類型
事件修飾符
stop:阻止冒泡 prevent:阻止默認(rèn)行為 self:僅綁定元素自身觸發(fā) once:只觸發(fā)一次
鼠標(biāo)按鈕修飾符
left:鼠標(biāo)左鍵 right:鼠標(biāo)右鍵 middle:鼠標(biāo)中間鍵
class 與 style 如何動(dòng)態(tài)綁定
class 和 style 可以通過對(duì)象語(yǔ)法和數(shù)組語(yǔ)法進(jìn)行動(dòng)態(tài)綁定
對(duì)象寫法
<template>
??<div?:class="{?active:?isActive?}">div>
??<div?:style="{?fontSize:?fontSize?}">
template>
<script>
export?default?{
??data()?{
????return?{
??????isActive:?true,
??????fontSize:?30
????}
??}
}
script>
復(fù)制代碼
數(shù)組寫法
<template>
??<div?:class="[activeClass]">div>
??<div?:style="[styleFontSize]">
template>
<script>
export?default?{
??data()?{
????return?{
??????activeClass:?'active',
??????styleFontSize:?{
????????fontSize:?'12px'
??????}
????}
??}
}
script>
復(fù)制代碼
v-show 和 v-if 區(qū)別
共同點(diǎn):控制元素顯示和隱藏。
不同點(diǎn):
v-show 控制的是元素的CSS(display);v-if 是控制元素本身的添加或刪除。 v-show 由 false 變?yōu)?true 的時(shí)候不會(huì)觸發(fā)組件的生命周期。v-if 由 false 變?yōu)?true 則會(huì)觸發(fā)組件的 beforeCreate、create、beforeMount、mounted鉤子,由 true 變?yōu)?false 會(huì)觸發(fā)組件的beforeDestory、destoryed方法。v-if 比 v-show有更高的性能消耗。
為什么 v-if 不能和 v-for 一起使用
性能浪費(fèi),每次渲染都要先循環(huán)再進(jìn)行條件判斷,考慮用計(jì)算屬性替代。
Vue2.x中v-for比v-if更高的優(yōu)先級(jí)。
Vue3.x中v-if?比?v-for?更高的優(yōu)先級(jí)。
computed 和 watch 的區(qū)別和運(yùn)用的場(chǎng)景
computed 和 watch 本質(zhì)都是通過實(shí)例化 Watcher 實(shí)現(xiàn),最大區(qū)別就是適用場(chǎng)景不同。
computed
計(jì)算屬性,依賴其他屬性值,且值具備緩存的特性。只有它依賴的屬性值發(fā)生改變,下一次獲取的值才會(huì)重新計(jì)算。
適用于數(shù)值計(jì)算,并且依賴于其他屬性時(shí)。因?yàn)榭梢岳镁彺嫣匦裕苊饷看潍@取值,都需要重新計(jì)算。
watch
觀察屬性,監(jiān)聽屬性值變動(dòng)。每當(dāng)屬性值發(fā)生變化,都會(huì)執(zhí)行相應(yīng)的回調(diào)。
適用于數(shù)據(jù)變化時(shí)執(zhí)行異步或開銷比較大的操作。
slot 插槽
slot 插槽,可以理解為slot在組件模板中提前占據(jù)了位置。當(dāng)復(fù)用組件時(shí),使用相關(guān)的slot標(biāo)簽時(shí),標(biāo)簽里的內(nèi)容就會(huì)自動(dòng)替換組件模板中對(duì)應(yīng)slot標(biāo)簽的位置,作為承載分發(fā)內(nèi)容的出口。
主要作用是復(fù)用和擴(kuò)展組件,做一些定制化組件的處理。
插槽主要有3種
默認(rèn)插槽
//?子組件
<template>
??<slot>
????<div>默認(rèn)插槽備選內(nèi)容div>
??slot>
template>
//?父組件
<template>
??<Child>
????<div>替換默認(rèn)插槽內(nèi)容div>
??Child>
template>
復(fù)制代碼
具名插槽
slot 標(biāo)簽沒有name屬性,則為默認(rèn)插槽。具備name屬性,則為具名插槽
//?子組件
<template>
??<slot>默認(rèn)插槽的位置slot>
??<slot?name="content">插槽content內(nèi)容slot>
template>
//?父組件
<template>
???<Child>
?????<template?v-slot:default>
???????默認(rèn)...
?????template>
?????<template?v-slot:content>
???????內(nèi)容...
?????template>
???Child>
template>
復(fù)制代碼
作用域插槽
子組件在作用域上綁定的屬性來將組件的信息傳給父組件使用,這些屬性會(huì)被掛在父組件接受的對(duì)象上。
//?子組件
<template>
??<slot?name="footer"?childProps="子組件">
????作用域插槽內(nèi)容
??slot>
template>
//?父組件
<template>
??<Child?v-slot="slotProps">
????{{?slotProps.childProps?}}
??Child>
template>
復(fù)制代碼
Vue.$delete 和 delete 的區(qū)別
Vue.$delete 是直接刪除了元素,改變了數(shù)組的長(zhǎng)度;delete 是將被刪除的元素變成內(nèi) undefined ,其他元素鍵值不變。
Vue.$set 如何解決對(duì)象新增屬性不能響應(yīng)的問題
Vue.$set的出現(xiàn)是由于Object.defineProperty的局限性:無法檢測(cè)對(duì)象屬性的新增或刪除。
源碼位置:vue/src/core/observer/index.js
export?function?set(target,?key,?val)?{
??//?數(shù)組
??if(Array.isArray(target)?&&?isValidArrayIndex(key))?{
????//?修改數(shù)組長(zhǎng)度,避免索引大于數(shù)組長(zhǎng)度導(dǎo)致splice錯(cuò)誤
????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]?=?val
????return?val
??}
??const?ob?=?target.__ob__
??//?target?不是響應(yīng)式數(shù)據(jù),直接賦值
??if(!ob)?{
????target[key]?=?val
????return?val
??}
??//?響應(yīng)式處理屬性
??defineReactive(ob.value,?key,?val)
??//?派發(fā)更新
??ob.dep.notify()
??return?val
}
復(fù)制代碼
實(shí)現(xiàn)原理:
若是數(shù)組,直接使用數(shù)組的 splice 方法觸發(fā)響應(yīng)式。 若是對(duì)象,判斷屬性是否存在,對(duì)象是否是響應(yīng)式。 以上都不滿足,最后通過 defineReactive 對(duì)屬性進(jìn)行響應(yīng)式處理。
Vue 異步更新機(jī)制
Vue 異步更新機(jī)制核心是利用瀏覽器的異步任務(wù)隊(duì)列實(shí)現(xiàn)的。
當(dāng)響應(yīng)式數(shù)據(jù)更新后,會(huì)觸發(fā) dep.notify 通知所有的 watcher 執(zhí)行 update 方法。
dep 類的 notify 方法
notify()?{
??//?獲取所有的?watcher
??const?subs?=?this.subs.slice()
??//?遍歷?dep?中存儲(chǔ)的?watcher,執(zhí)行?watcher.update
??for(let?i?=?0;?i?????subs[i].update()
??}
}
復(fù)制代碼
watcher.update 將自身放入全局的 watcher 隊(duì)列,等待執(zhí)行。
watcher 類的 update 方法
update()?{
??if(this.lazy)?{
????//?懶執(zhí)行走當(dāng)前?if?分支,如?computed
????//?這里的?標(biāo)識(shí)?主要用于?computed?緩存復(fù)用邏輯
????this.dirty?=?true
??}?else?if(this.sync)?{
????//?同步執(zhí)行,在?watch?選項(xiàng)參數(shù)傳?sync?時(shí),走當(dāng)前分支
????//?若為?true?,直接執(zhí)行?watcher.run(),不塞入異步更新隊(duì)列
????this.run()
??}?else?{
????//?正常更新走當(dāng)前?else?分支
????queueWatcher(this)
??}
}
復(fù)制代碼
queueWatcher 方法,發(fā)現(xiàn)熟悉的 nextTick 方法。看到這可以先跳到nextTick的原理,看明白了再折返。??
function?queueWatcher(watcher)?{
??const?id?=?watcher.id
??//?根據(jù)?watcher?id?判斷是否在隊(duì)列中,若在隊(duì)列中,不重復(fù)入隊(duì)?
??if?(has[id]?==?null)?{
????has[id]?=?true
????//?全局?queue?隊(duì)列未處于刷新狀態(tài),watcher?可入隊(duì)
????if?(!flushing)?{
??????queue.push(watcher)
????//?全局?queue?隊(duì)列處于刷新狀態(tài)
????//?在單調(diào)遞增序列尋找當(dāng)前?id?的位置并進(jìn)行插入操作
????}?else?{
??????let?i?=?queue.length?-?1
??????while?(i?>?index?&&?queue[i].id?>?watcher.id)?{
????????i--
??????}
??????queue.splice(i?+?1,?0,?watcher)
????}
???
????if?(!waiting)?{
??????waiting?=?true
??????//?同步執(zhí)行邏輯
??????if?(process.env.NODE_ENV?!==?'production'?&&?!config.async)?{
????????flushSchedulerQueue()
????????return
??????}
??????//?將回調(diào)函數(shù)?flushSchedulerQueue?放入?callbacks?數(shù)組
??????nextTick(flushSchedulerQueue)
????}
??}
}
復(fù)制代碼
nextTick 函數(shù)最終其實(shí)是執(zhí)行 flushCallbacks 函數(shù),flushCallbacks 函數(shù)則是運(yùn)行 flushSchedulerQueue 回調(diào)和項(xiàng)目中調(diào)用 nextTick 函數(shù)傳入的回調(diào)。
搬運(yùn) flushSchedulerQueue 源碼看做了些什么
/**
*??更新?flushing?為?true,表示正在刷新隊(duì)列,在此期間加入的?watcher?必須有序插入隊(duì)列,保證單調(diào)遞增
*??按照隊(duì)列的?watcher.id?從小到大排序,保證先創(chuàng)建的先執(zhí)行
*??遍歷?watcher?隊(duì)列,按序執(zhí)行?watcher.before?和?watcher.run,最后清除緩存的?watcher
*/
function?flushSchedulerQueue?()?{
??currentFlushTimestamp?=?getNow()
??//?標(biāo)識(shí)正在刷新隊(duì)列
??flushing?=?true
??let?watcher,?id
??queue.sort((a,?b)?=>?a.id?-?b.id)
??//?未緩存長(zhǎng)度是因?yàn)榭赡茉趫?zhí)行?watcher?時(shí)加入?watcher
??for?(index?=?0;?index?????watcher?=?queue[index]
????if?(watcher.before)?{
??????watcher.before()
????}
????id?=?watcher.id
????//?清除緩存的?watcher
????has[id]?=?null
????//?觸發(fā)更新函數(shù),如?updateComponent?或?執(zhí)行用戶的?watch?回調(diào)
????watcher.run()
??}
??
??const?activatedQueue?=?activatedChildren.slice()
??const?updatedQueue?=?queue.slice()
??
??//?執(zhí)行?waiting?=?flushing?=?false,標(biāo)識(shí)刷新隊(duì)列結(jié)束,可以向?yàn)g覽器的任務(wù)隊(duì)列加入下一個(gè)?flushCallbacks
??resetSchedulerState()
?
??callActivatedHooks(activatedQueue)
??callUpdatedHooks(updatedQueue)
??if?(devtools?&&?config.devtools)?{
????devtools.emit('flush')
??}
}
復(fù)制代碼
查看下 watcher.run 做了些什么,首先調(diào)用了 get 函數(shù),我們一起看下。
/**
*??執(zhí)行實(shí)例化?watcher?傳遞的第二個(gè)參數(shù),如?updateComponent
*??更新舊值為新值
*??執(zhí)行實(shí)例化?watcher?時(shí)傳遞的第三個(gè)參數(shù),用戶傳遞的?watcher?回調(diào)
*/
run?()?{
??if?(this.active)?{
????//?調(diào)用?get
????const?value?=?this.get()
????if?(
??????value?!==?this.value?||
??????isObject(value)?||
??????this.deep
????)?{
??????//?更新舊值為新值
??????const?oldValue?=?this.value
??????this.value?=?value
??????//?若是項(xiàng)目傳入的 watcher,則執(zhí)行實(shí)例化傳遞的回調(diào)函數(shù)。
??????if?(this.user)?{
????????const?info?=?`callback?for?watcher?"${this.expression}"`
????????invokeWithErrorHandling(this.cb,?this.vm,?[value,?oldValue],?this.vm,?info)
??????}?else?{
????????this.cb.call(this.vm,?value,?oldValue)
??????}
????}
??}
}
/**
*?執(zhí)行 this.getter,并重新收集依賴。
*?重新收集依賴是因?yàn)橛|發(fā)更新 setter 中只做了響應(yīng)式觀測(cè),但沒有收集依賴的操作。
*?所以,在更新頁(yè)面時(shí),會(huì)重新執(zhí)行一次 render 函數(shù),執(zhí)行期間會(huì)觸發(fā)讀取操作,這時(shí)進(jìn)行依賴收集。
*/
get?()?{
??//?Dep.target?=?this
??pushTarget(this)
??let?value
??const?vm?=?this.vm
??try?{
????//?執(zhí)行回調(diào)函數(shù),如?updateComponent,進(jìn)入?patch?階段
????value?=?this.getter.call(vm,?vm)
??}?catch?(e)?{
????if?(this.user)?{
??????handleError(e,?vm,?`getter?for?watcher?"${this.expression}"`)
????}?else?{
??????throw?e
????}
??}?finally?{
????//?watch?參數(shù)為?deep?的情況
????if?(this.deep)?{
??????traverse(value)
????}
????//?關(guān)閉?Dep.target?置空
????popTarget()
????this.cleanupDeps()
??}
??return?value
}
復(fù)制代碼
Vue.$nextTick 的原理
nextTick:在下次 DOM 更新循環(huán)結(jié)束之后執(zhí)行延遲回調(diào)。常用于修改數(shù)據(jù)后獲取更新后的DOM。
源碼位置:vue/src/core/util/next-tick.js
import?{?noop?}?from?'shared/util'
import?{?handleError?}?from?'./error'
import?{?isIE,?isIOS,?isNative?}?from?'./env'
//?是否使用微任務(wù)標(biāo)識(shí)
export?let?isUsingMicroTask?=?false
//?回調(diào)函數(shù)隊(duì)列
const?callbacks?=?[]
//?異步鎖
let?pending?=?false
function?flushCallbacks?()?{
??//?表示下一個(gè)?flushCallbacks?可以進(jìn)入瀏覽器的任務(wù)隊(duì)列了
??pending?=?false
??//?防止?nextTick?中包含?nextTick時(shí)出現(xiàn)問題,在執(zhí)行回調(diào)函數(shù)隊(duì)列前,提前復(fù)制備份,清空回調(diào)函數(shù)隊(duì)列
??const?copies?=?callbacks.slice(0)
??//?清空?callbacks?數(shù)組
??callbacks.length?=?0
??for?(let?i?=?0;?i?????copies[i]()
??}
}
let?timerFunc
//?瀏覽器能力檢測(cè)
//?使用宏任務(wù)或微任務(wù)的目的是宏任務(wù)和微任務(wù)必在同步代碼結(jié)束之后執(zhí)行,這時(shí)能保證是最終渲染好的DOM。
//?宏任務(wù)耗費(fèi)時(shí)間是大于微任務(wù),在瀏覽器支持的情況下,優(yōu)先使用微任務(wù)。
//?宏任務(wù)中效率也有差距,最低的就是?setTimeout
if?(typeof?Promise?!==?'undefined'?&&?isNative(Promise))?{
??const?p?=?Promise.resolve()
??timerFunc?=?()?=>?{
????p.then(flushCallbacks)
????if?(isIOS)?setTimeout(noop)
??}
??isUsingMicroTask?=?true
}?else?if?(!isIE?&&?typeof?MutationObserver?!==?'undefined'?&&?(
??isNative(MutationObserver)?||
??MutationObserver.toString()?===?'[object?MutationObserverConstructor]'
))?{
??let?counter?=?1
??const?observer?=?new?MutationObserver(flushCallbacks)
??const?textNode?=?document.createTextNode(String(counter))
??observer.observe(textNode,?{
????characterData:?true
??})
??timerFunc?=?()?=>?{
????counter?=?(counter?+?1)?%?2
????textNode.data?=?String(counter)
??}
??isUsingMicroTask?=?true
}?else?if?(typeof?setImmediate?!==?'undefined'?&&?isNative(setImmediate))?{
??timerFunc?=?()?=>?{
????setImmediate(flushCallbacks)
??}
}?else?{
??timerFunc?=?()?=>?{
????setTimeout(flushCallbacks,?0)
??}
}
export?function?nextTick?(cb?:?Function,?ctx?:?Object)?{
??let?_resolve
??//?將?nextTick?的回調(diào)函數(shù)用?try?catch?包裹一層,用于異常捕獲
??//?將包裹后的函數(shù)放到?callback?中
??callbacks.push(()?=>?{
????if?(cb)?{
??????try?{
????????cb.call(ctx)
??????}?catch?(e)?{
????????handleError(e,?ctx,?'nextTick')
??????}
????}?else?if?(_resolve)?{
??????_resolve(ctx)
????}
??})
??//?pengding?為?false,?執(zhí)行?timerFunc
??if?(!pending)?{
????//?關(guān)上鎖
????pending?=?true
????timerFunc()
??}
??if?(!cb?&&?typeof?Promise?!==?'undefined')?{
????return?new?Promise(resolve?=>?{
??????_resolve?=?resolve
????})
??}
}
復(fù)制代碼
總結(jié):
運(yùn)用異步鎖的概念,保證同一時(shí)刻任務(wù)隊(duì)列中只有一個(gè) flushCallbacks。當(dāng) pengding 為 false 的時(shí)候,表示瀏覽器任務(wù)隊(duì)列中沒有 flushCallbacks 函數(shù);當(dāng) pengding 為 true 的時(shí)候,表示瀏覽器任務(wù)隊(duì)列中已經(jīng)放入 flushCallbacks;待執(zhí)行 flushCallback 函數(shù)時(shí),pengding 會(huì)被再次置為 false,表示下一個(gè) flushCallbacks 可進(jìn)入任務(wù)隊(duì)列。 環(huán)境能力檢測(cè),選擇可選中效率最高的(宏任務(wù)/微任務(wù))進(jìn)行包裝執(zhí)行,保證是在同步代碼都執(zhí)行完成后再去執(zhí)行修改 DOM 等操作。 flushCallbacks 先拷貝再清空,為了防止nextTick嵌套nextTick導(dǎo)致循環(huán)不結(jié)束。
實(shí)現(xiàn)虛擬 DOM
虛擬 DOM 的出現(xiàn)解決了瀏覽器的性能問題。虛擬 DOM 是一個(gè)用 JS 模擬的 DOM 結(jié)構(gòu)對(duì)象(Vnode),用于頻繁更改 DOM 操作后不立即更新 DOM,而是對(duì)比新老 Vnode,更新獲取最新的Vnode,最后再一次性映射成真實(shí)的 DOM。這樣做的原因是操作內(nèi)存中操作 JS 對(duì)象速度比操作 DOM 快很多。
舉個(gè)真實(shí) DOM 的 ??
<div?id="container">
??<p>real?dom?p>
??<ul>
????<li?class="item">item?1li>
????<li?class="item">item?2li>
????<li?class="item">item?3li>
??ul>
div
復(fù)制代碼
用 JS 來模擬 DOM 節(jié)點(diǎn)實(shí)現(xiàn)虛擬 DOM
function?Element(tagName,?props,?children)?{
??this.tageName?=?tagName
??this.props?=?props?||?{}
??this.children?=?children?||?[]
??this.key?=?props.key
??let?count?=?0
??this.children.forEach(child?=>?{
????if(child?instanceof?Element)?count?+=?child.count
????count++
??})
??this.count?=?count
}
const?tree?=?Element('div',?{?id:?container?},?[
??Element('p',?{},?['real?dom'])
??Element('ul',?{},?[
????Element('li',?{?class:?'item'?},?['item1']),
????Element('li',?{?class:?'item'?},?['item2']),
????Element('li',?{?class:?'item'?},?['item3'])
??])
])
復(fù)制代碼
虛擬 DOM 轉(zhuǎn)為真實(shí)的節(jié)點(diǎn)
Element.prototype.render?=?function()?{
??let?el?=?document.createElement(this.tagName)
??let?props?=?this.props
??for(let?key?in?props)?{
????el.setAttribute(key,?props[key])
??}
??let?children?=?this.children?||?[]
??children.forEach(child?=>?{
????let?child?=?(child?instanceof?Element)???child.render()?:?document.createTextNode(child)
????el.appendChild(child)
??})
??return?el
}
復(fù)制代碼
Vue 中 Diff 的原理
核心源碼:vue/src/core/vdom/patch.js
搬運(yùn)對(duì)比新老節(jié)點(diǎn) patch 函數(shù)入口
/**
*?新節(jié)點(diǎn)不存在,老節(jié)點(diǎn)存在,調(diào)用?destroy,銷毀老節(jié)點(diǎn)
*?如果?oldVnode?是真實(shí)元素,則表示首次渲染,創(chuàng)建新節(jié)點(diǎn),并插入?body,然后移除來節(jié)點(diǎn)
*?如果?oldVnode?不是真實(shí)元素,則表示更新階段,執(zhí)行patchVnode
*/
function?patch(oldVnode,?vnode)?{
??//?新的?Vnode?不存在,老的?Vnode?存在,銷毀老節(jié)點(diǎn)
??if(isUndef(vnode))?{
????if(isDef(oldVnode))?invokeDestroyHook(oldVnode)?
????return?
??}
??
??//?新的?Vnode?存在,老的?Vnode?不存在
??//?
??//?這里的?com?組件初次渲染就走當(dāng)前的?if?邏輯
??if(isUndef(oldVnode))?{?
????isInitialPatch?=?true
????createElm(vnode,?insertedVnodeQueue)
??}?else?{
??????const?isRealElement?=?isDef(oldVnode.nodeType)
??????//?新老節(jié)點(diǎn)相同,更精細(xì)化對(duì)比
??????if(!isRealElement?&&?sameVnode(oldVnode,?vnode))?{
????????patchVnode(oldVnode,?vnode)?
??????}?else?{
????????//?是真實(shí)元素,渲染根組件
????????if(isRealElement)?{?
??????????//?掛載到真實(shí)元素以及處理服務(wù)端渲染情況
??????????if?(oldVnode.nodeType?===?1?&&?oldVnode.hasAttribute(SSR_ATTR))?{
????????????oldVnode.removeAttribute(SSR_ATTR)
????????????hydrating?=?true
??????????}
??????????if?(isTrue(hydrating))?{
????????????if?(hydrate(oldVnode,?vnode,?insertedVnodeQueue))?{
??????????????invokeInsertHook(vnode,?insertedVnodeQueue,?true)
??????????????return?oldVnode
????????????}?else?if?(process.env.NODE_ENV?!==?'production')?{
??????????????warn(
????????????????'The?client-side?rendered?virtual?DOM?tree?is?not?matching?'?+
????????????????'server-rendered?content.?This?is?likely?caused?by?incorrect?'?+
????????????????'HTML?markup,?for?example?nesting?block-level?elements?inside?'?+
????????????????',?or?missing?
.?Bailing?hydration?and?performing?'?+
????????????????'full?client-side?render.'
??????????????)
????????????}
??????????}
??????????//?基于真實(shí)節(jié)點(diǎn)創(chuàng)建一個(gè)?vnode
??????????oldVnode?=?emptyNodeAt(oldVnode)
???????}
???????//?獲取老節(jié)點(diǎn)的真實(shí)元素
???????const?oldElm?=?oldVnode.elm
???????//?獲取老節(jié)點(diǎn)的父元素,即?body
???????const?parentElm?=?nodeOps.parentNode(oldElm)
???????
???????//?基于新的?vnode?創(chuàng)建整顆?DOM?樹并插入到?body?元素下
???????creatElm(
?????????vnode,?
?????????insertedVnodeQueue,?
?????????oldElm._leaveCb???null?:?parentElm,?
?????????nodeOps.nextSibling(oldElm)
???????)
???????
???????//?遞歸更新父占位符節(jié)點(diǎn)元素
???????if(isDef(vnode.parent))?{
?????????...
???????}
???????
???????//?移除老節(jié)點(diǎn)
???????if(isDef(parentEle))?{
?????????...
???????}?else?if(isDef(oldVnode.tag))?{
?????????...
???????}
????}
??}
}
復(fù)制代碼
搬運(yùn) patchVnode 部分源碼。
/**
*?更新節(jié)點(diǎn)
*?如果新老節(jié)點(diǎn)都有孩子,則遞歸執(zhí)行?updateChildren
*?如果新節(jié)點(diǎn)有孩子,老節(jié)點(diǎn)沒孩子,則新增新節(jié)點(diǎn)的這些孩子節(jié)點(diǎn)
*?如果老節(jié)點(diǎn)有孩子,新節(jié)點(diǎn)沒孩子,則刪除老節(jié)點(diǎn)這些孩子
*?更新文本節(jié)點(diǎn)
*/
function?patchVnode(oldVnode,?vnode)?{
??//?如果新老節(jié)點(diǎn)相同,直接返回???
??if(oldVnode?===?vnode)?return?
??
??//?獲取新老節(jié)點(diǎn)的孩子節(jié)點(diǎn)
??const?oldCh?=?oldVnode.children
??const?ch?=?vnode.children
??
??//?新節(jié)點(diǎn)不是文本節(jié)點(diǎn)
??if(isUndef(vnode.text))?{
????//?新老節(jié)點(diǎn)都有孩子,則遞歸執(zhí)行?updateChildren
????if(isDef(oldCh)?&&?isDef(ch)?&&?oldCh?!==?ch)?{?//?oldVnode?與?vnode?的?children?不一致,更新children
??????updateChildren(oldCh,ch)
????//?如果新節(jié)點(diǎn)有孩子,老節(jié)點(diǎn)沒孩子,則新增新節(jié)點(diǎn)的這些孩子節(jié)點(diǎn)
????}?else?if(isDef(ch))?{?
??????if?(isDef(oldVnode.text))?nodeOps.setTextContent(elm,?'')
??????addVnodes(elm,?null,?ch,?0,?ch.length?-?1,?insertedVnodeQueue)
????//?如果老節(jié)點(diǎn)有孩子,新節(jié)點(diǎn)沒孩子,則刪除老節(jié)點(diǎn)這些孩子
????}?else?if(isDef(oldCh))?{?
??????removeVnodes(oldCh,?0,?oldCh.length?-?1)
????//?老節(jié)點(diǎn)文本存在,新的節(jié)點(diǎn)不存在文本,清空文本?
????}?else?if(isDef(oldVnode.text)){
??????nodeOps.setTextContent(elm,?'')
????}
??//?新老文本節(jié)點(diǎn)都是文本節(jié)點(diǎn),且文本發(fā)生改變,則更新文本節(jié)點(diǎn)
??}?else?if(oldVnode.text?!==?vnode.text)?{?
????nodeOps.setTextContent(elm,?vnode.text)
??}
}
復(fù)制代碼
搬運(yùn) updateChildren 源碼。
function?updateChildren(oldCh,?ch)?{
???//?const?oldCh?=?[n1,?n2,?n3,?n4]
???//?const?ch?=?[n1,?n2,?n3,?n4,?n5]
???//?舊節(jié)點(diǎn)起始索引
???let?oldStartIdx?=?0?
???//?新節(jié)點(diǎn)起始索引
???let?newStartIdx?=?0?
???//?舊節(jié)點(diǎn)結(jié)束索引
???let?oldEndIdx?=?oldCh.length?-?1?
???//?新節(jié)點(diǎn)結(jié)束索引
???let?newEndIdx?=?newCh.length?-?1?
???while(oldStartIdx?<=?oldEndIdx?&&?newStartIdx?<=?newEndIdx)?{
?????const?newStartVnode?=?ch[newStartIdx]
?????const?oldStartVnode?=?oldCh[oldStartIdx]
?????const?newEndVnode?=?ch[newEndIdx]
?????const?oldEndVnode?=?oldCh[oldEndIdx]
?????//?如果節(jié)點(diǎn)被移動(dòng),在當(dāng)前索引上可能不存在,檢測(cè)這種情況,如果節(jié)點(diǎn)不存在則調(diào)整索引
?????if(isUndef(oldStartVnode))?{
???????oldStartVnode?=?oldCh[++oldStartIdx]
?????}?else?if(isUndef(oldEndVnode))?{
???????oldEndVnode?=?oldCh[--oldEndIdx]
?????//?新開始和老開始節(jié)點(diǎn)是同一個(gè)節(jié)點(diǎn)
?????}?else?if(sameVnode(oldStartNode,?newStartNode))?{?
???????patchVnode(oldStartNode?,?newStartNode)
???????oldStartIdx++
???????newStartIdx++
?????//?新開始節(jié)點(diǎn)和老結(jié)束節(jié)點(diǎn)是同一節(jié)點(diǎn)
?????}?else?if(sameVnode(oldEndNode,?newEndNode))?{?
???????patchVnode(oldEndNode,?newEndNode)
???????oldEndIdx--
???????newEndIdx--
?????//?老開始和新結(jié)束是同一節(jié)點(diǎn)
?????}?else?if(sameVnode(oldStartNode,?newEndNode))?{?
???????patchVnode(oldStartNode,?newEndNode)
???????oldStartIdx++
???????newEndIdx--
?????//?老結(jié)束和新開始是同一節(jié)點(diǎn)
?????}?else?if(sameVnode(oldEndNode,?newStartNode))?{?
???????patchVnode(oldEndNode,?newStartNode)
???????oldEndIdx--
???????newStartIdx++
?????}?else?{
???????//?上面假設(shè)都不成立,則通過遍歷找到新開始節(jié)點(diǎn)和老節(jié)點(diǎn)中的索引位置
???????
???????//?創(chuàng)建老節(jié)點(diǎn)每個(gè)節(jié)點(diǎn)?key?和?索引的關(guān)系?{?key:?idx?}
???????if?(isUndef(oldKeyToIdx))?oldKeyToIdx?=?createKeyToOldIdx(oldCh,?oldStartIdx,?oldEndIdx)
???????//?尋找新開始節(jié)點(diǎn)在老節(jié)點(diǎn)的索引位置
???????idxInOld?=?isDef(newStartVnode.key)
???????????oldKeyToIdx[newStartVnode.key]
?????????:?findIdxInOld(newStartVnode,?oldCh,?oldStartIdx,?oldEndIdx)
???????
???????//?沒有找到,則說明是新創(chuàng)建的元素,執(zhí)行創(chuàng)建
???????if?(isUndef(idxInOld))?{?
??????????createElm(newStartVnode,?insertedVnodeQueue,?parentElm,?oldStartVnode.elm,?false,?newCh,?newStartIdx)
????????}?else?{
??????????//?在關(guān)系映射表中找到新開始節(jié)點(diǎn)
??????????vnodeToMove?=?oldCh[idxInOld]
??????????//?如果是同一個(gè)節(jié)點(diǎn),則執(zhí)行patch
??????????if?(sameVnode(vnodeToMove,?newStartVnode))?{
????????????patchVnode(vnodeToMove,?newStartVnode,?insertedVnodeQueue,?newCh,?newStartIdx)
????????????//?patch?結(jié)束后將老節(jié)點(diǎn)置為?undefined
????????????oldCh[idxInOld]?=?undefined
????????????canMove?&&?nodeOps.insertBefore(parentElm,?vnodeToMove.elm,?oldStartVnode.elm)
??????????}?else?{
??????????//?最后這種情況是,找到節(jié)點(diǎn),但發(fā)現(xiàn)兩個(gè)節(jié)點(diǎn)不是同一個(gè)節(jié)點(diǎn),則視為新元素,執(zhí)行創(chuàng)建
????????????createElm(newStartVnode,?insertedVnodeQueue,?parentElm,?oldStartVnode.elm,?false,?newCh,?newStartIdx)
??????????}
????????}
????????//?新節(jié)點(diǎn)向后移動(dòng)一位
????????newStartVnode?=?newCh[++newStartIdx]
?????}
?????if(newStartIdx?//?舊節(jié)點(diǎn)先遍歷結(jié)束,將剩余的新節(jié)點(diǎn)添加到DOM中
?????if(oldStartIdx?//?新節(jié)點(diǎn)先遍歷結(jié)束,將剩余的舊節(jié)點(diǎn)刪掉
???}
}
復(fù)制代碼
Vue 中的 key 的作用
key 是 Vue 中 vnode 的唯一標(biāo)記,我們的 diff 的算法中 sameVnode 和 updateChildren 中就使用到了 key。
sameVnode 用來判斷是否為同一節(jié)點(diǎn)。常見的業(yè)務(wù)場(chǎng)景是一個(gè)列表,若 key 值是列表索引,在新增或刪除的情況下會(huì)存在就地復(fù)用的問題。(簡(jiǎn)單說,復(fù)用了上一個(gè)在當(dāng)前位置元素的狀態(tài))所以 key 值的唯一,確保 diff 更準(zhǔn)確。
updateChildren 中當(dāng)其中四種假設(shè)都未匹配,就需要依賴?yán)瞎?jié)點(diǎn)的 key 和 索引創(chuàng)建關(guān)系映射表,再用新節(jié)點(diǎn)的 key 去關(guān)系映射表去尋找索引進(jìn)行更新,這保證 diff 算法更加快速。
Vue 動(dòng)態(tài)組件是什么
動(dòng)態(tài)組件通過is特性實(shí)現(xiàn)。適用于根據(jù)數(shù)據(jù)、動(dòng)態(tài)渲染的場(chǎng)景,即組件類型不確定。
舉個(gè)新聞詳情頁(yè)案例,如下圖所示。

未命名文件 (1).png 但是每篇新聞的詳情頁(yè)組件順序可能是不一樣的,所以我們得通過數(shù)據(jù)來動(dòng)態(tài)渲染組件,而非寫死每個(gè)組件的順序。
<template>
??<div?v-for="val?in?componentsData"?:key="val.id">
????<component?:is="val.type">
??div>
template>
<script>
import?CustomTitle?from?'./CustomTitle'
import?CustomText?from?'./CustomText'
import?CustomImage?from?'./CustomImage'
export?default?{
??data()?{
????return?{
??????componentsData:?[{
????????id:?1,
????????type:?'CustomTitle'
??????},{
????????id:?2,
????????type:?'CustomText'
??????},{
????????id:?3
????????type:?'CustomImage'
??????}]
????}
??}
}
script>
復(fù)制代碼
Vue.directive 有寫過么,應(yīng)用場(chǎng)景有哪些?
Vue.directive 可以注冊(cè)全局指令和局部指令。
指令定義函數(shù)提供如下鉤子函數(shù)
bind:指令第一次綁定到元素時(shí)調(diào)用(只調(diào)用一次) inserted: 被綁定元素插入父節(jié)點(diǎn)時(shí)使用(父節(jié)點(diǎn)存在即可調(diào)用) update:被綁定元素所在模板更新時(shí)調(diào)用,不論綁定值是否變化。通過比較更新前后的綁定值。 componentUpdated: 被綁定元素所在模板完成一次更新周期時(shí)調(diào)用。 unbind: 只調(diào)用一次,指令與元素解綁時(shí)調(diào)用。
我項(xiàng)目中有涉及 一鍵copy、權(quán)限控制 都可以用指令的方式控制,目的就是簡(jiǎn)化我們的工作量。
推薦一篇 分享8個(gè)非常實(shí)用的Vue自定義指令[5] 。??
Vue 過濾器了解么
Vue 過濾器可用在兩個(gè)地方:雙花括號(hào)插值和 v-bind 表達(dá)式。
Vue3 中已經(jīng)廢棄這個(gè)特點(diǎn)。
過濾器分為 全局過濾器 和 局部過濾器。
局部過濾器
<template>
??<div>{{?message?|?formatMessage?}}div>
template>
<script>
export?default?{
??filters:?{
????formatMessage:?function(value)?{
??????//?可基于源值做一些處理
??????return?value
????}
??}
}
script>
復(fù)制代碼
全局過濾器
Vue.filter('formatMessage',?function(value)?{
??//?可基于源值做一些處理
??return?value
})
復(fù)制代碼
過濾器可串聯(lián),執(zhí)行順序從左到右,第二個(gè)過濾器輸入值是第一個(gè)過濾器的輸出值。
{{?message?|?formatMessage1?|?formatMessage2?}}
復(fù)制代碼
關(guān)于 mixin 的理解,有什么應(yīng)用場(chǎng)景
定義:mixin(混入),提供了一種非常靈活的方式,來分發(fā) Vue 組件中可復(fù)用的功能。
mixin 混入分全局混入和局部混入,本質(zhì)是 JS 對(duì)象,如 data、components、computed、methods 等。
全局混入不推薦使用,會(huì)影響后續(xù)每個(gè)Vue實(shí)例的創(chuàng)建。局部混入可提取組件間相同的代碼,進(jìn)行邏輯復(fù)用。
適用場(chǎng)景:如多個(gè)頁(yè)面具備相同的懸浮定位浮窗,可嘗試用 mixin 封裝。
//?customFloatDialog.js
export?const?customFloatDialog?=?{
??data()?{
????return?{
??????visible:?false
????}
??},
??methods:?{
????toggleShow()?{
??????this.visible?=?!this.visible
????}
??}
}
</script>
//需要引入的組件
??div>
</template>
復(fù)制代碼
介紹一下 keep-alive
keep-alive 是 Vue 內(nèi)置的一個(gè)組件,可以緩存組件的狀態(tài),避免重復(fù)渲染,提高性能。
keep-alive 內(nèi)置組件有3個(gè)屬性
include:字符串或正則表達(dá)式,名稱匹配的組件會(huì)被緩存。 exclude:字符串或正則表達(dá)式,名稱匹配的組件不會(huì)被緩存。 max:緩存組件數(shù)量閾值
設(shè)置 keep-alive 的組件,會(huì)增加兩個(gè)生命鉤子(activated / deactivated)。
首次進(jìn)入組件:beforeCreate -> created -> beforeMount -> mounted -> activated
離開組件觸發(fā)deactivated,因?yàn)榻M件緩存不銷毀,所以不會(huì)觸發(fā) beforeDestroy 和 destroyed 生命鉤子。再次進(jìn)入組件后直接從 activated 生命鉤子開始。
常見業(yè)務(wù)場(chǎng)景:在列表頁(yè)的第 2 頁(yè)進(jìn)入詳情頁(yè),詳情頁(yè)返回,依然停留在第 2 頁(yè),不重新渲染。但從其他頁(yè)面進(jìn)入列表頁(yè),還是需要重新渲染。
思路:vuex 使用數(shù)組存儲(chǔ)列表頁(yè)名字,列表頁(yè)離開結(jié)合 beforeRouteLeave 鉤子判斷是否需要緩存,對(duì)全局?jǐn)?shù)組進(jìn)行更改。
在 router-view 標(biāo)簽位置如下使用
<template>
??<keep-alive?:include="cacheRouting">
????<router-view>router-view>
??keep-alive>
template>
<script>
export?default?{
??computed:?{
????cacheRouting()?{
??????return?this.$store.state.cacheRouting
????}
??}
}
script>
復(fù)制代碼
列表頁(yè)如下使用
<template>
??<div>div>
template>
<script>
export?default?{
??beforeRouteLeave(to,?from,?next)?{
????if(to.name?===?'詳情頁(yè)')?{
??????//?...?向全局緩存路由數(shù)組添加列表頁(yè)??????
??????next()
????}?else?{
??????//?...?向全局緩存路由數(shù)組刪除列表頁(yè)?????
??????next()
????}
??}
}
script>
復(fù)制代碼
keep-alive 的實(shí)現(xiàn)
核心源碼:vue/src/core/components/keep-alive.js
LRU(Least Recently Used) 替換策略核心思想是替換最近最少使用。
/**
*?遍歷?cache?將不需要的緩存的從?cache?中清除
*/
function?pruneCache?(keepAliveInstance,?filter)?{
??const?{?cache,?keys,?_vnode?}?=?keepAliveInstance
??for?(const?key?in?cache)?{
????const?cachedNode?=?cache[key]
????if?(cachedNode)?{
??????const?name?=?getComponentName(cachedNode.componentOptions)
??????if?(name?&&?!filter(name))?{
????????pruneCacheEntry(cache,?key,?keys,?_vnode)
??????}
????}
??}
}
/**
*?刪除?cache?中鍵值為?key?的虛擬DOM
*/
function?pruneCacheEntry?(cache,?key,?keys,?current)?{
??const?entry?=?cache[key]
??if?(entry?&&?(!current?||?entry.tag?!==?current.tag))?{
????//?執(zhí)行組件的?destroy?鉤子
????entry.componentInstance.$destroy()
??}
??//?cache?中組件對(duì)應(yīng)的虛擬DOM置null
??cache[key]?=?null
??//?刪除緩存虛擬DOM的?key
??remove(keys,?key)
}
export?default?{
??name:?'keep-alive',
??abstract:?true,??
??props:?{
????include:?patternTypes,
????exclude:?patternTypes,
????max:?[String,?Number]
??},
??created?()?{
????//?緩存虛擬?DOM
????this.cache?=?Object.create(null)?
????//?緩存虛擬DOM的鍵集合
????this.keys?=?[]?
??},
??destroyed?()?{
????//?刪除所有的緩存內(nèi)容
????for?(const?key?in?this.cache)?{
??????pruneCacheEntry(this.cache,?key,?this.keys)
????}
??},
??mounted?()?{
????//?監(jiān)聽?include、exclude?參數(shù)變化,調(diào)用?pruneCache修改緩存中的緩存數(shù)據(jù)
????this.$watch('include',?val?=>?{
??????pruneCache(this,?name?=>?matches(val,?name))
????})
????this.$watch('exclude',?val?=>?{
??????pruneCache(this,?name?=>?!matches(val,?name))
????})
??},
??//?由?render?函數(shù)決定渲染結(jié)果
??render?()?{
????const?slot?=?this.$slots.default
????//?獲取第一個(gè)子組件虛擬DOM
????const?vnode:?VNode?=?getFirstComponentChild(slot)
????//?獲取虛擬?DOM?的配置參數(shù)
????const?componentOptions:???VNodeComponentOptions?=?vnode?&&?vnode.componentOptions
????if?(componentOptions)?{
??????//?獲取組件名稱
??????const?name:??string?=?getComponentName(componentOptions)
??????const?{?include,?exclude?}?=?this
??????//?若不在include或者在exclude中,直接退出,不走緩存機(jī)制
??????if?(
????????(include?&&?(!name?||?!matches(include,?name)))?||
????????(exclude?&&?name?&&?matches(exclude,?name))
??????)?{
????????return?vnode
??????}
??????const?{?cache,?keys?}?=?this
??????//?獲取組件key
??????const?key:??string?=?vnode.key?==?null
??????????componentOptions.Ctor.cid?+?(componentOptions.tag???`::${componentOptions.tag}`?:?'')
????????:?vnode.key
??????//?命中緩存
??????if?(cache[key])?{
????????//?從?cache?中獲取緩存的實(shí)例設(shè)置到當(dāng)前的組件上
????????vnode.componentInstance?=?cache[key].componentInstance
????????//?刪除原有存在的key,并置于最后
????????remove(keys,?key)
????????keys.push(key)
??????//?未命中緩存
??????}?else?{
????????//?緩存當(dāng)前虛擬節(jié)點(diǎn)
????????cache[key]?=?vnode
????????//?添加當(dāng)前組件key
????????keys.push(key)
????????//?若緩存組件超過max值,LRU?替換
????????if(this.max?&&?keys.length?>?parseInt(this.max))?{
??????????pruneCacheEntry(cache,?keys[0],?keys,?this._vnode)
????????}
??????}
??????//?設(shè)置當(dāng)前組件?keep-alive?為?true
??????vnode.data.keepAlive?=?true
????}
????return?vnode?||?(slot?&&?slot[0])
??}
}
復(fù)制代碼
Vue-Router 配置 404 頁(yè)面
* 代表通配符,若放在任意路由前,會(huì)被先匹配,導(dǎo)致跳轉(zhuǎn)到 404 頁(yè)面,所以需將如下配置置于最后。
{
??path:?'*',
??name:?'404'
??component:?()?=>?import('./404.vue')??
}
復(fù)制代碼
Vue-Router 有哪幾種導(dǎo)航守衛(wèi)
全局前置守衛(wèi)
在路由跳轉(zhuǎn)前觸發(fā),可在執(zhí)行 next 方法前做一些身份登錄驗(yàn)證的邏輯。
const?router?=?new?VueRouter({})
router.beforeEach((to,?from,?next)?=>?{
??...
??//?必須執(zhí)行?next?方法來觸發(fā)路由跳轉(zhuǎn)?
??next()?
})
復(fù)制代碼
全局解析守衛(wèi)
與 beforeEach 類似,也是路由跳轉(zhuǎn)前觸發(fā),區(qū)別是還需在所有組件內(nèi)守衛(wèi)和異步路由組件被解析之后,也就是在組件內(nèi) beforeRouteEnter 之后被調(diào)用。
const?router?=?new?VueRouter({})
router.beforeResolve((to,?from,?next)?=>?{
??...
??//?必須執(zhí)行?next?方法來觸發(fā)路由跳轉(zhuǎn)?
??next()?
})
復(fù)制代碼
全局后置鉤子
和守衛(wèi)不同的是,這些鉤子不會(huì)接受 next 函數(shù)也不會(huì)改變導(dǎo)航本身。
router.afterEach((to,?from)?=>?{
??//?...
})
復(fù)制代碼
路由獨(dú)享守衛(wèi)
可在路由配置上直接定義 beforeEnter
const?router?=?new?VueRouter({
??routes:?[
????{
??????path:?'/home',
??????component:?Home,
??????beforeEnter:?(to,?from,?next)?=>?{
??????
??????}
????}
??]
})
復(fù)制代碼
組件內(nèi)的守衛(wèi)
組件內(nèi)可直接定義如下路由導(dǎo)航守衛(wèi)
const?Foo?=?{
??template:?`...`,
??beforeRouteEnter(to,?from,?next)?{
????//?不能獲取組件實(shí)例?this
????//?當(dāng)守衛(wèi)執(zhí)行前,組件實(shí)例還沒被創(chuàng)建
??},
??beforeRouteUpdate(to,?from,?next)?{
????//?當(dāng)前路由改變,但是組件被復(fù)用時(shí)調(diào)用
????//?可訪問實(shí)例?this
??},
??beforeRouteLeave(to,?from,?next)?{
????//?導(dǎo)航離開組件時(shí)被調(diào)用
??}
}
復(fù)制代碼
Vue-Router 完整的導(dǎo)航解析流程
導(dǎo)航被觸發(fā) 在失活的組件里調(diào)用 beforeRouteLeave 守衛(wèi) 調(diào)用全局 beforeEach 前置守衛(wèi) 重用的組件調(diào)用 beforeRouteUpdate 守衛(wèi)(2.2+) 路由配置調(diào)用 beforeEnter 解析異步路由組件 在被激活的組件里調(diào)用 beforeRouteEnter 守衛(wèi) 調(diào)用全局的 beforeResolve 守衛(wèi)(2.5+) 導(dǎo)航被確認(rèn) 調(diào)用全局的 afterEach 觸發(fā) DOM 更新 調(diào)用? beforeRouteEnter?守衛(wèi)中傳給?next?的回調(diào)函數(shù),創(chuàng)建好的組件實(shí)例會(huì)作為回調(diào)函數(shù)的參數(shù)傳入
Vue-Router 路由有幾種模式?說說他們的區(qū)別?
Vue-Router 有 3 種路由模式:hash、history、abstract。
hash 模式
Vue-Router 默認(rèn)為 hash 模式,基于瀏覽器的onhashchange事件,地址變化時(shí),通過window.location.hash獲取地址上的hash值;根據(jù)hash值匹配 routes 對(duì)象對(duì)應(yīng)的組件內(nèi)容。
特點(diǎn)
hash值存在 URL 中,攜帶#,hash值改變不會(huì)重載頁(yè)面。 hash改變會(huì)觸發(fā)onhashchange事件,可被瀏覽器記錄,從而實(shí)現(xiàn)瀏覽器的前進(jìn)后退。 hash傳參基于url,傳遞復(fù)雜參數(shù)會(huì)有體積限制。 兼容性好,支持低版本瀏覽器和 IE 瀏覽器。
案例代碼,需在本地啟用服務(wù)(http-server)訪問, 已測(cè)試過,可直接 cv 體驗(yàn)。
實(shí)現(xiàn)原理
<div?class="main">
??<a?href="#/home">homea>
??<a?href="#/detail">detaila>
??<div?id="content"><span>暫無內(nèi)容span>div>
div>
<script>
??const?routers?=?[{
????path:?'/',
????component:?`<span>暫無內(nèi)容span>`
??},
??{
????path:?'/home',
????component:?`<span>我是Home頁(yè)面span>`
??},?{
????path:?'/detail',
????component:?`<span>我是Detail頁(yè)面span>`
??}]
??function?Router(routers)?{
????console.log('執(zhí)行')
????this.routers?=?{}
????//?初始化生成?routers
????routers.forEach((router)?=>?{
??????this.routers[router.path]?=?()?=>?{
????????document.getElementById("content").innerHTML?=?router.component;
??????}
????})??
????this.updateView?=?function(e)?{
??????let?hash?=?window.location.hash.slice(1)?||?'/'
??????console.log('hash更新',?hash,?this.routers[hash])
??????this.routers[hash]?&&?this.routers[hash]()
????}
????//?路由加載觸發(fā)視圖更新
????window.addEventListener('load',?this.updateView.bind(this))
????//?路由改變觸發(fā)視圖更新
????window.addEventListener('hashchange',?this.updateView.bind(this))
??}
??//?實(shí)例化?hash?模式的?Router
??let?router?=?new?Router(routers)?
scrip
復(fù)制代碼
history 模式
基于HTML5新增的 pushState 和 replaceState 實(shí)現(xiàn)在不刷新的情況下,操作瀏覽器的歷史紀(jì)錄。前者是新增歷史記錄,后者是直接替換歷史記錄。
特點(diǎn)
URL 不攜帶`#`,利用 pushState 和 replaceState 完成 URL 跳轉(zhuǎn)而無須重新加載頁(yè)面。
URL 更改會(huì)觸發(fā) http 請(qǐng)求。所以在服務(wù)端需增加一個(gè)覆蓋所有情況的候選資源:若URL匹配不到任何靜態(tài)資源,則應(yīng)該返回同一個(gè)`index.html`。這個(gè)頁(yè)面就是app依賴的頁(yè)面。
// nginx 服務(wù)端配置
location / {
try_files $uri $uri/ /index.html;
}
復(fù)制代碼
兼容性 IE10+
實(shí)現(xiàn)原理
<div?class="main">
??<a?href="javascript:;"?path="/home">homea>
??<a?href="javascript:;"?path="/detail">detaila>
??<div?id="content"><span>暫無內(nèi)容span>div>
div>
<script>
const?routers?=?[{
??path:?'/home',
??component:?`我是Home頁(yè)面`
},?{
??path:?'/detail',
??component:?`我是Detail頁(yè)面`
},?{
??path:?'/',
??component:?'暫無內(nèi)容'
}]
function?Router(routers)?{
??this.routers?=?{}
??//?初始化生成?routers
??routers.forEach((router)?=>?{
????this.routers[router.path]?=?()?=>?{
??????document.getElementById("content").innerHTML?=?router.component;
????}
??})
??const?links?=?[...document.getElementsByTagName('a')]
??links.forEach(link?=>?{
????link.addEventListener('click',?()?=>?{
??????window.history.pushState({},?null,?link.getAttribute('path'))
??????this.updateView()
????})
??})
??this.updateView?=?function()?{
????let?url?=?window.location.pathname?||?'/'
????this.routers[url]?&&?this.routers[url]()
??}
??//?路由加載觸發(fā)視圖更新
??window.addEventListener('load',?this.updateView.bind(this))
??//?路由改變觸發(fā)視圖更新
??window.addEventListener('popstate',?this.updateView.bind(this))
}
//?實(shí)例化?history?模式的?Router
const?router?=?new?Router(routers)
script>
復(fù)制代碼
abstract 模式
支持所有 JS 運(yùn)行模式,Vue-Router 自身會(huì)對(duì)環(huán)境做校驗(yàn),如果發(fā)現(xiàn)沒有瀏覽器 API,路由會(huì)自動(dòng)強(qiáng)制進(jìn)入 abstract 模式。在移動(dòng)端原生環(huán)境也是使用 abstract 模式。
Vue 路由傳參方式
Vue 路由有 三種 方式進(jìn)行傳參
方案一
//?路由配置
{
??path:?'/detail/:id',
??name:?'Detail',
??component:?()?=>?import('./Detail.vue')
}
//?路由跳轉(zhuǎn)
let?id?=?1
this.$router.push({?path:?'/detail/${id}'})
//?獲取參數(shù)
this.$route.params.id
復(fù)制代碼
方案二
方案二,URL 雖然不顯示我們的傳參,但是是可以在子組件獲取參數(shù)的。當(dāng)然也有問題:會(huì)存在刷新丟失參數(shù)。
若想不丟失,需和方案一路由配置一樣。原因是第二種方式傳參是上一個(gè)頁(yè)面 push 函數(shù)中攜帶的,刷新沒有 push 的動(dòng)作。
//?路由配置
{
??path:?'/detail',
??name:?'Detail',
??component:?()?=>?import('./Detail.vue')
}
//?路由跳轉(zhuǎn)
let?id?=?1
this.$router.push({?name:?'Detail',?params:?{?id:?id?}?})
//?獲取參數(shù)
this.$route.params.id
復(fù)制代碼
方案三
//?路由配置
{
??path:?'/detail',
??name:?'Detail',
??component:?()?=>?import('./Detail.vue')
}
//?路由跳轉(zhuǎn)
let?id?=?1
this.$router.push({?name:?'Detail',?query:?{?id:?id?}?})
//?獲取參數(shù)
this.$route.query.id
復(fù)制代碼
Vuex 的理解及使用
Vuex 是一個(gè)專為 Vue.js 應(yīng)用程序開發(fā)的狀態(tài)管理模式,采用集中式存儲(chǔ)管理應(yīng)用的所有組件的狀態(tài)。
主要解決如下 兩個(gè) 問題
多個(gè)視圖依賴同一狀態(tài)。 來自不同視圖的行為需要變更同一個(gè)狀態(tài)。
其包含如下模塊,搬官網(wǎng)圖

vuex.png State:定義并初始化全局狀態(tài)。
Getter: 依賴 State 中的狀態(tài),進(jìn)行二次包裝,不會(huì)影響 State 源數(shù)據(jù)。
Mutation: 更改 State 狀態(tài)的函數(shù),必須是同步。
Action:用于提交 Mutation,可包含任意異步操作。
Module:若應(yīng)用復(fù)雜,Store 會(huì)集中一個(gè)比較大的對(duì)象而顯得臃腫,Module允許我們將 Store模塊化管理。
當(dāng)然,若應(yīng)用比較簡(jiǎn)單,共享狀態(tài)也比較少,可以用 Vue.observe 去替代 Vuex,省去安裝一個(gè)庫(kù)也挺好。
Vuex 刷新后數(shù)據(jù)丟失怎么辦
持久化緩存:localStorage、sessionStorage
Vuex 如何知道 State 是通過 Mutation 修改還是外部修改?
Vuex 中修改 state 唯一渠道是執(zhí)行 commit 方法,底層通過執(zhí)行 this._withCommit(fn),且設(shè)置_committing標(biāo)識(shí)符為 true,才能修改 state,修改完還需要將標(biāo)識(shí)符置為 false。外部修改是無法設(shè)置標(biāo)識(shí)位的,所以通過 watch 監(jiān)聽 state 變化,來判斷修改的合法性。
Vue SSR 了解么
Vue SSR 項(xiàng)目中暫時(shí)還沒有運(yùn)用過,后續(xù)會(huì)寫個(gè) Demo 單獨(dú)成文吧。這邊搬運(yùn)下其他答案。
SSR 服務(wù)端渲染,將 HTML 渲染工作放在服務(wù)端完成后,將 HTML 返回到瀏覽器端。
優(yōu)點(diǎn):SSR有更好的 SEO,首屏加載更快。
缺點(diǎn):服務(wù)端負(fù)載大。
如果是內(nèi)部系統(tǒng),SSR其實(shí)沒有太多必要。如果是對(duì)外的項(xiàng)目,維護(hù)高可用的node服務(wù)器是個(gè)難點(diǎn)。
Vue2 與 Vue3 的區(qū)別 ?Vue3有哪些優(yōu)化點(diǎn)?
自產(chǎn)自銷:【持續(xù)更新】梳理 Vue3 相比于 Vue2 的有哪些 “與眾不同” ?[6]
Vue 性能優(yōu)化
非響應(yīng)式數(shù)據(jù)通過 Object.freeze 凍結(jié)數(shù)據(jù) 嵌套層級(jí)不要過深 computed 和 watch 區(qū)別使用 v-if 和 v-show 區(qū)別使用 v-for 避免和 v-if 一起使用,且綁定 key 值要唯一 列表數(shù)據(jù)過多采用分頁(yè)或者虛擬列表 組件銷毀后清除定時(shí)器和事件 圖片懶加載 路由懶加載 防抖、節(jié)流 按需引入外部庫(kù) keep-alive緩存使用 服務(wù)端渲染和預(yù)渲染
總結(jié)
萬(wàn)字長(zhǎng)文總結(jié),若覺得有幫助的,順帶的點(diǎn)贊、關(guān)注、收藏。??
[1]
https://juejin.cn/post/7008476801634680869
[2]
https://juejin.cn/post/7001860784586227720
[3]
https://juejin.cn/post/7012419511907254308
[4]
https://www.zhihu.com/question/419154194
[5]https://juejin.cn/post/6906028995133833230
[6]https://juejin.cn/post/7011372376969445413
關(guān)于本文
來源:花哨
https://juejin.cn/post/7023197006998978597
Node 社群
我組建了一個(gè)氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對(duì)Node.js學(xué)習(xí)感興趣的話(后續(xù)有計(jì)劃也可以),我們可以一起進(jìn)行Node.js相關(guān)的交流、學(xué)習(xí)、共建。下方加 考拉 好友回復(fù)「Node」即可。
???“分享、點(diǎn)贊、在看” 支持一波??
瀏覽
1評(píng)論圖片表情
欧美成人在线无码
|
伊人色图吧|
欧美草逼|
内射肉丝内射在线播放
|
爱草在线视频
|
