EventLoop的理解

LiYajie: 2021-04-26 eventloop

# 什么是javascript

简单来讲: javascript是一个单线程,异步,非阻塞,解释性的脚本语言

javascript是一个单线程的编程语言, 单线程的运行环境, 有且只有一个调用栈, 同一时间只能做一件事.

# 调用栈的执行过程call stack

function multiply(a,b){
    return a * b;
}

function square(n){
    return multiply(n,n)
}

function printSquare(n){
    var result = square(n)
    console.log(result)
}
printSquare(5) // 25

调用栈首先有一个入口函数, 我们命名为main() 当执行到printSquare(5)的时候, 会将该函数入栈, 然后进入该函数执行, 发现里面还调用了square()函数,则又会把square()入栈, 又发现square中调用了multiply,则又会将multiply入栈, 执行完multiply得到返回值, 开始执行弹栈操作, 先将multiply弹出, 这样square的返回值也得到了, 再把square弹出, 这样参数result的结果得到了, 发现里面还执行了一个console.log, 再把console.log入栈, 打印出结果后将其弹栈, 最后printSquare执行完毕, 把该函数也弹出栈,最后main()也执行完也弹出, 这就是这个执行流程.

入main()-->
    入printSquare(5)-->
    
        入square(n)-->
            入multiply(n,n)-->
            出multiply(n,n)-->
        出square(n)-->
    
        入console.log(result)-->
        出console.log(result)-->
    
    出printSquare(5)-->
出main()

我们改造上面的函数后

function multiply(a,b){
    throw new Error('报错了')
}

function square(n){
    return multiply(n,n)
}

function printSquare(n){
    var result = square(n)
    console.log(result)
}
printSquare(5) // 25

结果:

Uncaught Error: 报错了
    at multiply (<anonymous>:2:11)
    at square (<anonymous>:6:12)
    at printSquare (<anonymous>:10:18)
    at <anonymous>:13:1

并且错误是由内-->外传递, 最终传递到printSquare

经过上述分析之后, 我们知道了调用栈的执行过程, 那么我们就可以写一个内存溢出的函数了, 如下:

function test(v){
    return test(v)
}
test(1)

结果:

Uncaught RangeError: Maximum call stack size exceeded
超出最大调用栈大小(内存溢出)

原因: 一直在入栈, 没有弹栈的操作,这样就导致了每执行到test(v)就入栈, 这样栈很快就会被溢出

# 事件循环

什么是事件循环也就是EventLoop

主线程也可以说调用栈从任务队列(回调队列)中读取事件, 这个过程是循环不断的, 这个过程就是EventLoop, 下面的图可以帮助我们更好的理解

# 回调队列

所谓回调队列就是如上图中的callback queue, 用下面代码讲解:

console.log('Hi');
setTimeout(function() {
    console.log('callback');
}, 1000);
console.log('Bye');

执行结果:

Hi
Bye
// 1s后
callback

上述代码执行顺序

console.log('Hi') 同步任务, 被推入主线程执行
setTimeout() 异步任务, 被放到event table中,1秒后被推入event queue
console.log('Bye') 同步任务, 被推入主线程执行

Hi Bye打印后, 主线程就去任务队列查看是否有可执行的任务, 如果有则执行setTimeout里的函数

先将console.log('Hi')添加到执行栈, 执行完成后输出Hi并弹栈, 接着执行setTimeout, 执行的时候发现是个定时器,是一个异步任务, 异步任务在event table中注册函数,当满足触发条件后(这里也就是时间到了之后),被推入event queue, setTimeout执行完成之后就会立马弹栈, 接着执行console.log('Bye'), 过程和执行console.log('Hi')的时候一样, 整体过程如下动图展示:

如果主线程中代码执行事件很长, 这样就不能保证setTimeout指定的回调会在指定的时间内执行

到这里, 以上就是我对event loop的理解

但是当我遇到下面代码的时候我发现我的理解还是有点问题的.

setTimeout(function(){
 console.log('定时器开始')
});

new Promise(function(resolve){
    console.log('马上执行for循环啦');
    for(var i = 0; i < 10000; i++){
        i == 99 && resolve();
    }
}).then(function(){
    console.log('执行then函数啦')
});
console.log('代码执行结束');

参考网站:

LiYajie 发布于: 2021-04-26