vue問題總結(jié)

原文地址

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:是ObserverCompile之間通信的橋梁,當(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ù)patchVnodeupdateChildren):

  • 先判斷根結(jié)點及變化后的節(jié)點是否是sameVnode,如果不是的化,就會創(chuàng)建新的根結(jié)點并進(jìn)行替換
  • 如果是sameVnode,則進(jìn)入patchVnode函數(shù),其基本判斷
    1. 如果兩個節(jié)點是相等oldVnode === vnode則直接return
    2. 如果新節(jié)點是文本節(jié)點,則判斷新舊文本節(jié)點是否一致,不一致(oldVnode.text !== vnode.text)則替換
    3. 如果新節(jié)點不是文本節(jié)點,則開始比較新舊節(jié)點的子節(jié)點oldChch
    4. 如果子節(jié)點都存在,則進(jìn)行updateChildren計算(稍后講)
    5. 如果只有新子節(jié)點存在,則如果舊節(jié)點有文本節(jié)點,則移除文本節(jié)點,然后將新子節(jié)點拆入
    6. 如果只有舊子節(jié)點存在,則移除所有子節(jié)點
    7. 如果均無子節(jié)點且舊節(jié)點是文本節(jié)點,則移除文本節(jié)點(此時新節(jié)點一定不是文本節(jié)點)
  • updateChildren函數(shù)做細(xì)致對比
    1. start && oldStart對比
    2. end && oldEnd對比
    3. start && oldEnd對比
    4. end && oldStart 對比
    5. 生成map映射,(key:舊子節(jié)點上的key,value:舊子節(jié)點在自己點中的位置),根據(jù)key記錄下老節(jié)點在新節(jié)點的位置(idxInOld
      1. 如果找到了idxInOld,如果是相同節(jié)點則移動舊節(jié)點到新的對應(yīng)的地方,否則雖然key相同但元素不同,當(dāng)作新元素節(jié)點去創(chuàng)建
      2. 如果沒有找到idxInOld,則創(chuàng)建節(jié)點
    6. 如果老節(jié)點先遍歷完,則新節(jié)點比老節(jié)點多,將新節(jié)點多余的插入進(jìn)去
    7. 如果新節(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)航解析流程

官網(wǎng)

  1. 導(dǎo)航被觸發(fā)。
  2. 在失活的組件里調(diào)用離開守衛(wèi)。
  3. 調(diào)用全局的 beforeEach 守衛(wèi)。
  4. 在重用的組件里調(diào)用 beforeRouteUpdate 守衛(wèi) (2.2+)。
  5. 在路由配置里調(diào)用 beforeEnter。
  6. 解析異步路由組件。
  7. 在被激活的組件里調(diào)用 beforeRouteEnter。
  8. 調(diào)用全局的 beforeResolve 守衛(wèi) (2.5+)。
  9. 導(dǎo)航被確認(rèn)。
  10. 調(diào)用全局的 afterEach鉤子。
  11. 觸發(fā) DOM 更新。
  12. 用創(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)用

僅代表個人見解,能力有限,如有錯誤會誤人子弟的地方歡迎留言指出;謝謝

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容