闭包到底是个什么鬼?

作者: 源大侠 | 来源:发表于2021-04-06 10:41 被阅读0次

    JavaScript这门语言中,闭包是它的核心基础之一,可以说是一个特色了,但是很多从事前端工作的程序员并没有真正的理解它!
    闭包有多重要?如果你是初入前端的朋友,我可以肯定得告诉你,前端面试,必问闭包!面试官们常常用对闭包的了解程度来判定面试者的基础水平,保守估计,10个前端面试者,至少5个都死在闭包上。
    通过本文讲解,希望你可以重新认识一下闭包!
    我们开始吧~\

    函数调用时发生了什么?

    为了理解闭包,首先我们需要完全理解 JavaScript 到底是如何工作的!
    那么函数调用是会发生什么呢?
    当浏览器在解析 JS代码的时候,会进行一个预解析的操作,会有一个js解析器,里面会执行其中的两步操作:
    1、预解析,找一些东西(var function 参数);
    2、逐行去解读代码。
    当解析器解读函数调用时,会将整个函数执行一个入栈操作,并为函数创建一个新的执行上下文。函数内部可以看作是一个小的区域,它有它自己的作用域和执行线程,也要逐行解读。当函数显式返回(到达return语句)或隐式返回(默认情况下函数返回undefined)时,函数将出栈,其执行上下文也将被销毁。

    闭包是什么鬼?

    我们先来看下这段代码:

    let name = "John"
    function greet() {
      const greeting = "Hi"
      function printHi() {
        console.log(greeting + ' ' + name)
      }
      printHi()
    }
    name = "Jane"
    greet() // "Hi Jane"
    

    我们发现,子函数printHi 可以访问全局作用域和其父函数 greet 的局部作用域。
    注意,我们实际上可以访问函数执行期间可用的“新”数据,而不是声明。这就是词法作用域在JavaScript 中的工作方式。
    但是如果我们返回一个函数,而不是仅仅在外部函数体中调用它,会发生什么呢?
    看好了,奇迹出现了!
    从一个函数中返回的函数不仅仅是一个简单的函数定义,它是这个定义加上它可以访问并需要执行的变量,这些变量存储在它附带的词法作用域中。
    我们刚刚描述的就是闭包。从形式上讲,闭包是一个「即使在词法范围之外调用,仍可以记住它的词法范围」的函数。

    function creator(num) {
      return function() {
        num = num * 2
        console.log(num)
      }
    }
    const double = creator(5)
    double() //10
    double() //20
    const double2 = creator(7)
    double2() // 14
    double2() //28
    double() // 40
    

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

    组件模式

    闭包允许我们保护或隐藏某些信息。[[scope]]是一个隐藏的属性,所以我们不能像使用标准对象那样访问和更新它。还有一点很重要,我们可以返回一组存储在对象上的函数,它们都是闭包。
    在下面的代码片段中,我们利用了所谓的IIFE(立即执行函数),它允许我们消除调用外部函数的中间步骤,就像我们在赋值时直接调用它一样。

    const myModule  = (function(){
      const apiKey = "123456789"
      return {
        displayKey() {
          console.log(apiKey)
        }
      } 
    })()
    myModule.displayKey() // "123456789"
    

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

    基础缓存和记忆化

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

    const newID = (function() {
      let current = 0
      return function() {
        return ++current
      }
    })()
    newID() // 1
    newID() // 2
    

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

    const factorialMemo = (function() {
      const cache = {}
      return function factorial(n) {
        if(n === 1 || n === 0) {
          return 1
        } else if (cache[n]) {
          return cache[n]
        } else {
          cache[n] = n * factorial(n-1)
          return cache[n]
        }
      }
    })()
    factorialMemo(5) //120
    // cache object looks like {'2': 2, '3' : 6, '4' : 24, '5' : 120}
    factorialMemo(6) // 6 * cached 120 
    

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

    相关文章

      网友评论

        本文标题:闭包到底是个什么鬼?

        本文链接:https://www.haomeiwen.com/subject/wmdskltx.html