JS作用域

    更新时间: 2022-08-30 12:12:53
    点击量: 529
    标签: 前端js

    简介:作用域为可访问变量,对象,函数的集合,限定其可用性的范围即作用域,作用域的使用提高了程序逻辑的局部性,增强程序的可靠性

    文章均为个人原创, 搬运请附上原文地址感谢, 原文来自MasterYi博客

    前言

    作用域 (scope)

    • 作用域为可访问变量,对象,函数的集合。
    • 通常来说一段程序代码中使用的变量和函数并不总是可用的,限定其可用性的范围即作用域,作用域的使用提高了程序逻辑的局部性,增强程序的可靠性,减少名字冲突。
    • JS的作用域主要为全局、局部、ES6新增的块级

    全局作用域

    • 网页中所有脚本和函数均可使用,window 对象的内置属性都拥有全局作用域
    • 全局作用域的变量只在页面关闭时销毁

    局部作用域

    • 因为局部变量只作用于函数内,所以不同的函数可以使用相同名称的变量。
    • 局部变量在函数开始执行时创建,函数执行完后局部变量会自动销毁。
        function test(){
            var a = 1;
        }
        test(); // 执行结束后, 局部变量会自动销毁
        console.log(a); // 无法访问局部的变量

    块级作用域

    • ES6新增作用域, 块级作用域通过 let 和 const 声明
    • js的常见块级场景有 if、for、while、switch
    • 内层变量覆盖外层变量 (下篇会讲 就是作用域链AO读取的问题)
    • 用来计数的循环变量泄露为全局变量
        for(var a = 1; a < 10 ; a++){
        }
        console.log(a); // 打印10 因为原有的var无块级作用域会泄漏为全局变量, 改为let即可
    
        if(true){
            var b = 1;
        }
        console.log(b); // 打印1 var无块级作用域会泄漏为全局变量
    
        var arr = [1, 2, 3]
        while (arr.length){
            var c = arr.pop();
        }
        console.log(c); // 打印 1  var无块级作用域会泄漏为全局变量
    
        switch (1){
            case 1:
                var d = 1;
        }
        console.log(d); // 打印 1  var无块级作用域会泄漏为全局变量

    作用域链(scope chain)

    • 作用域链则是作用域的集合 (子集可以访问父集,父集不能访问子集)
    • 作用域链决定了哪些数据能被函数访问。当一个函数创建后,它的作用域链会被创建此函数的作用域中可访问的数据对象填充。
    • Function对象有一个仅供 JavaScript 引擎存取的内部属性 [[Scope]]。[[Scope]] 存储了执行上下文的对象的集合。这个集合被称为函数的作用域链,它决定了哪些数据能被访问。
    • 沿着作用域链,内层代码可以访问外层声明变量与函数,外层无法访问内层声明的变量与函数

    示例

    • 示例只是简单解释了一下作用域的范围
    var a = 1;
    var b = 2;
    function foo1(){
        console.log(a);  // 打印 undefined, 读取顺序: foo1(读到a, 此处的a之所以为undefined,是因为函数激活时创建了AO(执行上下文),  var声明的变量进行了提升 名字为属性值,值默认为undefined )
        var a = 2;
        console.log(a);  // 打印 2, 读取顺序: foo1(读到a )
        
        function foo2(){
            console.log(a); // 打印 undefined , 读取顺序: foo2(读到a)
            var a = 3;
    
            function foo3(){
                console.log(a); // 打印 3, 读取顺序: foo3 -> foo2(读到a)
                var c = 4;
            }
            
            foo3();
            console.log(b); // 打印2, 读取顺序: foo2 -> foo1 -> 全局 (读到b)
            console.log(c); // 异常, 并不会读取foo3中的 c, 子集可以访问父集,父集不能访问子集 , 读取顺序: foo2 -> foo1 -> 全局(全局无抛异常)
        }
        
        foo2()
    }
    
    foo1();