事件、并发、js(翻译)

现在的web app本质上都是事件驱动的,虽然浏览器内部的triggering、executing、handling event,看起来是黑盒。

如下

1
2
3
4
5
6
7
8
<button id="doStuff">Do Stuff</button>
<script>
document.getElementById('doStuff')
.addEventListener('click', function() {
console.log('Do Stuff');
}
);
</script>

事件模型大概如下图
img

From Philip Robert’s diagram

以下几个点:
1.用户界面:用户点击do stuff按钮,很简单
2.Web APIS:
DOM API的事件中心,Web APIS是多线程区域,允许一次触发多个事件,他们在页面加载完毕后通过window全局对象可以访问到,DOM以外的的例子就是AJAX的XMLHttpRequest,和timer的setTimeout函数
3.Event Queue:
event的回调函数的下一步就是将其push到很多的events queues(也叫task queues)中,因为有很多的web api,浏览器有很多的event queues,比如network request,dom events、rendering更多

关于 event-loop

For example, a user agent could have one task queue for mouse and key events (the user interaction task source), and another for everything else. The user agent could then give keyboard and mouse events preference over other tasks three quarters of the time, keeping the interface responsive but not starving other task queues, and never processing events from any one task source out of order.

用户代理会把3/4的时间给keyboard和mouse事件,以保持用户界面的响应
4.Event loop:
单一的event loop会选择哪个callback放到javascript的call stack中,下面是火狐中c++的伪代码

1
2
3
while(queue.waitForMessage()){
queue.processNextMessage();
}

5.Call stack:
每个函数调用,包括event 回调,都会创建一个stack frame(也叫execution object),这些stack frames会从栈顶push和pop,栈顶是当前正在执行的代码,当函数返回时,就会从栈顶pop

chrome v8中stack frame的源代码

1
2
3
4
5
6
7
8
9
10
11
class V8_EXPORT StackFrame {
public:
int GetLineNumber() const;
int GetColumn() const;
int GetScriptId() const;
Local<String> GetScriptName() const;
Local<String> GetScriptNameOrSourceURL() const;
Local<String> GetFunctionName() const;
bool IsEval() const;
bool IsConstructor() const;
};

call stack的三个特征
1.单一线程
线程是CPU利用率的基本单位。 作为较低级别的OS结构,它们由线程ID,程序计数器,寄存器组和堆栈组成虽然JavaScript引擎本身是多线程的,但它的调用堆栈是单线程的,只允许一段代码在一个时间执行。
2.同步
JavaScript调用堆栈执行完成任务而不是任务切换,同样适用于事件。 这不是ECMAScript或WC3规格的要求。 但是有一些例外,如window.alert()中断当前执行的任务。
3.非阻塞
当线程执行的时候,会阻塞 浏览器是非阻塞的,仍然接受诸如鼠标点击的事件,即使它们可能不会立即执行。

##CPU 密集型 task
CPU密集型任务可能很困难,因为单一线程的原因,因为同步运行的队列,回调和线程会进入一个等待的状态

下面是一个cpu密集型的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<button id="bigLoop">Big Loop</button>
<button id="doStuff">Do Stuff</button>
<script>
document.getElementById('bigLoop')
.addEventListener('click', function() {
// big loop
for (var array = [], i = 0; i < 10000000; i++) {
array.push(i);
}
});
document.getElementById('doStuff')
.addEventListener('click', function() {
// message
console.log('do stuff');
});
</script>

codepen

解决方案
1.方案一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
document.getElementById('bigLoop')
.addEventListener('click', function() {
var array = []
// smaller loop
setTimeout(function() {
for (i = 0; i < 5000000; i++) {
array.push(i);
}
}, 0);
// smaller loop
setTimeout(function() {
for (i = 0; i < 5000000; i++) {
array.push(i);
}
}, 0);
});

2.方案二
web worker

参考资料
W3C Web APIs
W3C Event Queue (Task Queue)
W3C Event Loop
Concurrency modal and Event Loop, MDN
ECMAScript 10.3 Call Stack