前言:闭包在前端知识体系中可大可小,在日常编程我们依然与闭包有着紧密的联系,他涉及作用域(链)、执行上下文,内存管理等知识,所以js篇就用实例来讲讲闭包知识。
先上菜单【闭包】知识点:
1.作用域
在JavaScript中,ES6之前只有函数作用域和全局作用域之分。 这里将略跳过不讲函数作用域和全局作用域(因为太基础了^_^)。
块级作用域和暂时性死区
ES6增加了块级作用域let 和const 来声明变量。块级作用域,顾名思义,作用域范围限制在代码块中,当然 ES6带来新特性,也同时带来了新概念,比如暂时性死区,
function foo() {
console.log(bar)
var bar = 2
}
foo() 这里会输出undefined,原因是变量 bar 在函数内进行了提升
这里用let 声明时
function foo() {
console.log(bar)
let bar = 2
}
foo() 这里会报错Uncaught ReferenceError: bar is not defined。因为使用let 或const 声明变量,会针对这个变量形成一个封闭的块级作用域,在这个块级作用域当中,如果在声明变量前访问该变量,就会报 referenceError 错误;如果在声明变量后访问,则可以正常获取变量值。
暂时性死区: 起始于函数开头,终止于相关变量声明的一行.
需要理解执行上下文的请翻看js篇第一篇 用实例带你走进this、执行上下文世界[js篇一]
函数调用栈其实很好理解,我们在执行一个函数时,如果这个函数又调用了另外一个函数,而这个「另外一个函数」也调用了「另外一个函数」,便形成了一系列的调用栈。如下代码
function foo1() {
foo2()
}
function foo2() {
foo3()
}
function foo3() {
foo4()
}
function foo4() {
console.log('foo4')
}
foo1()调用关系:foo1 → foo2 → foo3 → foo4。这个过程是 foo1 先入栈,紧接着 foo1 调用 foo2,foo2入栈,以此类推,foo3、foo4,直到 foo4 执行完 —— foo4 先出栈,foo3 再出栈,接着是 foo2 出栈,最后是 foo1 出栈。这个过程「先进后出」(「后进先出」),因此称为调用栈

我们从中都可以借助 JavaScript 引擎,清晰地看到错误堆栈信息,也就是函数调用栈关系。
闭包
函数嵌套函数时,内层函数引用了外层函数作用域下的变量,并且内层函数在全局环境下可访问,就形成了闭包。
function counter(){
var num= 1;
return function(){
return num++;
}
var getNum = counter()
getNum()这个简单的闭包例子中,
num,返回打印 num 值的匿名函数,这个函数引用了变量 num,使得外部可以通过调用 getNum 方法访问到变量 num,因此在 numGenerator 执行完毕后,即相关调用栈出栈后,变量 num 不会消失,仍然有机会被外界访问.我们知道正常情况下外界是无法访问函数内部变量的,函数执行完之后,上下文即被销毁。但是在(外层)函数中,如果我们返回了另一个函数,且这个返回的函数使用了(外层)函数内的变量,外界因而便能够通过这个返回的函数获取原(外层)函数内部的变量值。这就是闭包的基本原理
实例1
const foo = () => {
var arr = []
var i
for (i = 0; i < 10; i++) {
arr[i] = function () {
console.log(i)
}
}
return arr[0]
}
foo()()
------------------------------------答案分析---------------------------------
// 10
foo() 执行返回的是 arr[0] ,此时arr[0]函数是
function () { console.log(i)}
变量i 值为10实例2
var fn = nullconst foo = () => {
var a = 2
function innerFoo() {
console.log(a)
}
fn = innerFoo
}
const bar = () => {
fn()
}
foo()
bar()
------------------------------------答案分析---------------------------------
执行到bar()时输出 2 , 根据调用栈的知识,foo 函数执行完毕之后,其执行环境生命周期会结束,所占内存被垃圾收集器释放,上下文消失。
但是通过innerfoo函数赋值给fn,fn是全局变量,这就导致了foo的变量对象a也被保留了下来。
所以函数fn在函数bar内部执行时,依然可以访问这个被保留下来的变量对象,输出结果为 2。
实例3
var fn = nullconst foo = () => {
var a = 2
function innerFoo() {
console.log(c)
console.log(a)
}
fn = innerFoo
}
const bar = () => {
var c = 100
fn()
}
foo()
bar()
------------------------------------答案分析---------------------------------
c is not defined。
在 bar 中执行 fn() 时,fn() 已经被复制为 innerFoo,变量 c 并不在其作用域链上,
c 只是 bar 函数的内部变量
总结:
觉得不错的请给个三连哦👍,接下我会更新大家感兴趣的话题,让我们一起进步。
