浅谈js不同数据类型(基本/引用)在拷贝与传参时的不同
1.不同数据类型的拷贝方式:深拷贝与浅拷贝
在JavaScript中,数据的类型主要可以分为基本类型值和引用类型值。
基本数据类型指的是简单的数据段(主要有Undefined,Null,String,Boolean,Number),是按值访问的,因为可以操作保存在变量中的实际的值。
而引用类型指的是可能由多个值构成的对象,所以他的值是保存在类型中的对象(主要有Object,Array,Date,RegExp,Function等类型)。JavaScript与其他语言不同的是,他不允许直接访问内存中的位置,即不能直接操作这个对象(指前文的值)的内存空间。在操作一个对象时,实际上操作的是对象的引用而不是实际的值。
由此,JavaScript在复制变量值时,会根据变量的基本数据类型的不同,采用不同的拷贝机制,即深拷贝与浅拷贝。
深拷贝:当从一个变量复制另一个基本类型的值时,使用的机制。即在变量对象上创建一个新的值,然后把该值复制到为新变量新变量分配的位置上,新旧变量完全独立,互不影响,很好理解。如下例子:
1 | var num1 = 5; |
浅拷贝:当从一个变量复制另一个引用类型的值时,使用的机制。这时,新变量仍然会开辟一个新的内存空间,并把储存在老变量对象中的值复制复制进去。但不同的是,由于老变量对象是引用类型,因此老变量中的值实际上是一个指针,这个指针指向的是储存在堆中的一个对象。因此,这时新老对象的内存空间中的值,是同一个指针,因此他们引用的也是同一个对象,从而造成新老对象会相互影响,也不难理解。如下:
1 | var obj1 = new Object; |
2.不同数据类型的函数传参
ECMAScript中的所有函数的参数都是按值传递的。基本类型值的传递和基本类型变量的复制一样,而引用类型值的传递,和引用类型变量的赋值一样。
可以把ECMAScript函数的参数想象成(函数的)局部变量。
——《JavaScript高级程序设计 4.1.3节》
读到这里,您可能跟我一样开始困惑:变量的复制(访问)有按值和按引用的方式,而所有函数传参都是按值的方式,是否有些矛盾?
答:并不矛盾,按照《1.不同基本数据类型的拷贝方式:深拷贝与浅拷贝》中的说法,可以理解到:
对于基本数据类型的变量,他的值就直接存入栈空间,例如
var i = 5
如果说变量 i 的栈空间地址是 0x5001,那么 ox5001 中存放的值就是数字5 。按照C语言的理解,即&i = 5
对于引用类型的变量,他的创建需要以下几个过程(顺序不严谨):
- 收到需要创建一个引用类型变量的请求,例如 var obj = { name : paopao }
- 在计算机中分配一个堆内存 ,假设地址为 0x0001;
- 在计算机中分配一个栈内存,假设地址为0x50002;
- 将引用变量的值{ name : paopao } 存入堆内存地址的所在的空间中。
- 将堆内存地址存入栈内存中地址所在的空间中。
- 让被创建的变量obj指向这个栈内存
那么以上整个步骤,按照C的理解,可以解释为 : *&obj = { name : paopao } ,其中,&obj = 0x0001。
由此可知,即使函数传参是按值传递的,但由于引用类型中的值是地址,所以如果在函数中直接使用参数,仍然会改变原值,如:
1 | function setName(obj){ |
如果想避免由于引用类型值函数传参的这个特点,必须要重新分配变量,让两个引用类型变量的栈空间中的值是两个不同堆空间的地址(尽管这两个堆空间存放的值相同)。以下方法可以实现,但原理待验证
1 | function setName(obj){ |