setTimeout会把回调放到代码执行完毕后,再做处理,so
所以就不难理解为什么第一张图会打印5个5了,因为不管setTimeout再快,也得在程序执行完毕后打印,而程序执行完毕时,for已经循环了5次了.....
所以呢,还有个问题,在setTimeout把方法放到任务队列之后,会不会保留上下文环境?答案是肯定的。
- 无论函数是在哪里调用,也无论函数是如何调用的,其确定的词法作用域永远都是在函数被声明的时候确定下来的。
- 当定义一个函数时,它实际上保存一个作用域链。
- 当调用这个函数时,它创建一个新的对象来储存它的参数或局部变量,并将这个对象添加保存至那个作用域链上,同时创建一个新的更长的表示函数调用作用域的“链”。
- 对于嵌套函数来说,情况又有所变化:每次调用外部函数的时候,内部函数又会重新定义一遍。因为每次调用外部函数的时候,作用域链都是不同的。内部函数在每次定义的时候都要微妙的差别---在每次调用外部函数时,内部函数的代码都是相同的,而且关联这段代码的作用域链也不相同。
- 当使用var声明变量的时候,没有块级作用域,因此每次循环中声明的setTimeout的回调,是去更高一级的作用域中寻找变量i。
- 当使用let声明变量的时候,有独立的块级作用域,因此声明setTimeout回调的时候,是在本次循环的块级作用域{}中寻找变量i。
如上图,在定义循环时,同时定义n,在回调中修改n的值以修改回调执行结果,如果是var声明的,则第一次执行的结果会对后面两次造成影响,而使用let声明n,则三次循环完全互不干扰,可知在使用let定义n时,循环定义的三个回调函数的作用域链被各自分隔开。var声明的i和let声明的i在作用域链上的位置不同。
最后总结一下:
不管是var也好,let也好,循环定义的若干个setTimeout的回调都属于同一个上级作用域,但是回调之间是独立存在的,不会相互影响;不一样的是:var和let在各自作用域链上的位置不同,var没有块级作用域,所以var所在作用域属于上级作用域;let有块级作用域,所以在for循环的时候,i的作用域被限制在了{}代码块之间,分别在三个{}中定义setTimeout回调的时候,i在作用域链中的位置分属三个回调所在的块级作用域,互不干扰。
PS:只有在调用到块级作用域中变量的时候,才会将块级作用域添加到作用域链中
PS2:三个回调并非在一个块级作用域下被依次声明,而是将块级作用域循环了三次,每次分别声明了自己作用域下的方法。
PS3:知道为什么就知道怎么影响这个块级作用域啦,引用传递可破,可知作用域变量遵循普通变量传递原则。
PS4:作用域链的非自己部分在函数对象被建立(函数声明、函数表达式)的时候建立,而不需要等到执行,这部分作用域链是静态的;当函数执行时,建立一个自己当次执行的作用域,然后把这个作用域与前面的作用域链关联起来