最纯粹的闭包理解

闭包是js中非常重要又难以掌握的知识点,尤其以理解了简单的闭包之后,各种框架里的语法都会对闭包大量应用。万丈高楼从地起,我们先了解一下简单的闭包吧

理解闭包

看一段简单的代码

php 复制代码
function foo () {
    var name = 'foo'
    function bar () {
        console.log('bar', name)
    }
    return bar
}
var fn = foo()
fn() // bar foo

在这个例子中,bar()显然是可以被正确执行的,但是有一个很特殊的现象,bar是定义在foo函数的作用域里的函数,但是执行的地方却不是在foo函数的作用域里,也就是说,它在自己定义的词法作用域以外的地方执行了 。foo()执行后,通常会期待foo()的整个内部作用域被摧毁,而闭包的神奇之处就是阻止了这个过程,原因是bar()在引用着foo()的作用域

拜bar()声明的位置所赐,它拥有涵盖foo()作用域的闭包,使得foo()的作用域可以一直保存。

什么叫闭包呢?bar()依然持有对该作用域的引用,这个引用就是闭包。也就是说,如果函数在定义时的词法作用域以外的地方被调用,就产生了闭包闭包使得这个函数引用的词法作用域一直存在,这就是闭包的定义。

闭包的原理

在程序中,变量存放的位置有全局空间(堆结构)和局部空间(栈结构)。

在JavaScript中,函数的变量一般都存储在局部空间,但是如果被闭包引用的变量会存储在全局空间,也正是因为如此才能在函数执行完毕之后才能使得变量被一直使用。因此在JavaScript中无论是存储在全局空间还是局部空间的变量,声明方式都是一样的,let a = 1,因此要理解闭包,避免在声明变量的时候造成内存泄露

闭包的应用

  1. 对函数类型的值进行传递,当函数在别处调用时,产生了闭包
scss 复制代码
function foo(){
  var a = 2
  function baz(){
    console.log(a);
  }
  bar(baz)
}

function bar(fn){
  fn() //妈妈快看呀,这就是闭包!
}

foo()和bar()都是定义在全局作用域下,baz()是定义在foo()的作用域下,当执行bar()时,baz()通过值传递的形式将其传到了bar的作用域里并调用,符合闭包的定义:函数在定义时的作用域以外的地方被调用,因此这里发生了闭包,读到这里,是不是觉得自己平时大多数时间都在使用闭包而不自知。

javascript 复制代码
var fn

function foo(){
  var a = 2
  function baz(){
    console.log(a);
  }
  fn = baz //将baz分配个全局变量
}

function bar(){
  fn() //妈妈快看呀,这就是闭包!
}

间接传递函数也是闭包。无论通过何种手段将内部函数传递到所在的词法作用域以外,他都会有对原始定义作用域的引用,无论在何处执行这个函数都会使用闭包

  1. 计时器

前面的代码或许有些许死板,但接下来的代码和我们的日常生活息息相关

javascript 复制代码
function wait(message){

  setTimeout(function timer(){
    console.log('message');
  },1000)
}

wait('hello,closure')

在这里,timer()作为参数,将其传递给setTimeout(),此时timer是定义在wait()函数里面,但是真实调用的地方却是在这个作用域以外,很明显,这是一个闭包,因此timer会抱有wait()作用域的闭包。

深入到引擎的内部原理中,内置的工具函数setTimeout()持有的参数函数的引用,可能叫fn或者func,在setTimeout的作用域中会调用这个函数,而这个函数在这个过程中作用域保持完整。

与setTimeout类似的,许多内置的函数例如事件监听器,Ajax请求,跨窗口通信,Web Workers或者其他的异步(或者同步)任务中,只要使用了回调函数,就是在使用闭包,因为这里都涉及到将函数作为参数传递到另一个作用域中

3.循环和闭包

css 复制代码
for(var i = 0; i <= 5; i++){
  setTimeout(() => {
    console.log(i);
  },i。*1000)
}

这段代码会以每秒一次的频率输出五次6,这是因为setTimeout作为宏任务,会在for循环结束时js引擎才会去执行,然后通过rhs查找i,找到的是全局的i,此时的i是6。没有出现每隔1秒依次输出1,2,3,4,5的原因正是因为没有每次循环时没有形成闭包,没有记住之前的作用域。

scss 复制代码
for(var i = 0; i <= 5; i++){
  function fn(){
    let j = i
    setTimeout(() => {
      console.log(j);
    },i*100)
  }
  fn()
}

只需要通过闭包创建一个会被记住的作用域即可。

同样我们也知道,这样写的代码也是有一样的效果。

css 复制代码
for(let i = 0; i <= 5; i++){
  setTimeout(() => {
    console.log(i);
  },i*100)
}

let本质上是将一个块转化成一个可以被关闭的作用域,也是利用了闭包的性质

总结

闭包的本质就是函数在定义的作用域以外的地方被调用了,这个函数会引用着之前的作用域。也正是因为一直被引用着,会导致没有被垃圾清理器清除导致内存越占越大,因此需要将这个函数赋值为null,才能避免内存泄露的问题

本文是参考《你不知道的JavaScript》,相比于网上的其他文章,个人感觉并并没有写得太多太复杂,仅仅解释了最简单的闭包应用,个人认为有一种回归最原始的闭包的定义,让每一个用过js的人都可以理解,希望可以帮助你开始理解闭包,理解闭包的妙用。

相关推荐
Cyclo-1 小时前
PDFJS 在React中的引入 使用组件打开文件流PDF
前端·react.js·pdf
椒盐螺丝钉3 小时前
Vue Router应用:组件跳转
前端·javascript·vue.js
顾安r3 小时前
11.20 开源APP
服务器·前端·javascript·python·css3
敲敲了个代码4 小时前
CSS 像素≠物理像素:0.5px 效果的核心密码是什么?
前端·javascript·css·学习·面试
少云清4 小时前
【软件测试】5_基础知识 _CSS
前端·css·tensorflow
倔强青铜三4 小时前
AI编程革命:React + shadcn/ui 将终结前端框架之战
前端·人工智能·ai编程
二川bro5 小时前
第57节:Three.js企业级应用架构
开发语言·javascript·架构
天外飞雨道沧桑5 小时前
前端开发 Cursor MCP 提效工具配置
前端·vscode·ai编程·开发工具·cursor
朱哈哈O_o5 小时前
前端通用包的作用——jquery篇
前端
葡萄城技术团队5 小时前
纯前端驱动:在线 Excel 工具的技术革新与实践方案
前端·excel