博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
javascript运行机制
阅读量:6036 次
发布时间:2019-06-20

本文共 5789 字,大约阅读时间需要 19 分钟。

面试题:请问执行结果是什么?

console.log(1);setTimeout(function(){        console.log(3);    },0);console.log(2);复制代码

输出结果:

一、javascript是单线程异步执行的。

单线程:意味着代码在任务队列中会按照顺序一个接一个的执行。

异步:代表代码在任务队列中的顺序并不等同于代码的书写顺序。

既然JavaScript是单线程机制,那Ajax为什么是异步的?setTimeout()是怎样执行的?

在浏览器中,javascript引擎是单线程执行的。也就是说,在同一时间内,只能有一段代码被javascript引擎执行。页面加载时,javascript引擎会顺序执行页面上所有的javascript代码,优先执行同步代码。而异步代码由事件触发引擎按照“事件发生”的顺序添加到javascript引擎的异步队列中,等待所有同步代码执行完毕后,javascript引擎会按照异步队列中的顺序来执行异步代码。

二、理解js为什么是单线程的?

作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准? 所以,为了避免复杂性,从一诞生,JavaScript就是单线程,这已经成了这门语言的核心特征,将来也不会改变。

三、关于javascript引擎和浏览器内核

javascript引擎是单线程运行的,浏览器无论在什么时候,有且只有一个线程在运行javascript程序。

浏览器的内核是多线程的。他们在内核控制下相互配合以保持同步,一个浏览器至少实现三个常驻线程:javascript引擎线程、GUI渲染线程、浏览器事件触发线程

1、javascript引擎:是单线程的。

2、GUI渲染引擎:负责渲染浏览器界面,当页面需要重绘(repaint)或者某种操作引发回流(reflow)时,该线程就会执行。但需要注意,GUI渲染线程与javascript引擎是互斥的。当Javascript引擎执行时,GUI线程会被挂起,GUI更新会被保持在一个队列中,等待javascript引擎空闲时立即执行。

3、事件触发线程:当一个事件被触发时,该线程会把事件添加到待处理队列的队尾,当线程中没有任何同步代码的前提下才会执行这些异步代码。

四、理解任务队列(消息队列)

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。JavaScript语言的设计者意识到这个问题,将所有任务分成两种,
一种是同步任务(synchronous),另一种是异步任务(asynchronous)。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务。
在所有同步任务执行完之前,任何的异步任务是不会执行的。

对同步任务和异步任务的理解

例1:

console.log("A");while(true){}console.log('B');复制代码

如图所示:输出结果为A,原因程序由上到下执行的过程中遇到了while死循环下面的内容将无法执行。

console.log("A");setTimeout(function(){    console.log('B');},0);while(true){}复制代码

输出结果:

输出结果还是A,原因:setTimeout()是一个异步任务,在所有的同步任务执行完前,任何异步任务是不会执行的。

五、理解Event Loop

异步运行机制:

1、所有的同步任务都在主线程上执行,形成一个执行栈。

2、循环队列之外,还有一个“任务队列”,当javascript引擎发起一个异步任务时,一方面继续执行后面的同步代码,一方面开始倒计时,把异步任务在xms之后添加到任务队列中去。

3、一旦任务栈中所有的同步代码执行完毕后,系统就会读取“任务队列”中的事件,使其结束等待状态,进入执行栈,开始执行。

4、主线程不断重复步骤3。

从setTimeout理解事件循环机制

例:

console.log('Hi');setTimeout(function cd(){    console.log('there');},5000);console.log('SJS');复制代码

1、首先main函数的执行上下文入栈

2、接着执行,遇到console.log('Hi');此时log('Hi')入栈,并立即执行,输出'Hi'。

3、当遇到setTimeout的时候,执行引擎将其添加到栈中。

4、调用栈发现setTimeout是之前提到的WebAPIs中的API,因此将其出栈,并将延时执行的函数交给浏览器的timer模块进行处理。

5、timer模块去处理延时执行的函数,此时执行引擎接着执行将log(‘SJS’)添加到栈中,此时输出’SJS’。

6、当timer模块中延时方法规定的时间到了之后就将其放入到任务队列之中,此时调用栈中的task已经全部执行完毕。

7、调用栈中的task执行完毕之后,执行引擎会接着看执行任务队列中是否有需要执行的回调函数。这里的cb函数被执行引擎添加到调用栈中,接着执行里面的代码,输出’there’。等到执行结束之后再出栈。

六、哪些语句会放入异步任务队列及放入时机

一般来说,有以下四种会放入异步任务队列:

  1. setTimeout和setlnterval
  2. DOM事件
  3. ES6中的Promise
  4. Ajax异步请求

放入时机:

例1:

for(var i = 0; i < 5; i++){    setTimeout(function(){        console.log(i);    },1000)};复制代码

执行结果:

javascript引擎遇到setTimeout()时,并不是马上把setTimeout()拿到异步队列中,而是以便执行同步代码,一边在timer模块中之行倒计时,要等到一秒后,才将其放到任务队列里面,一旦"执行栈"中的所有同步任务执行完毕(即for循环结束,此时i已经为5),系统就会读取已经存放"任务队列"的setTimeout()(有5个),于是答案是输出5个5。

例2:

$.ajax({    url:"xxxxx",    success:function (result){    console.log("a")}});setTimeout(function (){    console.log("b")},100);setTimeout(function (){    console.log("c")});console.log("d");复制代码

ajax、setTimeout都是异步任务,所以主线程先执行同步代码块,首先输出d;ajax和setTimout的执行顺序跟他们放入异步队列的先后顺序有关,c肯定是最先被放入异步队列的,a和b的先后顺序不确定,有两种情况:①d c b a ;②d c a b。

七、微任务和宏任务

异步任务分为宏任务和微任务,宏任务队列可以有多个,微任务队列只有一个

宏任务包括:script(全局任务), setTimeout, setInterval, setImmediate, I/O, UI rendering。微任务包括: new Promise().then(回调), process.nextTick, Object.observe(已废弃), MutationObserver(html5新特性)

宏任务和微任务的执行顺序

1、同步任务执行完毕时,会先执行微任务队列里的任务。

2、微任务执行完毕后,会读取宏任务队列中排在最前面的任务。

3、执行宏任务的过程中,遇到微任务,依次将其加入微任务队列。

4、栈空后,再次读取微任务队列里的任务。

5、微任务执行完毕后再去执行其他宏任务队列里的宏任务,依次类推。

Promise.resolve().then(()=>{    console.log('Promise1');    setTimeout(()=>{        console.log('setTimeout2')    },0)});setTimeout(()=>{    console.log('setTimeout1');    Promise.resolve().then(()=>{        console.log('Promise2');    })},0);复制代码

执行结果:

解释:

1、执行栈的同步任务执行完毕后,会去查看是否有微任务,上题中有promise,所以先执行微任务中的所有任务,输出promise1,然后生成一个宏任务setTimeout2,将其放入另一个宏任务队列中。

2、然后去查看宏任务队列,宏任务setTimeout1在setTimeout2之前,所以先执行宏任务setTimeout1,输出 setTimeout1。

3、在宏任务setTimeout1执行的过程中,又会生成一个微任务promise2,将其放入微任务队列。这时setTimeout1所在的宏任务队列执行完毕。

4、执行微任务队列,输出promise2;然后再去执行另一个宏任务队列里的宏任务setTimeout2。

八、setTimeout()与setInterval()比较

1、setTimeout()

javscript引擎在执行setTimeout(fn,10)时,一方面继续执行setTimeout(fn,10)后面的同步代码,同时另一方面开始计时,在10ms后将fn插到任务队列中。待所有同步代码结束后,依次执行任务队列中的异步代码。所以setTimeout(fn,10)并不能准确的在10ms之后执行,而是大于等于10ms。

例1:

console.log(1);setTimeout(function () {    console.log('a')}, 3);setTimeout(function () {    console.log('b')}, 0);var sum = 0;for (var i = 0; i < 1000000; i ++) {    sum += i;}console.log(sum);setTimeout(function () {    console.log('c');}, 2);复制代码

执行结果:

例2:

console.log(1);setTimeout(function(){    console.log('a');},10);setTimeout(function(){    console.log('b');},0);var sum = 0;for(var i = 0; i < 100000; i++){    sum += i;}console.log(sum);setTimeout(function(){    console.log('c');},0);复制代码

总结:两段代码的区别在于for循环执行的时间不同,第一段代码for循环执行的时间是大于5ms的,所以console.log('a');先被插入任务队列,等for循环结束后console.log('c');才被插入任务队列。第二段代码的for循环执行时间小于10ms,所以console.log('c');先被插入任务队列。

2、setInterval()

setInterval()的执行方式和setTimeout()有不同。假如执行setInterval(fn,10),则每隔10ms,定时器的事件就会被触发。与setTimeout()相同的是,如果当前没有同步代码在执行(JavaScript引擎空闲),则定时器对应的方法fn会被立即执行,否则,fn就会被加入到任务队列中。由于定时器的事件是每隔10ms就触发一次,有可能某一次事件触发的时候,上一次事件的处理方法fn还没有机会得到执行,仍然在等待队列中,这个时候,这个新的定时器事件就被丢弃,继续开始下一次计时。需要注意的是,由于JavaScript引擎这种单线程异步的执行方式,有可能两次fn的实际执行时间间隔小于设定的时间间隔。比如上一个定时器事件的处理方法触发之后,等待了5ms才获得被执行的机会。而第二个定时器事件的处理方法被触发之后,马上就被执行了。那么这两者之间的时间间隔实际上只有5ms。因此,setInterval()并不适合实现精确的按固定间隔的调度操作。

例:

console.log(1);var interval = setInterval(function(){    var date = new Date();    console.log(date.getMinutes() + ":" + date.getSeconds() + ':' + date.getMilliseconds());},10);var sum = 0;for(var i = 0; i < 1000000; i++){    sum += i;}console.log(2);//清除定时器,避免卡死浏览器setTimeout(function(){    clearTimeout(interval);},100);复制代码

总结:setTimeout()和setInterval()都不能满足精确的时间间隔。假如设定的时间间隔为10ms,则setTimeout(fn, 10)中的fn执行的时间间隔可能大于10ms,而setInterval(fn, 10)中fn执行的时间间隔可能小于10ms。

参考:

转载地址:http://jclhx.baihongyu.com/

你可能感兴趣的文章
Google 翻译的妙用
查看>>
算法导论--python--插入排序
查看>>
Hydra用户手册
查看>>
常用的集合
查看>>
Unity3D工程源码目录
查看>>
杀死进程命令
查看>>
cookie 和session 的区别详解
查看>>
浮点数网络传输
查看>>
Mongodb对集合(表)和数据的CRUD操作
查看>>
面向对象类的解析
查看>>
tomcat如何修改发布目录
查看>>
CentOS 5.5 使用 EPEL 和 RPMForge 软件库
查看>>
Damien Katz弃Apache CouchDB,继以Couchbase Server
查看>>
Target runtime Apache Tomcat is not defined.错误解决方法
查看>>
某机字长为32位,存储容量为64MB,若按字节编址.它的寻址范围是多少?
查看>>
VC++ 监视文件(夹)
查看>>
【转】keyCode对照表及JS监听组合按键
查看>>
[Java开发之路](14)反射机制
查看>>
mac gentoo-prefix安装git svn
查看>>
浅尝异步IO
查看>>