资讯专栏INFORMATION COLUMN

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

CarlBenjamin / 3251人阅读

摘要:闭包确实是一个说烂了的概念,校招社招都会被问到,今天总结一番。先下定义,闭包是函数和该函数的词法作用域的组合。在这个栗子里,函数以及它对变量的引用就构成了闭包。闭包和作用域对于闭包和作用域的关系,我的理解是闭包其实就是作用域的延伸。

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

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两个公共接口而隐藏私有的变量和方法。

闭包与循环

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

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即可。
可以使用自执行函数来创建一个新的作用域

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的本质是

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 中的闭包是什么?

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/94942.html

相关文章

  • JavaScript系列(四) - 收藏集 - 掘金

    摘要:函数式编程前端掘金引言面向对象编程一直以来都是中的主导范式。函数式编程是一种强调减少对程序外部状态产生改变的方式。 JavaScript 函数式编程 - 前端 - 掘金引言 面向对象编程一直以来都是JavaScript中的主导范式。JavaScript作为一门多范式编程语言,然而,近几年,函数式编程越来越多得受到开发者的青睐。函数式编程是一种强调减少对程序外部状态产生改变的方式。因此,...

    cfanr 评论0 收藏0
  • 前端知识点整理

    摘要:难怪超过三分之一的开发人员工作需要一些知识。但是随着行业的饱和,初中级前端就业形势不容乐观。整个系列的文章大概有篇左右,从我是如何成为一个前端工程师,到各种前端框架的知识。 为什么 call 比 apply 快? 这是一个非常有意思的问题。 作者会在参数为3个(包含3)以内时,优先使用 call 方法进行事件的处理。而当参数过多(多余3个)时,才考虑使用 apply 方法。 这个的原因...

    Lowky 评论0 收藏0
  • 前端知识点整理

    摘要:难怪超过三分之一的开发人员工作需要一些知识。但是随着行业的饱和,初中级前端就业形势不容乐观。整个系列的文章大概有篇左右,从我是如何成为一个前端工程师,到各种前端框架的知识。 为什么 call 比 apply 快? 这是一个非常有意思的问题。 作者会在参数为3个(包含3)以内时,优先使用 call 方法进行事件的处理。而当参数过多(多余3个)时,才考虑使用 apply 方法。 这个的原因...

    snowLu 评论0 收藏0
  • JavaScript - 收藏集 - 掘金

    摘要:插件开发前端掘金作者原文地址译者插件是为应用添加全局功能的一种强大而且简单的方式。提供了与使用掌控异步前端掘金教你使用在行代码内优雅的实现文件分片断点续传。 Vue.js 插件开发 - 前端 - 掘金作者:Joshua Bemenderfer原文地址: creating-custom-plugins译者:jeneser Vue.js插件是为应用添加全局功能的一种强大而且简单的方式。插....

    izhuhaodev 评论0 收藏0
  • 双十二大前端程师读书清单

    摘要:本文最早为双十一而作,原标题双大前端工程师读书清单,以付费的形式发布在上。发布完本次预告后,捕捉到了一个友善的吐槽读书清单也要收费。这本书便从的异步编程讲起,帮助我们设计快速响应的网络应用,而非简单的页面。 本文最早为双十一而作,原标题双 11 大前端工程师读书清单,以付费的形式发布在 GitChat 上。发布之后在读者圈群聊中和读者进行了深入的交流,现免费分享到这里,不足之处欢迎指教...

    happen 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<