你真的理解深、浅拷贝么?函数传参原理详解(实参与形参的关系)

本文介绍了JavaScript中的深拷贝和浅拷贝概念,包括JSON.parse()与JSON.stringify()、structuredClone()方法以及自定义递归函数实现深拷贝。同时,文章探讨了函数传参时实参与形参的关系,特别是对引用类型参数的影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、深浅拷贝定义

1、深拷贝

定义:对象的深拷贝是指其属性与其拷贝的源对象的属性不共享相同的引用(指向相同的底层值)的副本。因此,当你更改源或副本时,可以确保不会导致其他对象也发生更改;也就是说,你不会无意中对源或副本造成意料之外的更改。

2、浅拷贝

定义:对象的浅拷贝是其属性与拷贝源对象的属性共享相同引用(指向相同的底层值)的副本。因此,当你更改源或副本时,也可能导致其他对象也发生更改——也就是说,你可能会无意中对源或副本造成意料之外的更改。

二、Js中的浅拷贝

在 JavaScript 中,所有标准的内置对象复制操作(展开语法[...]、Array.prototype.concat()、Array.prototype.slice()、Array.from()、Object.assign() 和 Object.create())创建的是浅拷贝。

const array1 = [{ref: 111}, 'b', 'c'];
const array2 = ['d', 'e', 'f'];
const array3 = array1.concat(array2);

array3[0].ref = 222;
array3[1] = "bbb";
console.log("array3:", array3, "array1:", array1);
//array3: Array [Object { ref: 222 }, "bbb", "c", "d", "e", "f"] 
//array1: Array [Object { ref: 222 }, "b", "c"]

注意:对以上列举的函数或者是直接的赋值语句,直接修改拷贝对象中的基本数据类型相关的数据,是不会影响原始值的,只有修改引用数据类型的属性才有可能修改原始值。

三、实现深拷贝

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

如果一个 JavaScript 对象可以被序列化,则存在一种创建深拷贝的方式:使用 JSON.stringify 将该对象转换为 JSON 字符串,然后使用 JSON.parse()将该字符串转换回(全新的)JavaScript 对象。

​ 【序列化】:序列化是将一个对象或数据结构转换为适合网络传输或存储的格式(如数组缓冲区或文件格式)的过程。例如在 JavaScript 中,你可以通过调用 JSON.stringify() 函数将某个值序列化为 JSON 格式的字符串CSS值可以通过调用 。CSS用StyleDeclaration.getPropertyValue() 函数来序列化。 ​

let list = ["noodles", { list: ["flour", "water"] }];
let listDeepcopy = JSON.parse(JSON.stringify(list));

listDeepcopy [1].list = ["water"];
console.log(list[1].list);
// Array(2) ["flour", "water"]

从上面的代码可以看出,因为深拷贝与其源对象不共享引用,所以对深拷贝所做的任何更改都不会影响源对象。

然而,虽然上面代码中的对象足够简单,可以序列化,但许多 JavaScript 对象根本不能序列化——例如,函数(带有闭包)、Symbol、在 HTML DOM API 中表示 HTML 元素的对象、递归数据以及许多其他情况。在这种情况下,调用 JSON.stringify() 来序列化对象将会失败。所以没有办法对这些对象进行深拷贝。

const student1 = {
    name: null,
    age: undefined,
    sum: function (a, b) {
        return a + b;
    },
    date: new Date(),

}

const student2 = JSON.parse(JSON.stringify(student1));
console.log(student2);
/*
Object
 date: "2023-06-29T03:36:50.290Z"
 name: null
 [[Prototype]]: Object
*/ 

 2、structuredClone()

对于可序列化的对象,你也可以使用 structuredClone() 方法来创建深拷贝。structuredClone() 的优点是允许源代码中的可转移对象被转移到新的副本,而不仅仅是克隆。但是请注意,structuredClone() 不是 JavaScript 语言本身的特性——相反,它是浏览器和任何其他实现了 window 这样全局对象的 JavaScript 运行时的一个特性。调用 structuredClone() 来克隆一个不可序列化的对象会提示失败,与调用 JSON.stringify() 来序列化它会失败相同。

  • ​全局的 structuredClone() 方法使用结构化克隆算法将给定的值进行深拷贝。
  • 该方法还支持把原始值中的可转移对象转移到新对象,而不是把属性引用拷贝过去。
  • 可转移对象与原始对象分离并附加到新对象;它们不可以在原始对象中访问被访问到。 ​
const original = { name: "MDN", sum: function(){} };
original.itself = original;

const clone = structuredClone(original);

console.log(clone !== original); // true
console.log(clone.name === "MDN"); // true
console.log(clone.itself === clone); // true

/*
报错:
Uncaught DOMException: Failed to execute 'structuredClone' on 'Window': function(){} could not be cloned.
*/

3、自定义递归深度克隆函数

        const deepClone = obj => {
            if (typeof obj !== 'object' || obj === null)
            {
                return obj;
            }

            let result = Array.isArray(obj) ? [] : {};

            for (let key in obj)
            {
                if (obj.hasOwnProperty(key))
                {
                    result[key] = deepClone(obj[key]);
                }
            }

            return result;
        }

        const original  =  {
            name: null,
            age: undefined,
            sum: function (a, b) {
                return a + b;
            },
            member: {
                name1: 1,
                name2: 2,
            }
        }

        const deepOriginal = deepClone(original);
        deepOriginal.member.name1 = 111;

        console.log(original, deepOriginal);

打印结果:

 4、lodash库---cloneDeep()

        cloneDeep底层实现也是通过递归实现的,如果项目中引入了lodash库直接用现成的API,如果项目没有引入,为了不影响项目的体积,自己定义一个递归函数实现深拷贝比较推荐。

5、jQuery 中的 $.extend (添加true就是深拷贝,不添加就是浅拷贝)

四、函数传参原理详解(实参与形参的关系)

讨论JavaScript的传参原理之前,先看一段代码:

var num = 0;
var obj = {};

function testNum(parame)
{
    parame = 1;
}
 
function testObj(parame)
{
    parame.age = 18;
}
 
testNum(testA);
testObj(testB);

console.log(num);//输出0
console.log(obj);//输出{age:18}

结论:对形参进行修改,只能说有可能会影响原变量。

1、实参为普通变量

按值传递是一种比较容易理解又使用比较广泛的传参方式,这种方式在传参的时候,在内存中会直接把实参的值复制一份再把副本传递给形参,对于形参的修改并不会影响到实参。

 

2、实参为引用数据

引用类型的地址存放在栈中,对应的值存放在堆中。当传参发生的时候,值类型会直接将栈中的值进行复制,形参和实参此时实际上是两个完全不相干的变量。对于引用类型,传参发生时,会将实参变量位于栈中的地址进行复制,此时栈中会有两个指向同一个堆地址的指针,因此此时直接修改形参是会印象实参的值的

考虑直接修改形参,但不会影响实参的情况:

var obj = { name: "小明" };

function Obj(e)
{
    e = {};
}

 
Obj(obj);

console.log(obj); // { name: "小明" }

当我们直接将形参替换成另一个值,在内存中会形成下图的情况:


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值