变量、作用域和内存问题
JavaScript的变量和其他语言的变量有很大区别.JavaScript变量松散类型的本质,决定了它只是在特定时间用于保存特定值的一个名字而已.由于不存在定义某个变量必须要保存何种数据类型值的规则,变量的值及其数据类型可以在脚本的生命周期内改变.
基本类型和引用类型
ECMAScript变量可能包含两种不同数据类型的值:基本类型值和引用类型值
基本数据类型:Undefined.Null.Number.Boolean.String
这5种基本数据类型是按值访问的,因为可以操作保存在变量中实际的值.
引用类型的值是保存在内存中的对象.与其他语言不同,JavaScript不允许直接访问内存中的位置,也就是说不能直接操作对象的内存空间.在操作对象时,实际上实在操作对象的引用而不是实际的对象,为此,引用类型的值是按引用访问的.
(注:这种说法不严密,当复制保存着对象的某个变量时,操作的是对象的引用,但是为对象添加属性时,操作的是实际对象)
动态的属性
定义基本类型值和引用类型值的方式是类似的:创建一个变量并为该变量赋值.但对于引用类型的值,我们可以为其添加属性和方法,也可以改变和删除其属性和方法
举个栗子
var person=new Object();
person.name="jack";
console.log(person.name);//jack
在这个例子中我们创建了一个对象并将其保存了变量person中.然后我们为该对象添加了一个名为name的属性,并将"jack"字符串赋值给这个属性,然后我们通过控制台访问这个属性,成功打印"jack".如果对象不被销毁或者这个属性不被消除,则这个属性将一直存在.
复制变量值
除了保存的方式不同,在从一个变量向另一个变量复制基本类型值和引用类型值时,也存在不同.
基本类型值
var num1=5;
var num2=num1;
在上面的代码中,num1保存的值是5,当使用num1的值来初始化num2时,num2也保存了值5,num2和num1的值是完全独立的,因此,这两个变量和可以参与任何操作而不会互相影响.
如图所示
引用类型值
当从一个变量向另一个变量复制引用类型的值时,同样也会将存在在变量对象的值复制一份放到为新变量分配的空间里,但是,这个值的副本实际上是一个指针,而这个指针指向存储在堆中的一个对象.复制操作结束后,两个变量实际上将引用同一个对象.因此,改变其中一个变量,就会影响到另一个变量
var obj1=new Object();
var obj2=obj1;
obj1.name="rock";
alert(obj2.name); //"rock"
在上面的代码里,obj2的值为obj1.当我们为obj1设置了name属性并赋值"rock",但我们也可以用obj2来访问这个属性,因为这两个变量引用的都是同一个对象.
传递参数
ECMA中所有函数的参数都是按值传递的.
也就是说,把函数外部的值复制给函数内部的参数,就和把值从一个变量赋值到另一个变量一样.基本类型值的传递如同基本类型变量的复制一样,而引用类型值得传递,则如同引用类型变量的复制一样.
访问变量有按值和按引用两种方法,而参数只能按值传递.
在向参数传递基本类型的值时,被传递的值会被复制给一个局部变量.在向参数传递引用类型的值时,会把这个值在内存中的地址复制给一个局部变量,因此这个局部变量的变化会反映在函数的外部.
举个例子
function addTen(num) {
num += 10;
return num;
}
var count = 20;
var result = addTen(count);
alert(count); //20 没有发生改变
alert(result); //30
这里的函数addTen( )有一个参数num,而参数实际上是函数的的局部变量.在调用这个函数时,变量count作为参数被传递给函数,这个变量的值是20.于是,数值20被复制给参数num以便于addTen( )中使用.在函数内部,参数num的值被加上了10,但是这个变化并不影响函数外部的count变量.参数num与变量count互不相识,仅仅只是具有相同的值,假设num是按引用传递,那么变量count的值也将变成30.
但是如果使用对象,这个问题就不怎么好理解了
举个例子
function setName(obj){
obj.name="jack";
}
var person=new Object();
setName(person);
alert(person.name); //jack
以上代码中创建了一个对象.并保存在变量person中,然后这个变量被传递到setName( )函数中以后就被复制给obj.在这个函数内部,obj和person引用的是同一个对象.
换句话说,即使这个变量是按值传递的,obj也会按引用来访问同一个对象,所以,挡在函数内部为obj添加name数学后,函数外部的person也会有所反应,因为配送纸箱的对象在堆内存中只有一个,而且是全局对象.
疑点:在局部作用域中修改的对象会在全局作用域中反映出来,这是说明参数是按引用传递的吗?
为了证明对象是按值传递的,我们举个例子
function setName(obj){
obj.name="jack";
obj=new Object();
obj.name="luck";
}
var person=new Object();
setName(person);
alert(person.name); //jack
这个例子与上一个例子的区别是,在setName中添加了两行代码:一行为参数obj重新定义了一个对象,另一个代码为该对象定义了一个带有不同值的name属性.
把person传递给setName( )后,其name属性被设定为"jack",然后将一个新对象赋值给变量obj,同时将其name属性设置为"luck",但最后我们输出person.name,结果为"jack",如果person是按引用传递给参数obj的,那么person就会被自动修改为指向其name属性值为"luck"的新对象,结果显然不是如此.
这个例子说明了,即使在函数内部修改的参数的值,但原始的引用任然保持未变.实际上,当在函数内部重写obj时.这个变量就引用的就是一个局部变量了.而这个局部变量会在函数执行完毕后被立即销毁.