前言
拷贝是指通过一种手段创建了一个新对象,并且新对象和原对象长得一模一样。拷贝通常只针对引用类型 ,有深拷贝和浅拷贝。
浅拷贝 -- 引用类型直接复制引用地址
基于原对象,拷贝得到一个新对象,源对象中的内容修改会影响新对象
常见的浅拷贝方法
- Object.create(obj)
- Object.assign({}, a)
- [].concat(arr)
- 数组解构
- arr.slice(0)
- arr.toReversed().reverse()
实现原理:
- 借助for in 遍历原对象,将原对象的属性值增加在新对象中
- 因为 for in 会遍历到对象隐式具有的属性,通常要使用obj.hasOwnProperty(key)来判断要拷贝的属性是不是对象显示具有的属性
深拷贝
基于原对象,拷贝得到一个新对象,源对象中的内容修改不会影响新对象
- JSON.parse(JSON.stringify(obj))
缺陷:
- 不能识别BigInt 类型
- 不能拷贝 undefined Symbol function 类型的值
- 不能处理循环引用
- structuredClone()
实现原理:
- 借助for in 遍历原对象,将原对象的属性值增加在新对象中
- 因为 for in 会遍历到对象隐式具有的属性,通常要使用obj.hasOwnProperty(key)来判断要拷贝的属性是不是对象显示具有的属性
- 如果遍历到的属性值是原始值类型,直接往新对象中赋值,如果是引用类型,递归创建新的子对象
示例
ini
let a = 1
let b = a
a = 2
console.log(b);
打印结果
分析
let声明的一个变量a的值为1,声明一个变量b将变量a赋给b,此时b的值为1,将a的值改为2对b没有影响。
ini
let obj = {
age: 18
}
let obj2 = obj
obj.age = 20
console.log(obj2);
打印结果
分析 let 声明了一个对象obj,里面放的值为 age: 18
,let obj2 = obj
这一步是将obj的引用地址赋给了obj2(obj和obj2还是一个对象),因为对象可以无限大,所以js引擎在预编译的时候,对象会被存入堆中,而此时obj的值为一个引用地址,指向该对象,obj.age = 20
改变对象里面的属性的值但是并没有改变该引用地址,所以obj2的引用地址也没变,因此,obj2里面的值也会随着obj改变。
Object.create(obj)
用于创建一个新对象,并且让新对象隐式继承原对象里面的属性。
分析
此时b是一个空对象,但是b的隐式原型里继承到了a的属性。但是如果把a的属性改变,b也会变化。这是浅拷贝。
Object.assign(obj, obj2)
用于将两个对象合并,将obj2拼到obj上去。
ini
let obj = {
age: 18
}
let obj2 = {
name: 'lxp'
}
let c = Object.assign(obj, obj2)
console.log(c, obj);
打印结果
分析
a对象的属性也变了,说明此时a对象和c对象是一个对象。
换一种写法:let c = Object.assign({}, obj)
相当于c拷贝了obj
ini
let obj = {
age: 18
}
let c = Object.assign({}, obj)
obj.age = 20
console.log(c, obj);
打印结果
分析 c对象里的age没变,说明是浅拷贝,对吗?
ini
let obj = {
age: 18,
n: {
like: 'movie'
}
}
let c = Object.assign({}, obj)
obj.n.like = 'drink'
console.log(c, obj);
打印结果
分析 c和obj的属性n都改变了,说明属性n拷贝过来的还是n的引用地址。c对象里的属性还是会受原对象obj的影响,所以还是浅拷贝。
[].concat(arr)
将arr的元素和[]中的元素合并,并返回一个新的数组。
ini
let arr = [1,2,3, {a: 'movie'}]
let newArr = [].concat(arr)
arr[3].a = 'book'
console.log(newArr);
打印结果
分析
newArr还是会受arr的影响,说明还是浅拷贝。
解构
let newArr = []
相当于创建了一个新的空数组, ...arr
是数组的解构,将数组的元素解构出来,let newArr = [...arr]
也相当于是创建了一个新数组和原数组长得一模一样。
ini
let arr = [1,2,3, {a: 'movie'}]
let newArr = [...arr]
arr[3].a = 'book'
console.log(newArr);
打印结果
分析
newArr还是会受arr的影响,说明还是浅拷贝。
arr.slice()
截取数组原数组不受影响,接收两个参数,第一个参数是开始截的数组的下标,第二个参数是停止截取的数组的下标(第二参数不写默认是截到底)。左闭右开。
ini
let arr = [1, 2, 3, 4, 5]
let arr2 = arr.slice(1, 3)
console.log(arr2, arr);
打印结果
分析
左闭右开,不包含第二个参数的下标。
ini
let arr = [1, 2, 3, 4, {a: 'movie'}]
let arr2 = arr.slice(0)
arr[4].a = 'hello'
console.log(arr2, arr);
打印结果
分析
let arr2 = arr.slice(0)
截取arr中所有的元素并返回一个新数组arr2,arr2和arr长得一样,arr2还是会受arr的影响,所以还是浅拷贝。
arr.toReversed().reverse()-- toReversed()是一个比较新的方法如果node版本过低会识别不出来
将一个数组反转再反转,arr.toReversed()
会将一个数组反转得到一个新数组,将得到的新数组通过reverse()再反转一次,就得到了与arr长得一样的一个新数组。toReversed()
方法会创建一个新数组。注:如果node版本过低可以放到谷歌浏览器上运行。
ini
let arr = [1, 2, 3, 4, {a: 'movie'}]
let newArr = arr.toReversed().reverse()
arr[4].a = 'book'
console.log(newArr);
打印结果
分析
newArr还是受arr影响,还是浅拷贝。
手写一个浅拷贝方法shallow
实现原理:
- 借助for in 遍历原对象,将原对象的属性值增加在新对象中
- 因为 for in 会遍历到对象隐式具有的属性,通常要使用obj.hasOwnProperty(key)来判断要拷贝的属性是不是对象显示具有的属性
vbnet
let obj = {
name: 'lxp',
like: {
n: 'coding'
}
}
function shallow(obj) {
let newObj = {}
for(let key in obj){
newObj[key] = obj[key]
}
return newObj
}
console.log(shallow(obj));
打印结果
分析 for循环遍历原对象,newObj[key] = obj[key]
这是把obj的属性值赋给newObj相同键名的属性,不能写newObj.key = obj.key
因为key是变量。
注:拷贝一般是指拷贝对象上的显示属性,隐式具有的属性是不拷贝的
插播:hasOwnProperty()方法
判断对象中的key是否是隐式具有的,是就返回false,不是就返回true。
javascript
Object.prototype.d = 'a'
let obj = {
a: 'lxp',
b: {
n: 'coding'
}
}
console.log(obj.hasOwnProperty('d'));
console.log(obj.hasOwnProperty('a'));
打印结果
借助hasOwnProperty()
方法判断for循环遍历的对象里面是否有隐式具有的属性,有就不赋给newObj。
vbnet
let obj = {
name: 'lxp',
like: {
a: 'food'
}
}
function shallow(obj) {
let newObj = {}
for(let key in obj) {
if(obj.hasOwnProperty(key)){
newObj[key] = obj[key]
}}
return newObj
}
let obj2 = shallow(obj)
obj2.like.a = 'sport'
console.log(shallow(obj));
打印结果
JSON.parse(JSON.stringify(obj))
JSON.stringify(obj)
方法是将obj对象转为JSON字符串,再通过JSON.parse()
将JSON字符串转为对象。
javascript
let obj = {
name: 'lxp',
age: 18,
like: {
n: 'music'
},
a: true,
b: undefined,
c: null,
d: Symbol(1),
f: function() {},
e: 123n
}
let obj2 = JSON.parse(JSON.stringify(obj))
console.log(obj2);
打印结果
分析 JSON.parse(JSON.stringify(obj))
方法无法识别BigInt类型
javascript
let obj = {
name: 'lxp',
age: 18,
like: {
n: 'music'
},
a: true,
b: undefined,
c: null,
d: Symbol(1),
f: function() {}
}
let obj2 = JSON.parse(JSON.stringify(obj))
console.log(obj2);
打印结果
分析 不能拷贝 undefined Symbol function 类型的值
javascript
let obj = {
name: 'lxp',
age: 18,
like: {
n: 'music'
},
a: true,
b: undefined,
c: null,
d: Symbol(1),
f: function() {},
e: 123n
}
obj.c = obj.like
obj.like.n = obj.c
let obj2 = JSON.parse(JSON.stringify(obj))
console.log(obj2);
打印结果
分析 obj.c = obj.like; obj.like.n = obj.c
循环引用,JSON.parse(JSON.stringify(obj))
不能处理循环引用。
javascript
let obj = {
name: 'lxp',
age: 18,
like: {
n: 'music'
},
a: true,
c: null
}
let obj2 = JSON.parse(JSON.stringify(obj))
obj.like.n = 'majiang'
console.log(obj2);
打印结果
分析 obj不能影响obj2,实现了深拷贝。
structuredClone()
由js官方打造的实现深拷贝的方法,很新,node版本过低也运行不出来,放到谷歌浏览器运行。
ini
const user = {
name: {
firstname: 'L',
lastname: 'xp'
},
age: 18
}
const newUser = structuredClone(user)
user.name.firstname = 'Lee'
console.log(newUser)
运行结果
假如面试官让你自己手搓一个深拷贝方法deep()
实现原理:
- 借助for in 遍历原对象,将原对象的属性值增加在新对象中
- 因为 for in 会遍历到对象隐式具有的属性,通常要使用obj.hasOwnProperty(key)来判断要拷贝的属性是不是对象显示具有的属性
- 如果遍历到的属性值是原始值类型,直接往新对象中赋值,如果是引用类型,递归创建新的子对象
ini
const user = {
name: {
firstname: 'L',
lastname: 'xp'
},
age: 18
}
function deep(obj) {
let newObj = {}
for(let key in obj) {
if (obj.hasOwnProperty(key)){
if(obj[key] instanceof Object) { // obj[key] == 'object' && obj[key] !== null obj[key] 是不是对象
newObj[key] = deep(obj[key]);
} else{
newObj[key] = obj[key]
}
}
}
return newObj
}
const newobj = deep(user)
user.name.firstname = 'zhangsan'
console.log(newobj);
打印结果
结语
内容比较多,自己梳理一下记得更牢。