我有一个对象x。我想将其复制为对象y,这样对y的更改不会修改x。我意识到复制从内置JavaScript对象派生的对象会导致额外的、不需要的财产。这不是问题,因为我正在复制我自己的一个文字构造对象。
如何正确克隆JavaScript对象?
我有一个对象x。我想将其复制为对象y,这样对y的更改不会修改x。我意识到复制从内置JavaScript对象派生的对象会导致额外的、不需要的财产。这不是问题,因为我正在复制我自己的一个文字构造对象。
如何正确克隆JavaScript对象?
当前回答
结构化克隆
2022年更新:structuredClone()全局函数已在Node 17、Deno 1.14和大多数主要浏览器中可用(请参阅我可以使用)。
您可以使用与HTML标准相同的结构化克隆机制在领域之间发送数据。
const clone = structuredClone(original);
有关详细信息,请参阅另一个答案。
其他回答
如果您有一个带有函数的对象,可以使用JSONfn来实现,请参见http://www.eslinstructor.net/jsonfn/.
var obj= {
name:'Marvin',
getName : function(){
return this.name;
}
}
var cobj = JSONfn.parse(JSONfn.stringify(obj));
使用Lodash:
var y = _.clone(x, true);
要处理JSON.stringify无法处理的循环对象,可以引入一个名为JSOG的库,该库将任意图形序列化和反序列化为JSON格式。
var clone = JSOG.parse(JSOG.stringify(original));
尝试使用此技巧修补JSOG以进行克隆可能也很有趣(目前没有时间,但如果有人想尝试一下…):
序列化简单函数:
foo.f = function(a) { return a }
var stringForm = foo.f.toString() // "function (a) { return a }"
反序列化函数:
eval("foo.f = " + stringForm)
需要一些约定(可能是以属性的名称)来标识函数与正则字符串(可能是@func_f)。
当然,如果该函数调用第二个函数,则第二个功能将需要与原始函数一样存在。
然而,如果您要接受来自不可信来源的序列化表单,则上述内容非常危险,但接受来自不可靠来源的任何形式的函数都是危险的,因此如果您对克隆函数感兴趣,则必须已经建立了信任(或者您已经打算编写安全漏洞!)。
免责声明:我没有测试JSOG字符串/解析与JSON字符串/解析的速度,但它确实适用于我测试的简单(循环)对象。
function clone(src, deep) {
var toString = Object.prototype.toString;
if(!src && typeof src != "object"){
//any non-object ( Boolean, String, Number ), null, undefined, NaN
return src;
}
//Honor native/custom clone methods
if(src.clone && toString.call(src.clone) == "[object Function]"){
return src.clone(deep);
}
//DOM Elements
if(src.nodeType && toString.call(src.cloneNode) == "[object Function]"){
return src.cloneNode(deep);
}
//Date
if(toString.call(src) == "[object Date]"){
return new Date(src.getTime());
}
//RegExp
if(toString.call(src) == "[object RegExp]"){
return new RegExp(src);
}
//Function
if(toString.call(src) == "[object Function]"){
//Wrap in another method to make sure == is not true;
//Note: Huge performance issue due to closures, comment this :)
return (function(){
src.apply(this, arguments);
});
}
var ret, index;
//Array
if(toString.call(src) == "[object Array]"){
//[].slice(0) would soft clone
ret = src.slice();
if(deep){
index = ret.length;
while(index--){
ret[index] = clone(ret[index], true);
}
}
}
//Object
else {
ret = src.constructor ? new src.constructor() : {};
for (var prop in src) {
ret[prop] = deep
? clone(src[prop], true)
: src[prop];
}
}
return ret;
};
基于模板克隆对象。如果您不想要精确的副本,但确实想要某种可靠的克隆操作的健壮性,但您只想要克隆位,或者希望确保可以控制每个属性值的存在或格式,该怎么办?
我之所以这么做,是因为它对我们有用,我们之所以创建它,是因为我们找不到类似的东西。您可以使用它来基于模板对象克隆对象,该模板对象指定了要克隆的对象的属性,如果源对象上不存在这些属性,或者您希望如何处理克隆,则模板允许函数将这些属性转换为不同的属性。如果没有用,我相信有人可以删除这个答案。
function isFunction(functionToCheck) {
var getType = {};
return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
}
function cloneObjectByTemplate(obj, tpl, cloneConstructor) {
if (typeof cloneConstructor === "undefined") {
cloneConstructor = false;
}
if (obj == null || typeof (obj) != 'object') return obj;
//if we have an array, work through it's contents and apply the template to each item...
if (Array.isArray(obj)) {
var ret = [];
for (var i = 0; i < obj.length; i++) {
ret.push(cloneObjectByTemplate(obj[i], tpl, cloneConstructor));
}
return ret;
}
//otherwise we have an object...
//var temp:any = {}; // obj.constructor(); // we can't call obj.constructor because typescript defines this, so if we are dealing with a typescript object it might reset values.
var temp = cloneConstructor ? new obj.constructor() : {};
for (var key in tpl) {
//if we are provided with a function to determine the value of this property, call it...
if (isFunction(tpl[key])) {
temp[key] = tpl[key](obj); //assign the result of the function call, passing in the value
} else {
//if our object has this property...
if (obj[key] != undefined) {
if (Array.isArray(obj[key])) {
temp[key] = [];
for (var i = 0; i < obj[key].length; i++) {
temp[key].push(cloneObjectByTemplate(obj[key][i], tpl[key], cloneConstructor));
}
} else {
temp[key] = cloneObjectByTemplate(obj[key], tpl[key], cloneConstructor);
}
}
}
}
return temp;
}
一个简单的调用方法如下:
var source = {
a: "whatever",
b: {
x: "yeah",
y: "haha"
}
};
var template = {
a: true, //we want to clone "a"
b: {
x: true //we want to clone "b.x" too
}
};
var destination = cloneObjectByTemplate(source, template);
如果您想使用函数来确保返回属性或确保它是特定类型,请使用这样的模板。我们提供了一个函数,它仍然只是复制源对象的ID属性,但它确保它是一个数字,即使它不存在于源对象上。
var template = {
ID: function (srcObj) {
if(srcObj.ID == undefined){ return -1; }
return parseInt(srcObj.ID.toString());
}
}
数组可以很好地进行克隆,但如果您愿意,也可以使用自己的函数来处理这些单独的属性,并执行以下特殊操作:
var template = {
tags: function (srcObj) {
var tags = [];
if (process.tags != undefined) {
for (var i = 0; i < process.tags.length; i++) {
tags.push(cloneObjectByTemplate(
srcObj.tags[i],
{ a : true, b : true } //another template for each item in the array
);
}
}
return tags;
}
}
所以在上面,我们的模板只复制源对象的标签属性(如果它存在的话)(假设它是一个数组),并且对该数组中的每个元素调用clone函数,根据第二个模板单独克隆它,该模板只复制这些标签元素的a和b属性。
如果您要将对象移入和移出节点,并且希望控制这些对象的哪些属性被克隆,那么这是在node.js中控制这些属性的一种很好的方法,代码也可以在浏览器中工作。
下面是它的用法示例:http://jsfiddle.net/hjchyLt1/