好程序员HTML5培训技术分享JavaScript 闭包

简介:   1. 概述  闭包(closures),在 MDN 解释为:  Closures are functions that refer to independent (free) variables (variables that are used locally, but defined in an enclosing scope).

  1. 概述

  闭包(closures),在 MDN 解释为:

  Closures are functions that refer to independent (free) variables (variables that are used locally, but defined in an enclosing scope). In other words, these functions 'remember' the environment in which they were created.

  闭包是指那些能够访问独立(自由)变量的函数 (变量在本地使用,但定义在一个封闭的作用域中)。换句话说,这些函数可以“记忆”它被创建时候的环境。

  闭包是 JavaScript 语言的一个特色,当然也是它的一大难点,很多高级应用都要依靠闭包实现,或者我们平常编码过程中,也在有意无意间使用到闭包。

  2. 作用域链

  在理解闭包,首先就要理解 JavaScript 中的作用域链。

  在 JavaScript 中有两种作用域:全局作用域和函数作用域(在 ES6 中引入了块级作用域)。

  在函数中定义的变量只能在本函数体中使用到,在函数外部不能直接调用函数体内部定义的变量,但函数中可以调用到全局作用域中定义的变量。

  如果函数中有内嵌函数的定义,则在内嵌函数中可以访问到外部函数中定义的变量,也可访问到全局作用域中的变量,但在外部函数中不能访问内嵌函数中定义的变量。这样,就形成了作用域链,即内嵌函数可调用父级或祖先级函数中定义的变量,但父级函数不能调用子级或后代函数中定义的变量。

  function outer(){

  var outVar = 10;

  function inner(){

  var inVar = 20;

  console.log("inner 中调用外部函数变量 outVar = " + outVar);

  }

  inner();

  console.log("outer 中调用内嵌函数变量 inVar = " + inVar);

  }

  outer();

  执行结果:

  inner 中调用外部函数变量 outVar = 10

  ReferenceError: Can't find variable: inVar

  在 JavaScript 中,变量的作用域是由它在源代码中所处位置决定的,并且嵌套的函数可以访问到其外层作用域中声明的变量。

  3. 闭包

  如果有这样一种需求,我们需要在外部使用到函数内的变量,但正常情况下,通过直接调用的方式是不能访问到的,这就需要变通的方法了。

  function outer() {

  var i = 1;

  var inner = function(){

  return ++i;

  }

  return inner;

  }

  var result = outer();

  console.log("第一次调用:" + result());

  console.log("第二次调用:" + result());

  console.log("第三次调用:" + result());

  执行结果:

  第一次调用:2

  第二次调用:3

  第三次调用:4

  上例中,我们要使用到 outer 函数内部的变量 i,每次打印是在原有数值基础上自增 1。因在函数外部不能直接通过变量名对其进行访问,而嵌套在内部的 inner 函数则能够访问到外部函数变量 i,所以返回了内部函数的引用 inner,这样,当 outer 函数调用结束后,放置在 result 中的实际为内嵌函数的引用,这样就可以继续使用到在 outer 函数内部定义的变量 i 了。这就是闭包。

  以前常用到的定时器,相信大家写过类似的代码片段:

  function fn(){

  var i = 0;

  var timer = setInterval(function(){

  console.log(i++);

  if(i > 10)

  clearInterval(timer);

  }, 50);

  }

  fn();

  fn 函数调用结束后,按理说在 fn 函数内部的局部变量 i、timer 作用域该结束了,但 setInterval()函数的异步执行过程中,仍然可以使用到这两个变量的值。这也是典型的闭包使用情况。

  4. 一个故事

  来说明闭包可以有哪些适用场景前,我喜欢下面这个例子。

  很久很久以前:

  有一位公主......

  function princess() {

  她住在一个充满冒险的奇妙世界里,遇到了她的白马王子。白马王子带着她骑着独角兽开始周游世界,与巨龙战斗,巧遇会说话的动物,还有很多其他的不可思议的新奇事物。

  var adventures = [];

  function princeCharming() { / ... / }

  var unicorn = { / ... / },

  dragons = [ / ... / ],

  squirrel = "Hello!";

  但她不得不回到自己乏味的王国里,例行去见那些成年人。

  return {

  她会经常给大人分享她最近作为公主时的充满奇幻的冒险经历。

  sayStory: function() {

  return adventures[adventures.length - 1];

  }

  };

  }

  但在大人的眼里,公主仅仅只是一个小女孩儿......

  var littleGirl = princess();

  ......在讲着一些神奇的、充满幻想的故事。

  littleGirl.sayStory();

  即便所有大人都知道他们眼前的小女孩是真的公主,但是他们绝不相信有巨龙或独角兽,因为他们自己从来没有见到过。大人们说它们只存在于小女孩的想象之中。

  但是我们却知道小女孩述说的是事实......

  5. 闭包适用场景

  通常闭包有如下两种适用场景:

  · 在内存中维持变量,如缓存数据

  · 保护函数体内变量的安全,如为对象设置私有属性

  5.1 缓存数据

  一个比较常用到的例子就是,利用循环为元素绑定事件。

  让每个 div 元素被点击时,都能正确弹出当前被点击的 div 的索引:

  div-1

  div-2

  div-3

  div-4

  div-5

  如果使用如下写法:

  这时,在每个 div 上点击时弹出的结果都是你点击的 div 索引为:5。这是因为事件处理是异步的,但事件绑定是同步的,会先执行完循环体的 5 次操作,为每个 div 绑定上 onclick 事件。

  这个过程中,变量 i 的值一直在递增变化,当所有 div 元素都被遍历后,i 的值自增到 5 退出循环结构。函数 handle 调用结束后,由于在事件响应程序中仍然存在变量 i 的引用,如果释放变量 i 的资源,会导致事件响应程序执行错误,所以为了保证事件响应程序中仍然能正确使用到变量 i,会将变量 i 的值一直保留在内存中,但保留的 i 的值为 5。

  如果要正确输出索引值,可使用闭包修改如下:

  在为每个 div 绑定事件时,调用 clk() 函数将与 div 关联的变量值 i 传递到 clk() 函数内部使用,因为内部返回了一个内嵌函数的引用,该内嵌函数功能的实现依赖于外部函数中的局部变量 index,所以 index 变量的值会在内存中得以缓存。

  由于每个 div 绑定事件时,都调用了 clk() 函数来实现事件绑定操作,所以与之对应的变量索引 i 的数值也都在内存中得以缓存,只是这个值不是以 i 的名称来缓存。当我们再次测试时,就可以正确打印出所点击 div 的索引了。

  当然以上功能的实现也可以通过自定义属性方式实现:

  或是通过 let 命令来实现:

  5.2 为对象设置私有属性

  如果有一个对象,拥有年龄这样一个属性,我们要限定年龄的取值范围在 18~25 岁之间,以类似 Java 面向对象的方式来实现,可模拟如下:

  age 表示学生的年龄,这样的一个变量如果对于任何人都可以修改值,那么如果给定一个负值,比如 -35,虽然就语法上来说没问题,但就实际逻辑来说,一个人不可能年龄为 -35 岁,所以为了保障这种数据的安全,可以使用闭包来解决。

  对 Student 函数内部的局部变量 age 来说,本应该在 Student() 函数通过 new 调用结束后就释放掉资源,但在对象的 getAge/setAge 方法中仍然有对其的引用,释放资源会导致 getAge/setAge 功能不能正常完成,所以其值会保存在内存中。但要修改 age 年龄值时,由于它的作用域问题,我们没法在 Student 函数外直接通过调用 age 的方式来修改,仅能使用提供的 setAge 方法接口修改 age 值,这就保证了对 age 修改赋值的安全性。

  6. 一点误解

  以前在查阅资料时,经常见到说不要轻易使用闭包,否则容易造成内存泄漏的说法。

  直到看到这篇文章:《js闭包测试》

  闭包里面的变量是我们需要使用到的变量(lives),而内存泄漏通常是指访问不到的变量依然占据内存空间,不能够对其占据的空间再次利用。显然闭包是不属于访问不到的内存空间。

  之所以有这样的说法,大概是因为 IE,特别是 IE6 的 bug 吧。当然这是 IE 浏览器的问题,不是闭包的问题。

  现代浏览器在 JavaScript 引擎中大都优化处理了闭包情形下的垃圾回收,所以关于内存泄漏的说法,我们大可不必再理会了。

相关文章
|
9月前
|
前端开发 JavaScript Java
JavaScript闭包深入剖析:性能剖析与优化技巧
JavaScript 闭包是强大而灵活的特性,广泛应用于数据封装、函数柯里化和事件处理等场景。闭包通过保存外部作用域的变量,实现了私有变量和方法的创建,提升了代码的安全性和可维护性。然而,闭包也可能带来性能问题,如内存泄漏和执行效率下降。为优化闭包性能,建议采取以下策略:及时解除对不再使用的闭包变量的引用,减少闭包的创建次数,使用 WeakMap 管理弱引用,以及优化闭包结构以减少作用域链查找的开销。在实际开发中,无论是 Web 前端还是 Node.js 后端,这些优化措施都能显著提升程序的性能和稳定性。
235 70
|
7月前
|
存储 JavaScript 前端开发
|
9月前
|
自然语言处理 JavaScript 前端开发
当面试官再问我JS闭包时,我能答出来的都在这里了。
闭包(Closure)是前端面试中的高频考点,广泛应用于函数式编程中。它不仅指函数内部定义的函数,还涉及内存管理、作用域链和垃圾回收机制。闭包可以让函数访问其外部作用域的变量,但也可能引发内存泄漏等问题。通过合理使用闭包,可以实现模块化、高阶函数和回调函数等应用场景。然而,滥用闭包可能导致代码复杂度增加、调试困难以及潜在的性能问题。为了避免这些问题,开发时应谨慎处理闭包,避免不必要的嵌套,并及时清理不再使用的变量和监听器。
408 16
当面试官再问我JS闭包时,我能答出来的都在这里了。
|
8月前
|
缓存 自然语言处理 JavaScript
JavaScript中闭包详解+举例,闭包的各种实践场景:高级技巧与实用指南
闭包是JavaScript中不可或缺的部分,它不仅可以增强代码的可维护性,还能在模块化、回调处理等场景中发挥巨大作用。然而,闭包的强大也意味着需要谨慎使用,避免潜在的性能问题和内存泄漏。通过对闭包原理的深入理解以及在实际项目中的灵活应用,你将能够更加高效地编写出简洁且功能强大的代码。 只有锻炼思维才能可持续地解决问题,只有思维才是真正值得学习和分享的核心要素。如果这篇博客能给您带来一点帮助,麻烦您点个赞支持一下,还可以收藏起来以备不时之需,有疑问和错误欢迎在评论区指出~
|
Web App开发 移动开发 程序员
好程序员分享HTML5 发展史
  好程序员前端分享HTML5发展史,HTML5草案的前身名为WebApplications1.0,于2004年被WHATWG提出,于2007年被W3C接纳,并成立了新的HTML工作团队。   HTML5的第一份正式草案已于2008年1月22日公布。
1160 0
|
5月前
|
移动开发 前端开发 JavaScript
征信报告修改器,征信报告生成器,制作软件无痕修改软件【js+html+css】
本项目为信用评分模拟器教学工具,采用HTML5实现,仅供学习参考。核心功能通过JavaScript构建,包含虚拟数据生成、权重分配及信用因素分析(如还款记录、信用使用率等)。
|
5月前
|
存储 自然语言处理 前端开发
抖音快手小红书虚拟评论截图生成器,模拟对话制作工具,html+js+css
这是一款纯前端实现的多平台虚拟评论生成器,支持抖音、快手、小红书风格,适用于产品演示与UI设计。采用Vanilla JS与Flexbox布局,利用IndexedDB存储数据,CSS Variables切换主题。
|
5月前
|
前端开发 JavaScript
个人征信电子版无痕修改, 个人信用报告pdf修改,js+html+css即可实现【仅供学习用途】
本代码展示了一个信用知识学习系统的前端实现,包含评分计算、因素分析和建议生成功能。所有数据均为模拟生成
|
5月前
|
存储 前端开发 安全
病历单生成器在线制作,病历单生成器app,HTML+CSS+JS恶搞工具
本项目为医疗病历模拟生成器,旨在为医学教学和软件开发测试提供数据支持,严格遵守《医疗机构病历管理规定》。
|
5月前
|
存储 前端开发 JavaScript
仿真银行app下载安装, 银行卡虚拟余额制作app,用html+css+js实现逼真娱乐工具
这是一个简单的银行账户模拟器项目,用于学习前端开发基础。用户可进行存款、取款操作,所有数据存储于浏览器内存中

热门文章

最新文章

下一篇
oss云网关配置