Closure 闭包
闭包是函数和声明该函数的词法环境的组合.类似于c#中的委托,或者说是一个栈帧,在函数开始是被分配到堆,当函数返回后仍然不会被释放.
Profile
- 在function中使用另一个function, 就会使用闭包.但是构造器函数New Function()不会使用闭包
- 闭包可以视作一个函数的入口以及与此函数的相关的局部变量(函数退出时的局部变量的副本)的组合
- 每次调用有闭包的函数, 都会保留一组新的局部变量(如果函数包含一个内部函数, 并且内部函数的引用被返回或以某种方式保留)
- 因为隐秘的闭包, 两个函数看起来可能有相同的源代码, 但实际的作用完全不同
- 闭包在处理速度和内存消耗方面对脚本有负面影响
- 闭包可用来构建JavaScript私有成员
用途
- 访问变量(在变量的作用域内,多在外部函数中声明),被赋值给一个变量,
- 作为函数的实参被传递,
- 作为函数结果被返回.
function sayHello(name) {
var text = 'Hello ' + name; // Local variable
var say = function() { console.log(text); }
say();
}
sayHello('Joe');//Hello Joe
函数作为引用被返回
function sayHello2(name) {
var text = 'Hello2 ' + name; // Local variable
return function() { console.log(text); }
}
sayHello2('Joe')();//Hello2 Joe 等价于 var say2 = sayHello2('Joe');say2();
闭包是什么:与指针的对比
- C指针不同的是,JavaScript中函数引用变量不仅指向函数,还包括隐藏的指针指向闭包,一块儿分配在堆上的函数所处上下文环境的内存
- C等其他语言中在函数返回后,因为栈帧被销毁,其局部变量就不再可访问.相对的,JavaScript中在函数中声明一个函数,即使调用的函数被返回,外部函数中的局部变量仍旧可用.
sayHello2('Joe')()中的text是局部变量,匿名方法可以访问text,就是因为sayHello2()仍旧保存在一个闭包中.JavaScript中函数引用有一个对它的闭包的秘密引用,类似于委托,方法指针加上一个对对象的引用.
它们按引用保存.外部函数退出时,栈帧仍保存在内存中
function sayHello3() {
// Local variable that ends up within closure
var num = 42;
var say = function() { console.log(num); }
num++;
return say;
}
var sayNumber = sayHello3();
sayNumber(); // logs 43
闭包按引用保存
- 同一个闭包可以同时被多个函数所访问, setupSomeGlobals()中的局部变量可以同时被3个方法调用
- 但是, setupSomeGlobals一旦被重新调用, 新的闭包就会被创建, 堆上一块新的内存被分配.
var gLogNumber, gIncreaseNumber, gSetNumber;
function setupSomeGlobals() {
// Local variable that ends up within closure
var num = 42;
// Store some references to functions as global variables
gLogNumber = function() { console.log(num); }
gIncreaseNumber = function() { num++; }
gSetNumber = function(x) { num = x; }
}
setupSomeGlobals();gIncreaseNumber();
gLogNumber(); //43
gSetNumber(6);
gLogNumber();//6
var oldLog = gLogNumber; setupSomeGlobals();
gLogNumber(); //42
oldLog();//6
进阶: 共享一个闭包的函数数组
buildList返回的是根据list的个数生成的方法数组, 共享一个闭包, 也包括其局部变量i
当fnlist[j]()
调用匿名函数(function(){console.log(item + '_' + i +'_'+ list[i])})
时, 都指向同一闭包
此时var声明的索引变量i作用域是整个for循环体, 因为for循环已经遍历完成, 匿名函数对应的局部变量都变成了3
而let声明的变量k的作用域是匿名函数, 不同的遍历阶段, 匿名函数中的k值是不一致的
//var
function buildList(list){
var result = [];
for(var i=0; i<list.length; i++){
var item = 'item' + i;
result.push(function(){console.log(item + '_' + i +'_'+ list[i])});
}
return result;
}
function testList() {
var fnlist = buildList([1,2,3]);
// Using j only to help prevent confusion -- could use i.
for (var j = 0; j < fnlist.length; j++) {
fnlist[j]();
}
}
testList() // 打印3次 item2_3_undefined
//let
function buildList(list){
var result = [];
for(let k=0; k<list.length; k++){
let item = 'item' + k;
result.push(function(){console.log(item + '_' + k +'_'+ list[k])});
}
return result;
}
testList(); //item0_0_1 \n item1_1_2 \n item2_2_3