您好,登錄后才能下訂單哦!
這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)?lái)有關(guān)有哪些vue高頻原理面試題,文章內(nèi)容豐富且以專(zhuān)業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
本文分享12道高頻vue原理面試題,覆蓋了 vue 核心實(shí)現(xiàn)原理,其實(shí)一個(gè)框架的實(shí)現(xiàn)原理一篇文章是不可能說(shuō)完的,希望通過(guò)這 12 道問(wèn)題,讓讀者對(duì)自己的 Vue 掌握程度有一定的認(rèn)識(shí)(B 數(shù)),從而彌補(bǔ)自己的不足,更好的掌握 Vue。
1. Vue 響應(yīng)式原理
Observer : 它的作用是給對(duì)象的屬性添加 getter 和 setter,用于依賴(lài)收集和派發(fā)更新
Dep : 用于收集當(dāng)前響應(yīng)式對(duì)象的依賴(lài)關(guān)系,每個(gè)響應(yīng)式對(duì)象包括子對(duì)象都擁有一個(gè) Dep 實(shí)例(里面 subs 是 Watcher 實(shí)例數(shù)組),當(dāng)數(shù)據(jù)有變更時(shí),會(huì)通過(guò) dep.notify()通知各個(gè) watcher。
Watcher : 觀察者對(duì)象 , 實(shí)例分為渲染 watcher (render watcher),計(jì)算屬性 watcher (computed watcher),偵聽(tīng)器 watcher(user watcher)三種
watcher 中實(shí)例化了 dep 并向 dep.subs 中添加了訂閱者,dep 通過(guò) notify 遍歷了 dep.subs 通知每個(gè) watcher 更新。
當(dāng)創(chuàng)建 Vue 實(shí)例時(shí),vue 會(huì)遍歷 data 選項(xiàng)的屬性,利用 Object.defineProperty 為屬性添加 getter 和 setter 對(duì)數(shù)據(jù)的讀取進(jìn)行劫持(getter 用來(lái)依賴(lài)收集,setter 用來(lái)派發(fā)更新),并且在內(nèi)部追蹤依賴(lài),在屬性被訪問(wèn)和修改時(shí)通知變化。
每個(gè)組件實(shí)例會(huì)有相應(yīng)的 watcher 實(shí)例,會(huì)在組件渲染的過(guò)程中記錄依賴(lài)的所有數(shù)據(jù)屬性(進(jìn)行依賴(lài)收集,還有 computed watcher,user watcher 實(shí)例),之后依賴(lài)項(xiàng)被改動(dòng)時(shí),setter 方法會(huì)通知依賴(lài)與此 data 的 watcher 實(shí)例重新計(jì)算(派發(fā)更新),從而使它關(guān)聯(lián)的組件重新渲染。
一句話總結(jié):
vue.js 采用數(shù)據(jù)劫持結(jié)合發(fā)布-訂閱模式,通過(guò) Object.defineproperty 來(lái)劫持各個(gè)屬性的 setter,getter,在數(shù)據(jù)變動(dòng)時(shí)發(fā)布消息給訂閱者,觸發(fā)響應(yīng)的監(jiān)聽(tīng)回調(diào)
2. computed 的實(shí)現(xiàn)原理
computed 本質(zhì)是一個(gè)惰性求值的觀察者。
computed 內(nèi)部實(shí)現(xiàn)了一個(gè)惰性的 watcher,也就是 computed watcher,computed watcher 不會(huì)立刻求值,同時(shí)持有一個(gè) dep 實(shí)例。
其內(nèi)部通過(guò) this.dirty 屬性標(biāo)記計(jì)算屬性是否需要重新求值。
當(dāng) computed 的依賴(lài)狀態(tài)發(fā)生改變時(shí),就會(huì)通知這個(gè)惰性的 watcher,
computed watcher 通過(guò) this.dep.subs.length 判斷有沒(méi)有訂閱者,
有的話,會(huì)重新計(jì)算,然后對(duì)比新舊值,如果變化了,會(huì)重新渲染。 (Vue 想確保不僅僅是計(jì)算屬性依賴(lài)的值發(fā)生變化,而是當(dāng)計(jì)算屬性最終計(jì)算的值發(fā)生變化時(shí)才會(huì)觸發(fā)渲染 watcher 重新渲染,本質(zhì)上是一種優(yōu)化。)
沒(méi)有的話,僅僅把 this.dirty = true。 (當(dāng)計(jì)算屬性依賴(lài)于其他數(shù)據(jù)時(shí),屬性并不會(huì)立即重新計(jì)算,只有之后其他地方需要讀取屬性的時(shí)候,它才會(huì)真正計(jì)算,即具備 lazy(懶計(jì)算)特性。)
3. computed 和 watch 有什么區(qū)別及運(yùn)用場(chǎng)景?
computed 計(jì)算屬性 : 依賴(lài)其它屬性值,并且 computed 的值有緩存,只有它依賴(lài)的屬性值發(fā)生改變,下一次獲取 computed 的值時(shí)才會(huì)重新計(jì)算 computed 的值。
watch 偵聽(tīng)器 : 更多的是「觀察」的作用,無(wú)緩存性,類(lèi)似于某些數(shù)據(jù)的監(jiān)聽(tīng)回調(diào),每當(dāng)監(jiān)聽(tīng)的數(shù)據(jù)變化時(shí)都會(huì)執(zhí)行回調(diào)進(jìn)行后續(xù)操作。
運(yùn)用場(chǎng)景:
當(dāng)我們需要進(jìn)行數(shù)值計(jì)算,并且依賴(lài)于其它數(shù)據(jù)時(shí),應(yīng)該使用 computed,因?yàn)榭梢岳?computed 的緩存特性,避免每次獲取值時(shí),都要重新計(jì)算。
當(dāng)我們需要在數(shù)據(jù)變化時(shí)執(zhí)行異步或開(kāi)銷(xiāo)較大的操作時(shí),應(yīng)該使用 watch,使用 watch 選項(xiàng)允許我們執(zhí)行異步操作 ( 訪問(wèn)一個(gè) API ),限制我們執(zhí)行該操作的頻率,并在我們得到最終結(jié)果前,設(shè)置中間狀態(tài)。這些都是計(jì)算屬性無(wú)法做到的。
4. 為什么在 Vue3.0 采用了 Proxy,拋棄了 Object.defineProperty?
Object.defineProperty 本身有一定的監(jiān)控到數(shù)組下標(biāo)變化的能力,但是在 Vue 中,從性能/體驗(yàn)的性?xún)r(jià)比考慮,尤大大就棄用了這個(gè)特性(Vue 為什么不能檢測(cè)數(shù)組變動(dòng) )。為了解決這個(gè)問(wèn)題,經(jīng)過(guò) vue 內(nèi)部處理后可以使用以下幾種方法來(lái)監(jiān)聽(tīng)數(shù)組
push(); pop(); shift(); unshift(); splice(); sort(); reverse();
由于只針對(duì)了以上 7 種方法進(jìn)行了 hack 處理,所以其他數(shù)組的屬性也是檢測(cè)不到的,還是具有一定的局限性。
Object.defineProperty 只能劫持對(duì)象的屬性,因此我們需要對(duì)每個(gè)對(duì)象的每個(gè)屬性進(jìn)行遍歷。Vue 2.x 里,是通過(guò) 遞歸 + 遍歷 data 對(duì)象來(lái)實(shí)現(xiàn)對(duì)數(shù)據(jù)的監(jiān)控的,如果屬性值也是對(duì)象那么需要深度遍歷,顯然如果能劫持一個(gè)完整的對(duì)象是才是更好的選擇。Proxy 可以劫持整個(gè)對(duì)象,并返回一個(gè)新的對(duì)象。Proxy 不僅可以代理對(duì)象,還可以代理數(shù)組。還可以代理動(dòng)態(tài)增加的屬性。
5. Vue 中的 key 到底有什么用?
key 是給每一個(gè) vnode 的唯一 id,依靠 key,我們的 diff 操作可以更準(zhǔn)確、更快速 (對(duì)于簡(jiǎn)單列表頁(yè)渲染來(lái)說(shuō) diff 節(jié)點(diǎn)也更快,但會(huì)產(chǎn)生一些隱藏的副作用,比如可能不會(huì)產(chǎn)生過(guò)渡效果,或者在某些節(jié)點(diǎn)有綁定數(shù)據(jù)(表單)狀態(tài),會(huì)出現(xiàn)狀態(tài)錯(cuò)位。)
diff 算法的過(guò)程中,先會(huì)進(jìn)行新舊節(jié)點(diǎn)的首尾交叉對(duì)比,當(dāng)無(wú)法匹配的時(shí)候會(huì)用新節(jié)點(diǎn)的 key 與舊節(jié)點(diǎn)進(jìn)行比對(duì),從而找到相應(yīng)舊節(jié)點(diǎn).
更準(zhǔn)確 : 因?yàn)閹?key 就不是就地復(fù)用了,在 sameNode 函數(shù) a.key === b.key 對(duì)比中可以避免就地復(fù)用的情況。所以會(huì)更加準(zhǔn)確,如果不加 key,會(huì)導(dǎo)致之前節(jié)點(diǎn)的狀態(tài)被保留下來(lái),會(huì)產(chǎn)生一系列的 bug。
更快速 : key 的唯一性可以被 Map 數(shù)據(jù)結(jié)構(gòu)充分利用,相比于遍歷查找的時(shí)間復(fù)雜度 O(n),Map 的時(shí)間復(fù)雜度僅僅為 O(1),源碼如下:
function createKeyToOldIdx(children, beginIdx, endIdx) { let i, key; const map = {}; for (i = beginIdx; i <= endIdx; ++i) { key = children[i].key; if (isDef(key)) map[key] = i; } return map; }
6. 談一談 nextTick 的原理
JS 運(yùn)行機(jī)制
JS 執(zhí)行是單線程的,它是基于事件循環(huán)的。事件循環(huán)大致分為以下幾個(gè)步驟:
主線程的執(zhí)行過(guò)程就是一個(gè) tick,而所有的異步結(jié)果都是通過(guò) “任務(wù)隊(duì)列” 來(lái)調(diào)度。 消息隊(duì)列中存放的是一個(gè)個(gè)的任務(wù)(task)。 規(guī)范中規(guī)定 task 分為兩大類(lèi),分別是 macro task 和 micro task,并且每個(gè) macro task 結(jié)束后,都要清空所有的 micro task。
for (macroTask of macroTaskQueue) { // 1. Handle current MACRO-TASK handleMacroTask(); // 2. Handle all MICRO-TASK for (microTask of microTaskQueue) { handleMicroTask(microTask); } }
在瀏覽器環(huán)境中 :
常見(jiàn)的 macro task 有 setTimeout、MessageChannel、postMessage、setImmediate
常見(jiàn)的 micro task 有 MutationObsever 和 Promise.then
異步更新隊(duì)列
可能你還沒(méi)有注意到,Vue 在更新 DOM 時(shí)是異步執(zhí)行的。只要偵聽(tīng)到數(shù)據(jù)變化,Vue 將開(kāi)啟一個(gè)隊(duì)列,并緩沖在同一事件循環(huán)中發(fā)生的所有數(shù)據(jù)變更。
如果同一個(gè) watcher 被多次觸發(fā),只會(huì)被推入到隊(duì)列中一次。這種在緩沖時(shí)去除重復(fù)數(shù)據(jù)對(duì)于避免不必要的計(jì)算和 DOM 操作是非常重要的。
然后,在下一個(gè)的事件循環(huán)“tick”中,Vue 刷新隊(duì)列并執(zhí)行實(shí)際 (已去重的) 工作。
Vue 在內(nèi)部對(duì)異步隊(duì)列嘗試使用原生的 Promise.then、MutationObserver 和 setImmediate,如果執(zhí)行環(huán)境不支持,則會(huì)采用 setTimeout(fn, 0) 代替。
在 vue2.5 的源碼中,macrotask 降級(jí)的方案依次是:setImmediate、MessageChannel、setTimeout
vue 的 nextTick 方法的實(shí)現(xiàn)原理:
7. vue 是如何對(duì)數(shù)組方法進(jìn)行變異的 ?
我們先來(lái)看看源碼
const arrayProto = Array.prototype; export const arrayMethods = Object.create(arrayProto); 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; }); }); /** * Observe a list of Array items. */ Observer.prototype.observeArray = function observeArray(items) { for (var i = 0, l = items.length; i < l; i++) { observe(items[i]); } };
簡(jiǎn)單來(lái)說(shuō),Vue 通過(guò)原型攔截的方式重寫(xiě)了數(shù)組的 7 個(gè)方法,首先獲取到這個(gè)數(shù)組的ob,也就是它的 Observer 對(duì)象,如果有新的值,就調(diào)用 observeArray 對(duì)新的值進(jìn)行監(jiān)聽(tīng),然后手動(dòng)調(diào)用 notify,通知 render watcher,執(zhí)行 update
8. Vue 組件 data 為什么必須是函數(shù) ?
new Vue()實(shí)例中,data 可以直接是一個(gè)對(duì)象,為什么在 vue 組件中,data 必須是一個(gè)函數(shù)呢?
因?yàn)榻M件是可以復(fù)用的,JS 里對(duì)象是引用關(guān)系,如果組件 data 是一個(gè)對(duì)象,那么子組件中的 data 屬性值會(huì)互相污染,產(chǎn)生副作用。
所以一個(gè)組件的 data 選項(xiàng)必須是一個(gè)函數(shù),因此每個(gè)實(shí)例可以維護(hù)一份被返回對(duì)象的獨(dú)立的拷貝。new Vue 的實(shí)例是不會(huì)被復(fù)用的,因此不存在以上問(wèn)題。
9. 談?wù)?Vue 事件機(jī)制,手寫(xiě)$on,$off,$emit,$once
Vue 事件機(jī)制 本質(zhì)上就是 一個(gè) 發(fā)布-訂閱 模式的實(shí)現(xiàn)。
class Vue { constructor() { // 事件通道調(diào)度中心 this._events = Object.create(null); } $on(event, fn) { if (Array.isArray(event)) { event.map(item => { this.$on(item, fn); }); } else { (this._events[event] || (this._events[event] = [])).push(fn); } return this; } $once(event, fn) { function on() { this.$off(event, on); fn.apply(this, arguments); } on.fn = fn; this.$on(event, on); return this; } $off(event, fn) { if (!arguments.length) { this._events = Object.create(null); return this; } if (Array.isArray(event)) { event.map(item => { this.$off(item, fn); }); return this; } const cbs = this._events[event]; if (!cbs) { return this; } if (!fn) { this._events[event] = null; return this; } let cb; let i = cbs.length; while (i--) { cb = cbs[i]; if (cb === fn || cb.fn === fn) { cbs.splice(i, 1); break; } } return this; } $emit(event) { let cbs = this._events[event]; if (cbs) { const args = [].slice.call(arguments, 1); cbs.map(item => { args ? item.apply(this, args) : item.call(this); }); } return this; } }
10. 說(shuō)說(shuō) Vue 的渲染過(guò)程
11. 聊聊 keep-alive 的實(shí)現(xiàn)原理和緩存策略
export default { name: "keep-alive", abstract: true, // 抽象組件屬性 ,它在組件實(shí)例建立父子關(guān)系的時(shí)候會(huì)被忽略,發(fā)生在 initLifecycle 的過(guò)程中 props: { include: patternTypes, // 被緩存組件 exclude: patternTypes, // 不被緩存組件 max: [String, Number] // 指定緩存大小 }, created() { this.cache = Object.create(null); // 緩存 this.keys = []; // 緩存的VNode的鍵 }, destroyed() { for (const key in this.cache) { // 刪除所有緩存 pruneCacheEntry(this.cache, key, this.keys); } }, mounted() { // 監(jiān)聽(tīng)緩存/不緩存組件 this.$watch("include", val => { pruneCache(this, name => matches(val, name)); }); this.$watch("exclude", val => { pruneCache(this, name => !matches(val, name)); }); }, render() { // 獲取第一個(gè)子元素的 vnode const slot = this.$slots.default; const vnode: VNode = getFirstComponentChild(slot); const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions; if (componentOptions) { // name不在inlcude中或者在exlude中 直接返回vnode // 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; // 獲取鍵,優(yōu)先獲取組件的name字段,否則是組件的tag 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; // 命中緩存,直接從緩存拿vnode 的組件實(shí)例,并且重新調(diào)整了 key 的順序放在了最后一個(gè) if (cache[key]) { vnode.componentInstance = cache[key].componentInstance; // make current key freshest remove(keys, key); keys.push(key); } // 不命中緩存,把 vnode 設(shè)置進(jìn)緩存 else { cache[key] = vnode; keys.push(key); // prune oldest entry // 如果配置了 max 并且緩存的長(zhǎng)度超過(guò)了 this.max,還要從緩存中刪除第一個(gè) if (this.max && keys.length > parseInt(this.max)) { pruneCacheEntry(cache, keys[0], keys, this._vnode); } } // keepAlive標(biāo)記位 vnode.data.keepAlive = true; } return vnode || (slot && slot[0]); } };
原理
LRU 緩存淘汰算法
LRU(Least recently used)算法根據(jù)數(shù)據(jù)的歷史訪問(wèn)記錄來(lái)進(jìn)行淘汰數(shù)據(jù),其核心思想是“如果數(shù)據(jù)最近被訪問(wèn)過(guò),那么將來(lái)被訪問(wèn)的幾率也更高”。
keep-alive 的實(shí)現(xiàn)正是用到了 LRU 策略,將最近訪問(wèn)的組件 push 到 this.keys 最后面,this.keys[0]也就是最久沒(méi)被訪問(wèn)的組件,當(dāng)緩存實(shí)例超過(guò) max 設(shè)置值,刪除 this.keys[0]
12. vm.$set()實(shí)現(xiàn)原理是什么?
受現(xiàn)代 JavaScript 的限制 (而且 Object.observe 也已經(jīng)被廢棄),Vue 無(wú)法檢測(cè)到對(duì)象屬性的添加或刪除。
由于 Vue 會(huì)在初始化實(shí)例時(shí)對(duì)屬性執(zhí)行 getter/setter 轉(zhuǎn)化,所以屬性必須在 data 對(duì)象上存在才能讓 Vue 將它轉(zhuǎn)換為響應(yīng)式的。
對(duì)于已經(jīng)創(chuàng)建的實(shí)例,Vue 不允許動(dòng)態(tài)添加根級(jí)別的響應(yīng)式屬性。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套對(duì)象添加響應(yīng)式屬性。
那么 Vue 內(nèi)部是如何解決對(duì)象新增屬性不能響應(yīng)的問(wèn)題的呢?
export function set(target: Array<any> | Object, key: any, val: any): any { // target 為數(shù)組 if (Array.isArray(target) && isValidArrayIndex(key)) { // 修改數(shù)組的長(zhǎng)度, 避免索引>數(shù)組長(zhǎng)度導(dǎo)致splice()執(zhí)行有誤 target.length = Math.max(target.length, key); // 利用數(shù)組的splice變異方法觸發(fā)響應(yīng)式 target.splice(key, 1, val); return val; } // target為對(duì)象, key在target或者target.prototype上 且必須不能在 Object.prototype 上,直接賦值 if (key in target && !(key in Object.prototype)) { target[key] = val; return val; } // 以上都不成立, 即開(kāi)始給target創(chuàng)建一個(gè)全新的屬性 // 獲取Observer實(shí)例 const ob = (target: any).__ob__; // target 本身就不是響應(yīng)式數(shù)據(jù), 直接賦值 if (!ob) { target[key] = val; return val; } // 進(jìn)行響應(yīng)式處理 defineReactive(ob.value, key, val); ob.dep.notify(); return val; }
上述就是小編為大家分享的有哪些vue高頻原理面試題了,如果剛好有類(lèi)似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。