美文网首页
2018-12-18

2018-12-18

作者: 废废_siri | 来源:发表于2018-12-18 21:33 被阅读0次

    JS高级

    作用域&作用域链

    作用域:

    1.作用域的个数:n(函数声明的个数)+1(全局作用域)
    2.作用域不会存储变量,只是执行查询规则
    3.作用域分全局作用域和函数(局部)作用域
    4.作用域是编译时产生的
    5.没有块作用域(ES5)
    6.作用域管理执行上下文,执行上下文管理变量
    7.一般情况下,一个作用域对应一个执行上下文,但递归情况下一个作用域可以对应多个执行上下文。值得注意的是,任何情况下一个作用域只有一个活动的执行上下文

    作用域链:

    函数嵌套时会产生作用域链

    --
    作用域的作用:
    1.隔离变量
    2.为变量查询制定的一套规则。
    --
    变量查询的规则:
    左查询:等号左边的变量用左查询。
    --先在变量的当前作用域里面找变量是否声明,没有就到上一层找,直到整条作用域链都没有找到(也就是全局),那么浏览器会自动为变量声明。
    右查询:等号非左边的变量用右查询规则。
    --先在变量的当前作用域里面找变量是否声明,没有就到上一层找,直到整条作用域链都没有找到(也就是全局),那么报错。
    --
    左右查询案例:

    <script>
            console.log(b); // 报错
            (function(){
                function test(a){
                    var b=a;
                    console.log(b);//2
                }
                test(2);
            })()
            console.log(b);//b is not defined
    </script>
    

    执行步骤:
    1.console.log(b); 对b右查询,在当前作用域(全局)查找b的声明,没有则报错
    2.执行立即执行函数,然后调用test(2)函数,将实参2传给形参a
    3.var b=a;对b执行左查询,在当前作用域有声明var b,且b=a,则b为2,输出2
    4.立即执行函数运行结束, 运行console.log(b);对b进行右查询,在当前作用域(全局)查找b的声明,整条作用域链上没有找到,则报错。


    作用域面试题:


    image.png

    以上示例图的执行步骤:
    1.先执行var x =10;(x为全局变量)
    2.执行show(fn);调用show()函数,将实参fn函数传给形参f,那么形参f拥有fn函数的地址值
    3.f(),调用fn()函数。
    4.console.log(x);对x执行右查询,在fn()函数中没有对变量x进行声明,那么到全局作用域里面去找,刚好找到var x =10;所以结果为10.


    变量释放&内存回收

    变量释放:
    局部变量————>对应函数的作用域执行完后则变量释放
    全局变量————>当运行程序关闭时变量才会释放
    内存回收:
    垃圾收集器会按照固定的时间间隔周期性的回收内存。一般使用标记清除,引用计数两种策略。


    执行上下文&执行上下文栈

    执行上下文(可以认为是可执行代码的执行环境):
    1.执行上下文的个数:n(函数的调用次数)+1(全局执行上下文)
    2.在函数被调用时,都会创建新的其对应作用域的执行上下文。
    3.一般情况下,一个作用域对应一个执行上下文,但递归时,一个作用域可能对应多个执行上下文。(每调用一次函数创建一个执行上下文)
    4.一个作用域只能有一个活动状态的执行上下文。(单线程、同步执行)
    5.执行上下文分为:全局执行上下文(只有一个)、函数执行上下文(可以有无数个)
    --
    执行上下文栈:
    当一个函数被调用时,会创建一个当前被调用函数的执行上下文,并将执行上下文压入执行栈中。当js第一次加载脚本时,默认进入全局执行上下文中。


    image.png
    <!--
        1. 依次输出什么?
        2. 整个过程中产生了几个执行上下文?
    -->
    <script type="text/javascript">
      var i
      console.log('global begin: '+ i)   //undefined
      i = 1
      foo(1);
      function foo(i){
        if (i == 4) {
          return;
        }
        console.log('foo() begin:' + i);    //1,2,3
        foo(i + 1);
        console.log('foo() end:' + i);     //3,2,1                                    
      }
       console.log('global end: ' + i)     //1
    </script>
    
    image.png

    执行上下文的作用:

    1.存储对应的变量
    2.规则
    ---全局执行上下文规则:
    1.var定义的全局变量,添加为window的属性
    2.function声明的全局函数,添加为window的方法
    3.提升(函数的提升优于变量的提升)
    4.this指向(window)
    --
    ---函数执行上下文规则
    1.var定义的局部变量会成为局部执行上下文(在js中拿不到局部执行上下文对象)的属性
    2.function声明的局部函数会成为局部执行上下文(在js中拿不到局部上下文对象)的方法
    3.形参变量赋值给实参
    4.为arguments赋值(实参列表)
    5.提升(函数表达式不会提升)
    6.this指向(看调用函数的对象的调用形式)
    --
    ---函数执行上下文规则的this指向
    1.普通调用      this—>window
       在严格模式下 this—>undefined
    2.隐式调用      this—>最近的调用者
    3.显示调用      this—>指定的对象
    4.构造调用      this—>构造出的实例对象


    作用域与执行上下文的关系

    对应的执行上下文 会 成为 作用域的属性(执行上下文最终会挂靠给作用域)
    --作用域:
    从浏览器的角度看作用域,它是一个c++对象,对象的属性就是执行上下文
    从js语言的角度看作用域,它是一套规则(左查询、右查询)
    --执行上下文:
    从浏览器的角度看执行上下文,它是一个c++对象,对象的属性就是它所管理的变量
    从js语言的角度看执行上下文,它是一套规则(规则如上)


    提升

    总结:
    1.提升会提升到当前作用域的最顶层
    2.变量提升是声明的提升
    3.函数提升是整体提升
    4.函数的提升优于变量的提升
    注意点:如果有两个同名函数,那么后面的函数会覆盖前面所定义的函数(在js语言中没有重载的概念,所以无法通过参数来判断到底调用哪个函数)。


    编码规范

    1.使用变量前一定要先声明(避免污染全局,迎合js引擎的提升规则)
    2.在分支中最好不要定义函数(因为在分支中的函数会变为函数表达式)

    注意点:当函数名与变量名重名时
    function demo(){...}==>var demo = function(){...}
    var demo   //跟函数名重复,js引擎在解析代码时会忽略此条声明
    demo=5;
    demo()
    //最终demo = function(){...}=5    5()    结果报错
    

    隐式丢失

    当我们对函数进行隐式调用(对象.属性)时,可能会发生隐式丢失的问题。

      1.以 对象.的形式 进行变量的赋值时容易发生隐式丢失
                var fn = obj.test;
                fn()  // 发生了隐式丢失,原本this应该指向obj,结果fn()独立调用函数,this指向全局
    
                var write = document.write;
                write("****") // 原本this应该指向document,结果发生隐式丢失,所以报错
    

    --

    bind()函数

    bind()函数(硬绑定)是用来解决隐式丢失问题的一种手段。
    bind(this,[Arg1,Arg2...]),使用bind来实现硬绑定时,会产生一个新的函数,这个新的函数的this指向被绑定的对象(也就是bind中第一个参数this指定的对象),新的函数相当于拷贝了一份原函数,将bind中括号[]中的参数相当于实参([]中的参数可以省略),传给新函数中的形参。

    <script>
            var module = {
                x: 42,     
                getX: function (a,b) {
                    console.log(a,b);
                    return this.x;
                }
            }
    
            var unboundGetX = module.getX;
            /*
            使用bind硬绑定产生一个boundGetX的函数,boundGetX函数拥有跟原函数一样的函数体(this指向了module),
            相当于拷贝了一份原函数,bind中[1,2]相当于新函数的实参
            */
            var boundGetX = unboundGetX.bind(module,1,2);      
            console.log(boundGetX());
            console.log(boundGetX);
        </script>
    

    函数调用优先级

    构造函数 > 显示调用 > 隐式调用 > 普通调用

    相关文章

      网友评论

          本文标题:2018-12-18

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