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 相关文章推荐
js 图片缩放(按比例)控制代码
May 27 Javascript
js 父窗口控制子窗口的行为-打开,关闭,重定位,回复
Apr 20 Javascript
从零开始学习jQuery (二) 万能的选择器
Oct 01 Javascript
你必须知道的Javascript知识点之"this指针"的应用
Apr 23 Javascript
js控制分页打印、打印分页示例
Feb 08 Javascript
javascript在网页中实现读取剪贴板粘贴截图功能
Jun 07 Javascript
javascript实现禁止复制网页内容汇总
Dec 30 Javascript
基于jQuery下拉选择框插件支持单选多选功能代码
Jun 07 Javascript
JavaScript——DOM操作——Window.document对象详解
Jul 14 Javascript
layui实现二维码弹窗、并下载到本地的方法
Sep 25 Javascript
微信浏览器左上角返回按钮监听的实现
Mar 04 Javascript
vue 子组件watch监听不到prop的解决
Aug 09 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
php中get_headers函数的作用及用法的详细介绍
2013/04/27 PHP
thinkphp autoload 命名空间自定义 namespace
2015/07/17 PHP
PHP编写RESTful接口
2016/02/23 PHP
thinkPHP5.1框架中Request类四种调用方式示例
2019/08/03 PHP
ExtJS 设置级联菜单的默认值
2010/06/13 Javascript
NodeJS的url截取模块url-extract的使用实例
2013/11/18 NodeJs
jquery中ajax处理跨域的三大方式
2016/01/05 Javascript
JS获取时间的相关函数及时间戳与时间日期之间的转换
2016/02/04 Javascript
利用js查找数组中指定元素并返回该元素的所有索引示例
2017/03/29 Javascript
微信小程序上滑加载下拉刷新(onscrollLower)分批加载数据(二)
2017/05/11 Javascript
解决webpack无法通过IP地址访问localhost的问题
2018/02/22 Javascript
jquery点击回车键实现登录效果并默认焦点的方法
2018/03/09 jQuery
微信小程序http连接访问解决方案的示例
2018/11/05 Javascript
Vue唯一可以更改vuex实例中state数据状态的属性对象Mutation的讲解
2019/01/18 Javascript
swiper4实现移动端导航切换
2020/10/16 Javascript
使用layui前端框架弹出form表单以及提交的示例
2019/10/25 Javascript
[01:24]2014DOTA2 TI第二日 YYF表示这届谁赢都有可能
2014/07/11 DOTA
[52:00]2018DOTA2亚洲邀请赛 4.1 小组赛 A组加赛 LGD vs Optic
2018/04/02 DOTA
盘点提高 Python 代码效率的方法
2014/07/03 Python
Python学习笔记之os模块使用总结
2014/11/03 Python
Python实现获取网站PR及百度权重
2015/01/21 Python
python学习教程之使用py2exe打包
2017/09/24 Python
Django中更改默认数据库为mysql的方法示例
2018/12/05 Python
python 机器学习之支持向量机非线性回归SVR模型
2019/06/26 Python
python调用webservice接口的实现
2019/07/12 Python
python实现加密的方式总结
2020/01/19 Python
python实现简单井字棋小游戏
2020/03/05 Python
html5 Web SQL Database 之事务处理函数transaction与executeSQL解析
2013/11/07 HTML / CSS
华为python面试题
2016/05/03 面试题
职称自我鉴定
2013/10/15 职场文书
十佳青年个人事迹材料
2014/01/28 职场文书
乡镇党的群众路线教育实践活动总结报告
2014/10/30 职场文书
关于antd tree 和父子组件之间的传值问题(react 总结)
2021/06/02 Javascript
mysql如何配置白名单访问
2021/06/30 MySQL
python+pytest接口自动化之token关联登录的实现
2022/04/06 Python
Golang并发工具Singleflight
2022/05/06 Golang