Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

vue 中使用Object.assign遇到的问题 #10

Open
AnnVoV opened this issue May 15, 2018 · 9 comments
Open

vue 中使用Object.assign遇到的问题 #10

AnnVoV opened this issue May 15, 2018 · 9 comments
Labels

Comments

@AnnVoV
Copy link
Owner

AnnVoV commented May 15, 2018

前言

之前一直没有很注意vue源码中的proxy方法,最近遇到一个问题,发现了一些忽略的细节。背景如下:

背景

一般我们都会在mounted钩子里面去写一些通过异步接口获取数据的方法,比如下面,发现Object.assign 下面两种写法结果不一样

  <el-form :model="form" label-width="130px">
    <el-form-item>
      <el-input :model="form.title">
    </el-form-item>
  </el-form>
export default {
  data() {
    return {
      form: {}
    }
  },
  mounted() {
    Ajax.get(url)
      .then((data) => {
        // 这样title是双向绑定的
        this.form = data;
        // 这样title也是双向绑定的
        this.form = Object.assign({}, this.form, data)
        // 这样title并不是双向绑定的
        this.form = Object.assign(this.form, data)
      })
  }
}

原因

因为在set里面有这样一个判断

Object.defineProperty(obj, key, {
  enumerable: true,
    configurable: true,
    get: function reactiveGetter () {
      var value = getter ? getter.call(obj) : val;
      if (Dep.target) {
        dep.depend();
        if (childOb) {
          childOb.dep.depend();
          if (Array.isArray(value)) {
            dependArray(value);
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {
      var value = getter ? getter.call(obj) : val;
      /* eslint-disable no-self-compare */
      // 重点注意这里
      if (newVal === value || (newVal !== newVal && value !== value)) {
        return
      }
      /* eslint-enable no-self-compare */
      if (process.env.NODE_ENV !== 'production' && customSetter) {
        customSetter();
      }
      if (setter) {
        setter.call(obj, newVal);
      } else {
        val = newVal;
      }
      childOb = !shallow && observe(newVal);
      dep.notify();
    }
})

  因为Object.assign(this.form, newData)是在原对象上修改的新的值,所以this.form 在被修改值的时候,会进入其setter,且此时newVal === value 所以会return 也就是新的值没有进入observe(newVal)方法
  而Object.assign({}, this.form, newData) 相当于创建了一个新对象,此时newVal === value 就不成立了,所以会进入observe(newVal)的方法

@fxxjdedd
Copy link

fxxjdedd commented Aug 3, 2018

有个疑问, Object.assign(this.form, newData) 的情况下,此时newVal是新值,而value是在getter.call(obj)得到的,应该是旧值,因为set还没完成。此时,如果新值不等于旧值,那就不会return啊?

@fxxjdedd
Copy link

fxxjdedd commented Aug 3, 2018

而且,根据assgin的实现 Object.assign({}, this.form, newData) 其实是分两步的,第一步合并{}和this.form,第二部合并第一步的结果和newData,按照这个思路的话,其实和 Object.assign(this.form, newData) 是没区别的啊,但事实是有区别的,我到底哪里想错了呢?

@fxxjdedd
Copy link

fxxjdedd commented Aug 4, 2018

我懂了,您说的是form这个属性,而不是form中的title属性。对于Object.assign(this.form, newData),this.form的地址并没有变,所以newVal === value不会触发更新,对于Object.assign({}, this.form, newData),产生了新的对象赋值给this.form, 着改变了form的地址,所以newVal !== value可以触发更新。

@fxxjdedd
Copy link

fxxjdedd commented Aug 4, 2018

http://jsrun.net/YcgKp/edit
总结了一下

@AnnVoV
Copy link
Owner Author

AnnVoV commented Aug 9, 2018

是的 是那个意思

@ZSkycat
Copy link

ZSkycat commented Sep 25, 2018

=_= 所以即使只是想要修改子属性,就必须创建一个新的对象吗?但是我在自定义的class中,使用 Object.assign 是可以正确调用属性的 set 的

@ZSkycat
Copy link

ZSkycat commented Sep 25, 2018

t1 = {
	_a:1,
	set a(value){
		console.log('set a')
		this._a = value;
	},
	get a(){
		console.log('set a')
		return this._a;
	}
}
Object.assign(t1, { a: 1 })

控制台使用,可以看到属性 set 是可以被成功调用的,但是对 vue 响应对象却无效,无法理解。

@fxxjdedd
Copy link

vue初始化的时候会对t1进行Observe,把里面现有的属性都变成响应式的,现在你Object.assign(t1, { a: 1 }) 这个a属性是一个新的属性,之前没有被Observe过,所以不会生效。

简化一下问题:

new Vue({
  data() {
    return {
       _a:1
    }
  }
})

解决方案有:

  1. this.t1 = Object.assign({}, t1, {a:1}) assign此时会返回一个新对象,把它赋值给t1,实际上是this.data.t1=Object.assign({}, t1, {a:1}), 因为data同样在初始化的时候Observe过t1属性,而此时t1的引用变了,所以会触发更新

  2. this.$set(this.t1, 'a', 1) 这一步不仅给t1添加了一个a属性,同时还Observe了这个属性

  3. 一开始就让vue去Observe这个a属性

t1 = {
	_a:1,
        a: 0
}

@ZSkycat
Copy link

ZSkycat commented Sep 25, 2018

😅 发现我看错了,原来工作是正常。我的使用场景正是已经初始化过了,通过 Object.assign 同时修改多个属性而已。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

3 participants