js|浅拷贝和深拷贝

103 阅读4分钟

一、关于基础类型和引用类型

  1. 基础类型包括 number、boolean、string、null、undefined (ES6 Symbol、BigInt)
  2. 引用类型包括 Array、Object、Function

基础类型:直接存储在栈中的数据 引用类型:在栈中存储了指针,该指针指向堆中该实体的起始地址

二、浅拷贝和深拷贝

  1. 拷贝的意思就是复制一份,对于基础数据类型,不存在浅拷贝深拷贝的问题,复制的就是值的内容
  2. 对于引用数据类型,复制的是指向内容的地址,所以采用复制实现的是浅拷贝

三、值的改变

  1. 浅拷贝:复制的一份改变内容时,原始的内容会被改变,这是我们不希望看到的
  2. 深拷贝:复制的和原始的不在有关系,不会互相影响

四、浅拷贝方法

6.扩展运算符

let obj1 = { a: 1, b: { c: 1 } };
let obj2 = { ...obj1 }; // 扩展运算符
obj1.a = 2;
console.log(obj1); // { a: 2, b: { c: 1 } }
console.log(obj2); // { a: 1, b: { c: 1 } }
console.log(obj1 === obj2); // false

let arr = [1, 2, 3];
let arr2 = [...arr];
console.log(arr2); // [1,2,3]
console.log(arr === arr2); // false

1、简单复制

// 对象赋值 obj2改变属性会同时修改obj1 因为复制的地址指向同一个内容区
var obj1 = {
    'name': 'zhangsan',
    'age': '18',
    'language': [1, [2, 3], [4, 5]],
};
var obj2 = obj1;
obj2.name = "lisi";
obj2.language[1] = ["二", "三"];
console.log('obj1', obj1) // obj1 { name: 'lisi', age: '18', language: [ 1, [ '二', '三' ], [ 4, 5 ] ] }
console.log('obj2', obj2) // obj2 { name: 'lisi', age: '18', language: [ 1, [ '二', '三' ], [ 4, 5 ] ] }

2、Object.assign()

var obj = {
    a: {
        a: "kobe",
        b: 39
    }
};
var assignObj = Object.assign({}, obj);
assignObj.a.a = "ccc"; //会改变obj.a.a 的值
console.log(obj); // { a: { a: 'ccc', b: 39 } }
console.log(assignObj); //  a: { a: 'ccc', b: 39 } }
// assign对第一层实现的是深拷贝
let obj = {
    username: 'kobe'
};
let obj2 = Object.assign({}, obj);
obj2.username = 'ccc';
console.log(obj);//{username: "kobe"}
console.log(obj2); // { username: 'ccc' }

3、slice()

slice到底是深拷贝还是浅拷贝其实取决于数组中的元素,如果数组中的元素是基本数据类型,那就是深拷贝,如果是引用类型,那就是浅拷贝,可以理解成slice只能复制引用类型的地址,那当其他因素要改变当前的数组值时,值会发生改变

let arr = [1, 3, { username: ' kobe' }];
let arr3 = arr.slice();
arr3[2].username = 'ccc'
console.log(arr);  //[ 1, 3, { username: 'ccc' } ]
console.log(arr3); // [ 1, 3, { username: 'ccc' } ]
// 基础类型,slice不会修改原数组
let arr = [1, 3, 4];
let arr3 = arr.slice();
arr3[2] = 2;
console.log(arr);  //[ 1, 3, 4 ]
console.log(arr3); // [ 1, 3, 2 ]

4、concat()

let arr = [1, 3, { username: 'kobe' }];
let arr2 = arr.concat();
arr2[2].username = 'ccc';
console.log(arr); // [ 1, 3, { username: 'ccc' } ]
console.log(arr2); // [ 1, 3, { username: 'ccc' } ]
// 基础类型,concat不会修改原数组
let arr = [1, 3, 4];
let arr2 = arr.concat();
arr2[2] = 2;
console.log(arr); // [ 1, 3, 4 ]
console.log(arr2); // [ 1, 3, 2 ]

关于Array的slice和concat方法的补充说明:Array的slice和concat方法不修改原数组,只会返回一个浅复制了原数组中的元素的一个新数组。 原数组的元素会按照下述规则拷贝:

1.该元素是个对象引用(不是实际的对象),slice 会拷贝这个对象引用到新的数组里。两个对象引用都引用了同一个对象。如果被引用的对象发生改变,则新的和原来的数组中的这个元素也会发生改变。

2.对于字符串、数字及布尔值来说(不是 String、Number 或者 Boolean 对象),slice 会拷贝这些值到新的数组里。在别的数组里修改这些字符串或数字或是布尔值,将不会影响另一个数组。

5、浅拷贝

// 浅拷贝 基础数据不变,引用类型会变
var obj1 = {
    'name': 'zhangsan',
    'age': '18',
    'language': [1, [2, 3], [4, 5]],
};
var obj3 = qianCopy(obj1);
obj3.name = "lisi";
obj3.language[1] = ["二", "三"];
function qianCopy(obj) {
    let resObj = {};
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            resObj[key] = obj[key]; //基础的深拷贝,引用的复制的地址
        }
    }
    return resObj;
}
console.log('obj1', obj1) // obj1 {name: 'zhangsan',age: '18',language: [ 1, [ '二', '三' ], [ 4, 5 ] ]}
console.log('obj3', obj3) // obj3 { name: 'lisi', age: '18', language: [ 1, [ '二', '三' ], [ 4, 5 ] ] }

五、深拷贝方法

1、JSON.parse(JSON.stringify())

let arr = [1, 3, { username: ' kobe' }];
let arr4 = JSON.parse(JSON.stringify(arr));
arr4[2].username = 'ccc';
console.log(arr); //[ 1, 3, { username: ' kobe' } ]
console.log(arr4); //[ 1, 3, { username: 'ccc' } ]

2、递归方法

// 简单的递归
function deepCopy(target) {
    let res = Array.isArray(target) ? [] : {};
    for (let key in target) {
        if (typeof target[key] === 'object') {
            res[key] = deepCopy(target[key]);
        } else {
            res[key] = target[key]
        }
    }
    return res;
}
//对象拷贝
var A = {
    name: "martin",
    data: { num: 10 },
    say: function () {
        console.log("say");
    }
};
let B = deepCopy(A);
B.data.num = 18;
console.log(A); // { name: 'martin', data: { num: 10 }, say: [Function: say] }
console.log(B); //{ name: 'martin', data: { num: 18 }, say: [Function: say] }
//数组拷贝
let arr = [1, 3, { username: ' kobe' }];
let arr3 = deepCopy(arr);
arr3[2].username = 'ccc'
console.log(arr);  //[ 1, 3, { username: 'kobe' } ]
console.log(arr3); // [ 1, 3, { username: 'ccc' } ]
/**
 * 递归实现深拷贝 数组和对象一起
 * @param {*} target 
 */
//检测数据类型
function checkType(target) {
    return Object.prototype.toString.call(target).slice(8, -1)
}
//实现深拷贝克隆 对象或者数组
function clone(target) {
    //返回值
    let result;
    //检测类型
    let targetType = checkType(target);
    if (targetType === 'Object') {
        result = {};
    } else if (targetType === 'Array') {
        result = [];
    } else {
        //基础类型或者函数
        result = target;
    }
    for (let key in target) {
        //如果是对象/数组嵌套
        if (checkType(target[key]) === 'Object' || checkType(target[key]) === 'Array') {
            result[key] = clone(target[key]);
        } else {
            result[key] = target[key];
        }
    }
    return result;
}
//数组拷贝
let arr = [1, 3, { username: ' kobe' }];
let arr3 = clone(arr);
arr3[2].username = 'ccc'
console.log(arr);  //[ 1, 3, { username: 'kobe' } ]
console.log(arr3); // [ 1, 3, { username: 'ccc' } ]

3、函数库lodash

var _ = require('lodash');
var obj1 = {
   a: 1,
   b: { f: { g: 1 } },
   c: [1, 2, 3]
};
var obj2 = _.cloneDeep(obj1);
console.log(obj1.b.f === obj2.b.f);
// false