闭包
首先来看一个问题
function books() {
var book = '书包里有一本书'
}
console.log(book)
这个执行显然是出错的,为什么呢,这里就牵扯到了闭包,下面会用执行上下文来解释
执行上下文
每当运行代码时就会生成执行上下文,决定了代码的作用域,js的执行环境分为三种:
- 全局环境
- 函数环境
- eval环境(慎用)
当初次运行代码,会进入全局环境
例如:
function a(){
b()
function b(){
c()
function c(){
console.log('c')
}
}
}
a()
在上述代码中首先会进入全局环境,然后遇到a()时会进入函数a执行上下文,再然后遇到b(),则会进入函数b()的函数b执行上下文,最后遇到c(),进入函数c的执行上下文,这个过程是一个入栈的过程。这就像把薯片一片一片放进薯片罐,放的第一片在最底下,之后放的会叠在上面,拿的时候从最上面开始拿。
![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bei95e7a-1597033028120)(C:\Users\93221\AppData\Roaming\Typora\typora-user-images\image-20200810102444337.png)]](/https://i-blog.csdnimg.cn/blog_migrate/9e45dcc621873aa09273cf3ec8be40a9.png#pic_center)
当c函数执行完了以后会从函数c执行上下文开始出栈,然后一次是b、a、全局,然后代码就可以继续往下执行了,要注意的是js是单线程的形式
这样就可以解释第一个例子,第一个例子book显示未定义的原因在于没有执行函数books,也就是说没有进入函数books的作用域,当然就没有定义book这个变量了呀。
执行上下文的两大步骤
创建阶段
当我们调用了函数但还没有执行的时候,会创建:
- 作用域链(当前变量对象+所有父级变量对象)
- 变量对象(参数、变量、函数声明)
- this指针
执行阶段
当进入了执行阶段,就会进行具体的操作,比如变量赋值、函数引用等
下面就来解释一下,什么是作用域链和闭包
作用域链
下面的解释以段代码为例:
function books(){
var book='书包里有书'
return function(){
console.log(book)
}
}
var bag=books()
bag()
按照之前说的执行上下文,会有一下过程
- 1、进入全局执行上下文
全局执行上下文=[作用域链:[全局变量对象],[变量对象:book,bag]]
这就像把第一片全局变量对象的原味薯片放进薯片罐,其余的变量对象跟着创建,有books指向函数books,还有变量bag指向books函数,之后执行bag函数,因为它是指向books的所以会进入books执行上下文
- 2、books执行上下文
books执行上下文=[作用域链:[books变量对象+全局变量对象],[变量对象book]]
当前作用域链肯定会是自己作用域的变量对象,然后再放之前的,例子中就是全局变量对象。这就像在原本有原味薯片的薯片罐里放一片烧烤味的原味薯片,然后我们就会遇到匿名函数return function(){},这时候我们就为这个匿名函数创建执行上写文,和作用域链
- 3、匿名函数执行上下文
按道理说执行上下文应该如下所示:
匿名函数执行上下文=[作用域链:[匿名函数变量对象+books变量对象+全局变量对象],[变量对象:]]
但是在这个匿名函数里,这个函数没有自己的变量对象,所以它的执行上下文和books的执行上下文是一致的。也就是说又放了一片烧烤味的薯片进薯片罐
最顶层的薯片包含着之前薯片的所有味道,只要尝试最上面的薯片就可以知道底层薯片的味道,而且最顶层的薯片最新鲜,作用域链也一层链着一层,每新建一层都会包含前一层,最上层的薯片是最优先。
那么现在我们就可以吃薯片了,例子中我们要输出book这个变量,我们就在作用域中进行寻找,因为匿名函数变量对象没有实质内容,所以就找下一层,也就是books变量对象,books函数中有book这个变量,那么就可以输出了
秒杀一半程序员的经典题:
for (var i = 0; i < 5; i++) {
setTimeout(function () {
console.log(i++);
}, 4000)
}
console.log(i)
js是单线程的,如果每次都要执行完一件事再执行下一个,那么会很慢,所以就出现了任务队列。setTimeout函数中就算时间数为0也不会放入执行栈,而是会放进任务队列,等到执行栈执行完,才会根据时间间隔来执行任务队列中的代码。
所以在上述例子中会先在任务队列中
任务队列:
[外链图片转存
这里有计算结果,就像有五片烧烤味薯片在工厂中加工还没有出炉,在执行栈中有全局执行上下文,也就是还有打印输出i的任务,for循环中没有其他任务,所以会先循环5遍,然后打印一个5
当打印完成后,执行栈就没有别的任务了,那么就会执行任务队列中的任务,这5个i++就会开始按照间隔时间从小到大依次开始执行,因此会一次打印,5,6,7,8,9,也就是5片烧烤味的薯片就可以放入薯片罐。
这里既是匿名函数是浏览器单独进行处理的,但是作用域链依然是不变的,依旧可以访问到变量i,这就是闭包的原理,就算这些是烧烤味薯片,但是这依旧是在原味的基础上制作的。
始执行,因此会一次打印,5,6,7,8,9,也就是5片烧烤味的薯片就可以放入薯片罐。
这里既是匿名函数是浏览器单独进行处理的,但是作用域链依然是不变的,依旧可以访问到变量i,这就是闭包的原理,就算这些是烧烤味薯片,但是这依旧是在原味的基础上制作的。
像这样把函数作为值,放入队列,然后规定时刻进行回调执行的就是创建了一个闭包,也就是常说的回调函数。因为作用域链的关系,哪怕是回调函数内部一样可以访问作用域链的里面的变量对象。
本文深入解析JavaScript中的闭包概念与执行上下文的工作原理,通过实例讲解如何利用作用域链实现变量的持久访问。


被折叠的 条评论
为什么被折叠?



