【前端工程师手册】JavaScript之闭包

闭包确实是一个说烂了的概念,校招社招都会被问到,今天总结一番。
先下定义,闭包是函数和该函数的词法做用域的组合。其实这个定义是比较教条的,能够直白的理解为闭包是一个函数,且这个函数使用了既没在它内部声明且不是它的参数的变量。
举个栗子,html

function foo() { 
    var a = 2;
    function bar() { 
        console.log( a );
    }
    return bar; 
}
var baz = foo();
baz(); // 2

按照常理,foo函数在执行完毕以后会销毁掉其内部的变量a,可是bar函数内部保持着对a的引用,因此经过调用foo()把bar的引用赋给了baz,运行baz()依然能够打印出a。在这个栗子里,函数bar以及它对变量a的引用就构成了闭包。面试

闭包和做用域

对于闭包和做用域的关系,个人理解是闭包其实就是做用域的延伸
因为在JavaScript中函数内部可使用函数外部的变量,全部有时候会不知不觉的产生闭包,假如在上面那个代码片断中,不容许函数内部使用函数外部的变量,闭包也就无从谈起了。闭包

闭包有什么用?

模拟私有变量和私有方法函数

var Dog = (function(){
    var privateVal = 'dog'
    function doing(val) {
        console.log(privateVal + ' ' + val)
    }

    return {
        run: function(){
            doing('run')
        },
        bark: function(){
            doing('bark')
        }
    }
})()

Dog.run()    // dog run
Dog.bark()   //  dog bark

能够看到的是,run和bark这两个闭包分享了同一个词法做用域,且都引用了私有方法doing。这样,咱们就能够只向外暴露run和bark两个公共接口而隐藏私有的变量和方法。code

闭包与循环

或许这是面试中出现最多的问题...htm

for(var i = 1;i <= 5;i++) {
    setTimeout(function() {
        console.log(i)
    }, i*1000)
}
// 每隔一秒打印一个6,共打印5次

为何事与愿违,而不是按照咱们所想的依次的间隔1秒打印出1,2,3,4,5呢?首先,这段循环产生了5个闭包,并且最重要的是这5个闭包都处在同一个做用域中,也就是说它们引用的是同一个i,当for循环结束时,i变成了6。因此,5个匿名函数执行时会依次的去打印那同一个i,因此就打印出了5个6。
如何解决?
以前也说了让这5个闭包处于不一样的做用域且让它们在各自的做用域中拥有它们各自的i便可。
可使用自执行函数来建立一个新的做用域blog

for(var i = 1;i <= 5;i++) {
    (function(k){
        setTimeout(function() {
        console.log(k)
    }, k*1000)
    })(i)
}

在这个代码片断中,每个setTimeout都处于一个独立的做用域中,且都引用了它们各自的k,并非指向了外层做用域的i,因此就会打印出1,2,3,4,5
也可使用let接口

for(let i = 1;i <= 5;i++) {
    setTimeout(function() {
        console.log(i)
    }, i*1000)
}

for 循环头部的 let 不只将 i 绑定到了 for 循环的块中,事实上它将其从新绑定到了循环的每个迭代中,确保使用上一个循环迭代结束时的值从新进行赋值。
其实使用let的本质是ip

for(let i = 1;i <= 5;i++) {
    let i = 上次迭代结束的i
    setTimeout(function() {
        console.log(i)
    }, i*1000)
}

其实闭包就这么多东西,并且主要是做用域的概念,做用域明白了,闭包也就明白了。
that's all, thank you.作用域

参考资料
深刻理解JavaScript系列-闭包
MDN-闭包
《你不知道的JavaScript-上卷》
「每日一题」JS 中的闭包是什么?

相关文章
相关标签/搜索