美文网首页
JS 作用域和执行上下文[2] — 作用域和作用域链

JS 作用域和执行上下文[2] — 作用域和作用域链

作者: 弱冠而不立 | 来源:发表于2020-10-04 15:37 被阅读0次

    参考文章:
    [译] 理解 JavaScript 中的执行上下文和执行栈
    彻底明白作用域、执行上下文
    JavaScript 执行上下文和执行栈
    深入理解 JavaScript 作用域和作用域链
    谈谈 JavaScript 的作用域

    1. 作用域是什么?

    作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。
    例:

    var variable_G = "global";
    function fn() {
      var variable_G = "function_global"
      var variable_F = "function";
      console.log(variable_G, variable_F); 
    }
    
    fn();  // function_global function 
    console.log(variable_G);  //global
    console.log(variable_F);  //ReferenceError: variable_F is not defined
    
    

    变量 variable_F 只在 fn 中定义了,所以只能在 fn 函数作用域中使用,在全局作用域下使用则会报错未定义。而变量 variable_Gfn 和全局中都定义了,但 fn 调用的是自己内部定义的。

    即:作用域就是一个独立的地盘,让变量不会外泄、暴露出去。也就是说作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。

    2. 词法作用域和动态作用域

    JavaScript 采用词法作用域(lexical scoping),也就是静态作用域。
    因为 JavaScript 采用的是词法作用域——函数的作用域在函数定义的时候就决定了
    这就意味着,无论函数在哪里调用,取参数时都是到函数定义时的作用域中查找
    上面这两句话就是理解闭包的关键。

    和词法作用域相对的就是动态作用域——函数的作用域在函数调用的时候才决定
    虽然JavaScript 采用词法作用域,但是 this 的指向就有点动态作用域的感觉。不过其实 this 指向是在创建执行上下文时确定。

    经典的一道面试题

    var a = 1
    function out(){
        var a = 2
        inner()
    }
    function inner(){
        console.log(a)
    }
    out()  //====>  1
    

    inner 函数在全局作用域中定义,虽然在 out 函数中调用,但是打印 a 变量的值时,还是先在定义时的作用域(即全局作用域)下查找。

    3. 全局作用域和函数作用域、块级作用域

    因为JavaScript 采用词法作用域,所以全局作用域和函数作用域都属于词法作用域

    • 全局作用域

    全局作用域下的变量,也可称为全局变量。浏览器环境下全局变量会自动挂载到 window 对象下

    • 显式声明:在最外层函数外面用 var 关键字声明的变量都属于全局作用域
    var variable = "global";
    console.log(variable);  //global
    console.log(window.variable);  //global
    
    • 隐式声明:不带有声明关键字的变量无论在哪声明都属于全局作用域
    function fn() {
      variable_F = "function";
    }
    fn();  //要先调用 fn 这个时候就隐式声明了 variable_F
    console.log(variable_F);  //global
    console.log(window.variable_F);  //global
    
    • 函数作用域

    函数作用域内,对外是封闭的,从外层的作用域无法直接访问函数内部的作用域。但是也有几种间接的方法访问。

    1. 通过 return 返回函数内部变量访问

    function returnVariable(params) {
      let fn_variable = params;
      returnfn_variable;
    }
    
    let out_variable = returnVariable("out_variable"); //====>out_variable
    

    2. 通过闭包访问函数内部变量

    function out_fn() {
      let out_variable = "out";
      return function inner_fun() {
        let inner_variable = "inner";
        return {
            out_variable,
            inner_variable
          }
      }
    }
    
    out_fn()(); //====> {out_variable:"out", inner_variable: "inner"}
    

    同时还可以利用函数内部作用域内变量,外部是无法访问的这个特性来封装库函数,使它脱离全局作用域,从而形成一个单独的作用域。

    (function(){
      
    })();
    
    // 如果需要传参,还可以这样写:(param代指形参,data代指实参)
    (function(param){
      
    })(data);
    
    • 块级作用域
      在 ES6 之前是没有块级作用域的,以 for 循环为例:
    // var 声明的变量没有块级作用域
    for(var i=0; i<3; i++) {
      var a = 3;
    }
    
    console.log(i); //====> 3
    console.log(a); //=====> 3
    
    // let 和 const 声明的变量存在块级作用域,花括号外无法访问花括号内部的变量
    for(let i=0; i<3; i++) {
      const a = 3;
    }
    
    console.log(a); //Uncaught ReferenceError: a is not defined
    console.log(i); //Uncaught ReferenceError: i is not defined
    
    

    4. 作用域链

    认识作用域之前还有一个自由变量的概念,网上是这么解释自由变量的:
    当前函数作用域内调用了一个变量 a,但是 a 变量并不在当前作用域内而是在上一级作用域内定义,那么这个变量 a 就是自由变量,以代码为例:

    var a = 1;
    function sum(b) {
      return a + b;
    }
    sum(2);
    //=======> 这个 a 就可以称之为自由变量
    

    那么作用域链就可以从自由变量这里来拓展

    var global_variable = "global";
    function out_fn() {
      var out_fnVariable = "out_fn";
      console.log(global_variable); 
      //console.log(inner_fnVariable); ===> Error
      function inner_fn() {
        var inner_fnVariable = "inner_fn";
        console.log(out_fnVariable);
        console.log(global_variable);
      }
    inner_fn();
    }
    
    out_fn(); 
    //====> 
    //global
    //out_fn
    //global
    

    总结:函数在当前作用域找不到的变量,就会向定义这个函数的作用域去寻找(有些地方也称为父级作用域,其实是不严谨的,应该是去定义这个函数的作用域里寻找)这种层层向上,类似冒泡的结构就是作用域链。

    相关文章

      网友评论

          本文标题:JS 作用域和执行上下文[2] — 作用域和作用域链

          本文链接:https://www.haomeiwen.com/subject/cyqjyktx.html