Vue原理解析(一):Vue到底是什么?

Vue,現(xiàn)在前端的當(dāng)紅炸子雞,隨著熱度指數(shù)上升,實(shí)在是有必要從源碼的角度,對(duì)它功能的實(shí)現(xiàn)原理一窺究竟。個(gè)人覺(jué)得看源碼主要是看兩樣?xùn)|西,從宏觀上來(lái)說(shuō)是它的設(shè)計(jì)思想和實(shí)現(xiàn)原理;微觀上來(lái)說(shuō)就是編程技巧,也就是俗稱的騷操作。我們這次的側(cè)重點(diǎn)是它的實(shí)現(xiàn)原理。好吧,讓我們推開(kāi)它那神秘的大門(mén),進(jìn)入Vue的世界~

vue是什么?

vue究竟是什么?為什么就能實(shí)現(xiàn)這么多酷炫的功能,不知道大家有沒(méi)有思考過(guò)這個(gè)問(wèn)題。其實(shí)在每次初始化vue,使用new Vue({...})時(shí),不難發(fā)現(xiàn)vue其實(shí)是一個(gè)類。不過(guò)即使在ES6已經(jīng)如此普及的今天,vue的定義卻是普通構(gòu)造函數(shù)定義的,為什么沒(méi)有采用ES6class呢?這個(gè)我們稍后回答,通過(guò)層層追蹤終于找到了vue被定義的地方:

function Vue(options) {
  ...
  this._init(options)
}

因?yàn)槭窃斫馕觯?code>flow的類型檢測(cè)及一些邊界情況,如使用方式不對(duì)或參數(shù)不對(duì)或不是主要邏輯的代碼我們就省略掉吧。比如省略號(hào)這里邊界情況是使用時(shí)必須是new Vue()的形式,否則會(huì)報(bào)錯(cuò)。

其實(shí)vue源碼就像一棵樹(shù),我們看之前最好要確定看什么功能,然后避開(kāi)那些分叉邏輯,我們接下來(lái)的目標(biāo)就是以new Vue()開(kāi)始,走完一整條從初始化、數(shù)據(jù)、模板到真實(shí)Dom的這整個(gè)流程。

這就是vue最初始被定義的地方,你沒(méi)看錯(cuò),就是這么簡(jiǎn)單。當(dāng)執(zhí)行new Vue時(shí),內(nèi)部會(huì)執(zhí)行一個(gè)方法 this._init(options),將初始化的參數(shù)傳入。

這里需要說(shuō)明一點(diǎn),在vue的內(nèi)部,_符號(hào)開(kāi)頭定義的變量是供內(nèi)部私有使用的,而$ 符號(hào)定義的變量是供用戶使用的,而且用戶自定義的變量不能以_$開(kāi)頭,以防止內(nèi)部沖突。我們接著看:

import { initMixin } from './init'
import { stateMixin } from './state'
import { renderMixin } from './render'
import { eventsMixin } from './events'
import { lifecycleMixin } from './lifecycle'

function Vue(options) {
  ...
  this._init(options)
}

initMixin(Vue)
stateMixin(Vue)
eventsMixin(Vue)
lifecycleMixin(Vue)
renderMixin(Vue)

現(xiàn)在可以回答之前的問(wèn)題了,為什么不采用ES6class來(lái)定義,因?yàn)檫@樣可以方便的把vue的功能拆分到不同的目錄中去維護(hù),將vue的構(gòu)造函數(shù)傳入到以下方法內(nèi):

  • initMixin(Vue):定義_init方法。
  • stateMixin(Vue):定義數(shù)據(jù)相關(guān)的方法$set,$delete,$watch方法。
  • eventsMixin(Vue):定義事件相關(guān)的方法$on$once$off$emit
  • lifecycleMixin(Vue):定義_update,及生命周期相關(guān)的$forceUpdate$destroy
  • renderMixin(Vue):定義$nextTick_render將render函數(shù)轉(zhuǎn)為vnode

這些方法都是在各自的文件內(nèi)維護(hù)的,從而讓代碼結(jié)構(gòu)更加清晰易懂可維護(hù)。如this._init方法被定義在:

export function initMixin(Vue) {
  Vue.prototype._init = function(options) {
    ...當(dāng)執(zhí)行new Vue時(shí),進(jìn)行一系列初始化并掛載
  }
}

再這些xxxMixin完成后,接著會(huì)定義一些全局的API

export function initGlobalAPI(Vue) {
  Vue.set方法
  Vue.delete方法
  Vue.nextTick方法
  
  ...
  
  內(nèi)置組件:
  keep-alive
  transition
  transition-group
  
  ...
  
  initUse(Vue):Vue.use方法
  initMixin(Vue):Vue.mixin方法
  initExtend(Vue):Vue.extend方法
  initAssetRegisters(Vue):Vue.component,Vue.directive,Vue.filter方法
}

這里有部分APIxxxMixin定義的原型方法功能是類似或相同的,如this.$setVue.set他們都是使用set這樣一個(gè)內(nèi)部定義的方法。

這里需要提一下vue的架構(gòu)設(shè)計(jì),它的架構(gòu)是分層式的。最底層是一個(gè)ES5的構(gòu)造函數(shù),再上層在原型上會(huì)定義一些_init$watch_render等這樣的方法,再上層會(huì)在構(gòu)造函數(shù)自身定義全局的一些API,如setnextTickuse等(以上這些是不區(qū)分平臺(tái)的核心代碼),接著是跨平臺(tái)和服務(wù)端渲染(這些暫時(shí)不在討論范圍)及編譯器。將這些屬性方法都定義好了之后,最后會(huì)導(dǎo)出一個(gè)完整的構(gòu)造函數(shù)給到用戶使用,而new Vue就是啟動(dòng)的鑰匙。這就是我們陌生且又熟悉的vue,至于Vue.prototype._init內(nèi)部做了啥?我們下章節(jié)再說(shuō)吧,因?yàn)檫€有很多其他的要補(bǔ)充。

目錄結(jié)構(gòu)

剛才是從比較微觀的角度近距離的觀察了vue,現(xiàn)在我們從宏觀角度來(lái)了解它內(nèi)部的代碼結(jié)構(gòu)是如何組建起來(lái)的。
目錄如下:

|-- dist  打包后的vue版本
|-- flow  類型檢測(cè),3.0換了typeScript
|-- script  構(gòu)建不同版本vue的相關(guān)配置
|-- src  源碼
    |-- compiler  編譯器
    |-- core  不區(qū)分平臺(tái)的核心代碼
        |-- components  通用的抽象組件
        |-- global-api  全局API
        |-- instance  實(shí)例的構(gòu)造函數(shù)和原型方法
        |-- observer  數(shù)據(jù)響應(yīng)式
        |-- util  常用的工具方法
        |-- vdom  虛擬dom相關(guān)
    |-- platforms  不同平臺(tái)不同實(shí)現(xiàn)
    |-- server  服務(wù)端渲染
    |-- sfc  .vue單文件組件解析
    |-- shared  全局通用工具方法
|-- test 測(cè)試
  • flow:javaScript是弱類型語(yǔ)言,使用flow以定義類型和檢測(cè)類型,增加代碼的健壯性。

  • src/compiler:將template模板編譯為render函數(shù)。

  • src/core:與平臺(tái)無(wú)關(guān)通用的邏輯,可以運(yùn)行在任何javaScript環(huán)境下,如webNode.jsweex嵌入原生應(yīng)用中。

  • src/platforms:針對(duì)web平臺(tái)和weex平臺(tái)分別的實(shí)現(xiàn),并提供統(tǒng)一的API供調(diào)用。

  • src/observer:vue檢測(cè)數(shù)據(jù)數(shù)據(jù)變化改變視圖的代碼實(shí)現(xiàn)。

  • src/vdom:將render函數(shù)轉(zhuǎn)為vnode從而patch為真實(shí)dom以及diff算法的代碼實(shí)現(xiàn)。

  • dist:存放著針對(duì)不同使用方式的不同的vue版本。

vue版本

vue使用的是rollup構(gòu)建的,具體怎么構(gòu)建的不重要,總之會(huì)構(gòu)建出很多不同版本的vue。按照使用方式的不同,可以分為以下三類:

  • UMD:通過(guò)<script>標(biāo)簽直接在瀏覽器中使用。
  • CommonJS:使用比較舊的打包工具使用,如webpack1
  • ES Module:配合現(xiàn)代打包工具使用,如webpack2及以上。

而每個(gè)使用方式內(nèi)又分為了完整版和運(yùn)行時(shí)版本,這里主要以ES Module為例,有了官方腳手架其他兩類應(yīng)該沒(méi)多少人用了。再說(shuō)明這兩個(gè)版本的區(qū)別之前,抱歉我又要補(bǔ)充點(diǎn)其他的。在vue的內(nèi)部是只認(rèn)render函數(shù)的,我們來(lái)自己定義一個(gè)render函數(shù),也就是這么個(gè)東西:

new Vue({
  data: {
    msg: 'hello Vue!'
  },
  render(h) {
    return h('span', this.msg);
  }
}).$mount('#app');

可能有人會(huì)納悶了,既然只認(rèn)render函數(shù),同時(shí)我們開(kāi)發(fā)好像從來(lái)并沒(méi)有寫(xiě)過(guò)render函數(shù),而是使用的template模板。這是因?yàn)橛?code>vue-loader,它會(huì)將我們?cè)?code>template內(nèi)定義的內(nèi)容編譯為render函數(shù),而這個(gè)編譯就是區(qū)分完整版和運(yùn)行時(shí)版本的關(guān)鍵所在,完整版就自帶這個(gè)編譯器,而運(yùn)行時(shí)版本就沒(méi)有,如下面這段代碼如果是在運(yùn)行時(shí)版本環(huán)境下就會(huì)報(bào)錯(cuò)了:

new Vue({
  data: {
    msg: 'hello Vue!'  
  },
  template: `<div>{{msg}}</div>`
})

vue-cli默認(rèn)是使用運(yùn)行時(shí)版本的,更改或覆蓋腳手架內(nèi)的默認(rèn)配置,將其更改為完整版即可通過(guò)編譯:'vue$': 'vue/dist/vue.esm.js',推薦還是使用運(yùn)行時(shí)版本。好吧,具體區(qū)別最后我們以一個(gè)面試時(shí)經(jīng)常會(huì)被問(wèn)到的問(wèn)題作為本章節(jié)的結(jié)束。

面試官微笑而又不失禮貌的問(wèn)到:

  • 請(qǐng)問(wèn)runtimeruntime-only這兩個(gè)版本的區(qū)別?

懟回去:

  • 主要是兩點(diǎn)不同:
  1. 最明顯的就是大小的區(qū)別,帶編譯器會(huì)比不帶的版本大6kb
  2. 編譯的時(shí)機(jī)不同,編譯器是運(yùn)行時(shí)編譯,性能會(huì)有一定的損耗;運(yùn)行時(shí)版本是借助loader做的離線編譯,運(yùn)行性能更高。

下一篇:Vue原理解析(二):快速搞懂new Vue()時(shí)到底做了什么?(上)

順手點(diǎn)個(gè)贊或關(guān)注唄,找起來(lái)也方便~

分享一個(gè)筆者自己寫(xiě)的組件庫(kù),哪天可能會(huì)用的上了 ~ ↓

你可能會(huì)用的上的一個(gè)vue功能組件庫(kù),持續(xù)完善中...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評(píng)論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,615評(píng)論 3 419
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 176,606評(píng)論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,044評(píng)論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,826評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,227評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評(píng)論 3 442
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,447評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,992評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,807評(píng)論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,001評(píng)論 1 370
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評(píng)論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,243評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,667評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,930評(píng)論 1 287
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,709評(píng)論 3 393
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,996評(píng)論 2 374

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

  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,121評(píng)論 1 32
  • 前言 使用Vue在日常開(kāi)發(fā)中會(huì)頻繁接觸和使用生命周期,在官方文檔中是這么解釋生命周期的: 每個(gè) Vue 實(shí)例在被創(chuàng)...
    心_c2a2閱讀 2,272評(píng)論 1 8
  • 摘要: 2016年最火的前端框架當(dāng)屬Vue.js了,很多使用過(guò)vue的程序員這樣評(píng)價(jià)它,“vue.js兼具angu...
    OSC開(kāi)源社區(qū)閱讀 23,728評(píng)論 2 149
  • 前幾天想學(xué)學(xué)Vue中怎么編寫(xiě)可復(fù)用的組件,提到要對(duì)Vue的render函數(shù)有所了解。可仔細(xì)一想,對(duì)于Vue的ren...
    kangaroo_v閱讀 116,101評(píng)論 13 171
  • vue概述 在官方文檔中,有一句話對(duì)Vue的定位說(shuō)的很明確:Vue.js 的核心是一個(gè)允許采用簡(jiǎn)潔的模板語(yǔ)法來(lái)聲明...
    li4065閱讀 7,261評(píng)論 0 25