• 【JS核心概念】作用域与作用域链


    重点:

    • [x] 父级作用域无法访问其子级作用域中的变量和函数,子级作用域可以使用其父级作用域中的变量和函数;
    • [x] 当执行一个函数的时候,如果我们需要查找某个变量的值,那么会去这个函数被定义时所在的作用域链中去查找,一旦找到需要的变量,就会停止向后追溯。

    一、作用域

    简单来说,作用域就是变量和函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。作用域又分为全局作用域、局部作用域以及ES6中的块级作用域。

    • 全局作用域
      • 全局作用域中的变量和函数在任何地方都可以被访问到;
      • 未定义直接赋值的变量为自动变为全局变量;
        var a1 = 1;
        function say1() {
            console.log(a1); // 1,函数内部也可以访问全局变量
            a2 = 2;
            console.log(a2); // 2,a2自动声明为拥有全局作用域,在严格模式下这种写法会报错
        }
        say1();
        console.log(a2); // 2
        
        // 结果为:1 2 2
    
    • 局部作用域
      • 在函数中声明的变量会变为函数的局部变量,只能在函数内部使用,因此函数作用域又称为局部作用域;
      • 每个函数都有自己的作用域;
        function say2(a) {
            console.log(a); // 2
            var b = 1;
            console.log(b); // 1
        }
        say2(2);
        console.log(a); // ReferenceError: a is not defined,函数的参数也是该函数的局部变量
        console.log(b); // ReferenceError: b is not defined,局部变量不能被外部访问
    
    • 块级作用域
      • ES5中没有块级作用域,这导致很多场景都不合理:

        • 内层变量可能会覆盖外层变量
            var a = 1;
            // 函数fn的作用是if代码块内部使用内层的变量a,外部使用外层的变量a。
            // 但是函数fn执行后,输出结果为undefined,原因在于变量提升导致内层的变量a覆盖了外层的变量a。
            function fn() {
                console.log(a);
                if (false) {
                    var a = 2;
                }
            }
            fn(); // undefined
        
        • 用来计数的循环变量泄漏为全局变量
            // 变量i用于控制循环,但是循环结束后,变量i没有被销毁,而是变成了全局变量
            for (var i = 0; i < 5; i++) {
                console.log(i); // 0 1 2 3 4
            }
            console.log(i); // 5
        
      • ES6新增了块级作用域

        • 使用let和const命令声明变量会形成块级作用域
            function fn() {
                let a = 1;
                if (true) {
                    let a = 2;
                }
                console.log(a);
            }
            fn(); // 1
        
            for (let i = 0; i < 5; i++) {
                console.log(i); // 0 1 2 3 4
            }
            console.log(i); // ReferenceError: i is not defined
        
            {
                const c = 'C'; 
                {
                    console.log(c); // C
                }
            }
            console.log(c); // ReferenceError: c is not defined
        

    二、作用域链

    作用域链,顾名思义,可以想象为是由许多个作用域串在一起形成的一个链。那么问题来了,这许多个作用域是从何而来呢?又是如何串在一起形成链的呢?

    • 作用域链的形成
      • 程序执行时,会先创建全局上下文,并将全局上下文的变量对象挂载在作用域链的后端端;
      • 如果全局上下文中调用了一个函数,则创建该函数的执行上下文,并将该函数上下文的变量对象挂载在全局上下文变量对象之上;
      • 如果该函数的内部又调用了一个函数,同样创建该函数的执行上下文,并将其变量对象挂载在外层函数上下文变量对象之上……
      • 以此类推,形成了一个作用域链。
    • 作用域链的特点
      • 作用域链的前端是当前正在执行的代码所处环境的变量对象;
      • 全局上下文的变量对象位于作用域链的后端
    • 查找标识符

    当在某个环境中为了读取或者写入而引入一个标识符时,必须通过搜索来确定该标识符实际代表什么。

    搜索过程从作用域链的前端开始,向后逐级查询与给定名字匹配的标识符,如果找到了,则停止搜索,变量就绪;如果追溯到了全局环境的变量对象还是没有找到,则说明该变量尚未声明。

    三、练习时刻

        var a = 1;
        function fn() {
            console.log('1:' + a);
            function bar() {
                console.log('2:' + a)
            }
            var a = 2;
            bar();
            console.log('3:' + a);
        }
        
        fn();
        
        // 输出结果为:
        // 1:undefined => 使用var定义的变量会在当前作用域提升,且只提升声明,不提升赋值
        // 2:2 => 函数bar定义在函数fn中,因此其作用域为bar->fn->global,bar中没有定义a,则去fn中查找,在fn中找到后返回
        // 3:2 => 这行代码所在的作用域为fn->global,所以会在先在fn中查找a,找到后返回a的值
    
        var a = 1;
        function fn() {
            console.log('1:' + a);
            var a = 2;
            bar();
            console.log('2:' + a);
        }
        function bar() {
            console.log('3:' + a);
        }
        
        fn();
        
        // 输出结果为:
        // 1:undefined => 使用var定义的变量会在当前作用域提升,且只提升声明,不提升赋值
        // 3:1 => 函数bar是定义在全局作用域中的,所以作用域链是bar->global,bar中没有定义a,则去global中查找
        // 2:2 => 这行代码所在的作用域为fn->global,所以会在先在fn中查找a,找到后返回a的值
    
        var a = 1;
        function fn() {
            console.log('1:' + a);
            a = 2;
        }
        a = 3;
        function bar() {
            console.log('2:' + a);
        }
        
        fn();
        bar();
        
        // 输出结果为:
        // 1:3 => ① fn中的a=2是给变量a赋值,而不是声明变量a,因此执行fn时,查找到的变量a是全局作用域中的a;② JS中的代码是顺序执行的,因此执行fn之前已经执行了a=3,此时全局作用域中的a的值为3
        // 2:2 => ① fn中的a=2修改了全局作用域中a的值,因此执行bar时,a的值为2
    
        var a = 1;
        function fn(f) {
            var a = 2;
            return f;
        }
        function bar() {
            console.log(a)
        }
        
        var f1 = fn(bar);
        f1();
        
        // 输出结果为:
        // 1 => 函数中变量的值由函数定义时的所在的作用域链决定
    
        var a = 1;
        function fn(f) {
          var a = 2;
          return function () {
            console.log(a)
          }
        }
        
        var f1 = fn();
        f1();
        
        // 输出结果为:
        // 2 => 函数中变量的值由函数定义时的所在的作用域链决定
    



  • 相关阅读:
    业务领域建模Domain Modeling
    用例建模Use Case Modeling
    分析一套源代码的代码规范和风格并讨论如何改进优化代码
    结合工程实践选题调研分析同类软件产品
    如何提高程序员的键盘使用效率?
    第一次博客作业
    python_同时迭代多个对象
    python_判断奇偶数
    印象笔记markdown使用笔记
    【转】A*算法解决八数码问题
  • 原文地址:https://www.cnblogs.com/jiafifteen/p/12201388.html
Copyright © 2020-2023  润新知