vue2.x高階問(wèn)題,你能答多少
有句老話說(shuō),在父母那里我們永遠(yuǎn)是孩子,同樣在各位大佬這里,我永遠(yuǎn)是菜雞??????。不管怎樣,學(xué)習(xí)的激情永遠(yuǎn)不可磨滅。答案如有錯(cuò)誤,感謝指教??
首發(fā)個(gè)人博客
種一棵樹(shù),最好的時(shí)機(jī)是十年前,其次是現(xiàn)在

vue源碼中值得學(xué)習(xí)的點(diǎn)
柯里化: 一個(gè)函數(shù)原本有多個(gè)參數(shù), 只傳入一個(gè)參數(shù), 生成一個(gè)新函數(shù), 由新函數(shù)接收剩下的參數(shù)來(lái)運(yùn)行得到結(jié)構(gòu)偏函數(shù): 一個(gè)函數(shù)原本有多個(gè)參數(shù), 只傳入一部分參數(shù), 生成一個(gè)新函數(shù), 由新函數(shù)接收剩下的參數(shù)來(lái)運(yùn)行得到結(jié)構(gòu)高階函數(shù): 一個(gè)函數(shù)參數(shù)是一個(gè)函數(shù), 該函數(shù)對(duì)參數(shù)這個(gè)函數(shù)進(jìn)行加工, 得到一個(gè)函數(shù), 這個(gè)加工用的函數(shù)就是高階函數(shù)...
vue 響應(yīng)式系統(tǒng)
簡(jiǎn)述:vue 初始化時(shí)會(huì)用Object.defineProperty()給data中每一個(gè)屬性添加getter和setter,同時(shí)創(chuàng)建dep和watcher進(jìn)行依賴收集與派發(fā)更新,最后通過(guò)diff算法對(duì)比新老vnode差異,通過(guò)patch即時(shí)更新DOM
簡(jiǎn)易圖解:

詳細(xì)版本
可以參考下圖片引用地址: 圖解 Vue 響應(yīng)式原理
Vue的數(shù)據(jù)為什么頻繁變化但只會(huì)更新一次
檢測(cè)到數(shù)據(jù)變化 開(kāi)啟一個(gè)隊(duì)列 在同一事件循環(huán)中緩沖所有數(shù)據(jù)改變 如果同一個(gè) watcher (watcherId相同)被多次觸發(fā),只會(huì)被推入到隊(duì)列中一次
不優(yōu)化,每一個(gè)數(shù)據(jù)變化都會(huì)執(zhí)行: setter->Dep->Watcher->update->run
優(yōu)化后:執(zhí)行順序update -> queueWatcher -> 維護(hù)觀察者隊(duì)列(重復(fù)id的Watcher處理) -> waiting標(biāo)志位處理 -> 處理$nextTick(在為微任務(wù)或者宏任務(wù)中異步更新DOM)
vue使用Object.defineProperty() 的缺陷
數(shù)組的length屬性被初始化configurable false,所以想要通過(guò)get/set方法來(lái)監(jiān)聽(tīng)length屬性是不可行的。
vue中通過(guò)重寫了七個(gè)能改變?cè)瓟?shù)組的方法來(lái)進(jìn)行數(shù)據(jù)監(jiān)聽(tīng)
對(duì)象還是使用Object.defineProperty()添加get和set來(lái)監(jiān)聽(tīng)
參考
為什么defineProperty不能檢測(cè)到數(shù)組長(zhǎng)度的變化
Vue.nextTick()原理
在下次DOM更新循環(huán)結(jié)束之后執(zhí)行延遲回調(diào)。在修改數(shù)據(jù)之后立即使用這個(gè)方法,獲取更新后的DOM。
源碼實(shí)現(xiàn):Promise > MutationObserver > setImmediate > setTimeout
參考文章:淺析Vue.nextTick()原理
computed 的實(shí)現(xiàn)原理
computed 本質(zhì)是一個(gè)惰性求值的觀察者computed watcher。其內(nèi)部通過(guò) this.dirty 屬性標(biāo)記計(jì)算屬性是否需要重新求值。
當(dāng) computed 的依賴狀態(tài)發(fā)生改變時(shí),就會(huì)通知這個(gè)惰性的 watcher, computed watcher通過(guò)this.dep.subs.length判斷有沒(méi)有訂閱者,有的話,會(huì)重新計(jì)算,然后對(duì)比新舊值,如果變化了,會(huì)重新渲染。(Vue 想確保不僅僅是計(jì)算屬性依賴的值發(fā)生變化,而是當(dāng)計(jì)算屬性 最終計(jì)算的值發(fā)生變化時(shí)才會(huì)觸發(fā)渲染 watcher重新渲染,本質(zhì)上是一種優(yōu)化。)沒(méi)有的話,僅僅把 this.dirty = true(當(dāng)計(jì)算屬性依賴于其他數(shù)據(jù)時(shí),屬性并不會(huì)立即重新計(jì)算,只有之后其他地方需要讀取屬性的時(shí)候,它才會(huì)真正計(jì)算,即具備 lazy(懶計(jì)算)特性。)
watch 的理解
watch沒(méi)有緩存性,更多的是觀察的作用,可以監(jiān)聽(tīng)某些數(shù)據(jù)執(zhí)行回調(diào)。當(dāng)我們需要深度監(jiān)聽(tīng)對(duì)象中的屬性時(shí),可以打開(kāi)deep:true選項(xiàng),這樣便會(huì)對(duì)對(duì)象中的每一項(xiàng)進(jìn)行監(jiān)聽(tīng)。這樣會(huì)帶來(lái)性能問(wèn)題,優(yōu)化的話可以使用字符串形式監(jiān)聽(tīng)
注意:Watcher : 觀察者對(duì)象 , 實(shí)例分為渲染 watcher (render watcher),計(jì)算屬性 watcher (computed watcher),偵聽(tīng)器 watcher(user watcher)三種
vue diff 算法
只對(duì)比父節(jié)點(diǎn)相同的新舊子節(jié)點(diǎn)(比較的是Vnode),時(shí)間復(fù)雜度只有O(n) 在 diff 比較的過(guò)程中,循環(huán)從兩邊向中間收攏
新舊節(jié)點(diǎn)對(duì)比過(guò)程
1、先找到 不需要移動(dòng)的相同節(jié)點(diǎn),借助key值找到可復(fù)用的節(jié)點(diǎn)是,消耗最小
2、再找相同但是需要移動(dòng)的節(jié)點(diǎn),消耗第二小
3、最后找不到,才會(huì)去新建刪除節(jié)點(diǎn),保底處理
注意:新舊節(jié)點(diǎn)對(duì)比過(guò)程,不會(huì)對(duì)這兩棵Vnode樹(shù)進(jìn)行修改,而是以比較的結(jié)果直接對(duì) 真實(shí)DOM 進(jìn)行修改
Vue的patch是即時(shí)的,并不是打包所有修改最后一起操作DOM(React則是將更新放入隊(duì)列后集中處理)
參考文章:Vue 虛擬dom diff原理詳解
vue 渲染過(guò)程

調(diào)用 compile函數(shù),生成 render 函數(shù)字符串 ,編譯過(guò)程如下:
parse 使用大量的正則表達(dá)式對(duì)template字符串進(jìn)行解析,將標(biāo)簽、指令、屬性等轉(zhuǎn)化為抽象語(yǔ)法樹(shù)AST。 模板 -> AST (最消耗性能)optimize 遍歷AST,找到其中的一些靜態(tài)節(jié)點(diǎn)并進(jìn)行標(biāo)記,方便在頁(yè)面重渲染的時(shí)候進(jìn)行diff比較時(shí),直接跳過(guò)這一些靜態(tài)節(jié)點(diǎn), 優(yōu)化runtime的性能generate 將最終的AST轉(zhuǎn)化為render函數(shù)字符串
調(diào)用 new Watcher函數(shù),監(jiān)聽(tīng)數(shù)據(jù)的變化,當(dāng)數(shù)據(jù)發(fā)生變化時(shí),Render 函數(shù)執(zhí)行生成 vnode 對(duì)象調(diào)用 patch方法,對(duì)比新舊 vnode 對(duì)象,通過(guò) DOM diff 算法,添加、修改、刪除真正的 DOM 元素
結(jié)合源碼,談一談vue生命周期
vue 生命周期官方圖解
Vue 中的 key 到底有什么用?
key 是給每一個(gè) vnode 的唯一 id,依靠 key,我們的 diff 操作可以更準(zhǔ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;
}
注意:在沒(méi)有key的情況下,會(huì)更快。感謝評(píng)論區(qū)老哥fengyangyang的提醒:引用官網(wǎng)的話:key 的特殊 attribute 主要用在 Vue 的虛擬 DOM 算法,在新舊 nodes 對(duì)比時(shí)辨識(shí) VNodes。如果不使用 key,Vue 會(huì)使用一種最大限度減少動(dòng)態(tài)元素并且盡可能的嘗試就地修改/復(fù)用相同類型元素的算法。而使用 key 時(shí),它會(huì)基于 key 的變化重新排列元素順序,并且會(huì)移除 key 不存在的元素。
vue-router 路由模式有幾種
默認(rèn)值: "hash" (瀏覽器環(huán)境) | "abstract" (Node.js 環(huán)境)
可選值: "hash" | "history" | "abstract"
配置路由模式:
hash: 使用 URL hash 值來(lái)作路由。支持所有瀏覽器,包括不支持 HTML5 History Api 的瀏覽器。history: 依賴HTML5 History API和服務(wù)器配置。abstract: 支持所有 JavaScript 運(yùn)行環(huán)境,如 Node.js 服務(wù)器端。如果發(fā)現(xiàn)沒(méi)有瀏覽器的 API,路由會(huì)自動(dòng)強(qiáng)制進(jìn)入這個(gè)模式.
說(shuō)一說(shuō)keep-alive實(shí)現(xiàn)原理
定義
keep-alive組件接受三個(gè)屬性參數(shù):include、exclude、max
include指定需要緩存的組件name集合,參數(shù)格式支持String, RegExp, Array。當(dāng)為字符串的時(shí)候,多個(gè)組件名稱以逗號(hào)隔開(kāi)。exclude指定不需要緩存的組件name集合,參數(shù)格式和include一樣。max指定最多可緩存組件的數(shù)量,超過(guò)數(shù)量刪除第一個(gè)。參數(shù)格式支持String、Number。
原理
keep-alive實(shí)例會(huì)緩存對(duì)應(yīng)組件的VNode,如果命中緩存,直接從緩存對(duì)象返回對(duì)應(yīng)VNode
LRU(Least recently used)算法根據(jù)數(shù)據(jù)的歷史訪問(wèn)記錄來(lái)進(jìn)行淘汰數(shù)據(jù),其核心思想是“如果數(shù)據(jù)最近被訪問(wèn)過(guò),那么將來(lái)被訪問(wèn)的幾率也更高”。(墨菲定律:越擔(dān)心的事情越會(huì)發(fā)生)
對(duì)對(duì)象屬性訪問(wèn)的解析方法
eg:訪問(wèn) a.b.c.d
函數(shù)柯里化 + 閉包 + 遞歸
function createGetValueByPath( path ) {
let paths = path.split( '.' ); // [ xxx, yyy, zzz ]
return function getValueByPath( obj ) {
let res = obj;
let prop;
while( prop = paths.shift() ) {
res = res[ prop ];
}
return res;
}
}
let getValueByPath = createGetValueByPath( 'a.b.c.d' );
var o = {
a: {
b: {
c: {
d: {
e: '正確了'
}
}
}
}
};
var res = getValueByPath( o );
console.log( res );
vue中針對(duì)7個(gè)數(shù)組方法的重寫
Vue 通過(guò)原型攔截的方式重寫了數(shù)組的 7 個(gè)方法,首先獲取到這個(gè)數(shù)組的Observer。如果有新的值,就調(diào)用 observeArray 對(duì)新的值進(jìn)行監(jiān)聽(tīng),然后調(diào)用 notify,通知 render watcher,執(zhí)行 update
const arrayProto = Array.prototype;
export const arrayMethods = Object.create(arrayProto);
const methodsToPatch = [
"push",
"pop",
"shift",
"unshift",
"splice",
"sort",
"reverse"
];
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;
});
});
Observer.prototype.observeArray = function observeArray(items) {
for (var i = 0, l = items.length; i < l; i++) {
observe(items[i]);
}
};
vue處理響應(yīng)式 defineReactive 實(shí)現(xiàn)
// 簡(jiǎn)化后的版本
function defineReactive( target, key, value, enumerable ) {
// 折中處理后, this 就是 Vue 實(shí)例
let that = this;
// 函數(shù)內(nèi)部就是一個(gè)局部作用域, 這個(gè) value 就只在函數(shù)內(nèi)使用的變量 ( 閉包 )
if ( typeof value === 'object' && value != null && !Array.isArray( value ) ) {
// 是非數(shù)組的引用類型
reactify( value ); // 遞歸
}
Object.defineProperty( target, key, {
configurable: true,
enumerable: !!enumerable,
get () {
console.log( `讀取 ${key} 屬性` ); // 額外
return value;
},
set ( newVal ) {
console.log( `設(shè)置 ${key} 屬性為: ${newVal}` ); // 額外
value = reactify( newVal );
}
} );
}
vue響應(yīng)式 reactify 實(shí)現(xiàn)
// 將對(duì)象 o 響應(yīng)式化
function reactify( o, vm ) {
let keys = Object.keys( o );
for ( let i = 0; i < keys.length; i++ ) {
let key = keys[ i ]; // 屬性名
let value = o[ key ];
if ( Array.isArray( value ) ) {
// 數(shù)組
value.__proto__ = array_methods; // 數(shù)組就響應(yīng)式了
for ( let j = 0; j < value.length; j++ ) {
reactify( value[ j ], vm ); // 遞歸
}
} else {
// 對(duì)象或值類型
defineReactive.call( vm, o, key, value, true );
}
}
}
為什么訪問(wèn)data屬性不需要帶data
vue中訪問(wèn)屬性代理this.data.xxx 轉(zhuǎn)換 this.xxx的實(shí)現(xiàn)
/** 將 某一個(gè)對(duì)象的屬性 訪問(wèn) 映射到 對(duì)象的某一個(gè)屬性成員上 */
function proxy( target, prop, key ) {
Object.defineProperty( target, key, {
enumerable: true,
configurable: true,
get () {
return target[ prop ][ key ];
},
set ( newVal ) {
target[ prop ][ key ] = newVal;
}
} );
}
原文地址
https://juejin.cn/post/6921911974611664903]
