js闭包(闭包详解和使用场景说明)

前端这点事 86 0

也许你并不知道闭包是什么,但是你的代码中到处都有闭包的影子!也许你觉得闭包平时用不到,但是每次面试你都得去准备这个方面内容!也许你不觉得这个功能有什么用,但是很多框架的功能都是基于闭包去实现的!

下面我们将目光聚焦到以下几个问题,来理解一下闭包:

  • 词法作用域
  • 闭包的形成
  • 闭包的概念
  • 闭包的常见形式
  • 闭包的作用

闭包与词法作用域

在《你不知道的JavaScript》书有一句原话:闭包是基于词法作用域书写代码所产生的自然结果。所以在知道闭包之前,得先理解什么是词法作用域。以前的文章有过介绍: 理解JavaScript的词法作用域(可以稍微地翻看一下)。如果不想看,也没关系。 接下来我们分析一段代码,去理解什么是词法作用域,以及闭包的形成。

var a = 100

function foo() {
  var a = 10 
  function test() {
    var b = 9
    console.log(a + b)
  }
  return test
}

var func = foo()
func()
复制代码

作用域分析

老生常谈的JavaScript闭包

 

上图我们清晰地反应了作用域嵌套。

  • 其中全局变量func就是test函数的引用。
  • test定义虽然定义在foo包裹的作用域内,但运行在全局作用域内。
  • test里面执行a + b的时候,a变量的值等于10而不是等于100。说明变量a的查找跟test在哪里执行没有关系。
  • 这就是词法作用域,在书写阶段作用域嵌套就已经确定好了,跟函数在哪里运行没有关系。

闭包的形成

对上述代码进行作用域分析之后我们不难得出一个结论:test函数不管在哪里执行,它永远都属于foo作用域下的一个标识符,所以test永远对foo作用域持有访问的权限

正常情况下,foo函数执行完毕后,js的垃圾回收机制就会对foo函数作用域进行销毁。但是由于test函数对foo的作用域持有引用,所以只要程序还在运行中,你就永远不会知道test会在哪里被调用。 每当test要执行的时候,都会去访问foo作用域下的a变量。所以垃圾回收机制在foo执行完毕之后,不会对foo作用域进行销毁。这就形成了闭包

闭包的常见形式

以上我们分析了,闭包是怎么形成的。也分析了一段典型的闭包代码。前言中我们有说过一句话也许你并不知道闭包是什么,但是你的代码中到处都有闭包的影子。接下来我们分析一下,闭包的常见形式。

以下代码就算你不了解闭包,你也写过。类似的:

computed: {
  add() {
    return function(num) {
      return num + 1
    }
  }
},
复制代码

vue中可接受参数的计算属性

function init() {
    $(.name).click(function handleClickBtn() {
        alert('click btn')
    })
}
复制代码

初始化函数中使用jq绑定事件

$.ajax({url:"/api/getName",success:function(result){
    console.log(result)
}});
复制代码

ajax请求数据

window.addEventListener('click', function() {
        
})
复制代码

原生注册事件

可以发现当把函数当做值传递的时候,就会形成闭包。《你不知道的JavaScript》给出了总结: 如果将函数(访问它们各自的词法作用域)当作第一 级的值类型并到处传递,你就会看到闭包在这些函数中的应用。在定时器、事件监听器、 Ajax 请求、跨窗口通信、Web Workers 或者任何其他的异步(或者同步)任务中,只要使 用了回调函数,实际上就是在使用闭包!

闭包的作用

闭包的一大用处是回调函数。还有一个作用是封装私变量。在《JavaScript忍者秘籍》有专门章节对其进行详细讲解。 下面我们看看闭包如何封装私有变量

私有变量封装

场景:有一个函数foo, 统计其被调用的次数

var num = 0

function foo() {
  // 次数加一
  num = num + 1
  return num
}

foo()
foo()
foo()

console.log(num)
复制代码

全局变量num来统计foo调用次数,最大的坏处在于,你不知道程序运行到什么时候,num的值被篡改。如果能够将num变量私有化,外部不能随意更改就好了。

function baz() {
  var num = 0

  return function() {
    num++
    return num
  }
}

var foo = baz()
foo()
foo()
let fooNum = foo()

console.log(fooNum)
复制代码

通过闭包,num被私有化在baz作用域下,程序运行过程中,不能随意更改baz下的num值。

小结

  • 闭包是基于词法作用域书写代码产生的自然结果
  • 闭包经常出现在我们的代码里面,常见的是回调函数
  • 闭包作用很多,回调函数,私有化变量等等

标签: JavaScript

发表评论 (已有0条评论)

还木有评论哦,快来抢沙发吧~