【云驻共创】前端工程师在面试时经常被问的闭包到底是什么?我用打包礼物的例子让你秒懂
闭包是 JavaScript 中的一个重要概念,有时 JavaScript 开发人员都很难理解,闭包在面试中经常被问到,但是鲜有求职者能够流利、准确的答出,本文笔者将带大家好好了解一下闭包,由于闭包的知识点比较多,我尽可能用浅显易懂的话和图片来讲解。
一、前置知识:作用域
在正式学习闭包前,我们需要了解前置知识:作用域。JavaScript 中的作用域定义了可以访问的变量,一般有两种作用域:
- 全局作用域
- 局部作用域
1.1 全局作用域
如果一个变量在所有函数或花括号 ( {}) 之外声明,则称它是在全局范围内定义的。一旦声明了一个全局变量,你就可以在代码的任何地方使用这个变量,包括在函数中。
const wljslmz = 'I am wljslmz'
function printWhoIAm () {
console.log(wljslmz)
}
console.log(wljslmz) // 'I am wljslmz'
printWhoIAm() // 'I am wljslmz'
尽管可以在全局范围内声明变量,但建议不要这样做,因为很容易造成命名冲突。
使用let定义时,每当发生名称冲突时都会收到错误消息:
let wljslmz = 'I am wljslmz'
let wljslmz = 'I am wljslmz' //error
使用var定义时,第二个变量在声明后会覆盖第一个变量:
var wljslmz = 'I am wljslmz'
var wljslmz = 'I am wljslmz, i love nanjing' //此时全局变量就改变了
console.log(wljslmz) // 'I am wljslmz, i love nanjing'
所以我们得善用局部变量,而不是全局变量。
1.2 局部作用域
仅在代码的特定部分中可用的变量被视为在局部作用域,这些变量也称为局部变量。
在 JavaScript 中,有两种局部作用域:
- 函数作用域
- 块作用域
1.2.1 函数作用域
只能在函数内访问该变量,一旦脱离这个函数,就无法访问这个变量:
function wljslmz () {
const wljs = 'wljslmz good at wljs!'
console.log(wljs)
}
wljslmz() // 'wljslmz good at wljs!'
console.log(wljs) // Error
如上代码,定义了一个函数wljslmz,在函数外部直接调用wljslmz函数,可以正确执行函数中的打印输出,但是在外部想要直接打印wljslmz函数中的变量,就会报错,会提示“wljs没有定义”。
1.2.2 块作用域
块作用域是用大括号{}表示的作用域,在块作用域中声明变量,那么只能在该块作用域中进行访问。
{
const wljs = 'wljslmz good at wljs!'
console.log(wljs) //wljslmz good at wljs!
}
console.log(wljs) // Error
如上代码,有个块,其中定义了一个变量wljs,在此块中,可以进行调用,正常打印了这个变量wljs,但是在此块外部想要调用这个变量,就无法调用。
块作用域是函数作用域的子集,因为函数需要也是用花括号声明的。
1.3 作用域小结
通过以上示例代码的演示,我们知道,内部作用域很容易访问到外部范围的变量,这个就是词法范围,内部引擎规范好的。
以上就是我们要了解闭包前需要知道的前置知识,下面我们详细了解一下闭包。
二、闭包
2.1 什么是闭包?
闭包是当外部函数返回内部函数时,内部函数随后在不同的范围内执行,内部函数继续保持对外部函数变量的访问,即使外部函数不再存在。
这个解释我相信,没有个三五年经验的前端开发很难理解这句话的意思,笔者用一个生活的例子给大家解释一下。
国庆假期,你可以计划到出国旅行,日子太舒服了。
旅行完了,按照我们中国人的习惯都会给亲朋好友带当地的特产作为礼物,你也不例外。
你会买各种当地特产,然后用快递盒一件一件的包起来,护肤品寄给你妈妈,茶叶寄给你爸爸,面膜寄给你闺蜜……每打包好一个包裹,你就会在包裹上写上你要寄的人,如果你这个人记性不好,忘了给包裹贴标签了,那完了,你再寄快递的时候怎么去分辨?那么只有拆包裹,然后再一件一件区别开来。
那有人又说了,你可以开始的时候就把打包盒上写好名字,然后再根据名字和礼物对应上包装起来,这个不失为一个好办法,但是效率太低了,如果有1万个包裹呢,你随意拿出一个写好名字的盒子,然后你再去一万个礼物中挑选出装到这个盒子中的礼物嘛?这个显然不切实际。再者你把这些礼物包装好了,你就能确定当天能寄吗,最好是今天能寄多少就打包多少。种种麻烦和挑战告诉你需要找到一个好办法才能消耗你永远用不完的money。
正常的思维肯定是一次性无脑的打包好所有的箱子,然后在寄的时候再决定寄给谁。
在代码中,这类似于使用一个函数进行所有打包,并使用另一个函数来处理包。
可以使用闭包将一个函数放入另一个函数中,并在稍后执行内部函数,保存对实际内容的引用。
一个闭包包含三大块:
- 外部函数
- 内部函数
- 引用内部函数的 return 语句
伪代码就是:
function waibu(){
function neibu(){
}
return neibu
}
原谅我这里用了汉语拼音代替英文了,好懂一些。
2.2 闭包代码示例
我们还是以旅行寄礼物的例子来解释,请看以下代码:
// dabaoLiwu:打包礼物
function dabaoLiwu(liwu){
console.log('装' +liwu+ '到打包盒');
// jiadizhi:加地址
function jiadizhi(dizhi){
console.log('打包盒加地址,地址为' +dizhi+'并且准备寄礼物,礼物为'+liwu);
}
return jiadizhi;
}
解释一下为啥这里用汉语拼音,因为有些词汇英文大家在看的时候不认识还得翻译,我这边直接用汉语拼音表示得了,大家在真实开发中避免这种写法哈。
看上面的代码dabaoLiwu是外部函数,liwu为参数,内部函数是jiadizhi,dizhi为参数。
jiadizhi() 函数就是一个闭包,它可以在调用 dabaoLiwu 函数后的任何时间调用,另外它还可以访问最初调用 dabaoLiwu() 时的变量和参数,这样的话外部函数和内部函数都能访问礼物,但是地址只有内部函数可以访问。
到这里,你看明白了吗?没明白,我们来看下如何调用:
// dabaoLiwu:打包礼物
function dabaoLiwu(liwu){
console.log('装' +liwu+ '到打包盒');
// jiadizhi:加地址
function jiadizhi(dizhi){
console.log('打包盒加地址,地址为' +dizhi+'并且准备寄礼物,礼物为'+liwu);
}
return jiadizhi;
}
// 闺蜜礼物:口红
let guimiLiwu= dabaoLiwu('口红')
guimiLiwu('江苏省南京市XXXX王女士136XXXXX')
输出:
装口红到打包盒
打包盒加地址,地址为江苏省南京市XXXX王女士136XXXXX并且准备寄礼物,礼物为口红
我们打断点看下:
我们注意到执行到第15行代码的时候打印出了装口红到打包盒
,此时并没有打印出jiadizhi()函数中的console.log信息,因为此时jiadizhi()函数还没有运行,直到第17行代码运行结束后才打印了打包盒加地址,地址为江苏省南京市XXXX王女士136XXXXX并且准备寄礼物,礼物为口红
信息。
以上就是闭包的代码演示,看到这里你是否已经觉得闭包真的太好用了,不得不佩服闭包的设计者。
所以下次再有面试官问你闭包的时候,别忘了用旅游寄快递的例子说明一下。
我再给你分享一个万能闭包公式,等到你忘了闭包是怎么回事的时候,可以快速的回忆到:
function hello() {
return function (item) {
console.log(`hello ${item}`);
};
}
const helloWorld = hello();
helloWorld('world');
三、总结
Javascript 中的闭包是每位前端工程师都在使用的技术,有时候只是好多人用了也不知道这个概念,并且在面试中也是高频面试技术点,大多数人都反映闭包很难,但是我觉得只要你认真看了我的这篇文章,你会发现闭包也就那样。本文详细的介绍了闭包的前置知识:作用域,然后介绍了闭包的概念、生活中的例子、以及代码示例,希望本文对您学习闭包有所帮助,有任何问题欢迎在下方评论区与我讨论。
本文参与华为云社区【内容共创】活动第20期。
https://bbs.huaweicloud.com/blogs/374925
- 点赞
- 收藏
- 关注作者
评论(0)