理解JS作用域笔记(完):声明提升、作用域链
# 开头
笔记视频内容源自B站JavaScript从入门到放弃 第九章 深入理解JS作用域 (opens new window),笔记为自行整理复习使用,欢迎一同学习交流,转载请通知原作者
# 三、声明提升
# 3.1、变量声明提升
先看一段代码:
<body>
<script>
//没有编译阶段 边解释边执行
a = 2;
var a;
console.log(a);
</script>
</body>
2
3
4
5
6
7
8
从直观的角度看,js代码的执行是从上往下执行的,但实际上并不完全正确,函数或者变量的声明提升
就会改变这个规则
预解释
:当变量修饰采用var
来修饰的时候,将会提到最前面去声明,这个过程带来的现象称为预解释
再看一个代码:
<body>
<script>
console.log(a);
var a = 2;
</script>
</body>
2
3
4
5
6
如果按照从上而下的顺序来看,log并未找到有a这个变量应该会报错吗?答案是并没有
其实在内部,JS引擎将var声明
的变量提到了前面去执行,而赋值操作还是在原有的行,相当于:
<body>
<script>
var a;
console.log(a);
a = 2;
</script>
</body>
2
3
4
5
6
7
所以我们看到的结果是undefined
而不是报错,声明从他们在代码中出现的位置被移动到了最上面,这个过程我们叫做变量的提升
,也叫预解释
每个作用域都有提升的操作:
<body>
<script>
console.log(a);
var a = 0;
function fn() {
console.log(b);
var b = 1;
function test() {
console.log(c);
var c = 2;
}
test();
}
fn()
</script>
</body>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
先看一下结果:
为什么是undefined
呢,首先在全局作用域下,它会看到var
关键字,触发状态提升:
var a;
console.log(a);
a = 0;
2
3
接下来函数声明,调用,在函数作用域下寻找关键字标识符,看到了var
关键字触发变量提升,注意变量提升只会提升到当前函数的作用域
下不会提升外层的作用域:
var b;
console.log(b);
b = 1;
2
3
c也是同理
# 3.2、函数声明提升
先看代码:
<body>
<script>
foo();
function foo() {
console.log(1);
}
</script>
</body>
2
3
4
5
6
7
8
一般其他语言应该是会报错,但是在js上它能够进行执行,得到1这个输出
这是因为foo函数声明进行了提升,函数声明会提升,但是函数表达式
不会提升
<body>
<script>
foo();
var foo = function () {
console.log(1);
};
</script>
</body>
2
3
4
5
6
7
8
这是因为js首先看到var关键字将其提升:
<body>
<script>
var foo;
foo();
foo = function () {
console.log(1);
};
</script>
</body>
2
3
4
5
6
7
8
9
foo在运行时还不是一个函数所以就报错了,同样的具名函数也是:
foo();
var foo = function bar() {
console.log(1);
};
2
3
4
# 3.3、声明的注意事项
声明提升应该分为两种,一个是变量声明提升
一个是函数声明提升
,当同时存在变量和函数声明,js引擎会有些提升哪一个呢:
<body>
<script>
var a;
function a() {}
console.log(a);
</script>
</body>
2
3
4
5
6
7
可以看到,变量的声明优先于函数的声明,但是函数的声明会覆盖未定义
的同名变量
<body>
<script>
var a = 10;
function a() {}
console.log(a);
</script>
</body>
2
3
4
5
6
7
一旦变量被定义了,就不会被同名的函数覆盖
首先js引擎看到var标识符
对a进行状态提升,函数的声明提升在变量之后,所以相当于:
var a;
function a() {}
a = 10;
console.log(a);
2
3
4
变量的重复声明是无用的,但是函数的重复声明会覆盖前面的声明(无论是变量还是函数声明)
var a = 1;
var a;//无用
console.log(a);
2
3
相当于:
var a;
var a;
a = 1;
console.log(a);
2
3
4
函数的声明提升优先级高于变量的声明提升
var a;
function a() {
console.log("你好");
}
a();
2
3
4
5
后面的函数声明会覆盖前面的函数声明
fn()
function fn() {
console.log("fn");
}
function fn() {
console.log("fn2");
}
2
3
4
5
6
7
总结一下,应该避免在同一作用域中重复的声明
# 四、作用域链
# 4.1、理解作用域链
作用域:作用域是一套规则,用来确定在何处以及如何查找标识符。在js中作用域分为全局作用域和函数作用域,另外函数作用域可以互相嵌套
作用域链:作用域链只会向外部进行查找,各个作用域的嵌套关系组成了一条作用域链。例子中bar函数的作用域链式bar->fn>全局,fn函数保存的作用域链式fn->全局 使用作用域链主要是进行标识符(变量和函数)的查询,标识符(变量和函数)解析就是沿着作用域链一级一级地搜索标识符的过程,而作用域链就是保证对变量和函数的有序(由内而外逐层)访问。
更深层次的理解和案例可以看这个:
理解什么是作用域和执行上下文环境 (opens new window)
# 4.2、自由变量
<body>
<script>
var a = 1;
var b = 2;
function fn(x) {
var a = 10;
function bar(x) {
var a = 100;
b = x + a;
return b;
}
bar(20);
bar(200);
}
fn(0);
</script>
</body>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
在全局作用域下有a,b
,在fn的函数作用域下有a、bar
,在bar的作用域下有a
像b这样的在作用域中存在,但未在当前的作用域(bar作用域)声明的变量,我们称之为自由变量
,一旦出现自由变量,就肯定会有作用域链,再根据作用域链的查找机制,去查找到对应的变量
查找机制:在当前作用域下发现没有该变量,沿着作用域链往上级查找,直到查到对应的变量为止,如果查找不到就抛出异常
# 4.3、执行环境与执行流
执行环境:执行环境也叫执行上下文
,执行上下文环境。每个执行环境都有一个与之关联的变量对象,在环境中定义的函数和变量都保存在这个对象
<body>
<script>
var a = 1;
var b = 2;
function fn(x) {
var a = 10;
function bar(x) {
var a = 100;
b = x + a;
return b;
}
bar(20);
bar(200);
}
fn(0);
</script>
</body>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
比如在fn的执行环境下保存着一个x、a、bar,还有别的像arguments\this等,如视频的图:
执行流:执行流也叫执行的顺序,可以通过浏览器打断点的形式查看。
# 4.4、执行环境栈
<body>
<script>
var a = 1;
var b = 2;
function fn(x) {
var a = 10;
function bar(x) {
var a = 100;
b = x + a;
return b;
}
bar(20);
bar(200);
}
fn(0);
</script>
</body>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
每一个方法调用时,都会将当前的执行环境压入栈中,当运行fn(0)时,fn的执行环境就会被压入栈中,在当前的栈中就会存在全局作用域和函数作用域。
执行环境栈就是一个压栈
和出栈
的过程,以调用fn(0)函数为例子,全局的执行环境压入栈中,函数fn执行环境也压入栈中,这就是压栈的操作。运行时,函数fn处于活跃状态,当fn执行完成就要进行出栈操作,再把控制权交给全局
以bar函数执行为例子,因为有作用域链,执行时会有三个作用域环境,其中活跃的是bar(20)执行环境,在这个执行环境下,内部有一个x:20,a:undefined,全局中的b:2,this:window,在当前的栈中还存在着fn(0)的执行环境,处于非活跃状态,其中包含了x:0,a:10,bar:function,this:window,还有一个全局的环境,其中包含了a:1,b:2,fn:function,this:window,当执行环境结束,则会被弹出,活跃状态转交给上一层执行环境
# 五、总结
1、在js中,除了全局作用域,每个函数都会创建自己的作用域
2、作用域在函数定义的时候已经确定了,与函数调用无关 3、通过作用域,可以查找作用域范围内的变量和函数有哪些,却不知道变量的值是什么。所以作用域是静态 4、对于函数来说,执行环境在函数调用时确定的。执行环境包含作用域内的所有的变量和函数的值。在同一个作用域下,不同的调用会产生不同的执行环境,从而产生不同的变量和值。所以执行的环境是动态的