闭包指有权访问另一个函数作用域中变量的函数。创建闭包的常见方法,就是在一个函数内创建另一个函数。
函数调用的执行环境
function compare(value1, value2) {
var a = value1 > value 2 ? 1: -1
return a
}
var res = compare(5, 10)
当我们调用一个函数的时候,会进行下列步骤:
- 全局环境的变量对象始终存在
- 创建函数的时候,会预先创建一个包含全局变量对象的作用域链,保存在这个函数对象的[[scope]]属性中
- 当一个函数被调用时:
- 会创建一个函数的执行环境
- 复制[[scope]]属性中的对象创建相应的作用域链
- 使用arguments和其他命名参数的值来初始化函数的活动对象
- 将函数的活动对象推入函数作用域链的最前端
- 函数的局部环境的变量对象,只在函数执行过程中存在,执行完就被销毁了。
闭包
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'})
在另一个函数内部定义的函数,创建时其作用域链中将包含外部函数的活动对象。
闭包图解
根据上文的创建函数和调用函数进行的内容,可以画出闭包的执行过程(自己的理解,不一定准确。。。)
- 在调用外层函数前,就已经存在一个全局变量对象,并且在创建CCF函数时,在CCF函数的[[scope]]属性中创建了其作用域链,包含全局变量对象。
- 调用外层函数时,创建CCF的执行环境,同时创建CN函数
- 执行完外层函数之后,销毁CCF的执行环境,但由于CN的scope属性仍指向CCF的活动对象,所以CCF的活动对象没有被销毁。
- 调用内层函数,创建CN的执行环境
- 执行完内层函数之后,销毁CN的执行环境,结果后图3一致
解除闭包的引用
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对象。