JavaScript 闭包

闭包 = 函数 + 外部变量的引用 是指 一个函数可以访问其外部作用域的变量,即使这个外部作用域已经执行结束。闭包让函数“记住”它诞生时的作用域环境。

例:定义一个方法,方法的返回值为一个函数,返回函数的内部引用方法内部变量

function outer(){
  var count = 1;
  function inner(){
    return count;
  }
  return inner;
}
var f = outer();
console.log(f()); // 1
console.log(f()); // 2

outer() 执行后,其作用域本应被销毁,但 inner() 仍然引用 count,所以 count 变量仍然存在,形成闭包。

为什么要这么写

首先闭包可以实现在函数外部获取函数内部的变量的值; 难道闭包就是为了这个?当然不是了。闭包最主要的目的是为了能够保存函数的运行状态,间接的实现了私有变量

function outer(){
  var a = 1;
  function inner(){
    return a++;
  }
  return inner;
}

var f = outer();

console.log(f());// 1
console.log(f());// 2
console.log(f());// 3

这样outer方法内部变量a就可保存状态了,每调用一次就自增1; 外部方法无法访问到变量a,实现了数据私有化

闭包为什么可以保存状态

闭包的这种特性和 JavaScript 的内存回收机制有关,JavaScript 会对定时进行垃圾回收,例如:

// 这时obj和对象之间存在着引用关系
var obj = {
  a: 1,
  b: 2,
};

obj = null; // 这时obj对对象之间的引用关系就不存在了,对象就会被JavaScript引擎当作垃圾消耗

函数在执行完成后函数内部变量也会被回收,只保留全局变量;

在闭包中函数outer执行完成后将内部函数变量inner作为返回值返回; 这样全局变量 f 和闭包函数inner形成引用关系,inner函数在outer函数内部; 这样 JavaScript 引擎因为这之间的引用关系不会进行垃圾回收,就可以到达保存允许状态的目的。

  • 闭包的优点:不污染全局变量的情况下实现运行状态的保留
  • 闭包的缺点:使用不当会造成内存泄漏;
  • 使用场景:个人认为都可以使用 OOP 来替代,闭包实在让人难以理解控制;

深入闭包&内存回收

闭包的本质还是一个函数

  • 这个函数引用了自由变量
  • 即使创建这个函数的上下文已经销毁,这个函数仍然存在
var scope = "global scope";
function checkScope(){
  var scope = "local scope";
  function foo(){
    return scope;
  }
  return foo;
}

var foo = checkScope();
foo();

我们来分析一下上面代码的执行过程:

  1. 创建全局执行上下文,压入执行栈底
  2. 初始化全局执行上下文globalExecutionContext,执行代码
  3. 执行到checkScope函数,创建checkScope函数执行上下文checkScopeContext,并压入执行栈
  4. 初始化checkScopeContext执行上下文,继续执行代码
  5. checkScope函数执行完成,将checkScopeContext从执行栈中弹出
  6. 执行foo函数,创建foo函数执行上下文fooContext,并压入执行栈
  7. 初始化fooContext,继续执行代码
  8. foo执行完成,将fooContext从执行栈中弹出
  9. 完成

现象:在foo执行的时候,checkScopeExecutionContext已经出栈,但是仍然能够获取scope变量。

原理:在变量作用域&执行栈中介绍过标识符解析(即变量获取)是在作用域链中进行。 fooExecutionContext的作用域链是在初始化的时候生成的,如下:[AO, checkScopeContext.AO, globalContext.VO],因为函数foo引用了checkScopeContext.AO所以 JavaScripts 不会销毁checkScopeContext.AOfoo函数通过这个作用域链即使checkScopeContext已经被销毁但是仍然可以获得变量scope

fooContext = {
  Scope: [AO, checkScopeContext.AO, globalContext.VO],
}

例题

var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = function () {
    console.log(i);
  };
}

data[0]();
data[1]();
data[2]();

以上代码for循环执行完成之后的的全局上下文结构如下

globalContext = {
  VO: {
    data: [...],
    i: 3
  }
}

在执行data[0]()时,data[0]的作用域链为:

data[0]Context = {
  Scope: [AO, globalContext.VO]
}

data[0]Context.AO中并没有变量i,需要从globalContext.VO中获取,所以获取的i始终为3


改成闭包后

var data = [];

for (var i = 0; i < 3; i++) {
  data[i] = (function (i) {
    return function(){
      console.log(i);
    }
  })(i);
}

data[0]();
data[1]();
data[2]();

执行上下文结构如下:

data[0]Context = {
  Scope: [AO, 匿名函数Context.AO, globalContext.VO]
}

匿名函数Context = {
  AO: {
    arguments: {
      0: 0,
      length: 1
    },
    i: 0
  }
}

results matching ""

    No results matching ""