深入响应式原理
大家应该都知道响应式原理的核心API是Object.defineProperty。如果不了解请点击: Object.defineProperty,去MDN了解下这个API的用途。
那这个API是如何做到响应式对象的呢?
接下来我们从源码的角度来进行分析: 我们知道在组件的data中定义的数据,最终都能响应式,看下Vue2.x的源码是如何初始化data的:
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)
}
}
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 )
}
这里面主要分为如下几步:
- 获取到data中的返回对象;
- 验证是否与定义的methods与props冲突;
- proxy代理;
- oberve实现data对象响应式;
这里的第一步和第二部很容易理解,第四步是实现响应式对象的核心,第三步简单的分析下是为什么,贴上代码:
proxy
proxy(vm, `_data`, key)
const sharedPropertyDefinition = {
enumerable: true,
configurable: true,
get: noop,
set: noop
}
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)
}
可以看到,这个地方其实也是利用了Object.defineProperty这个api,定义了存储描述符,让我们在vue内部data属性中定义的key通过代理的方式,可以使用this.key的方式去访问到。简化了我们的代码量。
当我们访问this.key的时候,其实内部返回的就是this._data.key的值。
在vue中我们也可以通过this._data.key的方式访问到我们定义的data,但不建议这么做,_data的意思就是内部属性data。
observe
observe 的功能就是用来监测数据的变化,它是vue2.x 实现响应式的核心,它定义在 src/core/observer/index.js 目录中:
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 &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value)
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
可以看到,这里实例化了一个Observer类,并返回这个实例化后的值,接着看下Observer这个类的源码
export class Observer {
value: any;
dep: Dep;
vmCount: number;
constructor (value: any) {
this.value = value
this.dep = new Dep()
this.vmCount = 0
def(value, '__ob__', this)
if (Array.isArray(value)) {
const augment = hasProto
? protoAugment
: copyAugment
augment(value, arrayMethods, arrayKeys)
this.observeArray(value)
} else {
this.walk(value)
}
}
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
这个构造函数首先实例化了Dep对象(这个类跟依赖收集相关,这里不做分析,以后的文章会讲清。)接着执行def函数,把自身的实例添加到数据对象value的__ob__属性上
export function def (obj: Object, key: string, val: any, enumerable?: boolean) {
Object.defineProperty(obj, key, {
value: val,
enumerable: !!enumerable,
writable: true,
configurable: true
})
}
接下来对这个value值做判断,数组的话会调用observeArray方法,否则调用walk方法。而observe最终也会调用observe方法,最终都会执行defineReactive。
export function defineReactive (
obj: Object,
key: string,
val: any,
customSetter?: ?Function,
shallow?: boolean
) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) {
return
}
const getter = property && property.get
const setter = property && property.set
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val
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 (newVal === value || (newVal !== newVal && value !== value)) {
return
}
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (setter) {
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal)
dep.notify()
}
})
}
defineReactive目的就是定义一个响应式对象,首先实例化了一个Dep对象,接着拿到属性描述符,然后对子对象递归调用observe,这样就实现了对象的深度响应式,保证无论访问多深的obj的属性,最后都能触发属性描述符的getter和setter。最后使用Object.defineProperty给对应的属性添加了getter和setter。这个作用是为了访问属性的时候触发getter,收集属性的依赖,改变属性的时候触发setter,派发对应的更新,从而通知页面做渲染,这就涉及到了观察者模式,在之后的文章我们再细说。
|