JavaScript深拷贝与浅拷贝实现原理详解
本文详解JavaScript中基本类型与引用类型的拷贝机制。通过Object.assign、拓展运算符及Object.create实现浅拷贝,分析JSON.stringify的局限性。重点讲解利用递归遍历对象属性实现深拷贝的原理,并对比各方法的适用场景与注意事项。
讨论数据拷贝机制时,必须首先明确数据类型的基础分类。在JavaScript语言规范中,数据类型被划分为基本类型与引用类型两大类。
基本类型具体包括:number、boolean、string、symbol、bigint、undefined以及null。
引用类型则主要指:
object对象类型,以及Array、RegExp、String、Map、Set等标准内置对象。
一. 基本类型数据拷贝
由于基本类型数据属于值类型,其数据直接存储在栈内存中。因此,每次进行赋值操作时,实际上都是执行了一次完整的数据复制过程。
var a = 12;
var b = a;二. 引用类型数据拷贝
1、浅拷贝
浅拷贝仅复制对象的第一层数据。对于更深层次的引用类型值,浅拷贝仅复制其内存引用地址,而非实际内容。常见的实现方式包括:
1.Object.assign() 方法和ES6的拓展运算符。
在实际开发中,通常使用Object.assign()方法来实现浅拷贝。该方法的作用是将一个或多个源对象中所有可枚举属性的值复制到目标对象,并返回修改后的目标对象。
let aa = {
a: undefined,
func: function(){console.log(1)},
b:2,
c: {x: 'xxx', xx: undefined},
d: null,
e: BigInt(100),
f: Symbol('s')
}
let bb = Object.assign({}, aa) // 或者 let bb = {...aa}
aa.c.x = 111
console.log(bb)
// 第一层拷贝,遇到引用类型的值就会只拷贝引用
// {
// a: undefined,
// func: [Function: func],
// b: 2,
// c: { x: 111, xx: undefined },
// d: null,
// e: 100n,
// f: Symbol(s)
// }2.Object.create
Object.create()方法用于创建一个新对象,并利用现有对象作为新创建对象的__proto__(原型)。该方法接收两个参数:第一个参数指定新创建对象的__proto__,第二个参数是可选的属性列表。
let aa = {
a: undefined,
func: function(){console.log(1)},
b:2,
c: {x: 'xxx', xx: undefined},
}
let bb = Object.create(aa, Object.getOwnPropertyDescriptors(aa))
aa.c.x = 111
console.log(bb)
// 第一层拷贝,遇到引用类型的值就会只拷贝引用
// {
// a: undefined,
// func: [Function: func],
// b: 2,
// c: { x: 111, xx: undefined },
// }2、深拷贝
在对对象进行拷贝操作时,为了避免修改源数据造成意外影响,必须采用深拷贝策略。
常见的实现方式包括:
1、通过JSON.stringify()方法。
var a = {a:1, b: 2}
var b = JSON.stringify(a);
a.a = 'a'
console.log(a, b) // { a: 'a', b: 2 } {"a":1,"b":2}使用JSON.stringify()进行深拷贝存在明显弊端:它会忽略值为function、undefined或symbol的属性;此外,在序列化BigInt类型数据时会抛出语法错误:TypeError: Do not know how to serialize a BigInt。
// 序列化function, undefind, symbol,忽略--------------------------------------------------------
var obj = {
a:function(){},
b: undefined,
c: null,
d: Symbol('s'),
}
var objCopyed = JSON.stringify(obj);
console.log("a:", a)
// obj: { a: [Function: a], b: undefined, c: null, d: Symbol(s) }
console.log("objCopyed:", objCopyed)
// objCopyed: {"c":null}
// 序列化bigint抛出错误--------------------------------------------------------
var obj = {
a: 1,
e: BigInt(9007199254740991)
}
var objCopyed = JSON.stringify(obj); // TypeError: Do not know how to serialize a BigInt2、递归实现
const deepCopy = (obj) => {
// 优化 把值类型复制方放到这里可以少一次deepCopy调用
// if(!obj || typeof obj !== 'object') throw new Error("请传入非空对象")
if(!obj || typeof obj !== 'object') return obj
let result = {}
if (Object.prototype.toString.call(obj).indexOf('Array') > 0) {
result = []
}
// 另一种循环方式
// for (let key in obj) {
// if (obj.hasOwnProperty(key)) {
// result[key] = deepClone(obj[key])
// }
// }
Object.keys(obj).forEach(key => {
// 优化 把值类型复制方放到这里可以少一次deepCopy调用
// if (obj[key] && typeof obj[key] === 'object') {
// result[key] = deepCopy(obj[key])
// }else{
// result[key] = obj[key]
// }
result[key] = deepCopy(obj[key])
});
return result
}
let aa = {
a: undefined,
func: function(){console.log(1)},
b:2,
c: {x: 'xxx', xx: undefined},
d: null,
e: BigInt(100),
f: Symbol('s')
}
let bb = deepCopy(aa)
aa.c.x = 123
aa.func = {}
console.log("aa", aa)
console.log("bb", bb)
// aa {
// a: undefined,
// func: {},
// b: 2,
// c: { x: 123, xx: undefined },
// d: null,
// e: 100n,
// f: Symbol(s)
// }
// bb {
// a: undefined,
// func: [Function: func],
// b: 2,
// c: { x: 'xxx', xx: undefined },
// d: null,
// e: 100n,
// f: Symbol(s)
// }手写深拷贝等工具方法在实际生产环境中使用频率极低(约99.99%的情况),因为开发者通常直接使用lodash、underscore等成熟且便捷的工具库。
至此,关于JS实现深拷贝和浅拷贝的方式详解的文章就介绍到这了,更多相关JS深拷贝 浅拷贝内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!
ES6模块化中js文件怎么用import和export交互
2026-11-05 17:00:48
JS读取本地文件时报错或无响应的常见原因
2026-11-05 16:32:31
let、const与var的区别:JavaScript变量声明最佳实践
2026-11-05 14:16:38
JS中直接定义静态二维数组的字面量写法
2026-11-05 14:16:08
JS 怎么查找字符串中所有匹配项的位置
2026-11-05 14:15:53
