闭包就那么一两句话谁都能背出来。可是闭包偏偏就是那种初学者十次面试八次可能会遇到,答不上来就是送命题、答得出来也不加分题。为了不让我们前端开发从入门到放弃,我还是来谈谈我认为的 JS 里面的闭包。
闭包是什么
闭包创建一个词法作用域,这个作用域里面的变量被引用之后可以在这个词法作用域外面被自由访问,是一个函数和声明该函数的词法环境的组合
还有一种说法,闭包就是引用了自由变量的函数,这个自由变量与函数一同存在,即使脱离了创建它的环境。所以你常看到的说闭包就是绑定了上下文环境的函数,也大概是这个意思。只要想明白了,就觉得其实是很简单的一个东西,并没有多么高深。
在下面的介绍中,我还是比较偏向闭包是一个函数和声明该函数的词法环境的组合这种解释,所以也会基于这种解释去阐述。
闭包其实是计算机科学里面的一个概念,并不是JS里面独有的。闭包的概念出现于60年代,最早实现闭包的程序语言是Scheme。(别问我 Scheme 是什么,问了我也不知道,这段是从维基上抄的。)之后,闭包被广泛使用于函数式编程语言。
JS里面的闭包
现在,我就发大绝招了,徒手撸一个闭包。
function sayHello(name) { let str = `Hello,${name}`; function say() { console.log(str); } return say; } let myHello = sayHello('abby'); myHello(); // Hello,abby
上面这段代码,其实就形成了一个闭包,其中在 sayHello 这个函数里面定义的函数 say 和其声明它的词法环境就形成了一个闭包,因为它引用了sayHello 里面定义的一个变量 str,并且将 say 这个函数 return 了出去,这样在 sayHello 这个函数的外面也能访问它里面定义的变量 str,就好像 say 这个函数和这个变量绑定了一样。
看到这里可能会疑问为什么在外部还能访问到这个变量呢,因为在有些语言中,一般认为函数的局部变量只在函数的执行期间可访问。说到这里又不得不说到执行环境,不太了解的朋友可能先去看我这篇文章:你不知道的执行上下文。其实当执行到let myHello = sayHello('abby');这段代码的时候按理会销毁掉 sayHello()的执行环境,但是这里却没有,原因是因为 sayHello() 返回的是一个函数,这个函数里面的 str 引用了外部的变量 str,如果销毁了就找不到了,因此 sayHello() 这个函数的执行环境会一直在内存中,所以会有闭包会增加内存开销balabala之类的。
其实说到这里,闭包就应该是说完了的,但是可能有好多东西看的都是一脸懵,那么我们就继续来讲几个例子吧!
举个例子
例子1:闭包并不是一定需要 return 某个函数
虽然常见的闭包都是 return 出来一个函数,但是闭包并不一定非要 return,return 出一个函数只是为了能在作用域范围之外访问一个变量,我们用另一种方式也能做到,比如:
let say; function sayHello(name) { let str = `Hello,${name}`; say = function() { console.log(str); } } let myHello = sayHello('abby'); say(); // Hello,abby
在这个例子里面,say和声明它的词法环境其实也形成了一个闭包,在它的作用域里面持有了 sayHello 这个函数里面定义的 str 变量的引用,因此也能在 str 变量定义的作用域之外访问它。只要弄清楚闭包的本质即可。
但是在 JS 里面,最常用的形成闭包的方式便是在一个函数里面嵌套另一个函数,另一个函数持有父作用域里面定义的变量。
例子2:同一个调用函数生成同一个闭包环境,在里面声明的所有函数同时具有这个环境里面自由变量的引用。
这句话说起来很绕,其实我给个很简单的例子就可以了。
let get, up, down function setUp() { let number = 20 get = function() { console.log(number); } up = function() { number += 3 } down = function() { number -=2; } } setUp(); get(); // 20 up(); down(); get(); // 21
在这个例子里面,我们用setUp这个函数生成了一个闭包环境,在这个环境里面的三个函数共享了这个环境里面的 number 变量的引用,因此都可以对 number 进行操作。
例子3:每一个调用函数都会创建不同的闭包环境。
还是给一个很简单的例子。
function newClosure() { let array = [1, 2]; return function(num) { array.push(num); console.log(`array:${array}`); } } let myClosure = newClosure(); let yourClosure = newClosure(); myClosure(3); // array:1,2,3 yourClosure(4); // array:1,2,4 myClosure(5); // array:1,2,3,5
上面这个例子里面, myClosure 和 yourClosure 的赋值语句,也就是 newClosure 这个函数被调用了两次,因此创建了两个不同的闭包环境,因此里面的变量是互不影响的。
例子4:在循环里面创建闭包
function newClosure() { for(var i = 0; i < 5; i++) { setTimeout(function() { console.log(i); }) } } newClosure(); // 5个5
打印的结果大家也知道是5个5,因为 setTimeout 里面的函数保持对 i 的引用,在setTimeout的回调函数被执行的时候这个循环早已经执行完成,这里我之前在另一篇文章里面做过更深入的介绍:深入浅出Javascript事件循环机制(上)。
这里我要说的是我们如何才能得到我们想要的01234,在这里有两种做法。
一种是 创建一个新的闭包对象,这样每个闭包对象里面的变量就互不影响。例如下面的代码种每次 log(i)都会创建不同的闭包对象,所有的回调函数不会指向同一个环境。
function log(i) { return function() { console.log(i); } } function newClosure() { for(var i = 0; i < 5; i++) { setTimeout(log(i)); } } newClosure(); // 0 1 2 3 4
另一种做法就是使用自执行函数,外部的匿名函数会立即执行,并把 i 作为它的参数,此时函数内 e 变量就拥有了 i 的一个拷贝。当传递给 setTimeout 的匿名函数执行时,它就拥有了对 e 的引用,而这个值是不会被循环改变的。写法如下:
function newClosure() { for(var i = 0; i < 5; i++) { (function(e) { setTimeout(function() { console.log(e); }) })(i) } } newClosure(); // 0 1 2 3 4
看看,写这么多,多累是不是,还是let省事,所以赶紧拥抱 es6 吧。。。
好了,这次是真的结束了,我所理解的闭包大概就是这样了,如果理解有所偏差,欢迎指出,谁当初不是从颗白菜做起的呢,学习前端的小伙伴们可以看看哦!
关于闭包:
以上是JavaScript中的闭包的详细内容。更多信息请关注PHP中文网其他相关文章!

Python和JavaScript在开发环境上的选择都很重要。1)Python的开发环境包括PyCharm、JupyterNotebook和Anaconda,适合数据科学和快速原型开发。2)JavaScript的开发环境包括Node.js、VSCode和Webpack,适用于前端和后端开发。根据项目需求选择合适的工具可以提高开发效率和项目成功率。

是的,JavaScript的引擎核心是用C语言编写的。1)C语言提供了高效性能和底层控制,适合JavaScript引擎的开发。2)以V8引擎为例,其核心用C 编写,结合了C的效率和面向对象特性。3)JavaScript引擎的工作原理包括解析、编译和执行,C语言在这些过程中发挥关键作用。

JavaScript是现代网站的核心,因为它增强了网页的交互性和动态性。1)它允许在不刷新页面的情况下改变内容,2)通过DOMAPI操作网页,3)支持复杂的交互效果如动画和拖放,4)优化性能和最佳实践提高用户体验。

C 和JavaScript通过WebAssembly实现互操作性。1)C 代码编译成WebAssembly模块,引入到JavaScript环境中,增强计算能力。2)在游戏开发中,C 处理物理引擎和图形渲染,JavaScript负责游戏逻辑和用户界面。

JavaScript在网站、移动应用、桌面应用和服务器端编程中均有广泛应用。1)在网站开发中,JavaScript与HTML、CSS一起操作DOM,实现动态效果,并支持如jQuery、React等框架。2)通过ReactNative和Ionic,JavaScript用于开发跨平台移动应用。3)Electron框架使JavaScript能构建桌面应用。4)Node.js让JavaScript在服务器端运行,支持高并发请求。

Python更适合数据科学和自动化,JavaScript更适合前端和全栈开发。1.Python在数据科学和机器学习中表现出色,使用NumPy、Pandas等库进行数据处理和建模。2.Python在自动化和脚本编写方面简洁高效。3.JavaScript在前端开发中不可或缺,用于构建动态网页和单页面应用。4.JavaScript通过Node.js在后端开发中发挥作用,支持全栈开发。

C和C 在JavaScript引擎中扮演了至关重要的角色,主要用于实现解释器和JIT编译器。 1)C 用于解析JavaScript源码并生成抽象语法树。 2)C 负责生成和执行字节码。 3)C 实现JIT编译器,在运行时优化和编译热点代码,显着提高JavaScript的执行效率。

JavaScript在现实世界中的应用包括前端和后端开发。1)通过构建TODO列表应用展示前端应用,涉及DOM操作和事件处理。2)通过Node.js和Express构建RESTfulAPI展示后端应用。


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

MinGW - 适用于 Windows 的极简 GNU
这个项目正在迁移到osdn.net/projects/mingw的过程中,你可以继续在那里关注我们。MinGW:GNU编译器集合(GCC)的本地Windows移植版本,可自由分发的导入库和用于构建本地Windows应用程序的头文件;包括对MSVC运行时的扩展,以支持C99功能。MinGW的所有软件都可以在64位Windows平台上运行。

禅工作室 13.0.1
功能强大的PHP集成开发环境

EditPlus 中文破解版
体积小,语法高亮,不支持代码提示功能

mPDF
mPDF是一个PHP库,可以从UTF-8编码的HTML生成PDF文件。原作者Ian Back编写mPDF以从他的网站上“即时”输出PDF文件,并处理不同的语言。与原始脚本如HTML2FPDF相比,它的速度较慢,并且在使用Unicode字体时生成的文件较大,但支持CSS样式等,并进行了大量增强。支持几乎所有语言,包括RTL(阿拉伯语和希伯来语)和CJK(中日韩)。支持嵌套的块级元素(如P、DIV),

WebStorm Mac版
好用的JavaScript开发工具