导航


HTML

CSS

JavaScript

浏览器 & 网络

版本管理

框架

构建工具

TypeScript

性能优化

软实力

算法

UI、组件库

Node

冷门技能

什么是作用域?

理解

简单点理解:

<script>
  var b = 2;
  function fn() {
    var a = 1;
    console.log(b);
  }
  fn(); // 2
  console.log(a); // a is not defined  不能访问函数作用域中的变量a
</script>

全面理解

比如特殊的全局执行环境中的变量对象 window 对象,因此所有全局变量和函数都作为 window 对象的属性和方法创建的。在 Node 环境中,全局执行环境是 global 对象

<script>
  var a = 1;
  function sum(a, b) {
    return a + b;
  }
  var n = window.sum(2, 3); // sum 相当于window对象上的方法
  console.log(window.n); // 5  n相当于window对象的属性
  console.log(window.a); // 1  a相当于window对象的属性
</script>

解析

⭐ JS 中作用域的分类

JS 中有 3 种类型的作用域:

全局作用域

编写在 script 标签中的 js 代码(或单独 js 文件),都是在全局作用域中。

let arr = [1, 2, 3]; // 堆内存开辟空间,用来保存[1,2,3]
arr = null; // 垃圾回收器会把[1,2,3]回收掉,释放其在堆内存中占用的空间

局部作用域(函数作用域)

写在函数内部的代码,就是在局部作用域中

<script>
  function fn(a, b) {
    var c = 10;
    console.log((a + b) * c);
  }
  fn(1, 2); //函数调用创建函数作用域,代码执行用,作用域和变量a,b,c销毁
  fn(2, 3); //函数调用创建函数作用域,代码执行用,作用域和变量a,b,c销毁
</script>

特殊的闭包

<script>
  function checkWeight(weight) {
    return function (_weight) {
      weight > _weight ? alert("过胖") : alert("ok达标");
    };
  }
  var P1 = checkWeight(100); // 调用完毕,作用域和变量weight不会被销毁
  P1(110); // 调用完毕,作用域和变量_weight会被销毁
</script>

解析

块级作用域

使用 let 或 const 关键字声明的变量,会形成块级作用域。

{
  let a = 1;
}
console.log(a); // 会报错,{}里是块级作用域,外面是访问不到里面的变量的
<script>
  for (let i = 0; i < 3; i++) {
    console.log(i); // 0 1 2
  }
  console.log(i); // i is not defined
</script>

注意点:

对象的 {} 不会形成块级作用域

⭐ 总结

在 Javascript 中,作用域分为 全局作用域 、 函数作用域 和 块级作用域

Untitled

作用域有上下级关系,上下级关系的确定就看函数是在哪个作用域下创建的。如上,fn作用域下创建了bar函数,那么“fn作用域”就是“bar作用域”的上级。

作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。

什么是作用域链?

当代码在一个环境中执行时,会创建变量对象的一个作用域链(作用域形成的链条)

作用域链查找:

内部环境可以通过作用域链访问所有外部环境,但外部环境不能访问内部环境的任何变量和函数。

<script>
  var a = 1;
  var c = 4;
  function fn1() {
    var a = 2;
    var b = 3;
    function fn2() {
      var b = 2;
      console.log(a); // 2  自身没有,沿着作用域链向上找
      console.log(b); // 2  自身有,就用自身的
      console.log(c); // 4  自身没有,沿着作用域链向上找,直到全局作用域中找到c=4
    }
    fn2();
  }
  fn1();
</script>

总结

一般情况下,变量取值,是到 创建这个变量 的 函数的作用域 中取值

但是如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链

怎么理解 JS 静态作用域和动态作用域

<script>
  var a = 1;
  function fn() {
    console.log(a);
  }
  function test() {
    var a = 2;
    fn();
  }
  test(); // 1
</script>

最终输出的结果为 1

说明 fn 中打印的是全局下的 a ,这也印证了 JavaScript 使用了静态作用域。

静态作用域执行过程

当执行 fn 函数时,先从内部的 AO 对象查找是否有 a 变量,如果没有,沿着作用域链往上查找(由于 JavaScript 是词法作用域),上层为全局 GO ,所以结果打印 1****

动态作用域执行过程

如果采用的是动态作用域执行过程,那么就是执行 fn 函数,首先从函数内部查询 a 变量,如果没有,就从调用函数的作用域,即 test 函数的作用域内部查找变量 a,所以打印结果 2

习题

通过习题理解静态作用域: 函数定义位置就决定了作用域。

习题一

var a = 1
function fn1(){
    function fn3(){
        var a = 4
        fn2()
    }
    var a = 2
    return fn3
}
function fn2(){
    console.log(a)
}
var fn = fn1()
fn()

上面代码中:

做题之前,一定要理解 静态作用域 的概念。该题 fn2 定义在全局上,当 fn2 中找不到变量 a 时,它会去全局中寻找,与 fn1fn3 毫无关系,打印 1.

习题二

var a = 1
function fn1(){
    function fn2(){
        console.log(a)
    }
    function fn3(){
        var a = 4
        fn2()
    }
    var a = 2
    return fn3
}
var fn = fn1()
fn()

fn2 是定义在函数 fn1 内部,因此当 fn2 内部没有变量 a 时,它会去 fn1 中寻找,跟函数 fn3 毫无关系,如果 fn1 中寻找不到,会到 fn1 定义的位置的上一层(全局)寻找,直至寻找到第一个匹配的标识符。本题可以在 fn1 中找到变量 a,打印 2

习题三

var a = 1;
function fn1(){
    function fn3(){
        function fn2(){
            console.log(a)
        }
        var a;
        fn2()
        a = 4
    }
    var a = 2
    return fn3
}
var fn = fn1()
fn()

该题 fn2 定义在函数 fn3 中,当 fn2 中找不到变量 a 时,会首先去 fn3 中查找,如果还查找不到,会到 fn1 中查找。本题可以在 fn3 中找到变量 a ,但由于 fn2() 执行时,a 未赋值,打印 undefined

总结

关于JavaScript 的静态作用域,我们只需要记住一句话:函数定义的位置就决定了函数的作用域,遇到题目时不要被别的代码干扰到。

而且习题二,习题三查找变量的过程,其实本质上就是沿着作用域链在查找。