JavaScript变量是松散型的本质,即不强制类型,这决定了它只是在特定时间用于保存特定值的一个名字而已。由于不存在定义某一变量必须要保存何种数据类型值的规则,变量的值及其数据类型可以在脚本的生命周期内改变。
一、变量及作用域
1、基本类型和引用类型 的值
ECMAScript变量可能包含两种不同的数据类型的值:基本类型值和引用类型值。
基本类型值:那些保存在栈内存中的简单数据段,即这种值完全保存在内存中的一个位置。
引用类型值:那些保存在堆内存中的对象,即变量中保存的实际上只是一个指针,这个指针指向内存中的另一个位置,该位置着保存对象。
将一个值赋给变量时候,解析器必须要确定这个值是基本类型还是引用类型。基本类型值有:Undefined,Null,Boolean,Number,String。这些类型在内存中分别占有固定大小的空间,它们的值保存在栈空间,可通过按值访问。在有些编程语言中字符串以对象的形式来表示,因此认为是引用类型,在JS中没有这一传统。
若赋值的是引用类型的值,则必须在堆内存中为这个值分配空间。由于值的大小不固定,所以不能把它们放在栈内存中,可是内存地址的大小却是固定的,因此可以将内存地址保存在栈内存中。这样,当查询引用类型的变量时候,先从栈内存中读取内存地址,然后再通过这个地址找到相应的在堆内存中的值,如此过程称之为引用访问。
2、动态属性
对基本类型和引用类型的定义方式是相似的,创建一个变量并为其赋值。但是,当保存值到变量中以后,对不同的类型值可执行的操作也是不同的。
/*创建引用类型可新增一个属性*/
var test = new Object();
test.name = "bert";
alert(test.name);
/*创建一个基本类型,添加属性会出错*/
var test = "bert";
test.age = "24";
alert(test.age); //undefined
3、copy变量值
在变量赋值中,基本类型和引用类型有所不同。基本类型复制的是值本身;引用类型复制的是地址。
var test1 = 24;
var test2 = test1;
alert(test2 + 1); //25
alert(test1); //24
基本类型复制,test2是test1的副本,但它们相互独立,两者分别操作时候互不影响。
var test1 = new Object();
test1.name = "bert";
var test2 = test1;
alert(test2.name);
alert(test1.name);
引用类型复制时,test2就是test1,它们所指向的是同一个对象,如果这个对象的name属性被修改了,test2.name和test1.name输出的值都会被同时相应的修改。
4、传递参数
ECMAScript中所有函数的参数都是按值传递的,也就是说,参数不会按引用传递,即便变量有基本类型和引用类型之分。
/*传递的参数是一个基本类型的值*/
function test(num){
num += 10; //函数内部num为局部变量,和外面的num无关
return num;
}
var num = 50;
var result = test(num);
alert(result); //60
alert(num); //50
/*传递的参数为一个引用类型的值*/
function test(num){
num.age = 24;
}
var p = new Object(); //创建一个引用类型的值
test(p); //调用函数,引用类型的值作为参数
alert(p.age); //按值传递
5、检测类型
检测一个变量的类型可用typeof运算符。typeof检测基本类型:
var test = "bert";
alert(typeof test); //返回结果string
然而,引用类型中用typeof检测只能得到Object,数组是Object,nul也是Object,可我们想知道的是什么类型的对象,因此需采用instanceof运算符来判断。(注意返回的写法)
var test1 = [1,2,3];
alert(test1 instanceof Array); //判断是否为数组,返回结果true
var test2 = {};
alert(test2 instanceof Object); //判断是否为对象
var test3 = /[a-z]+/;
alert(test3 instanceof RegExp); //判断是否为正则表达式
var test4 = new String("bert");
alert(test4 instanceof String); //判断是否为字符串
6、执行环境与作用域
执行环境定义了变量或函数有权访问其他数据,决定了它们各自的行为。全局执行环境是最外围的执行环境,在Web浏览器中,全局执行环境就是window对象,因此所有的全局变量和函数都是基于window对象的属性个方法所创建的。
var test = "bert";
function setTest(){
alert(test);
}
setTest();
/*等价于*/
var test = "bert";
function setTest(){
alert(window.test);
}
window.setTest();
当执行环境中的所有代码执行完毕后,该环境会被销毁,保存在其中的所有变量和函数定义也随之销毁。如果是全局执行环境,程序执行完毕或者网页被关闭都会使其销毁。每个执行环境都有一个与之关联的变量对象,就好比全局的window可以调用变量和属性一样。局部的环境也有一个类似window 的变量对象,环境中定义的所有变量和函数都保存在这个对象中。(我们无法访问这个变量对象,但解析器会处理数据时后台使用它)
函数里的局部作用域里的变量替换全局变量,但作用域仅限在函数体内这个局部环境。
var test = "bert";
function setTest(){
var test = "Tom";
alert(test); //返回结果Tom
}
setTest(); //调用函数
alert(test); //传递的参数test为bert,和函数内部的参数无关,返回结果bert
通过传参可以替换函数体内的局部变量,但作用域仅限于在函数体内这个局部变量。
var test = "bert";
function setTest(test){ //通过传参,改变了全局变量
alert(test);
}
setTest("Tom"); //传参
alert(test);
函数体内还包含着函数,只有这个函数才可以访问内一层的函数。
var test1 = "bert";
function setTest1(){
function setTest2(){
var test2 = "Tom";
alert(test1);
alert(test2);
}
setTest2();
}
setTest1();
每个函数被调用时都会创建自己的执行环境。当执行到这个函数时,函数的环境就会被推倒环境栈中执行,而执行后又在环境中弹出,把控制权交给上一级的执行环境。当代码在一个环境中执行时,就会形成一种叫作用域链的东西。它的用途是保证对执行环境中有访问权限的变量和函数进行有序访问。作用域链的前端,是执行环境的变量对象。
7、没有块级作用域
块级作用域表示诸如if语句等有花括号封闭的代码块,所以,JS是支持条件判断来定义变量。
if (true) { //if语句代码块没有局部作用域
var test= 'bert';
}
alert(test);
/*for语句亦是如此*/
for(var i=0;i<10;i++){
var test='bert';
}
alert(i);
alert(test);
8、变量查询速度
对于局部变量和全局变量,看所处的位置不同所代表的不同,变量查询中,访问局部变量要比全局变量更快,因为不需要向上搜素作用域链。
var test="bert";
function getTest(){
return test;
}
alert(getTest()); //返回bert
var test="bert";
function getTest(){
var test="Tom";
return test;
}
alert(getTest()); //返回Tom
二、内存问题
JavaScript具有自动垃圾收集机制,也就是说,执行环境会负责管理代码执行过程中使用的内存。其他语言比如C和C++,必须手工跟踪内存使用情况,适时的释放,否则会造成很多问题。而JavaScript则不需要这样,它会自行管理内存分配及无用内存的回收。
JavaScript最常用的垃圾收集方式是标记清除。垃圾收集器会在运行的时候给存储在内存中的变量加上标记。然后,它会去掉环境中正在使用变量的标记,而没有被去掉标记的变量将被视为准备删除的变量。最后,垃圾收集器完成内存清理工作,销毁那些带标记的值并回收他们所占用的内存空间。
垃圾收集器是周期性运行的,这样会导致整个程序的性能问题。比如IE7以前的版本,它的垃圾收集器是根据内存分配量运行的,比如256个变量就开始运行垃圾收集器,这样,就不得不频繁地运行,从而降低的性能。
一般来说,确保占用最少的内存可以让页面获得更好的性能。那么优化内存的最佳方案,就是一旦数据不再有用,那么将其设置为null来释放引用,这个做法叫做解除引用。这一做法适用于大多数全局变量和全局对象。
var test = {
name : 'bert'
};
test = null; //解除对象引用,等待垃圾收集器回收