我的博客地址vue
像Vue官网上面说的,vue是经过Object.defineProperty
来侦测对象属性值的变化。数组
function defineReactive (obj, key, val) { let dep = new Dep() Object.defineProperty(obj, key, { enumerable: true, configurable: true, get () { return val }, set (newVal) { if (val === newVal) return val = newVal } }) }
函数 defineReactive 是对 Object.defineProperty 的封装,做用是定义一个响应式的数据。浏览器
不过若是只是这样是没有什么用的,真正有用的是收集依赖。在getter中收集依赖,在setter触发依赖。缓存
Dep (收集依赖)闭包
// 还有几个方法没写,好比怎么移除依赖。 class Dep { constructor () { // 依赖数组 this.subs = [] } addSub (sub) { this.subs.push(sub) } depend (target) { if (Dep.target) { // 这时的Dep.target是Watcher实例 Dep.target.addDep(this) } } notity () { this.subs.forEach(val => { val.update() }) } Dep.target = null }
Watcher (依赖)app
// 原本在Watcher中也要记录Dep,可是偷懒没写了,记录了Dep后能够通知收集了Watcher的Dep移除依赖。 class Watcher { constructor (vm, expOrFn, cb) { // vm: vue实例 // expOrFn: 字符串或函数 // cb: callback回调函数 this.vm = vm this.cb = cb // 执行this.getter就能够读取expOrFn的数据,就会收集依赖 if (typeof expOrFn === 'function') { this.getter = expOrFn } else { // parsePath是读取字符串keypath的函数,具体的能够去浏览Vue的源码 this.getter = parsePath(expOrFn) } this.value = this.get() } get () { Dep.target = this // 在这里执行this.getter let value = this.getter(this.vm, this.vm) Dep.target = null return value } addDep (dep) { dep.addSub(this) } // 更新依赖 update () { const oldValue = this.value this.value = this.get() this.cb.call(this.vm, this.value, oldValue) } }
接下来再改一下刚开始定义的 defineReactive 函数函数
function defineReactive (obj, key, val) { let dep = new Dep() // 闭包 Object.defineProperty(obj, key, { enumerable: true, configurable: true, get () { // 触发getter时,收集依赖 dep.addDep() return val }, set (newVal) { if (val === newVal) return val = newVal // 触发setter时,触发Dep的notify,便利依赖 dep.notity() } }) }
这个时候已经能够侦测数据的单独一个属性,最后再封装一下:this
class Observer { constructor (value) { this.value = value // 侦测数据的变化和侦测对象的变化是有区别的 if (!Array.isArray(value)) { this.walk(value) } } walk (value) { const keys = Object.keys(value) keys.forEach(key => { this.defineReactive(value, key, value[key]) }) } }
最后总结一下:prototype
实例化 Watcher 时经过 get 方法把 Dep.target 赋值为当前的 Wathcer 实例,并把 Watcher 实例添加在 Dep 中,当设置数据时,触发 defineReactive 的 set 运行 Dep.notify() 遍历 Dep 中收集的依赖 Watcher 实例,而后触发 Watcher 实例的 update 方法。code
Object 能够经过 getter/setter 来侦测变化,可是数组是经过方法来变化,好比 push 。这样就不能和对象同样,只能经过拦截器来实现侦测变化。
定义一个拦截器来覆盖 Array.prototype,每当使用数组原型上面的方法操做数组的时候,实际上执行的是拦截器上面的方法,而后再拦截器里面使用 Array 的原型方法。
const arrayProto = Array.prototype const arrayMethods = Object.create(arrayProto) const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse' ] methodsToPatch.forEach(function (method) { // 缓存原始方法 const original = arrayProto[method] Object.defineProperty(arrayMethods, method, { enumerable: false, configurable: true, writable: true, value: function mutator (...args) { return original.apply(this, args) } })
而后就要覆盖 Array 的原型:
// 看是否支持__proto__, 若是不支持__proto__,则直接把拦截器的方法直接挂载到value上。 const hasProto = "__proto__" in {} const arrayKeys = Object.getOwnPropertyNames(arrayMethods) class Observer { constructor (value) { this.value = value if (!Array.isArray(value)) { this.walk(value) } else { const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arraykeys) } } walk (value) { const keys = Object.keys(value) keys.forEach(key => { this.defineReactive(value, key, value[key]) }) } } function protoAugment (target, src: Object) { target.__proto__ = src } function copyAugment (target: Object, src: Object, keys: Array<string>) { for (let i = 0, l = keys.length; i < l; i++) { const key = keys[i] def(target, key, src[key]) } }
Array 也是在 getter 中收集依赖,不过依赖存的地方有了变化。Vue.js 把依赖存在 Observer 中:
class Observer { constructor (value) { this.value = value this.dep = new Dep // 新增Dep if (!Array.isArray(value)) { this.walk(value) } else { const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arraykeys) } } walk (value) { const keys = Object.keys(value) keys.forEach(key => { this.defineReactive(value, key, value[key]) }) } }
至于为何把 Dep 存在 Observer 是由于必须在 getter 和 拦截器中都能访问到。
function defineReactive (data, key, val) { let childOb = observer(val) // 新增 let dep = new Dep() Object.defineProperty(obj, key, { enumerable: true, configurable: true, get () { dep.addDep() if (childOb) { // 在这里收集数组依赖 childOb.dep.depend() } return val }, set (newVal) { if (val === newVal) return val = newVal dep.notity() } }) } // 若是value已是响应式数据,即有了__ob__属性,则直接返回已经建立的Observer实例 // 若是不是响应式数据,则建立一个Observer实例 function observer (value, asRootData) { if (!isObject(value)) { return } let ob if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observe) { ob = value.__ob__ } else { ob = new Observer(value) } return ob }
由于拦截器是对 Array 原型的封装,因此能够在拦截器中访问到this(当前正在被操做的数组),
dep保存在 Observer 实例中,因此须要在this上访问到 Observer 实例:
function def (obj, key, val, enumerable) { Object.defineProperty(obj, key, { value: val, enumerable: !!enumerable, writable: true, configerable: true }) } class Observer { constructor (value) { this.value = value this.dep = new Dep // 把value上新增一个不可枚举的属性__ob__,值为当前的Observer实例 // 这样就能够经过数组的__ob__属性拿到Observer实例,而后就能够拿到Observer的depp // __ob__不止是为了拿到Observer实例,还能够标记是不是响应式数据 def(value, '__ob__', this) // 新增 if (!Array.isArray(value)) { this.walk(value) } else { const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arraykeys) } } ... }
在拦截器中:
methodsToPatch.forEach(function (method) { const original = arrayProto[method] def(arrayMethods, method, function mutator (...args) { const result = original.apply(this, args) const ob = this.__ob__ // 新增 ob.dep.notify() // 新增 向依赖发送信息 return resullt }) })
到这里还只是侦测了数组的变化,还要侦测数组元素的变化:
class Observer { constructor (value) { this.value = value this.dep = new Dep def(value, '__ob__', this) if (!Array.isArray(value)) { this.walk(value) } else { const augment = hasProto ? protoAugment : copyAugment augment(value, arrayMethods, arraykeys) // 侦测数组中的每一项 this.observeArray(value) // 新增 } } observeArray (items) { items.forEach(item => { observe(item) }) } ... }
而后还要侦测数组中的新增元素的变化:
methodsToPatch.forEach(function (method) { const original = arrayProto[method] def(arrayMethods, method, function mutator (...args) { const result = original.apply(this, args) const ob = this.__ob__ // 新增开始 let inserted switch (method) { case 'push' case 'unshift' inserted = args breaak case 'splice' inserted = args.slice(2) break } if (inserted) ob.observeArray(inserted) // 新增结束 ob.dep.notify() return resullt }) })
总结一下:
Array 追踪变化的方式和 Object 不同,是经过拦截器去覆盖数组原型的方法来追踪变化。
为了避免污染全局的 Array.prototype ,因此只针对那些须要侦测变化的数组,对于不支持 __proto__
的浏览器则直接把拦截器布置到数组自己上。
在 Observer 中,对每一个侦测了变化的数据都加了 __ob__
属性,而且把this(Observer实例)
保存在__ob__
上,主要有两个做用:
__ob__
,进一步拿到 Observer 实例。因此把数组的依赖存放在 Observer 中,当拦截到数组发生变化时,向依赖发送通知。
最后还要经过observeArray
侦测数组子元素和数组新增元素的变化。