Vue 高頻原理面試篇+詳細(xì)解答

原文首地址 掘金

三連哦 更多好文 github

大家好夭问,我是林一一圈膏,這是一篇關(guān)于 vue 的原理面試題芳肌,如果能夠完全弄懂相信對大家很有幫助肛搬。

面試題篇

1.老生常談之党觅, MPA/SPA 的理解谅海,優(yōu)缺點是什么?

MPA 多頁面應(yīng)用嫁乘。

  • 構(gòu)成:有多個頁面 html 構(gòu)成昆婿,
  • 跳轉(zhuǎn)方式:頁面的跳轉(zhuǎn)是從一個頁面到另一個頁面
  • 刷新的方式:全頁面刷新
  • 頁面數(shù)據(jù)跳轉(zhuǎn):依賴 URL/cookie/localStorage
  • 跳轉(zhuǎn)后的資源 會重新加載
  • 優(yōu)點:對 SEO 比較友好,開發(fā)難度低一點蜓斧。
    SPA單頁面應(yīng)用
  • 頁面組成:由一個外殼頁面包裹仓蛆,多個頁面(組件)片段組成
  • 跳轉(zhuǎn)方式:在外殼頁面中跳轉(zhuǎn),將片段頁面(組件)顯示或隱藏
  • 刷新方式:頁面片段的局部刷新
  • 頁面的數(shù)據(jù)跳轉(zhuǎn):組件間的傳值比較容易
  • 跳轉(zhuǎn)后的資源 不會重新加載
  • 缺點:對 SEO 搜索不太友好需要單獨做配置挎春,開發(fā)難度高一點需要專門的開發(fā)框架

iframe 實際上是 MPA看疙,但是可以實現(xiàn) SPA 的一些效果豆拨,但是本身由不少問題。

2.老生常談之能庆,為什么需要有這些 MVC/MVVM 模式施禾?談?wù)勀銓?MVC,MVVM 模式的區(qū)別搁胆,

目的:借鑒后端的思想弥搞,職責(zé)劃分和分層

  • Vue, React 不是真正意義上的 MVVM 更不是 MVC,兩者核心只處理視圖層 view渠旁。

MVC模式

MVC.jpg

單向的數(shù)據(jù)攀例,用戶的每一步操作都需要重新請求數(shù)據(jù)庫來修改視圖層的渲染,形成一個單向的閉環(huán)顾腊。比如 jQuery+underscore+backbone粤铭。

  • M:model 數(shù)據(jù)存放層
  • V: view:視圖層 頁面
  • C: controller:控制器 js 邏輯層。

controller 控制層將數(shù)據(jù)層 model層 的數(shù)據(jù)處理后顯示在視圖層 view層杂靶,同樣視圖層 view層 接收用戶的指令也可以通過控制層 controller梆惯,作用到數(shù)據(jù)層 model。所以 MVC的缺點是視圖層不能和數(shù)據(jù)層直接交互吗垮。

MVVM模式

隱藏了 controller 控制層加袋,直接操控 View 視圖層和 Model 數(shù)據(jù)層。

MVVM.jpg
  • M:model 數(shù)據(jù)模型
  • V: view 視圖模板
  • VM:view-model 視圖數(shù)據(jù)模板(vue處理的層抱既,vue 中的definedProperty 就是處理 VM 層的邏輯)

雙向的數(shù)據(jù)綁定:model 數(shù)據(jù)模型層通過數(shù)據(jù)綁定 Data Bindings 直接影響視圖層 View,同時視圖層 view 通過監(jiān)聽 Dom Listener 也可以改變數(shù)據(jù)模型層 model扁誓。

  • 數(shù)據(jù)綁定和DOM事件監(jiān)聽就是 viewModelVue 主要做的事防泵。也就是說:只要將 數(shù)據(jù)模型層Model 的數(shù)據(jù)掛載到 ViewModelVue 就可以實現(xiàn)雙向的數(shù)據(jù)綁定。
  • 加上 vuex/redux 可以作為 vue和reactmodel 數(shù)據(jù)層蝗敢。
var vm = new Vue()

vm 就是 view-model 數(shù)據(jù)模型層捷泞,data:就是vm view-model 層所代理的數(shù)據(jù)。

  • 綜上兩者的區(qū)別:MVC 的視圖層和數(shù)據(jù)層交互需要通過控制層 controller 屬于單向鏈接寿谴。MVVM 隱藏了控制層 controller锁右,讓視圖層和數(shù)據(jù)層可以直接交互 屬于雙向連接。

3. 說一下對 Vue 中響應(yīng)式數(shù)據(jù)的理解

小tip:響應(yīng)式數(shù)據(jù)指的是數(shù)據(jù)發(fā)生了變化讶泰,視圖可以更新就是響應(yīng)式的數(shù)據(jù)

  • vue 中實現(xiàn)了一個 definedReactive 方法咏瑟,方法內(nèi)部借用 Object.definedProperty() 給每一個屬性都添加了 get/set 的屬性。
  • definedReactive 只能監(jiān)控到最外層的對象痪署,對于內(nèi)層的對象需要遞歸劫持?jǐn)?shù)據(jù)码泞。
  • 數(shù)組則是重寫的7個 push pop shift unshift reverse sort splice 來給數(shù)組做數(shù)據(jù)攔截,因為這幾個方法會改變原數(shù)組
  • 擴展:
// src\core\observer\index.js
export function defineReactive (
  obj: Object,
  key: string,
  val: any,
  customSetter?: ?Function,
  shallow?: boolean
) {
  // 準(zhǔn)備給屬性添加一個 dep 來依賴收集 Watcher 用于更新視圖狼犯。
  const dep = new Dep()
  // some code

  // observe() 用來觀察值的類型余寥,如果是屬性也是對象就遞歸领铐,為每個屬性都加上`get/set`
  let childOb = !shallow && observe(val)
  Object.defineProperty(obj, key, {
    enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
        // 這里取數(shù)據(jù)時依賴收集
        const value = getter ? getter.call(obj) : val
        if (Dep.target) {
            dep.depend()
            // childOb 是對對像進(jìn)行收集依賴
            if (childOb) {
                childOb.dep.depend()

                //這里對數(shù)組和內(nèi)部的數(shù)組進(jìn)行遞歸收集依賴,這里數(shù)組的 key 和 value 都有dep宋舷。
                if (Array.isArray(value)) {
                    dependArray(value)
                }
            }
        }
        return value
    },
    set: function reactiveSetter (newVal) {
      // 屬性發(fā)生改變绪撵,這里會通知 watcher 更新視圖
    }
  })
}

上面的 Dep(類) 是用來干嘛的?答:用來收集渲染的 Watcher祝蝠,Watcher 又是一個啥東西音诈?答:watcher 是一個類,用于更新視圖的

4. Vue 是怎么檢測數(shù)組的變化的续膳?

  • vue 沒有對數(shù)組的每一項用 definedProperty() 來數(shù)據(jù)攔截改艇,而是通過重寫數(shù)組的方法push pop shift unshift reverse sort splice
  • 手動調(diào)用 notify,通知 render watcher,執(zhí)行 update
  • 數(shù)組中如果有對象類型(對象和數(shù)組)的話會進(jìn)行數(shù)據(jù)攔截坟岔。
  • 所以通過修改數(shù)組下標(biāo)和數(shù)組長度是不會進(jìn)行數(shù)據(jù)攔截的谒兄,也就不會有響應(yīng)式變化。例如arr[0] = 1, arr.length = 2 都不會有響應(yīng)式
  • 擴展:
// src\core\observer\array.js
const methodsToPatch = ['push','pop','shift','unshift','splice','sort','reverse']
methodsToPatch.forEach(function (method) {
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (...args) {
    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
    }
    // 新增的類型再次觀察
    if (inserted) ob.observeArray(inserted)
    // 手動調(diào)用 notify 派發(fā)更新
    ob.dep.notify()
    return result
  })
})

5.Vue 是怎樣依賴收集的社付?(dep 和 Watcher 是什么關(guān)系)

dep.jpg

tip:Dep 是一個用來負(fù)責(zé)收集 Watcher 的類承疲,Watcher 是一個封裝了渲染視圖邏輯的類,用于派發(fā)更新的鸥咖。需要注意的是 Watcher 是不能直接更新視圖的還需要結(jié)合Vnode經(jīng)過patch()中的diff算法才可以生成真正的DOM

  • 每一個屬性都有自己的 dep 屬性燕鸽,來存放依賴的 Watcher,屬性發(fā)生變化后會通知 Watcher 去更新啼辣。
  • 在用戶獲取(getter) 數(shù)據(jù)時 Vue 給每一個屬性都添加了 dep 屬性來(collect as Dependency)收集 Watcher啊研。在用戶 setting 設(shè)置屬性值時 dep.notify() 通知 收集的Watcher 重新渲染。詳情見上面的 defineReactive()
  • Dep依賴收集類 其和 Watcher類 是多對多雙向存儲的關(guān)系
  • 每一個屬性都可以有多個 Watcher 類鸥拧,因為屬性可能在不同的組件中被使用党远。
  • 同時一個 Watcher 類 也可以對應(yīng)多個屬性。

6. Vue 中的模板編譯

<img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bc2f78cda8514103b4d0a29acb1a4c9a~tplv-k3u1fbpfcp-watermark.image" width="80%" height="360px"/>

Vue中模板編譯:其實就是將 template 轉(zhuǎn)化成 render 函數(shù)富弦。說白了就是將真實的 DOM(模板) 編譯成虛擬 dom(Vnode)

  • 第一步是將 template 模板字符串轉(zhuǎn)換成 ast 語法樹(parser 解析器)沟娱,這里使用了大量的正則來匹配標(biāo)簽的名稱,屬性腕柜,文本等济似。
  • 第二步是對 AST 進(jìn)行靜態(tài)節(jié)點 static 標(biāo)記,主要用來做虛擬 DOM 的渲染優(yōu)化(optimize優(yōu)化器)盏缤,這里會遍歷出所有的子節(jié)點也做靜態(tài)標(biāo)記
  • 第三步是 使用 ast語法樹 重新生成 render 函數(shù) 代碼字符串 code砰蠢。(codeGen 代碼生成器)

為什么要靜態(tài)標(biāo)記節(jié)點,如果是靜態(tài)節(jié)點(沒有綁定數(shù)據(jù)蛾找,前后不需要發(fā)生變化的節(jié)點)那么后續(xù)就不需要 diff 算法來作比較娩脾。

7. 生命周期鉤子實現(xiàn)原理

  • vue 中的生命周期鉤子只是一個回調(diào)函數(shù),在創(chuàng)建組件實例化的過程中會調(diào)用對應(yīng)的鉤子執(zhí)行打毛。
  • 使用Vue.mixin({})混入的鉤子或生命周期中定義了多個函數(shù)柿赊,vue 內(nèi)部會調(diào)用mergeHook() 對鉤子進(jìn)行合并放入到隊列中依次執(zhí)行
  • 擴展
// src\core\util\options.js
function mergeHook (
  parentVal: ?Array<Function>,
  childVal: ?Function | ?Array<Function>
): ?Array<Function> {
  const res = childVal
    ? parentVal
      ? parentVal.concat(childVal) // 合并
      : Array.isArray(childVal)
        ? childVal
        : [childVal]
    : parentVal
  return res
    ? dedupeHooks(res)
    : res
}

8.老生常談之 vue 生命周期有哪些俩功,一般在哪里發(fā)送請求?

  • beforeCreate: 剛開始初始化 vue 實例碰声,在數(shù)據(jù)觀測observer之前調(diào)用诡蜓,還沒有創(chuàng)建 data/methods 等屬性
  • created: vue 實例初始化結(jié)束,所有的屬性已經(jīng)創(chuàng)建胰挑。
  • beforeMount: 在 vue 掛載數(shù)據(jù)到頁面上之前蔓罚,觸發(fā)這個鉤子,render 函數(shù)此時被觸發(fā)瞻颂。
  • mounted: el 被 創(chuàng)建的vm.$el替換豺谈,vue 初始化的數(shù)據(jù)已經(jīng)掛載到頁面之上,這里可以訪問到真實的 DOM贡这。一般會在這里請求數(shù)據(jù)茬末。
  • beforeUpdate: 數(shù)據(jù)更新時調(diào)用,也就是在虛擬 dom 重新渲染之前盖矫。
  • updated: 數(shù)據(jù)變化導(dǎo)致虛擬 dom 發(fā)生重新渲染之后發(fā)生丽惭。
  • beforeDestroy: 實例銷毀之前調(diào)用該鉤子,此時實例還在辈双。vm.$destroy 觸發(fā)兩個方法责掏。
  • destroyed: Vue 實例銷毀之后調(diào)用。所有的事件監(jiān)聽都會被接觸湃望。

請求數(shù)據(jù)要看具體的業(yè)務(wù)需求決定在哪里發(fā)送 ajax

9.Vue.mixin({})的使用場景和原理

  • 使用場景:用于抽離一個公共的業(yè)務(wù)邏輯實現(xiàn)復(fù)用换衬。
  • 實現(xiàn)原理:調(diào)用 mergeOptions() 方法采用策略模式針對不同的屬性合并≈ぐ牛混入的數(shù)據(jù)和組件的數(shù)據(jù)有沖突就采用組件本身的冗疮。
  • Vue.mixin({}) 缺陷,1.可能會導(dǎo)致混入的屬性名和組件屬性名發(fā)生命名沖突檩帐;2. 數(shù)據(jù)依賴的來源問題
  • 擴展
export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  // some code
  if (!child._base) {
    if (child.extends) {
      parent = mergeOptions(parent, child.extends, vm)
    }
    if (child.mixins) {
      for (let i = 0, l = child.mixins.length; i < l; i++) {
        parent = mergeOptions(parent, child.mixins[i], vm)
      }
    }
  }

 // 遞歸遍歷合并組件和混入的屬性
  const options = {}
  let key
  for (key in parent) {
    mergeField(key)
  }
  for (key in child) {
    if (!hasOwn(parent, key)) {
      mergeField(key)
    }
  }
  function mergeField (key) {
    const strat = strats[key] || defaultStrat
    options[key] = strat(parent[key], child[key], vm, key)
  }
  return options
}

10.老生常談之 vue 組件中的data 為什么必須是一個函數(shù)?

  • 這和 js 本身機制相關(guān)另萤,data 函數(shù)中返回的對象引用地址不同湃密,就能保證不同組件之間的數(shù)據(jù)不相互污染。
  • Vue.mixin() 中如果混入data屬性四敞,那么 data 也必須是一個函數(shù)泛源。因為Vue.mixin()也可以多處使用。
  • 實例中data可以是一個對象也可以是一個函數(shù)忿危,因為我們一個頁面一般只初始化一個Vue實例(單例)

11. 老生常談之 vue 中 vm.$nextTick(cb)實現(xiàn)原理和場景

  • 場景:在 dom 更新循環(huán)結(jié)束后調(diào)用达箍,用于獲取更新后的 dom 數(shù)據(jù)
  • 實現(xiàn)原理:vm.$nextTick(cb) 是一個異步的方法為了兼容性做了很多降級處理依次有 promise.then,MutationObserver,setImmediate铺厨,setTimeout缎玫。在數(shù)據(jù)修改后不會馬上更新視圖硬纤,而是經(jīng)過 set 方法 notify 通知 Watcher 更新,將需要更新的 Watcher 放入到一個異步隊列中赃磨,nexTick 的回調(diào)函數(shù)就放在 Watcher 的后面筝家,等待主線程中同步代碼執(zhí)行借宿然后依次清空隊列中,所以 vm.nextTick(callback) 是在 dom 更新結(jié)束后執(zhí)行的邻辉。

上面將對列中Watcher 依次清空就是 vue 異步批量更新的原理溪王。提一個小思考:為什么不直接使用setTimeout代替?因為setTimeout是一個宏任務(wù)值骇,宏任務(wù)多性能也會差莹菱。關(guān)于事件循環(huán)可以看看 JS 事件循環(huán)

12.老生常談之 watch 和 computed 區(qū)別

  • computed 內(nèi)部就是根據(jù) Object.definedProperty() 實現(xiàn)的
  • computed 具備緩存功能,依賴的值不發(fā)生變化吱瘩,就不會重新計算道伟。
  • watch 是監(jiān)控值的變化,值發(fā)生變化時會執(zhí)行對應(yīng)的回調(diào)函數(shù)搅裙。
  • computedwatch 都是基于 Watcher類 來執(zhí)行的皱卓。

computed 緩存功能依靠一個變量 dirty,表示值是不是臟的默認(rèn)是 true部逮,取值后是 false娜汁,再次取值時 dirty 還是 false 直接將還是上一次的取值返回。

// src\core\instance\state.js computed 取值函數(shù)
function createComputedGetter (key) {
  return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      if (watcher.dirty) {  // 判斷值是不是臟 dirty
        watcher.evaluate()
      }
      if (Dep.target) {
        watcher.depend()
      }
      return watcher.value
    }
  }
}


// src\core\instance\state.js watch 實現(xiàn)
  Vue.prototype.$watch = function (
    expOrFn: string | Function,
    cb: any,
    options?: Object
  ): Function {
    const vm: Component = this
    if (isPlainObject(cb)) {
      return createWatcher(vm, expOrFn, cb, options)
    }
    options = options || {}
    options.user = true
    // 實例化 watcher
    const watcher = new Watcher(vm, expOrFn, cb, options)
    if (options.immediate) {
      const info = `callback for immediate watcher "${watcher.expression}"`
      pushTarget()
      invokeWithErrorHandling(cb, vm, [watcher.value], vm, info)
      popTarget()
    }
    return function unwatchFn () {
      watcher.teardown()
    }
  }

參考

Vue 模板編譯原理

Vue.nextTick 的原理和用途

結(jié)束

謝謝大家閱讀到這里兄朋,如果覺得寫的還可以掐禁,歡迎三連呀,我是林一一颅和,下次見傅事。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市峡扩,隨后出現(xiàn)的幾起案子蹭越,更是在濱河造成了極大的恐慌,老刑警劉巖教届,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件响鹃,死亡現(xiàn)場離奇詭異,居然都是意外死亡案训,警方通過查閱死者的電腦和手機买置,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來强霎,“玉大人忿项,你說我怎么就攤上這事。” “怎么了轩触?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵寞酿,是天一觀的道長。 經(jīng)常有香客問我怕膛,道長熟嫩,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任褐捻,我火速辦了婚禮掸茅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘柠逞。我一直安慰自己昧狮,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布板壮。 她就那樣靜靜地躺著逗鸣,像睡著了一般。 火紅的嫁衣襯著肌膚如雪绰精。 梳的紋絲不亂的頭發(fā)上撒璧,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機與錄音笨使,去河邊找鬼卿樱。 笑死,一個胖子當(dāng)著我的面吹牛硫椰,可吹牛的內(nèi)容都是我干的繁调。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼靶草,長吁一口氣:“原來是場噩夢啊……” “哼蹄胰!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起奕翔,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤裕寨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后派继,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體帮坚,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年互艾,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片讯泣。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡纫普,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情昨稼,我是刑警寧澤节视,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站假栓,受9級特大地震影響寻行,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜匾荆,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一拌蜘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧牙丽,春花似錦简卧、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至构罗,卻和暖如春铜涉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背遂唧。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工芙代, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蠢箩。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓链蕊,卻偏偏與公主長得像,于是被迫代替她去往敵國和親谬泌。 傳聞我的和親對象是個殘疾皇子滔韵,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容