JS学习计划(一):js的执行上下文,作用域,闭包的那些事

199 阅读5分钟

前言

在网上看到一篇很好的思维导图的文章。之前自己学习总是毫无目的,东打一枪,西打一炮的。决定按照别人的思维导图整理知识点,以分支为单位,预计10篇文章左右来整理。

下面直接上图:

执行上下文

执行上下文

在理解这个执行上下文前,先来看一个问题——js执行顺序的问题。Javascript给我们一个很直观的感受,就是顺序执行。

 function foo() {
 console.log('foo1');
 }
 foo();  // foo2
 function foo() {
     console.log('foo2');
 }
 foo(); // foo2
由这个题,我们可以知道这是因为javascript引擎并非是一行一行的分析和执行程序,而是一段段的去执行。当执行一段代码的时候。比如函数提升的这个过程。由此引出两个问题:1.这个段是如何划分的。2.javascript引擎遇到怎么样的代码会进行准备工作。
  • javascript的可执行代码有那些呢? 全局代码,函数代码,eval代码

当执行到一个函数的时候,便会进行一个准备工作。那么这个准备的过程,咱们就叫他执行上下文。

  • 全局执行上下文 任何不在函数内部的代码都在全局上下文忠。它会执行两件事件:创建一个全局的window对象(浏览器的情况下)。并且设置this的值等于这个全局对象。一个程序忠只有一个全局执行上下文。

  • 函数执行上下文 每当一个函数被调用的时候,都会为这个函数创建一个新的上下文。每个函数都有它自己的执行上下文,不过是在函数被调用的时候创建的。函数上下文可以有任意多个。

  • Eval函数执行的上下文

    • 执行在 eval 函数内部的代码也会有它属于自己的执行上下文。eval方法就像是一个完整的ECMAScript解析器,它只接受一个参数,即需要执行的ECMAScript(或JavaScript)字符串。
    • 缺点:1.性能差:引擎无法在编译时候对作用域查找进行优化。2.欺骗作用域。

接下来的问题在于,我们写的函数多了去了,怎么管理这么多的上下文呢?所以javaScript引擎创建了执行上下文栈。

  • 执行栈(一种后进先出的数据结构) 当JavaScript引擎开始解释执行代码的时候: 首先遇到的就是全局解释执行的代码,直接将全局执行上下文压入执行栈,只有当整个应用程序结束时才被清空。所以执行栈的最底部永远有个全局执行上下文栈。函数调用:为函数创建一个新的执行上下文压入当前执行栈。后进先出。
下面咱们直接来看一段代码:
```javascript
    var a = '0';
    console.log(a);
    function first() {
      console.log('1');
      second();
      console.log('3');
    }

    function second() {
      console.log('2');
    }

    first();
    console.log('4');


    //0 1 2 3 4
```
到这里先留下两个题,随后咱们就进入下一个话题作用域/作用域链。

问1执行结果?问2两种的上下文栈的具体变化有何不同?
   var scope = "global scope";
   function checkscope(){
       var scope = "local scope";
       function f(){
           return scope;
       }
       return f();
   }
   checkscope();
        var scope = "global scope";
        function checkscope(){
            var scope = "local scope";
            function f(){
                return scope;
            }
            return f;
        }
        checkscope()();

作用域/作用域链

作用域:作用域是指程序源代码中定义变量的区域。作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。JavaScript 采用词法作用域(lexical scoping),也就是静态作用域。

静态作用域:函数作用域在函数定义的时候就决定了。

动态作用域:函数作用域在函数调用的时候才决定的。
        var value = 1;
        function foo() {
            console.log(value);
        }
        function bar() {
            var value = 2;
            foo();
        }
        bar();
  • 站在静态作用域上思考这个问题: 执行foo函数,foo函数内部没有value这个局部变量,没有就去找书写的位置,所以打印结果是1.

  • 站在动态作用域上思考这个问题: 执行foo函数,foo函数内部查找有没有value这个局部变量,没有就去调用的作用域里面去找,结果为2.

  • 打印结果:1,也就是说javascript是采用静态作用域。

  • 问题 现在我们再回过头来再看看执行上下文中的两段代码。

    打印结果都是:local scope.

    原因: JavaScript采用词法作用域,函数的作用域基于函数创建的位置。

闭包

一个函数内嵌套一个函数。那么这个嵌套的函数就被称为闭包。(外部是代码块依旧产生闭包)

闭包的例子:

        (function(){
            let x=1;
            function log(){
                console.log(x);
            }
            log();
        })


        {
            let x = 1;
            setTimeout(function log(){
              console.log(x);
            }, 10000);
        }
* 内部函数可以访问外部函数中定义的变量,即使是外部函数已经执行完毕。
   (function autorun(){
       let x = 1;
       setTimeout(function log(){
         console.log(x);
       }, 10000);
   })();

* 闭包和循环
咱们先来看一个经典的问题
    function b(){
     for(var i=1; i<=3; i++){
       setTimeout(()=>{
           console.log(i);
       },1000);
     }
    }
    b();
打印结果多少呢?444
闭包只存储外部变量的引用,而不会拷贝外部变量的值。
* 解决办法:IIFE和使用let变量声明

总结:
1. 闭包是一个可以访问外部作用域中变量的内部函数。
2. 被引用的变量只有在闭包被销毁的时候才会销毁。
3. 闭包使得定时器,事件处理,ajax请求等异步任务更加容易。
4. 可以通过闭包达到封装性

最后

* 以上都是基于网上的各类文章资料的整理,加上自己学习的记录。
* 下一章内容:this/apply/call/bind的那些事。