引言
当谈到JavaScript编程和数据处理时,拷贝是一个至关重要的主题。在JavaScript中,拷贝涉及到复制和传递数据,以确保程序能够顺利运行并且避免意外的副作用。无论是针对对象、数组还是其他数据结构,深刻理解如何进行有效的拷贝操作都是每个开发者必须具备的技能。让我们一起深入探讨JavaScript中拷贝的重要性以及实现高效拷贝的方法。
拷贝的引入
提到拷贝,我们首先想起的是日常生活中将文件拷贝到另一个文件中,那么我们就得到了两个不同的相同类型的文件。那么在JavaScript中的拷贝又是怎么样的呢?我们先来看一段简单的代码:
js
let a = 10
let b = a
a = 20
console.log(b)//10
这段代码的输出结果是b = 10,可能大家不知道这么简单的问题为什么要拿出来讲,那我要给大家讲的是为什么输出b = 10。首先在调用栈的词法环境中声明了两个变量a,b,接下来给a赋值,再将a的值赋给b,再改变a的值,因为给b的是赋值操作,使得b与a并不是在同一个地址,于是当a改变,b仍然不变。
我们再来看一个对象的拷贝:
js
let obj = {
age:18
}
let obj2 = obj
obj.age = 20
console.log(obj2.age);//20
这段代码的输出结果是 obj2.age=20,掘友们思考一下为什么捏?原来是因为对象在词法环境中创建时,会在堆中开辟一个空间用来储存对象的key和value,而我们的对象在栈中只保存堆中的一个地址,当我们给obj2赋值时,是将obj在堆中储存的地址赋给了obj2,于是,当obj.age改变时,obj2.age会随之而变。
浅拷贝
什么是浅拷贝?在上面举的例子中,对象的拷贝就是一个浅拷贝,也就是当我们复制一个新的数组或者对象以后,改变原数组或者对象中的属性或者值,新数组或者对象也随之改变,这种拷贝我们称之为浅拷贝。那我们来举一些浅拷贝的例子:
1.Object.create(x)
js
let a = {
name:'666',
like:{
n: 'coding'
}
}
let b = Object.create(a)//创建一个空对象并且继承a
a.like.n = 'running'
console.log(b.like.n)//running
Object.create(x)
方法的作用是创建一个新对象并且让新对象继承x,在上面的代码中,我们创建了对象b并且继承了a,当 a.like.n = 'running' 运行以后,我们发现 b.like.n 同样改变。
2.Object.assign({},a)
js
let a = {
name:'666',
like:{
n: 'coding'
}
}
let c = Object.assign({},a)//将多个对象合并成一个
a.like.n = 'running'
console.log(c);//{ name: '666', like: { n: 'running' } }
Object.assign({},a)
方法的作用是将多个对象合并成一个对象,于是我们使用一个空对象和a合并来实现拷贝的功能。我们可以看到,当 a.like.n 改变时,c随之而变。
3.concat
js
let arr = [1,2,3,{a:10}]
let newArr = [].concat(arr)//复制一个数组
arr.push(4)
console.log(newArr);//[1,2,3,{a:10}]
arr[3].a = 20
console.log(newArr);//[1,2,3,{a:20}]
concat
方法的作用是复制一个新的数组newArr,当我们向原数组中添加一个4时,打印newArr,发现新数组中并没有添加4,那么是否可以认为concat方法创建的不是浅拷贝呢?我们再来看将 arr[3].a 赋为20,结果,newArr中的 arr[3].a 改变,说明concat创建的数组拷贝仍然是浅拷贝。聪明的掘友应该发现了。当我们将存储在词法环境中为地址的属性赋值给另一个对象或数组,那么这就是浅拷贝。
4.slice
js
let originalArray = [1, 2, { a: 3 }];
let shallowCopy = originalArray.slice();
originalArray[0] = 'modified';
originalArray[2].a = 4;
console.log(originalArray); // 输出: ["modified", 2, { a: 4 }]
console.log(shallowCopy); // 输出: [1, 2, { a: 4 }]
slice
方法可以创建一个新的数组,其中包含从原始数组中选择的元素。当使用 slice
方法进行浅拷贝时,它会复制原始数组中的元素值,但不会复制对象本身,也就是说如果数组中的元素是对象,那么新数组中的对应元素将指向相同的对象引用。
5.数组解构
js
let originalArray = [1, 2, { a: 3 }];
let shallowCopy = [...originalArray];
originalArray[0] = 'modified';
originalArray[2].a = 4;
console.log(originalArray); // 输出: ["modified", 2, { a: 4 }]
console.log(shallowCopy); // 输出: [1, 2, { a: 4 }]
同样的,在数组解构中,如果数组中存在对象,那么当原数组对象中的属性值改变时,新数组也会改变
6.arr.toReversed().reserve()
js
let arr = [1,2,3,{a:10}]
let newArr = arr.reverse()//反转数组
arr[0].a = 20
console.log(newArr);//[{a:20},3,2,1]
反转方法也是一样的,当旧数组中存在对象,同样在对象改变时,随之改变
深拷贝
我们说了那么多浅拷贝,那么有没有深拷贝呢?当然有啦掘友们!接下来我们就聊聊深拷贝。 什么是深拷贝呢?聪明的掘友们就会想到当然和浅拷贝相反啦!对,没错!深拷贝就是当我们拷贝一个一个新对象或者数组时,改变原数组或者对象的属性,新数组不变。
在JavaScript中,只存在一种深拷贝:
JSON.parse(JSON.stringify(obj))
js
let obj = {
name:'www',
age:18,
like:{
type: 'coding'
},
a:undefined,
b:null,
c:function(){},
d:{
n:100
},
e:Symbol('hello')
}
// obj.c = obj.d
// obj.d.n = obj.c
let newObj = JSON.parse(JSON.stringify(obj))
obj.like.type = 'sleeping'
console.log(newObj);//{name: 'www',age: 18,like: { type: 'coding' },b: null,d: { n: 100}
}
我们可以看到,当我改变obj中对象属性的值时,新数组并没有改变值,这就是深拷贝 但是这个方法有两个缺点:
-
无法拷贝 undefined,function,Symbol,bigint 这几种类型的数据
我们可以从上方代码的输出结果来看,undefined,function,Symbol,bigint 这几种类型的数据都未拷贝下来
-
无法拷贝循环引用的对象
当我们将 // obj.c = obj.d // obj.d.n = obj.c 两行注释解除后,运行发现报错,说明无法拷贝循环引用的对象
结尾
相信掘友们看完这篇文章功力大有增进!下一篇我将带大家手写浅拷贝和深拷贝!