什么是闭包
相较于“闭包”,它的英文其实更通俗易懂,closure。
closure把close名词化了,close是近,closure是靠近的东西,在代码中实际体现就是:当下的执行环境+外部环境。(execution context+outer environment)
用一个例子解释:
function greet(whattosay) {
return function(name) {
console.log(whattosay + ' ' + name);
}
}
var sayHi = greet('Hi');
sayHi('Brynn');
这是一个闭包的样子,它是一个返回值为函数的函数,可以通过greet('Hi')('Brynn')
来调用,但上面这种方式更常见。
这是这个函数执行栈的样子,它会解释,为什么叫“闭包”
左边是var sayHi = greet('Hi');
执行时,执行栈的样子,右边是sayHi('Brynn');
执行时,执行栈的样子。
var sayHi = greet('Hi');
执行完,拿到返回值(这个函数),greet这个执行环境就销毁了,通常来说,执行环境销毁时所分配的内存空间也会被销毁。但是当一个变量/函数还会被继续引用时,它便不会被当成垃圾回收(闭包就是子函数还在里保留了对父函数的引用。)
但闭包这种情况下没有, sayHi()
这个执行环境存在时,他的outer environmentgreet()
中的内存还保留着,依然可以找到whattosay
。
闭包就是将子函数作为父函数返回值,并且在子函数再次被调用时,可以拿到父函数的变量。
闭包应用
一:for循环问题
function buildFunctions() {
var arr = [];
for (var i = 0; i < 3; i++) {
arr.push(
function() {
console.log(i);
}
)
}
return arr;
}
var fs = buildFunctions();
fs[0]();//3
fs[1]();//3
fs[2]();//3
function buildFunctions2() {
var arr = [];
for (var i = 0; i < 3; i++) {
arr.push(//每次push,小括号中的代码被执行一些,有一个执行环境生成,拿到父函数中的0/1/2,再作为返回值
(function(j) {
return function() {
console.log(j);
}
}(i))
)
}
return arr;
}
var fs2 = buildFunctions2();
fs2[0]();//0
fs2[1]();//1
fs2[2]();//2
二:计数器问题
实现一个计数器函数add,只有调用add函数count才会增加。
function closure(){
var count = 0;
return function(){
count++;
return count;
}
}
var add = closure();
console.log(add());
console.log(add());
console.log(add());
这里的count是一个通过闭包实现的私有变量,外部函数无法访问count。
并且var count = 0;在closure的执行环境中声明了一个变量count,并赋予初值,在后续调用add时不会再重复赋值(不会又将count赋值为0),每次都是在上一次的累加结果上面再加一。
在开发中实际使用过一次:
需求:做一个隐式的button,只有在管理员点击三次才跳转到相应页面。
实现:
@click="gotoManagePage()"
gotoManagePage:this.gotoManage(3,this.realGotoManage),
gotoManage(times,fn){
let count = 0;
return function() {
if(++count==times){
count = 0;
fn();
}
}
},
realGotoManage(){
router.push({name:"login"});
}
将count赋值为0的操作在整个系统中只执行了一次,并且时初始化时执行的。
在用户点击按钮时执行的时count的自增操作,并且当count增加到3时,跳转函数才真正被触发。
三:函数的多态
function makeGreeting(language) {
return function(firstname, lastname) {
if (language === 'en') {
console.log('Hello ' + firstname + ' ' + lastname);
}
if (language === 'es') {
console.log('Hola ' + firstname + ' ' + lastname);
}
}
}
var greetEnglish = makeGreeting('en');
var greetSpanish = makeGreeting('es');
greetEnglish('John', 'Doe');
greetSpanish('John', 'Doe');
四:setTimeout与闭包
function sayHiLater() {
var greeting = 'Hi!';
setTimeout(function() {
console.log(greeting);
}, 3000);
}
sayHiLater();
function() {console.log(greeting);}
是setTimeout
的回调函数,setTimeout
是sayHiLater
的回调函数。子函数中没有的变量,取父函数的内存空间找。