# 单线程执行
JavaScript 中有许多操作看起来都像是多线程执行的,比如 AJAX 异步请求、还有定时器等。但事实上 JavaScript 是通过单线程执行的。
# 历史
布兰登·艾奇(Brendan Eich)用了 10 天的时间创造了 JavaScript。当时 JavaScript 只是用来简单和浏览器进行交互,做一些表单验证、操作 DOM。
由于需求并不多,所以并没有必要将运行模式变得复杂,而且多线程往往还会带来许多不必要的麻烦。比如,当我们在一个线程中把某一个 DOM 删除了,而另一个线程却在读取这一个 DOM,并试图对其进行一些操作,所以会发生一些错误。
为了更好的理解单线程,先来了解一下浏览器的内核。
# 浏览器内核
浏览器内核包括许多模块,比如渲染引擎、JavaScript 引擎、定时器模块、事件响应模块、网络请求模块等。
渲染引擎负责获取网页内容、整理信息和计算网页的显示方式,然后输出到显示器或打印机。JavaScript 引擎负责解析和执行 JavaScript 来实现网页的动态交互。
内核不同对于网页的解析也会不同,现在常见的浏览器内核包括:
内核 | 浏览器 |
---|---|
Trident | IE,MaxThon,TT,The World,360,搜狗浏览器等 |
Gecko | NETSCAPE6 及以上版本,FF,MozillaSuite / SeaMonkey 等 |
Presto | Opera7 及以上 |
Webkit | Safari,Chrome 等 |
虽然内核有多重,但是执行 JavaScript 的方式大同小异,需要注意的是浏览器不是单线程的,比如上面定时器模块、事件响应模块、网络请求模块都是在分线程中完成的。
# 认识单线程
单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。
alert('stop')
console.log('done')
上面的代码就是一个很简单的例子,如果我们不点击确认,那么控制台永远不会输出下面的字符串。
# 单线程的 JavaScript
在 JavaScript 中代码可以简单的分为两种代码,即初始化代码和回调代码。
JavaScript 引擎执行代码的基本流程就是先执行初始化代码,包括设置定时器,绑定事件监听和发送 AJAX 请求。
能很好的说明 JavaScript 是单线程执行的例子就是 setTimeout()
的回调函数只有在运行栈中的代码执行完成后才会执行。
为了理解回调函数的执行我们需要先来了解一下事件循环模型。
# 事件循环模型
在 JavaScript 中所有任务可以分成两种,一种是同步任务(synchronous),另一种是异步任务(asynchronous)。
同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入”任务队列”(task queue)的任务,只有”任务队列”通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
具体来说,异步执行的运行机制就像是这样:
- 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
- 主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
- "执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列"。里面等待的事件,进入执行栈,开始执行。
- 主线程不断重复上面的第三步。
第三步中,主线程从”任务队列”中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为 Event Loop(事件循环)。
# Web Workers
Web worker
是运行在后台的 JavaScript,独立于其他脚本,不会影响页面的性能。您可以继续做任何愿意做的事情:点击、选取内容等等,而此时 web worker
在后台运行。
// 主线程
if ('undefined' !== typeof Worker) {
/** 检测浏览器是否支持 */
if (typeof w == 'undefined') {
/* 创建一个 Worker */
WorkerInstance = new Worker('demo_workers.js')
}
/** 以事件监听的方式进行交互信息 */
WorkerInstance.onmessage = function(event) {
console.log(event.data)
}
/** 向分线程中发送数据 */
WorkerInstance.postMessage(number)
} else {
console.log('抱歉,你的浏览器不支持 Web Workers...')
}
// 分线程(demo_workers.js)
function fibonacci(number) {
return arguments.callee(number - 1) + arguments.callee(number - 2)
}
function onmessage(number) {
// 分线程中启动事件监听
postMessage(fibonacci(number)) // 向主线程返回数据
}
当我们创建 web worker
对象后,它会继续监听消息(即使在外部脚本完成之后)直到其被终止为止。如需终止 web worker
,并释放浏览器/计算机资源,请使用 terminate()
方法:
WorkerInstance.terminate()
WorkerInstance = undefined
由于 web worker
位于外部文件中,其全局对象不再是 window
,而且它们无法访问下列 JavaScript 对象:
window
对象document
对象parent
对象
所以它不能改变 DOM,也没有改变 JavaScript 是单线程执行的本质。另外,在一个分线程中有多个任务时,一样存在着一个队列,然后挨个处理。