前言
最近发现了一个理论的问题, 关于单线程语言的 js, 如何处理 ajax 这种异步处理的东西呢? 难道 js 当真不是单线程, 而是多线程语言? 如果有这方面疑问的小伙伴们可以接着看, 如果知道原理的大佬们可以不必往下了
Js 是单线程
- for(var j=0; j<5; j++){
- setTimeout(function(){
- console.log(j)
- },100)
- }
结果如下:
5 5 5 5 5
因为 JS 运行在浏览器中, 是单线程的, 每个 window 一个 JS 线程, 既然是单线程的, 在某个特定的时刻只有特定的代码能够被执行, 并阻塞其它的代码而浏览器是事件驱动的 (Event driven), 浏览器中很多行为是异步(Asynchronized) 的, 会创建事件并放入执行队列中 javascript 引擎是单线程处理它的任务队列, 你可以理解成就是普通函数和回调函数构成的队列当异步事件发生时, 如 mouse click, a timer firing,
or an XMLHttpRequest completing
(鼠标点击事件发生定时器触发事件发生 XMLHttpRequest 完成回调触发等), 将他们放入执行队列, 等待当前代码执行完成
前面已经提到浏览器是事件驱动的 (Event driven), 浏览器中很多行为是异步(Asynchronized) 的, 例如: 鼠标点击事件窗口大小拖拉事件定时器触发事件 XMLHttpRequest 完成回调等当一个异步事件发生的时候, 它就进入事件队列浏览器有一个内部大消息循环, Event Loop(事件循环), 会轮询大的事件队列并处理事件例如, 浏览器当前正在忙于处理 onclick 事件, 这时另外一个事件发生了(如: window onSize), 这个异步事件就被放入事件队列等待处理, 只有前面的处理完毕了, 空闲了才会执行这个事件 setTimeout 也是一样, 当调用的时 候, js 引擎会启动定时器 timer, 大约 xxms 以后执行 xxx, 当定时器时间到, 就把该事件放到主事件队列等待处理, 浏览器不忙的时候才会真正执行
浏览器不是单线程
虽然 JS 运行在浏览器中, 是单线程的, 每个 window 一个 JS 线程, 但浏览器不是单线程的, 例如 webkit 或是 Gecko 引擎, 都可能有如下线程:
javascript 引擎线程
界面渲染线程
浏览器事件触发线程
Http 请求线程
很多小伙伴搞不清这个问题, 如果 js 是单线程的, 那么谁去轮询大的 Event loop 事件队列? 答案是浏览器会有单独的线程去处理这个队列
AJAX 请求是否真的异步?
既然说 JavaScript 是单线程运行的, 那么 XMLHttpRequest 在连接后是否真的异步?
其实请求确实是异步的, 这请求是由浏览器新开一个线程请求 (见前面的浏览器多线程) 当请求的状态变更时, 如果先前已设置回调, 这异步线程就产生状态变更 事件放到 JavaScript 引擎的事件处理队列中等待处理当浏览器空闲的时候出队列任务被处理, JavaScript 引擎始终是单线程运行回调函数 javascript 引擎确实是单线程处理它的任务队列, 能理解成就是普通函数和回调函数构成的队列
总结一下, Ajax 请求确实是异步的, 这请求是由浏览器新开一个线程请求, 事件回调的时候是放入 Event loop 单线程事件队列等候处理
setTimeout(func, 0)为什么有时候有用?
写 js 多的小伙伴可能发现, 有时候加一个 setTimeout(func, 0)非常有用, 为什么? 难道是模拟多线程吗? 错! 前面已经说过了, javascript 是 JS 运行在浏览器中, 是单线程的, 每个 window 一个 JS 线 程, 既然是单线程的, setTimeout(func, 0)神奇在哪儿? 那就是告诉 js 引擎, 在 0ms 以后把 func 放到主事件队列中, 等待当前的代码执行完毕再执行, 注意: 重点是改变了代码流程, 把 func 的执行放到了等待当前的代码执行完毕再执行这就是它的神奇之处了它的用处有三个:
让浏览器渲染当前的变化(很多浏览器 UI render 和 js 执行是放在一个线程中, 线程阻塞会导致界面无法更新渲染)
重新评估
script is running too long
警告, 改变执行顺序
例如: 下面的例子, 点击按钮就会显示 "calculating....", 如果删除 setTimeout 就不会因为 reDraw 事件被进入事件队列到长时间操作的最后才能被执行, 所以无法刷新
- <button id='do'> Do long calc!</button>
- <div id='status'></div>
- <div id='result'></div>
- $('#do').on('click', function(){
- $('#status').text('calculating....'); // 此处会触发 redraw 事件的 fired, 但会放到队列里执行, 直到 long()执行完
- // without set timeout, user will never see "calculating...."
- //long();// 执行长时间任务, 阻塞
- // with set timeout, works as expected
- setTimeout(long,50);// 用定时器, 大约 50ms 以后执行长时间任务, 放入执行队列, 但在 redraw 之后了, 根据先进先出原则
- })
- function long(){
- var result = 0
- for (var i = 0; i<1000; i++){
- for (var j = 0; j<1000; j++){
- for (var k = 0; k<1000; k++){
- result = result + i+j+k
- }
- }
- }
- $('#status').text('calclation done') // has to be in here for this example. or else it will ALWAYS run instantly. This is the same as passing it a callback
- }
非阻塞 js 的实现(non-blocking javascript)
js 在浏览器中需要被下载解释并执行这三步在 html body 标签中的 script 都是阻塞的也就是说, 顺序下载解释执行尽管 Chrome 可以实现多线程并行下载外部资源, 例如: script fileimageframe 等 (CSS 比较复杂, 在 IE 中不阻塞下载, 但 Firefox 阻塞下载) 但是, 由于 js 是单线程的, 所以尽管浏览器可以 并发加快 js 的下载, 但必须依次执行所以 chrome 中 image 图片资源是可以并发下载的, 但外部 js 文件并发下载没有多大意义
效果图
要实现非阻塞 js(
non-blocking javascript
)有两个方法: 1. html5 2. 动态加载 js
HTML5 的 defer 和 async 关键字:
- defer:
- <script type="text/javascript" defer src="foo.js"></script>
- async:
- <script type="text/javascript" async src="foo.js"></script>
然后第二种方法是动态加载 js:
- setTimeout(function(){
- var script = document.createElement("script");
- script.type = "text/javascript";
- script.src = "foo.js";
- var head = true; // 加在头还是尾
- if(head)
- document.getElementsByTagName("head")[0].appendChild(script);
- else
- document.body.appendChild(script);
- }, 0);
- // 另外一个独立的动态加载 js 的函数
- function loadJs(jsurl, head, callback){
- var script=document.createElement('script');
- script.setAttribute("type","text/javascript");
- if(callback){
- if (script.readyState){ //IE
- script.onreadystatechange = function(){
- if (script.readyState == "loaded" ||
- script.readyState == "complete"){
- script.onreadystatechange = null;
- callback();
- }
- };
- } else { //Others
- script.onload = function(){
- callback();
- };
- }
- }
- script.setAttribute("src", jsurl);
- if(head)
- document.getElementsByTagName('head')[0].appendChild(script);
- else
- document.body.appendChild(script);
- }
后言
本文转自: 为什么 javascript 是单线程的却能让 AJAX 异步调用?
来源: http://www.jianshu.com/p/4896532d7525