单线程程序的弊端
var res = ajax(...)
//undefine
标准的 ajax请求不会同步完成,应该用回调的方式进行赋值,同步 ajax 的弊端就是单线程程序的弊端。应该把操作放在 ajax 的回调中进行。
可以用 setTimeout 的方式实现异步。
什么是事件循环机制呢?
JavaScript 引擎并不是单独执行的,都运行在一个托管环境当中,托管环境都有一个共同的特点--------具有一个称为事件循环的内置机制,这个机制可以处理程序多个块的执行。所以我们可以这样说,JavaScript 引擎是JavaScript 的一个任意执行的环境,而托管环境是安排JavaScript 事件执行的一个周边环境(对于“|周边”这个词,我抱有一些疑问,是否是指 JavaScript 事件的一个控制 )
明白了上面说的,我们就理解了一个 JavaScript 程序中,异步请求的一个执行逻辑:
首先你在你的 ajax 请求中设置了一个 callback,JavaScript 引擎会告诉托管环境,程序现在暂停执行,等你完成了数据的请求,拿到了数据之后,再继续执行程序。这是浏览器就会设置一个监听机制来响应网络请求回来的数据,拿到数据之后,浏览器再去执程序,将请求回来的数据插入到事件循环中来调度回调函数。
讲了这么多,我们终于提到 “事件循环机制” 这个词了,为什么要做上面的那些铺垫了,这是为了能让你更好地理解什么是事件循环机制。下面我们来正式介绍事件循环机制。
事件循环有一个任务--------监视调用堆栈和回调队列。如果调用堆栈是空的,它将从队列中取出第一个事件,并将其推到调用堆栈,堆栈会去运行被推进去的事件。这也一个任务迭代的过程,被称为事件循环中的一个 tick 。每个事件都只是一个回调函数。
举个例子,有下面这些程序:
console.log("你好啊!");
setTimeout(function(){
console.log("嘿嘿嘿");
}, 5000)
console.log("你好啊!");
程序按照顺序进行执行,执行 setTimeout 时,浏览器作为一个上面我们所提到的周边环境,会安排事件的执行的,浏览器会为 setTimeout 创建一个计时器,处理这个倒计时。计时器执行 5000ms 之后,会将 setTimeout 中的回调函数推到调用堆栈中,并被执行,然后回调中的程序被添加到调用堆栈。
在 ES6 标准出来之前,JavaScript 并没有一个清晰的关于异步的概念。ES6 中指定了事件循环应该如何工作,这就明确了异步不再只是JavaScript 托管环境的职责。ES6 中具体如何执行异步的呢?这就要到 ES6 中的 Promise,通过Promise可以实现 JavaScript 程序自身对于事件循环队列的直接控制。
setTimeout() 的执行机制
setTimeout 不会自动地将回调放到事件循环队列中,通过托管环境中的计时器,托管环境将回调放入到事件循环中,在之后某个时间点去执行。
setTimeout(callhandler, 5000) 并不意味着回调会在5000 ms 时执行,而是再5000 ms 时,它会被放到事件队列中去,如果之前有其他的事件,那么就要等待其他事件执行完毕后,再被执行,这就意味着setTimeout(callhandler, 5000) 被执行事件并不是准确的,可以用 setTimeout(callhandler, 0) 的方式,等调用堆栈为空的时候再去执行。
console.log("你好啊!");
setTimeout(function(){
console.log("嘿嘿嘿");
}, 5000)
console.log("谢谢!");
ES6中的 Job Queue(作业队列)
ES6 中引入了Job Queue 的概念,它是在事件循环队列上的一层。是使用 Promise 的时候会遇到这个概念。
作业队列连接到到事件循环队列的每一个 tick 末尾,在事件循环期间,可能发生的某些异步操作不会导致整个新事件添加到事件循环队列当中去,而是将 Job 添加到当前 tick 的作业队列的末尾。这样一来,你就可以添加一个稍后执行的功能,可以保证它在任何事件之前被执行。但是由此以来,可能会出现循环无限期运行的情况,消耗程序性能,以致无法进行下一个循环。Job Queue 其实是一种 setTimeout(callhandler, 0) hack 实现方式,只是它更明确了执行顺序,能够保证尽可能快的执行。