变量与值是两个不同的概念:变量相当于容器,而值相当于容器中的内容,为容器贴个标识符,就是变量名。程序能够根据变量名找到内容所在的位置,然后存取值。
在JavaScript中,声明变量使用var语句:
当声明多个变量时,应使用逗号运算符分隔变量名。
【示例1】 可以在声明中为变量赋值。未赋值的变量,则初始值为undefined(未定义)值。
提示: 变量命名应遵循JavaScript标识符命名规则。在计算机编程中,比较经典的变量命名法有3种。
这种命名方法是由微软公司的一位程序员查尔斯•西蒙尼提出来的,匈牙利命名法被广泛应用于Microsoft Windows编程环境中。它通过在变量名前面加上相应的小写字母的符号标识作为前缀,标识出变量的作用域、类型等,前缀后面是一个或多个单词组合,单词描述了变量的用途。例如,i表示整数,s表示字符串,命名示例如下:
var sUserName ="css8", iCount=0;
表4-6列举了匈牙利命名法定义JavaScript变量的前缀字符与数据类型对照表。
表4-6 匈牙利命名法前缀与变量类型
骆驼式命令法是混合使用大小写字母来构成变量的名称。例如,下面分别用骆驼式命名法和下划线命名法定义同一个函数。
function printLoadTemplates(){} function print_load_templates(){}
第一个函数名使用了骆驼式命名法,这种命名方法规定每一个单词首字母应使用大写字母来标记,而名称的首字母要小写,这与匈牙利命名法的名称首字母类似,第二个函数名使用了下划线法,函数名中的每一个单词都用一个下划线来标记。
骆驼式命名法近年来很流行,在很多新的语言和编程环境中,它应用得比较多。下划线命名法是C语言出现后开始流行起来的,在许多旧的程序和UNIX这样的环境中,它的使用非常普遍。
帕斯卡命名法和骆驼式命名法类似,只不过骆驼式命名法是第1个单词首字母小写,而帕斯卡命名法是第1个单词首字母要大写,例如,MyFunction就是一个帕斯卡命名的示例,而myFunction是一个骆驼命名法。在C#中,帕斯卡命名法和骆驼命名法使用比较多。
【拓展】 JavaScript的变量没有类型之分,检测变量的类型,实际上是检测变量包含的值的类型。所以,同一个变量名,它的类型可能会随时变化。用户应该根据开发需要先检测变量类型,再根据类型进行处理。
在JavaScript中,可以重复声明同一个变量,也可以反复初始化变量的值。例如:
JavaScript允许用户不声明变量,而直接为变量赋值,这是因为JavaScript解释器能够自动隐式声明变量。但是隐式声明的变量总是作为全局变量而存在。
【示例2】 当在函数中不声明就直接为变量赋值时,JavaScript会把它视为全局变量进行处理。由于是全局变量,函数外代码可以访问该变量的值。
但是,如果尝试读取一个未声明的变量的值,JavaScript会提示语法错误。为变量赋值的过程,实际上JavaScript也会隐式进行声明。在使用变量时,用户要养成良好习惯:先声明,后读写;先赋值,后运算。
【示例3】 下面示例设计:如果变量a不存在,就为其赋值为0。但是变量a在未声明前就直接读取,将会抛出语法错误。
针对上面代码,可以考虑使用属性法来读取。因为当读取一个未声明的属性时,JavaScript不会报错。又有变量a是全局变量,作为全局变量,它应该是window对象的一个属性,所以可以这样来设计:
但是上述方法只适用全局变量,如果是局部变量,就只能够通过类型检测法来判断了:
变量的作用域(scope)是指变量在程序中可供访问的有效范围,也称为变量的可见区域。在JavaScript中,变量作用域可以分为全局作用域和局部作用域。
☑ 全局作用域是指变量在整个页面脚本中都是可见的,可以自由访问。
☑ 局部作用域是指变量仅能在声明的函数内部可见,函数外是不允许访问的。
在函数体内,局部变量的优先级要比同名的全局变量高。此时,局部变量会覆盖同名全局变量。例如:
如果在函数体内存在同名参数变量和全局变量,则参数变量的优先级要比同名全局变量高。例如:
如果在函数体内存在同名参数变量和局部变量,则局部变量的优先级要比同名参数变量高。例如:
在JavaScript中,函数可以嵌套,也就形成了多个局部作用域嵌套的现象。
在上面代码中,内层的局部作用域要比外层局部作用域的变量优先级高。在上面示例中,alert(a);语句最终显示的是4,而不是其他局部变量值。
内层函数可以访问外层函数的变量,而外层函数却不能够访问内层函数的变量,这就是变量作用域链。
【示例1】 在JavaScript中,函数具有独立、封闭的作用域,用户可以利用这个特性使用函数封装代码,下面代码是jQuery框架的基本结构:
使用函数结构体的封装,可以有效避免在同一个文档中多个技术框架或其他JavaScript代码之间的相互影响。如果用户在文档全局域中又定义了同名变量jQuery或$,不会覆盖jQuery框架。
在全局作用域中使用变量,可以不用var语句,但是在函数中声明局部变量时,一定要使用var语句。
【示例2】 本示例演示了如果不显式声明局部变量所带来的后果。
因此,在函数体内使用全局变量是一种很危险的行为,很可能函数就会改变程序中其他部分的使用值。为了避免此类问题的发生,应该养成在函数体内使用var语句声明局部变量的习惯。
【拓展】 JavaScript在预编译期会先预处理声明的变量。但是,变量的赋值操作发生在JavaScript执行期,而不是预编译期。看下面这个示例:
通过上面示例可以看出,在函数未调用之前,函数内部定义的全局变量是无效的,这是因为在JavaScript预编译期,仅对函数名、函数内各种标识符进行检索,建立索引。
只有当在JavaScript执行期时,才按顺序为变量进行赋值,并初始化。而在执行期,如果函数未被调用,则函数内代码是不被解析的,所以才有了上面看到的示例演示效果。
【示例3】 根据JavaScript解析过程,再看下面这个示例:
上面代码显示,由于在函数内部声明了一个同名局部变量a,所以在预编译期,JavaScript就使用该变量覆盖掉全局变量对于函数内部的影响。而在执行初期,局部变量a未赋值,所以在函数第1行代码中读取局部变量a的值也就是undefined。当执行到函数第2行代码时,则为局部变量赋值2,所以在第3行中就显示为2。
变量的作用域是基于词法结构来确定,属于静态概念,而不是根据执行顺序来确定。作用域链是JavaScript提供的一套解决变量访问的机制。JavaScript规定每一个作用域都有一个与之相关联的作用域链。
作用域链就是一个对象列表,并根据对象的结构层次被串在一起,提供访问变量的优先顺序。当JavaScript访问变量时,会查询当前作用域中是否存在同名变量,当前作用域是一个调用对象,作用域中的变量为调用对象的属性。
如果当前调用对象存在同名属性,则访问该属性值;否则,会沿着作用域链向上继续查询上一级作用域中的调用对象。如果上一级调用对象仍然没有同名属性,那么就继续向上查询,依此类推。
最后,查询到作用域链的顶端(全局对象),如果在全局对象中仍然没有找到同名属性,则返回undefined的属性值。
【示例】 在下面的示例中,通过多层嵌套的函数设计一个多层作用域的上下文环境,在最内层函数中可以分别访问外层函数的私有变量。
在这个示例中,JavaScript解释器首先在最内层调用对象中查询属性a、b、c和d,其中只找到了属性d,并获得它的值(4),然后沿着作用域链,在上一层调用对象中继续查找属性a、b和c,其中找到了属性c,获得它的值(3),依此类推,直到找到所有需要的变量值为止,如图4-2所示。
图4-2 变量的作用域链
JavaScript包含一个垃圾回收的小程序,这个程序能够周期性地遍历JavaScript环境中的所有变量列表,并且给这些变量所引用的值做个标记。如果被引用的值是对象或数组,那么对象的属性或者数组的元素就会被递归做个标记。通过递归遍历所有值的树状图,垃圾回收器就能够找到(并标记)仍旧使用的每个值。那些没有标记的值就是无用的存储单元。
当给所有正在使用的变量做完标记之后,垃圾回收器就会开始进行清除。在这个阶段中,它将遍历环境中所有值的列表,同时释放那些没有标记的值。
【示例】 在本示例中,变量a存储的是字符串"JavaScript",然后再给变量a赋其他值,这时在内存中字符串"JavaScript"所占据的空间就没有被任何变量引用,此时JavaScript垃圾回收器就会把这个字符串视为垃圾,并执行回收,释放它占据的内存空间。
var a ="JavaScript"; a=123456;
同时,如果为变量a赋值为null,则JavaScript垃圾回收器就知道这个变量也没有用,于是把这个变量视为垃圾一并进行回收。如果一个变量、属性、元素或对象被赋值为null,也就意味着它们是无用放入垃圾了,JavaScript垃圾回收器将择机对其进行回收。
a=null;
定义全局变量有3种方式:
☑ 在任何函数外面直接执行var语句。
var f='value';
☑ 直接添加一个属性到全局对象上。全局对象是所有全局变量的容器。在Web浏览器中,全局对象名为window。
window.f='value';
☑ 直接使用未经声明的变量,以这种方式定义的全局变量被称为隐式的全局变量。
f='value';
JavaScript最为糟糕的就是对全局变量的依赖。由于在所有作用域中都可见,使用全局变量会降低程序的可靠性。
【示例1】 应该避免使用全局变量,努力减少使用全局变量的方法是:在应用程序中创建唯一一个全局变量,并定义该变量为当前应用的容器。
只要把多个全局变量都追加在一个名称空间下,将显著降低与其他应用程序产生冲突的几率,应用程序也会变得更容易阅读,因为My.work指向的是顶层结构。当然也可以使用闭包体将信息隐藏,它是另一种有效减少“全局污染”的方法。
【示例2】 在编程语言中,作用域控制着变量与参数的可见性及生命周期。这为程序开发提供了一个重要的帮助,因为它减少了名称冲突,并且提供了自动内存管理。
大多数使用C语法的语言都拥有块级作用域。对于一个代码块,即包括在一对大括号中的语句,其中定义的所有变量在代码块的外部是不可见的。定义在代码块中的变量在代码块执行结束后会被释放掉。但是,对于JavaScript语言来说,虽然该语言支持代码块的语法形式,但是它并不支持块级作用域。
JavaScript支持函数作用域,定义在函数中的参数和变量在函数外部是不可见的,且在一个函数中的任何位置定义的变量在该函数中的任何地方都可见。
其他主流编程语言都推荐尽可能迟地声明变量,但是在JavaScript中就不能够这样使用,因为它缺少块级作用域,最好的做法是在函数体的顶部声明函数中可能用到的所有变量。