前奏:在说明这两个类型之前先简单说一下“堆”和“栈”。
栈:由编译器自动分配和释放,如函数参数、局部变量、临时变量,返回值等等。
堆:由成员分配和释放,由程序员自己申请、自己释放。否则发生内存泄露。典型为使用new申请的堆内容。
栈空间一般大小固定,能够分配的空间比较小,由系统自动分配,速度较快。
堆空间适合不清楚所要的空间有多大的情况下,需要手工new出来,开销大,容易产生内存碎片。
基本数据类型
JavaScript中基本数据类型有5个 ,Number ,String, Boolean,Null ,Undefined
基本数据类型的值大小一般固定,所以保存在栈空间中,按值来访问。
引用数据类型
引用类型一般比较复杂,值的大小不固定,保存在堆空间中,而将指向堆内存的地址保存在栈空间中(地址大小是固定的)。
当查询某一引用类型的变量时,先去栈空间中找到其值的地址,然后根据地址去堆内存中找到该值,所以是按引用访问。
复制变量值
基本类型的变量在复制时,会在该变量上创建一个和原值一样的新值,然后将该值复制到新变量刚分配的空间中(栈内存)。
引用类型的变量在复制时,复制的是原值的指针,并将该指针写入到新变量刚分配的空间中(栈内存),实际上两个变量指向同一个值。
按值传递 (即形参是实参的副本)
按引用传递 (函数的形参接收实参的隐式引用,而不再是副本,对形参的修改会直接影响到实参)
借上图,按引用传递就是将 (obj1.obj地址)整体传进去。
JS中所有函数的参数传递都是按值传递
这句话已成为一个不争的事实。
下面说一下函数按值传参的内部原理:
对于基本类型的变量来说,会将该变量的值复制一份传入参数(在ECMAScribe中是函数的一个局部变量,保存在栈内存中),对该参数的操作不会影响到原变量。
对于引用类型的变量来说,会将该变量的值复制一份传入参数,但这个值指向堆内存,是一个地址,对该参数的操作会影响到原变量。
这么说,你可能又会弄混了,这不就是按引用传递么????
我们借用 JavaScript高级程序教程第三版 中一个例子来说明问题。
function setName(obj){
obj.name="Nicholas";
}
var person=new Object();
setName(person);
console.log(person.name); //输出 Nicholas
上面这个例子我们会搞混,这难道不是按引用传递么?
我们修改一下:
function setName(obj){
obj.name="Nicholas";
obj=new Object();
obj.name="Greg";
}
var person=new Object();
setName(person);
console.log(person.name); //输出的还是 Nicholas
下面我们来说明一下:
如果是按引用传递的,那么传递给setName参数obj的应该是 (person.地址)整体(可能说的不太严谨),在函数内部我们重新修改了一下 person.指针 的指向,让它指向一个新地址,并设置name为“Greg”,因此打印person.name应该是“Greg”,然而事实情况并不是。
如果是按值传递,那么传递给setName参数obj的应该是 “地址” 这个值而已,虽然和person的地址值一样,但两者没有任何关联,只是值相同而已。obj参数只是setName函数的一个局部变量,通过操作这个局部变量修改了person,后来obj重新指向了另一个地址,剩下的操作与person无关。
可能上面 (person.地址) 不太准确,但我实在想不到合适的比喻了。