强大的闭包

60 阅读5分钟

什么是闭包?

  • ✅ 官方说法:闭包就是指有权访问另一个函数作用域中的变量的函数。

  • ✅ MDN说法:闭包是一种特殊的对象。它由两部分构成:函数,以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成。

  • ✅ 自我理解:把函数执行,形成私有上下文,并且保存和保护私有变量的机制,称之为“闭包” ,它是一种机制。

函数执行看看闭包

过程

  • 形成私有上下文

  • 进栈执行

  • 一些列操作

    1. 初始化作用域链(两头<当前作用域,上级作用域>)

    2. 初始化this

    3. 初始化arguments

    4. 赋值形参

    5. 变量提升

    6. 代码执行

      1. 遇到变量就先看是否是自己私有的,不是自己私有的按照作用域链上查找,如果不是上级的就继续线上查找,,直到 EC(G),变量的查找其实就是一个作用域链的拼接过程,拼接查询的链式就是作用域链。
  • 正常情况下,代码执行完成之后,私有上下文出栈被回收。但是遇到特殊情况,如果当前私有上下文执行完成之后中的某个东西被执行上下文以外的东西占用,则当前私有上下文就不会出栈释放,也就是形成了不被销毁的上下文,闭包。

三种情况

  • 当前上下文的某些东西被上下文以外的某些东西占用,那么当前上下文就不会被释放。

  • 如果没有被占用就是执行完成之后就被释放。

  • 除了这上面两种情况还有一种情况是,上下文没有被占用,但是要紧接着被用一次,这样没有用完之前是不能释放的,用完在释放,这样就形成了一个临时不被释放 )

函数每一次执行 都是从新形成一个全新的私有上下文,和之前执行产生的上下文没有必然的联系

闭包的作用

函数执行会形成全新的私有上下文,这个上下文可能被释放,也可能不被释放,不论是否被释放,它的作用是:

  1. 保护:划分一个独立的代码执行区域,在这个区域中有自己私有变量存储的空间,而用到的私有变量和其它区域中的变量不会有任何的冲突(防止全局变量污染)
  2. 保存:如果上下文不被销毁,那么存储的私有变量的值也不会被销毁,可以被其下级上下文中调取使用

市面上一般认为只有形成的私有上下文不被释放,才算是闭包(因为如果一但释放,之前的东西也就不存在了);还有人认为,只有一下级上下文用到了此上下文中的动西才算闭包;

练习题

var nAdd;
function out(){
    var n=999;
    nAdd=function(){
        n++;
        // ++在后,先执行后自增
        // 如果上一行的代码是 console.log(n);输出999
        console.log(n);//1000 此时n已经自增为1000
    }
    return function(){
        console.log(n);
    }
}
var getN=out();
getN();//999
nAdd();//1000
getN();//1000
let x = 5;
const fn = function fn(x) {
  return function(y) {
    console.log(y + (++x));
  }
}

let f = fn(6);
f(7); // 14
fn(8)(9); // 18
f(10); // 18
console.log(x); // 5
  • 变量提升(跳过,没有var、function...)

  • 代码执行

    1. x -> 5

    2. fn -> 0x 001

    3. fn (6) 函数执行

      1. 创建私有上下文,创建活动对象 AO(fn1)
      2. 初始化作用域链,<<EC(fn1), EC(G)>>
      3. 初始化 this
      4. 初始化 arguments
      5. 形参赋值
      6. 变量提升
      7. 函数执行,返回一个小函数,为小函数创建新的堆内存存储函数内容(作用域,代码字符串,键值对)
    4. f = 返回的小函数,也就是新的函数执行,f -> 0x 002,由于0x 002 被 f 占用,所用0x 002 不能被释放,所以它的上下文(作用域)也不能被释放。

    5. f(7) 函数执行

      1. 创建私有上下文,创建活动对象 AO(f1)
      2. 初始化作用域链,<<EC(f1), EC(fn1)>>
      3. 初始化 this
      4. 初始化 arguments
      5. 形参赋值
      6. 变量提升
      7. 函数执行,y = 7,x 当前作用域没有,沿着作用域链往上找,所以在 EC(fn1) 中查找 x = 6, y +(++ x) = 14
    6. fn(8) 函数执行

      1. 创建私有上下文,创建活动对象 AO(fn2)
      2. 初始化作用域链,<<EC(fn2), EC(G)>>
      3. 初始化 this
      4. 初始化 arguments
      5. 形参赋值
      6. 变量提升
      7. 函数执行,返回一个小函数,为小函数创建新的堆内存存储函数内容(作用域,代码字符串,键值对)
    7. fn(8)(9) 函数执行

      1. 创建私有上下文,创建活动对象 AO(f2)
      2. 初始化作用域链,<<EC(f2), EC(fn2)>>
      3. 初始化 this
      4. 初始化 arguments
      5. 形参赋值
      6. 变量提升
      7. 函数执行,y = 9,x 当前作用域没有,沿着作用域链往上找,所以在 EC(fn2) 中查找 x = 8, y +(++ x) = 18
    8. f(10) 函数执行

      1. 创建私有上下文,创建活动对象 AO(f3)
      2. 初始化作用域链,<<EC(f3), EC(fn1)>>
      3. 初始化 this
      4. 初始化 arguments
      5. 形参赋值
      6. 变量提升
      7. 函数执行,y = 10,x 当前作用域没有,沿着作用域链往上找,所以在 EC(fn1) 中查找 x = 7, y +(++ x) = 18