Appearance
三、数据响应式原理
数据响应式原理
数据响应式和双向数据绑定机制是使用数据驱动开发的基石.
数据响应式:数据发生变化的时候自动更新视图,不需要手动操作DOM
通过查看源码解决下面的问题
vm.msg = { count: 0 }
,重新给属性赋值,是否是响应式的?vm.arr[0] = 4
,给数组元素赋值,视图是否会更新vm.arr.length = 0
,修改数组的 length,视图是否会更新vm.arr.push(4)
,视图是否会更新
响应式处理入口
根据之前的初始化代码的分析,我们可以知道响应式处理的代码位置在:
从 core/instance/init.js 中的 _.init
函数中的 initState,进入函数(core/instance/state.js中)找到里面的initData函数,进入函数找到里面的observe 函数就是处理的入口函数
js
// 把data转化成响应式对象
// 第一个参数是选项options中的data
// 第二个参数是根数据
observe(data, true /* asRootData */)
进入observe函数
js
/**
* Attempt to create an observer instance for a value,
* 试图为value创建一个observer对象
* returns the new observer if successfully observed,
* 如果创建成功会把observe对象返回
* or the existing observer if the value already has one.
* 或者返回一个已存在的observe对象
*/
export function observe (value: any, asRootData: ?boolean): Observer | void {
// 判断value是否是一个对象或者是否是VNode的实例
// 如果不是对象或者是VNode的实例,就不需要进行响应式处理,会直接返回
if (!isObject(value) || value instanceof VNode) {
return
}
// ob 是 Observer的实例
let ob: Observer | void
// 判断value中是否有__ob__这个属性
// 如果有的话还要判断__ob__是否是Observer的实例
// 如果条件成立就把那个赋值给ob变量
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
// 判断是否可以进行响应式处理
// 判断value是否是数组或者是一个纯object对象
// 判断value是否是Vue实例!value._isVue,如果是vue实例那么不需要进行响应式处理
shouldObserve &&
!isServerRendering() &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
// 最最最核心
// 如果可以进行响应式处理,就创建一个Observer对象,就把value的所有属性转换成setter和getter
ob = new Observer(value)
}
// 如果传入的是根数据,那么vmCount要进行++,计数
if (asRootData && ob) {
ob.vmCount++
}
// 最终将Observer实例对象返回
return ob
}
Observer
Observer 构造函数中,就是对数组和对象进行响应式处理.
js
/**
* Observer class that is attached to each observed
* Observer类被附加到每一个被观察的对象
* object. Once attached, the observer converts the target
* 一旦附加,会转换对象的所有属性
* object's property keys into getter/setters that
* 将其转换成getter和setter
* collect dependencies and dispatch updates.
* 用来收集依赖和派发更新
*/
export class Observer {
// flow的语法,把属性定义在类的最上面
// 观测对象
value: any;
// 依赖对象
dep: Dep;
// 实例计算器
vmCount: number; // number of vms that have this object as root $data
constructor (value: any) {
// 被观察的对象
this.value = value
//Observer对象都有一个dep属性,里面的值是Dep对象
this.dep = new Dep()
this.vmCount = 0
// 使用def函数,给value对象设置了__ob__属性,把Observer对象记录下来
// 在observe中函数中判断的__ob__就是在这里定义的
// def是对Object.defineProperty做了一个封装
def(value, '__ob__', this)
// 核心核心核心
// 判断是否是数组
if (Array.isArray(value)) {
if (hasProto) {
protoAugment(value, arrayMethods)
} else {
copyAugment(value, arrayMethods, arrayKeys)
}
this.observeArray(value)
// 如果是对象就调用其walk
/**
* walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
*/
// 遍历对象中的的每一个属性,用defineReactive方法转换成哼setter和getter
} else {
this.walk(value)
}
}
/**
* Walk through all properties and convert them into
* getter/setters. This method should only be called when
* value type is Object.
*/
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
/**
* Observe a list of Array items.
* 对数组做响应式处理
*/
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
}
defineReactive
js
/**
* Define a reactive property on an Object.
* 为一个对象定义一个响应式的属性
*/
export function defineReactive (
obj: Object, // 目标对象
key: string, // 设置的属性
val: any, // 值
// 下面都是可选参数
// 用户自定义的setter函数,很少会用到
customSetter?: ?Function,
// 浅的意思,如果为true则只监听其第一层的属性
// 如果是false,那就要深度监听
shallow?: boolean
) {
// 创建Dep对象,负责为当前属性收集依赖,也就是收集观察当前属性的所有Watcher
const dep = new Dep()
// 获取当前对象的属性描述符,在属性描述符中可以定义set,get和configurable(是否可配置)
const property = Object.getOwnPropertyDescriptor(obj, key)
// 如果当前属性能获取到,还不可配置,就返回
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
// 获取属性描述符中的set和get,因为这个属性是用户传入的,可能用户在传入之前已经设置了get和set,
// 所以要把用户设置的get和set取出来,后来有取重写get和set方法,给get和set增加依赖收集和派发更新的功能.
const getter = property && property.get
const setter = property && property.set
// 特殊情况的判断,如果传递的参数个数是两个,那么value就是obj[key]去获取
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
// 如果shallow是false,是深度监听,此时会调用observe
// 如果当前属性的值val是对象的话,会通过observe去监听val对象的所有属性,去转换getter和setter
let childOb = !shallow && observe(val)
// 调用 Object.defineProperty 将对象转换成getter和setter
Object.defineProperty(obj, key, {
enumerable: true, // 可枚举
configurable: true, // 可配置
get: function reactiveGetter () {
// 首先去调用了用户传入的getter获取值,如果没有就直接获取值
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) {
// 如果预定义 getter 存在就让 value 等于 getter 调用的返回值,否则直接赋予属性值
const value = getter ? getter.call(obj) : val
/* eslint-disable no-self-compare */
// 判断新值和旧值是否相等,|| 后面的其实是针对NaN的情况即新旧值都不为NaN
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
// #7981: for accessor properties without setter
// 如果没有setter直接返回,说明是只读的
if (getter && !setter) return
// 如果setter存在就调用setter给属性赋值
if (setter) {
setter.call(obj, newVal)
} else {
// getter和setter都不存在的时候,直接把新值赋值给旧值
val = newVal
}
// 如果是深度监听,就调用observe方法,其内部会判断如果新值是对象的话,把新值的属性转化成getter和setter
// childOb是Observer对象,是observe方法返回的
childOb = !shallow && observe(newVal)
// 派发更新(发布更改通知)
dep.notify()
}
})
}
依赖收集
在defineReactive函数中,先创建了一个dep对象
js
// 创建Dep对象,负责为当前属性收集依赖,也就是收集观察当前属性的所有Watcher
const dep = new Dep()
下面在Object.defineProperty的调用中收集依赖
js
// 调用 Object.defineProperty 将对象转换成getter和setter
Object.defineProperty(obj, key, {
enumerable: true, // 可枚举
configurable: true, // 可配置
get: function reactiveGetter () {
...
// 收集依赖
// 当我们访问这个值的时候会进行依赖收集,依赖收集就是将依赖该属性的 Watcher 对象添加到Dep对象的sub数组中,将来数据发生变化的时候,通知所有的Watcher
// 先判断这个Dep中是否有target静态属性,target这个属性中存储的是Watcher对象
if (Dep.target) {
// 核心核心
// 如果有的话就调用dep的depend方法,这个depend方法的作用就是进行依赖收集,就是把当前的Watcher对象添加到dep的sub数组中
dep.depend()
// 判断 childOb 子对象是否存在,childOb是Observer对象
if (childOb) {
// 每一个Observer对象都有一个dep的属性,这里用dep的depend方法,让子对象收集依赖
// 这里有两个dep,一个是当前函数中创建的dep对象,作用是为了给当前对象的属性收集依赖,还有一个dep是Observer对象中的属性dep,是为当前子对象收集依赖
/**
* 当属性变化的时候要发送通知,为什么这里要给子对象添加依赖?
* 当子对象中添加成员或者删除成员的时候也需要发送通知去更新视图,这句话的目的是给子对象添加依赖,当子对象的成员发生添加或者删除的时候,可以发送通知
*
* childOb什么时候发送通知呢?将来看$set和$delete的时候再解释
*/
childOb.dep.depend()
if (Array.isArray(value)) {
dependArray(value)
}
}
}
return value
},
set: function reactiveSetter (newVal) {
...
// 派发更新(发布更改通知)
dep.notify()
}
})
Dep.target
选中Dep.target按F12跳转到类定义的地方 core/observer/dep.js
js
export default class Dep {
// 定义了一个静态属性target,其类型是一个Watcher对象
static target: ?Watcher;
...
}
那Dep.target是在哪里赋值的? core/observer/watch.js
在Watcher对象的get方法中调用了一个pushTarget
js
get () {
// 调用pushTarget,将当前的Watcher对象放入栈中
// 调用pushTarget的时候传入的this,this是watcher的实例
pushTarget(this)
...
}
进入pushTarget中可以看到
js
// Dep.target 用来存放目前正在使用的watcher对象
// 全局唯一,并且同一时间只能有一个watcher被使用
Dep.target = null
const targetStack = []
export function pushTarget (target: ?Watcher) {
// 先把watcher对象存入一个栈中
// 这个代码的目的是:在Vue2.0以后,每一个组件对应一个watcher对象,因为每个组件都有一个mountComponent函数,每个mountComponent都会创建一个watcher对象,所以每个组件对应一个watcher对象. 如果组件有嵌套的话,如果A组件嵌套了B组件,当渲染A组件的时候,A组件发现还有子组件,于是要先去渲染子组件,此时A组件的渲染就被挂载起来了,所以A组件对应的watcher对象也应该被存储起来,就被存储到targetStack栈中. 当子组件渲染完毕之后,会把他从对应的栈中弹出继续执行父组件的渲染
targetStack.push(target)
//target就是传入的Watcher对象
Dep.target = target
}
dep.depend
选中depend方法进入
js
// 将观察对象和 watcher 建立依赖
depend () {
if (Dep.target) {
// 如果 target 存在,把 dep 对象添加到 watcher 的依赖中
Dep.target.addDep(this)
}
}
Dep.target就是Watcher对象,要找到Watcher的addDep,选中addDep进入
addDep
js
// 接收参数dep对象
addDep (dep: Dep) {
// 获取Dep的id,每创建一个dep都会让id++,是唯一标识
const id = dep.id
// 当前是否已经存储了Dep对象,如果没有就将id和其对应的dep对象存储到内部的集合中
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
// 这里的watcher中也添加了dep
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
// 最后会将watcher对象添加到dep对象的subs中
dep.addSub(this)
}
}
}
选中addSub进入
addSub
这是收集依赖的核心位置
js
addSub (sub: Watcher) {
// 将Watcher对象添加到Dep的subs数组中
this.subs.push(sub)
}
收集依赖过程总结
- 首先在Watcher的get方法中会给Dep.target赋值,Watcher的get方法,每次访问属性的时候都会被执行(首次渲染的时候和数据变化的时候),这个时候会进行收集依赖
- 如果Watcher对象的target方法存在就调用depand方法,里面最终会把Watcher对象添加到Dep的subs数组中
数组的响应式处理
数组的响应式处理,在Observer类的构造函数中
这里面用到了几个重要函数
- hasProto
- arrayMethods
- protoAugment
- arrayKeys
- copyAugment
- observeArray
js
export class Observer {
...
constructor (value: any) {
// 被观察的对象
this.value = value
//Observer对象都有一个dep属性,里面的值是Dep对象
this.dep = new Dep()
this.vmCount = 0
// 使用def函数,给value对象设置了__ob__属性,把Observer对象记录下来
def(value, '__ob__', this)
// 核心核心核心
// 判断是否是数组
if (Array.isArray(value)) {
/**
* 这里是处理浏览器的兼容性问题,判断当前对象中是否有__proto__,即当前浏览器是否支持对象原型属性
* export const hasProto = '__proto__' in {}
*/
if (hasProto) {
/**
* 这个函数接收两个参数,第一个参数value是当前对象,第二个参数是数组对应的方法
* 这个函数的作用是重新设置数组的原型属性等于第二个参数,修补了一些数组的方法(新增对象的响应式转换,让其dep对象发送通知)
* 其原型指向了数组Array构造函数的原型
*/
protoAugment(value, arrayMethods)
} else {
/**
* 前两个参数一样
* 第三个参数arrayKeys获取arrayMethods中改变原数组的方法名称,是个数组
* const arrayKeys = Object.getOwnPropertyNames(arrayMethods)
*/
copyAugment(value, arrayMethods, arrayKeys)
}
// 遍历数组判断其袁术是否是对象,如果是转换成成响应式对象
this.observeArray(value)
} else {
...
}
}
}
arrayMethods
在observer/array.js中
js
const arrayProto = Array.prototype
// 创建一个对象,将原型的对象指向Array.prototype
export const arrayMethods = Object.create(arrayProto)
// 下面的方法都是修改数组元素的方法,当数组进行修改的时候,我们要通知Dep,修改了数组要对视图进行更新,但是原生方法不知道要通知Dep,所以要进行修补
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
/**
* Intercept mutating methods and emit events
*/
// 遍历数组的每一个元素
methodsToPatch.forEach(function (method) {
// cache original method
// 从数组原型上获取数组对应的原始方法
const original = arrayProto[method]
// 调用Object.defineProperty() 重新定义修改数组的方法
// 第一个是目标对象arrayMethods,第二个是键method,第三个是值,调用pop/push 的时候传入的参数
def(arrayMethods, method, function mutator (...args) {
// 首先会调用数组中原始的方法,并且通过apply改变其内部指向,传入参数args
const result = original.apply(this, args)
// 上面因为改变了原数组,所以还需要进行特殊的处理
// 获取数组对象的 ob 对象,数组关联的Observer对象
const ob = this.__ob__
// 定义这个变量用来存储数组中新增的元素
let inserted
switch (method) {
case 'push':
case 'unshift':
// 如果是push和unshift,传入的参数就是新增的元素,直接赋值
inserted = args
break
case 'splice':
// 如果是splice,其第三个元素是新增的元素,把第三个值存储到inserted中
inserted = args.slice(2)
break
}
// 如果新增元素存在,就调用observeArray
// 遍历数组,并把每一个数组的元素,如果是对象的话就转换成响应式对象,
if (inserted) ob.observeArray(inserted)
// notify change
// 找到Observer的dep对象发送通知
ob.dep.notify()
return result
})
})
总结
- 先获取数组中原有的方法并执行
- 找到数组中新增元素的方法,如果有新增的元素就转换成响应式对象
- 调用dep对象发送通知
copyAugment
js
// 先去遍历之前拿到的修改数组的名称keys,找到keys对应的函数,调用def给当前对象定义之前修改的方法
function copyAugment (target: Object, src: Object, keys: Array<string>) {
for (let i = 0, l = keys.length; i < l; i++) {
const key = keys[i]
def(target, key, src[key])
}
}
observeArray
对数组做响应式处理
js
observeArray (items: Array<any>) {
// 便利数组中的所有成员,如果成员是对象的话就转化成响应式对象
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
总结
如果被观察的对象是数组,对会改变元素的方法进行修补,这些方法被调用的时候调用Dep对象的notify发送通知更新视图,遍历数组中的方法,将元素中的对象转换成响应式对象.