一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情。
1.如何产生闭包
当一个嵌套的内部(子)函数引用了嵌套的外部(父)函数的变量(函数)时,就产生了闭包
例如:以下代码片段中,fn2内部函数没有引用外部的变量b。
function fn1() {
var a = 2
var b = 'abc'
function fn2() {
console.log(a)
}
}
fn1()
2.闭包是什么
理解一:闭包是嵌套的内部函数
理解二:内部函数中包含被引用变量(函数)的对象
注意:闭包存在于嵌套的内部函数中
3.产生闭包的条件
- 函数嵌套
- 内部函数引用外部函数的数据
- 执行外部函数,执行函数定义就会产生闭包,不需要调用内部函数
4.常见的闭包(闭包指的是一个函数和它周围状态的引用捆绑在一起的组合)
- 将函数作为另一个函数的返回值
function test() {
const a =1
return function () {
console.log('a',a)
}
}
const fn = test()
const a = 2
fn()
执行test的时候(test())就会返回return后面这个函数,将返回值保存在fn里面,执行fn的时候(fn()),这个时候就会返回function。
输出a的值是1。函数中打印的变量会在函数定义的地方去向上一层查找它的值。
- 将函数作为实参传递给另一个函数调用
function test(fn) {
const a = 1
fn()
}
const a =2
function fn() {
console.log('a',a)
}
test(fn)
以上代码中,test(fn)将fn作为参数调用test函数,所以执行fn(),输出a=2,在定义fn函数的上级作用域中查找变量的值。
5.从作用域的角度理解闭包
闭包产生的本质是当前作用域中存在指向父级作用域的引用
function foo() {
var myName = "极客时间"
let test1 = 1
const test2 = 2
var innerBar = {
getName: function () {
console.log(test1)
return myName
},
setName: function (newName) {
myName = newName
}
}
return innerBar //该语句终止函数foo的执行,并返回innerBar给foo函数的调用者
}
var bar = foo()
bar.setName("极客邦")
bar.getName()
console.log(bar.getName())
当执行到return innerBar的时候,该语句代表终止当前的foo函数,将innerBar返回给foo函数的调用者。此时的调用栈如下(通过var声明或function(){}声明的变量存在变量环境、通过let const with() try-catch创建的变量存在词法环境):
当执行到return innerBar的时候调用栈是什么样
由代码可知innerBar是一个对象,包含了getName和setName两个方法,这两个方法都是在foo函数内部定义的,这两个方法使用了myName和test1这两个变量。
根据词法作用域的规则,内部函数getName和setName总是可以访问它们的外部函数foo中的变量,所以当innerBar对象返回给全局变量bar的时候{var bar = foo(),bar即函数的调用者},虽然此时foo函数以及执行完毕,但是getName和setName函数还是可以使用foo函数中的变量myName和test1。所以当foo执行完毕后,调用栈变成了:
foo执行完毕后,虽然它的执行上下文被弹出栈中,但是setName与getName需要用到的两个变量还在栈中,相当于一个背包,无论在哪里调用了setName和getName,她们都会从这个背包中获取变量,除了这两个其他是无法访问的,这个包可以称为foo函数的闭包。
6. 闭包的使用
那闭包是如何使用的,当执行到bar.setName方法中的myName = "极客邦"的时候,JavaScript会沿着当前setName的执行上下文->foo的闭包->全局执行上下文的顺序来查找myName变量。此时调用栈如下图
当然getName也是从foo闭包中获取。
从开发者工具中也能查看到闭包的情况
Global代表全局执行上下文,Scope中体现的就是作用域链,从上到下。
结论:可以给闭包下一个定义了。在JavaScript中,根据词法作用域的规则,内部函数总是可以访问外部函数中声明的变量,当通过调用一个外部函数返回内部函数后,即使外部函数执行完毕,但是内部函数引用外部函数的变量依然保存在内存中,我们把这些变量的集合称为闭包,比如外部函数是foo函数,那这些变量的集合就称为foo函数的闭包。