vue源码学习--vue源码学习入门
本文為開始學(xué)習(xí)vue源碼的思路整理。在拿到vue項目源碼的之后看到那些項目中的文件夾,會很困惑,不知道每個文件夾內(nèi)的世界,怎么變換,怎樣的魔力,最后產(chǎn)生了vue框架。學(xué)習(xí)源碼也無從學(xué)起。我解決了這些困惑之后,形成了這篇文章----vue源碼學(xué)習(xí)入門。文章會根據(jù)我自己的學(xué)習(xí)思路,理清vue項目的大體框架。文章先介紹vue項目中各個文件夾中內(nèi)包含的功能,然后是從package.json文件開始我們的尋找vue構(gòu)造函數(shù)之旅,最后再從vue構(gòu)造函數(shù)返回入口,看看旅程中的各個節(jié)點都為vue加了什么“戲”。
一、Vue項目結(jié)構(gòu)
在GitHub中下載vue項目的源碼后解壓我們會得到如下的目錄。
每個文件夾是做什么的,在項目中的CONTRIBUTING有介紹。我翻譯了一下,形成了以下腦圖:
?
我們源碼學(xué)習(xí),學(xué)習(xí)的是vue框架內(nèi)容的源碼,而項目中包含的許多構(gòu)建vuejs的配置文件和輔助vue代碼編寫的功能模塊是我們幾乎接觸不到的。
我們需要著重注意的是,包含vue源碼的【src】文件夾。我把src的內(nèi)容單獨整理,形成了又一腦圖,如下:
?
現(xiàn)在我們大致知道vue項目的各個文件夾的用處了。而且,對于我們需要著重關(guān)注的源碼src也大致掌握了每個文件夾負責(zé)的功能模塊。接下來,我們就要開始啃食vue源碼了。做一只饑渴的寄生蟲吧~
?
二、尋找構(gòu)造函數(shù)之旅
要了解一個前端項目,一般情況下我們都可以從查看項目的package.json文件開始。從package.json我們可以知道項目有哪些依賴包,定義了哪些腳本字段。其實在CONTRIBUTING中還提到了常用的npm命令。以下是我的截圖:
?
其中提及可以用npm run dev來達到監(jiān)控和自動重建dist目錄下的vue.js文件以及還有一些npm命令在package.json的script中,大家可以按需執(zhí)行。
那我們找到vue項目中的package.json,看看npm run dev執(zhí)行了什么命令
rollup指的是一個專注于es6模塊構(gòu)建的工具,會對其構(gòu)建的代碼進行tree-shaking,是tree-shaking的提出者。它與webpack的區(qū)別是rollup沒做uplify,其構(gòu)建后的代碼盡量保持源代碼的樣子。
rollup -c指定配置文件為build/config.js,最后將TARGET的值設(shè)置成web-full-dev。
打開build文件夾下的config.js。簡化后代碼如下:
const builds = {...// Runtime+compiler development build (Browser)'web-full-dev': {entry: resolve('web/entry-runtime-with-compiler.js'),dest: resolve('dist/vue.js'),format: 'umd',env: 'development',alias: { he: './entity-decoder' },banner}... }function genConfig (name) {const opts = builds[name]const config = {input: opts.entry,external: opts.external,plugins: [replace({__WEEX__: !!opts.weex,__WEEX_VERSION__: weexVersion,__VERSION__: version}),flow(),buble(),alias(Object.assign({}, aliases, opts.alias))].concat(opts.plugins || []),output: {file: opts.dest,format: opts.format,banner: opts.banner,name: opts.moduleName || 'Vue'}}if (opts.env) {config.plugins.push(replace({'process.env.NODE_ENV': JSON.stringify(opts.env)}))}Object.defineProperty(config, '_name', {enumerable: false,value: name})return config }if (process.env.TARGET) {module.exports = genConfig(process.env.TARGET) } else {exports.getBuild = genConfigexports.getAllBuilds = () => Object.keys(builds).map(genConfig) }?
我們可以看出,該js中包含著一個builds常量對象,對象的屬性名為之前package.json中npm對應(yīng)命里里設(shè)置的TAGET值。js的最后根據(jù)是否有TAGET值返回不同的值。我們npm run dev的命令對應(yīng)的是:
module.exports = genConfig({entry: resolve('web/entry-runtime-with-compiler.js'),dest: resolve('dist/vue.js'),format: 'umd',env: 'development',alias: { he: './entity-decoder' },banner});并最終返回一個config對象,也就是執(zhí)行rollup的配置對象。
從builds對象中可以知道,vue的入口文件為:web/entry-runtime-with-compiler.js。在項目結(jié)構(gòu)的介紹中,我們也提及了dist、build的入口文件在platform中。在經(jīng)過resolve函數(shù)之后,路徑就是:\vue-dev\src\platforms\web\entry-runtime-with-compiler.js。
?
打開這個文件,在import中看到了Vue又來自./runtime/index即\vue-dev\src\platforms\web\runtime\index.js。按照這個Vue是來自于import的套路我們接著又找到了core/index.js、instance/index.js。撥開層層迷霧,終識廬山真面目,最終找到了最終的vue構(gòu)造函數(shù),如下:(\vue-dev\src\core\instance\index.js)
1 function Vue (options) { 2 if (process.env.NODE_ENV !== 'production' && 3 !(this instanceof Vue) 4 ) { 5 warn('Vue is a constructor and should be called with the `new` keyword') 6 } 7 this._init(options) 8 }所以尋找Vue構(gòu)造函數(shù)之旅如下圖:
?
三、回首來路,旅程給Vue加了哪些戲
如果直接從Vue實例化開始閱讀源碼(一般思路)在閱讀的過程中常常會遇見一些沒有見過的屬性和方法。心中就會產(chǎn)生疑惑,這些屬性的值是啥,在哪賦值定義的。為了解決這個疑問,本節(jié)通過“回顧旅程”,其實也是vuejs執(zhí)行順序,來看看程序?qū)ue構(gòu)造函數(shù)都加了哪些“戲”。
Vue(options)作為函數(shù)本質(zhì)上也是一個對象。作為構(gòu)造函數(shù)其原型對象將會對實例產(chǎn)生影響。所以整理出了vuejs執(zhí)行時掛載在函數(shù)上和函數(shù)原型上的屬性方法有哪些。
vue的構(gòu)造函數(shù)的文件在\vue-dev\src\core\instance\index.js,源碼如下:
function Vue (options) {if (process.env.NODE_ENV !== 'production' &&!(this instanceof Vue)) {warn('Vue is a constructor and should be called with the `new` keyword')}this._init(options) }initMixin(Vue) stateMixin(Vue) eventsMixin(Vue) lifecycleMixin(Vue) renderMixin(Vue)export default Vue?
vue構(gòu)造函數(shù)先是做一個安全模式的處理,告訴使用者必須通過new字符串調(diào)用Vue方法,然后用了一個_init(options)方法。可以推斷出_init方法是在Vue原型對象上的方法。
構(gòu)造函數(shù)之外還執(zhí)行了許多從幾個依賴中引入的函數(shù),并將構(gòu)造函數(shù)作為參數(shù)傳入方法。整理過后再instance/index.js中對Vue以及Vue原型的改變?nèi)缦?#xff1a;
?
往上一層core/index.js中,這個js導(dǎo)入了已經(jīng)在原型上掛載了方法合屬性后的Vue,然后導(dǎo)入initGlobalAPI為Vue導(dǎo)入許多全局API(我們在閱讀vue官方文檔中API分為全局配置、全局API以及實例屬性等全局API正是這時導(dǎo)入的)。initGlobalAPI中最后調(diào)用依賴引入的initUse、initMixin、initExtend、initAssetRegisters等方法
然后和在Vue原型對象上定義$isServer、$ssrContext兩個訪問器屬性和為Vue添加version屬性。具體添加見下圖:
?
?
再往回看,我們回到runtime/index.js。這個js顯示為Vue下的config屬性添加屬性和賦值,再是將平臺相關(guān)的directives和components擴展給Vue.options下的directives和components中也就是安裝平臺特有的指令和組件。
然后為判斷是否在瀏覽器中,如果是就為Vue原型對象安裝平臺patch功能。接著定義公共的$mount方法。
Vue.prototype.$mount = function (el?: string | Element,hydrating?: boolean ): Component {el = el && inBrowser ? query(el) : undefinedreturn mountComponent(this, el, hydrating) }$mount方法先是根據(jù)是否在瀏覽器決定是否query(el),最后都將參數(shù)傳給core/instance/lifecycle.js中的mountComponent方法,該方法負責(zé)掛載更新組件。
具體添加如下:
?
最后是runtime/entry-runtime-with-compiler.js,這個文件首先是緩存原型對象中存在的$mount方法,然后用一個可以將template/el轉(zhuǎn)化為render函數(shù)的方法覆蓋原型對象的$mount方法。然后定義了Vue.compile屬性為compileToFunctions 函數(shù)。這個函數(shù)的作用就是將模板 template 編譯為render函數(shù)。如下圖所示:
匯總一下:
第一步:instance/index.js 為Vue.prototype定義實例屬性、實例方法/數(shù)據(jù)、實例方法/事件以及生命周期等實例相關(guān)的屬性方法。
第二步:core/index.js? 主要是通過initGlobalAPI掛載平臺相關(guān)的全局API 和Vue需要用到的工具函數(shù)
第三部:runtime/index.js? ?是添加平臺相關(guān)的指令、組件和配置參數(shù)和$mount方法
第四部? ? runtime/entry-runtime-with-compiler.js 給Vue添加了可以支持template的$mount方法和compiler編譯器。
回首之旅結(jié)束,希望你得到你想得到的。
如此一路走來,我想我們應(yīng)該是知道vue源碼學(xué)習(xí)的大致框架了。知道了去哪找對應(yīng)功能模塊的代碼,以及vue對象上的屬性和vue原型對象上的屬性有粗略的了解。如果遇見也知道要去哪認識他們,而不是帶著苦惱前行了。
希望能幫到你~
?注:本文章中學(xué)習(xí)的源碼版本為vue? 2.5.2. 文章中涉及的觀點和理解均是個人的理解,如有偏頗或是錯誤還請大神指出,不吝賜教,萬分感謝~
轉(zhuǎn)載于:https://www.cnblogs.com/mlFronter/p/7730042.html
總結(jié)
以上是生活随笔為你收集整理的vue源码学习--vue源码学习入门的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 团队-游戏《石头,剪刀,布》-团队一阶段
- 下一篇: Sass 的安装
