Vue 响应式原理

本文详细介绍了Vue响应式的基本原理,从ES5的Object.defineProperty实现数据劫持,到对象和数组的响应式转换,以及如何处理数组变异操作的问题。通过函数拦截扩展数组功能,确保变更能触发视图更新。内容涵盖了Vue数据绑定的核心机制。
摘要由CSDN通过智能技术生成
  • 1.响应式原理

  • 我们在使用 Vue 的时候,赋值属性获得属性都是i直接使用的 Vue 的实例

  • 我们在设计属性值的时候,页面的数据更新

  • ES5 语法

  •   let o = {}
    
      //等价于 let o = {}
      let value;
      Object.defineProperty(o, 'age', {
       //属性访问器 不能和 writable 和 value 共存
       // writable: true, //可修改的
       configurable: true, //可配置的
       enumerable: true, //可枚举的,可被 for in 遍历
       // value: 12, //属性值
       //属性访问器
       get() { //访问时 调用
        console.log('访问时 调用');
        return value
       },
       set(newValue) { //赋值时 调用
        console.log(newValue); //就是 get return 的值
        value = newValue; //这样 每次在访问时,就会显示 修改后的 value 值了
       }
      })

    2.将对象转换为响应式的

  • 但是 同时定义多个全局变量不好,vue 是这样实现的

    let o = {
       name: '张三',
       age: 15,
       gender: '女'
      }
    
      function defineReactive(target, key, value, enumerable) {
       Object.defineProperty(target, key, {
        enumerable: !!enumerable,
        get() {
         console.log(`读取 o 的 ${key}属性`);
         return value;
        },
        set(newVal) {
         console.log(`设置 o 的 ${key}属性为${newVal}`);
         value = newVal
        }
       })
      }
      Object.keys(o).forEach((item, i) => {
       defineReactive(o, item, o[item], true)
      })

     

    3.对象响应式化

  • 但是,如果 数据 中有引用类型呢?

  • let data = {
       name: '张三',
       age: 18,
       course: [{
         name: '语文'
        },
        {
         name: '数学'
        },
        {
         name: '英语'
        }
       ]
      };
      //简化后的版本
      function defineReactive(target, key, value, enumerable) {
       // if (typeof value === 'object' && value != null && !Array.isArray(value)) {
       //  //非数组 的引用类型
       //  reactify(value)
       // }
       Object.defineProperty(target, key, {
        configurable: true,
        enumerable: !!enumerable,
        get() {
         console.log(`读取 data 的 ${key}属性`);
    
         return value;
        },
        set(newVal) {
         value = newVal;
        }
       });
      }
    
      //将 data 响应式化
      function reactify(obj) {
       //因为 keys 只会遍历 可迭代类型 的数据,其他 不可迭代的 都会返回 空数组,而如果是空数组后面的forEach 是不会执行的
       //如果是字符串,使用 keys 也会返回数组,但是一旦进行递归,forEach 也不会执行
       let keys = Object.keys(obj)
       for (let i = 0; i < keys.length; i++) {
        let key = keys[i];
        let value = obj[key];
        //判断
        if (Array.isArray(value)) {
         //数组
         for (let j = 0; j < value.length; j++) {
          reactify(value[j])
    
         }
        } else {
         //判断方法一
         if (typeof value === 'object' && value != null && !Array.isArray(value)) {
          //非数组 的引用类型
          reactify(value)
         }
         //判断方法二
         // if (Object.prototype.toString.call(value) === '[object Object]') {
         //  //非数组 的引用类型
         //  reactify(value)
         // }
         //这俩方法 可以放在这里,也可以 放在defineReactive 中
         defineReactive(obj, key, value, true);
    
        }
       }
      }
      reactify(data);

    4.扩展函数功能

  • 问题:如果 数据 进行 push pop 等变异数组,是不会 defineProperty是不会响应的

    • 要怎么做呢?

    • 1.在改变数组的时候,要发出通知

      • 在Vue2中的缺陷,数组发生变化,设置 length 没发通知(vue3 中使用 proxy(ES6语法)解决了这个问题)

    • 2.加入的元素应该变成响应式的

    • 技巧:如果一个函数已经定义了,但是我们需要扩展其功能,我们一般的处理办法:

      • 1.使用一个临时的函数名存储函数

        2.重新定义原来的函数

        3.定义扩展的功能

        4.调用临时的那个函数

          //这个就是在函数的原有上增加额外的功能:函数的拦截
        
        function func() {
           console.log('原始的功能');
          }
          //1.使用一个临时的函数名存储函数
          let tempFn = func;
        
          //2.重新定义原来的函数
          func = function () {
           //4.调用临时的那个函数
           tempFn(); //'原始的功能'
        
           //3.定义扩展的功能
           console.log('新的扩展的功能');
          }
        
          func() //1.打印出 '原始的功能'
          //2.打印出 '新的扩展的功能'

         

        5. 数组的拦截

      • 思路:谁要响应化,就拦截谁

        继承关系: arr -> Array.prototype -> Object.prototype ->...

        改写为: arr -> 改写的方法 -> Array.prototype -> Object.prototype ->...

      •   const ARRAY_METHODS = [
           'push',
           // 'pop',
           'shift',
           'unshift',
           'sort',
           'reverse',
           'splice'
          ];
        
          let arr = []
        
          //创建一个 数组原型
          let array_methods = Object.create(Array.prototype)
        
          ARRAY_METHODS.forEach(method => {
           array_methods[method] = function () {
            console.log('调用的是拦截的' + method + '方法');
            //调用原来的方法
            //将 数据原型中的 this 指向将来 要调用的对象
            // arguments 就是这些 数组 方法 中 要使用的 参数
            //因为 push  pop  等 方法返回。所以接收到一个变量 最后 return 出去
            let res = Array.prototype[method].apply(this, arguments);
            return res;
           }
          })
        
          arr.__proto__ = array_methods;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值