前端系列——Object.assign的正确使用与错误示范

27

警告

看完文章,可能会颠覆你的认知!!

语法

Object.assign(target, ...sources)

错误示范

我们都知道Object.assign()可以实现对象拷贝,很多人认为他只能实现浅拷贝,我翻遍了MDN的文档,也没搜索到一个字。
那么,到底什么是深拷贝、什么是浅拷贝,你可以去搜索其他文章看看。

下面给大家展示一个错误使用Object.assign的例子(希望原作者看到别打我,我已经写上转载来源了?)

let obj = { name: '程序猿', age:{child: 12} }
let copy = Object.assign({}, obj);
copy.name = '单身狗'
copy.age.child = 24
console.log(obj) // { name: '程序猿', age:{child: 24} }

作者:拌着生活
链接:https://www.jianshu.com/p/70dc5b968767
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

上面的例子大概是这个意思,有一个原始的obj对象,他里面的age对象有一个子节点child,然后使用Object.assign({}, obj)直接拷贝这个对象(或者叫做浅拷贝obj),最后用点赋值法修改copy对象的属性,结果是对原obj对象也进行了修改。

老天,Object.assign怎么这么烂,说好的拷贝呢,只是浅拷贝啊,那react中是不是一大堆state都是N层结构的对象,不能用Object.assign来玩了呢?

来分析一下它错在哪里了。

从语法上来说,并没有用错,的确,这样能实现对象的拷贝,或者叫做“浅拷贝”,但是,如果要修改对象里面的属性,就需要用到“对象的合并”,也就是下面将要讲到的。其实MDN上讲的很明白,Object.assign应该怎样用,只是大家习惯了看博客。

正确使用

看了错误示范,不用担心,我带你来正确使用Object.assign。

我也定义一个对象obj。

let obj = {name: '二月', age: {c: 12}}

正确使用Object.assign进行对象的拷贝,通常有2种方式。

1、Object.assign()

我们对要修改的内部对象属性进行单独的赋值,既不修改obj,也不修改新对象o2,定义单独的age。还记得文章开头的语法吗,源对象...sources表示支持多个散列的对象。

下面的做法相当于,我们定义了一个新的age对象,将它和obj对象进行合并,然后拷贝给一个空对象{},这样使用Object.assign(),就不会对原对象obj产生影响。

let age = {c: 88}
let o2 = Object.assign({}, obj, {age})
console.log(obj, o2)

clipboard.png

那么,有人可能会质疑了,它只是浅拷贝啊,就算你取巧实现了拷贝,但是要是对o2进行赋值了咋办,不也一样会改变obj的值?

相信我,不会的,别被网上所谓的“浅拷贝”迷惑了你的眼睛。

我紧接着对o2里面的age进行直接的赋值,打印出来的obj仍旧不受影响,是不是很神奇!!

let obj = {name: '二月', age: {c: 12}}
let age = {c: 88}
let o2 = Object.assign({}, obj, {age})
o2.age.c = 66
console.log(obj, o2)

clipboard.png

2、ES6的扩展运算符

现在我们都喜欢用ES6的扩展运算符来替换Object.assign。

let obj = {name: '二月', age: {c: 12}}
let o1 = {...obj, age: {c: 88}}
console.log(obj, o1)

clipboard.png

同样的,我把o1也用点语法重新赋值看看会不会对obj产生影响,答案当然是不会!

let obj = {name: '二月', age: {c: 12}}
let o1 = {...obj, age: {c: 88}}
o1.age.c = 99
console.log(obj, o1)

clipboard.png

3、分析原理
一切都要从语法说起Object.assign(target, ...sources),第一个参数target决定了你的对象被拷贝到哪个目标对象上面,如果你不想对原始对象产生影响,就定义一个空对象{}作为target,单单这样还不够,sources只设置原始对象obj的话,表示对原始对象的“浅拷贝”,但是设置多个对象的合并,就会返回一个全新的对象。

总结

看了这篇文章,你摸着良心问问自己,真的理解什么是Object.assign和扩展运算符了吗?更深一个层次来看,你真的理解什么是浅拷贝、什么是深拷贝了吗??


如果觉得我的文章对你有用,请随意赞赏

你可能感兴趣的

45 条评论
杨军军 · 2018年02月07日

感觉你才是误导大家,MDN 明确说了 "For deep cloning, we need to use other alternatives because Object.assign() copies property values.",
文章中

let obj = {name: '二月', age: {c: 12}}
let age = {c: 88}
let o2 = Object.assign({}, obj, {age})
o2.age.c = 66
console.log(obj, o2)
  1. obj.age.c 没有变,但是 age.c 变了呀
  2. 引入一个没有用到的 age 变量
  3. 对于一个非常复杂的对象,你的 age 又打算换成什么呢?

其实 Object.assign 用法非常简单 Object.assign(target, ...sources), target 上的可枚举属性会被sources的可枚举属性覆盖,且后一个sources会覆盖前一个(这里的覆盖其实就是浅拷贝)

+21 回复

0

看你这样说,就是没理解为什么要这样定义一个age出来,写一下reducer再思考你理解哪里错了吧。

二月 作者 · 2018年02月07日
0

@二月 我当然明白你的意思了, o2 的 age 属性被 age 覆盖了,所以改变 o2 的 age 属性只会改变 age ,不会改变 obj。 那请问如果下面的按照你的方法怎么深拷贝呢?

let obj = {
    a: {
        a: 1,
        b: 2,
        c: 3
    },
    b: {
        a: 1,
        b: 2,
        c: 3
    },
    c: {
        a: 1,
        b: 2,
        c: 3
    }
}
杨军军 · 2018年02月07日
1

@杨军军 你这个太多了.... 必须得复制粘贴

lkiarest · 2018年02月07日
流浪到沙滩 · 2018年02月07日

误人子弟

+6 回复

梦令布孑 · 2018年02月09日

作者是不是没搞清楚深浅拷贝的定义?????

+5 回复

lkiarest · 2018年02月07日

按这个做法,我来理解一下:
想要拷贝一个对象 A, 先要知道 A 里面每一个属性的值(说不定可以debug出来呢), 然后重新把这个值写一遍出来(请注意不要把变量名或值写错了, 建议复制/黏贴).
接着用 assign 拷贝新写的这个对象, 妥妥的, 没毛病 ~~~

+3 回复

BBQ只有番薯 · 2018年02月07日

Object.assign()对于基元类型String,Number,Boolean的拷贝是复制,但是对于Object,Function等引用类型是引用传递。let obj2 = Object.assign({},obj,{age})这种做法,相当于已经生成了一个新的对象age,这个age的内存空间和obj.age的内存空间完全不一样。新对象obj2.age也是引用,只不过是对新声明对象age的引用,而不是obj.age的引用。这就是Object.assign()的正常用法呀。

+1 回复

0

你提到的正是我要表达的东西,深拷贝做的事情是完全copy一个对象然后存储到新的内存中。从这个思路理解和Object.assign合并对象操作是一致的。

二月 作者 · 4 天前
Youth · 2018年02月06日

不错不错

回复

0

你可得仔细推敲推敲。别被忽悠了

RowlingJoe · 2018年02月08日
少读书 · 2018年02月06日

移花接木,age的值改变了

回复

0

看你这么热情的评论的,我就跟你说一下吧,age本来就是个中间对象,他变不变对原对象没有任何影响,不知道你盯着一个自己定义的age做什么,并不是assign(age)啊。

二月 作者 · 2018年02月07日
曼高德 · 2018年02月07日

用一个新的对象将原来的对象替换了,改变其值原来对象的值当然不变,和返回新对象没有关系

回复

0

so,这就是assign的用法:合并对象,你看不懂可以用JSON.parse(JSON.stringify(obj))或者深度递归拷贝去完成你的需求

二月 作者 · 2018年02月07日
Jere · 2018年02月07日
let obj = {name: '二月', age: {c: 12}}
let age = {c: 88}

age 和obj.age 明显是两个不同的新对象,改变其中一个,另外一个肯定不受影响啊,作者自己没搞清楚就出来误导别人,真的好吗?

回复

0

你没看懂我说的东西,这是教你如何正确使用assign去拷贝而不会影响到原对象,age是需要被改变的子对象,它可以说是个引子,如果你写过react的reducer,对这种用法就会很了解。

二月 作者 · 2018年02月07日
0

你的这种用法是没有问题的,用新对象覆盖掉原对象,但是正如@杨军军的评论 。你不能通过这个讨巧的操作就来误导大家对Object.assign 的使用

Jere · 2018年02月07日
0

@Jere 这不是取巧,ok,assign合并对象的用法是MDN上有的,你自己没有发现它而已。

二月 作者 · 2018年02月07日
马鹿野郎 · 2018年02月07日

有个问题,如果我obj里面很多像age一样的并且多层嵌套的属性,难道我要定义多个类似age的对象吗?并且这个对象里面还有对象,那也要定义吗

回复

0

@马鹿野郎 看了前面楼主的回复,好像已经有人提了跟我类似的问题,楼主说用reduce解决,能具体点吗,reduce只是个累加器啊

马鹿野郎 · 2018年02月07日
0

有个前提是你已经知道新的age对象应该长什么样(通常你是知道的,因为能用点语法赋值说明你已经知道新的age格式),然后将它和原始对象合并,这个应用场景很广,他和“深拷贝”没什么关系。如果age内部还涉及多层,比如age: {a: {b: {child: 99}}},同样只需要定义最外层的age,也就是assign合并的只是位于原始对象obj下面一级的对象age。

二月 作者 · 2018年02月07日
toBeTheLight · 2018年02月07日

首先深拷贝是不是首先要保证原始值和结果的各个属性和值是相同的?
那么对于层次复杂的数据是不能通过只定义最外层属性来解决的。

var obj2 = {a:{b:{c:{d:{e:1}}}}}
Object.assign({}, obj2, {a:{}})

这么做的话属性就丢失了,这样就有一个问题,要么得重新写一遍复杂部分的数据(麻烦,没有用的必要),要么就丢失属性。
assign其实就是,遍历每一个源对象的属性添加到目标对象上。

Object.assign(a, {key1: 'val1', key2: 'val2'},  {key2: 'val3'})
// 一样一样的
a.key1 = obj1.key1
a.key2 = obj1.key2
a.key3 = obj2.key2

而且为了保证数据不变,是不能随便声明"age对象"的值的,不然拷贝就没意义了,并且在拷贝的时候,一般不会知道目标对象的具体值。

回复

0

当然不能拿assign当深拷贝用,也没人会这样用,你举的例子中Object.assign({}, obj2, {a:{}}),的确是要重写a,但是这种场景是很广的,尤其在react中非常普遍。而这样做是有效利用assign本身提供的特性完成一些对象的拷贝在不改变原始对象的情况下。

二月 作者 · 2018年02月07日
1

@二月 实际运用是什么,不改变原始对象不就是在做深拷贝的事情吗?

toBeTheLight · 2018年02月07日
0

@二月 我觉得如果是为了说明“连续合并”这种用法,文章后面的例子或者写法不太恰当。

toBeTheLight · 2018年02月07日
RowlingJoe · 2018年02月08日

自己留着用吧,就看你敢不敢用了

回复

Tennessee · 2018年07月10日

这个数据重写了吧

回复

0

重写是什么意思

二月 作者 · 2018年07月11日
Renshuo · 2018年11月22日

太扯了 你确定你不是误人子弟

回复

相门城下 · 2018年11月30日

你没有理解assign,也没有理解深拷贝和浅拷贝。你自己实现一个对象合并就懂了。
Object.assign 就是浅拷贝!
你也没有理解reduce,这里根本没有reduce!
如果不用考虑age对象的变化,同样就不需要考虑obj对象的变化!

回复

Bug开发师 · 2018年12月06日

牛皮牛皮 给我解决了一个大BUG

回复

luoyu412799755 · 2018年12月28日

这明显是用后面那个对象覆盖了前面的对象,怎么就成了深复制,楼主别乱讲哦

回复

foxCharon · 1月15日

可以偷鸡,但是不解决根本问题。

回复

rookie · 3月15日

真唬人

回复

0

js的对象存储在内存中,深拷贝的效果就是在内存的其他位置存储一个和之前完全一样的对象。合并对象之后返回的新对象引用的内存地址是新的了,即使修改key、value也不会影响原始对象,怎么就唬人了?你可以说说?别说唬人总冠军就行。

二月 作者 · 4 天前
chan_chun · 4 天前

。。。

回复

载入中...