首页 > 编程语言 >JavaScript闭包是如何工作的?

JavaScript闭包是如何工作的?

时间:2023-10-06 12:57:05浏览次数:40  
标签:闭包 function const 函数 JavaScript 如何 log

内容来自 DOC https://q.houxu6.top/?s=JavaScript闭包是如何工作的?

如何向一个对JavaScript闭包所包含的概念(例如函数、变量等)有一定了解,但不了解闭包本身的人解释这个概念?

我在维基百科上看到了Scheme示例,但不幸的是它并没有帮助。


闭包是由以下两部分组成的:

  1. 一个函数和
  2. 该函数的外部作用域(词法环境)的引用。

词法环境是每个执行上下文(堆栈帧)的一部分,是标识符(即局部变量名)和值之间的映射。

JavaScript中的每个函数都维护着对外部词法环境的引用。此引用用于在调用函数时配置创建的执行上下文。此引用使函数内部的代码能够“看到”在函数外部声明的变量,而不管函数何时以及在哪里被调用。

如果一个函数被另一个函数调用,然后又被另一个函数调用,则会创建一个指向外部词法环境的引用链。这个链被称为作用域链。

在以下代码中,inner与在调用foo时创建的执行上下文的词法环境形成一个闭包,覆盖了变量secret

function foo() {
  const secret = Math.trunc(Math.random() * 100)
  return function inner() {
    console.log(`The secret number is ${secret}.`)
  }
}
const f = foo() // `secret`无法直接从`foo`的外部访问
f() // 检索`secret`的唯一方法是调用`f`

换句话说:在JavaScript中,函数携带着对私有“状态盒子”的引用,只有它们(以及在同一词法环境中声明的其他所有函数)可以访问。这个状态盒子对于调用函数的用户是不可见的,提供了一个出色的数据隐藏和封装机制。

并且要记住:JavaScript中的函数可以像变量一样(第一类函数)被传递,这意味着这些功能和状态的配对可以在程序中被传递,类似于如何在C++中传递类的实例。

如果JavaScript没有闭包,那么更多的状态将不得不显式地在函数之间传递,使参数列表更长,代码更嘈杂。

因此,如果您希望函数始终能够访问私有状态,则可以使用闭包。

通常,我们确实希望将状态与函数关联起来。例如,在Java或C++中,当您向类添加私有实例变量和方法时,您就是在将状态与功能关联起来。

在C和大多数其他常见语言中,一旦函数返回,所有局部变量都不再可访问,因为堆栈帧被销毁。在JavaScript中,如果您在另一个函数中声明一个函数,则外部函数的局部变量可以在返回后仍然可访问。这样,在上面的代码中,secret在从foo返回后仍然可用于函数对象inner

闭包的用途

闭包在需要将与函数关联的私有状态时非常有用。这是一个非常常见的场景 - 请记住:JavaScript直到2015年才有类语法,并且仍然没有私有字段语法。闭包满足了这一需求。

私有实例变量

在以下代码中,函数toString关闭了汽车的细节。

function Car(manufacturer, model, year, color) {
  return {
    toString() {
      return `${manufacturer} ${model} (${year}, ${color})`
    }
  }
}

const car = new Car('Aston Martin', 'V8 Vantage', '2012', 'Quantum Silver')
console.log(car.toString())

函数式编程

在以下代码中,函数inner关闭了fnargs

function curry(fn) {
  const args = []
  return function inner(arg) {
    if(args.length === fn.length) return fn(...args)
    args.push(arg)
    return inner
  }
}

function add(a, b) {
  return a + b
}

const curriedAdd = curry(add)
console.log(curriedAdd(2)(3)()) // 5

事件驱动编程

在下面的代码中,函数onClick关闭了变量BACKGROUND_COLOR

const $ = document.querySelector.bind(document)
const BACKGROUND\_COLOR = 'rgba(200, 200, 242, 1)'

function onClick() {
  $('body').style.background = BACKGROUND\_COLOR
}

$('button').addEventListener('click', onClick)
<button>设置背景颜色</button>

模块化

在以下示例中,所有实现细节都隐藏在立即执行的函数表达式中。函数ticktoString关闭了它们需要的私有状态和函数以完成工作。闭包使我们能够模块化和封装我们的代码。

let namespace = {};

(function foo(n) {
  let numbers = []

  function format(n) {
    return Math.trunc(n)
  }

  function tick() {
    numbers.push(Math.random() * 100)
  }

  function toString() {
    return numbers.map(format)
  }

  n.counter = {
    tick,
    toString
  }
}(namespace))

const counter = namespace.counter
counter.tick()
counter.tick()
console.log(counter.toString())

示例

示例1

这个示例表明局部变量在闭包中没有被复制:闭包保持对原始变量本身的引用。就好像栈帧在内存中即使在外部函数退出后仍然存在一样。

function foo() {
  let x = 42
  let inner = () => console.log(x)
  x = x + 1
  return inner
}

foo()() // 输出 43

示例2

在以下代码中,三个方法logincrementupdate都关闭了相同的词法环境。

每次调用createObject时,都会创建一个新的执行上下文(栈帧),并创建一个全新的变量x,以及一组全新的函数(log等),这些函数关闭了新的变量。

function createObject() {
  let x = 42;
  return {
    log() { console.log(x) },
    increment() { x++ },
    update(value) { x = value }
  }
}

const o = createObject()
o.increment()
o.log() // 43
o.update(5)
o.log() // 5
const p = createObject()
p.log() // 42

示例3

如果你使用var声明的变量,请确保你了解你正在关闭的变量。使用var声明的变量是提升的。由于引入了letconst,这在现代JavaScript中不再是一个大问题。

在以下代码中,每次循环都会创建一个名为inner的新函数,它关闭了i。但由于var i在循环外被提升,所有这些内部函数都关闭了相同的变量,这意味着最终打印的i(3)的值,会打印三次。

function foo() {
  var result = []
  for (var i = 0; i < 3; i++) {
    result.push(function inner() { console.log(i) } )
  }

  return result
}

const result = foo()
// 下面将会打印出 `3`,三次...
for (var i = 0; i < 3; i++) {
  result[i]() 
}

最后一点:

  • 每当在JavaScript中声明一个函数时,都会创建一个闭包。
  • 从另一个函数返回function是闭包的经典示例,因为外部函数内部的 state 隐式地可用于返回的 inner function,即使在外部函数执行完成后也是如此。
  • 每当你在函数内部使用eval()时,都会使用闭包。你eval的文本可以引用函数的局部变量,并且在非严格模式下,你甚至可以通过使用eval('var foo = …')来创建新的局部变量。
  • 当你在函数内部使用new Function(…)Function构造函数)时,它不会关闭其词法环境:而是关闭全局上下文。新函数无法引用外部函数的局部变量。
  • JavaScript中的闭包就像在函数声明点保留对作用域的引用(不是复制),这反过来又保留了对其外部作用域的引用,依此类推,一直到作用域链顶部的全局对象。
  • 当声明一个函数时,会创建一个闭包;在调用函数时,使用此闭包来配置执行上下文。
  • 每次调用函数时,都会创建一组新的局部变量。

链接

  • Douglas Crockford模拟对象私有属性和私有方法的文章,使用了闭包。
  • 如果你不小心,闭包可能会导致IE内存泄漏的精彩解释。查看这里。
  • MDN关于JavaScript闭包的文档。
  • 初学者JavaScript闭包指南。

标签:闭包,function,const,函数,JavaScript,如何,log
From: https://www.cnblogs.com/xiaomandujia/p/17744449.html

相关文章

  • 如何将Git仓库还原到之前的提交?
    内容来自DOChttps://q.houxu6.top/?s=如何将Git仓库还原到之前的提交?如何将当前状态还原到某个提交的快照?如果我执行gitlog,则我会得到以下输出:$gitlogcommita867b4af366350be2e7c21b8de9cc6504678a61b`Author:Me<[email protected]>Date:ThuNov418:59:412010-0400......
  • 如何检查一个字符串是否包含子字符串的JavaScript方法?
    内容来自DOChttps://q.houxu6.top/?s=如何检查一个字符串是否包含子字符串的JavaScript方法?通常,我会期望有一个String.contains()方法,但似乎没有这个功能。有什么合理的方式来检查这个吗?ECMAScript6引入了String.prototype.includes:conststring="foo";constsubstri......
  • Linux如何查看公网ip地址
    https://baijiahao.baidu.com/s?id=1733450331822333849&wfr=spider&for=pccurl%20http://ifconfig.io对于任何一台Linux服务器,想要让所有服务器都能够进行网络通信,那么是如何通信的呢?这个时候ip地址和网卡发挥了很大的作用,IP地址就像我们的身份证一样能够唯一识别出具体的设备......
  • intellij idea如何查看项目maven依赖关系图
    文章底部有个人公众号:热爱技术的小郑。主要分享开发知识、有兴趣的可以关注一下。为何分享?踩过的坑没必要让别人在再踩,自己复盘也能加深记忆。利己利人、所谓双赢。1、打开maven项目依赖打开后的效果图2、选择缩放可以选择1:1缩放、下方是效果图。3、查看......
  • 华为云云耀云服务器L实例评测 | 3分钟搞懂如何在华为云服务器安装Nginx并配置静态访问
    文章目录一、什么是Nginx?二、申请华为云服务器三、使用XShell连接华为云服务器并安装Nginx四、FileZilla连接服务器五、Linux下安装Nginx❇️配置80端口并关闭Linux防火墙✳️测试六、配置静态html至华为云服务器并访问⚠️在华为服务器新建路径⏰使用Filezilla上传文件至华为云服务器⚡......
  • 了解python闭包
    了解python闭包1、闭包的作用当函数调用结束之后,函数内定义的变量都销毁了,但是有时候我们需要函数内的这个变量,每次在这个变量的基础上完成一系列的操作。(即在调用完函数之后,仍然想使用函数内部的变量)那么我们可以使用闭包来解决这个需求。闭包的定义:在函数嵌套的前提下,内部......
  • 【鱼授之以渔】如何安装和配置MySQL数据库?
    鱼弦:内容合伙人、新星导师、全栈领域创作新星创作者、51CTO(Top红人+专家博主)、github开源爱好者(go-zero源码二次开发、游戏后端架构https://github.com/Peakchen)如何安装和配置MySQL数据库?以下是安装和配置MySQL数据库的一般步骤:下载MySQL:访问MySQL官方网站,下载适用于您的操作......
  • 如何使用『Nginx』配置后端『HTTPS』协议访问
    前言本篇博客主要讲解如何使用Nginx部署后端应用接口SSL证书,从而实现HTTPS协议访问接口(本文使用公网IP部署,读者可以自行替换为域名)申请证书须知请在您的云服务平台申请SSL证书,一般来说证书期限为一年,到期限需要再次申请博主这里使用的是阿里云云服务器,阿里云每年可以免费......
  • 如何通过毕业论文评审?
    最近焦虑如何写好一篇毕业论文,拿出两位学长的毕业论文进行一番对比,得出一个有趣的结论。首先介绍下两位学长的基本信息。A学长:开发能力中等,文字描述能力较差,论文工作量大,一次通过;B学长:开发能力优秀,文字描述能力不错,论文工作量相对较少,二审。结论:毕业论文有毕业论文的特点,对于......
  • 我如何使用工具学习网络技术?
    在学习中使用哪些工具“工欲善其事必先利其器。”在网络技术的学习过程中,往往需要使用一些工具,来辅助我们学习,以此将抽象的技术通过具体的方式来表现出来,便于加深网络理论的印象。今天,我将列举我在学习过程中使用过的工具。以网络仿真工具为例,建议初学者选择一个厂商的软件作为......