概述
- vue响应式用了观察者的设计模式,响应式data的数据被修改,观察者会进行视图更新或者执行回调
1.用Observer类将对象变成响应式
- 遍历对象的每个属性:
- 给对象的每个属性创建Dep依赖收集器
- Object.defineProperty给对象的每个属性定义set、get方法:
get:使用Dep来收集观察者
set:Dep派发通知给收集到的观察者
- 如果对象的属性也是一个对象,进行递归,重复以上操作
class Observer {
constructor (value: Object) {
this.walk(value)
}
walk (obj: Object) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) { // 遍历对象的每个key
defineReactive(obj, keys[i], obj[keys[i]])
}
}
}
const observe = (value: any) => {
if (!isObject(value)) {
return
}
return new Observer(value)
}
function defineReactive (
obj: Object,
key: string,
val: any
) {
const dep = new Dep() //给该属性创建依赖收集器
observe(val) // 该属性可能是一个对象,用observe递归
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
dep.depend() // 进行依赖收集,收集观察者
return val
},
set: function reactiveSetter (newVal) {
val = newVal
observe(newVal) // 新的属性值可能是一个对象,用observe进行响应式处理,也就是重复以上的流程
dep.notify() // 收集器派发通知给观察者
}
})
}
2.Dep依赖收集器,收集观察者
- 上面get方法里面使用了dep.depend收集观察者,我们假设观察者是一个函数,保存在window.target上
- 那么dep收集观察者的逻辑就可以这么写:
export default class Dep {
constructor () {
this.id = uid++
this.subs = [] //收集器用来存放观察者的数组
}
addSub (sub: Watcher) {
this.subs.push(sub)
}
depend () {
if (window.target) {
this.addSub(window.target) // 把观察者添加到subs数组上
}
}
notify () {
const subs = this.subs.slice()
for (let i = 0, l = subs.length; i < l; i++) {
subs[i].update()
}
}
}
3.Watcher观察者
- Watcher是一个中介,当对象变化时通过dep.notify() 通知它,它再通知其他的地方
- 先看一下Watcher经典的使用方式
vm.$watch('user.name', function() {
// doing sometings
})
- 该函数代表user对象的name属性发生变化时,执行回调函数
- 上面有说过对象的每个属性都会创建一个Dep,并且在该属性的get方法上进行依赖收集。我们只需要触发name属性的get方法,把这个watcher实例添加到name属性的Dep上就行了
class Watcher {
constructor (
vm: Component,
expOrFn: string | Function,
cb: Function,
) {
this.vm = vm
this.cb = cb
this.getter = parsePath(expOrFn) // 解析user.name,生成访问name属性的函数
this.value = this.get()
}
get () {
window.target = this
const vm = this.vm
// getter是可以访问name属性的函数
// 此时会触发name属性的get函数执行dep.depend收集观察者
// 观察者在上面一步已经赋值到了window.target上了,dep.depend可以进行收集
let value = this.getter.call(vm, vm)
window.target = undefined // 收集完毕,清空window.target
return value
}
// 当name属性被修改时,触发dep.notify。notify函数会执行观察者的update方法
update () {
const oldValue = this.value
this.value = this.get()
this.cb.call(this.vm, this.value, oldValue) // 执行$watch('user.name', cb)的回调函数
}
}
4.Array响应式
- Array能够改变自身内容的方法有7个:push、pop、shift、unshfit、sort、splice、reverse
- 我们可以对数组的这些函数进行修改、在这些函数当中执行dep.notify通知观察者:
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
methodsToPatch.forEach(function (method) {
const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args)
// todo。这里执行通知观察者的逻辑
return result
})
})
- 我们微调一下Observer的代码,把以上重写后的数组方法赋值给数组
class Observer {
constructor (value: Object) {
if(Array.isArray(value)) {
value.__proto__ = arrayMethods // __proto__ 有兼容性问题 兼容性处理省略
}else {
this.walk(value)
}
}
}
- 我们重写了数组的方法,在这些方法里面执行Dep的通知。但我们还没有为数组创建Dep。
- 上面说到对象是遍历每个属性,为每个属性创建一个Dep,那么数组的Dep应该在那里创建呢?
- 我们可以在存放在Observer的实例上,Observer再次调整如下:
class Observer {
constructor (value: Object) {
this.dep = new Dep() // 新增
def(value, '__ob__', this) // 新增 在数组上添加__ob__属性指向当前的Observer实例
if(Array.isArray(value)) {
value.__proto__ = arrayMethods
}else {
this.walk(value)
}
}
}
- 这样数组就可以通过__ob__属性访问到dep了,调整arrayMethods如下:
methodsToPatch.forEach(function (method) {
const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args)
this.__ob__.dep.notify() // 这里执行通知观察者的逻辑
return result
})
})
- 通知观察者的逻辑实现完了,现在实现收集观察者的逻辑,我们调整defineReactive的代码如下:
function defineReactive (
obj: Object,
key: string,
val: any
) {
const dep = new Dep()
const childOb = observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get: function reactiveGetter () {
if(childOb) { // 新增
childOb.dep.depend() // 新增
}
dep.depend()
return val
},
set: function reactiveSetter (newVal) {
val = newVal
observe(newVal)
dep.notify()
}
})
}
- 以上已经处理好了数组的响应式,但是数组里面的每个元素却不是响应式的,我们再一次调整Observer:
class Observer {
constructor (value: Object) {
if(Array.isArray(value)) {
value.__proto__ = arrayMethods
this.observerArray(value) // 新增
}else {
this.walk(value)
}
}
}
observeArray (items: Array<any>) {
for (let i = 0, l = items.length; i < l; i++) {
observe(items[i])
}
}
掘金:peng_YT 的个人主页 - 动态 - 掘金
语雀:Peng_YT · 语雀
最后
以上就是慈祥花生为你收集整理的Vue2响应式原理总结的全部内容,希望文章能够帮你解决Vue2响应式原理总结所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复