1. 数据驱动
Vue框架的一个核心理念就是数据驱动视图。何谓数据驱动视图?简单理解,就是在我们想改变浏览器视图的时候,仅仅通过修改数据就可以完成。这个过程,大大简化了传统的前端开发的代码量,从而开发过程中不用考虑DOM的修改,不需考虑复杂的DOM操作,只需要将逻辑重点关注在数据的改变即可。
那这是怎么实现的呢?其实主要可以分为两个场景来分析:
1.1 页面初次渲染的过程
比如如下代码:
<div id="app">
{{ message }}
</div>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
})
这些看似非常简单的代码是如何渲染到浏览器的页面上面的呢?
1.2 改变数据触发视图更新
<div id="app">
{{ message }}
</div>
<button @click="handleClick"></button>
var app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
},
methods: {
handleClick() {
this.message = 'Hello huihui_new'
}
}
})
又比如说我们点击按钮改变了message的值,页面上立马更新了,这个过程又是如何发生的?
这两个过程如果清楚了,对vue的理解肯定会更深刻。
那么该如何分析这两个过程,那就开始读源码吧
2.new Vue() 的时候发生了什么
发生了什么?接下来我们以1.1中的代码来进行分析,new Vue(options)的时候到底发生了什么。
在src/core/instance的目录下,有下面一段代码:
import { initMixin } from "./init";
import { stateMixin } from "./state";
import { renderMixin } from "./render";
import { eventsMixin } from "./events";
import { lifecycleMixin } from "./lifecycle";
import { warn } from "../util/index";
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构造函数,所以在外部我们必须使用new 的方法去实例化Vue,实例化Vue的过程中执行了_init(options)方法(options是我们实例化的时候传入的配置对象)。
init()方法是在initMixin(Vue)的过程中挂载到vue上面的,
export function initMixin(Vue: Class<Component>) {
Vue.prototype._init = function (options?: Object) {
const vm: Component = this;
vm._uid = uid++;
let startTag, endTag;
if (process.env.NODE_ENV !== "production" && config.performance && mark) {
startTag = `vue-perf-start:${vm._uid}`;
endTag = `vue-perf-end:${vm._uid}`;
mark(startTag);
}
vm._isVue = true;
if (options && options._isComponent) {
initInternalComponent(vm, options);
} else {
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm,
);
}
if (process.env.NODE_ENV !== "production") {
initProxy(vm);
} else {
vm._renderProxy = vm;
}
vm._self = vm;
initLifecycle(vm);
initEvents(vm);
initRender(vm);
callHook(vm, "beforeCreate");
initInjections(vm);
initState(vm);
initProvide(vm);
callHook(vm, "created");
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);
}
};
}
这个过程中 主要做了几件事:
- 合并配置;
- 初始化生命周期、初始化事件中心、初始化渲染,初始化 data、props、computed、watcher 等;
- 挂载;
那data是如何渲染到页面呢,我们继续看源码,在上面第二步中执行了initState(vm),初始化了传入的数据配置,代码如下:
export function initState (vm: Component) {
vm._watchers = []
const 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 )
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
这里面初始化了props、methods、data等,我们的例子中传入了数据,则初始化数据,执行initData()方法;
function initData (vm: Component) {
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? getData(data, vm)
: 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
)
}
const keys = Object.keys(data)
const props = vm.$options.props
const methods = vm.$options.methods
let i = keys.length
while (i--) {
const 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, true )
}
这里主要分为4步:
- 首先判断定义的是否是函数,是的话执行函数,获取到返回的data对象;
- 判断与定义的methods、props是否存在冲突;
- 代理数据属性:
proxy(vm, `_data`, key)
export function proxy (target: Object, sourceKey: string, key: string) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
}
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val
}
Object.defineProperty(target, key, sharedPropertyDefinition)
}
通过defineProperty这个API对vm._data.key 进行代理,这样就可以通过 vm.key的方式访问到di定义在data中的属性,方便用户使用
- 对数据进行监听
这儿的逻辑主要是对数据进行深度监听,从而实现响应式,这点我们之后再分析。
到目前为止我们知道了new Vue() 是如何initData了,下一步继续分析data是如何挂载到页面上的!!! 未完待续!!!
|