JavaScript中的深拷贝和浅拷贝


数据类型

在 JavaScript 中数据分为基本数据类型(基本类型)和对象数据类型(引用类型)。

  1. 基本类型StringNumberBooleanNullUndefinedSymbol(ES6)、BigInt(ES11)
  2. 引用类型Object

基本类型的特点:直接存储在栈(stack)内存中的数据
引用类型的特点:指针存储在栈内存中,值存储在堆(heap)内存中

在这里插入图片描述
引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。
当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。


赋值

1. 基本类型(存放在栈中)的赋值:传值

基本数据类型是指存放在栈中的简单数据段,数据大小确定,内存空间大小可以分配,它们是直接按值存放的,所以可以直接按值访问

var a = 10;
var b = a; // 传值
b = 20;

console.log(a); // 10
console.log(b); // 20

在这里插入图片描述

2. 引用类型(存放在堆内存中的对象)的赋值:传址

引用类型是存放在堆内存中的对象,变量其实是保存的在栈内存中的一个指针(保存的是堆内存中的引用地址),这个指针指向堆内存。
引用类型数据在栈内存中保存的实际上是对象在堆内存中的引用地址。通过这个引用地址可以快速查找到保存中堆内存中的对象。
当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。

 var obj1 = {
    'age' :  18
};
var obj2 = obj1; // 传址
obj2.name = "zhangsan";

console.log('obj1',obj1); // {age: 18, name: "zhangsan"}
console.log('obj2',obj2); // {age: 18, name: "zhangsan"}

在这里插入图片描述

总结:

声明变量时不同的内存地址分配:

  • 简单类型的值存放在栈中,在栈中存放的是对应的值
  • 引用类型对应的值存储在堆中,在栈中存放的是指向堆内存的地址

不同的类型数据导致赋值变量时的不同:

  • 简单类型赋值,是生成相同的值,两个对象对应不同的地址
  • 复杂类型赋值,是将保存对象的内存地址赋值给另一个变量。也就是两个变量指向堆内存中同一个对象


深拷贝与浅拷贝

深拷贝和浅拷贝最根本的区别在于是否是真正获取了一个对象的拷贝实体,而不是引用。
浅拷贝只拷贝一层对象的属性,而深拷贝则递归拷贝了所有层级。

深拷贝在计算机中开辟了一块新的内存地址用于存放拷贝的对象

浅拷贝仅仅是指向被拷贝的内存地址,如果原地址中对象被改变了,那么浅拷贝出来的对象也会相应改变

在这里插入图片描述

浅拷贝

如果属性是基本类型,拷贝的就是基本类型的值;
如果属性是内存地址(引用类型),拷贝的就是内存地址 ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。

1. Object.assign()

只适用于Object对象

var obj = {
    a: {
        b: 1
    },
    c: 3
};
var newObj = Object.assign({}, obj); // Object对象浅拷贝

newObj.a.b = 2; // 修改的newObj.a是引用类型,会影响原对象obj.a
console.log(obj.a.b); // 2

newObj.c = 4; // 修改的newObj.c是基本类型,不会影响原对象obj.c
console.log(obj.c); // 3

2. slice() 、concat() 、Array.from()

只适用于Array数组
不修改原数组,只返回一个浅复制了原数组中的元素的一个新数组

var arr = [1, 2, [3, 4]];
var newArr = arr.slice(); // Array数组浅拷贝
// var newArr = arr.concat(); // 同理
// var newArr = Array.from(arr); // 同理

newArr[2][1] = 5; // 修改的newArr[2]是引用类型,会影响原数组arr[2]
console.log(arr[2][1]); // 5

newArr[0]=6;  // 修改的newArr[0]是基本类型,不会影响原数组arr[0]
console.log(arr); // 5

3. 扩展运算符 ...

适用于Object对象和Array数组

var obj = {
    a: {
        b: 1
    }
};
var newObj = { ...obj }; // Object对象浅拷贝
newObj.a.b = 2;
console.log(obj.a.b); // 2
var arr = [1, 2, [3, 4]];
var newArr = [...arr]; // Array数组浅拷贝
newArr[2][1] = 5;
console.log(arr[2][1]); // 5

深拷贝

深拷贝则是开辟新的栈,两个对象对应两个不同的地址,修改一个对象的属性,不会改变另一个对象的属性。

可以利用递归思想(遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝)来做,及省性能,又不会发生引用。

1. JSON.parse(JSON.stringify(…)) (简单但有缺陷)

  • 原理: 用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。
  • 缺点:不能深拷贝含有undefined、function、symbol值的对象。
  • 优点:用法简单粗暴,已经满足90%的使用场景了。
var obj = {
    a: {
        b: 1
    },
    c: 3
};
var newObj = JSON.parse(JSON.stringify(obj));
newObj.a.b = 2;
console.log(obj.a.b); // 1
newObj.c = 4;
console.log(obj.c); // 3

/** -------------------------- **/

var arr = [1, 2, [3, 4]];
var newArr = JSON.parse(JSON.stringify(arr));
newArr[2][1] = 5;
console.log(arr[2][1]); // 4
newArr[0] = 6;
console.log(arr[0]); // 1

2. 手写递归方法 (复杂但很完善)

常用、常考

// 定义检测数据类型的功能函数
function checkedType(target) {
    return Object.prototype.toString.call(target).slice(8, -1);
}

// 实现深度克隆---对象/数组
function deepCopy(target) {
    var result; // 初始化变量result 成为最终克隆的数据
    var targetType = checkedType(target); // 判断拷贝的数据类型
    if (targetType === 'Object') {
        result = {};
    } else if (targetType === 'Array') {
        result = [];
    } else {
        return target;
    }
    // 遍历目标数据
    for (let key in target) {
        // 获取遍历数据结构的每一项值。
        let value = target[key];
        // 判断目标结构里的每一值是否存在对象/数组
        if (checkedType(value) === 'Object' || checkedType(value) === 'Array') {
            // 继续递归遍历获取到value值
            result[key] = deepCopy(value);
        } else {
            // 获取到value值是基本的数据类型或者是函数。
            result[key] = value;
        }
    }
    return result;
}

var obj1 = {
    'name': 'zhangsan',
    'age': 18,
    'language': [1, [2, 3],
        [4, 5]
    ]
};
var obj2 = deepCopy(obj1);
obj2.name = "lisi";
obj2.language[1] = ["二", "三"];

console.log('obj1', obj1); // {name: "zhangsan", age: 18, language: [1, [2, 3], [4, 5]]}
console.log('obj2', obj2); // {name: "lisi", age: 18, language: [1, ["二", "三"], [4, 5]]}

3. 第三方库

lodash

const lodash = require('lodash');

lodash.clone(obj) // 浅拷贝
lodash.cloneDeep(obj) // 深拷贝

jQuery

$.extend(obj1,obj2) // 浅拷贝 默认
$.extend(true,obj1,obj2) // 深拷贝
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猫老板的豆

你的鼓励将是我创作的最大动力~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值