关于闭包以及作用域的一点理解

近期,为了准备面试,赶紧把JS的基础恶补了下,虽然《你不知道的JS》这本书挺好,但外国人的讲解方式多少让我第一次看时理解模糊,无意间在B站看到up主:小马哥_老师的教程,感觉理解更清晰了,强烈安利。

话不多说,开始吧!

首先,JS里有全局作用域和函数作用域,当嵌套作用域内下要查找某变量时,都是一层层往外找的,如下图(fn2 -> fn -> 全局),记住这个规则,就知道闭包是什么回事了。

关于闭包以及作用域的一点理解
我理解的闭包:
window全局作用域下,我想要访问fn中的某个变量,正常手段达不到目的;于是,我在fn内安插一个内线fn2;我站在全局调用fn,让fn2做我想做的事并返回结果,我在全局定义一个变量去接收一下,于是我的目的就达成了!总之就是,fn被我(全局)用fn2给方了。

贴个闭包存在的3个非官方条件:

  1. 函数嵌套
  2. 访问所在作用域
  3. 在作用域外被调用

再来看下面的例1:
关于闭包以及作用域的一点理解
首先注意图中标出的作用域嵌套关系
其次,注意bar是一个装着一群函数的数组
再分享一句让人醍醐灌顶的话 作用域的静态的,但执行环境栈是动态的
对于这句话的理解,可以去看看小马哥的****,真的超清晰,他用浏览器自带的调试工具为你展示,每一行代码执行后,变量以及this的指向变化,清清楚楚!!!(这里贴个结论:作用域只知道它有哪些变量,但不知道它们的内容;而执行环境栈会跟着代码的执行不断更新变量的内容)

综合以上三句话,可以得出for循环内i确实从0变成了10,但foo作用域只能有一个i,故i的输出只能是不再符合循环条件的最后状态10。所以,没得到你想象中的0-9是不是很失望,再看看下面的例2:
关于闭包以及作用域的一点理解
这里,想了一个办法来“套住”i,就是加一层作用域,熟悉es6的应该知道let的块级作用域,增加一层作用域真的是让循环看似拥有一个“动态作用域”的好办法!

其实,在例1中,如果在循环内部console.log(i)是能得到0-9的反馈的,我们在外部获取不到,因为我们没有任何措施去保留住这个中间的过程;而例2中,增加一层作用域,就能让底下的“小的们”帮它完成这事!同时注意:赋给arr[i]的是一个IIFE(立即执行函数),所以循环每执行一次,都会生成一个新的作用域去跟踪这个i,而且是通过函数传参的形式记录i当时的值,于是再执行console.log(bar[3]()),就能得到我们想要的数字3了。

建议自己敲敲,看看不同位置的i值都是多少。再试试,不用IIFE,保留传参但不让函数立即执行,看看是啥效果!你会发现只多包一层函数,不立马执行,根本锁不住循环里的值,所以我们要做的:不是多加一层函数,而是多加一层立即执行函数,来生成作用域,锁(套住/克隆)住i的某个瞬间。

然后可以拿下面这个例3来检查一下,你的思维是否被掰正了。这是一个首次检验的函数(姑且叫它函数吧!),左右两边不一样!!!
关于闭包以及作用域的一点理解
通过这个例子,引出闭包函数的一个问题:
使用闭包,相当于一直在调用一个函数,而它所产生的临时变量,都被存在内存中,所以用闭包时要慎重,及时销毁,不然期间产生的临时变量会一直存着,对内存消耗很大,影响页面性能。

闭包函数的作用呢,其一是,访问函数内部变量;二是,为函数创建私有属性和方法。具体应用,还是要到实际环境中去找思路。

小马哥教程中,可以自己试着敲敲简易的缓存机制。比如,数组求和,对于同一个数组,除第一次时真被算出来的外,以后的都是从缓存中直接查出来的。