引言
闭包这个词对很多前端开发人员来说既熟悉又陌生,熟悉是因为很多人都用过闭包,但是用的时候不知道闭包,陌生是因为并不理解闭包,接下来这篇文章将会从多方面介绍闭包
定义
闭包是怎么定义的呢?当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数在当前词法作用域之外执行。来看一个具体例子:
function foo () {
var a = 2
function bar () {
console.log(a)
}
return bar
}
var baz = foo()
baz() //2
函数 bar 的词法作用域可以访问 foo 的内部作用域,并且 bar 在被作为返回值赋值给 baz 执行时,bar 函数在定义时的词法作用域以外的地方被调用,依然可以访问 foo 函数的内部作用域变量 a,这就是闭包
分析
现在让我们来看为什么闭包可以在定义的词法作用域外记住并且访问定义时的词法作用域的变量,想要一探究竟,先来看一个简单的例子来函数的执行过程:
function foo (a) {
console.log(a)
}
foo (a)
上面是一个简单的函数调用,以及在执行时的上下文环境,重点看执行时上下文环境,在创建 foo 函数时,会创建一个预先包含全局变量对象的作用域链,这个作用域链被保存在内部的 [[Scope]] 属性中,当调用 foo()函数时,会为函数创建一个执行环境,然后通过复制函数的 [[Scope] 属性中的对象构建起执行环境的作用域链。此后,又有一个活动对象(包含 this、arguments、a)被创建并被推入执行环境作用域链的前端,对于 foo 函数来说,其作用域链包含两个变量对象,一个时全局的变量对象,一个是局部的活动变量对象,一般来说当函数执行完成后,局部的活动变量对象会被销毁,只留全局的,但是闭包执行过程有所不同,来看具体例子:
function foo () {
var a = 2
function bar (b) {
console.log(a + b)
}
return bar
}
var baz = foo()
baz(3) //5
接下来来分析下上面闭包的执行上下环境,在一个函数内部定义的函数会将包含函数的活动对象添加到它的作用域链中,因此,bar 函数的作用域链中会包含 foo 函数的活动对象,在 bar 函数从 foo 中被返回后,它的作用域链条被初始化为全局变量和 foo 中活动对象,因此,bar 函数可以访问 foo 函数中定义的所有变量,同时 foo 函数在执行完毕后,其活动对象也不会被销毁,因为 bar 函数的作用域链仍然在引用这个活动对象。
常见问题
说到闭包相关的问题,最典型的就是变量和 this 指向这两类问题。
变量
function test () {
var result = new Array()
for (var i = 0; i < 6; i++) {
result[i] = function () {
return i
}
}
return result
}
上面的代码展示就是面试题里面经常会碰到,result 的结果从上面截图能看到,作用域中保存的 i 都是 6,这是为什么呢?因为闭包保存的是函数中的活动对象,因此它们引用的都是同一个变量,并且是变量的最后一个值,因此都是 6,那这个问题怎么解决呢?最常见的最简单肯定是将 var 换成 let,也可以像下面这样:
function test () {
var result = new Array()
for (var i = 0; i < 6; i++) {
result[i] = (function () {
return i
})()
}
return result
}
将闭包直接改成一个自执行函数,自执行函数本身是没有变量作用域的,因此会使用外层函数的变量作用域,这样也能达到我们想要的效果
this 指向
var name = “window”
var obj = {
name: “object”,
getName: function () {
return function () {
return this.name
}
}
}
console.log(obj.getName()())
上面这段 js 代码的 this.name 的返回值是 window,这是为什么呢?按照上面写到的,此匿名函数在执行过程中,它的作用域会包含三部分:自身的活动对象、getName 函数的活动对象和全局的变量对象,同时每个活动对象自动取得两个特殊的变量:this 和 arguments,但是内部函数在查找 this 时是无法直接访问外部函数的 this 变量,因此会沿着作用域链去查找全局变量中继续查找,如果想要取外部函数中的 this 取值也很简单,只需要向下面代码这样:
var name = “window”
var obj = {
name: “object”,
getName: function () {
var that = this
return function () {
return that.name
}
}
}
console.log(obj.getName()())
将 this 赋值给一个变量,内部函数是可以访问外部函数变量的,这样就解决了
总结
闭包是一个容易混淆不清的概念,这篇文章对闭包的定义、执行、常见问题做了简单的介绍,希望通过这篇能对大家理解和使用闭包有所帮助。如果有错误或不严谨的地方,欢迎批评指正,如果喜欢,欢迎点赞。