看这篇以前,若是没有看过以前的文章,可拉到文章末尾查看以前的文章。vue
在前面的几个 step
中,咱们实现对象的属性的监听,可是有关于数组的行为咱们一直没有处理。
咱们先分析下致使数组有哪些行为:git
arr.splice(1, 2, 'something1', 'someting2')
arr[1] = 'something'
首先咱们知道数组下的一些方法是会对原数组照成影响的,有如下几个:github
这几个方法总的来讲会照成几个影响:数组
不像对象,若是对象的 key
值的顺序发生变化,是不会影响视图的变化,但数组的顺序若是发生变化,视图是要变化的。app
也就是说当着几个方法触发的时候,咱们须要视图的更新,也就是要触发 Dep
中的 notify
函数。函数
可是纵观咱们如今实现的代码( step5
中的代码),咱们并无特意的为数组提供一个 Dep
。测试
而且上述的几个数组方法是数组对象提供的,咱们要想办法去触发 Dep
下的 notify
函数。优化
咱们先为数组提供一个 Dep
,完善后的 Observer
:this
export class Observer { constructor(value) { this.value = value if (Array.isArray(value)) { // 为数组设置一个特殊的 Dep this.dep = new Dep() this.observeArray(value) } else { this.walk(value) } Object.defineProperty(value, '__ob__', { value: this, enumerable: false, writable: true, configurable: true }) } /** * 遍历对象下属性,使得属性变成可监听的结构 */ walk(obj) { const keys = Object.keys(obj) for (let i = 0; i < keys.length; i++) { defineReactive(obj, keys[i], obj[keys[i]]) } } /** * 同上,遍历数组 */ observeArray (items) { for (let i = 0, l = items.length; i < l; i++) { observe(items[i]) } } }
一样的在 defineReactive
咱们须要处理数组添加依赖的逻辑prototype
export function defineReactive(object, key, value) { let dep = new Dep() let childOb = observe(value) Object.defineProperty(object, key, { configurable: true, enumerable: true, get: function () { if (Dep.target) { dep.addSub(Dep.target) Dep.target.addDep(dep) // 处理数组的依赖 if(Array.isArray(value)){ childOb.dep.addSub(Dep.target) Dep.target.addDep(childOb.dep) } } return value }, set: function (newValue) { if (newValue !== value) { value = newValue dep.notify() } } }) }
ok 咱们如今完成了依赖的添加,剩下的咱们要实现依赖的触发。
处理方法:在数组对象调用特定方法时,首先找到的应该是咱们本身写的方法,而这个方法中调用了原始方法,并触发依赖。
咱们先来包装一下方法,获得一些同名方法:
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] let mutator = function (...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) break } // 新添加的对象须要添加监听 if (inserted) ob.observeArray(inserted) // 触发 notify 方法 ob.dep.notify() return result } Object.defineProperty(arrayMethods, method, { value: mutator, enumerable: false, writable: true, configurable: true }) })
ok 咱们如今获得了一些列同名的方法,我只要确保在调用时,先调用到咱们的方法便可。
有两种方式能够实现:
__proto__
,这样寻找原型链时,就会先找到咱们的方法具体到代码中的实现:
export class Observer { constructor(value) { this.value = value if (Array.isArray(value)) { this.dep = new Dep() const augment = ('__proto__' in {}) ? protoAugment : copyAugment // 覆盖数组中一些改变了原数组的方法,使得方法得以监听 augment(value, arrayMethods, arrayKeys) this.observeArray(value) } else { this.walk(value) } ... } ... } /** * 若是能使用 __proto__ 则将数组的处理方法进行替换 */ function protoAugment (target, src, keys) { target.__proto__ = src } /** * 若是不能使用 __proto__ 则直接将该方法定义在当前对象下 */ function copyAugment (target, src, keys) { for (let i = 0, l = keys.length; i < l; i++) { const key = keys[i] Object.defineProperty(target, key, { value: src[key], enumerable: false, writable: true, configurable: true }) } }
测试一下:
let object = { arrayTest: [1, 2, 3, 4, 5] } observe(object) let watcher = new Watcher(object, function () { return this.arrayTest.reduce((sum, num) => sum + num) }, function (newValue, oldValue) { console.log(`监听函数,数组内全部元素 = ${newValue}`) }) object.arrayTest.push(10) // 监听函数,数组内全部元素 = 25
到如今为止,咱们成功的在数组调用方法的时候,添加并触发了依赖。
首先先说明,数组下的索引是和对象下的键有一样的表现,也就是能够用 defineReactive
来处理索引值,可是数组是用来存放一系列的值,咱们并不能一开始就肯定数组的长度,而且极有可能刚开始数组长度为 0
,以后数组中的索引对应的内容也会不断的变化,因此为索引调用 defineReactive
是不切实际的。
可是相似于 arr[1] = 'something'
这样的赋值在数组中也是常见的操做,在 Vue
中实现了 $set
具体的细节这里不谈,这里实现了另外一种方法,咱们仅仅须要在数组对象下添加一个方法便可:
arrayMethods.$apply = function () { this.__ob__.observeArray(this) this.__ob__.dep.notify() }
测试一下:
object.arrayTest[1] = 10 object.arrayTest.$apply() // 监听函数,数组内全部元素 = 33
到目前为了,一个完整的数据监听的模型也就完成了,咱们可使用 observe
方法来获得一个可监听结构,而后用 Watcher
添加依赖。
在设置值的时候就能成功触发依赖。