(一)Vue常見面試題,看看你都會了嗎?

怎樣封裝一個組件?

//父組件
<template>
    <div>
        <h1>{{title}}</h1>
        <child :name="name" :age="age" :hobby="hobby" @titleChanged="titleChanged"></child>
    </div>
</template>
import child from "./components/child"
export default {
      name: 'App',
      data(){
        return{
            title: '父級內容',
              name: 'hello',
             age: 19,
              hobby: ['swim','run','walk']
        }
      },
      components:{
        "child":child
      },
    titleChanged(val){
        this.title = val;
    }
}
//子組件
<template>
  <div class="hello">
    <h3>{{name}}</h3>
    <p>{{age}}</p>
    <ul>
      <li v-for="h in hobby">{{h}}</li> //遍歷傳遞過來的值,而後呈現到頁面
    </ul>
    <button @click="changeTitle">向父級傳值</button>
  </div>
</template>
<script>
export default {
    name: 'HelloWorld',
      props:{
        users:{           //這個就是父組件中子標籤自定義名字
              type:String,
              required:true
        },
        age: {
            type: Number,
            default: 0,
        },
        hobby: {
            type: Array,
            defautl: ()=>[]
        }
      },
      methods: {
          changeTitle(){
              this.$emit("titleChanged","子向父組件傳值");
          }
      }
}
</script>

請說一下響應式數據的理解?

Vue經過設定對象屬性的 setter/getter 方法來監聽數據的變化,經過getter進行依賴收集,而每一個setter方法就是一個觀察者,在數據變動的時候通知訂閱者更新視圖。html

function observe (obj) { // 咱們來用它使對象變成可觀察的
  // 判斷類型
  if (!obj || typeof obj !== 'object') {
    return
  }
  Object.keys(obj).forEach(key => {
    defineReactive(obj, key, obj[key])
  })
  function defineReactive (obj, key, value) {
    // 遞歸子屬性
    observe(value)
    Object.defineProperty(obj, key, {
      enumerable: true, //可枚舉(能夠遍歷)
      configurable: true, //可配置(好比能夠刪除)
      get: function reactiveGetter () {
        console.log('get', value) // 監聽
        return value
      },
      set: function reactiveSetter (newVal) {
        observe(newVal) //若是賦值是一個對象,也要遞歸子屬性
        if (newVal !== value) {
          console.log('set', newVal) // 監聽
          render()
          value = newVal
        }
      }
    })
  }
}

observe這個函數傳入一個 obj(須要被追蹤變化的對象),經過遍歷全部屬性的方式對該對象的每個屬性都經過 defineReactive 處理,以此來達到實現偵測對象變化。值得注意的是,observe 會進行遞歸調用。vue

由於 Vue 經過Object.defineProperty來將對象的key轉換成getter/setter的形式來追蹤變化,但getter/setter只能追蹤一個數據是否被修改,沒法追蹤新增屬性和刪除屬性。若是是刪除屬性,咱們能夠用vm.$delete實現,那若是是新增屬性,該怎麼辦呢?
1)可使用 Vue.set(location, a, 1) 方法向嵌套對象添加響應式屬性;
2)也能夠給這個對象從新賦值,好比data.location = {...data.location,a:1}node

  • Object.defineProperty 不能監聽數組的變化,須要進行數組方法的重寫,具體代碼以下:
let methods = ['pop', 'shift', 'unshift', 'sort', 'reverse', 'splice', 'push'];
// 先獲取到原來的原型上的方法
let arrayProto = Array.prototype;
// 建立一個本身的原型 而且重寫methods這些方法
let proto = Object.create(arrayProto)
methods.forEach(method => {
  proto[method] = function() {
    // AOP
    arrayProto[method].call(this, ...arguments)
    render()
  }
})
function observer(obj) {
  // 把全部的屬性定義成set/get的方式
  if (Array.isArray(obj)) {
    obj.__proto__ = proto
    return
  }
  if (typeof obj == 'object') {
    for (let key in obj) {
      defineReactive(obj, key, obj[key])
    }
  }
}
function defineReactive(data, key, value) {
  observer(value)
  Object.defineProperty(data, key, {
    get() {
      return value
    },
    set(newValue) {
      observer(newValue)
      if (newValue !== value) {
        render()
        value = newValue
      }
    }
  })
}
observer(obj)
function $set(data, key, value) {
  defineReactive(data, key, value)
}

這種方法將數組的經常使用方法進行重寫,進而覆蓋掉原生的數組方法,重寫以後的數組方法須要可以被攔截。react

vue中模板編譯原理?

關於 Vue 編譯原理這塊的總體邏輯主要分三個部分,也能夠說是分三步,這三個部分是有先後關係的:算法

  • 第一步是將 模板字符串 轉換成 element ASTs(解析器)
<div>
  <p>{{name}}</p>
</div>

上面這樣一個簡單的 模板 轉換成 element AST 後是這樣的:express

{
  tag: "div"
  type: 1,
  staticRoot: false,
  static: false,
  plain: true,
  parent: undefined,
  attrsList: [],
  attrsMap: {},
  children: [
      {
      tag: "p"
      type: 1,
      staticRoot: false,
      static: false,
      plain: true,
      parent: {tag: "div", ...},
      attrsList: [],
      attrsMap: {},
      children: [{
          type: 2,
          text: "{{name}}",
          static: false,
          expression: "_s(name)"
      }]
    }
  ]
}

這段模板字符串會扔到 while 中去循環,而後 一段一段 的截取,把截取到的 每一小段字符串 進行解析,直到最後截沒了,也就解析完了數組

  • 第二步是對 AST 進行靜態節點標記,主要用來作虛擬DOM的渲染優化(優化器)

優化器的目標是找出那些靜態節點並打上標記,而靜態節點指的是 DOM 不須要發生變化的節點。性能優化

每次從新渲染的時候不須要爲靜態節點建立新節點;在 Virtual DOM 中 patching 的過程能夠被跳過。dom

  • 第三步是 使用 element ASTs 生成 render 函數代碼字符串(代碼生成器)
{
  render: `with(this){return _c('div',[_c('p',[_v(_s(name))])])}`
}

經過遞歸去拼一個函數執行代碼的字符串,遞歸的過程根據不一樣的節點類型調用不一樣的生成方法,若是發現是一顆元素節點就拼一個 _c(tagName, data, children) 的函數調用字符串,而後 datachildren 也是使用 AST 中的屬性去拼字符串。異步

若是 children 中還有 children 則遞歸去拼;最後拼出一個完整的 render 函數代碼。

vue中diff的原理?

渲染真實DOM的開銷是很大的,好比有時候咱們修改了某個數據,若是直接渲染到真實dom上會引發整個dom樹的重繪和重排,有沒有可能咱們只更新咱們修改的那一小塊dom而不要更新整個dom呢?diff算法可以幫助咱們。

咱們先根據真實DOM生成一顆virtual DOM,當virtual DOM某個節點的數據改變後會生成一個新的Vnode,而後VnodeoldVnode做對比,發現有不同的地方就直接修改在真實的DOM上,而後使oldVnode的值爲Vnode

diff的過程就是調用名爲patch的函數,比較新舊節點,一邊比較一邊給真實的DOM打補丁。

使用雙指針形式,對虛擬節點進行比對,對相同的節點進行復用,對發生變化的節點進行patch。如下是vue源碼中對虛擬節點的比對方式:

function updateChildren (parentElm, oldCh, newCh, insertedVnodeQueue, removeOnly) {
    let oldStartIdx = 0 // 舊頭索引
    let newStartIdx = 0 // 新頭索引
    let oldEndIdx = oldCh.length - 1 // 舊尾索引
    let newEndIdx = newCh.length - 1 // 新尾索引
    let oldStartVnode = oldCh[0] // oldVnode的第一個child
    let oldEndVnode = oldCh[oldEndIdx] // oldVnode的最後一個child
    let newStartVnode = newCh[0] // newVnode的第一個child
    let newEndVnode = newCh[newEndIdx] // newVnode的最後一個child
    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

    // 若是oldStartVnode和oldEndVnode重合,而且新的也都重合了,證實diff完了,循環結束
    while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
      // 若是oldVnode的第一個child不存在
      if (isUndef(oldStartVnode)) {
        // oldStart索引右移
        oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left

      // 若是oldVnode的最後一個child不存在
      } else if (isUndef(oldEndVnode)) {
        // oldEnd索引左移
        oldEndVnode = oldCh[--oldEndIdx]

      // oldStartVnode和newStartVnode是同一個節點
      } else if (sameVnode(oldStartVnode, newStartVnode)) {
        // patch oldStartVnode和newStartVnode, 索引左移,繼續循環
        patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue)
        oldStartVnode = oldCh[++oldStartIdx]
        newStartVnode = newCh[++newStartIdx]

      // oldEndVnode和newEndVnode是同一個節點
      } else if (sameVnode(oldEndVnode, newEndVnode)) {
        // patch oldEndVnode和newEndVnode,索引右移,繼續循環
        patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue)
        oldEndVnode = oldCh[--oldEndIdx]
        newEndVnode = newCh[--newEndIdx]

      // oldStartVnode和newEndVnode是同一個節點
      } else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
        // patch oldStartVnode和newEndVnode
        patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue)
        // 若是removeOnly是false,則將oldStartVnode.eml移動到oldEndVnode.elm以後
        canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))
        // oldStart索引右移,newEnd索引左移
        oldStartVnode = oldCh[++oldStartIdx]
        newEndVnode = newCh[--newEndIdx]

      // 若是oldEndVnode和newStartVnode是同一個節點
      } else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
        // patch oldEndVnode和newStartVnode
        patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue)
        // 若是removeOnly是false,則將oldEndVnode.elm移動到oldStartVnode.elm以前
        canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)
        // oldEnd索引左移,newStart索引右移
        oldEndVnode = oldCh[--oldEndIdx]
        newStartVnode = newCh[++newStartIdx]

      // 若是都不匹配
      } else {
        if (isUndef(oldKeyToIdx)) oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)

        // 嘗試在oldChildren中尋找和newStartVnode的具備相同的key的Vnode
        idxInOld = isDef(newStartVnode.key)
          ? oldKeyToIdx[newStartVnode.key]
          : findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)

        // 若是未找到,說明newStartVnode是一個新的節點
        if (isUndef(idxInOld)) { // New element
          // 建立一個新Vnode
          createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)

        // 若是找到了和newStartVnodej具備相同的key的Vnode,叫vnodeToMove
        } else {
          vnodeToMove = oldCh[idxInOld]
          /* istanbul ignore if */
          if (process.env.NODE_ENV !== 'production' && !vnodeToMove) {
            warn(
              'It seems there are duplicate keys that is causing an update error. ' +
              'Make sure each v-for item has a unique key.'
            )
          }

          // 比較兩個具備相同的key的新節點是不是同一個節點
          //不設key,newCh和oldCh只會進行頭尾兩端的相互比較,設key後,除了頭尾兩端的比較外,還會從用key生成的對象oldKeyToIdx中查找匹配的節點,因此爲節點設置key能夠更高效的利用dom。
          if (sameVnode(vnodeToMove, newStartVnode)) {
            // patch vnodeToMove和newStartVnode
            patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue)
            // 清除
            oldCh[idxInOld] = undefined
            // 若是removeOnly是false,則將找到的和newStartVnodej具備相同的key的Vnode,叫vnodeToMove.elm
            // 移動到oldStartVnode.elm以前
            canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm)

          // 若是key相同,可是節點不相同,則建立一個新的節點
          } else {
            // same key but different element. treat as new element
            createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm)
          }
        }

        // 右移
        newStartVnode = newCh[++newStartIdx]
      }
    }

vue的渲染流程?

​ 從模板到真實dom節點還須要通過一些步驟

​ 把模板編譯爲render函數;

​ 實例進行掛載, 根據根節點render函數的調用,遞歸的生成虛擬dom;

​ 對比虛擬dom,渲染到真實dom;

​ 組件內部data發生變化,組件和子組件引用data做爲props從新調用render函數,生成虛擬dom, 返回到步驟3。

爲什麼vue採用異步渲染?

若是不採起異步更新,那麼每次更新數據都會對當前組件進行從新渲染,爲了性能考慮,Vue 會在本輪數據更新後,再去異步更新數據。

原理:

  • dep.notify() 通知 watcher 進行更新操做
  • subs[i].update() 依次調用 watcher 的 update
  • queueWatcher將 watcher 從新放到隊列中
  • nextTick(flushSchedulerQueue) 異步清空 watcher 隊列

談一談你對Vue性能優化的理解 ?

主要包括:上線代碼包打包、源碼編寫優化、用戶體驗優化。

1.代碼包優化

  • 屏蔽sourceMap
  • 對項目代碼中的JS/CSS/SVG(*.ico)文件進行gzip壓縮
  • 對路由組件進行懶加載

2.源碼優化

  • v-if 和 v-show選擇調用
  • 爲item設置惟一key值
  • 細分vuejs組件
  • 減小watch的數據
  • 內容類系統的圖片資源按需加載
  • SSR(服務端渲染)

3.用戶體驗優化

  • 菊花loading
  • 骨架屏加載
相關文章
相關標籤/搜索