js中的浅拷贝和深拷贝

js 中的数据存储方式

js 中的数据类型分为简单数据类型和复杂数据类型。

  • 简单数据类型:简单数据类型指的是简单的数据段。简单数据类型有 string、number、boolean、undefined、null、symbol(ES6) 。简单数据类型在存储时变量中存储的是值本身,因此又叫做值类型或基本数据类型。
  • 复杂数据类型:复杂数据类型指其值可由多个不同类型值构成的对象。通过 new 关键字创建的对象(系统对象、自定义对象),如 Object、Array、Date 等都是复杂数据类型。复杂数据类型在存储时变量中存储的仅仅是地址(引用),因此又叫做引用数据类型

js 中数据存储的两种方式

  • 栈:由操作系统自动分配和释放用以存放变量的值的内存空间。值类型数据在栈中存储的是值本身;引用类型在栈中存储的是指向内容的地址(引用)
  • 堆:用于存储复杂数据类型得值(内容)的内存空间。一般由程序员分配释放,若程序员不释放则由垃圾回收机制回收。

内存分配的具体图示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IbOzepfL-1611240901042)(https://s3.ax1x.com/2021/01/21/s4xEy8.png)]

js 中的参数传递

简单数据类型传参

简单数据类型在传参时是传值,目标参数(形参)发生改变不会影响原来的参数(实参)。如下:

var num0 = 20;
var num1 = num0; 
// 步骤1:给num0 num1分别开辟一个内存空间,并将num0的值传给num1
num1 = 30; // 步骤2:将num1的值改为30,num0的值不变
function fn(a) {
	a = 40; // 步骤4:将a的值改为40,Num0的值也不会改变
	console.log(a); // ->40
}
fn(num0); // 步骤3:给形参a开辟一个内存空间并将num0的值传给a
// 步骤5:函数执行完毕形参a的内存空间释放。
console.log(num0,num1); // ->20,30

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8RoTqhDY-1611242765003)(https://s3.ax1x.com/2021/01/21/s5pmwD.png)]

复杂数据类型传参

复杂数据类型在传参时也是传值(但是这个值是变量保存的复杂数据类型的引用),目标参数(形参)发生改变也会影响原来的参数(实参)。如下:

var obj0 = {
        name: '冯绍峰',
        index: 0,
        girlFriend: {
            gfName: '赵丽颖',
            gfAge: 23,
        }
    }; 
// 步骤1:将obj0的内容保存在推内存并将该地址存储在obj0对象指向的栈内存中。
var obj1 = obj0; 
// 步骤2:给obj1开辟一个栈内存空间,并将obj0在栈内存保存的地址(引用)传给它
// 他们保存的引用相同指向同一推内存。
obj1.name = '老冯'; 
obj1.girlFriend.gfName = '小赵';
console.log(obj0.name,obj0.girlFriend.gfName); //->'老冯' '小赵'
// 步骤3:两个对象保存的地址指向相同的内存空间所以obj0也会改变
function fn(a) {
	a.name = '二叔'; 
	a.girlFriend.gfName = '六姑娘';
	// 步骤5:两个对象保存的地址指向相同的内存空间所以obj0也会改变
}
fn(obj0); // 步骤4:给a开辟一个栈内存空间,并将obj0在栈内存保存的地址(引用)传给它
// 步骤6:函数执行完毕形参a的内存空间释放。
console.log(obj0.name,obj0.girlFriend.gfName); // ->'二叔' '六姑娘'

在这里插入图片描述

在没有看过 JavaScript 高级程序设计时,我一直自认为将复杂数据类型变量的引用复制给函数形参就算是传递引用的传参方式。但是在看了相关内容后其实换位思考一下说是值传递的传参方式也是可以的(其实只是说法上的差异吧,百度一下大家都分值传递和引用传递,管他们怎么说,知道原理就好。)。书中有一句话很重要:访问变量有按值和按引用两种方式,但是参数只能按值传递。

浅拷贝和深拷贝

浅拷贝:浅拷贝只是拷贝一层,更深层次对象级别的只拷贝引用。

  1. 使用 for 循环实现浅拷贝。
  2. 使用 js 的 Object.assign() 方法实现浅拷贝。
  3. 在 jQuery 中可以使用 extend() 方法。

深拷贝:拷贝多层,每一级别的数据都会拷贝。

  1. 利用递归封装深拷贝函数(方法)
  2. 在 jQuery 中可以使用 extend() 方法。
  3. 还有 json 方法没学。

浅拷贝和浅拷贝原理

注意:以下均将函数(方法)传参过程的分析省略,但从图中可看出。

以下所说的第一轮拷贝是指拷贝第一个源对象,第二轮拷贝是指拷贝第二个源对象。(主要是本人已经写过一篇有关 extend() 方法的笔记,所以索性借用里面的代码案例和思维图。)
extend() 方法笔记:jQuery中的 extend() 方法

浅拷贝

对于直接两个复杂数据类型相互赋值(即不涉及参数传递的拷贝)不做分析。targetObj = obj0; tagerObj = obj1;

图1:声明三个对象(复杂数据类型)变量,在栈内存开辟三个内存空间分别存放三个指向不同堆内存的地址。在堆内存中如果属性是复杂数据类型则存储一个指向其他内存空间的地址。(如 #ccaa00 , #ccdc00)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ggryva3o-1611251710262)(https://s3.ax1x.com/2021/01/22/s5EvOs.png)]

图2:进行第一轮浅拷贝时,将 obj0(copyObj) 的所有内容都拷贝保存到 targetObj(newObj)的堆内存空间中,注意此时 targetObj.girlFriend 对象并没有重新开辟内存空间而是将 obj0.girlFriend 保存的内容(地址)拷贝过来,所以与 obj0.girlFriend 指向同一内存空间。

s5VSwq.png

图3:进行第二轮浅拷贝时,将 obj1(copyObj) 的所有内容都复制保存到 targetObj (newObj)的堆内存空间中(重复的属性将重新赋值),注意此时将 obj1.girlFriend 保存的内容(地址)拷贝过来对 targetObj.girlFriend 重新赋值,所以 targetObj.girlFriend 与 obj1.girlFriend 指向同一内存空间。

s5Ezmn.png
图4:改变两个属性值后出现的情况如下。
s5Ejyj.png

深拷贝

图5:进行第一轮深拷贝时,将 obj0 (copyObj) 的所有内容都复制保存到 targetObj(newObj)的堆内存空间中,此时 obj0.girlFriend 对象是复杂数据类型,系统会重新开辟内存空间并将其内容拷贝至新的内存空间,targetObj.girlFriend 中则保存了该新空间的地址 (#ccaaff) 。
s5EXlQ.png

图6:进行第二轮深拷贝时,将 obj1(copyObj) 的所有内容都复制保存到 targetObj (newObj)的堆内存空间中(重复的属性将重新赋值)。对于 girlFriend 属性是复杂对象则会根据地址(#ccaaff)找到相应的内存空间并将 obj1.girlFriend 的内容拷贝给 targetObj.girlFriend (重复的属性将重新赋值)
s5VifU.png
图7:改变两个属性值后出现的情况如下。
在这里插入图片描述

演示验证

注意:判断两个对象是否指向同一地址可以使用 “===” 我下面根据结果去验证就显得有点笨了🐷。

代码的第一公共部分:声明使用到的三个对象。

// 使用的三个对象
 var obj0 = {
        name: '冯绍峰',
        index: 0,
        girlFriend: {
            gfName: '赵丽颖',
            gfAge: 23,
        }
    };
    var obj1 = {
        name: '猪八戒',
        age: 28,
        girlFriend: {
            gfName: '嫦娥',
            gfWeight: 100,
        }
    };
    // 声明一个空对象来接收 copy 得到的对象(target)
    var targetObj = {}

浅拷贝

1、浅拷贝的前两种方式

方式1:

// 1、使用 for 循环实现浅拷贝
    function shallowCopy(newObj, copyObj) {
        for (let k in copyObj) {
            newObj[k] = copyObj[k];
        }
    };
    shallowCopy(targetObj, obj1); // 拷贝 obj1
    console.log(targetObj); // ->拷贝后输出
    targetObj.name = '吕布';
    targetObj.girlFriend.gfName = '貂蝉';
    console.log(targetObj); // ->改变后输出
    console.log(obj1); // ->输出此时的源对象

结果1:
在这里插入图片描述[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HQTsnxRJ-1611166245387)(https://s3.ax1x.com/2021/01/21/sffrbF.png)]

方式2:

// 2、使用assign()方法
    // Object.assign() 方法用于将所有可枚举属性的值从一个或多个源对象分配到目标对象。它将返回目标对象。
    // 语法:Object.assign(target, ...sources);
    Object.assign(targetObj, obj0, obj1);
    console.log(targetObj);
    targetObj.name = '吕布';
    targetObj.girlFriend.gfName = '貂蝉';
    console.log(targetObj);
    console.log(obj1);

结果2:
在这里插入图片描述在这里插入图片描述

3、使用 extend() 方法实现浅拷贝

    $.extend(targetObj, obj0, obj1);
    console.log(targetObj);
    targetObj.name = '吕布';
    targetObj.girlFriend.gfName = '貂蝉';
    console.log(targetObj);
    console.log(obj1);

结果与方式2的结果一样。

深拷贝

1、使用递归实现深拷贝

function deepCopy(newObj, copyObj) {
        for (var k in copyObj) {
            if (copyObj[k] instanceof Array) {
                newObj[k] = []; // 相当于如果是复杂数据类型则另外开辟空间存储 target[k]的属性值
                deepCopy(newObj[k], copyObj[k]); // 将 copyObj[k] 的内容拷贝给 target[k]
            } else if (copyObj[k] instanceof Object) {
                newObj[k] = {};
                deepCopy(targetObj[k], copyObj[k]);
            } else {
                newObj[k] = copyObj[k];
            }
        }
    };
    deepCopy(targetObj, obj0);
    // deepCopy(targetObj,obj1);// 如果想模仿下面的结果可以调用两次该函数
    console.log(targetObj);
    targetObj.name = '吕布';
    targetObj.girlFriend.gfName = '貂蝉';
    console.log(targetObj);
    console.log(obj0);

注意一定要先判断是否是数组类型,因为 数组类型的数据 instanceof Object 也为true。

在这里插入图片描述

2、使用 extend() 方法实现深拷贝

    $.extend(true, targetObj, obj0, obj1);
    console.log(targetObj);
    targetObj.name = '吕布';
    targetObj.girlFriend.gfName = '貂蝉';
    console.log(targetObj);
    console.log(obj1);

在这里插入图片描述

总结

浅拷贝:只拷贝一层。在遇到复杂数据类型的属性时,它不会判断并重新开辟新的内存去存储该属性的值,而是直接将源对象存储该属性的地址拷贝给目标对象。这样两者的该属性均指向同一个内存。
深拷贝:拷贝多层,每一级别的数据都会拷贝。在拷贝时每个属性时判断是否是复杂数据类型,如果是则重新开辟内存空间;并将源对象的该属性值拷贝至该空间,并将新地址保存到目标对象的对应属性中。但是若在此过程又出现复杂数据类型,则不断重复(递归)。

熬夜总结出来并写的笔记如果理解上或者内容有误希望大佬指出。不过我觉的没几个人有耐心看完😂。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 1024 设计师:白松林 返回首页