在看这篇文章的内容之前我们先来看下面的一道题目(这两天参加校招笔试,碰到了很多这种题目)
var test = 'test';
function f() {
console.log(test);
var test = 'hello';
console.log(test);
}
f();
执行函数fn
的时候会输出什么呢?
天啦,这道题我会,js
中的作用域链中说了,使用一个变量的时候会先向自己的作用域中查找,如果找不到,则会查找父级作用域
那么上面理所当然应该输出 test hello
,如果这样想,那么就是大错特错了,这一切还是和js
这门语言的特性有关.继续向后探索.
1. 作用域和变量
大多数的语言都有作用域这个概念,js中也有局部作用域和全局作用域.
首先来看下面的代码:
var a = 10;
console.log(a); //10
将a定义在全局作用域中,打印a的值为10
再看下面:
var a = '我是函数外部的变量';
function foo() {
var a = '我是函数内部的变量'
}
console.log(a) //我是函数外部的变量
此刻打印的结果是函数外面的变量a,为什么是这样呢,这就出现了局部作用域的概念.
从字面意思来理解全局作用域和局部作用域:
- 全局作用域:在整个文档范围内有效
- 局部作用域:只在某个范围内有效
在上面的代码中,在函数foo后面的大括号中包围的代码就属于一个块级作用域,它里面定义的变量a就是一个局部变量,只在这个function里面有定义。出了这个function,就如同没有定义过一样。
此外,js还可以不使用关键字直接定义变量,那么会发生什么情况,看下面:
var a = '我是函数外部的变量';
function foo() {
b = '我是直接定义的变量';
var c = '我是函数内部的变量';
}
foo()
console.log(a) //我是函数外部的变量
console.log(b) //我是直接定义的变量
console.log(c) //c is not defined
由此可以看出 c
在函数执行后是没有被定义的。说明在函数体内用 var 关键字声明的变量 c
是局部变量;
在函数体外使用 var 关键字定义的变量a
和在函数体内未用任何关键字定义的变量b
是全局变量。
得出结论:
在函数体外使用var关键字定义的变量和在函数体内未用任何关键字声明的变量是全局变量,在函数体内使用var关键字声明的变量是局部变量。
2. 块级作用域
在c语言中,for循环,if语句等都有块级作用域,那么在js中是怎样的呢?
var a = 'apple'
if(true) {
var a = 'banana'
}
console.log(a) //banana
通过上面的代码我们可以看到,a的值发生了改变
if后面的大括号不是块级作用域
实际上不仅仅是if语句,我们常用的for循环,while语句都不会形成一个块级作用域.
for(var i = 0 ; i < 3 ;i++) {
break;
}
console.log(i); //0
k =5;
while(k>1) {
k--;
var m = 10;
}
console.log(k); //1
console.log(m); //10
所以不要被括号所迷惑了,在大括号中定义的变量不一定就是局部作用域,在js中,形成局部作用域的方式只有一种,那就是函数.如果一个变量,没有定义在任何的function中,那么它将在全部程序范围内都可以使用
3. 作用域链
var a = 'apple'
var b = 'orange'
function f() {
var a = 'banana'
console.log(a); //banana
console.log(b); //orange
}
f();
console.log(a); //apple
在上面的代码中,首先会输出banana,因为在输出的时候会查找作用域链当遇见一个变量时,JS引擎会从其所在的作用域依次向外层查找,查找会在找到第一个匹配的标识符的时候,所以b的结果就是函数全局作用域内的orange
,最后打印a是在全局,所以结果是apple
当遇见一个变量时,JS引擎会从其所在的作用域依次向外层查找,查找会在找到第一个匹配的标识符的时候停止.
function outer(){
var a = 3; //a的作用域就是outer
function inner(){
var b = 5; //b的作用域就是inner
console.log(a); //能够正常输出3,a在本层没有定义,就是找上层
console.log(b); //能够正常输出5
}
inner();
}
outer();
console.log(a); //报错,因为a的作用域outer
作用域链:一个变量在使用的时候,就会在当前层去寻找它是否被定义,如果找不到,就找上一层function,直到找到全局变量,如果全局也没有,就报错。
var a = 1; //全局变量
var b = 2; //全局变量
function outer(){
var a = 3; //遮蔽了外层的a,a局部变量
function inner(){
var b = 4; //遮蔽了外层的b,b局部变量
console.log(a); //① 输出3,a现在在当前层找不到定义的,所以就上一层寻找
console.log(b); //② 输出4
}
inner(); //调用函数
console.log(a); //③ 输出3
console.log(b); //④ 输出2 b现在在当前层找不到定义的,所以就上一层寻找
}
outer(); //执行函数,控制权交给了outer
console.log(a); // ⑤ 输出1
console.log(b); // ⑥ 输出2
多层嵌套,如果有同名的变量,那么就会发生“遮蔽效应”:
var a = 2; //全局变量
function fn(){
console.log(a) //undefined
var a = 3; //就把外层的a给遮蔽了,这函数内部看不见外层的a了。
console.log(a); //输出3,变量在当前作用域寻找,找到了a的定义值为3
}
fn();
console.log(a); //输出2,变量在当前作用域寻找,找到了a的定义值为1
这个就回到了我们最初提出的问题,为什么会出现这种情况??
4. 变量提升
就像上面的一段代码,上面为什么会输出undefined
呢???按照作用域查找规则,如果自己的作用域内没有这个变量,就会向上查找,那么按道理应该是2
这便是JavaScript的变量提升机制起了”作用“。下面介绍一下变量提升:
在函数体内变量声明总会被解释器”提升“到函数体的顶部,向上面的代码,js
在执行的时候会处理成下面的样子,只不顾是我们看不到而已.
var a = 2;
function fn(){
var a;
console.log(a)
a = 3;
console.log(a);
}
fn();
console.log(a);
由此可见,变量提升只提升声明部分,不提升赋值部分。
我们再来看看下面的代码,在函数里面定义函数aa,并且在声明函数之前就调用函数,那会发生什么呢?
var a = 5;
function f() {
console.log(a); //undefined
var a = 10;
console.log(aa()); //10
function aa() {
return a;
}
}
f();
在函数声明之前就调用函数,不仅没有报错,而且还输出了正确的结果,刚才我们就提到了函数里定义的变量会进行变量提升,其实函数里定义的函数也会进行提升,而且提升的优先级还高于变量,上面的代码在执行时会被处理为如下:
function f() {
function aa() {
return a;
}
var a;
console.log(a); //undefined
a = 10;
console.log(aa()); //10
}
5. 一个例子
var b = 'boy';
console.log(b);
function f() {
console.log(a);
console.log(c);
if(a === 'apple'){
a = 'Alice'
}else {
a = 'Ada'
}
console.log(a);
var a = 'Andy';
middle();
function middle() {
console.log(c++);
var c = 100;
console.log(++c);
small();
function small() {
console.log(a);
}
}
var c = a = 88;
function bottom() {
console.log(this.b);
b = 'body'
console.log(b);
}
bottom()
}
f();
console.log(b);
// boy
// undefined
// undefined
// Ada
// NaN
// 101
// Andy
// boy
// body
// body
第一次打印b
.是全局的b
,输出boy
打印a
和 c
的时候,二者都还为定义,所以是undefined
判断a
不等于apple
,所以a
赋值为Ada
打印a
时,a
的值为Ada
又将a
的值改为Andy
执行函数middle
,打印c++
,但此刻c
的值是undefined
,c++
的值为NaN.
然后c
赋值为100
,++c
的值为101
打印a
,a
为Andy
;
执行函数bottom
,打印this.b
,此刻this指向window
,所以值为boy
;
b的值改为body,
打印b,为body
;
此刻值得注意的是,声明变量b的时候没有使用任何关键字,所以b
变为全局变量,最后打印b
的值为body