vue 中使用Object.assign遇到的问题
前言
之前一直没有很注意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)的方法
有个疑问, Object.assign(this.form, newData) 的情况下,此时newVal是新值,而value是在getter.call(obj)得到的,应该是旧值,因为set还没完成。此时,如果新值不等于旧值,那就不会return啊?
而且,根据assgin的实现 ,Object.assign({}, this.form, newData) 其实是分两步的,第一步合并{}和this.form,第二部合并第一步的结果和newData,按照这个思路的话,其实和 Object.assign(this.form, newData) 是没区别的啊,但事实是有区别的,我到底哪里想错了呢?
我懂了,您说的是form这个属性,而不是form中的title属性。对于Object.assign(this.form, newData),this.form的地址并没有变,所以newVal === value不会触发更新,对于Object.assign({}, this.form, newData),产生了新的对象赋值给this.form, 着改变了form的地址,所以newVal !== value可以触发更新。
http://jsrun.net/YcgKp/edit 总结了一下
是的 是那个意思
=_= 所以即使只是想要修改子属性,就必须创建一个新的对象吗?但是我在自定义的class中,使用 Object.assign 是可以正确调用属性的 set 的
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 响应对象却无效,无法理解。
vue初始化的时候会对t1进行Observe,把里面现有的属性都变成响应式的,现在你Object.assign(t1, { a: 1 }) 这个a属性是一个新的属性,之前没有被Observe过,所以不会生效。
简化一下问题:
new Vue({
data() {
return {
_a:1
}
}
})
解决方案有:
-
this.t1 = Object.assign({}, t1, {a:1})assign此时会返回一个新对象,把它赋值给t1,实际上是this.data.t1=Object.assign({}, t1, {a:1}), 因为data同样在初始化的时候Observe过t1属性,而此时t1的引用变了,所以会触发更新 -
this.$set(this.t1, 'a', 1)这一步不仅给t1添加了一个a属性,同时还Observe了这个属性 -
一开始就让vue去Observe这个a属性
t1 = {
_a:1,
a: 0
}
😅 发现我看错了,原来工作是正常。我的使用场景正是已经初始化过了,通过 Object.assign 同时修改多个属性而已。