JS 中的【作用域】与【作用域链】
简介
作用域是每一门编程语言重要的一部分,它决定了一个变量的有权访问
在JavaScript 中作用域又是面试常考的一个知识点
总结一下关于作用域的知识点:
从字面上来看作用域就是起作用的区域
即 :在函数执行时起作用的区域
- 先了解两个概念:
- 块级作用域
- 词法作用域
块级作用域
在 C 系的编程语言中,基本上都有块级作用域
所谓块级作用域,就是利用代码块来表示一个起作用的范围
在该范围内定义的变量,只允许在该范围内使用
在该范围外就无权访问该范围内定义的变量
这个范围使用花括号
{ }
来限定在JS语言中不存在块级作用域
1 | { |
词法作用域
在书写代码的时候决定变量的作用域
与运行时无关
以函数来划分作用域
1 | function fn(){ |
既然函数可以划分作用域,那么通过函数的自调用就可以模拟块级作用域了1
2
3
4
5(function(){
var num = 123;
console.log(num); // => 123
})();
console.log(num); // => Uncaught ReferenceError: num is not defined
那么在函数内再定义一个函数呢???1
2
3
4
5
6
7
8
9
10
11function parent(){
var num1 = 123;
function child(){
var num2 = 456;
console.log(num1); // => 123
console.log(num2); // => 456
}
child();
console.log(num2); // => Uncaught ReferenceError: num2 is not defined(…)
}
parent();
conclusion:
在函数内部可以访问函数外部定义的变量,反之不行
作用域链
那如果在函数内部定义的变量和函数外部定义的名字一样怎么办???1
2
3
4
5
6
7
8
9
10
11
12
13var num = 1;
function parent(){
var num = 123;
function child(){
var num = 456;
console.log('1-' + num); // => 1-456
}
console.log('2-' + num); // => 2-123
child();
console.log('3-' + num); // => 3-123
}
parent();
console.log('4-' + num); // => 4-1
- 可以得出:
- 函数内外定义的变量没有任何关系
- 并且函数在访问的时候有一定的查找顺序
有了js中的作用域的划分
那么有联系的作用域就形成了一个链式的结构,即作用域链
作用域链的作用:保证变量的有序访问
- 在进行执行JS代码时,分两步执行
- 第一步:JS引擎会先对JS代码进行预解析(变量提升)
- 第二步:逐步执行
绘制作用域链
- 绘制规则:
- 作用域链就是变量(对象)的数组
- 全部的
<script>
标签看成一个0级链 - 每个变量占据一个位置
- 因为只有函数划分作用域,所以函数会扩展出新的链,并一级级展开
- 由于声明提升,因此绘制时所有声明提到该链的最前面
- 在进行变量访问时,首先是查找该链是否有定义
- 该链上如果有定义直接获取数据,如果没有查找上一级链,直到0级,再没有则报错
看一个demo:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24var num = 1;
function fn1(){
var num = 2;
function fn2(){
var num = 3;
console.log('1-' + num); // => 1-3
}
fn2();
console.log('2-' + num); // => 2-2
}
function fn3(){
var num = 4;
fn4();
}
function fn4(){
console.log('3-' + num); // => 3-5
}
var num = 5;
fn1();
fn3();
console.log('4-' + num); // => 4-5
一步步分析:
在全局预解析,即0级上的声明有变量
num
,函数fn1
、fn3
、fn4
,还有一些语句这里的
var num = 5;
中的var
无效,相当于num = 5;
函数
fn1()
又延伸出一条新链,即1级链,该链上的声明有变量num
,函数fn2
,还有一些语句函数
fn2()
又延伸出一条新链,即2级链,该链上的声明有变量num
,和一条语句函数
fn3()
又延伸出一条新链,即1级链,该链上的声明有变量num
,和一条语句函数
fn3()
又延伸出一条新链,即1级链,该链上只有一条语句
即得到下面:
num = 1
fn1()
num = 2
fn2()
num = 3
console.log('1-' + num);
fn2(); console.log('2-' + num);
fn3()
num = 4
fn4();
fn4()
console.log('3-' + num);
num = 5; fn1(); fn3(); console.log('4-' + num);
变量搜索原则
- 在代码的运行过程中, 如果访问某一个变量
- 那么首先在当前链上找(无序), 如果没有,就在上一级找
- ( 在函数内部允许访问定义在函数外部的变量 )
- 如此往复, 直到 0 级链, 如果还没有 抛出异常
- 如果找到, 则结束寻找, 直接获得该链上变量的数据
conclusion:
- js中不存在块级作用域,遵循词法作用域原则
- 以函数来划分作用域
- 在代码书写时就已经决定了其作用域,与代码执行无关
- 自调用函数可以模拟块级作用域
- 变量提升
- 两条同级作用域链之间没有任何关系
- 下一级可以访问上一级变量,反之不行