最近在看“深刻浅出vuejs”,第一篇变化侦测,想把本身的理解总结一下。vue
Observer
类来实现this
当前的上下文;因此当数据变化时,咱们能够通知他,触发update
,从而触发渲染Dep
类来实现class Observer { constructor(value) { this.value = value if(!Array.isArray(value) { this.walk(value) } } walk (obj) { const keys = Object.keys(obj) for(let i = 0; i < keys.length; i++) { definedReactive(obj, keys[i], obj[keys[i]]) } } } function definedReactive(data, key, value) { if(typeof val === 'object') { new Observer(value) } let dep = new Dep() Object.defineProperty(data, key, { enumberable: true, configurable: true, get: function () { dep.depend() return value }, set: function (newVal) { if(value === newVal) { //这边最好是value === newVal || (value !== value && newVal !== newVal) return } value = newVal //这边新的newVal若是是引用类型也应该进行进行new Observer() dep.notify() } }) }
data
对象进行遍历设置其属性描述对象get
的设置就是为了在数据被访问时,将依赖dep.depend()
进去,至于作了什么看详细看Dep类set
的设置则是为了判断新值和旧值是否同样(注意NaN),若不同,则执行dep.notify()
,通知相应依赖进行更新变化class Dep { constructor () { this.subs = [] //存放依赖 } addSub () { this.subs.push(sub) }, remove () { remove(this.subs, sub) }, depend () { if(window.target) { this.addSub(window.target) //window.target 是this,watcher的上下文 } }, notify () { const subs = this.subs.slice() for(let i = 0, l = subs.length; i < l; i ++) { subs[i].update() //update这个方法来自watcher实例对象的方法 } } } function remove(arr, item) { if(arr.length) { const index = arr.indexOf(item) if(index > -1) { return arr.splice(index, 1) } } }
dep
实例对象的增删改查的操做window.target
这个依赖怎么来,就看watcher
实例对象了第一版:数组
class Watcher { constructor (vm, expOrFn, cb) { this.vm = vm this.getter = parsePath(expOrFn) this.cb = cb this.value = this.get() } get() { window.target = this let value = this.getter.call(this.vm, this.vm) window.target = undefined return value } update() { const oldValue = this.value this.value = this.get() this.cb.call(this.vm, this.value, oldValue) } }
vm.$watch('data.a', function (newValue, oldValue) { //执行相关操做 })
parsePath(expOrFn)
作了什么?从下面代码中能够看出做用就是返回一个函数,这个函数用来读取value
值const bailRE = /[^\w.$]/ // function parsePath(path) { if(bailRE.test(path) { return //当path路径中有一个字符不知足正则要求就直接return } return function () { const arr = path.split('.') let data = this for(let i = 0, l = arr.length; i < l; i ++) { let data = data.arr[i] } return data } }
new Watcher
时会执行this.value
,从而执行this.get()
,因此这时的window.target
是当前watcher
实例对象this
;接着执行this.getter.call(this.vm, this.vm)
,触发属性描述对象的get
方法,进行dep.depend()
,最后将其window.target = undefined
update
的方法是在数据改变后触发,但这边有个问题就是会重复添加依赖 key
进行监听key-value
不会被监听Observer
dep.notify()
,以及如何收集数组的依赖push, pop, shift, unshift, splice, sort, reverse
这几个方法的封装来触发dep.notify()
_proto_
属性的,直接改写原型链的这些方法;第二种对于不支持的,直接在实例对象上添加改变后的7个方法const arrayProto = Array.prototype const arrayMethods = Object.create(arrayProto) //新建对象,继承Array的原型链 class Observer { constructor (value) { this.value = value this.dep = new Dep() //在Observer中添加dep属性为了记录数组的依赖 def(value, "_ob_", this) //在当前value上新增`_ob_`属性,其值为this,当前observer实例对象 if(Array.isArray(value) { const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arrayKeys) this.observerArray(value) //将数组内元素也进行Observer }else { this.walk(value) } } //新增 observerArray (items) { for(let i = 0, l = items.length; i < l; i ++) { observe(items[i]) } } } //做用就是为obj,添加key值为val function def(obj, key, val, enumerable) { Object.defineProperty(obj, key, { value: val, enumerable: !!enumerable, writable: true, configurable: true }) } function observe(value, asRootData) { if(!isObject(value)) { return } let ob //判断value是否已是Observer实例对象,避免重复执行Observer if(hasOwn(value, "_ob_") && value._ob_ instanceof Observer) { ob = value._ob_ } else { ob = new Observer(value) } return ob } function definedReactive(data, key, value) { let childOb = observe(value) //修改 let dep = new Dep() Object.defineProperty(data, key, { enumberable: true, configurable: true, get: function () { dep.depend() if(childOb) { //新增 childOb.dep.depend() } return value }, set: function (newVal) { if(value === newVal) { //这边最好是value === newVal || (value !== value && newVal !== newVal) return } value = newVal //这边新的newVal若是是引用类型也应该进行进行new Observer() dep.notify() } }) } //触发数组拦截 ;[ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ].forEach(function (method) { const original = arrayProto[method] def(arrayMethods, method, function mutator() { const result = original.apply(this, args) const ob = this._ob_ //this就是数据value let inserted //对于新增变化的元素页进行observerArray() switch (method) { //由于这几个是有参数的 case 'push': case 'unshift': //由于push和unshift都是同样的取args,因此push不须要加break了 inserted = args break case 'splice': //新增变化元素是从索引2开始的 inserted = args.slice(2) break } ob.dep.notify() //通知依赖执行update return result }) }
data = { a: [1, 2, 3] }
为例data
对象进行Observer
,将执行this.walk(data)
let childOb = observe(val)
,发现value
是一个数组对象,进行Observer
,主要进行是augment(value, arrayMethods, arrayKeys)
,将7个方法进行拦截,接着遍历内部元素是否有引用数据类型,有继续Observer
,最后返回Observer
实例对象ob
get
方法,当数据data被访问时,首先执行dep.depend()
这里将依赖添加到data
的dep
中;接着由于childOb
为true
因此执行childOb.dep.depend()
,这里是将依赖加入到observer
实例对象的dep
中,为何,这个dep
是给数组发生变化时执行this._ob_.dep.notify()
,这个this就是value
对象,由于def(value, "_ob_", this)
,因此能够执行dep.notify()
this.list.length = 0
进行清空时,不会触发它的依赖更新,也就不会触发视图的渲染更新this.list[0] = 2
,这种经过索引来改变元素值时页同样不会触发更新vm.$watch,vm.$set,vm.$delete
下篇中进行整理掘金地址app