读懂浅拷贝和深拷贝

个人博客欢迎分享

大前端养成记欢迎StartFork

简介:

很多小伙伴对浅拷贝深拷贝并不是了解得很透测,在项目中可能由于操作不当,导致程序不能达到预期的结果,本篇文章会介绍javascript内置的一些拷贝方法,到底哪些是深拷贝,哪些是浅拷贝,最后还会带大家手写浅拷贝深拷贝

在了解浅拷贝深拷贝之前,我们要知道的是,数组对象是用地址的形式指向对应的值,例如这段代码:

const object = {
    info: {
        name: "Little Boy",
        age: 24
    },
    hobby: ["hobby1", "hobby2"]
}

如下图:

image

这张图很清晰的表示了数组对象是怎么用地址的形式指向对应的值。了解了这些之后我们进入正文。

一. 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.parseJSON.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"

上面的代码就实现深拷贝,只要在拷贝的时候发现引用值那就继续遍历就可以了,代码也并不算太难,如果有问题的话,可以联系博主。

你可能感兴趣的:(读懂浅拷贝和深拷贝)