03-vue源码学习-响应式原理(对象和数组)

一、响应式原理

什么是响应式原理:

意思就是在改变数据的时候,视图会跟着更新。

实现响应式原理的核心:Object.defineProperty

Object.defineProperty( 对象, '设置什么属性名', {
  writeable
  configurable
  enumerable:  控制属性是否可枚举, 是不是可以被 for-in 取出来
  set() {}  赋值触发
  get() {}  取值触发
} )

二、对象响应式化

    <script>
        let o = {
            name: 'jim',
            age: 19,
            gender: '男'
        }

        // 简化后的版本
        function defineReactive(target, key, value, enumerable) {
            // 函数内部就是一个局部作用域, 这个 value 就只在函数内使用的变量 ( 闭包 )
            Object.defineProperty(target, key, {
                configurable: true,
                enumerable: !!enumerable,
                get() {
                    console.log(`读取 o 的 ${key} 属性`); // 额外
                    return value
                },
                set(newVal) {
                    console.log(`设置 o 的 ${key} 属性为: ${newVal}`); // 额外
                    value = newVal
                }
            })
        }
        let keys = Object.keys(o)
        keys.forEach(key => {
            defineReactive(o, key, o[key], true)
        })

    </script>

三、对象数组响应式化

    <script>
        let data = {
            name: '张三',
            age: 19,
            course: [
                { name: '语文' },
                { name: '数学' },
                { name: '英语' },
            ]
        }
        // 简化后的数据响应函数
        function defineReactive(target, key, value, enumerable) {
            // 函数内部就是一个局部作用域, 这个 value 就只在函数内使用的变量 ( 闭包 )
            if (typeof value === 'object' && value != null && !Array.isArray(value)) {
                // 是非数组的引用类型
                reactify(value); // 递归
            }
            Object.defineProperty(target, key, {
                configurable: true,
                enumerable: !!enumerable,
                get() {
                    console.log(`读取 o 的 ${key} 属性`); // 额外
                    return value
                },
                set(newVal) {
                    console.log(`设置 o 的 ${key} 属性为: ${newVal}`); // 额外
                    value = newVal
                }
            })
        }

        // 将对象 o 响应式化
        function reactify(o) {
            keys = Object.keys(o)
            for (let i = 0; i < keys.length; i++) {
                let key = keys[i]
                let value = o[key]
                // 判断这个属性是不是引用类型, 判断是不是数组
                // 如果引用类型就需要递归, 如果不是就不用递归
                //  如果不是引用类型, 需要使用 defineReactive 将其变成响应式的
                //  如果是引用类型, 还是需要调用 defineReactive 将其变成响应式的
                // 如果是数组呢? 就需要循数组, 然后将数组里面的元素进行响应式化
                if (Array.isArray(value)) {
                    for (let j = 0; j < value.length; j++) {
                        reactify(value[j])
                    }
                } else {
                    // 对象或值类型
                    defineReactive(o, key, value, true);
                }
            }
        }
        reactify(data)

    </script>

四、拦截数组方法

在改变数组时,加入的元素也要响应式化,所以要对数组方法进行重写,采用的方式是拦截数组的方法。

拦截数组方法:

<script>
        let ARRAY_METHOD = [
            'push',
            'pop',
            'shift',
            'unshift',
            'reverse',
            'sort',
            'splice',
        ];

        // 思路, 原型式继承: 修改原型链的结构
        let arr = [];
        // 继承关系: arr -> Array.prototype -> Object.prototype -> ...
        // 继承关系: arr -> 改写的方法 -> Array.prototype -> Object.prototype -> ...
        let array_methods = Object.create(Array.prototype)
        ARRAY_METHOD.forEach(method => {
            array_methods[method] = function () {
                // 调用原来的方法
                console.log('调用的是拦截的 ' + method + ' 方法');
                // 将数据进行响应式化
                let res = Array.prototype[method].apply(this, arguments)
                return res
            }
        })

        arr.__proto__ = array_methods
    </script>

五、完整的对象和数组响应式化代码

  <script>


    let data = {
      name: '张三',
      age: 19,
      course: [
        { name: '语文' },
        { name: '数学' },
        { name: '英语' },
      ]
    }; // 除了递归还可以使用队列 ( 深度优先转换为广度优先 )



    let ARRAY_METHOD = [
      'push',
      'pop',
      'shift',
      'unshift',
      'reverse',
      'sort',
      'splice',
    ];
    let array_methods = Object.create(Array.prototype);
    ARRAY_METHOD.forEach(method => {
      array_methods[method] = function () {
        // 调用原来的方法
        console.log('调用的是拦截的 ' + method + ' 方法');

        // 将数据进行响应式化
        for (let i = 0; i < arguments.length; i++) {
          reactify(arguments[i]);
        }

        let res = Array.prototype[method].apply(this, arguments);
        // Array.prototype[ method ].call( this, ...arguments ); // 类比
        return res;
      }
    });

    // 简化后的版本
    function defineReactive(target, key, value, enumerable) {
      // 函数内部就是一个局部作用域, 这个 value 就只在函数内使用的变量 ( 闭包 )

      if (typeof value === 'object' && value != null && !Array.isArray(value)) {
        // 是非数组的引用类型
        reactify(value); // 递归
      }

      Object.defineProperty(target, key, {
        configurable: true,
        enumerable: !!enumerable,

        get() {
          console.log(`读取 ${key} 属性`); // 额外
          return value;
        },
        set(newVal) {
          console.log(`设置 ${key} 属性为: ${newVal}`); // 额外
          value = newVal;
        }
      });
    }
    // 将对象 o 响应式化
    function reactify(o) {
      let keys = Object.keys(o);

      for (let i = 0; i < keys.length; i++) {
        let key = keys[i]; // 属性名
        let value = o[key];
        if (Array.isArray(value)) {
          // 数组
          value.__proto__ = array_methods; // 数组就响应式了
          for (let j = 0; j < value.length; j++) {
            reactify(value[j]); // 递归
          }
        } else {
          // 对象或值类型
          defineReactive(o, key, value, true);
        }
      }
    }


    reactify(data);


  </script>
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值