【程序员反劝退系列】终于理解闭包了——以Javascript为例

·  阅读 204

闭包

一个闭包是指函数能够记住并访问其变量的作用域,即使该函数在其变量的作用之外执行。

作用域 (耐心看完,闭包在后头💋)

要理解闭包首先要理解作用域,程序的作用域规定了变量所存储并能被检索的位置(范围)。程序中的一些结构(if块,函数块,{}等)创建了自己的作用域。如果我们在一个作用域内声明了一个变量,那么该变量在其他作用域内是不可访问的。


// 方法声明
const func = () => {
  // This is a third scope
  let a = 'Joe'
  console.log(a) // Joe
}
//主逻辑代码
// I am in the global scope
let a = 'Damien'

if( true ) {
  // This is a different scope
  let a = 'John'
  console.log(a) //John
}

func()
console.log(a) // Damien

复制代码

程序结果:

John
Joe
Damien
复制代码

如果试图检索当前作用域中不存在的变量,Javascript将在外部作用域中查找该变量。Javascript将重复此过程,直到没有更多的外部作用域可供检查。如果找不到该变量,则会出现ReferenceError:

John

/project/target/index.js:15
console.log(a) // ReferenceError
            ^

ReferenceError: a is not defined
    at Object.<anonymous> (/project/target/index.js:15:13)
    at Module._compile (node:internal/modules/cjs/loader:1101:14)
    at Object.Module._extensions..js (node:internal/modules/cjs/loader:1153:10)
    at Module.load (node:internal/modules/cjs/loader:981:32)
    at Function.Module._load (node:internal/modules/cjs/loader:822:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:79:12)
    at node:internal/main/run_main_module:17:47

复制代码

上述代码我删除了全局作用域内的变量声明(全局变量)。 当我尝试检索(调用)它时,Javascript 找不到它并返回错误。 在这种情况下,没有可检查的外部作用域了,因为我们已经在全局作用域内了。


const func = () => {
  // This is a third scope
  let a = 'Joe'
  console.log(a) // Joe
}
// I am in the global scope
let a = 'Damien'
if( true ) {
  // This is a different scope
  console.log(a) //Damien
}

console.log(a) // Damien
func() 
复制代码

程序结果

Damien
Damien
Joe
复制代码

在上例中,我删除了if块中的变量声明。Javascript在if作用域内找不到变量a,因此它在外部范围内查找。程序在这个外部作用域(全局作用域)中找到并使用它。

回到闭包本身!!!👀

了解了作用域之后,我们来理解当函数在其作用域外执行时,闭包允许函数访问其作用域。 让我们看看它的实际效果。

function outer(){
  let a = 'Damien'
   function inner(){
    console.log(a)
  }

  return inner
}
const func = outer()

func() // 'Damien'
console.log('Closure happened!!')
复制代码

结果:

Damien
Closure happened!!
复制代码

💋💋💋

为什么这是一个闭包? 作为一个闭包,这意味着函数inner正在作用域之外执行,并且仍然可以访问其内部作用域。那么这里发生了什么?

1、我们执行外部函数outer()并将inner函数的引用传递给func变量(此时并没有执行inner())。 2、然后调用func()执行内部函数innter()。但inner此时并不会在outer函数的作用域之内执行而是在全局作用域内调用func()再执行。它在外部函数outer()之外执行。理论上,该程序将释放空间,并看到我们的外部函数不再需要(垃圾收集器)。

inner在outer的内部作用域上有一个作用域闭包(这块作用域单独封闭起来不受outer作用域影响了)。这将使作用域保持活动状态,以便内部使用。内部函数inner在外部作用域中的引用(也就是return inner 这个操作)使该作用域保持活动状态。==>闭包。

其他例子

例1:在js常用的定时打印

function chrono( message ){
  setInterval( function timer() {
    console.log( message )
  }, 1000)
}

chrono('GOGOGO')
复制代码

结果:

GOGOGO
GOGOGO
GOGOGO
GOGOGO
GOGOGO
GOGOGO
GOGOGO
.....
.....
复制代码

timer 引用了 chrono的作用域里的message变量。 即使在每打印之后1秒间隔内timer显然不再需要chrono,chrono的作用域仍会保持活动状态。 因为该范围仍然有效,一秒过后timer可以每秒打印 'GOGOGO'(消息变量)。

例2:普通模块化编程例子

function myModule(){
  let name = 'Damien'
  let age = 25

  function sayMyName(){
    console.log(name)
  }

  function sayMyAge(){
    console.log(age)
  }

  return {
    sayMyAge,
    sayMyName
  }
}

const boom = myModule()

boom.sayMyAge()
boom.sayMyName()
复制代码

SayMyAgeSayMyName被执行在它被定义的作用域之外(本来被定义在myModule函数内的作用域,通常来说只在该作用域内执行才有用,但由于闭包的存在打破了这一概念),但是因为SayMyAgeSayMyName都引用了myModule函数的内部作用域,所以myModule函数的内部作用域始终保持活动状态以便SayMyAgeSayMyName使用 nameage 变量。

总结 (感谢耐心读完💋💋💋)

闭包就是通过引用作用域让该作用域内的变量等一直能被使用。因为引用的特点使在最外部作用域调用本来在某作用域内部的变量或方法时时能被调用。

分类:
阅读
标签: