最近利用空閑時間又翻看了一遍Vue的源碼,只不過這次不同的是看了Flow版本的源碼承桥。說來慚愧凶异,最早看的第一遍時對Flow不了解挤巡,因此閱讀的是打包之后的vue文件矿卑,大家可以想象這過程的痛苦母廷,沒有類型的支持琴昆,看代碼時摸索了很長時間,所以我們這次對Vue源碼的剖析是Flow版本的源碼抖拦,也就是從Github上下載下來的源碼中src目錄下的代碼。不過噩茄,在分析之前绩聘,我想先說說閱讀Vue源碼所需要的一些知識點(diǎn)君纫,掌握這些知識點(diǎn)之后蓄髓,相信再閱讀源碼會較為輕松会喝。
1. 前置知識點(diǎn)
我個人認(rèn)為要想深入理解Vue的源碼肢执,至少需要以下知識點(diǎn):
下面咱們一一介紹
1.1 Flow基本語法
相信大家都知道预茄,javascript是弱類型的語言耻陕,在寫代碼灰常爽的同時也十分容易犯錯誤刨沦,所以Facebook搞了這么一個類型檢查工具想诅,可以加入類型的限制来破,提高代碼質(zhì)量,舉個例子:
function sum(a, b) {
return a + b;
}
可是這樣诅诱,我們?nèi)绻@么調(diào)用這個函數(shù)sum('a', 1) 甚至sum(1, [1,2,3])這么調(diào)用逢艘,執(zhí)行時會得到一些你想不到的結(jié)果它改,這樣編程未免太不穩(wěn)定了央拖。那我們看看用了Flow之后的結(jié)果:
function sum(a: number, b:number) {
return a + b;
}
我們可以看到多了一個number的限制鲜戒,標(biāo)明對a和b只能傳遞數(shù)字類型的,否則的話用Flow工具檢測會報錯抹凳。其實(shí)這里大家可能有疑問赢底,這么寫還是js嗎? 瀏覽器還能認(rèn)識執(zhí)行嗎粹庞?當(dāng)然不認(rèn)識了庞溜,所以需要翻譯或者說編譯流码。其實(shí)現(xiàn)在前端技術(shù)發(fā)展太快了旅掂,各種插件層出不窮--Babel商虐、Typescript等等崖疤,其實(shí)都是將一種更好的寫法編譯成瀏覽器認(rèn)識的javascript代碼(我們以前都是寫瀏覽器認(rèn)識的javascript代碼的)叮趴。我們繼續(xù)說Flow的事情眯亦,在Vue源碼中其實(shí)出現(xiàn)的Flow語法都比較好懂妻率,比如下面這個函數(shù)的定義:
export function renderList (
val: any,
render: (
val: any,
keyOrIndex: string | number,
index?: number
) => VNode
): ?Array<VNode>{
...
}
val是any代表可以傳入的類型是任何類型宫静, keyOrIndex是string|number類型孤里,代表要不是string類型捌袜,要不是number蜓堕,不能是別的套才;index?:number這個我們想想正則表達(dá)式中背伴?的含義---0個或者1個傻寂,這里其實(shí)意義也是一致的疾掰,但是要注意?的位置是在冒號之前還是冒號之后--因?yàn)檫@兩種可能性都有静檬,上面代碼中問號是跟在冒號前面,代表index可以不傳稻励,但是傳的話一定要傳入數(shù)字類型加矛;如果問號是在冒號后面的話,則代表這個參數(shù)必須要傳遞,但是可以是數(shù)字類型也可以是空。這樣是不是頓時感覺嚴(yán)謹(jǐn)多了草戈?同時丙猬,代碼意義更明確了茧球。為啥這么說呢抢埋? 之前看打包后的vue源碼,其中看到觀察者模式實(shí)現(xiàn)時由于沒有類型十分難看懂,但是看了這個Flow版本的源碼酷愧,感覺容易懂伟墙。 當(dāng)然戳葵,如果想學(xué)習(xí)Flow更多的細(xì)節(jié)生蚁, 可以看看下面這個學(xué)習(xí)文檔:
Flow學(xué)習(xí)資料
1.2 原型與原型繼承
Vue中的組件相信大家都使用過,并且組件之中可以有子組件志衣,那么這里就涉及到父子組件了念脯。組件其實(shí)初始化過程都是一樣的,顯然有些方法是可以繼承的假勿。Vue代碼中是使用原型繼承的方式實(shí)現(xiàn)父子組件共享初始化代碼的。所以堡距,要看懂這里羽戒,需要了解js中原型的概念易稠;這里不多談驶社,只是提供幾個學(xué)習(xí)資料供大家參考:
廖雪峰js教程
js原型理解
1.3 Object.defineProperty
這個方法在js中十分強(qiáng)大亡电,Vue正是使用了它實(shí)現(xiàn)了響應(yīng)式數(shù)據(jù)功能。我們先瞄一眼Vue中定義響應(yīng)式數(shù)據(jù)的代碼:
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
.....
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
其中我們看到Object.defineProperty這個函數(shù)的運(yùn)用,其中第一個參數(shù)代表要設(shè)置的對象颂暇,第二個參數(shù)代表要設(shè)置的對象的鍵值,第三個參數(shù)是一個配置對象阳啥,對象里面可以設(shè)置參數(shù)如下:
value: 對應(yīng)key的值苫纤,無需多言
configurable:是否可以刪除該key或者重新配置該key
enumerable:是否可以遍歷該key
writable:是否可以修改該key
get: 獲取該key值時調(diào)用的函數(shù)
set: 設(shè)置該key值時調(diào)用的函數(shù)
我們通過一個例子來了解一下這些屬性:
let x = {}
x['name'] = 'vue'
console.log(Object.getOwnPropertyDescriptor(x,'name'))
Object.getOwnPropertyDescriptor可以獲取對象某個key的描述對象,打印結(jié)果如下:
{
value: "vue",
writable: true,
enumerable: true,
configurable: true
}
從上可知祝高,該key對應(yīng)的屬性我們可以改寫(writable:true),可以重新設(shè)置或者刪除(configurable: true),同時可以遍歷(enumerable:true)。那么讓我們修改一下這些屬性,比如configurable,代碼如下:
Object.defineProperty(x, 'name', {
configurable: false
})
執(zhí)行成功之后叠殷,如果你再想刪除該屬性,比如delete x['name']壶冒,你會發(fā)現(xiàn)返回為false胖腾,即無法刪除了瓶摆。
那enumerable是什么意思呢?來個例子就明白了书斜,代碼如下:
let x = {}
x[1] = 2
x[2] = 4
Object.defineProperty(x, 2, {
enumerable: false
})
for(let key in x){
console.log("key:" + key + "|value:" + x[key])
}
結(jié)果如下:
key:1|value:2
為什么呢荐吉? 因?yàn)槲覀儼?設(shè)置為不可遍歷了缺脉,那么我們的for循環(huán)就取不到了攻礼,當(dāng)然我們還是可以用x[2]去取到2對應(yīng)的值得礁扮,只是for循環(huán)中取不到而已雇锡。這個有什么用呢锰提?Vue源碼中Observer類中有下面一行代碼:
def(value, '__ob__', this);
這里def是個工具函數(shù),目的是想給value添加一個key為__ob__赛不,值為this文黎,但是為什么不直接 value.__ob__ = this 反而要大費(fèi)周章呢耸峭?
因?yàn)槌绦蛳旅嬉闅vvalue對其子內(nèi)容進(jìn)行遞歸設(shè)置洽瞬,如果直接用value.__ob__這種方式伙窃,在遍歷時又會取到造成为障,這顯然不是本意呻右,所以def函數(shù)是利用Object.defineProperty給value添加的屬性窿冯,同時enumerable設(shè)置為false。
至于get和set嘛?這個就更強(qiáng)大了,類似于在獲取對象值和設(shè)置對象值時加了一個代理错蝴,在這個代理函數(shù)中可以做的東西你就可以想象了顷锰,比如設(shè)置值時再通知一下View視圖做更新。也來個例子體會一下吧:
let x = {}
Object.defineProperty(x, 1, {
get: function(){
console.log("getter called!")
},
set: function(newVal){
console.log("setter called! newVal is:" + newVal)
}
})
當(dāng)我們訪問x[1]時便會打印getter called束世,當(dāng)我們設(shè)置x[1] = 2時,打印setter called贫堰。Vue源碼正是通過這種方式實(shí)現(xiàn)了訪問屬性時收集依賴粱檀,設(shè)置屬性時源碼有一句dep.notify茄蚯,里面便是通知視圖更新的相關(guān)操作汗盘。
1.4 Vnode概念
Vnode隐孽,顧名思義踢俄,Virtual node都办,虛擬節(jié)點(diǎn)琳钉,首先聲明胰蝠,這不是Vue自己首創(chuàng)的概念,其實(shí)Github上早就有一個類似的項(xiàng)目:Snabbdom查剖。我個人認(rèn)為,Vue應(yīng)該也參考過這個庫的實(shí)現(xiàn)直砂,因?yàn)檫@個庫包含了完整的Vnode以及dom diff算法静暂,甚至實(shí)現(xiàn)的具體代碼上感覺Vue和這個庫也是有點(diǎn)相像的。為啥要用Vnode呢郊供?其實(shí)原因主要是原生的dom節(jié)點(diǎn)對象太大了,我們運(yùn)行一下代碼:
let dom = document.createElement('div');
for(let key in dom){
console.log(key)
}
打印的結(jié)果灰常長7枰?恳帧荠列!說明這個dom對象節(jié)點(diǎn)有點(diǎn)重量級,而我們的html網(wǎng)頁經(jīng)常數(shù)以百計個這種dom節(jié)點(diǎn)川队,如果采用之前的Jquery這種方式直接操作dom眠蚂,性能上確實(shí)稍微low一點(diǎn)。所以snabbdom或者Vue中應(yīng)用了Vnode笛臣,Vnode對象啥樣呢沈堡? 看看Vue源碼對Vnode的定義:
export default class VNode {
tag: string | void;
data: VNodeData | void;
children: ?Array<VNode>;
text: string | void;
elm: Node | void;
ns: string | void;
context: Component | void; // rendered in this component's scope
key: string | number | void;
componentOptions: VNodeComponentOptions | void;
componentInstance: Component | void; // component instance
parent: VNode | void; // component placeholder node
// strictly internal
raw: boolean; // contains raw HTML? (server only)
isStatic: boolean; // hoisted static node
isRootInsert: boolean; // necessary for enter transition check
isComment: boolean; // empty comment placeholder?
isCloned: boolean; // is a cloned node?
isOnce: boolean; // is a v-once node?
asyncFactory: Function | void; // async component factory function
asyncMeta: Object | void;
isAsyncPlaceholder: boolean;
ssrContext: Object | void;
fnContext: Component | void; // real context vm for functional nodes
fnOptions: ?ComponentOptions; // for SSR caching
fnScopeId: ?string;
....
}
相比之下, Vnode對象的屬性確實(shí)少了很多率拒;其實(shí)光屬性少也不見得性能就能高到哪兒去猬膨,另一個方面便是針對新舊Vnode的diff算法了。這里其實(shí)有一個現(xiàn)象:其實(shí)大多數(shù)場景下即便有很多修改沛申,但是如果從宏觀角度觀看铁材,其實(shí)修改的點(diǎn)不多惊暴。舉個例子:
比如有以下三個dom節(jié)點(diǎn)A B C
我們的操作中依次會改成 B C D
如果采用Jquery的改法辽话,當(dāng)碰到第一次A改為B時,修改了一次忽肛,再碰到B改為C,又修改了一次罕模,再次碰到C改為D,又又修改了一次抛腕,顯然其實(shí)從宏觀上看担敌,只需要刪除A,然后末尾加上D即可刹悴,修改次數(shù)得到減少土匀;但是這種優(yōu)化是有前提的,也就是說能夠從宏觀角度看才行钓丰。以前Jquery的修改方法在碰到第一次修改的時候兰怠,需要把A改為B肥橙,這時代碼還沒有執(zhí)行到后面,它是不可能知道后面的修改的椭坚,也就是無法以全局視角看問題。所以從全局看問題的方式就是異步垂涯,先把修改放到隊列中航邢,然后整成一批去修改鞠苟,做diff当娱,這個時候從統(tǒng)計學(xué)意義上來講確實(shí)可以優(yōu)化性能。這也是為啥Vue源碼中出現(xiàn)下述代碼的原因:
queueWatcher(this);
1.5 函數(shù)柯里化
函數(shù)柯里化是什么鬼呢冀惭?其實(shí)就是將多參數(shù)的函數(shù)化作多個部分函數(shù)去調(diào)用。舉個例子:
function getSum(a,b){
return a+b;
}
這是個兩個參數(shù)的函數(shù)戚丸,可以直接getSum(1,2)調(diào)用拿到結(jié)果夺颤;然而,有時候并不會兩個參數(shù)都能確定寥裂,只想先傳一個值,另外一個在其他時間點(diǎn)再傳入俭驮,那我們把函數(shù)改為:
function getSum(a){
return function(b){
return a+b;
}
}
那我們?nèi)绾握{(diào)用這個柯里化之后的函數(shù)呢?
let f = getSum(2)
console.log(f(3))
console.log(getSum(2)(3)) //結(jié)果同上
可見逸嘀,柯里化的效果便是之前必須同時傳入兩個參數(shù)才能調(diào)用成功而現(xiàn)在兩個參數(shù)可以在不同時間點(diǎn)傳入崭倘。那為毛要這么做嘛悉患?Vue源碼是這么應(yīng)用這個特性的售躁,Vue源碼中有一個platform目錄回窘,專門存放和平臺相關(guān)的源碼(Vue可以在多平臺上運(yùn)行 比如Weex)啡直。那這些源碼中肯定有些操作是和平臺相關(guān)的烹玉,比如會有些以下偽代碼所表示的邏輯:
if(平臺A){
....
}else if(平臺B){
....
}
可是如果這么寫會有個小不舒服的地方,那就是其實(shí)代碼運(yùn)行時第一次走到這里根據(jù)當(dāng)前平臺就已經(jīng)知道走哪一個分支了,而現(xiàn)在這么寫必當(dāng)導(dǎo)致代碼再次運(yùn)行到這里的時候還會進(jìn)行平臺判斷装获,這樣總感覺會多一些無聊的多余判斷,因此Vue解決此問題的方式就是應(yīng)用了函數(shù)柯里化技巧精肃,類似聲明了以下一個函數(shù):
function ...(平臺相關(guān)參數(shù)){
return function(平臺不相關(guān)參數(shù)){
處理邏輯
}
}
在Vue的patch以及編譯環(huán)節(jié)都應(yīng)用了這種方式,講到那部分代碼時我們再細(xì)致的看习柠,讀者提前先了解一下可以幫助理解Vue的設(shè)計。
1.6 Macrotask與Microtask
可能有的讀者第一次聽到這兩個詞烈炭,實(shí)際上這個和js的事件循環(huán)機(jī)制息息相關(guān)。在上面我們也提到膏执,Vue更新不是數(shù)據(jù)一改馬上同步更新視圖的欺栗,這樣肯定會有性能問題,比如在一個事件處理函數(shù)里先this.data = A 然后再this.data=B,如果要渲染兩次类腮,想想都感覺很low。Vue源碼實(shí)際上是將更改都放入到隊列中厂抽,同一個watcher不會重復(fù)(不理解這些概念不要緊昭殉,后面源碼會重點(diǎn)介紹),然后異步處理更新邏輯吃靠。在實(shí)現(xiàn)異步的方式時族奢,js實(shí)際提供了兩種task--Macrotask與Microtask靠欢。兩種task有什么區(qū)別呢掷空?先從一個例子講起:
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
Promise.resolve().then(function() {
console.log('promise3');
}).then(function() {
console.log('promise4');
});
}, 0);
Promise.resolve().then(function() {
console.log('promise1');
}).then(function() {
console.log('promise2');
});
console.log('script end');
以上代碼運(yùn)行結(jié)果是什么呢驱入?讀者可以思考一下可霎,答案應(yīng)該是:
script start
script end
promise1
promise2
setTimeout
promise3
promise4
簡單可以這么理解癣朗,js事件循環(huán)中有兩個隊列绢记,一個叫MacroTask,一個MircroTask签孔,看名字就知道Macro是大的,Micro是小的(想想宏觀經(jīng)濟(jì)學(xué)和微觀經(jīng)濟(jì)學(xué)的翻譯)罐盔。那么大任務(wù)隊列跑大任務(wù)--比如主流程程序了、事件處理函數(shù)了纬黎、setTimeout了等等莹桅,小任務(wù)隊列跑小任務(wù)煤禽,目前讀者記住一個就可以--Promise。js總是先從大任務(wù)隊列拿一個執(zhí)行选脊,然后再把所有小任務(wù)隊列全部執(zhí)行再循環(huán)往復(fù)恳啥。以上面示例程序钝的,首先整體上個這個程序是一個大任務(wù)先執(zhí)行,執(zhí)行完畢后要執(zhí)行所有小任務(wù)碗脊,Promise就是小任務(wù)橄妆,所以又打印出promise1和promise2呼畸,而setTimeout是大任務(wù),所以執(zhí)行完所有小任務(wù)之后卧须,再取一個大任務(wù)執(zhí)行儒陨,就是setTimeout,這里面又往小任務(wù)隊列扔了一個Promise椭员,所以等setTimeout執(zhí)行完畢之后笛园,又去執(zhí)行所有小任務(wù)隊列侍芝,所以最后是promise3和promise4埋同。說的有點(diǎn)繞虱肄,把上面示例程序拷貝到瀏覽器執(zhí)行一下多思考一下就明白了,關(guān)鍵是要知道上面程序本身也是一個大任務(wù)纸淮。一定要理解了之后再去看Vue源碼侈沪,否則不會理解Vue中的nextTick函數(shù)歼秽。
推薦幾篇文章吧(我都認(rèn)真讀完了肆氓,受益匪淺)
Macrotask Vs Microtask
理解js中Macrotask和Microtask
阮一峰 Eventloop理解
1.7 遞歸編程算法
很多程序員比較害怕遞歸,但是遞歸真的是一種灰常灰常強(qiáng)大的算法。Vue源碼中大量使用了遞歸算法--比如dom diff算法、ast的優(yōu)化鼻百、目標(biāo)代碼的生成等等....很多很多。而且這些遞歸不僅僅是A->A這么簡單琐鲁,大多數(shù)源碼中的遞歸是A->B->C...->A等等這種復(fù)雜遞歸調(diào)用奈泪。比如Vue中經(jīng)典的dom diff算法:
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
if (isUndef(oldStartVnode)) {
oldStartVnode = oldCh[++oldStartIdx]; // Vnode has been moved left
} else if (isUndef(oldEndVnode)) {
oldEndVnode = oldCh[--oldEndIdx];
} else if (sameVnode(oldStartVnode, newStartVnode)) {
patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue);
oldStartVnode = oldCh[++oldStartIdx];
newStartVnode = newCh[++newStartIdx];
} else if (sameVnode(oldEndVnode, newEndVnode)) {
patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue);
oldEndVnode = oldCh[--oldEndIdx];
newEndVnode = newCh[--newEndIdx];
} else if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right
patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue);
canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm));
oldStartVnode = oldCh[++oldStartIdx];
newEndVnode = newCh[--newEndIdx];
} else if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left
patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue);
canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm);
oldEndVnode = oldCh[--oldEndIdx];
newStartVnode = newCh[++newStartIdx];
} else {
if (isUndef(oldKeyToIdx)) { oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx); }
idxInOld = isDef(newStartVnode.key)
? oldKeyToIdx[newStartVnode.key]
: findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx);
if (isUndef(idxInOld)) { // New element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx);
} else {
vnodeToMove = oldCh[idxInOld];
if (sameVnode(vnodeToMove, newStartVnode)) {
patchVnode(vnodeToMove, newStartVnode, insertedVnodeQueue);
oldCh[idxInOld] = undefined;
canMove && nodeOps.insertBefore(parentElm, vnodeToMove.elm, oldStartVnode.elm);
} else {
// same key but different element. treat as new element
createElm(newStartVnode, insertedVnodeQueue, parentElm, oldStartVnode.elm, false, newCh, newStartIdx);
}
}
newStartVnode = newCh[++newStartIdx];
}
上面代碼是比較新舊Vnode節(jié)點(diǎn)更新孩子節(jié)點(diǎn)的部分源碼究反,調(diào)用者是patchVnode函數(shù)琅锻,我們發(fā)現(xiàn)這部分函數(shù)中又會調(diào)用會patchVnode,調(diào)用鏈條為:patchVnode->updateChildren->patchVnode。同時疮方,即便沒有直接應(yīng)用遞歸,在將模板編譯成AST(抽象語法樹)的過程中石挂,其使用了棧去模擬了遞歸的思想拯腮,由此可見遞歸算法的重要性琼懊。這也難怪,畢竟不管是真實(shí)dom還是vnode车胡,其實(shí)本質(zhì)都是樹狀結(jié)構(gòu),本來就是遞歸定義的東西笑旺。我們也會單獨(dú)拿出一篇文章講講遞歸鸟蟹,比如用遞歸實(shí)現(xiàn)一下JSON串的解析藤韵。希望讀者注意查看。
1.8 編譯原理基礎(chǔ)知識
這恐怕比遞歸更讓某些程序員蛋疼,但是我相信只要讀者認(rèn)真把Vue這部分代碼看懂,絕對比看N遍編譯原理的課本更能管用。我們看看Vue源碼這里的實(shí)現(xiàn):
const ast = parse(template.trim(), options)
if (options.optimize !== false) {
optimize(ast, options)
}
const code = generate(ast, options)
return {
ast,
render: code.render,
staticRenderFns: code.staticRenderFns
}
上述代碼首先通過parse函數(shù)將template編譯為抽象語法樹ast檩互,然后對ast進(jìn)行代碼優(yōu)化饵较,最后生成render函數(shù)。其實(shí)這個過程就是翻譯锌畸,比如gcc把c語言翻譯為匯編、又比如Babel把ES6翻譯為ES5等等,這里面的流程十分都是十分地相似锭魔。Vue也玩了這么一把,把模板html編譯為render函數(shù),什么意思呢中符?
<li v-for="record in commits">
<span class="date">{{record.commit.author.date}}</span>
</li>
比如上面的html亚再,你覺得瀏覽器會認(rèn)識嘛棍现?顯然v-for不是html原生的屬性娄柳,上述代碼如果直接在瀏覽器運(yùn)行需了,你會發(fā)現(xiàn){{record.commit.author.date}}就直接展示出來了锚烦,v-for也沒有起作用,當(dāng)然還是會出現(xiàn)html里面(畢竟html容錯性很高的);但是經(jīng)過Vue的編譯系統(tǒng)一編譯生成一些函數(shù)大咱,這些函數(shù)一執(zhí)行就是瀏覽器認(rèn)識的html元素了,神奇吧? 其實(shí)僅僅是應(yīng)用了編譯原理課本的部分知識罷了欺抗,這部分我們后面會灰常灰常詳細(xì)的介紹源碼志鹃,只要跟著看下來,必定會對編譯過程有所理解⊥勺牛現(xiàn)在可以這么簡單理解一下AST(抽象語法樹)零聚,比如java可以寫一個if判斷,C語言也可以寫,js、python等等也可以(如下所示):
java:
if(x > 5){
....
}
python:
if x>5:
....
雖然從語法形式上寫法不太一致枫慷,但是抽象出共同點(diǎn)其實(shí)都是一個if語句跟著一個x>5 的條件顿颅,那么ast就是一種表現(xiàn)大家共同點(diǎn)的一種結(jié)構(gòu)绍些。得到ast是翻譯的基礎(chǔ)氮帐。
綜上参咙,Vue源碼其實(shí)代碼行數(shù)并不是很多,但是其簡約凝練的風(fēng)格深深吸引了我。我會重點(diǎn)分析Vue源碼中觀察者模式的實(shí)現(xiàn)、Vnode以及dom diff算法的實(shí)現(xiàn)以及模板編譯為render函數(shù)的實(shí)現(xiàn)酝碳。這三者我感覺就是Vue源碼中最精彩的地方,希望你我都可以從中汲取養(yǎng)分,不斷提高!
最后送上一個視頻連接捂齐,希望大家可以先設(shè)置VSCode調(diào)試Vue源碼的環(huán)境,只要可以調(diào)試的代碼沒有啥讀不懂的岳悟,視頻介紹很詳細(xì)普碎,給其點(diǎn)贊动猬。
VSCode搭建Vue源碼調(diào)試環(huán)境