在vue原理中,最重要的部分就是如何实现数据的观测,依赖的收集,视图的更新。本文讲的就是Observer, Dep, Watcher这三个的简单实现。 pub(publish)表示发布者,sub(subscribe)表示订阅者, cb(callback)表示回调函数 若是你以为这篇讲的对你有所帮助,请帮我点个starvue
Observer的做用简单来讲就是让object对象的属性都用Object.defineProperty()来进行定义,这样当获取object的属性,或者修改属性的时候,就可以触发get,set达到数据的观测的效果。git
class Observer {
constructor(value) {
this.value = value
this.walk(this.value)
}
walk (value) {
// 递归遍历value的属性
Object.keys(value).forEach((key) = > {
defineReactive(value, key, value[key])
})
}
}
function defineReactive(obj, key ,val) {
let childOb = observe(val)
Obeject.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
console.log('')
return val
},
set(newVal) {
console.log('')
val = newVal
childOb = observe(val)
}
})
}
function observe (value) {
if (typeof value === 'object' && !Array.isArray(value)) {
value = new Observer(value)
}
}
复制代码
defineReactive的做用就是给对象的属性进行简单的数据观测,一旦值获取或者设置就会触发一些行为.由于一个对象的属性可能仍是对象,因此在这里咱们添加observe函数来遍历值,让一个对象的属性的属性仍是能够进行观测的,简单呢来讲的意思就是让全部属性均可以进行忽略。固然在实际状况中,咱们还须要考虑数组的状况,但都大同小异。 这样作代码彷佛有点丑,咱们在设置属性触发set会发生console.log()函数,有没有一种更加智能的方式来实现通知变化呢。这里咱们就须要用消息订阅器来进行实现,这样作咱们就不须要经过观察console.log()输出的值来看进行的状况,咱们只须要在set方法里边加一个通知,一旦值发生变化,就通知外边值发生了改变github
Dep的做用就是用来收集属性值的变化,一旦set方法触发的时候,就更新视图。那就准备一个数组来进行收集吧! 下面是Dep的实现:数组
class Dep {
constructor() {
this.subs = []
}
addSub (sub) {
this.subs.push(sub)
}
notify () {
const subs = this.subs.slice()
subs.forEach((sub) => {
sub.update() // 视图更新
})
}
}
复制代码
上面就是Dep的简单实现,addSub的做用是增长订阅者,由于有不少订阅者,咱们须要用一个数组将它进行存储,notify()函数的做用就是当set发生的时候,进行通知,update()这个函数待会在watcher中会讲到。实现了Dep咱们是否是该更改了set()函数了呢,下面是defineReactive()修改后的代码bash
function defineReactive(obj, key ,val) {
let dep = new Dep() // 毕竟要使用Dep的方法
let childOb = observe(val)
Obeject.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
return val
},
set(newVal) {
val = newVal
childOb = observe(val)
dep.notify() // 由于数据改变了,咱们就通知Dep
}
})
}
复制代码
一旦触发set,就调用dep.notify(),notify的做用就是针对订阅者遍历进行更新。函数
watcher的做用,就是当状态发生改变的时候,更新视图,咱们能够假设ui
class Watcher {
constructor (vm, cb, expOrFn) {
this.vm = vm // 这表示一个Vue的实例
this.cb = cb
// 这里须要考虑expOrFn是字符串或者函数的状况
// 这里作一个简化,只考虑函数的状况
this.getter = expOrFn
this.value = this.get()
}
get () {
Dep.target = this
const vm = this.vm
value = this.getter.call(this.vm, vm)
Dep.target = null
return value
}
update () {
this.run()
}
run () {
const value = this.get()
if (value !== this.value) {
const oldValue = this.value
this.value = value
this.cb.call(this.vm, value, oldValue)
}
}
}
复制代码
Watcher的简单实现就完成了,在Dep()构造函数中,咱们使用了sub.update()这行代码,而update函数是Watcher里边的方法,说明每个sub都是Wathcer的实例,问题是咱们应该如何经过addSub()这个方法,将Watcher加入到subs这个数组中尽心存储呢,答案仍是在defineReactive()里边进行修改this
function defineReactive(obj, key ,val) {
let dep = new Dep() // 毕竟要使用Dep的方法
let childOb = observe(val)
Obeject.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
if(Dep.target) {
dep.addSub(Dep.target)
}
return val
},
set(newVal) {
val = newVal
childOb = observe(val)
dep.notify() // 由于数据改变了,咱们就通知Dep
}
})
}
复制代码
这样是否是就实现了往Dep里边加Watcher了,vue源码中比这个复杂的多,各类参数,看着头大。本文的宗旨就是经过简化让你了解内部原理,若是须要更深刻了解就须要阅读源码了。spa