了解过javascript的都知道其最大的特点就是单线程,也就是说同一时间只能干一件事情。那么为什么不能是多线程呢?原因很简单,多线程太复杂了,假设javascript有2个线程,一个去添加dom,一个去删除dom,那么浏览器就懵逼了,这到底要我选择哪个?所以为了避免不必要的麻烦,javascript一开始就选择了单线程。但是单线程也有问题,假设有个任务是要向服务器去请求一个文件,如果这个文件很大,那么就不能立即执行下一语句(要等到文件回来),这样就造成了浏览器假死的现象。所以html5提出了web worker标准,允许javascript创建子线程,但是规定很严格,子线程要受到主线程控制,并且不能操作dom,这种折中方法使得javascript更加灵活了。
到目前为止javascript可以有子线程了,这时候再遇到像之前提到的如果一个io操作很费时间,那么就可以把这个任务挂起来,等返回结果了再来执行这个任务。于是所有的任务都变成了2种,一种是同步任务(从上到下一步一步执行),另外一种就是异步任务(等有结果了再执行,即所谓的消息队列)。这2种任务进入到线程也不一样,同步的从上到下依次直接进入主线程形成执行栈,异步的等有返回结果了,比如ajax请求成功了,就把成功的回调放到子线程里面去(失败就把失败的回调放到子线程里面去)。现在浏览器开始执行主线程里面的执行栈了,等主线程里面的执行栈都执行完毕了,主线程就会到子线程里面去看之前挂起到任务哪些有回调了,如果有回调了,那就把该回调内容放到主线程里面去执行,等执行完毕了再去子线程看有没有新的回调了(这里要注意的是主线程全部执行完毕,才会去子线程去看),主线程不断的重复这个步骤,这就是所谓的Event loop,也就是javascript的运行机制。比较特殊的是setTimeout,setinterval这2个方法,它们也会被放倒子线程里面去,比如我使用setTimeout(fn,3000),有时候不一定是3s之后会执行fn这个事件,还要看主线程里面的任务是否完成。
案例1:
function f() { console.log("foo"); setTimeout(g, 0); console.log("baz"); h();}function g() { console.log("bar");}function h() { console.log("blix");}f();输出的结果为:foo 、baz 、 blix 、bar
案例2 :
var req = new XMLHttpRequest();req.open('GET', url); req.onload = function (){}; req.onerror = function (){}; req.send();var req = new XMLHttpRequest();req.open('GET', url);req.send();req.onload = function (){}; req.onerror = function (){};
这2个的执行结果都是一样的,都会先执行onload事件,因为javascript要等主线程空了才会去查看子线程有没有回调内容。
注意点:
异步的任务执行的顺序是不固定的,主要看返回的速度,假设a任务写在b任务之前,但是a任务比较大,耗时比较长,而b任务耗时短,那么b任务有了回调先会进入到子线程里面,这样会被主线程先轮询到,但是也有可能b任务网络不好,a任务先返回了,那么a任务的回调先被注册到子线程了,导致a先执行了。