本文深入解析JavaScript中的变量机制,涵盖无类型特性、显式与隐式声明的区别、全局与局部变量的生命周期,以及变量提升导致的undefined现象。重点对比基本类型与引用类型的内存分配与赋值行为,阐明引用传递原理及克隆必要性。
本文旨在探讨JavaScript中关于变量的若干关键细节,如有疏漏或不当之处,欢迎指正。
变量的类型
一、变量的类型
与Java、C等强类型语言不同,JavaScript是一种无类型且弱检测的语言。在定义变量时,无需显式声明其类型,只需通过赋值操作即可将任意类型的数据赋予同一变量。例如:
i=100;//Number类型
i="variable";//String类型
i={x:4};//Object类型
i=[1,2,3];//Array类型
这种动态特性虽然赋予了编码更高的灵活性,但也带来了调试困难的问题。由于编译器缺乏严格的类型检查,在维护长代码时,这种弱检测机制往往会导致排查错误变得异常艰难。
变量的声明
二、变量的声明
JavaScript中的变量声明分为显式声明和隐式声明两种形式。
var i=100;//显式申明
i=100;//隐式申明
在函数内部,使用var关键字进行显式声明的变量被视为局部变量;而未使用var关键字、直接通过赋值方式声明的变量则被视作全局变量。
需要注意的是,当尝试访问一个未声明的变量时,JavaScript会抛出错误。然而,如果对一个未声明的变量进行赋值,JavaScript不会报错,而是默认将其视为隐式声明了一个全局变量。这一点在开发中务必引起重视。
全局变量和局部变量
三、全局变量和局部变量
当JavaScript解析器开始执行时,首先会在执行环境中构建一个全局对象。我们在顶层定义的全局属性实际上就是该对象的属性,在顶层代码中可以通过this关键字或window对象来访问它。相比之下,函数体内的局部变量仅存在于函数执行时生成的调用对象中,一旦函数执行完毕,局部变量便会立即销毁。因此,在程序设计中合理声明变量至关重要,这不仅能减少不必要的内存开销,还能极大程度地避免因变量重复定义而覆盖先前变量所引发的调试难题。
变量作用域
四、变量作用域
在任何编程语言中,变量的作用域都是关键细节。与Java、C等语言相比,JavaScript的变量作用域更为宽松,其显著特征之一是JavaScript变量没有块级作用域。这意味着函数中的变量在整个函数体内均有效,运行以下代码:
<SCRIPT LANGUAGE="JavaScript" type="text/javascript">
//定义一个输出函数
function outPut(s){
document.writeln(s)
}
//全局变量
var i=0;
//定义外部函数
function outer(){
//访问全局变量
outPut(i); // 0
//定义一个类部函数
function inner(){
//定义局部变量
var i = 1;
// i=1; 如果用隐式申明 那么就覆盖了全局变量i
outPut(i); //1
}
inner();
outPut(i); //0
}
outer();
</SCRIPT>
输出结果为0 1 0。从上述代码可以证明,若在函数体中使用var声明变量,则该变量仅在且仅在该函数体内有效;函数运行结束时,本地变量即被销毁。
基于上述JavaScript特性,还有一个关键问题需要注意。此前一直使用ActionScript,虽然它与JavaScript均基于ECMA标准,但在此处存在细微差异。例如以下代码:
<SCRIPT LANGUAGE="JavaScript" type="text/javascript">
//定义一个输出函数
function outPut(s){
document.writeln(s)
}
//全局变量
var i=0;
//定义外部函数
function outer(){
//访问全局变量
outPut(i); // 0
//定义一个类部函数
function inner(){
outPut(i); //undefiend
var i=1;
outPut(i); //1
}
inner();
outPut(i); //0
}
outer();
</SCRIPT>
你可能预期输出结果为0 0 1 0,事实上在ActionScript中确实如此,但在JavaScript中的输出却是0 undefined 1 0。为何会出现这种情况?前文提到,JavaScript函数体中声明的本地变量在整个函数中都有效。因此,在上面的代码中,var i = 1; 在inner函数中均有效。实际上,显式声明的变量i是在预编译阶段就已经被编译到调用对象中,这与隐式声明变量在解释时才被定义为全局变量不同。只是在调用outPut(i)时,该变量尚未初始化,此时的本地变量i是未赋值的变量,而非未定义变量,因此输出了undefined。上述代码等效于以下代码:
function inner(){
var i; //定义但不赋值
outPut(i); //undefiend
i=1;
outPut(i); //1
}
为避免此类问题,极力推荐在函数起始位置集中进行变量声明。
基本类型和引用类型
五、基本类型和引用类型
JavaScript与Java、C等语言不同,在变量声明时无需指定变量所占用的存储空间。变量中存储的数据可分为两类:基本类型和引用类型。其中,数值、布尔值、null和undefined属于基本类型,而对象、数组和函数属于引用类型。
基本类型在内存中具有固定的大小。例如:数值型在内存中占用八个字节,布尔值只占用一个字节。对于引用型数据,它们可以具有任意长度,因此内存大小是不定的。变量中存储的实际上是对该数据的引用,通常是内存地址或指针,通过它们我们可以找到对应的数据。
引用类型和基本类型在使用行为上存在显著差异:
<SCRIPT LANGUAGE="JavaScript" type="text/javascript">
//定义一个输出函数
function outPut(s){
document.writeln(s)
}
var a = 3;
var b = a;
outPut(b);
//3
a = 4;
outPut(a);
//4
outPut(b);
//3
</SCRIPT>
对基本类型b进行赋值时,实际上是开辟了一块新的内存空间。因此,改变变量a的值对变量b没有任何影响。
<SCRIPT LANGUAGE="JavaScript" type="text/javascript">
//定义一个输出函数
function outPut(s){
document.writeln(s)
}
var a_array = [1,2,3];
var b_array = a_array;
outPut(b_array); //1,2,3
a_array[3] = 4;
outPut(b_array);//1,2,3,4
</SCRIPT>
上述代码演示了对引用类型变量的赋值,实际上传递的是对内存地址的引用。因此,对a_array和b_array的存取操作,实际上都是对同一块内存区域进行操作。如果希望重新分配内存空间来存储引用型变量,则需要使用克隆方法或自定义方法来复制引用变量的数据。