文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

死磕JS:闭包到底是个什么鬼?

2024-12-03 10:04

关注

本文转载自微信公众号「前端思维框架」,作者ViktorHub。转载本文请联系前端思维框架公众号。     

 在JavaScript这门语言中,闭包是它的核心基础之一,可以说是一个特色了,但是很多从事前端工作的程序员并没有真正的理解它!

闭包有多重要?如果你是初入前端的朋友,我可以肯定得告诉你,前端面试,必问闭包。面试官们常常用对闭包的了解程度来判定面试者的基础水平,保守估计,10个前端面试者,至少5个都死在闭包上。

通过本文讲解,希望你可以重新认识一下闭包!

函数调用时发生了什么?

为了理解闭包,首先我们需要完全理解 JavaScript 到底是如何工作的!

那么函数调用是会发生什么呢?

当浏览器在解析 JS代码的时候,会进行一个预解析的操作,会有一个js解析器,里面会执行其中的两步操作:

预解析,找一些东西(var function 参数);

逐行去解读代码。

当解析器解读函数调用时,会将整个函数执行一个入栈操作,并为函数创建一个新的执行上下文。函数内部可以看作是一个小的区域,它有它自己的作用域和执行线程,也要逐行解读。当函数显式返回(到达return语句)或隐式返回(默认情况下函数返回undefined)时,函数将出栈,其执行上下文也将被销毁。

闭包是什么鬼?

我们先来看下这段代码:

  1. let name = "John" 
  2.  
  3. function greet() { 
  4.   const greeting = "Hi" 
  5.  
  6.   function printHi() { 
  7.     console.log(greeting + ' ' + name
  8.   } 
  9.   printHi() 
  10.  
  11. name = "Jane" 
  12.  
  13. greet() // "Hi Jane" 

我们发现,子函数 printHi 可以访问全局作用域和其父函数 greet 的局部作用域。

注意,我们实际上可以访问函数执行期间可用的“新”数据,而不是声明。这就是词法作用域在 JavaScript 中的工作方式。

但是如果我们返回一个函数,而不是仅仅在外部函数体中调用它,会发生什么呢?

看好了,奇迹出现了!

从一个函数中返回的函数不仅仅是一个简单的函数定义,它是这个定义加上它可以访问并需要执行的变量,这些变量存储在它附带的词法作用域中。

我们刚刚描述的就是闭包。从形式上讲,闭包是一个「即使在词法范围之外调用,仍可以记住它的词法范围」的函数。

  1. function creator(num) { 
  2.   return function() { 
  3.     num = num * 2 
  4.     console.log(num) 
  5.   } 
  6.  
  7. const double = creator(5) 
  8. double() //10 
  9. double() //20 
  10.  
  11. const double2 = creator(7) 
  12. double2() // 14 
  13. double2() //28 
  14.  
  15. double() // 40 

正如我们在上面的代码片段中看到的,每当我们调用 double 时,它都会更新存储在其词法作用域中的同一个变量(来自其父函数的num),从技术上讲,这是函数所具有的隐藏 [[scope]] 属性。

如果你想知道闭包到底有什么用,请继续看下面的示例。

模块封装

闭包允许我们保护或隐藏某些信息。[[scope]] 是一个隐藏的属性,所以我们不能像使用标准对象那样访问和更新它。还有一点很重要,我们可以返回一组存储在对象上的函数,它们都是闭包。

在下面的代码片段中,我们利用了所谓的IIFE(立即执行函数),它允许我们消除调用外部函数的中间步骤,就像我们在赋值时直接调用它一样。

  1. const myModule  = (function(){ 
  2.   const apiKey = "123456789" 
  3.  
  4.   return { 
  5.     displayKey() { 
  6.       console.log(apiKey) 
  7.     } 
  8.   }  
  9. })() 
  10.  
  11. myModule.displayKey() // "123456789" 

如果我们将这个模块 export 出去, 提供给其他人使用,我们为他准备的 API 不允许他更改 apiKey,这就做到了只读属性,除了在源代码中重写它之外,调用方不可能更改它。

缓存和记忆化

假设您想创建一个简单的ID生成器。为了确保总是返回比上一个高的数字,也可以使用闭包。我们将缓存当前变量中最高的 ID 值。

  1. const newID = (function() { 
  2.   let current = 0 
  3.   return function() { 
  4.     return ++current 
  5.   } 
  6. })() 
  7.  
  8. newID() // 1 
  9. newID() // 2 

当我们的算法时间复杂度很高时,这种缓存方式就非常有用,我们可以将部分结果存储在缓存中,当我们使用更高的数字进行计算时,我们可以使用缓存中的数据作为基础。这个过程叫做记忆化。一个最好的例子就是处理处理递归问题,比如斐波那契序列。

  1. const factorialMemo = (function() { 
  2.   const cache = {} 
  3.   return function factorial(n) { 
  4.     if(n === 1 || n === 0) { 
  5.       return 1 
  6.     } else if (cache[n]) { 
  7.       return cache[n] 
  8.     } else { 
  9.       cache[n] = n * factorial(n-1) 
  10.       return cache[n] 
  11.     } 
  12.   } 
  13. })() 
  14.  
  15.  
  16. factorialMemo(5) //120 
  17. // cache object looks like {'2': 2, '3' : 6, '4' : 24, '5' : 120} 
  18. factorialMemo(6) // 6 * cached 120  

好了,今天的内容到此就结束了,你有 get 到闭包到底是个什么鬼了吗?当然要熟练掌握,还需要你在不断的练习与总结中自我体会!

在《面向对象分析与设计》这本书里有一句话对闭包的概括我很喜欢,他是这样说的:

“闭包是懒人的对象,对象是天然的闭包!”

慢慢悟吧,欢迎评论交流,周末愉快~

 

来源: 前端思维框架内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯