注册

老生常谈的JavaScript闭包

老生常谈的闭包


很多观点参考于《你不知道的JavaScript》、《JavaScript忍者秘籍》,私信我,可发电子书呀。进入正文:


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


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



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

闭包与词法作用域


在《你不知道的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()

作用域分析


image


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



  • 其中全局变量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值。



小结



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

作者:limbo
链接:https://juejin.cn/post/6989148728649072653

0 个评论

要回复文章请先登录注册