vue源码学习——初始化data
vue源码学习——响应式数据
在《vue源码学习——初始化data》一文中,知道了在new Vue()时做了一系列初始化操作,其中在初始化data数据时,利用observe(data,true)方法,对数据属性进行了观察。下面来具体看下是如何对data进行的观察,从而实现数据驱动~
vue版本:v2.5.21
代码如下(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 // 定义observer实例 并最后返回
if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) {
ob = value.__ob__
} else if (
shouldObserve &&
!isServerRendering() && // 判断是否服务端渲染,node环境
(Array.isArray(value) || isPlainObject(value)) && // 只有当数据对象是数组或纯对象的时候
Object.isExtensible(value) && // 对象可扩展 即不是冻结对象等等
!value._isVue //不是Vue实例
) {
ob = new Observer(value) // 创建一个 Observer 实例
}
if (asRootData && ob) {
ob.vmCount++
}
return ob
}
value
【纯对象数据】、asRootData
【是否是根级数据,用来统计vm数量ob.vmCount++
】ob = new Observer(value)
代码如下(src/core/observer/index.js):
export class Observer {
value: any; // 纯对象
dep: Dep;
vmCount: number; // number of vms that have this object as root $data,vm数量
constructor (value: any) {
this.value = value // 纯对象,observe函数已经判定过
this.dep = new Dep() // 依赖
this.vmCount = 0 // vm数量,observe函数赋值过此值
def(value, '__ob__', this) // 给数据定义了__ob__属性,值为当前的Observe实例
if (Array.isArray(value)) {
// 若此对象是个数组
if (hasProto) {
// [util/env.js] : hasProto = '__proto__' in {}
protoAugment(value, arrayMethods) // [./array.js] value.__proto__ = arrayMethods , 重写了数组的7个方法,
} else {
copyAugment(value, arrayMethods, arrayKeys) //
}
this.observeArray(value) // 观察数组,遍历数组,依次观察各项:observe(items[i])
} else {
// 纯对象,调用walk,继续走流程
this.walk(value)
}
}
value
【观察的对象】、dep
【依赖收集容器】、vmCount
【vm个数】def(value, '__ob__', this)
,value对象下,挂载__ob__
属性,对应的值为实例本身(this)。也就是在vue中所有data下都挂载一个 __ob__
属性,为观察数据添加观察者引用,如下:const data = {
a: 1,
__ob__: {
value: data, // value 属性指向 data 数据对象本身,这是一个循环引用
dep: dep实例对象, // new Dep()
vmCount: 0
}
}
Array.isArray(value)
判定是否是数组对象,这里对数组对象做了特殊处理,重写了数组的7个方法(在observer/array.js),observeArray(value)遍历数组各项进行观察observe(items[i])
,又回到了最初流程。 walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i]) // 遍历可枚举属性,为每个属性 defineReactive
}
}
defineReactive(obj, keys[i])
为每个对象属性进行getter,setter设置,把每个属性数据变成了响应式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) {
// 当且仅当指定对象的属性描述可以被改变或者属性可被删除时,configurable为true。
return
}
// cater for pre-defined getter/setters
const getter = property && property.get // 获取该属性的访问器函数(getter)
const setter = property && property.set // 获取该属性的设置器函数(setter)。 如果没有设置器, 该值为undefined
if ((!getter || setter) && arguments.length === 2) {
val = obj[key]
}
// !shallow时,observe(val)递归调用深度监听
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true, // 可枚举
configurable: true, // 可修改
get: function reactiveGetter () {
const value = getter ? getter.call(obj) : val // 有 `getter ` 就是自定义的`get`函数,如果没有就是取`val`的值。
if (Dep.target) {
// Dep.target 全局变量指向的就是当前正在解析指令的Complie生成的 Watcher
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 // 首先是判断了是使用有 `getter ` 就是自定义的`get`函数,如果没有就是取`val`的值。
/* eslint-disable no-self-compare */
if (newVal === value || (newVal !== newVal && value !== value)) {
return
}
/* eslint-enable no-self-compare */
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (getter && !setter) return // 有getter,没setter时,为只读状态,不可修改,继而无需观察
if (setter) {
// 如果有`setter`就调用`setter`处理,否则直接复制给`val`。
setter.call(obj, newVal)
} else {
val = newVal
}
childOb = !shallow && observe(newVal) // `shallow `为false的话,就重新监听`newVal`的值。
dep.notify() // 调用 Dep 实例的 notify 方法, 更新
}
})
}
Object.getOwnPropertyDescriptor(obj, key)
获取该字段可能已有的属性描述对象,属性key对应的属性描述符,若属性存在于obj上,返回其属性描述符对象(property descriptor),否则返 undefined。Object.defineProperty
是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。property.get
、property.set
获取该属性的访问器函数(getter
),设置器函数(setter
),若无返回undefinedgetter
中,Dep.target
【实则是一个Watcher】存在时,在依赖容器dep中添加依赖dep.depend()
(下面讲到)setter
中,更新完最新值后,调用了dep.notify()
发出了DOM更新通知代码如下(src/core/observer/dep.js):
let uid = 0 // 全局变量,唯一标识
export default class Dep {
static target: ?Watcher; // 当前正在计算的一个watcher对象,
id: number; // 每个Dep实例都有唯一的ID
subs: Array<Watcher>; // subs用于存放依赖
constructor () {
this.id = uid++ // 保证每个Dep实例都有唯一的ID
this.subs = []
}
// 添加依赖
addSub (sub: Watcher) {
this.subs.push(sub)
}
// 移除依赖
removeSub (sub: Watcher) {
remove(this.subs, sub)
}
depend () {
if (Dep.target) {
// 对应的就是下面的全局变量Dep.target对应的watcher
Dep.target.addDep(this) // 调用当前watcher的appDep(this)
}
}
// 通知更新,defineReactive dep=new Dep() ==>setter dep.notify() =>watcher update() ==> ...更新UI
notify () {
const subs = this.subs.slice()
if (process.env.NODE_ENV !== 'production' && !config.async) {
subs.sort((a, b) => a.id - b.id)
}
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update() // 通知所有绑定 Watcher。调用watcher的update()
}
}
}
// 这是全局变量,因为任何时候都可能只有一个watcher正在评估。
Dep.target = null
const targetStack = []
export function pushTarget (target: ?Watcher) {
targetStack.push(target)
Dep.target = target
}
export function popTarget () {
targetStack.pop()
Dep.target = targetStack[targetStack.length - 1]
}
Observer
实例中,对象属性getter
中调用Dep
实例的dep.depend()
,最终调用的是Dep.target.addDep(this)
,Dep.target
全局变量存储的是当前的Watcher
实例,也就是调用的watcher的addDep()方法(下面提到)Observer
实例中,对象属性setter
中调用Dep
实例的dep.notify()
,最终调用的是subs[i].update()
,subs
存储的所有的watchers,数组遍历调用watcher的update()方法,也就是说当observer对应setter触发时,会向对应的dep中全部watchers发送更新消息Dep.target
代表着当前正在计算的watcher实例。通过在watcher中调用Dep的pushTarget()
、popTarget()
更改Dep.target代码如下(src/core/observer/watcher.js):
export default class Watcher {
//...
addDep (dep: Dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) {
dep.addSub(this)
}
}
}
update () {
if (this.lazy) {
this.dirty = true
} else if (this.sync) {
this.run()
} else {
queueWatcher(this)
}
}
//...
}
每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。
addDep()
方法,绕了一圈,又调用的是dep.addSub(this)
,Dep实例的addSub()方法就是在this.subs依赖数组里面增加一个依赖。notify()
方法,会触发组件重新渲染。