3. 数组的响应式原理
数组的响应式原理
一般使用数组很少使用 arr[1] = 100, arr.length = 10 这两种方式修改数组, vue2同样也不支持
vue2中实现数组响应式的方法是重写能改变数组的7个方法
特殊情况: 形如: arr: [1,2,3, {num: 100}], 这种数组里面有嵌套对象的, 也要能劫持对象的属性, 添加get和set
特殊情况2: arr: [1,2,3], arr.push({num: 20}), 在能改变数组的方法中, 如push一个对象, 那这个对象的属性也需要被劫持
上述为本章需要实现的目标
-
新建文件dist/2.array.html 文件, 内容如下
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>array</title> </head> <body> <script src="vue.js"></script> <script> // 新建一个vue实例 const vm = new Vue({ data() { return { arr: [1,2,3, {num: 100}] } } }) // 一般使用数组很少使用 arr[1] = 100, arr.length = 10 这两种方式修改数组, vue2同样也不支持 // vue2中实现数组响应式的方法是重写能改变数组的7个方法 // 特殊情况: 形如: arr: [1,2,3, {num: 100}], 这种数组里面有嵌套对象的, 也要能劫持对象的属性, 添加get和set // 特殊情况2: arr: [1,2,3], arr.push({num: 20}), 在能改变数组的方法中, 如push一个对象, 那这个对象的属性也需要被劫持 vm.arr.unshift({age: 3}) console.log('vm.data', vm) </script> </body> </html> // 需要实现的目标: // 1. 重写数组方法 // 2. 让数组中的对象也变成响应式
-
切入点, 接上一章, 在Observer类中值考虑了对象, 以及对象嵌套的情况,
现在来处理数组, 修改Observer类如下
// 新增一个if判断, // 同目录新建array.js文件, 实现数组重写并引入 // 新增observeArray方法, 对数组中的对象进行处理 // 在data中添加一个不可枚举的属性 __ob__, 用来标识是否已经被劫持, 同时给observe方法里面新增一个判断逻辑, 同时给array.js里面提供observeArray方法 class Observer { constructor(data) { // 2) 在data上增加一个属性__ob__, 指向this,便于在array.js里面使用Observer里面的observeArray方法 // 同样这个属性可以作为一个标识, 表明属性是否有被劫持过, 可在observe方法中新增一个判断 // 注意这个属性不可枚举, 不然会死循环 Object.defineProperty(data, '__ob__', { value: this, enumerable: false }) // 1) 针对对象和数组做区分 if(Array.isArray(data)){ // 数组需要实现两个内容: // 1. 数组方法重写, 新建文件实现 array.js // 2. 遍历数组, 劫持其中的对象, 并称响应式 data.__proto__ = newArrayProto this.observeArray(data) } else { // 对对象的属性劫持放在walk方法中 this.walk(data) } } // 遍历对象, 给每一个属性添加响应式, defineReactive不在class中, 因为其他地方也可能用到, 写了外面 walk(data) { Object.keys(data).forEach(key => defineReactive(data, key, data[key])) } // 循环数组, 找出其中的对象(这异步已经封装在observe方法里面, 不用做), observe每一项 observeArray(data) { data.forEach(item => observe(item)) } }
-
重写数组方法 在array.js文件中
// 重写数组方法 // 不能干掉旧的方法 // 重新定义方法, 就是在执行push 方法的时候, 调用旧的push方法,注意this的指向问题,以及返回值 然后在添加一些自己的内容 // 对新增的内容(肯定是数组)进行observeArray ??? 注意进行observeArray是哪里来的 // 先copy旧的数组 let oldArrayProto = Array.prototype // 定义并导出新的原型 export let newArrayProto = Object.create(oldArrayProto) // 能改变数组的7种方法 let methods = ['push', 'pop', 'shift', 'unshift', 'reverse', 'sort', 'splice'] methods.forEach(method => { newArrayProto[method] = function(...args) { const result = oldArrayProto[method].call(this, ...args) // 需要对新增的内容做处理, 新增的方法有 : push, unshift, 和 splice let inserted // 从实例上获取__ob__属性 let ob = this.__ob__ switch(method) { case 'push': case 'unshift': inserted = args break case 'splice': inserted = args.slice(2) default: break } // 方法里面的this, 是谁调用push, this指向谁 // 这里的this其实就是 Observer 里面 constructor里面的参数data, 在data里面添加一个__ob__属性, 指向类的实例, 就能从this上面获取observeArray了 if(inserted) { ob.observeArray(inserted) } console.log('mrthios:', method) return result } })
-
浏览器上打开2.array.html 可以显示 使用的是数组的哪个方法, 数组里面的指向被劫持, 新增的对象也被劫持, ok