零、参考
下面两篇是尝试对异步有个了解,其实讲得还是有很多不清楚不规范的地方
https://www.cnblogs.com/sameen/p/7922627.html
【只看前言和浅谈异步,因为后面的异步原理讲得不是很清楚,然后看下面的文章理解浅谈异步里面的例子】
https://www.cnblogs.com/moxiaowohuwei/p/8438236.html
【第二点的例子看不懂也没关系...还有后面的浏览器内核线程图不太靠谱,毕竟消息队列和事件循环都没有...】
下面是对异步原理的深入探索(带质疑态度去看,不一定对。有些地方不理解可以先跳过,而且不同文章的解释是不统一的...):
https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/EventLoop
【这个靠谱点,但讲得不够详细。】
https://segmentfault.com/a/1190000004322358
【这个讲得详细点,但是忽略了非异步的函数执行栈,消息队列和事件循环讲得不错,评论也可以看一下】
【结合上面两篇可以知道:函数的处理会一直进行到执行栈再次为空为止;然后事件循环将会处理队列中的下一个消息(如果还有的话)。】
【其实最正确的答案可能要研究整个异步过程的源代码,但时间和难度都...,不过有一定的理解,加上知道如何利用异步实现自己想要的功能,已经很不错了】
异步的实现:http://www.ruanyifeng.com/blog/2012/12/asynchronous%EF%BC%BFjavascript.html
【会发现都存在延时函数setTimeout()】
一、结合例子尝试理解异步
题目一:
function B(){
setTimeout("console.log('我是回调函数')", 3000);//模仿耗时操作
}
function A() {
B();//在第0秒执行这句,估计在第3秒输出
setTimeout("console.log('我是主函数1')", 1000);//可假设在0.0001秒执行这句,估计1.0001秒输出
setTimeout("console.log('我是主函数2')", 1000);//可假设在0.0002秒执行这句,估计1.0002秒输出
setTimeout("console.log('我是主函数3')", 3000);//可假设在0.0003秒执行这句,估计3.0003秒输出
}
A();//题目一输出结果:主1、主2、回调、主3
题目一JS引擎处理过程解析:
1.语法分析,发现没有语法错误;
2.进行预编译,对变量声明进行处理,这里只有函数声明,就开辟空间存放函数B、A(对延时函数的处理还不太清楚)
3.开始解释执行,遇到A();需要执行
3.1把A()放进函数执行栈,JS主线程处理执行栈里面的函数A
3.2遇到函数B需要执行,压入执行栈,JS主线程处理执行栈栈顶的函数B
3.3遇到函数B里面的延时函数,发起异步请求,把异步任务交给工作线程(对于延时函数的话应该是交给了定时器线程)
3.4函数B处理完从执行栈弹出,主线程执行执行栈的新栈顶函数A;同时工作线程处理异步任务,让定时器至少等3秒,然后把延时函数里面的输出语句封装成消息,放在消息队列里面,等待主线程处理完执行栈的任务,再通过事件循环来取这里的消息。
3.5主线程执行函数A里面的三条异步发起请求、然后工作线程处理这三条异步任务,至少等待对应的设置时间,才把输出语句封装成消息放在消息队列
3.6主线程执行栈为空了,就通过事件循环逐一地去取消息,执行消息,取消息,执行消息...不断循环,根据延时函数设置时间的不同,消息队列的消息顺序为输出主1、主2、回调、主3
3.7所以最终的结果就是输出主1、主2、回调、主3
思考题:
如果在时间循环的过程中函数执行栈又加入了新的函数,例如点击了按钮输出10000个1,这时候的事件循环会被阻塞吗?
在题目一的基础上加入
<button onclick="C()">点击</button>
function C(){
for(var i = 0 ; i<10000; i++)
console.log('cccc')
}
答案:会阻塞的,当函数执行栈不为空了,就会让消息队列的事件循环停止,先让函数执行,再等函数栈为空了,再去执行消息队列里面剩下的消息。
题目二、三:
//定义主函数,回调函数作为参数
function A(callback) {
callback();//题目二的输出是(10000)2、(10000)1、主1、主2、回调、主3
setTimeout("console.log('我是主函数1')", 1000);
setTimeout("console.log('我是主函数2')", 1000);
setTimeout("console.log('我是主函数3')", 3000);
for(var i = 0; i < 10000; i++) {
console.log(1);//这里耗时一般超过三秒
}
// callback(); //题目三的输出是:(10000)1、(10000)2、主1、主2、主3、回调函数
}
//定义回调函数
function B(){
for(var i = 0; i < 10000; i++) {
console.log(2);
}
setTimeout("console.log('我是回调函数')", 3000);//模仿耗时操作
}
// 调用主函数,将函数B传进去
A(B);
尝试去解释题目二、三的输出。
提示1:这里面的for循环是非异步的,所以一旦遇到就马上执行
提示2:可能有人有这样的误区:认为回调函数要等主函数执行完才处理。其实不是,只要回调函数写在主函数的前面,JS一旦解析到了也会执行的,所以题目二先输出了2,题目三先输出了1。因为往往在回调里面写的都是耗时间长的异步任务,到最后才看到结果,所以给人一种回调函数最后执行的错觉。
另一个小题目:https://www.cnblogs.com/smswei/p/5240065.html
【后面的立即函数讲的优点不清楚,值得再去查资料理解】