JavaScript的变量与其它语言的变量有很大的区别。JavaScript变量是松散型(不强制类型)本质,决定了它只是在特定的时间用保存特定值的一个名字而已。由于不存在定义某个变量必须要保存何种数据类型值的规则,变量的值及其数据类型可以在脚本的生命周期内改变。
一、变量及作用域
1、基本类型和引用类型
ECMAScript变量可以包含两种不同的数据类型的值:基本类型值和引用类型值。基本类型值指的是那些保存在栈内存中的简单数据段,即这种值完全保存在内存中的一个位置。而引用类型值则是指那些保存在堆内存中的对象,意思是变量中保存的实际上只是一个指针,这个指针指向内存中的另外一个位置,该位置保存对象。
将一个值赋给变量时,解析器必须确定这个值是基本类型值,还是引用类型值;基本类型值有:Undefined,Null,Boolean,Number,String。这些类型在内存中分别占有固定大小的空间,他们的值保存在栈空间,我们通过按值来访问的。
如果赋值是引用类型的值,则必须在堆内存中为这个值分配空间。由于这种值的大小不固定,因此不能把它们保存在栈内存中。但内存地址大小固定的,因此可以将内存地址大小保存在栈内存中。这样当查询引用类型变量时,先从栈中读取内存地址,然后通过地址找到堆中的值。
2、动态属性
定义基本类型值和引用类型值的方法是相似的:创建一个变量并为该变量赋值。但是,当这个值保存在变量中以后,对不同类型的值可以执行的操作则大相径庭。
var pesson=new Object(); //创建引用类型
pesson.name="boke"; //新增一个属性
alert(pesson.name); //输出
如果基本类型值添加属性:
var pesson="boke"; //创建一个基本类型
pesson.age=29; //给基本类型添加属性
alert(pesson.age); //undefined
3、复制变量值
在变量复制方面,基本类型和引用类型也有所不同。基本类型复制的是值的本身,而引用类型赋值的是地址。
var box="Lee"; //在栈内存中生成一个box "Lee";
var box2=box; //在栈内存再生成一个box2 "Lee";
box2虽然是box的一个副本,但从图可以看出,它们是独立的。也就是说,两个变量分别操作互不影响。
var box=new Object(); //创建一个引用类型
box.name="Lee";
var box2=box;
在引用类型中,box2其实就是box,因为他们指向的是一个对象。如果这个对象name属性的值修改,则另外一个也会被修改。
4、传递参数
ECMAScript中所有函数的参数都是按值传递的。
function addTen(num){
num += 10;
return num;
}
var count = 20;
var result = addTen(count);
alert(count); //20 没有变化
alert(result); //30
传递的参数是一个基本类型,而函数里面的num是一个局部变量,和外面的的count没有任何关系。
下面一个引用类型的例子。
function setName(obj){
obj.name="bo";
}
var persson = new Object();
setName(persson);
alert(persson.name); //bo
如果按引用传递的话,那么函数里面的那个变量将会是全局变量,在外部也可以访问。
5、检测类型
要检测一个变量的类型,我们可以用过typeof运算符来判别:
var name = "类型";
alert(typeof name); //string
虽然typeof运算符在检查基本的类型的时候非常好用。但检测引用类型的时候,它就不是那么好用。通常,我们并不向知道它是不是对象,而想知道它是什么类型的对象。因为数组也是object,null也是object类型等等;
这时我们要采用instanceof运算符来查看。
var num = [1,2,3];
alert(num instanceof Array); //true 是否是数组
var num2={};
alert(num2 instanceof Object); //true 是否是对象
var num3=/g/;
alert(num3 instanceof RegExp); //true 是否是正则表达式
var num4=new String("Lee");
alert(num4 instanceof String); //true 是否是字符串
当instanceof 检测基本类型的值,会返回false;
5、执行环境及作用域
执行环境是javascript中最重要的一个概念。执行环境定义了变量或函数有权访问的其它数据,决定了他们各自的行为。
全局执行环境是最外围的执行环境。在WEB浏览器中,全局执行环境被认为是window对象。因此所有全局变量和函数都作为window对象的属性和方法创建的。
var num = 123; //声明一个全局的变量
function sum(){
alert(num); //全局变量可以在函数内部访问
}
function sum2(){
alert(window.num); //全局变量即window属性
}
sum(); //调用函数
window.sum2(); //全局函数即window函数
当执行环境中的所有代码执行完毕后,该环境被销毁,保存在其中的所有变量和函数定义也随之销毁。如果在全局环境下,需要程序执行完毕,或则网页关闭才销毁。
每个执行环境都有一个与之关联的变量对象,就好比全局的window可以调用变量和属性一样。局部的环境也是一个类似window的变量对象,环境中定义的所有变量和函数都保存在这个对象中。
var num = 100;
function setNum(){
var num = 200;
alert(num);
}
setNum(); //200
alert(num); //100
6、没有块级作用域
块级作用域表示诸如if语句等有花括号封闭的代码块,所以,支持条件判断定义变量。
if(true){
var num = 100;
}
在if语句中定义的变量num,在if语句执行完毕后被销毁。
内存问题
JavaScript具有自动垃圾收集机制,也就是说,执行环境会负责管理代码执行过程中使用的内存。其他语言比如C和C++,必须手工跟踪内存使用情况,适时的释放,否则会造成很多问题。而JavaScript则不需要这样,它会自行管理内存分配及无用内存的回收。
JavaScript最常用的垃圾收集方式是标记清除。垃圾收集器会在运行的时候给存储在内存中的变量加上标记。然后,它会去掉环境中正在使用变量的标记,而没有被去掉标记的变量将被视为准备删除的变量。最后,垃圾收集器完成内存清理工作,销毁那些带标记的值并回收他们所占用的内存空间。
垃圾收集器是周期性运行的,这样会导致整个程序的性能问题。比如IE7以前的版本,它的垃圾收集器是根据内存分配量运行的,比如256个变量就开始运行垃圾收集器,这样,就不得不频繁地运行,从而降低的性能。
一般来说,确保占用最少的内存可以让页面获得更好的性能。那么优化内存的最佳方案,就是一旦数据不再有用,那么将其设置为null来释放引用,这个做法叫做解除引用。这一做法适用于大多数全局变量和全局对象。
var obj = {
num = 100;
};
obj = null; //解除对象引用,等待垃圾收集器回收