首页 文章 精选 留言 我的

精选列表

搜索[html解析器],共10000篇文章
优秀的个人博客,低调大师

好程序员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). 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 引擎中大都优化处理了闭包情形下的垃圾回收,所以关于内存泄漏的说法,我们大可不必再理会了。

资源下载

更多资源
腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

Rocky Linux

Rocky Linux

Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。

WebStorm

WebStorm

WebStorm 是jetbrains公司旗下一款JavaScript 开发工具。目前已经被广大中国JS开发者誉为“Web前端开发神器”、“最强大的HTML5编辑器”、“最智能的JavaScript IDE”等。与IntelliJ IDEA同源,继承了IntelliJ IDEA强大的JS部分的功能。

用户登录
用户注册