1 | for (var i = 0; i < 10; i++) { |
前言
这是一道常见的面试题,一般大家都问代码输出什么,考的是var
,ES6 的let
,for
和闭包
,之后还可能会问你箭头函数
,然后再问你setTimeout
原理甚至到 Micro-Task 和 Macro-Task 我们一个一个攻破。
var let
1 | { |
var
和let
最最大区别就在这里,var
并没有作用域这一说,还能重复声明,let
只允许存在于自己的作用域,也不允许重复声明。
for
for
在实现的时候要求,每次迭代会新建运行环境记录拷贝最后迭代内容,所以每一次都是声明在一个独立的作用域,所以改用let
可以输出 1 2 3 4 5…
规范
13.7.4.7 Runtime Semantics: LabelledEvaluation
13.7.4.8 Runtime Semantics: ForBodyEvaluation( test, increment, stmt, perIterationBindings, labelSet )
13.7.4.9 Runtime Semantics: CreatePerIterationEnvironment( perIterationBindings )
闭包
1 | for (var i = 0; i < 10; i++) { |
闭包解决这个问题的具体原因就是通过闭包将变量转为参数,如果是基本类型的参数会进行值传递,但是如果是Object
类型的会通过引用传递。
闭包更常用的用法就是工厂模式的方法进行生产方法,比如 redis 存储的时候经常有一些前缀,但是不多,不同用途不同前缀,每一个独立方法或者通过参数传递变得麻烦,就可以通过闭包形式进行
1 | function generateRedisStore(prefix) { |
箭头函数
1 | this.value = "value"; |
箭头函数在别的语言可能就是个语法糖,在 JavaScript 上就不同了…他可以改变this
的指向,本来我们的function
里面也有自己的this
,但是如果使用箭头函数,就放弃了function
的this
一些坑点
由于箭头函数没有自己的
this
,所以如果调用call
或者apply
只能传参数而不能绑定this
箭头函数不能作为
constructor
,如果和new
一起使用会报错。箭头函数没有
prototype
属性
setTimeout
setTimeout
就不得不先讲 EventLoop,众所周知 JavaScript 是单线程异步,有异步,实际上一定有多条线程,只是,我们接触不到而已,而浏览器和 Node.js 环境又有一点不同,这里就不细讲了,简单讲讲关于 Micro-Task 和 Macro-Task
为了清晰,我们先区分三个队列,Main-Task,Micro-Task 和 Macro-Task
- Main-Task 就是一开始我们代码运行的 Task,这个 Task 如果没有运行完,其他 Task 无法参与。
- Micro-Task 是
Promise
为首的 Task,浏览器还多一个 MutationObserver。 - Macro-Task 比如
setTimeout
,setInterval
,setImmediate
,I/O
,UI rendering
,<script>
都是。
简单来讲 EventLoop 就是不断的执行一个流程,从 Main-Task 开始,执行完了到 Micro-Task 然后 Macro-Task,所以其实 setTimeout
在到 Macro-Task 如果想要执行,还是得等到前面两个 Task 完全清空,才轮到你。
所以无论如何,都会在 Main-Task 之后执行。
EventLoop 更详细的解答可以参考Eventloop 不可怕,可怕的是遇上 Promise,其实一旦理解了Promise.then
是增加到 Micro-Task 就基本能理解其他了。
注意async/await
在不同版本上有不同实现,V8/Node.js 12 之前需要经过三轮的 Micro-Task 才完成await
。