合规国际互联网加速 OSASE为企业客户提供高速稳定SD-WAN国际加速解决方案。 广告
object是通过 getter 和 seter 来监听数据的变化,但是数组方法 push 执行的时候,是不会触发 getter 和 setter的,那么数组是怎么监听数组元素的变换的呢。 ## 数组如何实现响应式 ### 拦截器 ![](https://img.kancloud.cn/31/8e/318e166e879cf0035db30be37b26c165_1328x662.png) 使用数组上的 push pop shift unshift reverse sort splice (因为这几个是改变原数组的方法)这 7 个方法时,vue进行拦截,**阻止调用原型上的方法,去调用我们在拦截器里写的方法**,这样就可以在拦截器重写的方法里,做一些 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) const ob = this.__ob__ let inserted switch (method) { case 'push': case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) // inserted 是 新增的元素 break } if (inserted) ob.observeArray(inserted) // 新增的元素调用 observeArray, 使其变成响应式的数据 // notify change ob.dep.notify() return result }) }) ``` ### 数组中新增是如何侦测的 在拦截器中,判断如果是 push unshift splice时,拿到新增的元素,并调用 **new Observer** 来使新增的数据变成响应式的。 ### 拦截器何时覆盖 Array的 原型 在数据需要转换被成响应式数据的时候,替换数组原型为我们新写的拦截器 arrayMethods 。 数组中的元素通过循环调用 **new Observer(value)** ,来转换成响应式数据。 ``` if (Array.isArray(value)) { if (hasProto) { protoAugment(value, arrayMethods) } else { copyAugment(value, arrayMethods, arrayKeys) } this.observeArray(value) } else { this.walk(value) } observeArray (items: Array) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } } export function observe (value: any, asRootData: ?boolean): Observer | void { if (!isObject(value) || value instanceof VNode) { return } let ob: Observer | void if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { ob = value.__ob__ } else if ( shouldObserve && !isServerRendering() && (Array.isArray(value) || isPlainObject(value)) && Object.isExtensible(value) && !value.\_isVue ) { ob = new Observer(value) } if (asRootData && ob) { ob.vmCount++ } return ob } function protoAugment (target, src: Object) { target.__proto__ = src } ``` ![](https://img.kancloud.cn/72/72/7272f5c0cf8587869d2b8e64f931fd8a_1099x715.png) ## 如何收集和触发数组的依赖 数组依赖的收集也是在 defineProperty 的 getter 中收集 在拦截器中触发依赖 ``` get: function reactiveGetter () { const value = getter ? getter.call(obj) : val if (Dep.target) { dep.depend() if (childOb) { childOb.dep.depend() if (Array.isArray(value)) { dependArray(value) } } } return value } function dependArray (value: Array) { for (let e, i = 0, l = value.length; i < l; i++) { e = value[i] e && e.__ob__ && e.__ob__.dep.depend() if (Array.isArray(e)) { dependArray(e) } } } ``` 问题: 1. 依赖存在哪里? 2. 拦截器中如何取到依赖 ? a. 依赖存在 Observer 的 dep 中. b. 收集依赖时,getter 可以拿到Observer的实例, 拦截器也是在 Observer 的构造函数中调用。(转换成响应式数据的时候),这样拦截器就也可以拿到 Observer的实例。拿到实例就可以拿到实例上的 dep,进行依赖的收集和触发。 c. 通过 __ob__ 这个属性拿到 Observer 的实例,所以 __ob__ 也可以用来表示是数据是响应式的。 ## 总结 * Array 数组通过拦截器覆盖数组原型的方式来实现变化侦测,追踪数据的变化,通知依赖 * 拦截器覆盖只会覆盖那些需要变化侦测的数据,而不是全局 * Array 收集依赖和 Object 是一样的,都在在 getter 中收集 * Array 收集依赖在 Observer的实例中, Object收集的依赖在 Dep的实例中 * __ob__ 属性可以用来判断数据是否已经是响应式数据,也可以用来获取 Observer的实例 * 在拦截器中,数组中新增的元素需要调用 new Observer 使新增元素变成响应式数据