简介

作用域是每一门编程语言重要的一部分,它决定了一个变量的有权访问

在JavaScript 中作用域又是面试常考的一个知识点

总结一下关于作用域的知识点:

从字面上来看作用域就是起作用的区域

即 :在函数执行时起作用的区域

  • 先了解两个概念:
    • 块级作用域
    • 词法作用域

块级作用域

在 C 系的编程语言中,基本上都有块级作用域

所谓块级作用域,就是利用代码块来表示一个起作用的范围

在该范围内定义的变量,只允许在该范围内使用

在该范围外就无权访问该范围内定义的变量

这个范围使用花括号{ }来限定

在JS语言中不存在块级作用域

1
2
3
4
{
var num = 123;
}
console.log(num); => 123

词法作用域

在书写代码的时候决定变量的作用域

与运行时无关

以函数来划分作用域

1
2
3
4
5
6
function fn(){
var num = 123;
console.log(num); // => 123
}
fn();
console.log(num); // => Uncaught ReferenceError: num is not defined

既然函数可以划分作用域,那么通过函数的自调用就可以模拟块级作用域了

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
11
function 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
13
var 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
24
var 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,函数fn1fn3fn4,还有一些语句

这里的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中不存在块级作用域,遵循词法作用域原则
  • 以函数来划分作用域
  • 在代码书写时就已经决定了其作用域,与代码执行无关
  • 自调用函数可以模拟块级作用域
  • 变量提升
  • 两条同级作用域链之间没有任何关系
  • 下一级可以访问上一级变量,反之不行
文章目录
  1. 1. 简介
  2. 2. 块级作用域
  3. 3. 词法作用域
  4. 4. 作用域链
  5. 5. 绘制作用域链
  6. 6. 变量搜索原则
  7. 7. conclusion: