5.谈谈对响应式数据的理解?

63 阅读2分钟

vue2

对象内部的defineReactive方法,使用Object.defineProperty将属性进行劫持重写getter和setter

const obj = {
  name:'lihk',
  age:25,
  n:{
    num:2222
  }
}
function observer(data){
  if(typeof data !=='object'||typeof data ===null){
    return data
  }
  for (const key in data) {
    defineReactive(data,key,data[key])
  }
}
function defineReactive(target,key,value){
  observer(value)//递归深度将其属性进行数据劫持
  Object.defineProperty(target,key,{
    // 重写get和set
    get(){
      return value
    },
    set(newValue){
      if(value!==newValue){
        value=newValue
        // 将新值继续监听
        observer(newValue)
      }
    }
  })
}
observer(obj)
obj.c=111
console.log('obj', obj)

控制台打印其属性

image.png

缺点

重写getter和setter,性能差
新增和删除属性时无法监控变化,需要$set、$delete来更新属性进行重新监控
对ES6的Map、set这些数据结构不支持劫持

数组不采用defineProperty,对所有的索引进行劫持会造成性能浪费,则是通过重写数组七个方法来实现的 ('push', 'pop', 'shift', 'unshift', 'reverse', 'sort', 'splice')

const obj = {
        name: 'lihk',
        age: 25,
        n: {
          num: 2222
        },
        m: [1, 2, 3]
      };
      //创建一个不影响原数组的对象来创建__proto__
      let newArr = Object.create(Array.prototype);
      let oldArr = Array.prototype;
      ['push', 'pop', 'shift', 'unshift', 'reverse', 'sort', 'splice'].forEach(
        method => {
          newArr[method] = function (...args) {
            console.log('调用了', method);
            // 将七个方法依次追加到数组里面,实现重写
            oldArr[method].call(this, ...args);
          };
        }
      );

      function observer(data) {
        if (typeof data !== 'object' || typeof data === null) {
          return data;
        }
        if (Array.isArray(data)) {
          // 判断是不是数组
          data.__proto__ = newArr;
        } else {
          // 是对象属性
          for (const key in data) {
            defineReactive(data, key, data[key]);
          }
        }
      }
      function defineReactive(target, key, value) {
        observer(value); //递归深度将其属性进行数据劫持
        Object.defineProperty(target, key, {
          // 重写get和set
          get() {
            return value;
          },
          set(newValue) {
            if (value !== newValue) {
              value = newValue;
              // 将新值继续监听
              observer(newValue);
            }
          }
        });
      }
      observer(obj);
      obj.m.push(2, 3, 4);
      console.log('obj', obj.m);

将七个方法追加到数组的prototype上面 image.png

缺点

数组的索引和长度变化无法检测到

vue3

使用proxy进行getter和setter代理,递归是惰性的,只有触发get才会将其变成proxy,vue2需要全部添加get和set

const obj = { name: 'lihk', age: 25, n: [1, 2, 3, 4] }
      function reactive(target) {
        return new Proxy(target, handler)
      }
      let handler = {
        get(target, key) {
          // 收集effect
          let temp = target[key]
          if (typeof temp === 'object') {
            // 递归是惰性的,只有触发get才会将其变成proxy,vue2需要全部添加get和set
            return new Proxy(temp, handler)
          }
          return temp
        },
        set(target, key, value) {
          // 触发effect
          target[key] = value
          console.log('key', key)
        },
      }
      const proxy = reactive(obj)
      console.log('proxy', proxy,proxy.n)

image.png