代码:
<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>
结果:
问题:背后发生了什么
阅读源码的第一步是知道如何调试,不会调试就不可能分析出代码的执行逻辑。首先,在项目中我们引入了Vue:import Vue from 'vue'。问题是vue到底从哪里来的。从node_modules中来。在node_modules路径下存在vue文件夹,vue文件夹中存在一个package.json文件,这个文件的作用是描述整个项目的。在这个文件中存在两个配置字段,它们都是程序的主入口文件。
"main": "dist/vue.runtime.common.js",
"module": "dist/vue.runtime.esm.js",
在module的优先级大于main的优先级。在module不存在时,main对应的配置项就是主入口文件。可以看到
dist/vue.runtime.esm.js才是主入口文件。我们是通过
new Vue({
render: h => h(App),
}).$mount('#app')
创建Vue实例的,因此首先搜索Vue的定义
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);
}
};
}
读源码需要注意的一点就是不相关的一定要忽略,一旦遵循深度遍历法则读下去,你是一定会失败的。如果方法不对,那还不如不读,睡会觉。可以将上面的代码简化为:
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方法总体上做的事情其实并不多,第一项就是合并配置项。比如路由,状态管理,渲染函数。
new Vue({
store: store,
router: router,
render: h => h(App),
}).$mount('#app')
然后初始化状态,initState的方法定义如下:
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);
另外需要注意的是,_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)中才开始存在的属性。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。