JavaScript:闭包与实际应用

145

什么是闭包

相较于“闭包”,它的英文其实更通俗易懂,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的回调函数,setTimeoutsayHiLater的回调函数。子函数中没有的变量,取父函数的内存空间找。