VUE源碼相關面試題匯總


1.談一下你對MVVM原理的理解

傳統的MVC指的是,用戶操作會請求服務端路由,路由攔截分發請求,調用對應的控制器來處理。控制器會獲取數據,然后數據與模板結合,將結果返回給前端,頁面重新渲染。
數據流是單向的,view——>model——>view
MVVM:傳統的前端會將數據手動渲染到頁面上,MVVM模式不需要用戶手動操作DOM元素,將數據綁定到viewModel層上,會自動將數據渲染到頁面中。視圖變化會通知viewModel層更新數據,viewModel就是MVVM模式的橋梁。

數據驅動數據流動時雙向的,model——>viewModel<——>view

2.請說一下響應式數據的原理

vue2——核心點:Object.defineProperty —— 修改每一個屬性

默認Vue在初始化數據時,會給data中的屬性使用Object.defineProperty,在獲取和設置的進行攔截,重新定義所有屬性。當頁面取到對應屬性時,會進行依賴收集(收集當前組件的watcher)。如果屬性發生變化會通知相關依賴進行更新操作。
依賴收集、派發更新的作用:如果沒有這項操作,每個數據更新就會去渲染頁面,極大的消耗性能。加了這項操作,去監聽相關數據的改變,添加到隊列里,當所有改變完事兒之后,一起進行渲染(詳情見:https://www.dazhuanlan.com/2019/12/10/5dee72f414290/的3)

vue3——核心點:proxy(代理)—— 直接處理對象

解決了vue2中的處理對象遞歸、處理數組麻煩的問題(詳情見:https://www.dazhuanlan.com/2019/12/10/5dee72f414290/的1,2)

原理:

響應式原理圖

源碼:

export function defineReactive ( obj: Object, key: string, val: any, customSetter?: ?Function, shallow?: boolean ) { const dep = new Dep() const property = Object.getOwnPropertyDescriptor(obj, key) if (property && property.configurable === false) { return } // cater for pre-defined getter/setters
  const getter = property && property.get const setter = property && property.set if ((!getter || setter) && arguments.length === 2) { val = obj[key] } let childOb = !shallow && observe(val)//遞歸處理子
  //每一個對象屬性添加get、set方法,變為響應式對象
 Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend()//依賴收集
        if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } } } return value }, set: function reactiveSetter (newVal) { const value = getter ? getter.call(obj) : val /* eslint-disable no-self-compare */
      if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) { customSetter() } // #7981: for accessor properties without setter
      if (getter && !setter) return
      if (setter) { setter.call(obj, newVal) } else { val = newVal } childOb = !shallow && observe(newVal) dep.notify()//派發更新
 } }) }

 

3.vue中時如何檢測數組變化的:使用了函數劫持的方式,重寫了數組方法

Vue將data中的數組,進行了原型鏈重寫,指向了自己定義的數組原型方法。這樣當調用數組api時,可以通知依賴更新。如果數組中包含着引用類型,會對數組中的引用類型再次進行監控。
Object.create(),保存原有原型
原理:

源碼:

import { def } from '../util/index'

const arrayProto = Array.prototype export const arrayMethods = Object.create(arrayProto)//es6語法,相當於繼承一個對象,添加的屬性是在原型下

const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] /** * Intercept mutating methods and emit events */ methodsToPatch.forEach(function (method) { // cache original 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)//數組中新操作的對象進行響應式處理 // notify change
    ob.dep.notify()//派發更新,渲染頁面
    return result }) })

4.為何vue采用異步渲染?

vue是組件級更新,如果不采用異步更新,那么每次更新數據都會對當前組件重新渲染。為了性能考慮,vue會在本輪數據更新后,再去異步更新視圖。
原理:

源碼:

export function queueWatcher (watcher: Watcher) { const id = watcher.id//判斷watcher的id是否存在
  if (has[id] == null) { has[id] = true
    if (!flushing) { queue.push(watcher) } else { // if already flushing, splice the watcher based on its id // if already past its id, it will be run next immediately.
      let i = queue.length - 1
      while (i > index && queue[i].id > watcher.id) { i-- } queue.splice(i + 1, 0, watcher) } // queue the flush
    if (!waiting) {//wating默認為false
      waiting = true

      if (process.env.NODE_ENV !== 'production' && !config.async) { flushSchedulerQueue() return } nextTick(flushSchedulerQueue)//調用nextTick方法,批量更新
 } } }

5.nextTick實現原理?

nextTick主要是使用了宏任務和微任務,定義了一個異步方法。多次調用nextTick會將方法存入隊列中,通過這個異步方法清空當前隊列,所以nextTick就是異步方法。

原理:

源碼:

export function nextTick (cb?: Function, ctx?: Object) { let _resolve callbacks.push(() => {//callbacks是一個數組
    if (cb) { try { cb.call(ctx) } catch (e) { handleError(e, ctx, 'nextTick') } } else if (_resolve) { _resolve(ctx) } }) if (!pending) {//pengding默認為false
    pending = true timerFunc()//調用異步方法
 } // $flow-disable-line
  if (!cb && typeof Promise !== 'undefined') { return new Promise(resolve => { _resolve = resolve }) } }

 示例:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="initial-scale=1, maximum-scale=1, user-scalable=no">
  <title>Vue.js elastic header example</title>
  <!-- Delete ".min" for console warnings in development -->
  <script src="../../dist/vue.min.js"></script>
  <link rel="stylesheet" href="style.css">
  <!-- template for the component -->
  <script type="text/x-template" id="header-view-template">
      <button @click="changeMsg" id="button" :style="{width:width+'px'}" v-if="isShow">
        {{msg}}
      </button>
      </div>      
    </script>
</head>
<body>
  <div id="app">
    <draggable-header-view>
     
    </draggable-header-view>
    
  </div>

  <script>
    Vue.component('draggable-header-view', {
      template: '#header-view-template',
      data: function () {
        return {
          msg: 'Hello Vue.',
          width:200,
        }
      },
      computed: {
      },
      methods: {
        changeMsg() {
          this.msg = "Hello world."
          this.width +=50;
          console.log("this.$nextTick前",document.getElementById('button').innerHTML);
          console.log("this.$nextTick前 width",document.getElementById('button').style.width);
          this.$nextTick(() => {
          console.log("this.$nextTick",document.getElementById('button').innerHTML);
          console.log("this.$nextTick width",document.getElementById('button').style.width);
          })
          console.log("this.$nextTick后",document.getElementById('button').innerHTML);
          console.log("this.$nextTick后 width",document.getElementById('button').style.width);
        },
      }
    })

    new Vue({ el: '#app' })
  </script>
</body>

</html>

  

6.vue中computed的特點

默認computed也是一個watcher,具備緩存,只有當依賴的屬性發生變化才會更新視圖。
原理:

源碼:

const computedWatcherOptions = { lazy: true }
function initComputed (vm: Component, computed: Object) {
    ...
    if (!isSSR) {
      // create internal watcher for the computed property.
      watchers[key] = new Watcher(
        vm,
        getter || noop,//將用戶定義傳到watcher中
        noop,
        computedWatcherOptions//lazy:true懶watcher
      )
    }

    // component-defined computed properties are already defined on the
    // component prototype. We only need to define computed properties defined
    // at instantiation here.
    if (!(key in vm)) {
      defineComputed(vm, key, userDef)
    } else if (process.env.NODE_ENV !== 'production') {
      if (key in vm.$data) {
        warn(`The computed property "${key}" is already defined in data.`, vm)
      } else if (vm.$options.props && key in vm.$options.props) {
        warn(`The computed property "${key}" is already defined as a prop.`, vm)
      }
    }
  }
}

export function defineComputed (
  target: any,
  key: string,
  userDef: Object | Function
) {
  const shouldCache = !isServerRendering()
  if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)//創建計算屬性的getter,不是用用戶傳的
      : createGetterInvoker(userDef)
    sharedPropertyDefinition.set = noop
  } else {
    sharedPropertyDefinition.get = userDef.get
      ? shouldCache && userDef.cache !== false
        ? createComputedGetter(key)
        : createGetterInvoker(userDef.get)
      : noop
    sharedPropertyDefinition.set = userDef.set || noop
  }
  if (process.env.NODE_ENV !== 'production' &&
      sharedPropertyDefinition.set === noop) {
    sharedPropertyDefinition.set = function () {
      warn(
        `Computed property "${key}" was assigned to but it has no setter.`,
        this
      )
    }
  }
  Object.defineProperty(target, key, sharedPropertyDefinition)
}

function createComputedGetter (key) {
  return function computedGetter () {//用戶取值的時候會調用此方法
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      if (watcher.dirty) {//dirty為true會去進行求值,這兒的dirty起到了緩存的作用
        watcher.evaluate()
      }
      if (Dep.target) {
        watcher.depend()
      }
      return watcher.value
    }
  }
}

詳解:https://www.cnblogs.com/vickylinj/p/14034645.html 

7.watch中的deep:true是如何實現的?

當用戶指定了watch中的deep屬性為true時,如果當時監控的屬性是數組類型,會對對象中的每一項進行求值,此時會將當前watcher存入到對應屬性的依賴中,這樣數組中對象發生變化時也會通知數據更新。
內部原理就是遞歸,耗費性能 。

traverse

 

詳解:https://www.cnblogs.com/vickylinj/p/14034909.html

8.vue組件的生命周期

每個生命周期什么時候被調用

  • beforeCreate 在實例初始化new Vue()之后,數據觀測(data observer)響應式處理之前被調用
  • created 實例已經創建完成之后被調用,實例已完成以下的配置:數據觀測(data observer)、屬性和方法的運算、watch/event事件回調。數據可以拿到,但是沒有$el。
  • beforeMount 在掛載開始之前被調用:相關的render函數首次被調用。//template
  • mounted el被新創建的vm.$el替換,並掛載到實例上去之后被調用。頁面渲染完畢
  • beforeUpdate 數據更新時調用,發生在虛擬DOM重新渲染和打補丁之前。
  • updated 由於數據更改導致的虛擬DOM重新渲染和打補丁,在這之后會調用該鈎子。
  • beforeDestroy 實例銷毀之前調用,在這一步,實例仍然完全可用。
  • destroyed Vue實例銷毀后調用。調用后,Vue實例指示的所有東西都會解綁定,所有的事件監聽器會被移除,所有的子實例也會被銷毀。該鈎子在服務器端渲染期間不被調用。

每個生命周期內部可以做什么事

  • created 實例已經創建完成,因為它時最早觸發的,可以進行一些數據資源的請求。
  • mounted 實例已經掛載完成,可以進行一些DOM操作。
  • beforeUpdate 可以在這個鈎子中進一步地更改狀態,不會觸發附加的重渲染過程。
  • updated 可以執行依賴於DOM的操作,盡量避免在此期間更改狀態,因為可能會導致更新無限循環。該鈎子在服務器端渲染期間不被調用。
  • destroyed 可以執行一些優化操作,清空定時器,解除綁定事件。

原理:

 

9.ajax請求放在哪個生命周期中?

在created的時候,視圖中的dom並沒有被渲染出來,所以此時如果直接去操作dom節點,無法找到相關元素。
在mounted中,由於此時的dom元素已經渲染出來了,所以可以直接使用dom節點。

因此,一般情況下,都放在mounted中,保證邏輯的統一性。因為生命周期是同步執行的,ajax是異步執行的。

但是,服務端渲染不支持mounted方法,所以在服務端渲染的情況下統一放在created中。

10.何時需要使用beforeDestroy?

可能在當前組件使用了$on方法,需要在組件銷毀前解綁
清除自己定義的定時器
解除事件的原生綁定scroll、mousemove…

11.vue中模板編譯原理

模板(template)》 ast語法樹(抽象語法樹)》 codegen方法 ==》render函數 ==》createElement方法 ==》 Virtual Dom(虛擬dom)
模板轉語法樹

模板結合數據,生成抽象語法樹,描述html、js語法

語法樹生成render函數

 

render函數

 

生成Virtual Dom(虛擬dom),描述真實的dom節點

 

渲染成真實dom

 12.vue中v-if和v-show的區別

v-if如果條件不成立,不會渲染當前指令所在節點的dom元素

 

v-show切換當前dom的顯示和隱藏,本質上display:none
在這里插入圖片描述
在這里插入圖片描述

13.為什么v-for和v-if不能連用?

v-for會比v-if的優先級高一些,如果連用的話,會把v-if給每個元素都添加一下,會造成性能問題。
如果確實需要判斷每一個,可以用計算屬性來解決,先用計算屬性將滿足條件的過濾出來,然后再去循環。
在這里插入圖片描述

14.用vnode來描述一個dom結構

在這里插入圖片描述
在這里插入圖片描述


15.diff算法的時間復雜度

15.1、兩個樹完全diff算法的時間復雜度為O(n3):

 

 就上面兩樹的變化而言,若要達到最小更新,首先要對比每個節點是否相同,也就是:

 

 查找不同就需要O(n^2),找到差異后還要計算最小轉換方式,最終結果為O(n^3)。

 

15.2、Vue中的diff算法進行了優化,只考慮同級不考慮跨級,將時間復雜度降為O(n)。前端當中,很少會跨層級的移動Dom元素,所以Virtual Dom只會對同一個層級的元素進行對比。

 

 

16.簡述vue中diff算法原理 

diff流程圖

 diff算法原理 

1、先同級比較,再比較兒子節點
2、先判斷一方有兒子一方沒兒子的情況
3、比較都有兒子的情況
4、遞歸比較子節點
vue3中做了優化,只比較動態節點,略過靜態節點,極大的提高了效率
雙指針去確定位置
diff算法原理圖
在這里插入圖片描述
在這里插入圖片描述

核心源碼: 

patch=>patchVnode=>updateChildren 
function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
    let oldStartIdx = 0
    let newStartIdx = 0
    let oldEndIdx = oldCh.length - 1
    let oldStartVnode = oldCh[0]
    let oldEndVnode = oldCh[oldEndIdx]
    let newEndIdx = newCh.length - 1
    let newStartVnode = newCh[0]
    let newEndVnode = newCh[newEndIdx]
    let oldKeyToIdx, idxInOld, vnodeToMove, refElm

    // removeOnly is a special flag used only by <transition-group>
    // to ensure removed elements stay in correct relative positions
    // during leaving transitions
    const canMove = !removeOnly

    if (process.env.NODE_ENV !== 'production') {
      checkDuplicateKeys(newCh)
    }

    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
      if (isUndef(oldStartVnode)) {
        oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left
      } else if (isUndef(oldEndVnode)) {
        oldEndVnode = oldCh[--oldEndIdx]
      } else if (sameVnode(oldStartVnode, newStartVnode)) {
        patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
        oldStartVnode = oldCh[++oldStartIdx]
        newStartVnode = newCh[++newStartIdx]
      } else if (sameVnode(oldEndVnode, newEndVnode)) {
        patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
        oldEndVnode = oldCh[--oldEndIdx]
        newEndVnode = newCh[--newEndIdx]
      } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
        patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)
        canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
        oldStartVnode = oldCh[++oldStartIdx]
        newEndVnode = newCh[--newEndIdx]
      } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
        patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
        canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
        oldEndVnode = oldCh[--oldEndIdx]
        newStartVnode = newCh[++newStartIdx]
      } else {
        if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
        idxInOld = isDef(newStartVnode.key)
          ? oldKeyToIdx[newStartVnode.key]
          : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
        if (isUndef(idxInOld)) { // New element
          createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
        } else {
          vnodeToMove = oldCh[idxInOld]
          if (sameVnode(vnodeToMove, newStartVnode)) {
            patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)
            oldCh[idxInOld] = undefined
            canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)
          } else {
            // same key but different element. treat as new element
            createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx)
          }
        }
        newStartVnode = newCh[++newStartIdx]
      }
    }
    if (oldStartIdx > oldEndIdx) {
     //取得兄弟節點
      refElm = isUndef(newCh[newEndIdx + 1]) ? null : newCh[newEndIdx + 1].elm
//oldCh先遍歷完,將多余的節點根據index添加到dom中去 addVnodes(parentElm, refElm, newCh, newStartIdx, newEndIdx, insertedVnodeQueue) }
else if (newStartIdx > newEndIdx) {
//newCh先遍歷完,那么就在真實dom中將區間為[oldStartIdx, oldEndIdx]的多余節點刪掉
removeVnodes(oldCh, oldStartIdx, oldEndIdx) } }

 17.v-for中為什么要用key?

解決vue中diff算法結構相同key相同,內容復用的問題,通過key(最好自定義id,不要用索引),明確dom元素,防止復用
在這里插入圖片描述

 

源碼:

function sameVnode (a, b) {
  return (
    a.key === b.key && (
      (
        a.tag === b.tag &&
        a.isComment === b.isComment &&
        isDef(a.data) === isDef(b.data) &&
        sameInputType(a, b)
      ) || (
        isTrue(a.isAsyncPlaceholder) &&
        a.asyncFactory === b.asyncFactory &&
        isUndef(b.asyncFactory.error)
      )
    )
  )
}

 詳見:https://www.cnblogs.com/vickylinj/p/14036812.html

18.描述組件渲染和更新過程


渲染組件時,會通過Vue.extend方法構建子組件的構造函數,並進行實例化,最終手動調用$mount進行掛載,如下圖。渲染組件時進行createElm流程分支,而更新組件時會進行patchVnode流程分支,核心就是diff算法。
在這里插入圖片描述

圖示:組件渲染流程

19.組件中的data為什么是個函數?


同一個組件被復用多次,會創建多個實例。這些實例用的是同一個構造函數,如果data是一個對象的話,所有組件共享了同一個對象。為了保證組件的數據獨立性,要求每個組件都必須通過data函數返回一個對象作為組件的狀態。

20.Vue中事件綁定的原理

Vue的事件綁定分為兩種:一種是原生的事件綁定,一種是組件的事件綁定
原生dom事件綁定采用的是addEventListener
組件的事件綁定采用的是$on方法
在這里插入圖片描述
在這里插入圖片描述

21.v-model的實現原理及如何自定義v-model?

v-model可以看成是value+input方法的語法糖
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述
不同的標簽去觸發不同的方法
在這里插入圖片描述

22.vue中的v-html會導致哪些問題

可能會導致XXS攻擊
v-html會替換掉標簽內的子元素
原理:
在這里插入圖片描述

23.vue父子組件生命周期調用順序

加載渲染過程

父beforeCreate ==> 父created ==> 父beforeMount ==> 子beforeCreat ==>子created ==> 子beforeMount ==> 子mounted ==> 父mounted

子組件更新過程

父beforeUpdate ==> 子beforeUpdate ==> 子updated ==> 父updated

父組件更新過程

父beforeUpdate ==> 父updated

銷毀過程

父beforeDestroy ==> 子beforeDestroy ==> 子destroyed ==> 父destroyed

理解

組件的調用順序都是先父后子,渲染完成的順序是先子后父
組件的銷毀操作是先父后子,銷毀完成的順序是先子后父

原理圖
在這里插入圖片描述
在這里插入圖片描述

24.vue組件如何通信?(單向數據流)

  • 父子間通信 父 ==> 子通過props ,子 ==> 父通過$on、$emit(發布訂閱)
  • 通過獲取父子組件實例的方式$parent、$children
  • 在父組件中提供數據,子組件進行消費 Provide、Inject(插件必備)
  • Ref獲取實例的方式調用組件的屬性和方法
  • Event Bus實現跨組件通信 Vue.prototype.$bus = new Vue,全局就可以使用$bus
  • Vuex狀態管理實現通信

25.vue中相同邏輯如何抽離


Vue.mixin用法給組件每個生命周期、函數都混入一些公共邏輯

 
        
Vue.mixin({
    beforeCreate(){}//這兒定義的生命周期和方法會在每個組件里面拿到
})
 
        

源碼:

 
        
import { mergeOptions } from '../util/index'
export function initMixin (Vue: GlobalAPI) {
  Vue.mixin = function (mixin: Object) {
    this.options = mergeOptions(this.options, mixin)//將當前定義的屬性合並到每個組件中
    return this
  }
}

export function mergeOptions (
  parent: Object,
  child: Object,
  vm?: Component
): Object {
  if (process.env.NODE_ENV !== 'production') {
    checkComponents(child)
  }

  if (typeof child === 'function') {
    child = child.options
  }

  normalizeProps(child, vm)
  normalizeInject(child, vm)
  normalizeDirectives(child)

  // Apply extends and mixins on the child options,
  // but only if it is a raw options object that isn't
  // the result of another mergeOptions call.
  // Only merged options has the _base property.
  if (!child._base) {
    if (child.extends) {//遞歸合並extends
      parent = mergeOptions(parent, child.extends, vm)
    }
    if (child.mixins) {//遞歸合並mixin
      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
}
 
        
26.為什么要使用異步組件?

如果組件功能多,打包出的結果會變大,可以采用異步組件的方式來加載組件。主要依賴import()這個語法,可以實現文件的分割加載。
在這里插入圖片描述

27.插槽和作用域插槽


渲染的作用域不同,普通插槽是父組件,作用域插槽是子組件
插槽

  • 創建組件虛擬節點時,會將組件的兒子的虛擬節點保存起來。當初始化組件時,通過插槽屬性將兒子進行分類,{a:[vnode],b:[vnode]}
  • 渲染組件時,會拿對應的slot屬性的節點進行替換操作。(插槽的作用域為父組件)

在這里插入圖片描述
在這里插入圖片描述
作用域插槽

  • 作用域插槽在解析的時候,不會作為組件的兒子節點,會解析成函數。當子組件渲染時,調用此函數進行渲染。(插槽的作用域為父組件)

在這里插入圖片描述
在這里插入圖片描述
原理:
在這里插入圖片描述

28.談談你對keep-alive的理解(一個組件)


keep-alive可以實現組件的緩存,當組件切換時,不會對當前組件卸載
常用的2個屬性include、exclude
常用的2個生命周期activated、deactivated
源碼:

 
        
export default {
  name: 'keep-alive',
  abstract: true,//抽象組件

  props: {
    include: patternTypes,
    exclude: patternTypes,
    max: [String, Number]
  },

  created () {
    this.cache = Object.create(null)//創建緩存列表
    this.keys = []//創建緩存組件的key列表
  },

  destroyed () {//keep-alive銷毀時,會清空所有的緩存和key
    for (const key in this.cache) {//循環銷毀
      pruneCacheEntry(this.cache, key, this.keys)
    }
  },

  mounted () {//會監控include和exclude屬性,進行組件的緩存處理
    this.$watch('include', val => {
      pruneCache(this, name => matches(val, name))
    })
    this.$watch('exclude', val => {
      pruneCache(this, name => !matches(val, name))
    })
  },

  render () {
    const slot = this.$slots.default//默認拿插槽
    const vnode: VNode = getFirstComponentChild(slot)//只緩存第一個組件
    const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
    if (componentOptions) {
      // check pattern
      const name: ?string = getComponentName(componentOptions)//取出組件的名字
      const { include, exclude } = this
      if (//判斷是否緩存
        // not included
        (include && (!name || !matches(include, name))) ||
        // excluded
        (exclude && name && matches(exclude, name))
      ) {
        return vnode
      }

      const { cache, keys } = this
      const key: ?string = vnode.key == null
        // same constructor may get registered as different local components
        // so cid alone is not enough (#3269)
        ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
        : vnode.key//如果組件沒key,就自己通過組件的標簽和key和cid拼接一個key
      if (cache[key]) {
        vnode.componentInstance = cache[key].componentInstance//直接拿到組件實例
        // make current key freshest
        remove(keys, key)//刪除當前的[b,c,d,e,a] //LRU最近最久未使用法
        keys.push(key)//將key放到后面[b,a]
      } else {
        cache[key] = vnode//緩存vnode
        keys.push(key)//將key存入
        // prune oldest entry
        if (this.max && keys.length > parseInt(this.max)) {//緩存的太多,超過了max就需要刪除掉
          pruneCacheEntry(cache, keys[0], keys, this._vnode)//要刪除第0個,但是渲染的就是第0個
        }
      }

      vnode.data.keepAlive = true//標准keep-alive下的組件是一個緩存組件
    }
    return vnode || (slot && slot[0])//返回當前的虛擬節點
  }
}
 
        

29.Vue中常見性能優化

一個優秀的Vue團隊代碼規范是什么樣子的?
1、編碼優化

    1. 不要將所有的數據都放到data中,data中的數據都會增加getter、setter,會收集對應的watcher
    2. vue在v-for時給每項元素綁定事件需要用事件代理
    3. SPA頁面采用keep-alive緩存組件
    4. 拆分組件(提高復用性、增加代碼的可維護性,減少不必要的渲染)
    5. v-if當值為false時,內部指令不會執行,具有阻斷功能。很多情況下使用v-if替換v-show
    6. key保證唯一性(默認vue會采用就地復用策略)
    7. Object.freeze凍結數據
    8. 合理使用路由懶加載、異步組件
    9. 數據持久化的問題,防抖、節流

2、Vue加載性能優化

    • 第三方模塊按需導入(babel-plugin-component)
    • 滾動到可視區域動態加載(https://tangbc.github.io/vue-virtual-scroll-list)
    • 圖片懶加載(https://github.com/hilongjw/vue-lazyload.git)

3、用戶體驗

    • app-skeleton骨架屏
    • app shell app殼
    • pwa

4、SEO優化

    • 預渲染插件prerender-spa-plugin
    • 服務端渲染ssr

5、打包優化

    • 使用cdn的方式加載第三方模塊
    • 多線程打包happypack
    • splitChunks抽離公共文件
    • sourceMap生成

6、緩存壓縮

    • 客戶端緩存、服務端緩存
    • 服務端gzip壓縮

30.Vue3.0的改進

  • 采用了TS來編寫
  • 支持composition API
  • 響應式數據原理改成了proxy
  • diff對比算法更新,只更新vdom綁定了動態數據的部分

31.實現hash路由和history路由

  • onhashchange #
  • history.pushState h5 api

32.Vue-Router中導航守衛有哪些


完整的導航解析流程 runQueue
在這里插入圖片描述


33.action和mutation區別

  • mutation是同步更新數據(內部會進行是否為異步方式更新的數據檢測)
  • action異步操作,可以獲取數據后調用mutation提交最終數據

34.簡述Vuex工作原理
在這里插入圖片描述

35.vue3今年發布了,請你說一下他們之間在響應式的實現上有什么區別?
vue2采用的是defineProperty去定義get,set,而vue3改用了proxy。也代表着vue放棄了兼容ie。

36.像vue-router,vuex他們都是作為vue插件,請說一下他們分別都是如何在vue中生效的?
通過vue的插件系統,用vue.mixin混入到全局,在每個組件的生命周期的某個階段注入組件實例。

37.請你說一下vue的設計架構。
vue2采用的是典型的混入式架構,類似於express和jquery,各部分分模塊開發,再通過一個mixin去混入到最終暴露到全局的類上。
簡述一個框架的同時,說出他的設計來源、類似的框架。

轉自:https://blog.csdn.net/weixin_40970987/article/details/106396285

https://www.dazhuanlan.com/2019/12/10/5dee72f414290/

7.2queueWatcher(src\core\observer\scheduler.js):

oldStartIdx


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM