热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

一文剖析JavaScript中闭包的难点

这篇文章主要为大家详细介绍了JavaScript中闭包的一些难点,文中的示例代码讲解详细,对我们学习JavaScript有一定帮助,需要的可以参考一下

一、作用域基本介绍

ES6之前只有全局作用域与函数作用域两种,ES6出现之后,新增了块级作用域。

1. 全局作用域

在Javascript中,全局变量是挂载在window对象下的变量,所以在网页中的任何位置你都可以使用并且访问到这个全局变量。

当我们定义很多全局变量的时候,会容易引起变量命名的冲突,所以在定义变量的时候应该注意作用域的问题:

var globalName = 'global'
function getName() {
  console.log(globalName) // global
  var name = 'inner'
  console.log(name) // inner
}
getName()
console.log(name) // 报错
console.log(globalName) // global
function setName() {
  vName = 'setName'
}
setName()
console.log(vName) // setName
console.log(windwo.vName) // setName

2. 函数作用域

在Javascript中,函数定义的变量叫作函数变量,这个时候只能在函数内部才能访问到它,所以它的作用域也就是函数的内存,称为函数作用域。

当这个函数被执行完之后,这个局部变量也相应会被销毁。所以你会看到在getName函数外面的name是访问不到的:

function getName() {
  var name = 'inner'
  console.log(name) // inner
}
getName()
console.log(name) // 报错

3. 块级作用域

ES6新增了块级作用域,最直接的表现就是新增的let关键词,使用let关键词定义的变量只能在块级作用域中被访问,有"暂时性死区"的特定,也就是说这个变量在定义之前是不能被使用的。

if语句及for语句后面的{...}这里面所包括的,就是块级作用域:

console.log(a) // a is not defined
if (true) {
  let a = '123'
  console.log(a) // 123
}
console.log(a) // a is not defined

二、什么是闭包

红宝书:闭包是指有权访问另外一个函数作用域中的变量的函数 MDN:一个函数和对其周围状态的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。

1. 闭包的基本概念

闭包其实就是一个可以访问其他函数内部变量的函数。即一个定义在函数内部的函数,或者直接说闭包是个内嵌函数也可以。

因为通常情况下,函数内部变量是无法在外部访问的(即全局变量和局部变量的区别),因此使用闭包的作用,就具备实现了能在外部访问某个函数内部变量的功能,让这些内部变量的值始终可以保存在内存中。

function fun1() {
  var a = 1
  return function () {
    console.log(a)
  }
}
fun1()
var result = fun1()
result() // 1

2. 闭包产生的原因

当访问一个变量时,代码解释器会首先在当前的作用域查找,如果没找到,就去父级作用域去查找,直到找到该变量或者不存在父级作用域中,这样的链路就是作用域链。

var a = 1
function fun1() {
  var a = 2
  function fun2() {
    var a = 3
    console.log(a) // 3
  }
}
// fun1 函数的作用域指向全局作用域(window)和它自己本身;fun2 函数的作用域指向全局作用域(window)、fun1 和它本身;而作用域是从最底层向上找,直到找到全局作用域 window 为止,如果全局还没有的话就会报错

function fun1() {
  var a = 2
  function fun2() {
    console.log(a) // 2
  }
  return fun2
}
var result = fun1()
result()
// 那是不是只有返回函数才算是产生了闭包呢?其实也不是,回到闭包的本质,**我们只需要让父级作用域的引用存在即可**

var fun3
function fun1() {
  var a = 2
  fun3 = function () {
    console.log(a)
  }
}
fun1()
fun3()

闭包产生的本质:当前环境中存在指向父级作用域的引用。

3. 闭包的表现形式

返回一个函数,上面将原因的时候已经说过,这里就不在赘述了。

在定时器、事件监听、Ajax请求、Web Workers 或者任何异步中,只要使用了回调函数,实际上就是在使用闭包。

// 2.1定时器
setTimeout(function handler() {
  console.log('1')
}, 1000)
// 2.2事件监听
$('app').click(function () {
  console.log('Event Listener')
})

作为函数参数传递的形式,比如下面的例子:

// 3.作为函数参数传递的形式
var a = 1
function foo() {
  var a = 2
  function baz() {
    console.log(a)
  }
  bar(baz)
}
function bar(fn) {
  // 这就是闭包
  fn()
}
foo() // 输出2,而不是1

IIFE(立即执行函数),创建了闭包,保存了全局作用域(window)和当前函数的作用域,因此可以输出全局的变量,如下所示:

// 4.IIFE(立即执行函数)
var a = 2
(function IIFE() {
  console.log(a) // 输出2
})()

IIFE 这个函数会稍微有些特殊,算是一种自执行匿名函数,这个匿名函数拥有独立的作用域。这不仅可以避免了外界访问此 IIFE 中的变量,而且又不会污染全局作用域,我们经常能在高级的 Javascript 编程中看见此类函数。

三、如何解决循环输出问题

for (var i = 1; i <= 5; i++) {
  setTimeout(function () {
    console.log(i)
  }, 0)
}
// 依次输出 5个6

setTimeout 为宏任务,由于 JS 中单线程 eventLoop 机制,在主线程同步任务执行完后才去执行宏任务,因此循环结束后 setTimeout 中的回调才依次执行。

因为 setTimeout 函数也是一种闭包,往上找它的父级作用域就是 window,变量 i 为 window 上的全局变量,开始执行 setTimeout 之前变量 i 已经是 6 了,因此最后输出的连续都是 6。

1. 利用 IIFE

利用 IIFE,当每次 for 循环时,把此时的变量 i 传递到定时器中,然后执行:

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

2. 使用 ES6 中的 let

let 让 JS 有了块级作用域,代码的作用域以块级为单位进行执行:

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

3. 定时器传入第三个参数

setTimeout 作为经常使用的定时器,它是存在第三个参数的,日常工作中我们经常使用的一般是前两个,一个是回调函数,另外一个是时间,而第三个参数用得比较少:

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

第三个参数的传递,改变了 setTimeout 的执行逻辑,从而实现我们想要的结果,这也是一种解决循环输出问题的途径。

到此这篇关于一文剖析Javascript中闭包的难点的文章就介绍到这了,更多相关Javascript闭包内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!


推荐阅读
  • 开发笔记:加密&json&StringIO模块&BytesIO模块
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了加密&json&StringIO模块&BytesIO模块相关的知识,希望对你有一定的参考价值。一、加密加密 ... [详细]
  • 本文介绍了闭包的定义和运转机制,重点解释了闭包如何能够接触外部函数的作用域中的变量。通过词法作用域的查找规则,闭包可以访问外部函数的作用域。同时还提到了闭包的作用和影响。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 本文介绍了在wepy中运用小顺序页面受权的计划,包含了用户点击作废后的从新受权计划。 ... [详细]
  • PHP图片截取方法及应用实例
    本文介绍了使用PHP动态切割JPEG图片的方法,并提供了应用实例,包括截取视频图、提取文章内容中的图片地址、裁切图片等问题。详细介绍了相关的PHP函数和参数的使用,以及图片切割的具体步骤。同时,还提供了一些注意事项和优化建议。通过本文的学习,读者可以掌握PHP图片截取的技巧,实现自己的需求。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 如何使用Java获取服务器硬件信息和磁盘负载率
    本文介绍了使用Java编程语言获取服务器硬件信息和磁盘负载率的方法。首先在远程服务器上搭建一个支持服务端语言的HTTP服务,并获取服务器的磁盘信息,并将结果输出。然后在本地使用JS编写一个AJAX脚本,远程请求服务端的程序,得到结果并展示给用户。其中还介绍了如何提取硬盘序列号的方法。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • 本文介绍了前端人员必须知道的三个问题,即前端都做哪些事、前端都需要哪些技术,以及前端的发展阶段。初级阶段包括HTML、CSS、JavaScript和jQuery的基础知识。进阶阶段涵盖了面向对象编程、响应式设计、Ajax、HTML5等新兴技术。高级阶段包括架构基础、模块化开发、预编译和前沿规范等内容。此外,还介绍了一些后端服务,如Node.js。 ... [详细]
  • 本文讨论了编写可保护的代码的重要性,包括提高代码的可读性、可调试性和直观性。同时介绍了优化代码的方法,如代码格式化、解释函数和提炼函数等。还提到了一些常见的坏代码味道,如不规范的命名、重复代码、过长的函数和参数列表等。最后,介绍了如何处理数据泥团和进行函数重构,以提高代码质量和可维护性。 ... [详细]
  • 使用nodejs爬取b站番剧数据,计算最佳追番推荐
    本文介绍了如何使用nodejs爬取b站番剧数据,并通过计算得出最佳追番推荐。通过调用相关接口获取番剧数据和评分数据,以及使用相应的算法进行计算。该方法可以帮助用户找到适合自己的番剧进行观看。 ... [详细]
  • 本文详细介绍了SQL日志收缩的方法,包括截断日志和删除不需要的旧日志记录。通过备份日志和使用DBCC SHRINKFILE命令可以实现日志的收缩。同时,还介绍了截断日志的原理和注意事项,包括不能截断事务日志的活动部分和MinLSN的确定方法。通过本文的方法,可以有效减小逻辑日志的大小,提高数据库的性能。 ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • Metasploit攻击渗透实践
    本文介绍了Metasploit攻击渗透实践的内容和要求,包括主动攻击、针对浏览器和客户端的攻击,以及成功应用辅助模块的实践过程。其中涉及使用Hydra在不知道密码的情况下攻击metsploit2靶机获取密码,以及攻击浏览器中的tomcat服务的具体步骤。同时还讲解了爆破密码的方法和设置攻击目标主机的相关参数。 ... [详细]
  • 本文记录了在vue cli 3.x中移除console的一些采坑经验,通过使用uglifyjs-webpack-plugin插件,在vue.config.js中进行相关配置,包括设置minimizer、UglifyJsPlugin和compress等参数,最终成功移除了console。同时,还包括了一些可能出现的报错情况和解决方法。 ... [详细]
author-avatar
走下去就下去
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有