Which array methods in vue can be responsive

Changes in attributes](#%E5%A4%8D%E6%9D%82%E5%AF%B9%E8%B1%A1%E4%B8%AD%E5%AF%B9%E8%B1%A1% E5%B1%9E%E6%80%A7%E7%9A%84%E5%B1%9E%E6%80%A7%E7%9A%84%E5%8F%98%E5%8C%96)
    - [给数据的属性set新对象](#%E7%BB%99%E6%95%B0%E6%8D%AE%E7%9A%84%E5%B1%9E%E6%80%A7set%E6%96%B0%E5%AF%B9%E8%B1%A1)
- [对Array的处理](#%E5%AF%B9array%E7%9A%84%E5%A4%84%E7%90%86)
    - [以原来的Array原型为模板,创建新模板对象](#%E4%BB%A5%E5%8E%9F%E6%9D%A5%E7%9A%84array%E5%8E%9F%E5%9E%8B%E4%B8%BA%E6%A8%A1%E6%9D%BF%E5%88%9B%E5%BB%BA%E6%96%B0%E6%A8%A1%E6%9D%BF%E5%AF%B9%E8%B1%A1)
    - [重写新模板的push pop 等数组变异函数](#%E9%87%8D%E5%86%99%E6%96%B0%E6%A8%A1%E6%9D%BF%E7%9A%84push-pop-%E7%AD%89%E6%95%B0%E7%BB%84%E5%8F%98%E5%BC%82%E5%87%BD%E6%95%B0)

In Vue2, why modifying elements directly through the index of the array does not trigger a view update?

In Vue2, if you modify the element directly through the index of the array, the view update will not be triggered . The reason is that Vue2 uses "hijacking" technology to achieve data responsiveness.

The responsiveness of objects in Vue2 is achieved through the Object.defineProperty() method. This method can convert object properties into getter and setter forms, thereby achieving "hijacking" of properties.

But for 数组来说,Array.prototype 中的方法并不会触发这样的 getter 和 setter,因此 Vue 无法监听到这些变化,也就不能及时地更新视图. Vue.js cannot trigger an update of the view when the elements of the array change because the properties of the array (such as length) are read-only.

In order to solve this problem, Vue2 provides some mutation methods for arrays (such as push(), pop(), splice(), etc.), which are responsive. When using these methods to operate an array, Vue2 will detect changes in the array and update the view in time to ensure synchronization of the view and state.

Modifying elements directly through the index cannot trigger this change, so responsive updates cannot be achieved.

In order to solve this problem, the following two methods can be used to implement responsive updates to array elements:

  1. Instructions $set()_

$set()Method can add a new element to the array and ensure that this new element is also responsive. For example:

this.$set(this.items, index, newValue);
  1. splice()Modify array elements through method

splice()Method can delete elements from the array and insert new elements at the location of the deleted element. Because this method changes the original array, Vue2 can detect changes in the array and update the related views responsively. For example:

this.items.splice(index, 1, newValue);

Why doesn't vue2 listen to the array directly?

The reason why Vue2 does not listen to arrays directly is based on performance and consistency considerations.

In Vue2, Object.defineProperty is used to hijack the getters and setters of object properties, and implement tracking and notification of data changes in the setters. This method is very efficient when processing object properties, and can accurately track data changes and perform responsive updates.

However, for arrays, it is a special object type.数组的操作方法(例如 push、pop、splice 等)会改变数组的内容,但不会触发数组本身的 setter。因此,Vue2 无法直接侦听数组的变化。

To solve this problem, Vue2 provides a set of special array methods (such as set, set,se t , splice, $push, etc.), by modifying the array through these methods, Vue2 can detect changes in the array and perform responsive updates. These special array methods are equivalent to a proxy or encapsulation of native array methods.

The design of not listening directly to an array is a performance trade-off, as directly listening to each element of the array may result in performance degradation. By wrapping it with a special array method, updates are only triggered when needed, which can better control performance.

Although Vue2 cannot directly monitor arrays, you can still monitor arrays by manually calling $set or using deep monitoring. In addition, Vue3 implements array monitoring capabilities through Proxy objects, which is more flexible and efficient.

Vue2 provides some mutation methods for arrays

  • 1. Delete the last element of the array: pop()
  • 2. Add elements to the end of the array: push(). Note: You can add multiple elements, such as letters.push('a', 'b')
  • 3. Delete the first element of the array: shift()
  • 4. Add elements to the front of the array: unshift(). Note: Multiple elements can be added, such as letters.unshift('a', 'b')
  • 5. Delete (or insert or replace) array elements: splice()
    . For example, to delete elements: splice(2) starts from the second position and deletes all subsequent elements;
    for example, to delete elements: splice(2,3) starts from the second position. Delete 3 elements;
    for example, insert elements: splice(2,0,'j','k') insert elements 'j','k' starting from the second position;
    for example, replace elements: splice(2,3,' m','n','p') is to replace 3 elements starting from the second position with 'm','n','p';
  • 6. Array sorting: sort();
  • 7. Reverse the contents of the array: reverse();

Rewriting array method source code analysis

Implementation idea: Generally speaking, the interceptor is used to cover the methods on Array.prototype, and the data is responsive in addition to executing the methods on the prototype .

  • Store the prototype of the array in the object arrayMethods
  • Find the 7 methods on Array that can change the array itself push, pop, shift, unshift, splice, sort, reverse
  • Use these 7 methods for responsive processing
  • After the processing is completed, use them to overwrite the corresponding methods in arrayMethods.
  • Point the __proto__ of the array arr that needs to be responsively processed to arrayMethods. If the browser does not support access to __proto__, directly add the 7 methods after responsive processing to the array arr.
  • If you want to make an array fully responsive, you need to traverse the array, use this method to process the arrays in the array responsively, and use the walk method to process the objects responsively.

For more details, please search " " on WeChat前端爱好者 and click me to view .

Define interceptor
// 获取Array的原型
const arrayProto = Array.prototype;
// 创建一个新对象,该新对象的原型指向Array的原型。
export const arrayMethods = Object.create(arrayProto);
[
	'push',
	'pop',
	'shift',
	'unshift',
	'splice',
	'sort',
	'reverse'
]
.forEach(mentod => {
    
    
	 // 缓存原始方法
	const original = arrayProto[method];
	// 对新原型对象上的方法,做数据绑定
	Object.defineProperty(arrayMethods, method, {
    
    
		value: function mutator(...args) {
    
    
			// 返回原始方法
			return original.apply(this, args); 
		},
		enumerable: false,
		writable: true,
		configurable: true
	})
})
Attach the interceptor to the array
import {
    
     arrayMethods } from './array' // 处理好的Array原型对象
// __proto__是否可用
const hasProto = '__proto__' in {
    
    };
// 所有属性名,不论是否可枚举(与Object.keys的区别)
const arrayKeys = Object.getOwnPropertyNames(arrayMethods);

export class Observe {
    
    
	// 将value转为响应式
	constructor (value) {
    
    
		this.value = value;

		if (Array.isArray(value)) {
    
    
			const augment = hasProto ? protoAugment : copyAugment;
			augment(value, arrayMethods, arrayKeys);
		} else {
    
    
			this.walk(value); // Object的响应式处理,在其他文章中
		}
	}
}

/**
* __proto__可用的处理方式
* 将target对象的原型对象替换为src
*/
function protoAugment(target, src, keys) {
    
    
	target.__proto__ = src;
}

/**
* __proto__不可用的处理方式
* 将src上面的所有属性都copy到target
*/
function copyAugment (target, src, keys) {
    
    
	for (let i = 0, len = keys.length; i < len; i ++) {
    
    
		const key = keys[i];
		def(target, key, src[key]);
	}
}

// Object.defineProperty()的封装
function def (obj, key, val, enumerable) {
    
    
	Object.defineProperty(obj, key, {
    
    
		value: val,
		enumerable: !!enumerable,
		writable: true,
		configurable: true
	})
}
Collect dependencies

Collect dependencies:

function defineReactive(data, key, val) {
    
    
    let childOb = observe(val);
    let dep = new Dep(); // 存储依赖
    Object.defineProperty(data, key, {
    
    
        enumerable: true,
        configurable: true,
        get: function () {
    
    
            dep.depend();

            if (childOb) childOb.dep.depend(); // 收集
            return val;
        },
        set: function (newVal) {
    
    
            if (val === newVal) return;
            dep.notify();
            val = newVal;
        }
    })
}

// 返回val的响应式对象
function observe(val, asRootData) {
    
    
    if (!isObject(value)) return;
    let ob;
    // 避免重复侦测
    if (hasOwn(value, '__ob__') && value.__ob__ instanceof observer) {
    
    
        ob = value.__ob__;
    } else {
    
    
        ob = new Observe(value)
    }
    return ob;
}

Extension: Understand how Vue2 solves the responsiveness problem of arrays and objects

Vue2 implements monitoring of data and view changes by using Object...defineProperty to set the getter and setter of the data.

For reference types such as arrays and objects, getters and setters cannot detect their internal changes.

So how does Vue2 solve this problem?

Use a simple example to understand how to solve the responsiveness problem of arrays and objects in Vue2.

<html>

<head>

</head>

<body>
  <script>
    //1. 定义一个data对象来模拟组件中的数据
    var data = {
    
    
      name: 'xwd'sex: 1dog: {
    
    
        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: falseconfigurable: falseget() {
    
    
		//用打印控制台模拟视图发生渲染
          console.log("视图渲染使用了数据")
          return value;
        }set(newValue) {
    
    
          if (newValue !== value) {
    
    
	
            value = newValue;
		    //用打印控制台模拟数据变更视图更新
            console.log("更新视图")
          }

        }
      })

    }
	
  </script>
</body>

Handling complex objects

Changes to the properties of complex objects mainly include the following situations:

Changes in properties of object properties in complex objects

候深度遍历Data对象的属性The way Vue2 handles it is to add getters and setters to all basic types of properties during responsiveness .

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

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

        }
      })

    }
Set a new object to the data attribute

When setting a new object, the update view will be displayed, but the value of the newly added object will not be responsive.

The way Vue2 handles it is to re-perform the defineReactive operation on the property during set and add getters and setters to the property.

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

Note: Because Vue2 adds data responsively during initial initialization and set attributes, data attributes may be added or deleted during use.

Vue cannot add responsiveness. If you want to be responsive to properties added during runtime, you must use the Vue.delete method or Vue.Set.

Processing of Array

Changes within the array include using our commonly used array functions, push, pop, etc.

Neither can be detected by the setter function, it will only be detected when the entire array is replaced.

The way Vue2 solves this problem is:

  • Provides a new set of array mutation functions.

Replace the prototype of Array with a new mutation function, and update the view in the custom mutation function.

Create a new template object using the original Array prototype as a template
const oldArrayProto = Array.prototype;
    const newArrProto = Object.create(oldArrayProto);
Rewrite the push pop and other array mutation functions of the new template
  if (Array.isArray(data)) {
    
    
    data.__proto__ = newArrProto
  }

Note: Vue cannot detect changes to the following arrays:

  • When you set an array item directly using the index, for example: vm.items[indexOfItem] = newValue
  • When you modify the length of the array, for example: vm.items.length = newLength
<html>

<head>

</head>

<body> 
  <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>

Problems that still exist

Use the Object.defineProperty method to monitor changes in data and views 遇到复杂对象的时候需要对所有的对象进行深度遍历来给属性设置上getter和setter函数,这会导致首屏加载速度很慢.

In response to this problem, Vue3 changed the responsive implementation from Object.defineProperty to Proxy, and added responsiveness when the data is needed to improve the loading speed of the first screen.

Reference documentation

  • https://blog.csdn.net/wlijun_0808/article/details/127714522
  • https://blog.csdn.net/qq_36290842/article/details/120941497

Guess you like

Origin blog.csdn.net/BradenHan/article/details/135007115