我有一个对象x。我想将其复制为 object y,以便更改为y不修改x。我意识到复制从内置 JavaScript 对象派生的对象会导致不需要的属性。
x
y
如何正确克隆 JavaScript 对象?
对 JavaScript 中的任何对象执行此操作都不会简单或直接。您将遇到错误地从对象原型中获取属性的问题,这些属性应该留在原型中而不是复制到新实例中。例如,如果您要向 中添加一个clone方法Object.prototype,正如某些答案所描述的那样,您将需要显式跳过该属性。Object.prototype但是,如果您不知道添加了其他附加方法或其他中间原型怎么办?在这种情况下,您将复制不应该复制的属性,因此您需要使用该方法检测不可预见的非本地属性hasOwnProperty。
clone
Object.prototype
hasOwnProperty
除了不可枚举的属性之外,当您尝试复制具有隐藏属性的对象时,您还会遇到更棘手的问题。例如,prototype是函数的隐藏属性。此外,对象的原型由属性引用,该属性__proto__也是隐藏的,并且不会被迭代源对象属性的 for/in 循环复制。我认为__proto__可能是 Firefox 的 JavaScript 解释器特有的,在其他浏览器中可能有所不同,但你明白了。并非所有事物都是可枚举的。如果你知道它的名字,你可以复制一个隐藏的属性,但我不知道有什么方法可以自动发现它。
prototype
__proto__
寻求优雅解决方案的另一个障碍是正确设置原型继承的问题。如果您的源对象的原型是Object,那么只需创建一个新的通用对象{}就可以了,但是如果源的原型是 的某个后代Object,那么您将丢失使用hasOwnProperty过滤器跳过的原型中的其他成员,或者在原型中,但一开始就无法枚举。一种解决方案可能是调用源对象的constructor属性来获取初始复制对象,然后复制属性,但是您仍然不会获得不可枚举的属性。例如,一个Date对象将其数据存储为隐藏成员:
Object
{}
constructor
Date
function clone(obj) { if (null == obj || "object" != typeof obj) return obj; var copy = obj.constructor(); for (var attr in obj) { if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr]; } return copy; } var d1 = new Date(); /* Executes function after 5 seconds. */ setTimeout(function(){ var d2 = clone(d1); alert("d1 = " + d1.toString() + "\nd2 = " + d2.toString()); }, 5000);
的日期字符串d1将比 的日期字符串晚 5 秒d2。一种使一个Date与另一个相同的方法是调用该setTime方法,但这是特定于Date类的。我认为这个问题没有万无一失的通用解决方案,尽管我很乐意犯错!
d1
d2
setTime
当我不得不实现一般的深度复制时,我最终妥协了,假设我只需要复制一个普通的Object, Array, Date, String, Number, 或Boolean. 最后 3 种类型是不可变的,所以我可以执行浅拷贝而不用担心它会改变。我进一步假设包含在Object或Array也将是该列表中 6 种简单类型之一的任何元素。这可以通过如下代码来完成:
Array
String
Number
Boolean
function clone(obj) { var copy; // Handle the 3 simple types, and null or undefined if (null == obj || "object" != typeof obj) return obj; // Handle Date if (obj instanceof Date) { copy = new Date(); copy.setTime(obj.getTime()); return copy; } // Handle Array if (obj instanceof Array) { copy = []; for (var i = 0, len = obj.length; i < len; i++) { copy[i] = clone(obj[i]); } return copy; } // Handle Object if (obj instanceof Object) { copy = {}; for (var attr in obj) { if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]); } return copy; } throw new Error("Unable to copy obj! Its type isn't supported."); }
只要对象和数组中的数据形成树形结构,上述函数就可以充分适用于我提到的 6 种简单类型。也就是说,对象中对相同数据的引用不超过一个。例如:
// This would be cloneable: var tree = { "left" : { "left" : null, "right" : null, "data" : 3 }, "right" : null, "data" : 8 }; // This would kind-of work, but you would get 2 copies of the // inner node instead of 2 references to the same copy var directedAcylicGraph = { "left" : { "left" : null, "right" : null, "data" : 3 }, "data" : 8 }; directedAcyclicGraph["right"] = directedAcyclicGraph["left"]; // Cloning this would cause a stack overflow due to infinite recursion: var cyclicGraph = { "left" : { "left" : null, "right" : null, "data" : 3 }, "data" : 8 }; cyclicGraph["right"] = cyclicGraph;
它不能处理任何 JavaScript 对象,但它可能足以满足许多目的,只要你不认为它只适用于你扔给它的任何东西。
如果你不在Date你的对象中使用 s、functions、undefined、regExp 或 Infinity,一个非常简单的方法是JSON.parse(JSON.stringify(object)):
JSON.parse(JSON.stringify(object))
const a = { string: 'string', number: 123, bool: false, nul: null, date: new Date(), // stringified undef: undefined, // lost inf: Infinity, // forced to 'null' } console.log(a); console.log(typeof a.date); // Date object const clone = JSON.parse(JSON.stringify(a)); console.log(clone); console.log(typeof clone.date); // result of .toISOString()
这适用于包含对象、数组、字符串、布尔值和数字的所有类型的对象。