我猜你不会拷贝!
不管是在写代码的时候还是在日常处理文件的时候,我们都会遇到复制粘贴的情况,就是将一个文件或一段代码复制一份到其他的地方使用。这个过程叫复制,又叫拷贝。但是我们今天聊的拷贝并不是我们常用的只用ctrl+c操作,而是在JavaScript中将一个变量值复制给另外的一个变量。这样听起来很简单,有些人认为直接将 a = b,进行赋值操作就行了。但是!并没有那么简单!
为什么说拷贝并不是a=b呢?
拷贝,是从一个现有的数据实例生成一个新的实例,使得新实例具有与原实例相同的数据状态,也就是说拷贝过来的数据与原数据一模一样。那我们常用的赋值操作就这样的啊,咋还说不是a=b
呢。我们想想我们平时在日常生活中进行的拷贝工作,如果我们修改原始的文件内容,那我们拷贝得到的副本是不会变的。但是在我们的代码人生中就会出现一种修改了原本数据,副本中的数据也会发生改变。所以说并不是仅仅是a=b这么简单。例如:
let a ={
name:'tom',
age:18
}
let b = a
a.age = 20
console.log(b);
此时我们得到的结果是:
修改了对象a中的属性值,最后b中的属性值也发生了改变。嗯?what?这与我们的想法不一样啊?
其实我们弄清楚了js的执行过程我们就容易弄清楚了。
为什么b中的属性值也会发生改变?
在JavaScript代码实际执行之前,js引擎会进行预编译。它会在调用栈中创建一个全局执行上下文或者函数执行上下文(对于函数内部的代码)。在这个阶段,变量会被初始化(但尚未赋值,除了直接赋默认值的const和let声明),函数定义会被处理,作用域链会被建立。接下来,就开始进行赋值。此时,原始数据类型的变量就开始直接赋值了,但是引用数据类型是在堆里面,只能赋个地址给引用类型的变量,到时候直接在堆中寻找这个地址。用图可能更好理解一点
而我们使用obj1=obj时,也是直接将引用地址传给了obj,所以修改obj中的值,就是通过地址找到然后进行修改。obj1中的值也会发生改变。
浅拷贝与深拷贝
我们会发现有两种拷贝情况:一种是拷贝后,修改原始值后,拷贝后的副本值不会发生改变,一种是修改了原始值后,副本也会发生改变。我们将这两种情况称为深拷贝和浅拷贝。
浅拷贝
顾名思义,是浅浅的拷贝并不深入,即拷贝后的对象受原对象的影响(共用同一个地址)
我们常见的浅拷贝有如下一些:
- Object.create(obj)
- Object.assign({},obj)
- [].concat(arr)
- 解构
- arr.slice(0)
通过这些拷贝后得到的对象会因为原来的对象发生改变而改变
let obj = {
a:1,
b:[1,2]
}
let obj2 = Object.assign({},obj)
obj.b.push(3)
console.log(obj2);
此时打印出来的值就是
let obj2 = { a:1, b:[1,2,3] }
Object.assign()
:运用对象中合并两个对象的方法来实现,该方法接受俩个参数,将后者合并到前者。Object.create()
:接收一个对象参数,创建一个新对象,其原型为提供的对象。[].concat(arr)
:是数组的合并方法arr.slice(0)
:这是切割数组返回一个新数组的方法 ···
通过这些我们就可以发现,我们想要实现一个浅拷贝方法,就需要新建一个对象,然后向其中添加原来对象的属性和值。通过这个我们可以自己手搓一个浅拷贝的实现方法。
function shallowCopy(obj){
let newobj = {}
for(let key in obj){
// 判断是不是显示具有的
if(obj.hasOwnProperty(key)){
newobj[key] = obj[key]
}
}
return newobj
}
这个还是比较简单的,就是新建一个对象,然后遍历原对象,向新对象中添加值。注意我们要在这里判断一下是不是对象直接拥有的属性,如果是的话就向新对象中添加。
深拷贝
深拷贝就是完全复制对象及其所有嵌套的对象,产生一个全新的独立对象,无论怎样修改都不会影响到原对象。
一些大佬们在没出es6新特性的时候,通常使用JSON.parse(JSON.stringify(obj)) 方法,将需要对象先转出字符串,在转换成对象以得到一个全新的对象,这个对象与之前那个对象就没有任何关联了。所以就是一个简单的深拷贝例子。但是这个方法还是有一定的局限性的,例如:
- 无法识别bigInt类型
- 无法拷贝undefined,function,Symbol
- 无法处理循环引用
let obj = {
a:1,
b:{n:2},
c:'cc',
d:true,
e:null,
f:undefined,
g:function(){},
h:Symbol(1),
i:123n
}
let newObj = JSON.parse(JSON.stringify(obj))
console.log(newObj);
之后又出现了一个新的方法,这使得深拷贝变得容易了不少structuredClone(obj)
但是这个方法也有一定的缺陷,就是不能拷贝function
和Symbol
类型的数据,具体的大佬们可以一会去试试。
重点:面试官叫你手写一个深拷贝函数。
其实这个和浅拷贝差不了太多,我们深拷贝就是要让拷贝后的对象完全独立于老对象。所以我们就需要一层一层的遍历对象中的对象,确保每一层都是独立的。这里我们可以使用递归的思想,其他的都差不了太多!
丢一份手写深拷贝出来:
function deepCopy(obj){
let newObj = {}
for(let key in obj){
if(obj.hasOwnProperty(key)){
// 判断obj[key]是不是对象
if(obj[key] instanceof Object){
newObj[key] = deepCopy(obj[key])
}else{
newObj[key] = obj[key]
}
}
}
return newObj
}
总结
深拷贝与浅拷贝是JavaScript中处理对象复制时不可忽视的概念。选择合适的拷贝方式,能够有效地避免数据不一致和难以预料的副作用,从而保证程序的稳定性和可靠性。所以大佬不要以后听到拷贝就认为是复制粘贴哦,还有在拷贝对象哦!
转载自:https://juejin.cn/post/7391703459810902068