RainZhai / rainzhai.github.com

宅鱼
http://rainzhai.github.io
Apache License 2.0
2 stars 0 forks source link

javascript事件机制同步异步和事件循环(Event Loop) #1

Open RainZhai opened 8 years ago

RainZhai commented 8 years ago

运行时概念 Runtime

可视化描述

event loop

函数调用形成了一个frames的栈。

function f(b){
  var a = 12;
  return a+b+35;
}

function g(x){
  var m = 4;
  return f(m*x);
}

g(21);

调用g的时候,创建了第一个frame,包含了g的参数和局部变量。当 g 调用 f 的时候,第二个frame就被创建、并置于第一个frame之上,包含了f的参数和局部变量。当f返回时,最上层的frame就出栈了(剩下g函数调用的frame)。当g返回的时候,栈就空了。

对象被分配在一个堆中,一个用以表示一个内存中大的未被组织的区域。

队列

一个JavaScript运行时包含了一个待处理的消息队列。每一个消息都与一个函数相关联。当栈为空时,从队列中取出一个消息进行处理。这个处理过程包含了调用与这个消息相关联的函数(以及因此而创建的一个初始栈结构)。当栈再次为空的时候,也就意味着消息处理结束。

单线程

Javascript是单线程的,在JS引擎中负责解释和执行JavaScript代码的线程只有一个(暂称为主线程)。

浏览器不是单线程的

虽然JS运行在浏览器中,是单线程的,每个window一个JS线程,但浏览器不是单线程的其可能有如下线程:

另外Event loop事件队列有单独的线程去处理(暂称为事件队列工作线程)。

同步和异步

如果在函数执行完成的时候,调用者就能够得到预期结果(即拿到了预期的返回值或者看到了预期的效果),那么这个函数就是同步的。 如果在函数执行完成的时候,调用者还不能够得到预期结果,而是需要在将来通过一定的手段得到,那么这个函数就是异步的。 总结一下,一个异步过程通常是这样的:

主线程发起一个异步请求,相应的事件队列工作线程接收请求并告知主线程已收到(异步函数返回);
主线程可以继续执行后面的代码,同时事件队列工作线程执行异步任务;事件队列工作线程完成工作后,通知主线程;
主线程收到通知后,执行一定的动作(调用回调函数)。

事件循环 Event Loop

类似如下的方式来实现:

while(queue.waitForMessage()){
  queue.processNextMessage();
}

如果当前没有任何消息,queue.waitForMessage 会同步等待消息到来。

"执行至完成"

每一个消息执行完成后,其它消息才会被执行。当一个函数运行时,它不能被取代且会在后续代码运行前先完成(而且能够修改这个函数控制的数据)。这点与C语言不同。例如,C语言中当一个程序在一个线程中运行时,它可以在任何点停止且可以在其它线程中运行其它代码。

这个模型的一个缺点在于当一个消息的完成耗时过长,网络应用无法处理用户的交互如点击或者滚动。浏览器用“程序需要过长时间运行”的对话框来缓解这个问题。一个比较好的解决方案是使消息处理变短。如果可能的话,将一个消息拆分成几个消息。

添加消息

在浏览器里,当一个事件出现且有一个事件监听器被绑定时,消息会被随时添加。所以点击一个附带点击事件处理函数的元素会添加一个消息。其它事件亦然。

调用 setTimeout 函数会在一个时间段过去后在队列中添加一个消息。这个时间段作为函数的第二个参数被传入。如果队列中没有其它消息,消息会被马上处理。但是,如果有其它消息,setTimeout消息必须等待其它消息处理完。因此第二个参数仅仅表示最少的时间 而非确切的时间。

零延迟

零延迟(Zero delay) 并不是意味着回调会立即执行。在零延迟调用setTimeout时,其并不是过了给定的时间间隔后就马上执行回调函数。其等待的时间基于队列里正在等待的消息数量。在下面的例子中,"this is just a message"在将会在回调(callback)获得处理之前输出到控制台,这是因为延迟是要求运行时(runtime)处理请求所需的最小时间,但不是确定的时间。

(function () {
  console.log('this is the start');

  setTimeout(function cb() {
    console.log('this is a msg from call back');
  });

  console.log('this is just a message');

  setTimeout(function cb1() {
    console.log('this is a msg from call back1');
  }, 0);

  console.log('this is the  end');
})();

面试题:执行下面这段代码,执行后,在 5s 内点击两下,过一段时间(>5s)后,再点击两下,整个过程的输出结果?

setTimeout(function(){
    for(var i = 0; i < 100000000; i++){}
    console.log('timer a');
}, 0)

for(var j = 0; j < 5; j++){
    console.log(j);
}

setTimeout(function(){
    console.log('timer b');
}, 0)

function waitFiveSeconds(){
    var now = (new Date()).getTime();
    while(((new Date()).getTime() - now) < 5000){}
    console.log('finished waiting');
}
//事件监听在js 解析阶段已经生效
document.addEventListener('click', function(){
    console.log('click');
})

console.log('click begin');
waitFiveSeconds();

几个运行时(Runtime)互相通信

一个web worker或者一个跨域的iframe都有它们自己的栈,堆和消息队列。两个不同的运行时只有通过postMessage方法进行通信。这个方法会给另一个运行时添加一个消息如果后者监听了message事件。

绝不阻塞

一个很有趣的事件循环(event loop)模型特性在于,Javascript跟其它语言不同,它永不阻塞。处理I/O (input/output)通常由事件或者回调函数进行实现。所以当一个应用正等待IndexedDB的查询的返回或者一个XHR(译者注:用于Ajax)的请求返回时,它仍然可以处理其它事情例如用户输入。

例外是存在的,如alert或者同步XHR,但避免以它们为最佳实践。

相关链接

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/EventLoop https://segmentfault.com/a/1190000004322358 http://latentflip.com