这里我们可以看到几个beforeCreate
,created
和Mount
关键字,大概就能够猜到vu
e实例的部分生命周期方法就是在这里进行了挂载,再结合 vue官方文档的图示
关于初始化整个vue
的状态,可以举例来说,例如initLifecycle
中就赋值了parent,children
,以及一些isMounted,isDestroy
的标识符。initRender
中就将attrs,listeners
响应化,等等,诸如此类。
从initMixin=>initState=>initData
,便可以看到挂载props,methods,data,computed,watch
了,
可以看到,此处先挂载了
props,methods
,然后是data
的顺序,其实再往下探究逻辑就可以知道,如果存在变量重名,优先级是props>methods>data
的,这也就解释了为什么初始化的顺序是这样安排的
在initData
中,先是获取了data
数据,判断props,methods
变量重名问题,然后是走了一个代理,将变量名代理到vue
实例上,这样的话你的vue
实例中,使用this.x
指向就可以访问到this.data.x,
这类代理也用在了props
和methods
中
在
initData
获取数据中可以看到一个判断typeof data === 'function' ? getData(data, vm) : data || {}
, 支持两种方式获取,实际上如果是自己写这样一个逻辑是会藏有隐患的,如果你的data是直接使用对象,而js的复杂数据类型是地址引用,这意味着,你实例化了两个vue
对象,实际上他们的data引用地址是同一个地址,对其中一个vue data
的修改会触发另一个vue数据的变动,带来的问题是巨大的
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)
}
这个逻辑处理的设计也是非常巧妙,他覆盖了实例中对该key的访问,使用setter
和getter
将实际访问指向了this.data[key]
。
这里可以说一下computed的逻辑,实际上也是取巧使用了原本用于data的响应式逻辑,其实看到上面贴出来的proxy代码,大概就能猜到,既然proxy能够改变一个变量读取的指向,那么他也能创造一个虚假变量的指向,这个创造出来的这个变量实际上就是computed所使用的变量,将每次computed函数赋给getter,再加上响应式处理,就完全实现了computed,
走到最后,就是observe(data)
,也就是开始处理vue数据的双向绑定
不同于react
的单向数据流,vue
使用的双向绑定,单向数据流可以理解为当源头的数据发生变动则触发行为,当然这个变动是主动的,即你需要setState
才能触发,而双向绑定则可以抽象为,每一个数据旁边都有一个监护人(一种处理逻辑),当数据发生变化,这个监护人就会响应行为,这个流程是被动发生的,只要该数据发生变动,就会通过监护人触发行为。
如果你之前有过了解,大概就会知道,js每个数据的变动都是通过Object
原型链中的setter
去改变值,而如果你在他改变值之前,去通知监护人,就能够实现上述的逻辑,这一点很多博客文章都写的非常清楚了。
接着第一部分的initData
知道最后observe(data)
,这里开始正式处理响应式。
2.1 前置条件
前面一直提到,通过Object
的原型链改变对象的默认行为:getter
和setter
,首先我们需要知道,在js
中,读取一个对象的值并不是直接读取,而是通过Object的原型链上的默认行为getter拿到对应的值,而改变这种行为实际上是通过Object.defineProperty
,来重新定义一个对象的getter
和setter
,在/src/core/observer/index.js
中我们可以看一个defineReactive
方法,他就是vue
用来实现这种行为的方法,也是这个响应式的核心
function defineReactive(obj, key, val, … ) {
// 此处需要保留getter、setter是因为,开发者可能自己基于defineProperty已经做过一层覆盖,
// 而响应式又会覆盖一次,所以为了保留开发者自己的行为,此处需要兼容原有的getter、setter
const getter = property && property.get // 拿到默认的getter、setter行为
const setter = property && property.set
Object.defineProperty(obj, key, {
enumerable: true, // 是否可以被枚举出来(例如Object.keys(),for in)
configurable: true, // 是否可以被配置,是否可以被删除
get: function() {
const value = getter ? getter.call(obj) : val
…
return value
}</