前言
當回答面試官問及的Vue問題,我們除了照本宣科的回答外,其實還可以根據少量的原始碼來秀一把,來體現出你對Vue的深度瞭解。
本文會陸續更新,此次涉及以下問題:
- “new Vue()做了什麼?”
- “什麼階段才能訪問DOM?”
- “談談你對Vue生命週期的理解。”
- 擴充套件:新生命週期鉤子serverPrefetch是什麼?
- “vue-router路由模式有幾種?”
- “談談你對keep-alive的瞭解?”
- “瞭解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 這裡我們概述一遍:
initProxy
,作用域代理,攔截元件內訪問其它元件的資料。initLifecycle
, 建立父子元件關係,在當前例項上新增一些屬性和生命週期標識。如:$children
、$refs
、_isMounted
等。initEvents
,用來存放除@hook:生命週期鉤子名稱="繫結的函式"
事件的物件。如:$on
、$emit
等。initRender
,用於初始化$slots
、$attrs
、$listeners
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的生命週期的理解”
常規回答這裡就不說了,來稍微深入點的:
created/mounted/updated/destroyed
,以及對應的before
鉤子。分別是建立=>掛載=>更新=>銷燬。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的。允許我們在渲染過程中“等待”非同步資料。可在任何元件中使用,而不僅僅是路由元件。
<!-- 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) 。
然後你可以開始騷了:
<keep-alive>
是Vue
原始碼中實現的一個全域性抽象元件,通過自定義render
函式並且利用了插槽來實現資料快取和更新。它的定義在src/core/components/keep-alive.js
中:
export default {
name: 'keep-alive',
abstract: true,
...
}
複製程式碼
- 所有的抽象元件是通過定義
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+
新全域性API
:Vue.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
}
複製程式碼
再看看它import
的observe
,最近一次提交在12/1/2018
,唔。。。。
observe(obj)
觀測後的資料,程式碼啥都沒改。懂了吧?
求一份深圳的內推
目前本人在準備跳槽,希望各位大佬和HR小姐姐可以內推一份靠譜的深圳前端崗位!
- 微信:
huab119
- 郵箱:
454274033@qq.com
作者掘金文章總集
- 為何你始終理解不了JavaScript作用域鏈?
- 「Vue實踐」專案升級vue-cli3的正確姿勢
- 「從原始碼中學習」徹底理解Vue選項Props
- 「從原始碼中學習」Vue原始碼中的JS騷操作
- 「從原始碼中學習」面試官都不知道的Vue題目答案