代碼:vue
<template> <div id="app"> {{message}} </div> </template> <script> export default { name: 'app', data: function() { return { message: "Hello World" } } } </script> <style> #app { text-align: center; color: #2c3e50; font-family: 'Courier New', Courier, monospace; font-size: 18px; } </style>
結果:node
問題:背後發生了什麼
閱讀源碼的第一步是知道如何調試,不會調試就不可能分析出代碼的執行邏輯。首先,在項目中咱們引入了Vue:import Vue from 'vue'。問題是vue到底從哪裏來的。從node_modules中來。在node_modules路徑下存在vue文件夾,vue文件夾中存在一個package.json文件,這個文件的做用是描述整個項目的。在這個文件中存在兩個配置字段,它們都是程序的主入口文件。json
"main": "dist/vue.runtime.common.js", "module": "dist/vue.runtime.esm.js",
在module的優先級大於main的優先級。在module不存在時,main對應的配置項就是主入口文件。能夠看到
dist/vue.runtime.esm.js纔是主入口文件。咱們是經過app
new Vue({ render: h => h(App), }).$mount('#app')
建立Vue實例的,所以首先搜索Vue的定義ide
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); }
能夠看到Vue構造函數的核心代碼只有一行:this._init(options);所以搜索私有化_init方法。因爲_init是做爲this的一個方法,注意此處的this就是Vue。通過查找_init方法的定義以下:函數
Vue.prototype._init = function (options) { var vm = this; // a uid vm._uid = uid$3++; var startTag, endTag; /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { startTag = "vue-perf-start:" + (vm._uid); endTag = "vue-perf-end:" + (vm._uid); mark(startTag); } // a flag to avoid this being observed vm._isVue = true; // merge options if (options && options._isComponent) { // optimize internal component instantiation // since dynamic options merging is pretty slow, and none of the // internal component options needs special treatment. initInternalComponent(vm, options); } else { vm.$options = mergeOptions( resolveConstructorOptions(vm.constructor), options || {}, vm ); } /* istanbul ignore else */ if (process.env.NODE_ENV !== 'production') { initProxy(vm); } else { vm._renderProxy = vm; } // expose real self vm._self = vm; debugger initLifecycle(vm); initEvents(vm); initRender(vm); callHook(vm, 'beforeCreate'); initInjections(vm); // resolve injections before data/props initState(vm); initProvide(vm); // resolve provide after data/props callHook(vm, 'created'); /* istanbul ignore if */ if (process.env.NODE_ENV !== 'production' && config.performance && mark) { vm._name = formatComponentName(vm, false); mark(endTag); measure(("vue " + (vm._name) + " init"), startTag, endTag); } if (vm.$options.el) { vm.$mount(vm.$options.el); } }; }
讀源碼須要注意的一點就是不相關的必定要忽略,一旦遵循深度遍歷法則讀下去,你是必定會失敗的。若是方法不對,那還不如不讀,睡會覺。能夠將上面的代碼簡化爲:ui
Vue.prototype._init = function (options) { var vm = this; ... vm.$options = mergeOptions(options || {}, vm); ... initState(vm); ... if (vm.$options.el) { vm.$mount(vm.$options.el); } ... }; }
_init方法整體上作的事情其實並很少,第一項就是合併配置項。好比路由,狀態管理,渲染函數。this
new Vue({ store: store, router: router, render: h => h(App), }).$mount('#app')
而後初始化狀態,initState的方法定義以下:spa
function initState (vm) { vm._watchers = []; var opts = vm.$options; if (opts.props) { initProps(vm, opts.props); } if (opts.methods) { initMethods(vm, opts.methods); } if (opts.data) { initData(vm); } else { observe(vm._data = {}, true /* asRootData */); } if (opts.computed) { initComputed(vm, opts.computed); } if (opts.watch && opts.watch !== nativeWatch) { initWatch(vm, opts.watch); } }
從源碼能夠看出,initState就是將vue實例中的data,method,computed,watch等數據項作進一步得處理,其實就是作代理以及轉化成可觀測對象。
數據處理完成以後就將數據掛載到指定的鉤子上:vm.$mount(vm.$options.el);prototype
另外須要注意的是,_init方法中有一下一段代碼,在上面我爲來突出主線而省略了,這就是
initLifecycle(vm); initEvents(vm); initRender(vm); callHook(vm, 'beforeCreate'); initInjections(vm); // resolve injections before data/props initState(vm); initProvide(vm); // resolve provide after data/props callHook(vm, 'created');
能夠看到在initState(vm)執行以前,咱們執行了beforeCreate方法,在initState(vm)執行以後,咱們執行了created方法。所以在beforeCreate方法中,咱們沒法直接引用data,method,computed,watch等在initState(vm)中才開始存在的屬性。