个人博客欢迎分享
。
大前端养成记欢迎Start
和Fork
。
简介:
很多小伙伴对浅拷贝
和深拷贝
并不是了解得很透测,在项目中可能由于操作不当,导致程序不能达到预期的结果,本篇文章会介绍javascript
内置的一些拷贝方法,到底哪些是深拷贝
,哪些是浅拷贝
,最后还会带大家手写浅拷贝
和深拷贝
。
在了解浅拷贝
和深拷贝
之前,我们要知道的是,数组
和对象
是用地址的形式指向对应的值,例如这段代码:
const object = {
info: {
name: "Little Boy",
age: 24
},
hobby: ["hobby1", "hobby2"]
}
如下图:
这张图很清晰的表示了数组
和对象
是怎么用地址的形式指向对应的值。了解了这些之后我们进入正文。
一. Array.prototype.concat
Array.prototype.concat
方法是用于连接多个数组的,意义上也是用于拷贝数组。
用法:const newArr = [].concat(arr1, arr2, ...)
那么接下来我们用一个例子证明Array.prototype.concat
是哪一种拷贝:
const arr = [{name: "LIttle Boy"}]
const newArr = [].concat(arr)
newArr[0].name = "Hello Little Boy"
console.log(newArr[0].name) // Hello Little Boy
console.log(arr[0].name) // Hello Little Boy
从代码我们可以发现,newArr
是我们通过concat
拷贝来的,当我们给newArr
的第一个元素(也就是拷贝来的元素)修改的时候,原来的数组arr
的对应元素也修改,这时候就证明了Array.prototype.concat
是浅拷贝。
二. Array.prototype.slice
Array.prototype.concat
方法是用于从现有的数组中返回特定的元素,意义上也可用于拷贝数组。
用法:const newArr = arr.slice()
那么接下来我们用一个例子证明Array.prototype.slice
是哪一种拷贝:
const arr = [{name: "LIttle Boy"}]
const newArr = arr.slice()
newArr[0].name = "Hello Little Boy"
console.log(arr[0].name) // Hello Little Boy
可以看到结果和Array.prototype.concat
一致,所以Array.prototype.slice
也是浅拷贝
。
三. Object.assign
Object.assign
方法用于将所有可枚举属性的值从一个或多个源对象复制到目标对象。它将返回目标对象。
用法: newObj = Object.assign(obj1, obj2)
那么接下来我们用一个例子证明Object.assign
是哪一种拷贝:
const obj1 = {
name: "Little Boy"
}
const obj2 = {
info: {
type: "a"
}
}
const newObj = Object.assign(obj1, obj2)
newObj.name = "Hello Little Boy"
console.log(obj1.name) // "Hello Little Boy"
可以看到结果和Array.prototype.concat
一致,所以Object.assign
也是浅拷贝
。
四. 拓展运算符
拓展运算符
是ES6新增的语法,拓展运算符对象和数组都可以使用。
用法:[...arr1, ...arr2]
或{...obj1, ...obj2}
那么接下来我们用一个例子证明拓展运算符
是哪一种拷贝:
const arr = [{name: "LIttle Boy"}]
const newArr = [...arr]
newArr[0].name = "Hello Little Boy"
console.log(arr[0].name) // Hello Little Boy
可以看到结果和Array.prototype.concat
一致,所以拓展运算符
也是浅拷贝
。
五. JSON.parse(JSON.stringify())
JSON.parse(JSON.stringify())
是JSON.parse
和JSON.stringify
组合实现,这两个方法都是JSON
的方法,可以先将对象或者数组转化成json字符串,然后就可以转成数组或者对象了。
用法:JSON.parse(JSON.stringify([]))
或者JSON.parse(JSON.stringify({}))
那么接下来我们用一个例子证明JSON.parse(JSON.stringify())
是哪一种拷贝:
const obj = {name: "Little Boy"}
const newObj = JSON.parse(JSON.stringify(obj))
newObj.name = "Hello Little Boy"
console.log(obj.name) // Little Boy
和上面几种方法不同的是,当修改了拷贝的对象后,原来的对象并没有被修改,所以这种方法的拷贝属于深拷贝
,虽然这种方法在项目中也有人使用,但是并不是每一个对象都可以这样使用的,当对象的某个字段是等于自身的时候,这种方法就会报错了,所以这个方法要慎用。
六. 手写浅拷贝
浅拷贝
的特点就是:只遍历一层,并且遇到引用值的时候并不用去处理,直接赋值即可,实现如下:
function lightCopy(target) {
if (!(target instanceof Object)) throw Error("传入的值必须为数组或者是对象")
if (target instanceof Array) {
const newArr = []
target.forEach(key => newArr.push(key))
return newArr
} else {
const newObj = {}
Object.keys(target).forEach(key => newObj[key] = target[key])
return newObj
}
}
const newArr = lightCopy([1, 2, 3])
console.log(newArr) // [1, 2, 3]
const newObj = lightCopy({name: "Little Boy"})
console.log(newObj) // {name: "Little Boy"}
浅拷贝
实现起来也不是很难,如果对上面的代码不理解的同学需要好好的补充基础知识了。
七. 手写深拷贝
深拷贝
的特点就是:拷贝出来的数据无论做了什么修改,原来的数据不会发生变化,那么如何做到这一点呢?上面我们说过数组和对象都是用地址来指向对应的值,所以我们可以通过修改指向去做深拷贝
,这样拷贝出来的数据就不会影响到原始数据。
// 判断是否是引用值
function isReferenceValue(target) {
return target instanceof Object
}
// 获取每项克隆后的值
function getCopyValue(target) {
return isReferenceValue(target) ? deepCopy(target) : target
}
// 深度拷贝
function deepCopy(target) {
if (!isReferenceValue(target)) throw Error("传入的值必须为数组或者是对象")
if (target instanceof Array) {
const newArr = []
const targetLen = target.length
for (let i = 0; i < targetLen; i++) {
newArr.push(getCopyValue(target[i]))
}
return newArr
} else {
const newObj = {}
for (const key in target) {
newObj[key] = getCopyValue(target[key])
}
return newObj
}
}
const arr = [{
hobby: [{
name: "Little Boy"
}]
}]
const newObj = deepCopy(arr)
newObj[0].hobby[0].name = "Hello Little Boy"
console.log(arr) // "Little Boy"
上面的代码就实现深拷贝
,只要在拷贝的时候发现引用值那就继续遍历就可以了,代码也并不算太难,如果有问题的话,可以联系博主。