Why can $watch be triggered with Object.assign

Why can $watch be triggered with Object.assign

Object.assign, this api is often used when simply copying the property value of an enumerable object. Here is a usage of Object.assign in vue2, which is described in detail in the official website documentation :

watch: {
	someObject(nvalue, ovalue) {
		...
	}
}

// 为对象添加新属性
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
复制代码

Also note that adding a new property like the following won't trigger an update:

this.someObject = Object.assign(this.someObject, { a: 1, b: 2 })
复制代码

The question is why does it work this way?

First look at the vue2 documentation

As detailed in the vue2 documentation , in the process of component dependency collection, all properties will be notified of changes when they are accessed and modified. For objects, Vue cannot detect the addition or removal of properties.

In general, in vue, if we want to add root-level properties to instances in the data object, we can do this:

Vue.set(someObject, 'name', value)
复制代码

or do this

this.$set(this.someObject,'name',2)
复制代码

But if we want to add multiple properties to an object while maintaining the responsiveness of the object, the method mentioned at the beginning is used in this case.

On mdn , Object.assignthere is an explanation for this sentence: this method uses the source object [[Get]]and the target object [[Set]], so it will call the relevant getter and setter. For this sentence, we use the following example a to understand.

var obj = {};
var c = null
Object.defineProperty(obj, 'c', {
  set:function(x){
    console.log('c被赋值:',x);
    c=x
  },
  get:function(){
    console.log('c被取出:',c)
    return c
  }
})

obj.c=3  //c被赋值: 3
obj.c  //c被取出:  3

obj = Object.assign(obj, {c: 'wer23e'}) // 触发了set!
obj = Object.assign(obj, {a: 'wer23e'}) // 由于事先未用defineProperty定义a,所以无法监听
// 由于目标对象未定义属性,无法监听
obj = Object.assign({},obj, {a: 'wer23e',c: 'dfrr23e'}) 
复制代码

Through this code, you can understand the above-mentioned "Object.assign will use the target object [[Set]]". At the same time, this code also demonstrates the response principle in vue2, because all the properties that need to be responded in vue2 are used Object.definePropertyfor response binding, so All access and modification actions are tracked. But for properties that are not Object.definePropertydefined , such as adding a property, it cannot be monitored. In the above example, even if I use the usage mentioned by the documentation, the c attribute obj = Object.assign({},obj, {a: 'wer23e',c: 'dfrr23e'})still cannot be triggered set. At this point, do you have to look at the watch source code? Actually it doesn't have to be!

Trigger the watch principle with Object.assign

In response to this problem, the source code of watch does not need to be read, but Object.assignthe source code of watch must be read.

if (typeof Object.assign !== 'function') {
  // Must be writable: true, enumerable: false, configurable: true
  Object.defineProperty(Object, "assign", {
    value: function assign(target, varArgs) { // .length of function is 2
      'use strict';
      if (target === null || target === undefined) {
        throw new TypeError('Cannot convert undefined or null to object');
      }

      var to = Object(target);

      for (var index = 1; index < arguments.length; index++) {
        var nextSource = arguments[index];

        if (nextSource !== null && nextSource !== undefined) {
          for (var nextKey in nextSource) {
            // Avoid bugs when hasOwnProperty is shadowed
            if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
              to[nextKey] = nextSource[nextKey];
            }
          }
        }
      }
      return to;
    },
    writable: true,
    configurable: true
  });
}
复制代码

In fact, it is assignto copy all the enumerable properties of the parameters in the method to the first parameter of this method. Let’s go back and understand the following example a. obj = Object.assign({},obj, {a: 'wer23e',c: 'dfrr23e'})The setfunction because the obj reference relationship has been changed, and it is no longer the original object, so there is no corresponding attribute monitoring, but why does the official document recommend this use? ?

Next, I wrote a small demo to help you understand, you can try the effect

<template>
 <div style="width: 100px; height: 50px; position: absolute; top: 100px" @click="clickme()"> 点我啊 {{ test.a }}</div>
</template>

<script>
export default {
	data() {
		test: { a: 3 },
	},
	watch: {
    test(n, o) {
      debugger
      console.log(n)
    }
  },
	methods: {
		clickme() {
      debugger
      this.test = Object.assign(this.test, { a: 1 })
      debugger
      this.test = Object.assign(this.test, { a: 1, b: 2 })
      debugger
      this.test = Object.assign({}, this.test, { a: 1, b: 2 })
    },	
}
}
</script>
复制代码

As you can see, indeed, the last Object.assign method will trigger the test object to listen. The first two ways of writing can only trigger the property update response of the object. If you add properties to the obj object, you cannot monitor the changes of obj. At this point, in fact, if you want to be completely clear, it is best to look at the watchsource code . After careful analysis, I found that it watchhas nothing to do with the source code, so this article will not analyze it, this is another article.

It doesn’t matter if you don’t understand the watchsource code . In fact, the simple explanation is that in obj = Object.assign({},obj, {a: 'wer23e',c: 'dfrr23e'})this way, the reference relationship of obj is changed, that is, the value of obj has changed, so if you monitor obj in the watch function, obj has changed. It's just that the value of obj is an Objectobject , so the response of the obj object will be triggered.

References:

Guess you like

Origin juejin.im/post/7078610313255321608