关于javascript:深入理解JavaScript之执行上下文闭包

38次阅读

共计 2347 个字符,预计需要花费 6 分钟才能阅读完成。

在了解闭包之前,须要先来理解几个概念,上下文、作用域链、流动对象、变量对象:

  • 上下文:函数的上下文决定了他们能够拜访哪些数据,以及他们的行为。全局上下文是最外层的上下文,当代码执行流进入到函数时,函数的上下文被推到上下文栈上,当函数执行完之后,上下文栈会弹出该函数上下文。
  • 作用域链:上下文中代码执行的时候会创立作用域链,它 决定了各级上下文中代码拜访变量或函数的程序。代码正在执行的上下文变量对象总是位于作用域链最顶端,而后是蕴含上下文对象,而后是下一个蕴含上下文对象,直至到全局上下文对象为止。
  • 流动对象:函数上下文中,蕴含其中变量的对象。
  • 变量对象:全局上下文中,蕴含其中变量的对象。

咱们来总结一下,在调用一个函数时,它的作用域链都保留了哪些对象?

  1. 在函数被调用时,先创立一个执行上下文,并创立一个作用域链。
  2. argument 和其余命名参数初始化该函数的流动对象。
  3. 内部函数的流动对象是该函数作用域链的第二个对象,该作用域链始终向内部串起所有的蕴含该函数的流动对象,晓得全局上下文才终止。

上面申明并调用了一个办法 compare(),当初来梳理一下该办法从创立到执行的过程:

  function compare(value1, value2) {if (value1 < value2) {return -1;} else if (value1 > value2) {return 1;} else {return 0;}
  }

  let result = compare(5, 6);

1. 执行全局代码,创立全局执行上下文,全局上下文被压入执行上下文栈

    ECStack = [// 执行上下文栈
        globalContext
    ];

2. 全局上下文变量即变量对象初始化,初始化的同时,compare 函数被创立,创立作用域链,并在外部属性 [[scope]] 中预装载全局变量对象。

  globalContext = {// 变量对象初始化
        VO: [global],
        Scope: [globalContext.VO],
        this: globalContext.VO
    }

  compare.[[scope]] = [//compare 在作用域链预装载全局变量对象
        globalContext.VO
  ];

3. 执行 compare 函数,创立 compare 函数执行上下文,compare 函数执行上下文被压入执行上下文栈

  ECStack = [// 执行上下文栈
        comapreContext,
        globalContext
    ];

4.comapre 函数执行上下文初始化并为变量赋值:
1)复制函数 [[scope]] 属性创立作用域链,
2)用 arguments 创建活动对象,
3)初始化流动对象,即退出形参、函数申明、变量申明,
4)将流动对象压入 comapre 作用域链顶端。

    comapreContext = {
        AO: {
            arguments: {
                0:5
                1:6
                length: 2
            },
            scope: undefined,
        },
        Scope: [AO, globalContext.VO],
    }

5. 执行代码,函数执行结束后返回,并将函数 comapre 的执行上下文从执行上下文栈中弹出。

    ECStack = [globalContext];

compare()办法的作用域链如下图:
作用域链实际上是一个蕴含指针的列表,每个指针别离指向一个变量对象,然而物理上不会蕴含相应的对象。

闭包

《JavaScript 高级编程》:闭包是指那些援用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的。
MDN– 闭包:一个函数和对其四周状态(lexical environment,词法环境)的援用捆绑在一起(或者说函数被援用突围),这样的组合就是闭包(closure)。

定义略显形象,借用阮一峰老师的了解:闭包就是可能读取其余函数外部变量的函数。 因为在 Javascript 语言中,只有函数外部的子函数能力读取局部变量,因而能够把闭包简略了解成 ”定义在一个函数外部的函数“。所以,在实质上,闭包就是将函数外部和函数内部连接起来的一座桥梁。

  var winVar = 'window- 小白';

  function fun() {
    var funVar = 'fun- 小白';
    console.log(winVar)//window- 小白
    console.log(funVar)//fun- 小白
  }

  fun();
  console.log(funVar)//Uncaught ReferenceError: funVar is not defined

函数作用域链如下图:当函数在作用域链顶端找不到 winVar 对象,就会去全局变量对象中寻找,而在 winodow 中,无法访问到 fun()函数的变量,因而须要应用闭包。

闭包能够用在许多中央。它的最大用途有两个,一个是 能够读取函数外部的变量 ,另一个就是 让这些变量的值始终保持在内存中

  function fun() {
    var funVar = 'fun- 小白';
    return function () {return funVar;}
  }

  let variable = fun();
  let result = variable();
  console.log(result)//fun- 小白

函数的作用域链如下图,在 fun 办法返回匿名函数后,匿名函数的作用域链被初始化为蕴含 fun 的流动对象和全局变量对象 ,尽管fun()函数执行完结后,其执行上下文的作用域链会销毁,然而在 匿名函数的作用域链中,依然有对他的援用。这样应该就不难理解为什么闭包能够读取函数外部的变量,也能够让变量的值始终保持在内存中了。

返回的匿名函数被保留在 variable 办法中,把 variable()办法设置为 null 能够解除对函数的援用,从而让垃圾回收程序将内存开释掉。 variable = null;

同时因为闭包会保留蕴含它们的函数作用域,所以比其余函数更占内存,适度应用闭包会导致内存适度占用。

正文完
 0