笔记-js高级程序设计-7.闭包

89 阅读3分钟

闭包指有权访问另一个函数作用域中变量的函数。创建闭包的常见方法,就是在一个函数内创建另一个函数。

函数调用的执行环境

function compare(value1, value2) {
  var a = value1 > value 2 ? 1: -1
  return a
}
var res = compare(5, 10)

当我们调用一个函数的时候,会进行下列步骤: image.png

  1. 全局环境的变量对象始终存在
  2. 创建函数的时候,会预先创建一个包含全局变量对象的作用域链,保存在这个函数对象的[[scope]]属性中
  3. 当一个函数被调用时
    • 会创建一个函数的执行环境
    • 复制[[scope]]属性中的对象创建相应的作用域链
    • 使用arguments和其他命名参数的值来初始化函数的活动对象
    • 将函数的活动对象推入函数作用域链的最前端
  4. 函数的局部环境的变量对象,只在函数执行过程中存在,执行完就被销毁了。

闭包

function createComparisionFunction (propertyName) {
  return function(object1, object2) {
    var value1 = object1[propertyName]
    var value2 = object2[propertyName]
    var a = value1 > value2 ? 1: -1
    return a
  }
}
// 调用外层函数,同时创建内层函数
var compareName = createComparisionFunction('name')
// 调用内层函数
var res = compareName({name: 'Nicholas'}, {name: 'Greg'})

在另一个函数内部定义的函数,创建时其作用域链中将包含外部函数的活动对象

闭包图解

根据上文的创建函数和调用函数进行的内容,可以画出闭包的执行过程(自己的理解,不一定准确。。。)

  1. 在调用外层函数前,就已经存在一个全局变量对象,并且在创建CCF函数时,在CCF函数的[[scope]]属性中创建了其作用域链,包含全局变量对象。 image.png
  2. 调用外层函数时,创建CCF的执行环境,同时创建CN函数 image.png
  3. 执行完外层函数之后,销毁CCF的执行环境,但由于CN的scope属性仍指向CCF的活动对象,所以CCF的活动对象没有被销毁。 image.png
  4. 调用内层函数,创建CN的执行环境 image.png
  5. 执行完内层函数之后,销毁CN的执行环境,结果后图3一致 image.png

解除闭包的引用

compareName = null

从图解的最后结果来看,非常明显,执行完内外层函数之后,外层函数的活动对象,由于仍被引用,所以一直未被销毁,会导致内存泄漏的问题,所以需要最后手动解除引用。

闭包和变量

var funcs = [];
for(var i=0;i<10;i++){
    funcs.push(
        function(){console.log(i);}
    );
}
funcs.forEach(
    function(func){func();}
);

我们会看到控制台打印了10个10,这是因为使用var声明的变量存在于全局作用域中,当执行函数的时候,沿着作用域链找到全局的i,已经循环完毕变成10了。

而我们可以利用闭包解决这个问题,加一个自执行函数,创建了一层函数作用域

var funcs = [];
for (var i = 0; i < 10; i++) {
    funcs.push(
        // 自执行函数会马上执行,但是会在内层函数的作用域链上加一层闭包作用域,记录不同的i
        (function (value) {
            return function () {
                console.log(value);
            }
        })(i)
    );
}
funcs.forEach(
    function(func){func();}
);

闭包和this

闭包中内层函数的this一般指向window对象。