五个线程

js引擎线程: 执行js代码
GUI线程: 绘制用户界面
http网络请求线程: 处理网络请求, 等请求返回之后, 将回调函数放进事件队列中
定时器触发线程: 定时器等待事件结束之后, 将回调函数放进事件队列中
浏览器事件处理线程clickmouse等事件触发, 将回调函数放进事件队列

js引擎线程GUI线程的运行状态是互斥的, 即 当js引擎线程在执行的时候, 它可能会改变DOM元素, 进而影响到GUI 的渲染结果, 所以此时GUI线程是冻结的。最明显的例子是, 当js代码进入死循环的时候, 用户是无法跟界面进行交互的。

js引擎是单线程的, 同一时间只能做一件事。

function foo() {
    var a = 10
    function bar() {
        console.log(a)
    }
    bar()
}
foo()

这是同步代码, 上面的代码在执行的时候, 经历了一下几个阶段

  1. 预编译阶段, 执行栈为空
  2. foo执行, 创建一个执行帧,包含参数列表局部变量等等, 将这一帧压入栈中
  3. 执行foo 内部代码, 执行bar 函数
  4. 创建 bar 的执行帧, 包含参数列表,局部变量等, 压入栈中
  5. 打印a 的值,bar执行完毕, 此帧从执行栈中弹出
  6. foo 函数执行完毕, foo的帧从栈中弹出
  7. 执行栈为空
$ajax({
  url: 'http://www.example.com?a=1&b=2',
  method: 'get',
  data: {},
  success: function(res){
    console.log(res)
  }
})
console.log(111)
  1. ajax进入Event Table
  2. 打印111
  3. ajax事件完成http网络请求把任务放进事件队列
  4. 主线程读取任务执行回调函数

setTimeout 并不准确

js 任务在被读取到的时候, 都会进入一个叫做执行栈的地方。
然后会判断这个任务是同步任务还是异步任务
异步任务会进入 事件表中, 注册回调函数之后,放进任务队列里面
同步任务会交给js主线程立即执行,同步任务执行完毕之后, 读取任务队列里面的异步任务执行。
js 引擎会一直检测主线程是否为空, 一旦为空, 就去看任务队列里面有没有任务需要执行。

所以如果有一个同步任务, 去跑一万次循环, 同时有一个异步任务, 30ms之后打印一句话, 那么肯定会出问题

function foo(times) {
    for (var i = 0; i < times; i++){
        console.log(i);
    }
}
var start = + new Date()
foo(100000)
console.log('timeout start: ' + start);
setTimeout(()=>{
    console.log('timeout end');
    console.log(+ new Date() - start);
}, 30)

当疯狂的打印完 100000 次之后, 时间已经过去了 600+ ms

image.png