JavaScript有两种变量:局部变量和全局变量。而这两个对于初学JavaScript的同学来说是一个较为麻烦的。非常容易搞错。我也看了好几天有关于这方面的教程,也还没完全整明白。今天把自己理解的记录下来,有不对之处,还请高手斧正。
在深入了解JavaScript中的局部变量和全局变量,我们必须要了解以下几个方面:
在JavaScript中,常常把定义在函数外的变量称为全局变量;而在函数内声明的变量称为局部变量。
来看一个例子:
var variable = 'global';
function checkVariable () {
var variable = 'local';
console.log(variable); // local
}
checkVariable();
console.log(variable); // global
上面的示例,在函数checkVariable()
外声明了一个全局变量variable
,同时在函数内声明了一个局部变量variable
。打个比方来说,把函数checkVariable()
当作一个容器,那么放在容器里的变量就是局部变量,而放在容器外的变量则是全局变量。当然,这也不是一尘不变的。为什么这么说呢?我们还是继续用代码来说话吧,因为代码比我更能阐述要说的一切:
variable = 'global';
function checkVariable() {
variable = 'local';
console.log(variable); // local
myVariable = 'local';
console.log(myVariable); // local
}
checkVariable();
console.log(variable); // local
console.log(myVariable); // local
对于我这样的初学者来说,我很多时候搞不清楚,为什么在函数外,variable
的值也变成了local
?为什么在函数体外可以访问myVariable
变量?
其实这两个问题都源于一个特性。在全局作用域中声明变量可以省略var
关键字,但如果在函数体内声明变量时不使用var
关键字,就会发生上面这样的现象。
另外,第一个示例告诉我们,在函数体内部,局部变量的优先级比同名的全局变量要高。那么在声明局部变量的函数体范围内,局部变量将覆盖同名的全局变量。
感觉有点啰嗦,不过还是做个小小总结:
声明局部变量一定要使用var
关键字,使用var
关键字声明变量时,变量会自动添加到距离最近的可用环境中。如果没有写var
, 变量就会暴露在全局上下文中, 这样很可能会和现有变量冲突. 另外, 如果没有加上, 很难明确该变量的作用域是什么, 变量也很可能像在局部作用域中, 很轻易地泄漏到 Document
或者 Window
中, 所以务必用var
去声明变量。如果变量在未声明的情况下被初始化,该变量会自动添加到全局环境。
如果要理解JavaScript中变量是局部变量还是全局变量,那就必须要先理解变量作用域链。要理解这个就得先整明白执行环境。
每当JavaScript执行时,都会有一个对应的执行环境被创建,执行环境中很重要的一部分就是函数的调用对象,而调用对象就是用来存储相应函数的局部变量的对象。每一个JavaScript方法都是在自己独有的执行环境中运行。
简而言之,函数的执行环境包含了调用对象,调用对象的属性就是函数的局部变量,每个函数就是在这样的执行环境中执行,而在函数之外的代码,也在一个环境中执行,这个执行环境包含了全局变量。
在JavaScript的执行环境中,还有一个词要理解,那就是与执行环境对应的作用域链,它是一个由对象组成的列表或链。
在理解作用域链之前先来看一个示例:
var rain = 1;
function rainMan () {
var man = 2;
function inner () {
var innerVar = 4;
console.log(rain); // 1
}
inner(); // 调用inner函数
}
rainMan(); // 调用rainMan函数
执行上面的代码,浏览器控制器会输出rain
的值,其值为1
。先来看一张执行的示意图:
执行rainMan()
函数后,会执行其内部的inner()
函数,而这个函数执行console.log(rain)
会输出一个值。这个过程在JavaScript是这样执行的,首先在inner()
函数中查找是否声明了变量rain
,如果声明了则会使用inner()
函数中的rain
变量;如果inner()
函数中未声明变量rain
,JavaScript则会继续在rainMan()
函数中查找是否定义了rain
变量。和inner()
函数查找方式一样,如果在rainMan()
函数中声明了变量rain
,则会使用这个声明的变量;如果函数内没有定义rain
变量,则JavaScript则会继续往向(全局对象)查找是否定义了rain
变量。在这个示例中,inner()
和rainMan()
函数中并未声明rain
变量,而在全局对象中声明了rain=1
,因此函数执行完最终结果会输出1
。如果在全局对象中都未声明rain
变量的话,则会输出报错信息:Uncaught ReferenceError: raine is not defined
。
作用域链:JavaScript需要查询一个变量x
时,首先会查找作用域链的第一个对象,如果在第一个对象中没有定义x
变量,JavaScript会继续查找有没有定义x
变量,如果第二个对象没有定义则会继续查找,以此类推。上面的代码涉及到了三个作用域链对象,依次是:inner
、rainMan
、window
。
在全局的 Javascript 执行环境中,作用域链中只包含一个对象,就是全局对象。而在函数的执行环境中,则同时包含函数的调用对象。由于 Javascript 的函数是可以嵌套的,因此每个函数执行环境的作用域链可能包含不同数目个对象,一个非嵌套的函数的执行环境中,作用域链包含了这个函数的调用对象和全局对象,而在嵌套的函数的执行环境中,作用域链包含了嵌套的每一层函数的调用对象以及全局变量。
上图来自于@范斌写的《理解 Javascript 中变量的作用域》文章中。
在一些程序语言中,比如C或者Java等语言,if
、for
这样的语句块中可以包含自己的局部变量,这些变量的作用域是这些语句块的,而在JavaScript中却不存在块级作用域这样的说法。
仔细看看下面的示例:
function checkVariable (obj) {
var i = 0;
if (typeof obj == 'object') {
var j = 0;
for (var k = 0; k < 3; k++) {
console.log(k); // 0,1,2
}
console.log(k); // 3
}
console.log(j); // 0
}
checkVariable(new Object());
你会发现变量i
,j
,k
作用域是相同的,他们都在checkVariable()
函数内都是全局的。也就是说,在函数中声明的所有变量,无论是在哪里声明的,在整个函数中它们都是有定义的。
在JavaScript中,函数中声明的所有变量,无论是在哪里声明的,在整个函数中它们都是有定义的,即使在声明之前。其实这里隐藏一个坑,来看看下面的示例代码:
function checkVariable() {
var a = 1;
function inner () {
a = 100;
}
inner();
console.log(a); // 100
}
checkVariable();
上面的代码说明了,变量a
在整个checkVariable()
函数体内都可以使用,并可以重新赋值。由于这条规则,给入门者留埋下一个坑:
var a = 1;
function checkVariable () {
console.log(a); // undefined
var a = 2;
console.log(a); // 2
}
checkVariable();
在上面的示例中,console.log(a)
输出的是undefined
,既不是全局变量a
的值1
,也不是全局变量a
的值10
。是由于在函数checkVariable()
内局部变量a
在整个函数内都有定义(var a = 2
进行了声明),所以在整个checkVariable()
函数内隐藏了同名的全局变量a
。所以才会输出undefined
。
上面的代码其实就等同于:
var a = 1;
function checkVariable() {
var a;
console.log(a);
a = 2;
console.log(a)
}
checkVariable();
JavaScript的变量的域是根据方法块来划分的(也就是说以function
的一对大括号{}
来划分)。切记:是function
块,而不是for
、while
和if
块。因为在JavaScript中没有块作用域。
function checkVariable() {
console.log(i); //i=> undefined i未赋值
for (var i = 0; i < 3; i++) {
console.log(i); // i=>0,1,2;当i为3时跳出循环
}
console.log(i); // i=>3 i在for循环外,但i的值仍然保留为3
while (true) {
var j = 1;
break;
}
console.log(j); // j=>1; j在while循环外,但j的值仍然保留为1
if (true) {
var k = 1;
}
console.log(k); // k=>1; k已经在if循环外,但k的值仍然保留为1
}
checkVariable();
console.log(i); // Uncaught ReferenceError: i is not defined; 程序报错,中断脚本执行
console.log(j); // 未执行
console.log(k); // 未执行
JavaScript在执行前会对整个脚本做完整的分析(包括局部变量),从而确定变量的是局部变量还是全局变量。如下面的示例代码:
var a = 1;
function checkVariable () {
console.log(a); // a=> undefined
a = 2;
console.log(a); // a=> 2
var a;
console.log(a); // a=> 2
}
checkVariable();
console.log(a); // a=>1
著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。
原文: https://www.w3cplus.com/javascript/the-basics-of-variable-scope-in-javascript.html © w3cplus.com