Vue從何處來 (import Vue from 'vue') ?
- 首先分析一下vue的相關檔案
\ | UMD | CommonJS | ES Module (基於構建工具使用) | ES Module (直接用於瀏覽器) |
---|---|---|---|---|
完整版 | vue.js | vue.common.js | vue.esm.js | vue.esm.browser.js |
只包含執行時版 | vue.runtime.js | vue.runtime.common.js | vue.runtime.esm.js | - |
完整版 (生產環境) | vue.min.js | - | - | vue.esm.browser.min.js |
只包含執行時版 (生產環境) | vue.runtime.min.js | - | - | - |
詳 情 戳 這 裡
在利用webpack的Vue專案中,在main.js中通過
import Vue from 'vue'
匯入的vue包如下圖所示。(在node_modules/vue/package.json中配置了main屬性)
這個包功能其實是不完整的,只有runtime-only的功能。來建立 Vue 例項、渲染並處理虛擬 DOM 等的程式碼。基本上就是除去編譯器的其它一切。 解決辦法有一下三種:
- import Vue from '../node_modules/vue/dist/vue.js'
- 在Vue包的package.json檔案中main屬性指定的入口檔案修改為【"main": "dist/vue.js"】(或者其他功能完善的包)
- 在專案的webpack.config.js中新增resolve屬性,如下圖所示
原始碼分析(只看主線)
import Vue from 'vue'
過程中,Vue 初始化主要就幹了幾件事情,合併配置,初始化生命週期,初始化事件中心,初始化渲染,初始化 data、props、computed、watcher 等等。目前主要注意一下initMixin方法
initMixin()
有刪減,目前主要看主線,其他的功能逐步拆開來分析
function initMixin (Vue) {
Vue.prototype._init = function (options) { // 在Vue的原型上定義 _init 方法
var vm = this;
vm._self = vm;
initLifecycle(vm); // 初始化生命週期
initEvents(vm); // 事件
initRender(vm); // render 方法
callHook(vm, 'beforeCreate');
initInjections(vm); // resolve injections before data/props
initState(vm); // 初始化狀態
initProvide(vm); // resolve provide after data/props
callHook(vm, 'created');
if (vm.$options.el) { // 掛載dom元素
vm.$mount(vm.$options.el);
}
};
}
複製程式碼
new Vue()
通過initMixin方法之後,執行 new Vue() 操作,在 Vue建構函式內部,呼叫了vm原型上的 _init()方法,在this._init()方法中我們目前主要關注的是 initState()方法
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原型上定義了的方法(initMixin方法中),使構建出來的Vue例項呼叫
}
new Vue({
el: '#app',
render: h => h(App)
})
複製程式碼
initState()
我們深入到 initData 方法中
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);
}
}
複製程式碼
initData()
在這個方法中,需要注意的是 proxy() 方法
function initData (vm) {
var data = vm.$options.data;
data = vm._data = typeof data === 'function' // 在vm 上定義一個 '_data' 屬性
? getData(data, vm) // getData()方法返回的就是我們在vue data()方法中定義了的屬性和方法
: data || {};
if (!isPlainObject(data)) {
data = {};
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object:\n' +
'https://vuejs.org/v2/guide/components.html#data-Must-Be-a-Function',
vm
);
}
// proxy data on instance
var keys = Object.keys(data);
var props = vm.$options.props;
var methods = vm.$options.methods;
var i = keys.length;
while (i--) {
var key = keys[i];
if (process.env.NODE_ENV !== 'production') {
if (methods && hasOwn(methods, key)) {
warn(
("Method \"" + key + "\" has already been defined as a data property."),
vm
);
}
}
if (props && hasOwn(props, key)) {
process.env.NODE_ENV !== 'production' && warn(
"The data property \"" + key + "\" is already declared as a prop. " +
"Use prop default value instead.",
vm
);
} else if (!isReserved(key)) {
proxy(vm, "_data", key); // 資料代理
}
}
// observe data
observe(data, true /* asRootData */);
}
複製程式碼
proxy()
在這個方法中,通過Object.defineProperty()來實現資料劫持,當我們通過 this.xxx 訪問我們定義的資料時,其實就是訪問的 this._data.xxx,從而達到資料代理的目的。
var sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
};
function proxy (target, sourceKey, key) {
// target是vm, sourceKey是'_data', key 是我們定義的data中的鍵
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
};
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val;
};
Object.defineProperty(target, key, sharedPropertyDefinition);
}
複製程式碼
總結
在初始化的最後,檢測到如果有 el 屬性,則呼叫 vm.$mount 方法掛載 vm,掛載的目標就是把模板渲染成最終的 DOM,在下個系列中將分析Vue例項掛載的實現