「译」Copying Objects in JavaScript (JavaScript中的对象复制)
在这篇文章中,我们将会来了解一下JavaScript中对象复制的一些方法,我们将会看到浅复制以及深复制。
在我们开始之前,得先提到一些基础知识:Javascript中的对象只是内存中某个位置的引用。 这些引用是可变的,即它们可以被重新分配。 因此,简单地复制了个引用只会导致这两个引用指向了内存中的相同位置:
正如你在上面的示例中所看到的,foo和bar都反映出了任一对象中所做的变化。 因此,在JavaScript中复制对象的时候根据不同的用例需要多加注意。
浅复制
如果你的对象中只有值类型的属性,你可以使用扩展语法或者Object.assign():
注意,上述两种方法同样可以应用于将属性值从多个源对象复制到目标对象:
上述方法的问题在于,对于那些具有对象类型的属性的对象,只有引用被复制了,即它相当于执行了var bar = foo; 与第一个示例代码一样:
深复制(带坑)
为了深层复制对象,一个可行的解决方案是先将对象序列化为字符串,然后再将其反序列化回来:
遗憾的是,此方法仅在源对象包含可序列化的值类型且没有任何循环引用时才有效。不可序列化值类型的一个例子是Date对象,它以非ISO标准格式打印并且无法解析回其原始值 :( 。
深复制(较少坑)
对于更复杂的情况,有一个利用了新的HTML5克隆算法叫“structured clone”的。遗憾的是,写此文时,它仍然局限于某些内置类型,但它支持比JSON.parse更多的类型:Date,RegExp,Map,Set,Blob,FileList,ImageData,稀疏和类型数组。 它还保留了克隆数据中的引用,并且支持上述提到的序列化方法所不支持的循环和递归结构。
目前,没有直接的方法来调用structured clone算法,但是有一些较新的浏览器特性在底层使用了这种算法。 因此,有一些变通方法可能用于对象的深复制。
MessageChannels: 这背后的思路是利用通信功能来应用序列化算法。 由于此功能是基于事件的,因此生成的克隆也是异步操作。
history API: history.pushState()和history.replaceState()都用第一个参数创造了结构化克隆! 注意,由于此方法是同步的,而且操作浏览器历史记录并不是一项能快速完成的操作,反复调用此方法可能会导致浏览器无响应。
notification API: 在创建新通知时,构造函数会创建其关联数据的结构化克隆。 注意,它还会尝试向用户显示浏览器通知,但除非应用程序已请求显示通知的权限,否则将以静默方式失败。 在得到权限的情况下,通知会立即关闭。
Node.js中的深复制
呃,又表示遗憾了,结构化克隆算法目前仅适用于基于浏览器的应用程序。 对于服务器端,可以使用lodash的cloneDeep方法,该方法大致上基于结构化克隆算法。
结论
总而言之,在JavaScript中复制对象的最佳算法在很大程度上取决于你所要复制的对象类型及环境。 虽然lodash是通用的深复制功能最安全的选择,但如果你自己动手,可能会得到更效率的实现,以下是一个简单的深复制示例,它也支持Date对象类型:
个人而言,我期待能够在任何地方使用结构化克隆,然后可以不用再操心这些问题,快乐的克隆 :)