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一个对象, 那这个对象的属性也需要被劫持

上述为本章需要实现的目标

  1. 新建文件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. 让数组中的对象也变成响应式
    
  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))
        }
    }
    
    
  3. 重写数组方法 在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
        }
    })
    
    
    
  4. 浏览器上打开2.array.html 可以显示 使用的是数组的哪个方法, 数组里面的指向被劫持, 新增的对象也被劫持, ok

posted @ 2022-06-23 01:04  littlelittleship  阅读(132)  评论(0编辑  收藏  举报