深拷贝之 structuredClone ()
引言
**
在 JavaScript 开发中,数据的复制是一个常见的操作。其中,深拷贝(Deep Copy)尤为重要,它可以创建一个与原始对象完全独立的副本,确保对副本的修改不会影响到原始对象。这在处理复杂数据结构,如嵌套对象和数组时,显得尤为关键。比如在状态管理库(如 Redux)中,要求在 reducer 中返回新的对象而不是直接修改原来的对象,此时深拷贝就能派上用场,避免因引用传递导致的意外数据修改。
传统实现深拷贝的方法有JSON.parse(JSON.stringify())、递归函数实现以及借助第三方库(如 Lodash 的_.cloneDeep())等。然而,JSON.parse(JSON.stringify())存在局限性,它无法处理函数、正则表达式、Date对象等非标准 JSON 数据类型,也不支持循环引用;递归实现虽然灵活,但需要手动处理复杂的边界情况,如循环引用,否则容易导致栈溢出;第三方库虽然功能强大,但会增加项目的依赖和打包体积。
而structuredClone()方法的出现,为深拷贝提供了一种更高效、更安全的原生解决方案。它不仅能够处理复杂的数据结构,还支持更多的数据类型,并且能够正确处理循环引用,大大简化了深拷贝的操作。接下来,我们就深入探讨structuredClone()方法的使用。
structuredClone () 初相识
定义与功能
structuredClone()是 JavaScript 提供的一个全局方法,用于对给定的值进行深拷贝。它使用结构化克隆算法,能够递归地复制复杂的数据结构,包括对象、数组、Map、Set 等,并且可以正确处理循环引用 ,确保克隆后的对象与原始对象在结构和值上完全一致,同时保持两者相互独立,对克隆对象的修改不会影响到原始对象。
语法与参数
structuredClone()的基本语法如下:
structuredClone(value);structuredClone(value, { transfer });
其中,value是必需参数,表示要被克隆的对象,可以是任何结构化克隆支持的类型,包括但不限于普通对象、数组、Date对象、RegExp对象、Map、Set、ArrayBuffer、TypedArrays、Blob、File、ImageData、MessagePort,以及null、undefined、NaN、Infinity、-Infinity等特殊值。
transfer是一个可选参数,它是一个可转移对象的数组。数组中的对象将被移动而不是克隆到返回的对象上。可转移对象与原始对象分离并附加到新对象,它们将无法在原始对象中被访问。这在一些场景中非常有用,比如在保存缓冲区之前先异步校验里面的数据,为了避免缓冲区在保存之前有其他修改,可以先克隆这个缓冲区然后校验数据,为了防止意外的错误引用,在传输数据时,任何修改缓冲区的尝试都会失败。例如:
const buffer = new ArrayBuffer(16);const transferred = structuredClone(buffer, { transfer: [buffer] });console.log(buffer.byteLength); // 0,原始buffer被清空
返回值与异常处理
structuredClone()方法返回原始值value的深拷贝。当输入值的任一部分不可序列化时,会抛出DataCloneError异常 。例如,当尝试克隆包含函数、symbol、WeakMap、WeakSet、HTMLElement等不支持类型的对象时,就会触发该异常:
const obj = {func: function() { return "I'm a function"; }, // 函数symbol: Symbol('uniqueSymbol'), // SymbolweakMap: new WeakMap(), // WeakMapweakSet: new WeakSet(), // WeakSetelement: document.createElement('div') // HTMLElement};try {const clonedObj = structuredClone(obj);console.log(clonedObj);} catch (error) {console.error('Error:', error); // 抛出 DataCloneError: Failed to execute 'structuredClone'}
了解了structuredClone()的基本概念后,接下来通过实际的示例来深入学习它的使用。
深入探究 structuredClone ()
支持的数据类型
structuredClone()方法支持多种数据类型的克隆,这使得它在处理复杂数据结构时非常强大。以下是一些常见的支持类型及示例:
- 基本类型:包括string、number、boolean、null、undefined、NaN、Infinity和-Infinity。这些类型的克隆非常直接,克隆后的副本与原始值完全相同:
const original = {str: 'Hello, structuredClone!',num: 42,bool: true,nul: null,und: undefined,nan: NaN,inf: Infinity,negInf: -Infinity};const clone = structuredClone(original);console.log(clone.str === original.str); // trueconsole.log(clone.num === original.num); // trueconsole.log(clone.bool === original.bool); // trueconsole.log(clone.nul === original.nul); // trueconsole.log(clone.und === original.und); // trueconsole.log(clone.nan !== original.nan); // true,NaN 与任何值都不相等,包括它自身console.log(clone.inf === original.inf); // trueconsole.log(clone.negInf === original.negInf); // true
- 普通对象:对于普通对象,structuredClone()会递归地克隆其属性,确保所有嵌套对象也被正确克隆:
const original = {name: 'John',age: 30,address: {street: '123 Main St',city: 'Anytown',state: 'CA'}};const clone = structuredClone(original);clone.address.city = 'Newcity';console.log(original.address.city); // Anytownconsole.log(clone.address.city); // Newcity
- 数组:无论是一维数组还是多维数组,structuredClone()都能准确地克隆:
const original = [1, 2, [3, 4], { key: 'value' }];const clone = structuredClone(original);clone[2][0] = 99;clone[3].key = 'new value';console.log(original[2][0]); // 3console.log(original[3].key); // valueconsole.log(clone[2][0]); // 99console.log(clone[3].key); // new value
- Date 对象:structuredClone()会克隆Date对象,而不是像JSON.stringify()那样将其转换为字符串 。克隆后的Date对象与原始对象具有相同的时间值:
const original = new Date();const clone = structuredClone(original);console.log(clone instanceof Date); // trueconsole.log(clone.getTime() === original.getTime()); // true
- RegExp 对象:RegExp对象也能被正确克隆,克隆后的对象保持与原始对象相同的正则表达式模式和标志 :
const original = /abc/gmi;const clone = structuredClone(original);console.log(clone instanceof RegExp); // trueconsole.log(clone.source === original.source); // trueconsole.log(clone.flags === original.flags); // true
- Map 和 Set 对象:Map和Set对象的克隆会保留其所有键值对(对于Map)或成员(对于Set),并且克隆后的对象与原始对象相互独立:
const originalMap = new Map([['key1', 'value1'], ['key2', 'value2']]);const cloneMap = structuredClone(originalMap);cloneMap.set('key1', 'new value');console.log(originalMap.get('key1')); // value1console.log(cloneMap.get('key1')); // new valueconst originalSet = new Set([1, 2, 3]);const cloneSet = structuredClone(originalSet);cloneSet.add(4);console.log(originalSet.has(4)); // falseconsole.log(cloneSet.has(4)); // true
- ArrayBuffer 和 TypedArrays:ArrayBuffer和各种TypedArrays(如Uint8Array、Int16Array等)也在支持范围内。克隆后的ArrayBuffer和TypedArrays与原始对象具有相同的二进制数据:
const originalBuffer = new Uint8Array([1, 2, 3]).buffer;const cloneBuffer = structuredClone(originalBuffer);const originalTypedArray = new Uint8Array(originalBuffer);const cloneTypedArray = new Uint8Array(cloneBuffer);cloneTypedArray[0] = 99;console.log(originalTypedArray[0]); // 1console.log(cloneTypedArray[0]); // 99
- Blob、File 和 ImageData 对象:这些类型也可以通过structuredClone()进行克隆,保持其数据和属性的完整性:
// Blob对象const originalBlob = new Blob(['Hello, world!'], { type: 'text/plain' });const cloneBlob = structuredClone(originalBlob);console.log(cloneBlob instanceof Blob); // true// File对象const originalFile = new File(['file content'], 'filename.txt', { type: 'text/plain' });const cloneFile = structuredClone(originalFile);console.log(cloneFile instanceof File); // true// ImageData对象const canvas = document.createElement('canvas');const context = canvas.getContext('2d');const originalImageData = context.createImageData(100, 100);const cloneImageData = structuredClone(originalImageData);console.log(cloneImageData instanceof ImageData); // true
不支持的数据类型
尽管structuredClone()功能强大,但它也存在一些局限性,某些数据类型是不支持克隆的。当尝试克隆这些不支持的类型时,会抛出DataCloneError异常。以下是一些不支持的数据类型及示例:
- 函数:JavaScript 函数是不可序列化的,因此structuredClone()无法克隆函数。如果对象中包含函数属性,克隆操作将失败:
const original = {func: function() {return 'This is a function';}};try {const clone = structuredClone(original);} catch (error) {console.error('Error:', error); // 抛出 DataCloneError: Failed to execute'structuredClone'}
- symbol:symbol类型用于创建唯一的标识符,它也不支持克隆。如果对象中包含symbol属性,克隆时会抛出错误:
const sym = Symbol('unique');const original = {[sym]: 'Symbol value'};try {const clone = structuredClone(original);} catch (error) {console.error('Error:', error); // 抛出 DataCloneError: Failed to execute'structuredClone'}
- WeakMap 和 WeakSet:WeakMap和WeakSet的设计目的是为了存储弱引用,它们的键或成员是弱引用的,不会阻止对象被垃圾回收。由于其特殊的性质,structuredClone()不支持克隆WeakMap和WeakSet:
const originalWeakMap = new WeakMap();const originalWeakSet = new WeakSet();const original = {weakMap: originalWeakMap,weakSet: originalWeakSet};try {const clone = structuredClone(original);} catch (error) {console.error('Error:', error); // 抛出 DataCloneError: Failed to execute'structuredClone'}
- DOM 元素(如 HTMLElement):DOM 元素与浏览器的文档对象模型紧密相关,包含许多无法轻易序列化的内部状态和引用,因此structuredClone()不支持克隆 DOM 元素:
const div = document.createElement('div');const original = {element: div};try {const clone = structuredClone(original);} catch (error) {console.error('Error:', error); // 抛出 DataCloneError: Failed to execute'structuredClone'}
处理循环引用
在处理复杂数据结构时,循环引用是一个常见的问题。循环引用是指对象之间相互引用,形成一个闭环。例如,对象A包含一个指向对象B的属性,而对象B又包含一个指向对象A的属性。传统的深拷贝方法(如JSON.parse(JSON.stringify()))在遇到循环引用时会抛出错误,因为 JSON.stringify () 无法处理循环引用,会导致递归进入无限循环 。而structuredClone()方法能够正确处理循环引用,确保克隆过程不会陷入无限循环,并创建出一个包含正确循环引用结构的克隆对象。以下是一个示例:
let obj1 = {};let obj2 = {ref: obj1};obj1.ref = obj2;const clone = structuredClone(obj1);console.log(clone.ref === clone.ref.ref.ref); // true,说明循环引用被正确处理在这个示例中,obj1和obj2相互引用,形成了一个循环引用。通过structuredClone()方法克隆obj1后,克隆对象clone中也包含了正确的循环引用结构,clone.ref指向clone.ref.ref.ref,这表明structuredClone()成功地处理了循环引用,使得克隆对象与原始对象在结构上保持一致,同时避免了因循环引用导致的克隆失败问题。
structuredClone () 与其他克隆方法对比
与 JSON.parse (JSON.stringify ()) 对比
在 JavaScript 中,JSON.parse(JSON.stringify()) 是一种常用的实现深拷贝的方法,但它与 structuredClone() 存在诸多差异。
在支持的数据类型方面,JSON.parse(JSON.stringify()) 仅支持基本数据类型(如数字、字符串、布尔值)、普通对象和数组 。对于 Date 对象,它会将其转换为字符串,在反序列化后不再是 Date 对象,而是普通字符串;Map、Set、RegExp 对象会被转换为空对象;函数、undefined、symbol、Infinity、NaN 以及循环引用等都无法正确处理 。而 structuredClone() 支持的数据类型更为广泛,除了基本数据类型和普通对象、数组外,还支持 Date、RegExp、Map、Set、ArrayBuffer、TypedArrays、Blob、File、ImageData、MessagePort 等,并且能正确处理循环引用 。例如:
// JSON.parse(JSON.stringify()) 处理不支持的类型const original1 = {date: new Date(),map: new Map([['key1', 'value1']]),func: function() { return 'function' }};const jsonClone1 = JSON.parse(JSON.stringify(original1));console.log(jsonClone1.date instanceof Date); // false,Date 被转为字符串console.log(jsonClone1.map instanceof Map); // false,Map 被转为空对象console.log(jsonClone1.func); // undefined,函数丢失// structuredClone() 处理支持的类型const original2 = {date: new Date(),map: new Map([['key1', 'value1']]),arrayBuffer: new Uint8Array([1, 2, 3]).buffer};const structuredClone2 = structuredClone(original2);console.log(structuredClone2.date instanceof Date); // trueconsole.log(structuredClone2.map instanceof Map); // trueconsole.log(structuredClone2.arrayBuffer instanceof ArrayBuffer); // true
在循环引用处理上,JSON.parse(JSON.stringify()) 无法处理对象中的循环引用,当对象存在循环引用时,调用 JSON.stringify() 会抛出 TypeError: Converting circular structure to JSON 错误 ,导致克隆失败。而 structuredClone() 可以正确处理循环引用,确保克隆后的对象包含正确的循环引用结构,不会陷入无限循环 。比如:
// JSON.parse(JSON.stringify()) 处理循环引用let obj1 = {};let obj2 = { ref: obj1 };obj1.ref = obj2;try {const jsonClone = JSON.parse(JSON.stringify(obj1));} catch (error) {console.error('JSON.parse(JSON.stringify())循环引用错误:', error); // 抛出错误}// structuredClone() 处理循环引用let obj3 = {};let obj4 = { ref: obj3 };obj3.ref = obj4;const structuredClone3 = structuredClone(obj3);console.log(structuredClone3.ref === structuredClone3.ref.ref); // true,循环引用处理正确
性能方面,JSON.parse(JSON.stringify()) 在处理简单的、JSON 兼容的数据结构时性能尚可,但在处理复杂对象或包含大量非 JSON 兼容类型的数据时,由于需要进行序列化和反序列化操作,效率较低 。structuredClone() 是为深度克隆设计的原生方法,内部针对复杂场景进行了优化,通常在处理复杂对象时性能更优 。通过以下测试可以明显看出两者的性能差异:
// 性能测试
const largeArray = new Array(10000).fill({ key: 'value' });
console.time('JSON.parse(JSON.stringify())');
const jsonClone = JSON.parse(JSON.stringify(largeArray));console.timeEnd('JSON.parse(JSON.stringify())');console.time('structuredClone');const structuredClone4 = structuredClone(largeArray);console.timeEnd('structuredClone');
在浏览器兼容性上,JSON.parse(JSON.stringify()) 在现代浏览器和较旧的浏览器中都有广泛支持 。而 structuredClone() 是一种较新的 API,在某些较旧的浏览器中不被支持 ,使用时需要考虑兼容性问题,可以通过检测 typeof structuredClone === 'function' 来判断浏览器是否支持该方法,不支持时可采用其他替代方案。
与浅拷贝方法对比
在理解 structuredClone() 与浅拷贝方法的区别之前,先来明确浅拷贝和深拷贝的概念。浅拷贝是指创建一个新对象,这个对象具有原对象属性的精确副本 。对于基本数据类型(如字符串、数字等),在浅拷贝过程中它们是通过值传递的,修改值并不会影响原对象;但如果这些属性是引用类型(如对象、数组等),浅拷贝只会复制它们的引用,而不会复制它们的内容 ,浅拷贝后的新对象和原对象中的引用类型属性仍然指向相同的内存地址,修改其中一个的引用类型数据,会影响另一个 。例如,使用 Object.assign() 方法进行浅拷贝:
const original = {name: 'John',age: 30,hobbies: ['reading', 'running']
};const shallowCopy = Object.assign({}, original);shallowCopy.hobbies.push('swimming');console.log(original.hobbies); // ['reading', 'running','swimming'],原对象的hobbies被修改
深拷贝则是将一个对象的所有属性都完整地复制到另一个对象中,包括嵌套的对象或数组 。深拷贝与浅拷贝不同,它会递归地复制对象的所有层次,确保原始对象和新对象完全独立,任何一方的修改不会影响另一方 。structuredClone() 就是一种深拷贝方法,它能递归地克隆复杂的数据结构,包括对象、数组、Map、Set 等,并且保持克隆对象与原始对象相互独立 。比如:
const original = {name: 'John',age: 30,hobbies: ['reading', 'running']};const deepCopy = structuredClone(original);deepCopy.hobbies.push('swimming');console.log(original.hobbies); // ['reading', 'running'],原对象的hobbies不受影响
从实现方式上看,浅拷贝方法相对简单,如 Object.assign()、扩展运算符(...)、数组的 slice() 方法等 。而 structuredClone() 作为深拷贝方法,使用结构化克隆算法,内部实现更为复杂,以确保能够正确处理各种复杂的数据结构和循环引用 。
在适用场景上,浅拷贝适用于复制简单的数据结构,如基本数据类型或简单的对象和数组,或者在需要传递引用而不是副本以减少内存开销的场景 。深拷贝则适用于需要保留原始数据的完整性,确保修改副本不会影响原始数据,以及处理含有循环引用的对象,避免在拷贝过程中出现无限循环的情况 。例如,在状态管理中,为了确保状态的不可变性,通常需要使用深拷贝来创建新的状态对象,而不是直接修改原始状态,此时 structuredClone() 就非常适用;而在一些简单的数据展示场景中,如果数据结构较为简单且不需要深度复制,浅拷贝方法可以提高效率。
使用示例与应用场景
基本使用示例
在实际开发中,structuredClone()方法的使用非常直观。以下是一些基本的使用示例,展示了如何对普通对象和数组进行克隆:
// 克隆普通对象const originalObject = {name: 'John',age: 30,hobbies: ['reading', 'traveling']};const clonedObject = structuredClone(originalObject);clonedObject.age = 31;clonedObject.hobbies.push('swimming');console.log(originalObject.age); // 30console.log(originalObject.hobbies); // ['reading', 'traveling']console.log(clonedObject.age); // 31console.log(clonedObject.hobbies); // ['reading', 'traveling','swimming']// 克隆数组const originalArray = [1, 2, [3, 4], { key: 'value' }];const clonedArray = structuredClone(originalArray);clonedArray[2][0] = 99;clonedArray[3].key = 'new value';console.log(originalArray[2][0]); // 3console.log(originalArray[3].key); // valueconsole.log(clonedArray[2][0]); // 99console.log(clonedArray[3].key); // 'new value'
从这些示例中可以看出,structuredClone()方法成功地创建了原始对象和数组的深拷贝,对克隆对象的修改不会影响到原始对象。
复杂数据结构示例
当处理包含多种数据类型的复杂对象时,structuredClone()方法同样表现出色。以下是一个更复杂的示例:
const complexObject = {stringValue: 'Hello',numberValue: 42,booleanValue: true,nullValue: null,undefinedValue: undefined,dateValue: new Date(),regExpValue: /abc/gmi,mapValue: new Map([['key1', 'value1'], ['key2', 'value2']]),setValue: new Set([1, 2, 3]),arrayValue: [1, 2, new Date(), new Map([['subKey','subValue']])],nestedObject: {nestedString: 'Nested',nestedNumber: 100,nestedArray: [new Set([4, 5, 6]), new RegExp('def')]}};const clonedComplexObject = structuredClone(complexObject);// 修改克隆对象的属性clonedComplexObject.numberValue = 43;clonedComplexObject.mapValue.set('key1', 'new value');clonedComplexObject.arrayValue[2] = new Date('2024-01-01');clonedComplexObject.nestedObject.nestedArray[0].add(7);// 输出验证console.log(complexObject.numberValue); // 42console.log(complexObject.mapValue.get('key1')); // value1console.log(complexObject.arrayValue[2]); // 原日期console.log(complexObject.nestedObject.nestedArray[0].has(7)); // falseconsole.log(clonedComplexObject.numberValue); // 43console.log(clonedComplexObject.mapValue.get('key1')); // new valueconsole.log(clonedComplexObject.arrayValue[2]); // 新日期console.log(clonedComplexObject.nestedObject.nestedArray[0].has(7)); // true
在这个示例中,complexObject包含了多种数据类型,如字符串、数字、布尔值、null、undefined、Date对象、RegExp对象、Map、Set、数组以及嵌套对象。通过structuredClone()方法克隆后,对clonedComplexObject的修改没有影响到complexObject,充分展示了structuredClone()在处理复杂数据结构时的强大能力。
应用场景
structuredClone()方法在实际开发中有许多重要的应用场景,以下是一些常见的场景:
- Web Workers 通信:在 Web Workers 中,主线程和 Worker 线程之间需要进行数据传递 。structuredClone()方法可以确保传递的数据被正确克隆,避免因引用传递导致的数据共享问题,保证了线程之间数据的独立性和安全性。例如,在进行复杂的数据处理时,主线程可以将数据通过structuredClone()克隆后传递给 Worker 线程,Worker 线程在处理数据时不会影响到主线程的数据:
// 主线程const data = {numbers: [1, 2, 3, 4, 5],message: 'Process this data'};const worker = new Worker('worker.js');worker.postMessage(structuredClone(data));worker.onmessage = function (e) {console.log('Result from worker:', e.data);};// worker.jsself.onmessage = function (e) {const receivedData = e.data;// 处理数据const result = receivedData.numbers.reduce((acc, num) => acc + num, 0);self.postMessage(result);};
- IndexedDB 数据存储:IndexedDB 是一种用于在浏览器中存储大量结构化数据的 API 。在将数据存储到 IndexedDB 之前,使用structuredClone()方法可以确保存储的数据是独立的副本,避免存储过程中对原始数据的意外修改,同时也能正确处理复杂数据类型的存储。例如,存储用户的购物车数据,其中可能包含商品列表、价格、数量等多种数据类型:
const cartData = {items: [{ name: 'Product A', price: 10, quantity: 2 },{ name: 'Product B', price: 20, quantity: 1 }],totalPrice: 40,updatedAt: new Date()};const request = indexedDB.open('CartDB', 1);request.onsuccess = function (event) {const db = event.result;const transaction = db.transaction(['cart'], 'readwrite');const store = transaction.objectStore('cart');const clonedData = structuredClone(cartData);store.put(clonedData);};
- React 组件状态管理:在 React 应用中,状态管理是一个关键部分 。为了确保状态的不可变性,通常需要在更新状态时创建新的状态对象。structuredClone()方法可以方便地实现这一点,特别是在处理复杂的状态数据结构时,能够确保新状态与旧状态相互独立,避免因引用传递导致的状态更新异常。例如,在一个待办事项应用中,管理待办事项列表的状态:
import React, { useState } from'react';function TodoList() {const [todos, setTodos] = useState([{ id: 1, text: 'Learn structuredClone', completed: false },{ id: 2, text: 'Practice React', completed: false }]);const markTodoAsCompleted = (todoId) => {const clonedTodos = structuredClone(todos);const todoToUpdate = clonedTodos.find(todo => todo.id === todoId);if (todoToUpdate) {todoToUpdate.completed = true;}setTodos(clonedTodos);};return (<div><ul>{todos.map(todo => (<li key={todo.id}>{todo.text} - {todo.completed? 'Completed' : 'Not Completed'}<button onClick={() => markTodoAsCompleted(todo.id)}>Mark as Completed</button></li>))}</ul></div>);}export default TodoList;
在这个例子中,当用户点击 “Mark as Completed” 按钮时,markTodoAsCompleted函数使用structuredClone()克隆当前的todos状态,然后修改克隆后的状态,最后通过setTodos更新状态,确保了状态更新的正确性和不可变性。
注意事项与局限性
注意事项
在使用structuredClone()方法时,需要注意以下几点:首先,由于structuredClone()不支持函数、symbol、WeakMap、WeakSet、DOM 节点等类型的克隆,在进行克隆操作前,应确保被克隆的对象中不包含这些不支持的类型,或者在克隆之前对这些属性进行特殊处理,例如将函数属性替换为其他可克隆的数据表示形式,避免在克隆时抛出DataCloneError异常。
其次,structuredClone()方法返回的克隆对象虽然在结构和值上与原始对象相同,但它们是相互独立的对象,具有不同的内存地址。这意味着对克隆对象的修改不会影响原始对象,反之亦然。在进行数据操作时,要明确这一点,避免因误解而导致逻辑错误。
最后,当处理大数据量或嵌套层级过深的对象时,structuredClone()可能会产生性能损耗,因为它需要递归地复制整个对象结构。在这种情况下,可以考虑对数据进行优化,如减少不必要的嵌套层级,或者在性能要求较高的场景下,对性能进行测试和评估,确保不会对应用的整体性能产生负面影响。
局限性
尽管structuredClone()为深拷贝提供了强大的支持,但它也存在一些局限性:
- 函数克隆不支持:与JSON.parse(JSON.stringify())方法一样,structuredClone()无法克隆函数。在 JavaScript 中,函数包含了可执行的代码和作用域信息,这些信息难以以结构化的方式进行序列化和克隆。如果对象中包含函数属性,在使用structuredClone()时,函数属性将无法被正确克隆,这在一些需要保留函数逻辑的场景中可能会带来不便。例如,在克隆一个包含事件处理函数的对象时,克隆后的对象将丢失这些函数,导致相关功能无法正常运行。
- 原型链不克隆:structuredClone()克隆的对象会丢失原始对象的原型链。这意味着克隆后的对象的原型将为Object.prototype,而不是原始对象的构造函数的原型 。对于类的实例,克隆后将不再是原类的实例,无法访问原类定义的方法。比如,有一个自定义类User,包含greet方法,克隆User类的实例后,克隆对象将无法调用greet方法,因为它的原型已经改变。
class User {constructor(name) {this.name = name;}greet() {console.log(`Hello, ${this.name}`);}}const user = new User('John');const clonedUser = structuredClone(user);console.log(clonedUser instanceof User); // false// clonedUser.greet(); // 报错,clonedUser没有greet方法
- DOM 节点不支持:由于 DOM 节点与浏览器的文档对象模型紧密相关,包含了许多与浏览器环境相关的状态和引用,structuredClone()不支持克隆 DOM 节点 。如果对象中包含 DOM 节点属性,克隆操作将失败并抛出DataCloneError异常。这在处理与 DOM 相关的数据时需要特别注意,例如在克隆一个包含HTMLElement的配置对象时,需要将 DOM 节点相关的逻辑与数据克隆分开处理。
- 部分 Error 对象不支持:虽然structuredClone()可以处理部分Error对象,但并非所有Error对象都能被正确克隆。一些特殊的Error类型可能包含与运行时环境相关的不可序列化信息,导致克隆失败 。在捕获和处理错误时,如果需要克隆错误对象,要注意structuredClone()对Error对象的支持情况。
总结
structuredClone()作为 JavaScript 原生的深拷贝方法,以其对复杂数据类型的广泛支持、对循环引用的有效处理以及高效的性能,在众多深拷贝解决方案中脱颖而出,为开发者提供了极大的便利。在 Web Workers 通信、IndexedDB 数据存储、React 组件状态管理等场景中,structuredClone()都能发挥重要作用,确保数据的独立性和安全性。
尽管structuredClone()存在对函数、symbol、WeakMap、WeakSet、DOM 节点等类型克隆的限制,以及原型链不克隆、部分Error对象不支持等局限性,但在大多数实际开发场景中,它仍然是深拷贝的首选方法。当遇到不支持的类型时,开发者可以结合其他技术或方法进行处理,以满足项目的需求。