JavaScript中的深拷贝和浅拷贝

文章目录

  • 数据类型
  • 赋值
    • 1. 基本类型(存放在栈中)的赋值:传值
    • 2. 引用类型(存放在堆内存中的对象)的赋值:传址
  • 深拷贝与浅拷贝
    • 浅拷贝
      • 1. Object.assign()
      • 2. slice() 、concat() 、Array.from()
      • 3. 扩展运算符 `...`
    • 深拷贝
      • 1. JSON.parse(JSON.stringify(...)) (简单但有缺陷)
      • 2. 手写递归方法 (复杂但很完善)
      • 3. 第三方库


数据类型

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

  1. 基本类型String, Number, Boolean, Null, UndefinedSymbol(ES6)
  2. 引用类型Object

基本类型的特点:直接存储在栈(stack)内存中的数据
引用类型的特点:存储的是该对象在栈中引用,真实的数据存放在堆(heap)内存中

JavaScript中的深拷贝和浅拷贝_第1张图片
引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。
当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。


赋值

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

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

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

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

JavaScript中的深拷贝和浅拷贝_第2张图片

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"}

JavaScript中的深拷贝和浅拷贝_第3张图片


深拷贝与浅拷贝

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

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

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

JavaScript中的深拷贝和浅拷贝_第4张图片

浅拷贝

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

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); // 2
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) // 深拷贝

你可能感兴趣的:(JavaScript,面试必看,javascript,深拷贝,浅拷贝)