为什么可以用Object.assign触发$watch

字节逆旅

共 1917字,需浏览 4分钟

 · 2022-04-01


471fb520825b4037554820a97b5dbcb8.webp

    油菜花

Object.assign,这个api在简单拷贝可枚举对象的属性值时经常用到。这里介绍一个在vue2中Object.assign的用法,这个用法在官网文档 有详细介绍:

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

// 为对象添加新属性
this.someObject = Object.assign({}, this.someObject, { a1b2 })

而且要注意的是如果像下面这样添加上去的新属性无法触发更新:

this.someObject = Object.assign(this.someObject, { a1b2 })

问题是为什么前面那种写法会有效?

先看vue2文档

在vue2的文档中有详细说明,在组件的依赖收集过程中,所有property 在被访问和修改时会通知变更,对于对象来说,Vue 无法检测 property 的添加或移除。

fa240669e894a45d604b0233f3f416a3.webp

一般情况下,在vue中,如果要对data对象中实例添加根级别property,我们可以这样操作:

Vue.set(someObject, 'name', value)

或者这样操作

this.$set(this.someObject,'name',2)

但是如果我们要对一个对象添加多个属性,同时还要保持对象的响应性,这种情况下就要用到开篇提到的方法。

在mdn 上,对 Object.assign 有这一句解释:该方法使用源对象的[[Get]]和目标对象的[[Set]],所以它会调用相关 getter 和 setter。对这句,我们用下面的例子a来理解。

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'}) 

通过这段代码可以理解上面说的“Object.assign会使用目标对象的[[Set]]”,同时,这段代码也演示了vue2中响应原理,因为vue2中所有需要响应的属性都是用 Object.defineProperty 进行响应绑定,这样所有的访问和修改动作都会被追踪到。但是对于没有事先被 Object.defineProperty定义的属性,比如添加一个属性就无法监听到了。在上面的示例中,即使我用文档提到的用法 obj = Object.assign({},obj, {a: 'wer23e',c: 'dfrr23e'}) 仍然无法触发c属性的 set。走到这一步,是不是得看watch源码了?其实不必!

用Object.assign触发watch原理

针对这个问题,watch的源码不必看,但是 Object.assign 的源码必须要看,

if (typeof Object.assign !== 'function') {
  // Must be writable: true, enumerable: false, configurable: true
  Object.defineProperty(Object"assign", {
    valuefunction 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;
    },
    writabletrue,
    configurabletrue
  });
}

其实就是把 assign 方法中的参数的可枚举属性全部复制到此方法的第一参数上去。回头再去理解下例子a, obj = Object.assign({},obj, {a: 'wer23e',c: 'dfrr23e'}) 无法触发c属性的 set 函数是因为,obj引用关系已经被改变了,不再是原来那个对象,也就没有了对应的属性监控,但是为什么官方文档会建议这么用呢?

接下来,我写了个小demo,来帮你理解,你可以试下效果