JavaScript对象拷贝与Object.assign用法实例分析


Posted in Javascript onJune 20, 2018

本文实例讲述了JavaScript对象拷贝与Object.assign用法。分享给大家供大家参考,具体如下:

深拷贝与浅拷贝

在 JavaScript 中,对于基本数据类型(undefined、null、boolean、number、string)来说,在变量中存储的就是这个变量本身的值,复制是对值的复制,不存在深浅之说。但C系语言的共同特点中有,存储引用类型(对象),实际中在变量里存的是它的地址。因此对 JavaScript 中的复杂数据类型(object)来说,也会有浅拷贝和深拷贝的概念:浅拷贝指两个不同的变量存的是同一个对象的地址,即两个变量指向同一块内存区域;深拷贝则是重新分配了一块内存区域来存储复制后的对象,两个变量存的是真正的两个互不影响的变量。

p.s. 有些资料认为浅拷贝是重新分配内存,并把原对象中的各个属性进行依次复制而不进行递归复制属性值是对象的情况,也就是只复制对象的最外面一层。本文将这种情况归于“深拷贝和浅拷贝的中间情况”,文中以“是否划分新的内存”为界限划分深浅拷贝,这种划分方式与 C/C++、C#、Java 等C系语言保持概念一致。

浅拷贝在JavaScript中的实现

浅拷贝在js中很简单,例如:

let objA = {
  name: '对象A',
  content: '我是A'
};
let copyA = objA;
console.log(objA.name); // ==> "对象A"
console.log(copyA.name); // ==> "对象A"

如此即得到了objA的一份浅拷贝copyA,由于指向的是同一个对象,因此在修改objA的同时也是修改了copyA,反之亦然。

Object.assign()

Object.assign(target, …sources) MDN上对该方法的描述是 将所有可枚举属性的值从一个或多个源对象复制到目标对象,String类型和 Symbol 类型的属性都会被拷贝, 并且该方法会忽略值为 或者 undefined 的源对象。例如:

var o1 = { a: 1 };
var o2 = { [Symbol('foo')]: 2 };
var obj = Object.assign({}, o1, o2);
console.log(obj); // { a : 1, [Symbol("foo")]: 2 } (cf. bug 1207182 on Firefox)
Object.getOwnPropertySymbols(obj); // [Symbol(foo)]

Object.assign 的深拷贝与浅拷贝

注意前面说的是可枚举属性,这是一个介于完全的深拷贝和完全的浅拷贝之间的方法:如果我们把它的第一个参数target设置为一个空对象 {},同时保证剩余的源对象sources中的属性类型不包含引用类型,则该方法的返回值就是一个与源对象相同的但并不在同一块内存空间另一个对象,即获得了源对象的深拷贝。但是,如果源对象的属性中包含某个对象,也就是这个属性的值指向某个对象,就像下面这样:

var obj = {
  name: 'obj name',
  content: {
    a: 1,
    b: 2
  }
};

则使用 Object.assign({}, obj) 时,返回的目标对象中的content属性与源对象obj中的content属性指向的同一块内存区域,即对obj下的content属性进行了浅拷贝。因此针对深拷贝,需要使用其他方法,比如自己实现一个深拷贝的方法,或者使用 JSON.parse(JSON.stringify(obj)), 因为 Object.assign()拷贝的是属性值。

Object.assign 的属性覆盖

如果目标对象中的属性具有相同的键,则属性将被源中的属性覆盖。后来的源的属性将类似地覆盖早先的属性,因此可以用来合并对象(常用的一个场景是使用reducers更新React应用的状态)。

var o1 = { a: 1, b: 1, c: 1 };
var o2 = { b: 2, c: 2 };
var o3 = { c: 3 };
var obj = Object.assign({}, o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }

关于对象的继承属性和不可枚举属性

前文有提到,Object.assign 拷贝的是对象的可枚举属性,该方法使用源对象的 [[Get]] 和目标对象的 [[Set]],所以它会调用相关 getter setter。因此,不如说它是分配属性,而不仅仅是复制或定义新的属性。如果合并源包含 getter,这可能使其不适合将新属性合并到原型中,将属性定义(包括其可枚举性)复制到原型应使用Object.getOwnPropertyDescriptor()Object.defineProperty() ,因此 Object.assign 不能拷贝对象的继承属性,例如:

var obj = Object.create({foo: 1}, { // foo 是个继承属性。
  bar: {
    value: 2 // bar 是个不可枚举属性。
  },
  baz: {
    value: 3,
    enumerable: true // baz 是个自身可枚举属性。
  }
});
var copy = Object.assign({}, obj);
console.log(copy); // { baz: 3 }

当源对象是原始数据类型时

ECMAScript 有 5 种原始类型(primitive type): UndefinedNullBooleanNumber String。当Object.assign的源对象是原始类型时,源对象会被包装成“对象”,对应的键是它在源对象中的索引值:

var v1 = "abc";
var v2 = true;
var v3 = 10;
var v4 = Symbol("foo")
var obj = Object.assign({}, v1, null, v2, undefined, v3, v4); 
// 原始类型会被包装,null 和 undefined 会被忽略。
// 注意,只有字符串的包装对象才可能有自身可枚举属性。
console.log(obj); // { "0": "a", "1": "b", "2": "c" }

当拷贝过程中发生异常时

在出现错误的情况下,例如,如果属性不可写,会引发TypeError,异常会打断后续的拷贝任务。如果在引发错误之前添加了任何属性,则可以更改target对象。

var target = Object.defineProperty({}, "foo", {
  value: 1,
  writable: false
}); // target 的 foo 属性是个只读属性。
Object.assign(target, {bar: 2}, {foo2: 3, foo: 3, foo3: 3}, {baz: 4});
// TypeError: "foo" is read-only
// 注意这个异常是在拷贝第二个源对象的第二个属性时发生的。
console.log(target.bar); // 2,说明第一个源对象拷贝成功了。
console.log(target.foo2); // 3,说明第二个源对象的第一个属性也拷贝成功了。
console.log(target.foo); // 1,只读属性不能被覆盖,所以第二个源对象的第二个属性拷贝失败了。
console.log(target.foo3); // undefined,异常之后 assign 方法就退出了,第三个属性是不会被拷贝到的。
console.log(target.baz); // undefined,第三个源对象更是不会被拷贝到的。

希望本文所述对大家JavaScript程序设计有所帮助。

Javascript 相关文章推荐
jQuery 浮动广告实现代码
Dec 25 Javascript
获取HTML DOM节点元素的方法的总结
Aug 21 Javascript
JS拖动鼠标画出方框实现鼠标选区的方法
Aug 05 Javascript
BootStrap初学者对弹出框和进度条的使用感觉
Jun 27 Javascript
JS判断是否在微信浏览器打开的简单实例(推荐)
Aug 24 Javascript
几行js代码实现自适应
Feb 24 Javascript
Express + Node.js实现登录拦截器的实例代码
Jul 01 Javascript
vue父组件点击触发子组件事件的实例讲解
Feb 08 Javascript
怎样使你的 JavaScript 代码简单易读(推荐)
Apr 16 Javascript
JavaScript实现五子棋游戏的方法详解
Jul 08 Javascript
使用vue-cli4.0快速搭建一个项目的方法步骤
Dec 04 Javascript
JavaScript实现联动菜单特效
Jan 07 Javascript
vue打包的时候自动将px转成rem的操作方法
Jun 20 #Javascript
详解基于vue的服务端渲染框架NUXT
Jun 20 #Javascript
JS中call和apply函数用法实例分析
Jun 20 #Javascript
微信小程序模拟cookie的实现
Jun 20 #Javascript
JS伪继承prototype实现方法示例
Jun 20 #Javascript
通过jquery.cookie.js实现记住用户名、密码登录功能
Jun 20 #jQuery
Vue.JS实现垂直方向展开、收缩不定高度模块的JS组件
Jun 19 #Javascript
You might like
ThinkPHP的RBAC(基于角色权限控制)深入解析
2013/06/17 PHP
浅析php header 跳转
2013/06/17 PHP
浅谈php正则表达式中的非贪婪模式匹配的使用
2014/11/25 PHP
php中header设置常见文件类型的content-type
2015/06/23 PHP
分享微信扫码支付开发遇到问题及解决方案-附Ecshop微信支付插件
2015/08/23 PHP
JS BASE64编码 window.atob(), window.btoa()
2021/03/09 Javascript
关于jquery input textare 事件绑定及用法学习
2013/04/03 Javascript
JS防止用户多次提交的简单代码
2013/08/01 Javascript
Jquery获取和修改img的src值的方法
2014/02/17 Javascript
自己使用js/jquery写的一个定制对话框控件
2014/05/02 Javascript
JavaScript中数据结构与算法(四):串(BF)
2015/06/19 Javascript
原生JavaScript实现Ajax的方法
2016/04/07 Javascript
javascript实现任务栏消息提示的简单实例
2016/05/31 Javascript
JavaScript_ECMA5数组新特性详解
2016/06/12 Javascript
Js 获取、判断浏览器版本信息的简单方法
2016/08/08 Javascript
easyui datagrid 表格中操作栏 按钮图标不显示的解决方法
2017/07/27 Javascript
vue.js指令v-for使用以及下标索引的获取
2019/01/31 Javascript
vue之a-table中实现清空选中的数据
2019/11/07 Javascript
[53:15]2018DOTA2亚洲邀请赛3月29日 小组赛A组 LGD VS TNC
2018/03/30 DOTA
[11:42]2018DOTA2国际邀请赛寻真——OG卷土重来
2018/08/17 DOTA
[00:15]天涯墨客终极技能展示
2018/08/25 DOTA
[01:39:04]DOTA2-DPC中国联赛 正赛 SAG vs CDEC BO3 第二场 2月1日
2021/03/11 DOTA
在Python的Django框架中加载模版的方法
2015/07/16 Python
再谈Python中的字符串与字符编码(推荐)
2016/12/14 Python
Python如何快速实现分布式任务
2017/07/06 Python
pyspark.sql.DataFrame与pandas.DataFrame之间的相互转换实例
2018/08/02 Python
pytorch进行上采样的种类实例
2020/02/18 Python
Django调用百度AI接口实现人脸注册登录代码实例
2020/04/23 Python
python爬虫爬取网页数据并解析数据
2020/09/18 Python
实习老师离校感言
2014/02/03 职场文书
置业顾问岗位职责
2014/03/02 职场文书
个人廉洁自律承诺书
2014/03/27 职场文书
小学一年级评语大全
2014/04/22 职场文书
2014超市双十一活动策划方案
2014/09/29 职场文书
2015夏季作息时间调整通知
2015/04/24 职场文书
火烧圆明园的观后感
2015/06/03 职场文书