干巴爹兔的博客 干巴爹兔的博客
首页
  • 前端文章

    • JavaScript
    • HTML
    • Vue
  • 学习笔记

    • JavaScript教程
    • React学习笔记
    • Electron学习笔记
  • 开源项目

    • cloud-app-admin
    • 下班了吗Vscode插件
    • Subversion变更单插件
  • Server

    • Django
  • 学习笔记

    • MySQL学习笔记
  • 运维

    • 服务器部署
    • Linux
  • 日常学习

    • 学习方法
关于
收藏
友链
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

干巴爹兔

卑微的前端打工人
首页
  • 前端文章

    • JavaScript
    • HTML
    • Vue
  • 学习笔记

    • JavaScript教程
    • React学习笔记
    • Electron学习笔记
  • 开源项目

    • cloud-app-admin
    • 下班了吗Vscode插件
    • Subversion变更单插件
  • Server

    • Django
  • 学习笔记

    • MySQL学习笔记
  • 运维

    • 服务器部署
    • Linux
  • 日常学习

    • 学习方法
关于
收藏
友链
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 理解JS作用域

    • 理解JS作用域笔记(一):五个阶段、词法作用域
    • 理解JS作用域笔记(完):声明提升、作用域链
    • JS面向对象

    • 《JavaScript教程》笔记
    • 理解JS作用域
    干巴爹兔
    2020-07-07
    目录

    理解JS作用域笔记(完):声明提升、作用域链

    # 开头

    笔记视频内容源自B站JavaScript从入门到放弃 第九章 深入理解JS作用域 (opens new window),笔记为自行整理复习使用,欢迎一同学习交流,转载请通知原作者

    # 三、声明提升

    # 3.1、变量声明提升

    先看一段代码:

    <body>
        <script>
          //没有编译阶段 边解释边执行
          a = 2;
          var a;
          console.log(a);
        </script>
      </body>
    
    1
    2
    3
    4
    5
    6
    7
    8

    从直观的角度看,js代码的执行是从上往下执行的,但实际上并不完全正确,函数或者变量的声明提升就会改变这个规则

    image-20200707192131246

    预解释:当变量修饰采用var来修饰的时候,将会提到最前面去声明,这个过程带来的现象称为预解释

    再看一个代码:

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

    如果按照从上而下的顺序来看,log并未找到有a这个变量应该会报错吗?答案是并没有

    image-20200707192436298

    其实在内部,JS引擎将var声明的变量提到了前面去执行,而赋值操作还是在原有的行,相当于:

    <body>
        <script>
          var a;
          console.log(a);
          a = 2;
        </script>
      </body>
    
    1
    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>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    先看一下结果:

    image-20200707192902242

    为什么是undefined呢,首先在全局作用域下,它会看到var关键字,触发状态提升:

    var a;
    console.log(a);
    a = 0;
    
    1
    2
    3

    接下来函数声明,调用,在函数作用域下寻找关键字标识符,看到了var关键字触发变量提升,注意变量提升只会提升到当前函数的作用域下不会提升外层的作用域:

    var b;
    console.log(b);
    b = 1;
    
    1
    2
    3

    c也是同理

    # 3.2、函数声明提升

    先看代码:

    <body>
        <script>
          foo();
          function foo() {
            console.log(1);
          }
        </script>
      </body>
    
    1
    2
    3
    4
    5
    6
    7
    8

    一般其他语言应该是会报错,但是在js上它能够进行执行,得到1这个输出

    image-20200707193430499

    这是因为foo函数声明进行了提升,函数声明会提升,但是函数表达式不会提升

    <body>
        <script>
          foo();
          var foo = function () {
              console.log(1);
          };
        </script>
      </body>
    
    1
    2
    3
    4
    5
    6
    7
    8

    image-20200707193622827

    这是因为js首先看到var关键字将其提升:

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

    foo在运行时还不是一个函数所以就报错了,同样的具名函数也是:

    foo();
    var foo = function bar() {
    console.log(1);
    };
    
    1
    2
    3
    4

    image-20200707193852106

    # 3.3、声明的注意事项

    声明提升应该分为两种,一个是变量声明提升一个是函数声明提升,当同时存在变量和函数声明,js引擎会有些提升哪一个呢:

    <body>
        <script>
          var a;
          function a() {}
          console.log(a);
        </script>
      </body>
    
    1
    2
    3
    4
    5
    6
    7

    image-20200707194239488

    可以看到,变量的声明优先于函数的声明,但是函数的声明会覆盖未定义的同名变量

    <body>
        <script>
          var a = 10;
          function a() {}
          console.log(a);
        </script>
      </body>
    
    1
    2
    3
    4
    5
    6
    7

    一旦变量被定义了,就不会被同名的函数覆盖

    image-20200707194435111

    首先js引擎看到var标识符对a进行状态提升,函数的声明提升在变量之后,所以相当于:

    var a;
    function a() {}
    a = 10;
    console.log(a);
    
    1
    2
    3
    4

    变量的重复声明是无用的,但是函数的重复声明会覆盖前面的声明(无论是变量还是函数声明)

    var a = 1;
    var a;//无用
    console.log(a);
    
    1
    2
    3

    image-20200707194918165

    相当于:

    var a;
    var a;
    a = 1;
    console.log(a);
    
    1
    2
    3
    4

    函数的声明提升优先级高于变量的声明提升

    var a;
    function a() {
    console.log("你好");
    }
    a();
    
    1
    2
    3
    4
    5

    image-20200707195149326

    后面的函数声明会覆盖前面的函数声明

    fn()
    function fn() {
    console.log("fn");
    }
    function fn() {
    console.log("fn2");
    }
    
    1
    2
    3
    4
    5
    6
    7

    image-20200707195348844

    总结一下,应该避免在同一作用域中重复的声明

    # 四、作用域链

    # 4.1、理解作用域链

    作用域:作用域是一套规则,用来确定在何处以及如何查找标识符。在js中作用域分为全局作用域和函数作用域,另外函数作用域可以互相嵌套

    image-20200707195747255

    作用域链:作用域链只会向外部进行查找,各个作用域的嵌套关系组成了一条作用域链。例子中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>
    
    1
    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>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    比如在fn的执行环境下保存着一个x、a、bar,还有别的像arguments\this等,如视频的图:

    image-20200707201758681

    执行流:执行流也叫执行的顺序,可以通过浏览器打断点的形式查看。

    # 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>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    每一个方法调用时,都会将当前的执行环境压入栈中,当运行fn(0)时,fn的执行环境就会被压入栈中,在当前的栈中就会存在全局作用域和函数作用域。

    image-20200707202533960

    执行环境栈就是一个压栈和出栈的过程,以调用fn(0)函数为例子,全局的执行环境压入栈中,函数fn执行环境也压入栈中,这就是压栈的操作。运行时,函数fn处于活跃状态,当fn执行完成就要进行出栈操作,再把控制权交给全局

    image-20200707202931354

    以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,当执行环境结束,则会被弹出,活跃状态转交给上一层执行环境

    image-20200707203605634

    # 五、总结

    1、在js中,除了全局作用域,每个函数都会创建自己的作用域

    2、作用域在函数定义的时候已经确定了,与函数调用无关 3、通过作用域,可以查找作用域范围内的变量和函数有哪些,却不知道变量的值是什么。所以作用域是静态 4、对于函数来说,执行环境在函数调用时确定的。执行环境包含作用域内的所有的变量和函数的值。在同一个作用域下,不同的调用会产生不同的执行环境,从而产生不同的变量和值。所以执行的环境是动态的

    编辑 (opens new window)
    上次更新: 2022/08/26, 15:52:02
    理解JS作用域笔记(一):五个阶段、词法作用域
    JS面向对象编程笔记(一):原型、原型链

    ← 理解JS作用域笔记(一):五个阶段、词法作用域 JS面向对象编程笔记(一):原型、原型链→

    最近更新
    01
    使用Vscode开发一个小插件
    10-21
    02
    Vscode插件配置项监听
    10-18
    03
    使用has属性构造必填效果
    10-14
    更多文章>
    Theme by Vdoing | Copyright © 2020-2023 互联网ICP备案: 闽ICP备18027236号
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式