深拷贝和浅拷贝

76 阅读4分钟

开发中经常需要赋值一个对象。

如果采用直接赋值的方法

let obj = {
    name:'tom',
    age:'18'
}

let obj1 = obj
obj1.age = 20
console.log(obj1); // {name: 'tom', age: 20}
console.log(obj); //{name: 'tom', age: 20}

当修改某一个对象的属性,另一个对象也会随着改变

因为赋值是直接复制的地址,两个对象指向同一个地址,所以会影响

那么想要复制一份,且不会互相影响,那就得使用拷贝了。

浅拷贝

浅拷贝拷贝的是地址。

常见的方法:

  • 拷贝对象:Object.assign(); 扩展运算符{...obj}

  • 拷贝数组:Array.prototype.concat() ;扩展运算符[...arr]

扩展运算符

let obj = {
    name:'tom',
    age:18
}

let obj1 = {...obj}
console.log(obj1);  //{name: 'tom', age: 18}
obj1.age = 20
console.log(obj1);  //{name: 'tom', age: 20}
console.log(obj);  //{name: 'tom', age: 18}

Object.assign()

语法是 Object.assign(目标文件, 原文件)

let obj = {
    name:'tom',
    age:18
}

let obj1 = {}
Object.assign(obj1,obj)
console.log(obj1);  //{name: 'tom', age: 18}
obj1.age = 20
console.log(obj1);  //{name: 'tom', age: 20}
console.log(obj);  //{name: 'tom', age: 18}

都不会影响原对象。

但是浅拷贝在拷贝复杂数据类型时,拷贝的是地址,所以修改复杂数据类型的数据时,会影响另外的对象

let obj = {
    name:'tom',
    age:18,
    hobby:{
        hobbyName:'dance'
    }
}

let obj1 = {}
Object.assign(obj1,obj)
// 当修改复杂数据类型时,会影响另外一个对象
obj1.hobby.hobbyName = 'sing'
console.log(obj1);
console.log(obj);

image.png

Array.prototype.concat()

const arr = [1,2,3,[4,5]]
let arr1 = arr.concat()
arr1[3][1] = 9
console.log(arr1);
console.log(arr);

image.png

Array.prototype.slice()

参数传0或者不传参都可以

const arr = [1,2,3,[4,5]]
let arr1 = arr.slice(0)
arr1[3][1] = 9
console.log(arr1);
console.log(arr);

浅拷贝只是拷贝一层,更深层次对象级别的只拷贝引用(地址),所以改变新对象,旧对象也会改变,因为新旧对象共享一块内存。

浅拷贝如果属性值是简单数据类型则直接拷贝值,如果是引用数据类型则拷贝的是地址

Array.prototype.filter()

let arr = [1,2,3,4,5,6]
let f = arr.filter(item=>item)
console.log(f);  // [1, 2, 3, 4, 5, 6]
console.log(arr);  // [1, 2, 3, 4, 5, 6]

Array.prototype.map()

let arr = [1,2,3,4,5,6]
let m = arr.map(item=>item)
console.log(m);  // [1, 2, 3, 4, 5, 6]
console.log(arr);  // [1, 2, 3, 4, 5, 6]

Array.prototype.flat()

let arr = [1,2,3,4,5,6]

let newArr = arr.flat()
console.log(newArr); // [1, 2, 3, 4, 5, 6]

但是数据有嵌套时不能进行拷贝

Array.from

是数组的静态方法,是能够创建一个新的浅拷贝的数组实例

let arr = [1,2,3,4,5,6]

let newArr = Array.from(arr)
console.log(newArr); // [1, 2, 3, 4, 5, 6]

手写浅拷贝

function shallowClone(oldValue){
    let newValue
    // 先判断target是否为数组
    if(Array.isArray(oldValue)){
        newValue = []
    }else{
        newValue = {}
    }
    for(let i in oldValue){
        // 判断是否为自身属性,而不是继承来的
        if(target.hasOwnProperty(i)){
            newValue[i] = oldValue[i]
        }
    }
    return newValue
}
let obj = {
    name:'tom',
    age:18,
    hobby:{
        h1:'dance'
    }
}
let obj1 = shallowCole(obj)

或者是传两个参数,传两个参数就不用判断是否为数组了。传两个参数后的用法就和Object.assign差不多,但是还可以传数组。

function shallowClone(newValue,oldValue){
    for(let i in oldValue){
        // 判断是否为自身属性,而不是继承来的
        if(oldValue.hasOwnProperty(i)){
            newValue[i] = oldValue[i]
        }
    }
    return newValue
}
let obj = {
    name:'tom',
    age:18,
    hobby:{
        h1:'dance'
    }
}
let obj1 = {}
shallowClone(obj1,obj)

深拷贝

深拷贝拷贝的是对象,不是地址

常见的方法:

  • 通过递归实现深拷贝
  • js库lodash/cloneDeep
  • 通过JSON.stringify()实现
  • jQuery.extend()

使用递归实现手写深拷贝

// 手写深拷贝
function deepClone(newValue,oldValue){
    for(let i in oldValue){
        // 如果是引用类型的值,则再次调用函数
        if(typeof(oldValue[i]) === 'object'){
            // 判断是否为数组
            newValue[i] = Array.isArray(oldValue[i]) ? [] :{}
            deepClone(newValue[i],oldValue[i])
        }else{
            newValue[i] = oldValue[i]
        }
    }
    return newValue
}
let obj = {
    name:'tom',
    age:18,
    hobby:['dance','sing']
}
let obj1 = {}
deepClone(obj1,obj)
obj1.hobby[0] = 'play'
console.log(obj1);
console.log(obj);

image.png

js库lodash里面的cloneDeep

要先安装lodash

去官网Lodash 简介 | Lodash中文文档 | Lodash中文网 (lodashjs.com)

也可以在html直接引入

let obj = {
    name:'tom',
    age:18,
    hobby:['dance','sing']
}
let obj1 = _.cloneDeep(obj)
obj1.hobby[0] = 'play'

使用_.cloneDeep()则可以实现深拷贝

JSON.stringify()

JSON.stringify()的用法是将对象转换为json字符串

JSON.parse()是将json字符串转换为新的字符串

let obj = {
    name:'tom',
    age:18,
    hobby:['dance','sing'],
}
let obj1 = JSON.parse(JSON.stringify(obj))

obj1和obj没有任何的关系,因为转换为字符串后成为基本数据类型,不再指向原来的对象了。

但是这种方式存在弊端,不会拷贝undefined、symbol、函数,会忽略。

let obj = {
    name:'tom',
    age:18,
    hobby:['dance','sing'],
    a:undefined,
    b:function(){}
}
let obj1 = JSON.parse(JSON.stringify(obj))

image.png

jQuery.extend()

首先要引入jQuery

let obj = {
    name:'tom',
    age:18,
    hobby:['dance','sing'],
    a:undefined,
    b:function(){}
}
let obj1 = $.extend(true,{},obj)

第一个参数传递布尔值,指是否为深度合并对象。

但是这个方法也会忽略undefined

小结

浅拷贝只拷贝一层,属性对引用类型时,浅拷贝复制地址,浅拷贝只复制属性指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存,修改对象属性会影响原对象

深拷贝是递归拷贝深层次,深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象