浅析Javascript中作用域、作用域链和预解析

一、作用域

作用域(scope),程序设计概念,通常来说,一段程序代码中所用到的名字并不总是有效/可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。

作用域的使用提高了程序逻辑的局部性,增强程序的可靠性,减少名字冲突。

1.全局作用域(全局变量)

全局变量,原理就是将变量挂载到window对象中.

全局变量拥有全局的作用域,可在任意地方被调用

全局变量有两种声明方式

(1)函数外部

(写在函数外面的都是全局变量)

1
2
3
4
5
6
7
8
9
10
11
12
<script>
var num = 15; //全局变量
function f() {
var num = 10; //不是全局变量
function f2(){
/*----*/
}
f2();
}
f();
console.log(num); // 15
</script>

(2)函数内部

(不加var的变量声明,隐式全局变量)

1
2
3
4
5
6
7
<script>
function f() {
num = 10; //隐式全局变量
}
f();
console.log(num); // 10
</script>

全局变量与隐式全局变量的区别

隐式全局变量可以通过delete关键字来删除,全局变量不可以
1
2
3
4
5
6
7
8
9
10
//全局变量不可被delete
<script>
var a = 2; //全局变量
function f(){
//****
}
console.log(a); // 2
delete a;
console.log(a); // 2
</script>
1
2
3
4
5
6
7
8
9
//隐式全局变量被delete
<script>
function f(){
a = 2; //隐式全局变量
}
console.log(a); // 2
delete a;
console.log(a); // not defined (已被删除)
</script>

2.局部作用域(局部变量)

局部变量:写在函数体里面的变量

局部变量只可以在当前函数内部使用
1
2
3
4
5
6
7
8
9
10
11
<script>
function f() {
var num = 10; //局部变量
function f2(){
console.log(num); // 10
}
f2();
}
f();
console.log(num); // not defined
</script>

二、作用域链

作用域链简而言之就是,调用变量时,若当前作用域内没有该变量时就向它的上一级作用域去寻找。

Javascript用的是词法作用域(静态作用域)

实例:

1
2
3
4
5
6
7
8
9
10
11
12
<script>    /* 0级作用域 */
var a = 1;
function aa(){ /* 1级作用域 */
//var a = 2;
function bb(){ /* 2级作用域 */
//var a = 3;
console.log(a); // 1
}
bb();
}
aa();
</script>

作用过程:

  1. 当运行到console.log(a)时,会在当前作用域(2级作用域)内寻找是否有a可用;
  2. 若有直接使用,否则向上一级作用域(1级作用域)去寻找;
  3. 此时来到1级作用域,若此时有a可用则用,否则继续向上一级寻找,直到到达0级作用域;
  4. 此时来到0级作用域,若此时有a可用则用,否则报错;

作用域链

值得注意的是:作用域链的始发点与函数的调用位置无关,而与函数的声明位置有关 例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
<script>
var value = 1;
function foo() {
console.log(value);
}

function bar() {
var value = 2;
foo();
}
bar();
</script>


此时的打印结果为1,而不是foo函数调用所在作用域的value = 2;

这里的1是foo函数声明所在的作用域中的value = 1;

## 三、预解析

当浏览器执行Javascript代码时,为了提高执行效率会在代码解释执行前进行预解析处理。

预解析处理规则:

1. 变量提升 仅仅将变量声明提升到当前作用域的最上面,不包括变量的赋值
2. 函数提升 将函数的声明提升到当前作用域的最上面,不包括函数的调用 函数的声明会提前到变量声明之前

变量提升

实例:

1
2
3
4
<script>
console.log(a); //undefined
var a = 10;
</script>

上述代码结果打印为undefined,而不是10,这是为什么呢?

我们来看看与解析之后的结果,

预解析为:

1
2
3
4
5
<script>
var a
console.log(a);
a = 10;
</script>

由于变量声明被提升到作用域最前端,而赋值没有被提升,导致执行console.log(a);时 a还未被赋值。

函数提升

实例:

1
2
3
4
5
6
<script>
f1();
function f1() {
/*-----*/
}
</script>

被解析为:

1
2
3
4
5
6
<script>
function f1() {
/*-----*/
}
f1();
</script>

函数的声明会提前到变量声明之前

函数提升和变量提升同时发生时,函数的声明会提前到变量声明之前。
1
2
3
4
5
6
7
8
<script>
var a = 3;
function a(){
console.log(10);
}
console.log(a);
a();
</script>

被解析为:

1
2
3
4
5
6
7
8
9
<script>
function a(){
console.log(10);
}
var a;
a = 3
console.log(a);
a(); //not function 因为此时a为变量而不是函数
</script>

值得注意的是:

1.函数是可以被打印的

1
2
3
4
5
6
<script>
function a(){
/*----*/
}
console.log(a); //打印函数a
</script>

2.变量a的赋值可以被普通类型和函数互相取代

1
2
3
4
5
6
7
8
9
<script>
var a;
a = function(){
/*----*/
}
console.log(a); //打印函数a
a = 1;
console.log(a); // 1;
</script>

匿名函数的申明不遵循预解析规则

1
2
3
4
5
6
<script>
f(); //报错
var f = function(){
/*----*/
}
</script>

因为解析前后代码相同 执行 f() 时,f未被声明为函数类型。

隐式全局变量的注意点

1
2
3
4
5
6
7
8
9
10
<script>
f();
console.log(a); //undefined
console.log(b); //9
function f() {
var a = b = c = 9;
console.log(a); //9
console.log(b); //9
}
</script>

被解析为:

1
2
3
4
5
6
7
8
9
10
11
12
<script>
function f() {
var a; //局部变量
a = 9;
b = 9; //隐式全局变量
console.log(a); //9
console.log(b); //9
}
f();
console.log(b); //9
console.log(a); //undefined 因为a是局部变量
</script>
× 请我吃糖~
打赏二维码