如图如果要看Vue源码,网上的分析巨多,但是因为Vue源码逻辑分的很细致,以至于看起来每个文件之间跳来跳去看的有点累,于是画了一个图,对响应式的部分大致做了一个总结
1. new Vue与Observer
1.1 起源src/core/instance/index.js
首先Vue定义在文件src/core/instance/index.js
处,声明式定义后执行了一系列的初始化操作
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)
}
_init方法对平常使用new Vue()
传入的options进行了处理
1.2 initData
Vue.prototype._init = function (options?: Object) {
...
// expose real self
vm._self = vm
// 实例生命周期的相关变量初始化
initLifecycle(vm)
// 事件监听初始化
initEvents(vm)
initRender(vm)
// 到此,进入beforeCreate钩子函数,开始beforeCreate到created阶段
callHook(vm, 'beforeCreate')
initInjections(vm) // resolve injections before data/props
// 初始化各种状态 props/data/method/computed/watch
initState(vm)
initProvide(vm) // resolve provide after data/props
// 完成初始化,调用了created钩子函数
callHook(vm, 'created')
}
_init方法中,在initState函数中对绑定的数据进行了处理
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 /* asRootData */)
}
if (opts.computed) initComputed(vm, opts.computed)
if (opts.watch && opts.watch !== nativeWatch) {
initWatch(vm, opts.watch)
}
}
我们重点来看initData()
function initData (vm: Component) {
// 取到options里的data
let data = vm.$options.data
// proxy data on instance
let i = keys.length
// 判断变量名是否和methods方法名和props属性名重复,且不允许用$或者_开头
while (i--) {
const key = keys[i]
if (process.env.NODE_ENV !== 'production') {
// ...省略
} else if (!isReserved(key)) {
// 做代理,访问vm[key]代理到vm._data[key]
proxy(vm, `_data`, key)
}
}
// observe data
observe(data, true /* asRootData */)
}
- 首先,函数取到new Vue里定义的data对象,然后进行了一些变量名是否重复的判断
- 对变量名不是$或者_开头进行代理,当访问this.xxx的时候代理到this._data[xxx]
- observe(data, true)响应式拦截
1.3 Observer(重点)
Observer模块是实现响应式的重点,下面来看看实际处理是怎么使用的
在src/core/observer/index.js
中定义了observe的处理
export function observe (value: any, asRootData: ?boolean): Observer | void {
if (!isObject(value) || value instanceof VNode) {
return
}
let ob: Observer | void
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__ // 定义了监听就无须再次定义
} else if (
shouldObserve && // 默认设置为true
!isServerRendering() && // 判断是否服务端
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) && // 可扩展
!value._isVue
) {
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
- 进行一些配置属性的判断,然后实例化一个监听器Observer,对传入的data进行监听
下面是Observer类的定义
export class Observer {
value: any;
dep: Dep;
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
this.value = value
this.dep = new Dep() // 订阅器
this.vmCount = 0
def(value, '__ob__', this) // 对value定义了一个__ob__属性,指向监听器
if (Array.isArray(value)) {
// __proto__ in {} 是否可以用隐式属性__proto__
if (hasProto) {
protoAugment(value, arrayMethods) // 原型链继承Array
} else {
copyAugment(value, arrayMethods, arrayKeys) // 手动的实现Array原型上的属性和方法
}
this.observeArray(value) // 遍历每个元素执行observe
} else {
this.walk(value) // 定义响应式的监听处理
}
}
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i]) // 遍历传入value的每个值
}
}
/**
* Observe a list of Array items.
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
- 首先定义了一个订阅器Dep,对传入的value进行数组还是对象的判断,最后执行defineReactive
// 省略部分不重要代码
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep() // 新订阅器
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
// 子属性遍历监听
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
// watcher订阅者会在初次实例化的时候设置Dep.target属性为实例本身
if (Dep.target) {
dep.depend() // 添加进订阅者列表中
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify() // 调用订阅器方法触发更新
}
})
}
- 从函数中我们可以看到在getter中如果存在Dep.target属性,就执行dep.depend()方法添加进订阅者列表中,在订阅者进行初始化操作时,如最开始的图,会进行设置Dep.target属性,循环触发这里的get拦截,然后移除Dep.target属性
- 在setter中,实现了常规的设置值的阶段后,进行了dep.notify(),通知订阅器来遍历订阅者列表,发布更新
至此,从new Vue()开始,Vue内部对其中传入的参数进行了分阶段的处理,保证了宽松输入,严格输出,对每个属性实现订阅-发布模式,初始化时添加进订阅列表,值发生更新时发布更新
对于对象来说
- 直接遍历对象的每一个key,然后定义响应式
对于数组来说
- 因为数组来说,需要对
push
,pop
,shift
,unshift
,splice
,sort
,reverse
这些方法执行的时候触发更新,所以定义响应式的数组需要去继承上这些自定义过的方法,才能够再变化时发布更新
网友评论