前言
上篇文章是Vue初始化加载的过程,主要的处理是对Vue实例方法以及静态方法的定义。
本篇是分析调用Vue构造函数的过程,实际上是主要分析大致流程、props、methods、data。
具体逻辑
使用Vue构造函数创建Vue实例,具体的处理逻辑如下图:
从上图中可以看出,Vue实例的具体处理是定义相关的内部属性包括props、data、methods、computed都会在创建实例时被处理,还有beforeCreate、created生命周期函数的执行。
本篇就具体initState以及$mount函数的执行逻辑。
initState执行逻辑
initState的具体处理逻辑如下图所示:
从上面中可以看出initState的处理逻辑是处理组件中的props、data、computed、watch、methods,从上面可以推测响应式data以及compute都是在对应的方法中处理,这部分也是主要处理逻辑。
本文主要是针对于data部分的具体分析。
data部分
data部分的处理实际上是在initState部分,具体如下:
opts.data ? initData(vm) : observe(vm._data = {}, true);
首先看看initData的具体处理:
从上图中的逻辑处理中可以看出,实际上initData的主要处理:
- data为函数或对象的处理
- 判断data中定义的key是否在methods、props中同名,同名就报错
- 定义_data并使用内部定义的proxy方法代理所有属性
- 调用observe来观察data中所有属性
上面需要主要关注的点:
- getData中具体处理
- proxy(vm, ‘_data’, key)的具体处理
- observe(data, true):实现data响应式的具体处理
getData
实际上该方法主要执行就是调用data,即data.call(vm, vm)
proxy
实际上该方法就是使用Object.defineProperty实现数据监听,但是是在_data对象上,_data是Vue内data的拷贝,具体处理如下:
var proxy = function(target, source, key) {
sharedPropertyDefinition.get = function proxyGetter () {
return this[sourceKey][key]
};
sharedPropertyDefinition.set = function proxySetter (val) {
this[sourceKey][key] = val;
};
Object.defineProperty(target, key, sharedPropertyDefinition);
};
从上面可以看出这边是对将data中属性拷贝到_data上,并实现监听
observe
这是实现data响应式的关键处理,实际上这边主要的处理如下:
var ob = new Observer(value);
return ob;
即observe是用于创建观察观察者对象,用于观察data中数据。
接下来就具体分析Observer对象创建过程:
从上面的处理逻辑中可以看到对于data中数组和非数组类型有不同的处理,分别是observeArray和walk函数的处理。
observeArray的处理实际上是:
递归将Array中元素平铺成非数组来处理,即对数组中每个元素都调用observe函数
walk中的处理实际上是:
循环遍历data中属性,调用defineReactive函数来实现监听
defineReactive
这是Vue中是实现监听的函数,主要就是Object.defineProperty拦截数据定义响应式,不过该函数还有一些具体的处理的逻辑,具体如下:
其中set和get中的处理就是响应式实现的处理,而set中有一点需要关注是通知视图更新,而这实现依赖dep对象,dep对象中保存了Watcher对象集合,通过notify实现视图更新处理,即:
- this.text = ‘world’ 会触发set函数
- set函数中使用dep.notify()来实现视图更新处理
而Watcher对象的创建,实际上是new Vue()实例最后挂载时处理的,即el或$mount时处理的。
el或$mount挂载
对于挂载的处理是在_init即Vue()构造函数中调用的,具体的处理就是调用$mount方法,如下:
if (vm.$options.el) vm.$mount(vm.$options.el);
而$mount是定义在Vue原型上的实例方法,这也解释了Vue挂载的两种方式:
- el形式,例如: new Vue({ el: ‘#app’})
- mount(‘#app’)
$mount方法中具体处理如下:
从上面可以得出知晓下面几个结论:
- el和 mount方法
- 若render函数存在,源码中会将template/el 转换为render函数
- 挂载点应该是普通的html元素,而不应该是html或body这种特殊的
- 如果template不存在,实际上会获取包含挂载点在内的outerHTML部分作为template
$mount内部是调用mountComponent函数,该函数的具体处理是什么呢?具体处理逻辑如下图:
从上图逻辑处理中可知:
- mountComponent中实际上处理beforeMount、mounted
- 每个Vue实例都会有相应的Watcher实例对应,是Watcher构造函数中vm._watcher和vm._watchers记录了当前实例和watcher对象集合
Vue响应式原理
下面是Vue.js官网给出的响应式的具体流程:
通过上面中defineReactive中可知在set函数中会通过dep.notify()通知视图更新,但是没有分析get中的具体处理,接下来解释getter中是Watcher是如何收集依赖的。
var value = getter ? getter.call(obj) : val;
// new Watcher中会触发视图渲染,实际上是底层还是调用Vue原型上
// 的实例方法_render,_render中会调用data中数据触发getter
// 此处Dep.Target是Watcher对象
if (Dep.target) {
dep.depend();
if (childOb) {
childOb.dep.depend();
if (Array.isArray(value)) {
dependArray(value);
}
}
}
return value
Watcher是如何收集依赖呢?实际上就是上面dep.depend方法,Dep.target当前指向Watcher对象,dep.depend方法内部实际上是:
Dep.prototype.depend = function() {
Dep.target && Dep.target.addDep(this);
}
即会调用Watcher的实例对象addDep,看看addDep实例方法的具体处理:
Watcher.prototype.addDep = function addDep (dep) {
var id = dep.id;
// 是否已存在
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id);
// 这里Watcher建立与依赖对象dep的联系
this.newDeps.push(dep);
if (!this.depIds.has(id)) {
// 此处是进行赋值时视图更新的关键,setter中会触发dep.notify,实际上notify内部是遍历subs属性,该属性此时存储的就是Watcher对象,即通知Watcher对象渲染视图
dep.addSub(this);
}
}
};
总结
本文只是针对data和挂载的流程做了分析:
- 对于new Vue()初始化过程有了了解
对于data的处理过程有了了解:
initState -> initData -> observe -> new Observer() -> Observer.prototype.walk -> defineReactive()
而defineReactive是整个响应式实现的关键即Object.defineProperty中get和set函数具体处理,而set中会调用dep.notify()通知视图更新,而get中会建立依赖对象Dep与Watcher对象之间的关系el和$mount两种挂载背后的处理:
$mount -> mountComponent -> new Watcher() -> _render()执行渲染视图