vue(前端框架)解決了什么問題?
現(xiàn)在的前端頁面元素越來越多,結(jié)構(gòu)也變得越來越復(fù)雜,當(dāng)數(shù)據(jù)和視圖混合在一起的時候?qū)λ鼈兊奶幚頃謴?fù)雜,同時也很容易出現(xiàn)錯誤,而現(xiàn)代框架使用聲明式語法,描述組件對象的嵌套關(guān)系,并自動生成與dom對象的對應(yīng)關(guān)系
vue生命周期
| vue生命周期 | 描述 |
|---|---|
| beforeCreate | 組件實力被創(chuàng)建,el和數(shù)據(jù)對象都為undefined,還未初始化 |
| create | 數(shù)據(jù)已經(jīng)被初始化,并且初始化了Vue內(nèi)部事件,但是DOM還未生成 |
| befroeMount | 完成了模板的編譯。把data對象里面的數(shù)據(jù)和vue的語法寫的模板編譯成了虛擬DOM |
| mouted | 執(zhí)行了render函數(shù),將渲染出來的內(nèi)容掛載到了DOM節(jié)點上 |
| beforeUpdate | 組件更新之前:數(shù)據(jù)發(fā)生變化時,會調(diào)用beforeUpdate,然后經(jīng)歷DOM diff |
| updated | 組件更新后 |
| actived |
keep-alive組件被激活 |
| deactivated |
keep-alive移除 |
| beforeDestroy | 組件銷毀前 |
| destroyed | 組件銷毀后 |
簡述Vue的響應(yīng)式原理
可以問數(shù)據(jù)變動如何和視圖聯(lián)系在一起?
Vue是采用數(shù)據(jù)劫持結(jié)合發(fā)布者-訂閱者模式的方式, Vue相應(yīng)系統(tǒng)有三大核心:observe,dep,watcher;精簡版Vue代碼參考
-
Observe:當(dāng)一個Vue實例創(chuàng)建時,initData階段,vue會遍歷data選項的屬性(observe),用 Object.defineProperty 將它們轉(zhuǎn)為 getter/setter并且在內(nèi)部追蹤相關(guān)依賴(dep),在屬性被訪問和修改時通知變化。 -
Compite:調(diào)用compile方法解析模版,當(dāng)視圖中有用到vue.data中數(shù)據(jù)的時候,會調(diào)用實例化watcher方法進(jìn)行依賴收集 -
Watcher:是Observer和Compile之間通信的橋梁,當(dāng)視圖中遇到綁定的數(shù)據(jù)時,在watcher方法中會獲取這個數(shù)據(jù),此時會觸發(fā)observe中的getter方法, -
Dep:發(fā)布訂閱模式,observe中數(shù)據(jù)的getter被觸發(fā)時會收集依賴的watcher(dep.depend方法) - 當(dāng)有數(shù)據(jù)被改動時會觸發(fā)
observe中數(shù)據(jù)的setter,此時會調(diào)用dep.notify方法給所有訂閱的watcher發(fā)通知(通過回掉方式)進(jìn)行視圖更新,此時會進(jìn)行diff流程:
image
vue中data為什么必須要是一個函數(shù)
vue中的data為對象,是引用類型,當(dāng)重用組件時,一個組件對data做了更改,那么另一個組件也會跟著改,而使用返回一個函數(shù)返回數(shù)據(jù),則每次返回都是一個新對象,引用地址不用,所以就不會出現(xiàn)問題
Virtual DOM 是什么
虛擬DOM是一個JavaScript對象,包含了當(dāng)前DOM的基本結(jié)構(gòu)和信息,它的存在是為了減少對操作無用DOM所帶來的性能消耗,在大量的、頻繁的數(shù)據(jù)更新下能夠?qū)σ晥D進(jìn)行合理的高效的更新(細(xì)粒度的精準(zhǔn)修改),同時也抽象了原來的渲染過程,實現(xiàn)了跨平臺的能力
簡述vue中的DOM DIFF算法
精簡源碼;當(dāng)數(shù)據(jù)發(fā)生改變時,set方法會讓調(diào)用Dep.notify通知所有訂閱者Watcher,訂閱者就會調(diào)用patch給真實的DOM打補(bǔ)丁(兩個重要函數(shù)patchVnode和updateChildren):
- 先判斷根結(jié)點及變化后的節(jié)點是否是
sameVnode,如果不是的化,就會創(chuàng)建新的根結(jié)點并進(jìn)行替換 - 如果是
sameVnode,則進(jìn)入patchVnode函數(shù),其基本判斷- 如果兩個節(jié)點是相等
oldVnode === vnode則直接return - 如果
新節(jié)點是文本節(jié)點,則判斷新舊文本節(jié)點是否一致,不一致(oldVnode.text !== vnode.text)則替換 - 如果
新節(jié)點不是文本節(jié)點,則開始比較新舊節(jié)點的子節(jié)點oldCh和ch: - 如果
子節(jié)點都存在,則進(jìn)行updateChildren計算(稍后講) - 如果
只有新子節(jié)點存在,則如果舊節(jié)點有文本節(jié)點,則移除文本節(jié)點,然后將新子節(jié)點拆入 - 如果
只有舊子節(jié)點存在,則移除所有子節(jié)點 - 如果
均無子節(jié)點且舊節(jié)點是文本節(jié)點,則移除文本節(jié)點(此時新節(jié)點一定不是文本節(jié)點)
- 如果兩個節(jié)點是相等
-
updateChildren函數(shù)做細(xì)致對比- start && oldStart對比
- end && oldEnd對比
- start && oldEnd對比
- end && oldStart 對比
- 生成map映射,(key:舊子節(jié)點上的key,value:舊子節(jié)點在自己點中的位置),根據(jù)key記錄下老節(jié)點在新節(jié)點的位置(
idxInOld)- 如果
找到了idxInOld,如果是相同節(jié)點則移動舊節(jié)點到新的對應(yīng)的地方,否則雖然key相同但元素不同,當(dāng)作新元素節(jié)點去創(chuàng)建 - 如果
沒有找到idxInOld,則創(chuàng)建節(jié)點
- 如果
- 如果
老節(jié)點先遍歷完,則新節(jié)點比老節(jié)點多,將新節(jié)點多余的插入進(jìn)去 - 如果
新節(jié)點先遍歷完,則就節(jié)點比新節(jié)點多,將舊節(jié)點多余的刪除
vue中key的作用
主要是為了復(fù)用節(jié)點,高效的更新虛擬DOM,另外,在使用標(biāo)簽元素過渡效果時也會用到key
computed的原理
- vue對象初始化的同時對計算屬性進(jìn)行初始化
initComputed, - computed會對初始化的Watcher傳入
lazy: true就會觸發(fā)Watcher中的watcher.dirty=true(dirty決定了當(dāng)前屬性是否更新), - 當(dāng)視圖中有對
computed引用的時候會第一次執(zhí)行計算屬性,并將dirty設(shè)置為false,并將結(jié)果保存在this.value中進(jìn)行緩存, - 如果依賴沒有更改,則下次獲取
computed會這直接返回this.value,只有當(dāng)computed所依賴的屬性發(fā)生變化時會將dirty設(shè)置為true,并重新計算
class Watcher{
……
evaluate () {
this.value = this.get()
this.dirty = false
}
……
}
class initComputed{
……
//計算屬性的getter 獲取計算屬性的值時會調(diào)用
createComputedGetter (key) {
return function computedGetter () {
//獲取到相應(yīng)的watcher
const watcher = this._computedWatchers && this._computedWatchers[key]
if (watcher) {
//watcher.dirty 參數(shù)決定了計算屬性值是否需要重新計算,默認(rèn)值為true,即第一次時會調(diào)用一次
if (watcher.dirty) {
/*每次執(zhí)行之后watcher.dirty會設(shè)置為false,只要依賴的data值改變時才會觸發(fā)
watcher.dirty為true,從而獲取值時從新計算*/
watcher.evaluate()
}
//獲取依賴
if (Dep.target) {
watcher.depend()
}
//返回計算屬性的值
return watcher.value
}
}
}
……
}
計算屬性computed和watch的區(qū)別
計算屬性顧名思義就是通過其他變量計算得來的,它的值是基于其所依賴的屬性來進(jìn)行緩存的,只有在其所依賴的屬性發(fā)生變化時才會從新求值
watch是監(jiān)聽一個變量,當(dāng)變量發(fā)生變化時,會調(diào)用對應(yīng)的方法
對$nextTick的理解
vue實現(xiàn)響應(yīng)式并不是數(shù)據(jù)一更新就立刻觸發(fā)dom變化,而是按照一定的策略對dom進(jìn)行更新,源碼位置,原理:
- 首先會將所有的nextTick放到一個函數(shù)中,然后放在
callbacks數(shù)組中,$nextTick沒有傳cb回掉,則返回一個promise - 接下來就是
callbacks的執(zhí)行時機(jī)- 首先如果瀏覽器是否兼容
promise,則用promise.resolve().then來執(zhí)行callbacks - 如果瀏覽器兼容
MutationObserver,則用實例化的MutationObserver監(jiān)聽文本變化來執(zhí)行回掉, - 如果兼容
setImmediate,則用setImmediate(cb)來執(zhí)行回掉 - 最后降級為用
setTimeout(fn,0)來執(zhí)行
- 首先如果瀏覽器是否兼容
- 在
vue2.5.X版本中對于像v-on這樣的DOM交互事件,默認(rèn)走macroTimerFunc,也就是,跳過第一步promise的判斷,
子組件為何不可以修改父組件傳遞的 Prop,是如何監(jiān)控并給出錯誤提示的
- 單向數(shù)據(jù)流,易于監(jiān)測數(shù)據(jù)的流動,出現(xiàn)了錯誤可以更加迅速的定位到錯誤發(fā)生的位置
- 在
initProps時,會對props進(jìn)行defineReactive操作,傳入的第四個參數(shù)是自定義的set報錯判斷函數(shù),該函數(shù)會在觸發(fā)props的set方法時執(zhí)行
// src/core/instance/state.js 源碼路徑
function initProps (vm: Component, propsOptions: Object) {
...
for (const key in propsOptions) {
if (process.env.NODE_ENV !== 'production') {
...
defineReactive(props, key, value, () => {
// 如果不是跟元素并且不是更新子元素
if (!isRoot && !isUpdatingChildComponent) {
warn(
`Avoid mutating a prop directly since the value will be ` +
`overwritten whenever the parent component re-renders. ` +
`Instead, use a data or computed property based on the prop's ` +
`value. Prop being mutated: "${key}"`,
vm
)
}
})}
...
}
}
// src/core/observer/index.js
export function defineReactive (obj,key,val,customSetter,shallow) {
const property = Object.getOwnPropertyDescriptor(obj, key)
const getter = property && property.get
const setter = property && property.set
Object.defineProperty(obj, key, {
...
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
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
父子組件的生命周期執(zhí)行順序
加載過程:父組件beforeCreate => 父組件created => 父組件beforeMount => 子組件beforeCreate => 子組件created => 子組件 beforeMount => 子組件mounted => 父組件mounted
更新過程:父組件beforeUpdate => 子組件beforeUpdate => 子組件updated => 父組件updated
銷毀過程:父組件beforeDestroy => 子組件 beforeDestroy => 子組件 destoryed => 父組件 destoryed
vue-router的導(dǎo)航解析流程
- 導(dǎo)航被觸發(fā)。
- 在失活的組件里調(diào)用離開守衛(wèi)。
- 調(diào)用全局的
beforeEach守衛(wèi)。 - 在重用的組件里調(diào)用
beforeRouteUpdate守衛(wèi) (2.2+)。 - 在路由配置里調(diào)用
beforeEnter。 - 解析異步路由組件。
- 在被激活的組件里調(diào)用
beforeRouteEnter。 - 調(diào)用全局的
beforeResolve守衛(wèi) (2.5+)。 - 導(dǎo)航被確認(rèn)。
- 調(diào)用全局的
afterEach鉤子。 - 觸發(fā) DOM 更新。
- 用創(chuàng)建好的實例調(diào)用
beforeRouteEnter守衛(wèi)中傳給 next 的回調(diào)函數(shù)。
router.beforeResolve注冊一個全局守衛(wèi)。這和router.beforeEach類似,區(qū)別是在導(dǎo)航被確認(rèn)之前,同時在所有組件內(nèi)守衛(wèi)和異步路由組件被解析之后,解析守衛(wèi)就被調(diào)用