本文主要介绍了vue中的数据绑定原理的实现,分享给大家,也给自己留个笔记,具体如下:
vue中的响应式数据绑定是通过数据劫持和观察者模式来实现的。当前学习源码为vue2.0
源码关键目录
当我们实例化一个vue应用的时候,会伴随着各种的初始化工作,相关的初始化工作代码在init.js文件中
...
initLifecycle(vm)
initEvents(vm)
callHook(vm,'beforeCreate')
initState(vm)
callHook(vm,'created')
initRender(vm)
}
在这里可以看到对state的初始化工作initState()
vm._watchers = []
initProps(vm)
initData(vm)
initComputed(vm)
initMethods(vm)
initWatch(vm)
}
可以看到这里有对各种sate的初始化工作,我们看initData()
let data = vm.$options.data
data = vm._data = typeof data === 'function'
? data.call(vm)
: data || {}
if (!isPlainObject(data)) {
data = {}
process.env.NODE_ENV !== 'production' && warn(
'data functions should return an object.',vm
)
}
// proxy data on instance
const keys = Object.keys(data)
const props = vm.$options.props
let i = keys.length
while (i--) {
if (props && hasOwn(props,keys[i])) {
process.env.NODE_ENV !== 'production' && warn(
The data property "${keys[i]}" is already declared as a prop.
+Use prop default value instead.
,vm)
} else {
proxy(vm,keys[i])
}
}
// observe data
observe(data)
data.ob && data.ob.vmCount++
}
这里做了一点判断,判断data方法是否返回的是一个对象,以及props中是否有与data中重名的属性,最后会调用observe对data进行监听,看一下observe
if (!isObject(value)) {
return
}
let ob: Observer | void
if (hasOwn(value,'ob') && value.ob instanceof Observer) {
ob = value.ob
} else if (
observerState.shouldConvert &&
!config._isServer &&
(Array.isArray(value) || isPlainObject(value)) &&
Object.isExtensible(value) &&
!value._isVue
) {
ob = new Observer(value)
}
return ob
}
可已看到这里也是做了一点判断,如果有__ob__属性的话就用它,或者如果data是数组或对象或可扩展对象的话,就为它新建一个Observer,看一下Observer
value: any;
dep: Dep;
vmCount: number; // number of vms that has this object as root $data
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 through each property 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],obj[keys[i]])
}
}
/**
- Observe a list of Array items.
*/
observeArray (items: Array) {
for (let i = 0,l = items.length; i < l; i++) {
observe(items[i])
}
}
}
判断data是不是数组,如果是数组就对数组元素再去调用observe方法做同样的处理,如果不是,就调用walk去劫持该数据,对数据的劫持主要再defineReactive方法中,正如函数名,让数据变得响应式。看一下defineReactive方法
obj: Object,key: string,val: any,customSetter?: Function
) {
const dep = new Dep()
// data中的每一个成员都有一个对应的Dep,在此闭包创建。
const property = Object.getOwnPropertyDescriptor(obj,key)
if (property && property.configurable === false) {
return
}
// cater for pre-defined getter/setters
const getter = property && property.get
const setter = property && property.set
let childOb = 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)) {
for (let e,i = 0,l = value.length; i < l; i++) {
e = value[i]
e && e.ob && e.ob.dep.depend()
}
}
}
return value
},set: function reactiveSetter (newVal) {
const value = getter ? getter.call(obj) : val
if (newVal === value) {
return
}
if (process.env.NODE_ENV !== 'production' && customSetter) {
customSetter()
}
if (setter) {
setter.call(obj,newVal)
} else {
val = newVal
}
childOb = observe(newVal)
dep.notify() // 发布通知
}
})
}
遍历状态,修改状态的getter和setter,当页面上对应状态被首次渲染的时候,会为页面上每一个使用到data的地方新建一个watcher,并将当前watcher保存到全局变量Dep.target中,在对应data的getter中就会调用Dep.depend方法,将当前的watcher添加到当前的Dep中,一个Dep对应一个或多个watcher,着取决于,此状态被使用的数量。当data被修改时,对应的setter就会被触发,会调用对应的Dep中的notify方法,通知所有观察者,进行更新。
这里出现了两个定的类:Dep和Watcher,其中Dep管理观察者,Wathcer代表观察者
先看一下Dep
static target: ?Watcher;
id: number;
subs: Array
constructor () {
this.id = uid++
this.subs = []
}
addSub (sub: Watcher) {
this.subs.push(sub)
}
removeSub (sub: Watcher) {
remove(this.subs,sub)
}
depend () {
if (Dep.target) {
// 调用当前target,也就是正在处理的watcher的addDep方法,并把此Dep传进去
Dep.target.addDep(this)
}
}
notify () {
// stablize the subscriber list first
const subs = this.subs.slice()
for (let i = 0,l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
看一下watcher.js
...
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)) {
// 将当前watcher添加到当前的Dep中
dep.addSub(this)
}
}
}
...
}
总结
vue的响应式数据绑定主要依赖Object.defineProperty和观察者模式。
- 在我们新建一个vue实例的时候,做一系列的初始化工作,这部分的逻辑集中在src文件夹下的core文件夹下的instance和observer文件夹内
- 响应式数据绑定是在状态的初始化阶段完成的,在initState方法中的initData中进行data的数据绑定。
- 在initData中调用observe方法,为该data新建一个Observer类,然后最终调用为data中的每一个成员调用walk方法,在walk中通过defineReactive方法劫持当前数据
- 在defineReactive中通过Object.defineProperty去修改数据的getter和setter
- 在页面渲染的时候,页面上每一个用到data的地方都会生成一个watcher,并将它保存到全局变量Dep.target中,watcher改变每一个观察者,Dep用来管理观察者。
- 然后在data的getter中将调用Dep的depend方法,将Dep.target中的watcher添加到此data对应的Dep中,完成依赖收集
- 在data被修改的时候,对应data的setter方法就会被出动,会调用Dep.notify()方法发布通知,调用每个watcher的uptade方法进行更新。