JavaScript对象深度克隆的高效方法 技术背景 在JavaScript开发中,经常会遇到需要复制对象的场景。简单的赋值操作只会创建对象的引用,而不是对象的副本,这意味着修改副本会影响原始对象。为了创建一个独立于原始对象的副本,就需要进行对象克隆。浅克隆只复制对象的一层属性,而深度克隆会递归地复制对象的所有属性,包括嵌套对象,确保副本和原始对象完全独立。然而,JavaScript本身并没有提供直接的深度克隆方法,因此需要开发者自己实现或借助第三方库来完成。
实现步骤 1. 使用structuredClone
函数(现代浏览器和Node.js >= 17) 1 2 3 4 const original = { name : 'John' , age : 30 };const clone = structuredClone (original);console .log (clone);
注意事项:
函数对象不能被structuredClone
复制,尝试复制会抛出DataCloneError
异常。 克隆DOM节点同样会抛出DataCloneError
异常。 某些对象属性不会被保留,如RegExp
对象的lastIndex
属性、属性描述符、setter、getter等。 2. 使用JSON.parse(JSON.stringify())
1 2 3 const original = { name : 'John' , age : 30 };const clone = JSON .parse (JSON .stringify (original));console .log (clone);
注意事项:
如果对象中包含Date
、函数、undefined
、Infinity
、RegExps
、Maps
、Sets
、Blobs
、FileLists
、ImageDatas
、稀疏数组、类型化数组等复杂类型,这些类型会丢失或被转换。例如,JSON.stringify(new Date())
返回日期的字符串表示,JSON.parse()
不会将其转换回Date
对象。 3. 使用第三方库 Lodash的cloneDeep
1 2 3 4 const _ = require ('lodash' );const original = { name : 'John' , age : 30 };const clone = _.cloneDeep (original);console .log (clone);
Ramda的clone
1 2 3 4 const R = require ('ramda' );const original = { name : 'John' , age : 30 };const clone = R.clone (original);console .log (clone);
4. 自定义递归克隆函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 function clone (obj ) { if (obj === null || typeof (obj) !== 'object' || 'isActiveClone' in obj) return obj; if (obj instanceof Date ) var temp = new obj.constructor ( ); 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; }const original = { name : 'John' , age : 30 };const clone = clone (original);console .log (clone);
核心代码 自定义递归克隆函数(处理循环引用) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 function deepCopy (src, _visited, _copiesVisited ) { if (src === null || typeof (src) !== 'object' ){ return src; } if (typeof src.clone == 'function' ){ return src.clone (true ); } if (src instanceof Date ){ return new Date (src.getTime ()); } if (src instanceof RegExp ){ return new RegExp (src); } if (src.nodeType && typeof src.cloneNode == 'function' ){ return src.cloneNode (true ); } if (_visited === undefined ){ _visited = []; _copiesVisited = []; } var i, len = _visited.length ; for (i = 0 ; i < len; i++) { if (src === _visited[i]) { return _copiesVisited[i]; } } if (Object .prototype .toString .call (src) == '[object Array]' ) { var ret = src.slice (); _visited.push (src); _copiesVisited.push (ret); var i = ret.length ; while (i--) { ret[i] = deepCopy (ret[i], _visited, _copiesVisited); } return ret; } var proto = (Object .getPrototypeOf ? Object .getPrototypeOf (src): src.__proto__ ); if (!proto) { proto = src.constructor .prototype ; } var dest = Object .create (proto); _visited.push (src); _copiesVisited.push (dest); for (var key in src) { dest[key] = deepCopy (src[key], _visited, _copiesVisited); } return dest; }const original = { name : 'John' , age : 30 };const clone = deepCopy (original);console .log (clone);
最佳实践 选择合适的方法 :根据对象的类型和复杂度选择合适的克隆方法。如果对象只包含简单的JSON数据,JSON.parse(JSON.stringify())
是一个简单有效的方法;如果对象包含复杂类型或循环引用,建议使用第三方库或自定义递归克隆函数。性能优化 :在性能敏感的场景中,避免使用JSON.parse(JSON.stringify())
,因为它的性能相对较低。可以使用自定义递归克隆函数或手动内联克隆过程来提高性能。测试和验证 :在使用克隆方法之前,进行充分的测试和验证,确保克隆的对象和原始对象是独立的,并且没有丢失重要的属性或方法。常见问题 1. JSON.parse(JSON.stringify())
丢失复杂类型 如前所述,JSON.parse(JSON.stringify())
会丢失Date
、函数、undefined
等复杂类型。可以通过自定义解析函数来处理,例如:
1 2 3 4 5 6 7 8 9 10 11 12 function clone (obj ) { var regExp = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/ ; return JSON .parse (JSON .stringify (obj), function (k, v ) { if (typeof v === 'string' && regExp.test (v)) return new Date (v) return v; }) }const original = { date : new Date () };const cloneObj = clone (original);console .log (cloneObj);
2. 循环引用导致栈溢出 如果对象存在循环引用,递归克隆函数会导致栈溢出。可以通过记录已经克隆的对象来避免,如上述的deepCopy
函数。
3. 性能问题 某些克隆方法(如JSON.parse(JSON.stringify())
)性能较低,在处理大型对象或频繁克隆时会影响性能。可以通过优化算法或使用更高效的克隆方法来解决。