资讯专栏INFORMATION COLUMN

JavaScript中的闭包

AaronYuan / 1676人阅读

摘要:但是,不合理地滥用闭包,也会造成很多性能问题,从而使项目维护成本增加。

前言

相信很多小伙伴在工作或者面试过程中都遇到过这个问题,作为经典的前端面试题之一,它高频地出现在我们的求职生涯中。所以,了解和掌握它也就变得十分必要了

读完这篇文章,你或许就会知道:

闭包是什么,它是怎么形成的

为什么要使用闭包

闭包会造成哪些问题

如果文章中有出现纰漏、错误之处,还请看到的小伙伴多多指教,先行谢过

以下↓

作用域

what? 不是在说闭包么,怎么又扯到作用域上面去了

稍安勿躁,在我们了解闭包之前,还是很有必要先了解一下 JavaScript 中的作用域

我们都知道在 JavaScript 中存在着全局变量和局部变量,全局变量可以在任何地方访问到,然而局部变量只能在当前作用域中访问。全局作用域是不能直接访问局部作用域中的变量,而局部作用域可以直接访问全局作用域当中的变量

就像一个代码块儿或函数被嵌套在另一个代码块儿或函数中一样,作用域也会被嵌套在其他的作用域中。所以,如果在直接作用域中找不到一个变量的话,就会咨询下一个外层作用域,如此继续直到找到这个变量或者到达最外层作用域(也就是全局作用域)

说了这么多,其实说白了,所谓 作用域就是一组规则,它决定了一个变量(标识符)在哪里和如何被查找

试想一下,现在有一个这样的需求:我们想在全局作用域拿到局部作用域的某一个变量该怎么去做呢?

初识闭包
JavaScript 中闭包无所不在,你只是必须认出它并接纳它

正常情况下,我们并不能拿到局部作用域的变量。但是,我们可以使用变通的方式:定义一个函数

让我们看一下这段代码

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

这样在函数 foo 中定义一个 bar 函数,在这个函数中我们就能访问到定义在函数 foo 中的变量 a 。既然我们这样就可以访问到 foo 函数里面的变量,那么,只要我们将 bar 这个函数作为返回值输出,不就实现我们的需求了么? 是的!

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

var result = foo();

result() // 2

这就是闭包

让我们来看看这个函数做了什么:

创建了一个函数 foo

函数里面创建了一个变量 a 与函数 bar

返回函数 bar

现在,我们就对闭包有了一个基本的概念:定义在一个函数内部的函数

再遇闭包
词法作用域:简单理解为作用域是由编写时函数被声明的位置定义的

还是来看一下下面的代码

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

function bar(fn) {
    fn(); 
}

与前面示例不同的是,这里我们并没有将函数 baz 返回,而是将它当做值传递给 bar 这个函数,然后在 bar 这个函数里面执行,函数 bar 保持了函数 baz引用

相同的是,实际上函数 baz 都在它被编写时的词法作用域之外被调用,bar() 依然拥有对那个作用域的引用,而这个引用称为闭包

这就是闭包

好了,看到这里是不是还有点懵呢,让我们再来看一个更加常见的示例

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

毫无疑问,运行上面的代码会输出 56.很明显,我们得到的结果是 i 在循环之后的最终值

那么,为什么会是这样呢?

其实,由于作用域的工作方式,我们在定时器函数中访问到的 i 是共享到全局作用域的上的,它只有一个,就是最终循环结束的值

想让这个循环显示我们想要的结果也很简单,只需要这样:

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

使用一个立即执行函数将定时器函数包裹起来,在这个函数中定义一个变量 j,然后将 i 当做值传递进去。这样,在每次迭代的时候,变量 j 都会拥有 i 的一个拷贝,自然得到了我们想要的结果

同样的,这也是一个闭包

当然,我们也可以使用 ES6 中的 let 关键字声明变量 i

通过前面的一些示例,我们不难发现:闭包其实并没有特定的格式,只要满足一些条件,它就是闭包

所以:

闭包就是当一个函数即使是在它的词法作用域之外被调用时,也可以记住并访问它的词法作用域

闭包的用途

闭包最大的用途:

读取函数内部的变量

让这些变量始终保存在内存中

function f1(){
  var n=999;
  nAdd=function(){n+=1}
  function f2(){
    alert(n);
  }
  return f2;
}
var result=f1();
result(); // 999
nAdd();
result(); // 1000

在这段代码中,result 实际上就是闭包 f2 函数。它一共运行了两次,第一次的值是 999 ,第二次的值是 1000 。这证明了,函数 f1 中的局部变量 n 一直保存在内存中,并没有在 f1 调用后被自动清除

使用闭包模拟私有方法(数据隐藏和封装)

私有方法不仅仅有利于限制对代码的访问:还提供了管理全局命名空间的强大能力,避免非核心的方法弄乱了代码的公共接口部分

var Counter = (function() {
  var privateCounter = 0;
  function changeBy(val) {
    privateCounter += val;
  }
  return {
    increment: function() {
      changeBy(1);
    },
    decrement: function() {
      changeBy(-1);
    },
    value: function() {
      return privateCounter;
    }
  }   
})();

这个环境中包含两个私有项:名为 privateCounter 的变量和名为 changeBy 的函数。这两项都无法在这个匿名函数外部直接访问。必须通过匿名函数返回的三个公共函数访问。

这三个公共函数是共享同一个环境的闭包

闭包的问题

闭包的用途在一定程度上也造成了很多问题,比如:闭包会使函数中的变量始终保存在内存中,不能被 JavaScript 的垃圾回收清理,很容易造成内存消耗过大,影响程序性能

后记

闭包的合理运用,会让我们在开发中写出更优雅和干净的代码。但是,不合理地滥用闭包,也会造成很多性能问题,从而使项目维护成本增加。

所以,如何合理地使用这个有趣的东西,还需要我们多多钻研和摸索,相信你一定可以对它越来越熟悉

最后,推荐一波前端学习历程,不定期分享一些前端问题和有意思的东西欢迎 star 关注 传送门

参考文档

You-Dont-Know-JS

闭包 - MDN

学习JavaScript闭包 - 阮一峰

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

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

相关文章

  • JavaScript中的闭包

    摘要:闭包引起的内存泄漏总结从理论的角度将由于作用域链的特性中所有函数都是闭包但是从应用的角度来说只有当函数以返回值返回或者当函数以参数形式使用或者当函数中自由变量在函数外被引用时才能成为明确意义上的闭包。 文章同步到github js的闭包概念几乎是任何面试官都会问的问题,最近把闭包这块的概念梳理了一下,记录成以下文章。 什么是闭包 我先列出一些官方及经典书籍等书中给出的概念,这些概念虽然...

    HmyBmny 评论0 收藏0
  • JavaScript闭包,只学这篇就够了

    摘要:当在中调用匿名函数时,它们用的都是同一个闭包,而且在这个闭包中使用了和的当前值的值为因为循环已经结束,的值为。最好将闭包当作是一个函数的入口创建的,而局部变量是被添加进这个闭包的。 闭包不是魔法 这篇文章使用一些简单的代码例子来解释JavaScript闭包的概念,即使新手也可以轻松参透闭包的含义。 其实只要理解了核心概念,闭包并不是那么的难于理解。但是,网上充斥了太多学术性的文章,对于...

    CoderBear 评论0 收藏0
  • 还担心面试官问闭包

    摘要:一言以蔽之,闭包,你就得掌握。当函数记住并访问所在的词法作用域,闭包就产生了。所以闭包才会得以实现。从技术上讲,这就是闭包。执行后,他的内部作用域并不会消失,函数依然保持有作用域的闭包。 网上总结闭包的文章已经烂大街了,不敢说笔者这篇文章多么多么xxx,只是个人理解总结。各位看官瞅瞅就好,大神还希望多多指正。此篇文章总结与《JavaScript忍者秘籍》 《你不知道的JavaScri...

    tinyq 评论0 收藏0
  • JavaScript中的闭包

    摘要:权威指南第版中闭包的定义函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中成为闭包。循环中的闭包使用闭包时一种常见的错误情况是循环中的闭包,很多初学者都遇到了这个问题。 闭包简介 闭包是JavaScript的重要特性,那么什么是闭包? 《JavaScript高级程序设计(第3版)》中闭包的定义: 闭包就是指有权访问另一个函数中的变...

    Donne 评论0 收藏0
  • 深入javascript——作用域和闭包

    摘要:注意由于闭包会额外的附带函数的作用域内部匿名函数携带外部函数的作用域,因此,闭包会比其它函数多占用些内存空间,过度的使用可能会导致内存占用的增加。 作用域和作用域链是javascript中非常重要的特性,对于他们的理解直接关系到对于整个javascript体系的理解,而闭包又是对作用域的延伸,也是在实际开发中经常使用的一个特性,实际上,不仅仅是javascript,在很多语言中都...

    oogh 评论0 收藏0
  • Javascript闭包入门(译文)

    摘要:也许最好的理解是闭包总是在进入某个函数的时候被创建,而局部变量是被加入到这个闭包中。在函数内部的函数的内部声明函数是可以的可以获得不止一个层级的闭包。 前言 总括 :这篇文章使用有效的javascript代码向程序员们解释了闭包,大牛和功能型程序员请自行忽略。 译者 :文章写在2006年,可直到翻译的21小时之前作者还在完善这篇文章,在Stackoverflow的How do Java...

    Fourierr 评论0 收藏0

发表评论

0条评论

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