一. Vue源碼解析準(zhǔn)備篇

最近利用空閑時間又翻看了一遍Vue的源碼,只不過這次不同的是看了Flow版本的源碼承桥。說來慚愧凶异,最早看的第一遍時對Flow不了解挤巡,因此閱讀的是打包之后的vue文件矿卑,大家可以想象這過程的痛苦母廷,沒有類型的支持琴昆,看代碼時摸索了很長時間,所以我們這次對Vue源碼的剖析是Flow版本的源碼抖拦,也就是從Github上下載下來的源碼中src目錄下的代碼。不過噩茄,在分析之前绩聘,我想先說說閱讀Vue源碼所需要的一些知識點(diǎn)君纫,掌握這些知識點(diǎn)之后蓄髓,相信再閱讀源碼會較為輕松会喝。

1. 前置知識點(diǎn)

我個人認(rèn)為要想深入理解Vue的源碼肢执,至少需要以下知識點(diǎn):


vue前置知識點(diǎn).png

下面咱們一一介紹

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)境

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末猿涨,一起剝皮案震驚了整個濱河市俺附,隨后出現(xiàn)的幾起案子氛琢,更是在濱河造成了極大的恐慌撮奏,老刑警劉巖于微,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異哈肖,居然都是意外死亡游两,警方通過查閱死者的電腦和手機(jī)铸董,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進(jìn)店門汰具,熙熙樓的掌柜王于貴愁眉苦臉地迎上來聚蝶,“玉大人,你說我怎么就攤上這事〖希” “怎么了哈雏?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵派殷,是天一觀的道長扶叉。 經(jīng)常有香客問我,道長乌庶,這世上最難降的妖魔是什么盯滚? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任交排,我火速辦了婚禮都许,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好茫蛹,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布匈辱。 她就那樣靜靜地躺著大州,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上颇象,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機(jī)與錄音百侧,去河邊找鬼膨处。 笑死测摔,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的脏款。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼尽爆,長吁一口氣:“原來是場噩夢啊……” “哼募强!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起品追,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后牲阁,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體棉胀,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡熬北,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了竿开。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片枚尼。...
    茶點(diǎn)故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡赎瑰,死狀恐怖瓶佳,靈堂內(nèi)的尸體忽然破棺而出胶惰,到底是詐尸還是另有隱情,我是刑警寧澤痘绎,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布须教,位于F島的核電站,受9級特大地震影響咖杂,放射性物質(zhì)發(fā)生泄漏埃唯。R本人自食惡果不足惜闯传,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望利耍。 院中可真熱鬧咬崔,春花似錦、人聲如沸扰肌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽们何。三九已至躲株,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間酥宴,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工您觉, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留拙寡,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓琳水,卻偏偏與公主長得像肆糕,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子在孝,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容