由于JS是单线程的,同一时间内它只能做一件事情,多个任务执行就需要排队。

    那在使用JS的时候可不可以在做一件事情的同时去处理另一件事呢?

    是可以的。我们可以通过异步代码(比如:setTimeout)去执行新的任务。

    那是否是使用了异步代码就可以在同步任务(主线程上执行的代码)进行的同时一起并行执行呢?

    并不可以。因为JS的执行机制是这样的:

    • 先执行主线程上的同步任务;
    • 异步任务放入任务队列中;
    • 一旦主线程上的所有同步任务都执行完后,系统就会依次读取任务队列中的异步任务,于是被读取的异步任务结束等待状态,系统将异步任务放入主线程中开始执行,执行完毕后继续去任务队列中读取下一个任务,直到任务队列中的任务全部执行完毕,代码结束。像这种主线程不断重复获取任务、执行任务、再获取、再执行的过程,被称为 事件循环

    可以通过这段代码来感受一下这个过程:(使用chrome控制台操作)

    1. function fn() {
    2. let lastTime = new Date()
    3. let newTime = new Date()
    4. while(newTime - lastTime < 5000){
    5. newTime = new Date()
    6. }
    7. console.log('已经过了五秒了!')
    8. }
    1. # 控制台执行
    2. fn()
    3. setTimeout(() => {console.log('异步代码已执行!')},1000)
    4. fn()

    上面代码的执行结果顺序是这样的,第一个fn()执行,五秒后输出信息,第二个fn()执行,五秒后输出信息,再过1秒后输出异步代码已执行!。结果就是11秒后才输出异步代码中的信息,通过这个例子可以清晰的了解到,异步代码总要等到同步代码执行完后才会被执行,这就是JS的执行机制。

    了解完JS的执行机制,我们再来看看作用域对代码执行的影响。

    问题来了,下面代码的执行结果是什么?

    1. // 代码1
    2. let i = 0
    3. for(i = 0; i < 6; i++){
    4. setTimeout(()=>{
    5. console.log(i)
    6. },0)
    7. }
    1. // 代码2
    2. for(let i = 0; i < 6; i++){
    3. setTimeout(() => {
    4. console.log(i)
    5. },0)
    6. }

    代码1结果:6个6

    代码2结果:0,1,2,3,4,5

    解读:代码1中的i是一个全局变量,作用域是全局。整个for除了setTimeout都属同步代码,他们会在执行栈中直接执行完毕,执行完后再开始执行setTimeout中的函数,而此时i的值已经变为6了,所以for循环中创建的6个异步代码都输出同一个i的值。

    那为什么代码2中只是将let放入for中声明并定义i就能让输出结果为0,1,2,3,4,5呢?因为JS会在for和let一起使用的时候会多加东西,会在每次循环开始的时候创建一个看起来一样的i。换一种理解方式就是,通过let为每次的循环内的代码创建了一个独立的作用域,这个作用域中的i就是每次循环i的值,而setTimeout执行的时候根据作用域规则中的「就近原则」会去寻找和自己同一个作用域的变量i,于是最终的输出结果就是0,1,2,3,4,5了。

    由于作用域的原因,我们可以实现如代码2中的变量和异步代码输出同步结果的情况。除了像代码2中的let使用方式还有哪些方式可以实现同样的效果呢?tips:通过作用域下手(js的作用域有全局作用域、函数作用域、ES6新增块级作用域)。

    1. 使用立即执行函数运行代码,因为函数可以创建一个独立的作用域。有两种方式可以操作
    1. // 方式1:立即执行函数在setTimeout的第一个参数上。
    2. for(var i = 0; i < 6; i++) {
    3. setTimeout((function(){
    4. console.log(i)
    5. })(),0)
    6. }
    7. // 这种方式和上一种是一样的方法,但是打印i时的作用域是不相同的,这里的i要传入立即执行函数中,
    8. // 为了方便理解,用变量a来区分。
    9. // 上面的是打印的全局变量i
    10. // 另外就是方式1的方法,它们都并没有进入异步函数中执行,是在进入之前就随着同步代码执行完毕了。
    11. // 所以这样虽然顺序打印了0,1,2,3,4但是是一种比较鸡贼的办法。
    12. for(var i = 0; i < 6; i++) {
    13. setTimeout((function(a){
    14. console.log(a)
    15. })(i),0)
    16. }
    17. // 方式2:立即执行函数包装了setTimeout函数
    18. for(var i = 0; i < 6; i++){
    19. (function(a){
    20. setTimeout(function(){
    21. console.log(a)
    22. },0)
    23. })(i)
    1. 在setTimeout前用一个新的变量去接收循环,属于多此一举。
    1. for(var i = 0; i < 6; i++){
    2. let j = i
    3. setTimeout(function(){
    4. console.log(j)
    5. },0)
    6. }
    1. 利用最新浏览器支持的setTimeout函数将参数3传给function。传多个时,function可以使用多个变量去接收。
    1. for(var i = 0; i < 5; i++) {
    2. setTimeout(function(a){
    3. console.log(a)
    4. },0,i)
    5. }

    以上就是关于JavaScript的执行机制以及作用域的相关知识介绍!如有错误,欢迎指正!