前言
JavaScript 中的闭包指的是一个函数以及其捆绑的周边环境状态的引用的组合。闭包可以让开发者从内部函数访问外部函数的作用域,即使外部函数已经执行完毕
今天我们通过JavaScript执行机制来聊聊闭包
正文
首先来分析这段代码的执行机制,这段代码我们主要了解作用域链,以及函数的外部作用域outer
outer是根据词法作用域规则生成的
function bar() {
console.log(myName);
}
function foo() {
var myName = 'Tom'
bar()
}
var myName = 'Jerry'
foo()
在上述代码中,执行 foo
函数时,在 bar
函数内部打印 myName
时,会查找变量 myName
。
由于 bar
函数内部没有定义 myName
,它会沿着作用域链向上查找。首先在 foo
函数的作用域中查找,foo
函数中定义了 myName
为 'Tom'
,但是 bar
函数调用时,它无法直接访问 foo
函数作用域内的 myName
。
接着继续向上查找,在全局作用域中找到了定义的 myName
为 'Jerry'
,所以最终打印的是全局作用域中的 myName
,即 'Jerry'
我们来画一下这个的执行流程
- 调用栈中创建全局上下文执行对象(变量、词法环境)里面有bar=x001指向堆里面(非原始数据类型存放地)foo myName
- myName最开始是undefined,全局预编译结束,全局执行,myName=Jerry 调用foo
- 对foo进行预编译,创建AO对象(foo的执行上下文对象)有变量词法环境,myName=undefined,foo里面代码开始执行,
- myName=Tom,foo变量环境中还有一个outer指针,值指向全局上下文执行对象,为什么指向全局?
- outer指向谁取决于foo函数所在的词法作用域在哪里(foo声明在哪里),foo在全局作用域中,所以outer指向全局上下文执行对象
- 这个指向过程就是作用域链,bar开始调用,创建bar执行上下文,入栈,有变量、词法环境,
- 变量环境中没有声明东西,但是有outer指针,指向全局上下文对象。
- 然后开始找myName,自己bar中没有找到,然后顺着outer往全局找,找到了jerry因此打印jerry
这里我们还没具体讲解闭包
接下来我们分析这段代码
function foo() {
function bar() {
var age = 18
console.log(myName);
}
var myName = 'Tom'
return bar
}
var myName = 'jerry'
var fn = foo()
fn()
在上述代码中,执行 fn()
时,在 bar
函数内部打印 myName
,首先在 bar
函数内部查找 myName
变量,未找到。
然后沿着作用域链向上查找,在 foo
函数的作用域中找到了定义的 myName
为 'Tom'
。
而全局作用域中定义的 myName
为 'jerry'
,但由于作用域链的查找顺序,优先使用了 foo
函数作用域中的 myName
,所以最终打印的是 'Tom'
这我们同样分析一下预编译过程
这里我们就要引出闭包了,当bar
执行完了,就得被销户,然后就是foo
了,可是这里就出现了一个问题,foo
返回了一个方法bar
,bar
的outer
指向的foo
,但是foo
就被销毁了,可是在全局调用了bar
,bar是需要使用到foo
里的参数的,于是就还是销毁了foo
,但是留下了bar
需要的参数,以及他的outer
,形成了小书包,这就是闭包
闭包有许多的好处:
- 实现数据私有化和封装
通过闭包,可以将一些变量和函数隐藏在内部函数中,外部无法直接访问和修改,从而实现了一定程度的封装和数据保护。 - 保持函数执行的上下文和状态
闭包能够让函数记住其创建时的环境状态,即使外部函数已经执行完毕,内部函数仍然可以访问和操作这些状态信息。这对于实现需要保持状态的功能非常有用,比如计数器、缓存等。 - 模拟私有方法
在 JavaScript 中没有真正的私有方法,但可以利用闭包来模拟私有方法,只在特定的函数内部可访问和操作。 - 函数柯里化和偏函数应用
闭包有助于实现函数柯里化和偏函数应用,使函数的参数传递更加灵活和可定制。 - 模块模式
可以使用闭包创建模块,模块中的变量和方法只在模块内部可访问,对外只暴露必要的接口。
那么让我们分析一下这段代码
function add() {
let count = 0;
function fn() {
count++
return count;
}
return fn;
}
var res = add();
console.log(res());
console.log(res());
console.log(res());
add
函数的调用是不依赖全局变量的,封装函数 实现累加
add
函数返回了内部函数 fn
。
当执行 var res = add();
时,res
接收到了 fn
函数,并且 fn
函数形成了一个闭包,能够访问到 add
函数中的 count
变量。
第一次执行 console.log(res());
时,count
自增为 1
并返回,所以打印 1
。
第二次执行 console.log(res());
时,count
再次自增为 2
并返回,所以打印 2
。
第三次执行 console.log(res());
时,count
继续自增为 3
并返回,所以打印 3
。
总结
本文讲解了JavaScript的执行机制和JavaScript执行机制中的闭包,相信看到这里的你一定会有收获的!!!!