「從原始碼中學習」面試官都不知道的Vue題目答案

前端勸退師發表於2019-03-25

前言

當回答面試官問及的Vue問題,我們除了照本宣科的回答外,其實還可以根據少量的原始碼來秀一把,來體現出你對Vue的深度瞭解。

本文會陸續更新,此次涉及以下問題:

  1. “new Vue()做了什麼?”
  2. “什麼階段才能訪問DOM?”
  3. “談談你對Vue生命週期的理解。”
  4. 擴充套件:新生命週期鉤子serverPrefetch是什麼?
  5. “vue-router路由模式有幾種?”
  6. “談談你對keep-alive的瞭解?”
  7. “瞭解Vue2.6+新全域性API:Vue.observable()嗎?”

1. “new Vue()做了什麼?”

new關鍵字代表例項化一個物件, 而Vue實際上是一個類, 原始碼位置是/src/core/instance/index.js

function Vue (options) {
  if (process.env.NODE_ENV !== 'production' &&
    !(this instanceof Vue)
  ) {
    warn('Vue is a constructor and should be called with the `new` keyword')
  }
  this._init(options)
}
複製程式碼

接著我們跳轉追蹤至this._init(),即Vue.prototype._init,位於src\core\instance\init.js_init()方法的內部有一系列 init* 的方法

Vue.prototype._init = function (options?: Object) {
    const vm: Component = this
    // ...忽略,從第45行看起
    if (process.env.NODE_ENV !== 'production') {
      initProxy(vm)
    } else {
      vm._renderProxy = vm
    }
    // expose real self
    vm._self = vm
    initLifecycle(vm)
    initEvents(vm)
    initRender(vm)
    callHook(vm, 'beforeCreate')
    initInjections(vm) // resolve injections before data/props
    initState(vm)
    initProvide(vm) // resolve provide after data/props
    callHook(vm, 'created')
    // ...忽略
    if (vm.$options.el) {
      vm.$mount(vm.$options.el)
    }
  }
}
複製程式碼

1.1 這裡我們概述一遍:

  1. initProxy,作用域代理,攔截元件內訪問其它元件的資料。
  2. initLifecycle, 建立父子元件關係,在當前例項上新增一些屬性和生命週期標識。如:$children$refs_isMounted等。
  3. initEvents,用來存放除@hook:生命週期鉤子名稱="繫結的函式"事件的物件。如:$on$emit等。
  4. initRender,用於初始化$slots$attrs$listeners
  5. initInjections,初始化inject,一般用於更深層次的元件通訊,相當於加強版的props。用於元件庫開發較多。

只要在上一層級的宣告的provide,那麼下一層級無論多深都能夠通過inject來訪問到provide的資料。這麼做也是有明顯的缺點:在任意層級都能訪問,導致資料追蹤比較困難,不知道是哪一個層級宣告瞭這個或者不知道哪一層級或若干個層級使用。

  • initState,是很多選項初始化的彙總,包括:props、methods、data、computed 和 watch 等。
  • initProvide,初始化provide
  • vm.$mount,掛載例項。

2. “什麼階段才能訪問DOM?”

這個回答可以從beforeCreate以及 created 的呼叫時機談起,我們根據上面的概述,來簡化下程式碼:

callHook(vm, 'beforeCreate')
// 初始化 inject
// 初始化 props、methods、data、computed 和 watch
// 初始化 provide
callHook(vm, 'created')
// 掛載例項 vm.$mount(vm.$options.el)
複製程式碼

所以當面試官問你:

  • beforeCreate以及 created 呼叫時,哪些資料能用與否?
  • 什麼階段才能訪問DOM?
  • 為什麼created之後才掛載例項?

知道怎麼回答了吧。

3. “談談你對Vue的生命週期的理解”

常規回答這裡就不說了,來稍微深入點的:

  1. created/mounted/updated/destroyed,以及對應的before鉤子。分別是建立=>掛載=>更新=>銷燬。
  2. Vue原始碼中定義了一個mergeHook函式來遍歷一個常量陣列LIFECYCLE_HOOKS,該陣列實際上是由與生命週期鉤子同名的字串組成的陣列。
// v2.6.10 最新版
var LIFECYCLE_HOOKS = [
    'beforeCreate',
    'created',
    'beforeMount',
    'mounted',
    'beforeUpdate',
    'updated',
    'beforeDestroy',
    'destroyed',
    'activated',
    'deactivated',
    'errorCaptured',
    // v2.6+ 
    'serverPrefetch'
];
複製程式碼

於是,你可以答多activated & deactivated(keep-alive 元件啟用/停用)、errorCaptured(v2.5 以上版本有的一個鉤子,用於處理錯誤)這三個。

3.1 新生命週期鉤子:serverPrefetch是什麼?

可以看到,serverPrefetch前身是ssrPrefetch。顧名思義,這是用來處理ssr的。允許我們在渲染過程中“等待”非同步資料。可在任何元件中使用,而不僅僅是路由元件。

「從原始碼中學習」面試官都不知道的Vue題目答案
這裡我們貼出一段官方例子:

<!-- Item.vue -->
<template>
  <div v-if="item">{{ item.title }}</div>
  <div v-else>...</div>
</template>

<script>
export default {
  computed: {
    item () {
      return this.$store.state.items[this.$route.params.id]
    }
  },
  serverPrefetch () {
    return this.fetchItem()
  },
  mounted () {
    if (!this.item) {
      this.fetchItem()
    }
  },
  methods: {
    fetchItem () {
      // return the Promise from the action
      return this.$store.dispatch('fetchItem', this.$route.params.id)
    }
  }
}
</script>
複製程式碼
  • 絕大多數的面試官都不會去關注v2.6+ 以後的程式碼記錄和變更。這裡如果你說出這個v2.6.10的變化,嘖嘖...面試官肯定更加欣賞你。

3.2 生命週期鉤子的合併策略

callHook(vm, 'created')講,先判斷元件的選項中有無對應名字的生命週期鉤子,再判斷是否有 parentVal(vm)。若存在parentVal(vm)且都有對應的生命週期鉤子,則會將兩者concat為一個陣列(parentVal.concat(childVal))。所以,生命週期鉤子其實是可以寫成陣列。如:

created: [
function () {
  console.log('first')
},
function () {
  console.log('second')
},
function () {
  console.log('third')
}]
複製程式碼

鉤子函式將按順序執行。

4. “Vue-router 路由模式有幾種?”

三種 "hash" | "history" | "abstract",一般人只知道兩種"hash" | "history"

這裡貼出原始碼:

switch (mode) {
  case 'history':
    this.history = new HTML5History(this, options.base)
    break
  case 'hash':
    this.history = new HashHistory(this, options.base, this.fallback)
    break
  case 'abstract':
    this.history = new AbstractHistory(this, options.base)
    break
  default:
    if (process.env.NODE_ENV !== 'production') {
      assert(false, `invalid mode: ${mode}`)
    }
}
複製程式碼

# mode

型別: string

預設值: "hash" (瀏覽器環境) | "abstract" (Node.js 環境)

可選值: "hash" | "history" | "abstract" 配置路由模式:

  • hash: 使用 URL hash 值來作路由。支援所有瀏覽器,包括不支援 HTML5 History Api 的瀏覽器。
  • history: 依賴 HTML5 History API 和伺服器配置。檢視 HTML5 History 模式。
  • abstract: 支援所有 JavaScript 執行環境,如 Node.js 伺服器端。如果發現沒有瀏覽器的 API,路由會自動強制進入這個模式.

5. “談談你對keep-alive的瞭解?”

先貼一個常規回答:

keep-alive是 Vue 內建的一個元件,可以使被包含的元件保留狀態,或避免重新渲染。 在vue 2.1.0 版本之後,keep-alive新加入了兩個屬性: include(包含的元件快取) 與 exclude(排除的元件不快取,優先順序大於include) 。

然後你可以開始騷了:

  1. <keep-alive>Vue 原始碼中實現的一個全域性抽象元件,通過自定義 render 函式並且利用了插槽來實現資料快取和更新。它的定義在src/core/components/keep-alive.js 中:
export default {
  name: 'keep-alive',
  abstract: true,
  ...
}
複製程式碼
  1. 所有的抽象元件是通過定義abstract選項來宣告的。抽象元件不渲染真實DOM,且不會出現在父子關係的路徑上(initLifecycle會忽略抽象元件),相關程式碼片段:
if (parent && !options.abstract) {
  // abstract 即 `ptions.abstract`
  // while 迴圈查詢第一個非抽象的父元件
  while (parent.$options.abstract && parent.$parent) {
    parent = parent.$parent
  }
  parent.$children.push(vm)
}
複製程式碼

6. “瞭解Vue2.6+新全域性APIVue.observable()嗎?”

Vue2.6+新的全域性API是Vue.observable(),它的使用方式:

import vue from vue;
const state = Vue.observable ({
   counter: 0,
});
export default {
   render () {
     return (
       <div>
         {state.counter}
           <button v-on:click={() => {state.counter ++; }}>
           Increment counter
         </ button>
       </ div>
     );
   },
};
複製程式碼

而它定義在/src/core/global-api/index.js第48行:

import { observe } from 'core/observer/index'
// ...
// 2.6 explicit observable API
Vue.observable = <T>(obj: T): T => {
observe(obj)
return obj
}
複製程式碼

再看看它importobserve,最近一次提交在12/1/2018,唔。。。。

「從原始碼中學習」面試官都不知道的Vue題目答案
核心就是暴露出observe(obj)觀測後的資料,程式碼啥都沒改。懂了吧?

求一份深圳的內推

「從原始碼中學習」面試官都不知道的Vue題目答案

目前本人在準備跳槽,希望各位大佬和HR小姐姐可以內推一份靠譜的深圳前端崗位!

  • 微信:huab119
  • 郵箱:454274033@qq.com

作者掘金文章總集

公眾號:

「從原始碼中學習」面試官都不知道的Vue題目答案

相關文章