Vue学习笔记:理解Vue2如何解决数组和对象的响应式问题

目录

  • 前言
  • 基本的代码
  • 对复杂对象的处理
  • 对Array的处理
  • 完整代码
  • 仍存在的问题
  • 参考的资料

前言

Vue2是通过用Object…defineProperty来设置数据的getter和setter实现对数据和以及视图改变的监听的。对于数组和对象这种引用类型来说,getter和setter无法检测到它们内部的变化。那么Vue2是则么来解决这个问题的呢?
通过一个简单的例子来理解Vue2中是如何解决数组和对象的响应式问题。

基本的代码

<html>

<head>

</head>

<body>
  <script>
    //1. 定义一个data对象来模拟组件中的数据
    var data = {
      name: 'xwd',
      sex: 1,
      dog: {
        name: 'peter',
        age: 5
      },
      hobby: [
        "pingpang""basktetball"
      ]}
	//2. 对Data做 reactive化
    Observer(data)
	
    function Observer(data) {
		// 模拟组件初始化对Data reactive化
      if (typeof data != "object" || data == null) {
        return data
      }
      for (let item in data) {
        //将数据响应式化
        defineReactive(data, item, data[item])
      }
      return data
    }
	
    function defineReactive(target, key, value) {
      Object.defineProperty(target, key, {
        enumerable: false,
        configurable: false,
        get() {
		//用打印控制台模拟视图发生渲染
          console.log("视图渲染使用了数据")
          return value;
        },
        set(newValue) {
          if (newValue !== value) {
	
            value = newValue;
		    //用打印控制台模拟数据变更视图更新
            console.log("更新视图")
          }

        }
      })

    }
	
  </script>
</body>

对复杂对象的处理

对复杂对象对象属性的变更主要有以下几种情况:

  1. 复杂对象中对象属性的属性的变化

    Vue2的处理方式是在响应化的时候深度遍历Data对象的属性直到对所有的基本类型的属性都添加上getter和setter。
    Vue学习笔记:理解Vue2如何解决数组和对象的响应式问题_第1张图片

  function defineReactive(target,key,value) {
      Observer(value)
      Object.defineProperty(target,key,{
        enumerable: false,
        configurable: false,
        get() {
          console.log("视图渲染使用了数据")
          return value;
        },
        set(newValue) {

          if (newValue !== value) {
            value = newValue;
            console.log("更新视图")
          }

        }
      })

    }
  1. 给数据的属性set新对象
    Vue学习笔记:理解Vue2如何解决数组和对象的响应式问题_第2张图片
    在set新对象的时候会显示更新视图,但是新添加对象的value并不会加上响应式。
    Vue2的处理方式是在set的时候对该属性重新进行defineReactive操作给属性加上getter和setter。
set(newValue) {
  Observer(value)
  if (newValue !== value) {
    value = newValue;
    console.log("更新视图")
  }
}

Note: 因为Vue2对数据响应式的添加是在一开始初始化以及set属性的时候,所以在使用过程中当发生data的属性的增加或者删除。Vue不能添加响应式。如果要对运行过程中添加的属性做响应式,必须使用Vue.delete方法或者Vue.Set。

对Array的处理

数组内部的变化,包括使用我们常用的数组函数,push,pop等。都不能被setter函数检测到,只有当整个数组被换掉才会被检测到。
Vue学习笔记:理解Vue2如何解决数组和对象的响应式问题_第3张图片
Vue2为了解决这个问题采用的方式是提供一组新的数组变异函数。换掉Array的原型用新的变异函数,在自定义的变异函数里做更新视图的操作。

  1. 以原来的Array原型为模板,创建新模板对象
const oldArrayProto = Array.prototype;
    const newArrProto = Object.create(oldArrayProto);
  1. 重写新模板的push pop 等数组变异函数
    ['push''pop'].forEach(methodName => {
      newArrProto[methodName] = function () {

        console.log("更新视图")
        oldArrayProto[methodName].call(this, ...arguments)
      }
});
  1. 在扫描中替换data中Array对象的原型
  if (Array.isArray(data)) {
    data.__proto__ = newArrProto
  }

Note: Vue 不能检测以下数组的变动:

  1. 当你利用索引直接设置一个数组项时,例如:vm.items[indexOfItem] = newValue
  2. 当你修改数组的长度时,例如:vm.items.length = newLength

解决方法

完整代码

<html>

<head>

head>

<body>
  123
  <script>
    //1. 定义一个data对象来模拟组件中的数据
    var data = {
      name: 'xwd',
      sex: 1,
      dog: {
        name: 'peter'
        ,
        type: 'dog'
      },
      hobby: [
        "pingpang", "basktetball"
      ],

    }
    const oldArrayProto = Array.prototype;
    const newArrProto = Object.create(oldArrayProto);

    ['push', 'pop'].forEach(methodName => {
      newArrProto[methodName] = function () {

        console.log("更新视图")
        oldArrayProto[methodName].call(this, ...arguments)
      }
    });

    Observer(data)


    function Observer(data) {
      if (typeof data != "object" || data == null) {
        return data
      }
      if (Array.isArray(data)) {
        data.__proto__ = newArrProto
      }
      for (let item in data) {
        //将数据响应式化
        defineReactive(data, item, data[item])
      }
      return data



    }
    function defineReactive(target, key, value) {
      Observer(value)
      Object.defineProperty(target, key, {
        enumerable: false,
        configurable: false,
        get() {
          console.log("视图渲染使用了数据")
          return value;
        },
        set(newValue) {
          Observer(value)
          if (newValue !== value) {
            value = newValue;
            console.log("更新视图")
          }

        }
      })

    }


  script>
body>



html>

仍存在的问题

用 Object.defineProperty这种方法去监听数据和视图的改变,当遇到复杂对象的时候需要对所有的对象进行深度遍历来给属性设置上getter和setter函数,这会导致首屏加载速度很慢。针对这个问题 Vue3 将响应式的实现由
Object.defineProperty换成了Proxy。实现在数据要用的时候再添加响应式提高了首屏加载速度。

参考的资料

1. (完整版)快速掌握Vue2响应式原理【Vue】_哔哩哔哩_bilibili
2. 深入响应式原理 — Vue.js (vuejs.org)

你可能感兴趣的:(vue,前端,vue.js,前端)