克隆JavaScript对象的最有效方法是什么?我见过 obj = eval(uneval(o));
正在使用,但是 这是非标准的,只有Firefox支持。
我做过类似的事情 obj = JSON.parse(JSON.stringify(o));
但质疑效率。
我也看到了具有各种缺陷的递归复制功能。
我很惊讶没有规范的解决方案。
克隆JavaScript对象的最有效方法是什么?我见过 obj = eval(uneval(o));
正在使用,但是 这是非标准的,只有Firefox支持。
我做过类似的事情 obj = JSON.parse(JSON.stringify(o));
但质疑效率。
我也看到了具有各种缺陷的递归复制功能。
我很惊讶没有规范的解决方案。
注意: 这是对另一个答案的回复,而不是对这个问题的正确回答。如果您希望快速克隆对象,请关注 Corban在答案中的建议 对这个问题。
我想要注意的是 .clone()
方法 jQuery的 只克隆DOM元素。为了克隆JavaScript对象,您可以:
// Shallow copy
var newObject = jQuery.extend({}, oldObject);
// Deep copy
var newObject = jQuery.extend(true, {}, oldObject);
更多信息可以在 jQuery文档。
我还要注意,深层副本实际上比上面显示的更加智能 - 它可以避免许多陷阱(例如,尝试深度扩展DOM元素)。它经常在jQuery核心和插件中使用,效果很好。
查看此基准: http://jsben.ch/#/bWfk9
在我之前的测试中,速度是我发现的一个主要问题
JSON.parse(JSON.stringify(obj))
成为深度克隆对象的最快方法(它击败了 jQuery.extend 深旗设置为10-20%)。
当深度标志设置为false(浅层克隆)时,jQuery.extend非常快。这是一个很好的选择,因为它包含一些额外的类型验证逻辑,不会复制未定义的属性等,但这也会让你慢下来。
如果您知道要尝试克隆的对象的结构,或者可以避免深层嵌套数组,则可以编写一个简单的 for (var i in obj)
在检查hasOwnProperty时循环克隆你的对象,它将比jQuery快得多。
最后,如果您尝试在热循环中克隆已知对象结构,只需嵌入克隆过程并手动构建对象,即可获得更多性能。
JavaScript跟踪引擎很擅长优化 for..in
循环和检查hasOwnProperty也会减慢你的速度。当速度是绝对必须时手动克隆。
var clonedObject = {
knownProp: obj.knownProp,
..
}
小心使用 JSON.parse(JSON.stringify(obj))
方法 Date
对象 - JSON.stringify(new Date())
返回ISO格式的日期的字符串表示形式 JSON.parse()
不 转换回来 Date
目的。 有关详细信息,请参阅此答案。
此外,请注意,至少在Chrome 65中,本机克隆是不可取的。根据 这个JSPerf通过创建一个新函数来执行本机克隆几乎就是 800X 比使用JSON.stringify要慢得多,这一切都非常快。
假设您的对象中只有变量而不是任何函数,您可以使用:
var newObject = JSON.parse(JSON.stringify(oldObject));
HTML5定义 内部“结构化”克隆算法 这可以创建对象的深层克隆。它仍然局限于某些内置类型,但除了JSON支持的几种类型外,它还支持日期,RegExps,地图,集合,Blob,FileLists,ImageDatas,稀疏数组, 键入的数组,将来可能更多。它还保留了克隆数据中的引用,允许它支持会导致JSON错误的循环和递归结构。
浏览器目前不提供结构化克隆算法的直接接口,而是全局的 structuredClone()
功能正在积极讨论中 在GitHub上的whatwg / html#793 并且可能即将推出!正如目前提出的那样,在大多数情况下使用它将如下所示:
const clone = structuredClone(original);
在此之前,浏览器的结构化克隆实现仅间接暴露。
使用现有API创建结构化克隆的低开销方法是通过一个端口发布数据 MessageChannels。另一个端口会发出一个 message
具有附加结构化克隆的事件 .data
。不幸的是,监听这些事件必然是异步的,并且同步替代方案不太实用。
class StructuredCloner {
constructor() {
this.pendingClones_ = new Map();
this.nextKey_ = 0;
const channel = new MessageChannel();
this.inPort_ = channel.port1;
this.outPort_ = channel.port2;
this.outPort_.onmessage = ({data: {key, value}}) => {
const resolve = this.pendingClones_.get(key);
resolve(value);
this.pendingClones_.delete(key);
};
this.outPort_.start();
}
cloneAsync(value) {
return new Promise(resolve => {
const key = this.nextKey_++;
this.pendingClones_.set(key, resolve);
this.inPort_.postMessage({key, value});
});
}
}
const structuredCloneAsync = window.structuredCloneAsync =
StructuredCloner.prototype.cloneAsync.bind(new StructuredCloner);
const main = async () => {
const original = { date: new Date(), number: Math.random() };
original.self = original;
const clone = await structuredCloneAsync(original);
// They're different objects:
console.assert(original !== clone);
console.assert(original.date !== clone.date);
// They're cyclical:
console.assert(original.self === original);
console.assert(clone.self === clone);
// They contain equivalent values:
console.assert(original.number === clone.number);
console.assert(Number(original.date) === Number(clone.date));
console.log("Assertions complete.");
};
main();
同步创建结构化克隆没有好的选择。以下是一些不切实际的黑客行为。
history.pushState()
和 history.replaceState()
都创建了第一个参数的结构化克隆,并将该值赋值给 history.state
。您可以使用它来创建任何对象的结构化克隆,如下所示:
const structuredClone = obj => {
const oldState = history.state;
history.replaceState(obj, null);
const clonedObj = history.state;
history.replaceState(oldState, null);
return clonedObj;
};
'use strict';
const main = () => {
const original = { date: new Date(), number: Math.random() };
original.self = original;
const clone = structuredClone(original);
// They're different objects:
console.assert(original !== clone);
console.assert(original.date !== clone.date);
// They're cyclical:
console.assert(original.self === original);
console.assert(clone.self === clone);
// They contain equivalent values:
console.assert(original.number === clone.number);
console.assert(Number(original.date) === Number(clone.date));
console.log("Assertions complete.");
};
const structuredClone = obj => {
const oldState = history.state;
history.replaceState(obj, null);
const clonedObj = history.state;
history.replaceState(oldState, null);
return clonedObj;
};
main();
虽然是同步的,但这可能非常慢。它会产生与操纵浏览器历史记录相关的所有开销。反复调用此方法可能会导致Chrome暂时无响应。
该 Notification
构造函数 创建其关联数据的结构化克隆。它还会尝试向用户显示浏览器通知,但除非您已请求通知权限,否则它将无声地失败。如果您有其他用途的许可,我们将立即关闭我们创建的通知。
const structuredClone = obj => {
const n = new Notification('', {data: obj, silent: true});
n.onshow = n.close.bind(n);
return n.data;
};
'use strict';
const main = () => {
const original = { date: new Date(), number: Math.random() };
original.self = original;
const clone = structuredClone(original);
// They're different objects:
console.assert(original !== clone);
console.assert(original.date !== clone.date);
// They're cyclical:
console.assert(original.self === original);
console.assert(clone.self === clone);
// They contain equivalent values:
console.assert(original.number === clone.number);
console.assert(Number(original.date) === Number(clone.date));
console.log("Assertions complete.");
};
const structuredClone = obj => {
const n = new Notification('', {data: obj, silent: true});
n.close();
return n.data;
};
main();
如果没有内置的,你可以尝试:
function clone(obj) {
if (obj === null || typeof(obj) !== 'object' || 'isActiveClone' in obj)
return obj;
if (obj instanceof Date)
var temp = new obj.constructor(); //or new Date(obj);
else
var temp = obj.constructor();
for (var key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
obj['isActiveClone'] = null;
temp[key] = clone(obj[key]);
delete obj['isActiveClone'];
}
}
return temp;
}
一个 Object.assign
method是ECMAScript 2015(ES6)标准的一部分,完全符合您的要求。
var clone = Object.assign({}, obj);
Object.assign()方法用于将所有可枚举的自有属性的值从一个或多个源对象复制到目标对象。
该 填充工具 支持旧浏览器:
if (!Object.assign) {
Object.defineProperty(Object, 'assign', {
enumerable: false,
configurable: true,
writable: true,
value: function(target) {
'use strict';
if (target === undefined || target === null) {
throw new TypeError('Cannot convert first argument to object');
}
var to = Object(target);
for (var i = 1; i < arguments.length; i++) {
var nextSource = arguments[i];
if (nextSource === undefined || nextSource === null) {
continue;
}
nextSource = Object(nextSource);
var keysArray = Object.keys(nextSource);
for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
var nextKey = keysArray[nextIndex];
var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
if (desc !== undefined && desc.enumerable) {
to[nextKey] = nextSource[nextKey];
}
}
}
return to;
}
});
}
码:
// extends 'from' object with members from 'to'. If 'to' is null, a deep clone of 'from' is returned
function extend(from, to)
{
if (from == null || typeof from != "object") return from;
if (from.constructor != Object && from.constructor != Array) return from;
if (from.constructor == Date || from.constructor == RegExp || from.constructor == Function ||
from.constructor == String || from.constructor == Number || from.constructor == Boolean)
return new from.constructor(from);
to = to || new from.constructor();
for (var name in from)
{
to[name] = typeof to[name] == "undefined" ? extend(from[name], null) : to[name];
}
return to;
}
测试:
var obj =
{
date: new Date(),
func: function(q) { return 1 + q; },
num: 123,
text: "asdasd",
array: [1, "asd"],
regex: new RegExp(/aaa/i),
subobj:
{
num: 234,
text: "asdsaD"
}
}
var clone = extend(obj);
这就是我正在使用的:
function cloneObject(obj) {
var clone = {};
for(var i in obj) {
if(typeof(obj[i])=="object" && obj[i] != null)
clone[i] = cloneObject(obj[i]);
else
clone[i] = obj[i];
}
return clone;
}
按性能深度复制: 排名从最好到最差
深层复制一个字符串或数字数组(一个级别 - 没有引用指针):
当数组包含数字和字符串时 - 函数如.slice(),. concat(),. splice(),赋值运算符“=”和Underscore.js的克隆函数;将制作数组元素的深层副本。
重新分配的地方表现最快:
var arr1 = ['a', 'b', 'c'];
var arr2 = arr1;
arr1 = ['a', 'b', 'c'];
并且.slice()的性能优于.concat(), http://jsperf.com/duplicate-array-slice-vs-concat/3
var arr1 = ['a', 'b', 'c']; // Becomes arr1 = ['a', 'b', 'c']
var arr2a = arr1.slice(0); // Becomes arr2a = ['a', 'b', 'c'] - deep copy
var arr2b = arr1.concat(); // Becomes arr2b = ['a', 'b', 'c'] - deep copy
深层复制一个对象数组(两个或多个级别 - 引用指针):
var arr1 = [{object:'a'}, {object:'b'}];
编写自定义函数(具有比$ .extend()或JSON.parse更快的性能):
function copy(o) {
var out, v, key;
out = Array.isArray(o) ? [] : {};
for (key in o) {
v = o[key];
out[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
}
return out;
}
copy(arr1);
使用第三方实用程序功能:
$.extend(true, [], arr1); // Jquery Extend
JSON.parse(arr1);
_.cloneDeep(arr1); // Lo-dash
jQuery的$ .extend具有更好的性能:
var clone = function() {
var newObj = (this instanceof Array) ? [] : {};
for (var i in this) {
if (this[i] && typeof this[i] == "object") {
newObj[i] = this[i].clone();
}
else
{
newObj[i] = this[i];
}
}
return newObj;
};
Object.defineProperty( Object.prototype, "clone", {value: clone, enumerable: false});