js的拷贝
前言(引言):
在 JavaScript 开发中,数据拷贝是每个开发者都会遇到的基础操作,但也是隐藏陷阱最多的领域之一。当你把一个对象赋值给另一个变量时,是创建了独立副本还是共享了数据?为什么修改新数组会影响原数组?如何真正"克隆"一个包含嵌套结构的复杂对象?
这些问题背后,涉及 JavaScript 的核心特性——按值传递与按引用传递的差异。浅拷贝只复制表层,深拷贝才真正"斩断"所有引用关联。错误的拷贝方式可能导致数据污染、状态混乱甚至内存泄漏。
本文将系统剖析 JavaScript 中各种拷贝方法的原理、性能差异和适用场景
V8 是如何储存数据的
1. 栈内存
- 储存基本数据类型(
Undefined、Null、Boolean、Number、String、BigInt、Symbol)和引用数据类型的地址
2. 堆内存
- 储存引用数据类型(
Object、Function、Array、Date)
拷贝
- 复刻一个对象和原对象长的一模一样
浅拷贝
浅拷贝只拷贝第一层的数据,若数据中存在引用类型,修改引用类型的数据,拷贝的数据也会改变
1.Object.create(obj)
let obj = {
a: 1
}
let obj2 = Object.create(obj) //创建一个新对象,让新对象的隐式原型指向obj
console.log(obj2.a) // 1
2.[].concat(arr)
let arr1 = [1,2,3]
let arr2 = [4,5]
let arr3 = [].concat(arr1)
console.log(arr1.concat(arr2)); //[1,2,3,4,5] 把arr2中的元素拼接到arr1中
console.log(arr1); //[1,2,3]
console.log(arr3); //[1,2,3]
- 注意:用concat拼接是先创建一个新数组,然后将拼接好的数组放到新数组上去,不会影响原数组
3.数组的解构
let arr1 = [1,2,3]
let arr2 = [...arr1] //数组的解构
console.log(arr2); //[1,2,3]
4.arr.slice(0,arr.length)
let arr1 = ['a','b','c']
arr1.splice(1,1)
console.log(arr1); //['a','c']
let arr2 = ['a','b','c']
let arr3 = arr2.slice(0,arr2.length)
console.log(arr3); //['a','b','c']
注意:
-
arr.splice()会影响原数组,arr.slice(0,arr.length)不会影响原数组 -
arr.slice(0,arr.length)是左闭右开区间 -
arr.slice(0,arr.length)不能增加元素 -
arr.splice(x,y,z)x为从第x元素开始,y表示删除y个元素,z表示增加元素z
5.Object.assign(obj,{})
let obj = {
name : 'CJ',
age : 18
}
let girlfriend = {
girlname : 'ZHT'
}
let newObj = Object.assign(obj,{})
console.log(obj); //{ name: 'CJ', age: 18 }
注意:
-
Object.assign()对象的拼接,不分前后顺序
-
会改变原对象
6.arr.toReversed().reverse()
let arr = [1,2,3]
let newArr = arr.toReversed().reverse()
console.log(newArr);
console.log(arr);
注意:
-
toReversed()和reverse()都是将数组反转
-
toReversed()不改变原数组,reverse()改变原数组
eg:
let obj = {
name: 'CJ',
age: 22,
like: {
a: '唱',
b: '跳',
c: 'rap'
}
}
let newObj = Object.assign({},obj)
obj.like.a = '篮球'
console.log(newObj);
❤如何手写一个浅拷贝❤
Object.prototype.d = 4
let obj = {
name: 'CJ',
age: 22,
like: {
a: '唱',
b: '跳',
c: 'rap'
}
}
//构造一个拷贝函数
function shallowCopy(obj) {
let newObj = {}
for (let key in obj) {
if(obj.hasOwnProperty(key)){
newObj[key] = obj[key]
}
}
return newObj
}
console.log(shallowCopy(obj));
注意:
Object.prototype.d = 4 是在Object的原型上增加一个属性,由于for in 的缘故会把Object的原型上的属性也给拷贝过来,那么就会与我们所需要拷贝的不符,那么我们用obj.hasOwnProperty(key)方法判断是不是我们所要拷贝的对象里面的属性
深拷贝
层层拷贝,新对象不受原对象的影响
1.JSON.parse(JSON.stringify(obj))
let obj = {
name: 'CJ',
age: 22,
like: {
a: '唱',
b: '跳',
c: 'rap'
}
}
let newObj = JSON.parse(JSON.stringify(obj))
console.log(newObj);
注意:
无法识别 bigint 类型,无法处理 undefined,symbol,function
无法处理循环引用
2.structuredClone()
let obj = {
name: 'CJ',
age: 22,
like: {
a: '唱',
b: '跳',
c: 'rap'
}
}
let newObj = structuredClone(obj)
❤如何手写一个深拷贝❤
let obj = {
name: 'CJ',
age: 22,
like: {
a: '唱',
b: '跳',
c: 'rap'
}
}
function deepCopy(obj) {
let newObj = {}
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
//先判断 obj[key] 值的类型,如果是原始类型,直接赋值,如果是引用类型,进行递归
if (typeof obj[key] === 'object' && obj[key] !== null) {
newObj[key] = deepCopy(obj[key])
} else
newObj[key] = obj[key]
}
}
return newObj
}
console.log(deepCopy(obj));
- 深拷贝比浅拷贝多一个判断obj[key] 值的类型,如果是原始类型,直接赋值,如果是引用类型,则进行一次递归