1.不同数据类型的拷贝方式:深拷贝与浅拷贝

在JavaScript中,数据的类型主要可以分为基本类型值和引用类型值。

基本数据类型指的是简单的数据段(主要有Undefined,Null,String,Boolean,Number),是按值访问的,因为可以操作保存在变量中的实际的值。

而引用类型指的是可能由多个值构成的对象,所以他的值是保存在类型中的对象(主要有Object,Array,Date,RegExp,Function等类型)。JavaScript与其他语言不同的是,他不允许直接访问内存中的位置,即不能直接操作这个对象(指前文的值)的内存空间。在操作一个对象时,实际上操作的是对象的引用而不是实际的值。

由此,JavaScript在复制变量值时,会根据变量的基本数据类型的不同,采用不同的拷贝机制,即深拷贝与浅拷贝。

深拷贝:当从一个变量复制另一个基本类型的值时,使用的机制。即在变量对象上创建一个新的值,然后把该值复制到为新变量新变量分配的位置上,新旧变量完全独立,互不影响,很好理解。如下例子:

1
2
3
var num1 = 5;
var num2 = num1 + 5;
alter(num1); // 5

浅拷贝:当从一个变量复制另一个引用类型的值时,使用的机制。这时,新变量仍然会开辟一个新的内存空间,并把储存在老变量对象中的值复制复制进去。但不同的是,由于老变量对象是引用类型,因此老变量中的值实际上是一个指针,这个指针指向的是储存在堆中的一个对象。因此,这时新老对象的内存空间中的值,是同一个指针,因此他们引用的也是同一个对象,从而造成新老对象会相互影响,也不难理解。如下:

1
2
3
4
var obj1 = new Object;
var obj2 = obj1;
obj2.name = 'logistic';
alter(obj1.name); // logistic

2.不同数据类型的函数传参

ECMAScript中的所有函数的参数都是按值传递的。基本类型值的传递和基本类型变量的复制一样,而引用类型值的传递,和引用类型变量的赋值一样。

可以把ECMAScript函数的参数想象成(函数的)局部变量。

——《JavaScript高级程序设计 4.1.3节》

读到这里,您可能跟我一样开始困惑:变量的复制(访问)有按值和按引用的方式,而所有函数传参都是按值的方式,是否有些矛盾?

答:并不矛盾,按照《1.不同基本数据类型的拷贝方式:深拷贝与浅拷贝》中的说法,可以理解到:

对于基本数据类型的变量,他的值就直接存入栈空间,例如

var i = 5

如果说变量 i 的栈空间地址是 0x5001,那么 ox5001 中存放的值就是数字5 。按照C语言的理解,即&i = 5

对于引用类型的变量,他的创建需要以下几个过程(顺序不严谨):

  1. 收到需要创建一个引用类型变量的请求,例如 var obj = { name : paopao }
  2. 在计算机中分配一个堆内存 ,假设地址为 0x0001;
  3. 在计算机中分配一个栈内存,假设地址为0x50002;
  4. 将引用变量的值{ name : paopao } 存入堆内存地址的所在的空间中。
  5. 将堆内存地址存入栈内存中地址所在的空间中。
  6. 让被创建的变量obj指向这个栈内存

那么以上整个步骤,按照C的理解,可以解释为 : *&obj = { name : paopao } ,其中,&obj = 0x0001。

由此可知,即使函数传参是按值传递的,但由于引用类型中的值是地址,所以如果在函数中直接使用参数,仍然会改变原值,如:

1
2
3
4
5
6
function setName(obj){
obj.name = 'paopao'
}
var lover = new Object();
setName(lover);
alter(lover.name) //paopao

如果想避免由于引用类型值函数传参的这个特点,必须要重新分配变量,让两个引用类型变量的栈空间中的值是两个不同堆空间的地址(尽管这两个堆空间存放的值相同)。以下方法可以实现,但原理待验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function setName(obj){
//方法一 ... : 构造字面量对象时,进行克隆或者属性拷贝(ECMAScript 2018规范新增特性)
var newObj = {...obj}
//方法二 JSON.parse(JSON.stringify()) :可以进行深拷贝 ?待验证
var newObj = JSON.parse(JSON.stringify(obj))
//方法三 Object.assign({},obj),似乎是浅拷贝方法,若有多层则会改变原值 ? 待验证
var newObj = Object.assign({},obj)
newObj.name = 'paopao';
}

var lover = new Object();
setName(lover);

alter(lover.name) //undefined