资讯专栏INFORMATION COLUMN

学习JavaScript之闭包

shiguibiao / 1366人阅读

摘要:闭包在我的前端学习中一直也是盲点,之前很多次看到别人提到我都是完全听不懂。闭包导致的问题因为闭包会使得函数中的变量都保存在内存中,如不能及时释放会对性能造成影响。

闭包在我的前端学习中一直也是盲点,之前很多次看到别人提到我都是完全听不懂。最近一直看书和写demo,对闭包也逐渐有所理解了,在这里写下这篇博客。

从作用域链讲起

首先明确几个概念:
1.JavaScript有函数级作用域,但没有块级作用域。
2.当要使用一个变量时,会沿着作用域链一步一步向上查找。
这里有一个demo:

var a = 1

function foo () {
  var a = 2
}

foo()
console.log(a) // a = 1

结果当然是a = 1,虽然在foo函数中重新声明了a并且赋给它一个新的值,但是var声明的变量只在foo()函数中有效,函数执行完毕就会销毁,因此全局作用域中a的值没有变化。
接下来再看这个demo:

for (var i = 0; i < 10; i++) {
  // code
}

console.log(i) // i = 10

这里在for循环结束之后仍然能在外部访问到i,就是因为JavaScript没有块级作用域造成的。

闭包的特性

闭包的主要特性就是可以从外部访问函数内部的属性和方法。先看一个demo:

function foo () {
  var a = 1
}
foo()
console.log(a) // 出错

正常情况下,定义在函数内部的局部变量在函数执行完之后就会被销毁,因此在外部是无法访问局部变量的。那应该怎么做才能访问呢?请继续看:

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

在这个demo中,我们在函数foo()内部又定义了一个函数bar(),并把它的函数名返回,这样便能在外部实现对内部变量a的访问。

有人问了:不是说函数执行结束之后内部变量会被销毁吗?你这不科学啊。

是的,原来函数执行结束之后内部变量的确会被销毁,但是这里内部函数bar()在foo()执行时被返回并保存到了外部的baz中。这时候foo()执行完后,baz中依旧保存着对函数bar()的引用,因此bar()的作用域并没有被释放,根据之前提到的变量查找方式,在bar()函数的外层作用域中找到了a。

以上就是使用闭包能从外部访问内部属性的原理。

闭包的用途

利用闭包强大的特性,最方便的用途就是实现私有变量和私有方法;另外,因为使用闭包会在内存中保存函数作用域,因此也能保存变量的值。

此话怎解?请看下面的demo:

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

这里的结果并不是我们预想中的每隔一秒依次输出12345,而是每隔一秒输出一个6,为什么会这样呢?

首先,这个6是变量i最终退出循环时的值,其次var声明的变量没有块级作用域,因此i实际上在全局作用域中都访问得到。到这里不难理解了,setTimeout在循环结束后执行timer()函数时,访问的i实际上在全局作用域中,此时i=6,因此后面的每个timer()函数执行访问的i都是6。那么问题又来了,怎样才能让闭包访问的i是我们想要的呢?

其实很简单,让i变成局部变量,在循环结束之后销毁,这样每个闭包访问的就是保存在作用域中的局部变量i,也就是对应的12345。请看下面的demo:

for (var i = 1; i <= 5; i++) {
  (function (j) {
    setTimeout(function timer () {
      console.log(j)
    }, j * 1000)
  })(i)
} // 1 2 3 4 5

这里用到了立即执行函数(IIFE),可能有些难以理解。那我一步一步解释:立即执行函数其实就是JavaScript模仿块级作用域的方法,这里你可以把它简单看成一个函数调用。i就是传给调用函数的参数,内部匿名函数的形参j接收到i的值开始执行setTimeout。此时j就是函数级作用域中的局部变量,在循环结束后,timer()函数开始执行,因为它是一个闭包,所以能访问保存在作用域中的变量j,也就输出了我们想要的结果。

当然更简单的方式是用ES6的let来声明i,这样也是使得i变成局部变量,其他关于ES6这里就不多提,有兴趣的可以自行看let基本用法。

闭包导致的问题

1.因为闭包会使得函数中的变量都保存在内存中,如不能及时释放会对性能造成影响。
2.在IE9以下的浏览器会有内存泄漏的问题。(关于这块我后续会写文章详细说明)

本人经验尚浅,目前对于前端仍在不断摸索和学习,文章如有错误,欢迎各位指正。最后附上本人博客地址和原文链接,希望能向各位多多学习。

lbj的前端之路
原文链接:学习JavaScript之原型链

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

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

相关文章

  • 学习JavaScript内存泄漏

    摘要:接上回我写了一篇关于闭包的博客学习之闭包,最后谈到闭包导致的问题时留了一个尾在以下的浏览器中会有内存泄漏的问题。今天的博客就继续探索一下内存泄漏的问题。博客地址的前端之路原文链接学习之内存泄漏 接上回我写了一篇关于闭包的博客《学习JavaScript之闭包》, 最后谈到闭包导致的问题时留了一个尾: 在IE9以下的浏览器中会有内存泄漏的问题。 今天的博客就继续探索一下内存泄漏的问题。 浅...

    nodejh 评论0 收藏0
  • JS笔记

    摘要:从最开始的到封装后的都在试图解决异步编程过程中的问题。为了让编程更美好,我们就需要引入来降低异步编程的复杂性。异步编程入门的全称是前端经典面试题从输入到页面加载发生了什么这是一篇开发的科普类文章,涉及到优化等多个方面。 TypeScript 入门教程 从 JavaScript 程序员的角度总结思考,循序渐进的理解 TypeScript。 网络基础知识之 HTTP 协议 详细介绍 HTT...

    rottengeek 评论0 收藏0
  • 前端学习笔记闭包——看了一张图终于明白啥是闭包

    摘要:在一个闭包环境内修改变量值,不会影响另一个闭包中的变量。直到看到函数闭包闭包这篇文章的代码一部分,终于明白其中的逻辑了。 闭包 闭包定义:指拥有多个变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。函数内部可以直接读取全局变量。函数内部变量无法在函数外部访问。函数内部声明要用var或者let声明,不然会变成全局变量链式作用域:子对象会一级级向上寻找...

    andycall 评论0 收藏0
  • 进击JavaScript(三)玩转闭包

    摘要:为了更好的理解,在阅读此文之前建议先阅读上一篇进击之词法作用域与作用域链什么是闭包闭包的含义就是闭合,包起来,简单的来说,就是一个具有封闭功能与包裹功能的结构。在中函数构成闭包。 为了更好的理解,在阅读此文之前建议先阅读上一篇《进击JavaScript之词法作用域与作用域链》 1.什么是闭包 闭包的含义就是闭合,包起来,简单的来说,就是一个具有封闭功能与包裹功能的结构。所谓的闭包就是...

    cyixlq 评论0 收藏0
  • Deep in JS - 收藏集 - 掘金

    摘要:今天同学去面试,做了两道面试题全部做错了,发过来给道典型的面试题前端掘金在界中,开发人员的需求量一直居高不下。 排序算法 -- JavaScript 标准参考教程(alpha) - 前端 - 掘金来自《JavaScript 标准参考教程(alpha)》,by 阮一峰 目录 冒泡排序 简介 算法实现 选择排序 简介 算法实现 ... 图例详解那道 setTimeout 与循环闭包的经典面...

    enali 评论0 收藏0

发表评论

0条评论

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