美文网首页
由一道题展开的作用域链知识点

由一道题展开的作用域链知识点

作者: 饥人谷_魏少锋 | 来源:发表于2018-03-29 21:17 被阅读6次

    在学习作用域链的时候碰到一道很代表的题目,并展开了很多知识点。所以贴出来和大家交流。

    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() //输出多少
    

    在解题之前先来看一些基础知识点吧

    var

    1. var的声明前置
      function fn() {
          fi (true) {
          console.log(a)  // a 只有声明,打印出undefined
          }else {
          var a = 1   // var a 声明会提前到function fn()函数里的最前面
          console.log(2)
          }
      }
      
    2. 立即执行函数
      目的:包裹函数里的变量,使他们变成局部变量,避免污染全局变量
      表达式:
    (function(){
        var a = 1
        window.frank = function(){
            console.log(a)
        }
    }())   //立即执行函数,这个函数是匿名的
    

    function

    1. function 的声明前置
      function 有两种声明方法
    /* 函数声明 myFunc */
     function myFunc(theObject)
     {
       //实参 xxx 和形参 theObject 指向同一个对象.
       theObject.brand = "Toyota";
     }
     // 函数表达式 printName
    var myFunction = function(){  // 没有名字就是匿名函数
        statements
    }
    

    接下来看看这个代码

        sayName('world');   //  因为函数已经声明,所以可以执行
        sayAge(10);      // 变量不能传参,会报错
        // function函数声明会提前到最上面
        function sayName(name){ 
            console.log('hello ', name);   
        }
        // 函数表达式将变ayAge作为变量声明前置
        var sayAge = function(age){
            console.log(age);
        };
    
    1. 函数返回值
      如果函数传的实参不是引用类型而是普通类型,一定要先看返回值return,就算这个函数中途跑去执行别的函数,但是最终结果还是return的值。
      如果没有return值默认是返回undefined。
      比如
       function sumOfSquares( ){
            var sum = 0;
           for(var i = 0; i < arguments.length; i++) {
               sum = sum + arguments[ i ] *  arguments[ i ]
             }
               return sum       
       }
       var result = sumOfSquares(2,3,4)
       var result2 = sumOfSquares(1,3)
    

    如果是传入的实参是引用类型,比如数组,那么可以通过改变arguments来改变返回的值

    var arr = [3, 4, 6]
    function squireArr( arr ){
        for ( var i = 0; i < arr.length ; i ++) {
        arr[ i ] = arr[  i ] * arr[ i ]
      }
    }
    squireArr(arr)
    console.log(arr)  // [9, 16, 36]
    

    作用域链

    讲了这么多,我们来看看什么是作用域链。
    先看定义好的函数找寻变量的规则

    • 函数在执行的过程中,先从自己内部找变量
    • 如果找不到,再从创建当前函数所在的作用域去找, 以此往上
    • 注意找的是变量的当前的状态

    再来看看这道题目

    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() //输出多少
    

    解释:
    fn()执行,var fn = fn1(),调用fn1(),fn1函数return了fn3,fn3中调用了fn2(),fn2()打印出了a,也就是fn()打印出了a,那fn2中的a指的是什么呢,我们先看第一条规则,找寻自身的变量,发现fn2()中没有这个变量a,那就遵循第二条规则,往上找,fn2()上面就是fn1(),fn1()中有一句var a = 2,虽然是在fn2()后面,但是fn1()是fn2()的上升作用域,这里还不用考虑变量声明前置。

    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() //输出多少
    

    其实完整的解析代码是下面这样子的

    var a = undefined //声明变量提前
    function fn1()   //函数声明提前
    var fn = undefined  //变量声明提前
    
    a = 1   // 赋值
    fn = fn1()  //到这里执行的时候产生了新的作用域
    ---
    //fn1()内部的作用域解析过程如下
    function fn2() { }
    function fn3() { }
    var a = undefined
    
    a = 2 //到这里,a的值已经赋值确定为2了
    return fn3()  //这里返回了函数fn3
    ---
    //返回到上一层的作用域,fn() = fn1(),fn又要去执行fn3()
    //执行fn3(),又要创立新的作用域
    var a 
    a = 4
    fn2()
    //在当前作用域找不到fn2,所以到外层作用域找,找到fn2(()并执行
    //然后又出现了一个执行过程,fn2()中执行了console.log(a),当前作用域也没有a,就
    //是上一层,即是fn2所在的作用域,也就是fn1的内部,回去看到fn1的解析过程,a已经
    //被赋值为2了,所以console的a = 2.
    

    流程图连接: https://www.processon.com/view/link/5abce6e2e4b007d25127faf3

    现在看懂了吗,是不是觉得觉得很流畅。但是在实际中,如果没有上述流程,看原代码很容易因为寻找a的时候发现var a = 2在fn2()后面,以为只有变量声明提前,会输出undefined,这是很坑的一点,博主就是受了这个影响,被困扰了很久,好在后来有人提醒才看懂了流程。

    接下来让我们再看两道题练练手吧

    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()   //输出1
    
    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()时还没赋值a,所以是undefined
    

    相关文章

      网友评论

          本文标题:由一道题展开的作用域链知识点

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