js实例教程-浏览器UI多线程及对JavaScript单线程底层运行机制详解

发布时间:2018-12-01 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了js实例教程-浏览器UI多线程及对JavaScript单线程底层运行机制详解脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。
小宝典致力于为广大程序猿(媛)提供高品质的代码服务,请大家多多光顾小站,小宝典在此谢过。

浏览器UI多线程先来上一张图

一般情况下浏览器至少有三个常驻线程

GUI渲染线程(渲染页面)

JS引擎线程(处理脚本)

事件触发线程(控制交互)

包括浏览器有时候会开辟新的线程比如用完就扔的Http请求线程(Ajax)等等其他线程

它们共同构成了浏览器的UI线程

这些线程在UI线程的控制下井然有序的工作着

关于这个常驻线程网上观点不一致,各个浏览器实现可能也不一样,这里就不深考究了

虽然我把js引擎线程放在了右下角,但是它是浏览器的主线程

而且它和GUI渲染线程是水火不容的,不能够同时工作

道理很简单,因为它们都要操作DOM,如果js线程想要某个DOM的样式,渲染引擎必须停止工作(霸道总裁让你站那儿别动)

js单线程

为什么JavaScript是单线程

单线程就是同一时间只能干一件事

那么JavaScript多线程不好吗,那效率多高啊

不好

js设计出来就是为了与用户交互,处理DOM

如果JavaScript多线程了,那就必须处理多线程同步的问题(依稀记得曾经被C++线程同步支配的恐怖)

假如js是多线程,同一时间一个线程想要修改DOM,另一个线程想要删除DOM

问题就变得复杂许多,浏览器不知道听谁的,如果引入“锁”的机制,那就麻烦死了(那我就不学前端了( ̄_, ̄ ))

所以我们这样一个脚本语言根本没有必要搞得那么复杂,所以JavaScript诞生起就是单线程执行

虽然H5提出了Web Worker,但是它不能够操作DOM,还是要委托个大哥js主线程解决

这个Web Worker我还没有了解太多,这里就不多说了

这些子线程完全受主线程大哥控制的,所以没有改变JavaScript单线程的本质

执行栈

我们先来看看什么是执行栈

栈是先进后出(FILO)的数据结构

执行栈中存放正在执行的任务,每一个任务叫做“帧”

举个例子

 [code]function foo(c){     VAR a = 1;     bar(200); } function bar(d){     var b = 2; } foo(100);

我们来看看执行栈发生了怎样的变化

开始,代码没有执行的时候,执行栈为空栈

foo函数执行时,创建了一帧,这帧中包含了形参、局部变量(预编译过程),然后把这一帧压入栈中

然后执行foo函数内代码,执行bar函数

创建新帧,同样有形参、局部变量,压入栈中

bar函数执行完毕,弹出栈

foo函数执行完毕,弹出栈

执行栈为空

执行栈其实相当于js主线程

任务队列

队列是先入先出(FIFO)的数据结构

js线程中还存在着一个任务队列

任务队列包含了一系列待处理的任务

单线程就意味着所有任务需要一个接一个的执行,如果一个任务执行的时间太长,那后面的任务就不得不等着

就好比护士阿姨给排队的小朋友打针,如果最前面的小朋友一直滚针,那就一直扎,后面的小朋友就得等着(这比喻好像不恰当)

可是如果最前面的小朋友晕针昏倒了

那么护士阿姨不可能坐那里了等到他醒来,一定是先给后面的小朋友扎针

也就是相当于把那位小朋友“挂起”(异步)

所以,任务可以分为两种

同步任务

异步任务

同步任务就是正在主线程执行栈中执行的任务(在屋子内打针的小朋友)

而异步任务是在任务队列等候处理的任务(在屋子外等候打针的小朋友)

一旦执行栈中没有任务了,它就会从执行队列中获取任务执行

事件与回调

任务队列是一个事件的队列,IO设备(输入/输出设备)每完成一项任务,就会在任务队列中添加事件处理

用户触发了事件,也同样会将回调添加到任务队列中去

主线程执行异步任务,便是执行回调函数(事件处理函数)

只要执行栈一空,排在执行队列前面的会被优先读取执行,

不过主线程会检查时间,某些事件需要到了规定时间才能进入主线程处理(定时器事件)

事件循环

主线程从执行队列不断地获取任务,这个过程是循环不断地,叫做“Event Loop”事件循环

同步任务总是会在异步任务之前执行

只有当前的脚本执行完,才能够去拿任务队列中的任务执行

前面也说到了,任务队列中的事件可以是定时器事件

定时器分为两种 setTimeout() 和 setInterval()

前者是定时执行一次,后者定时重复执行

第一个参数为执行的回调函数,第二个参数是间隔时间(ms)

来看这样一个例子

 [code]setTimeout(function(){     console.LOG('timer'); },1000); console.log(1); console.log(2); console.log(3);

这个没什么问题,浏览器打印的是 1 2 3 timer

但是这样呢

 [code]setTimeout(function(){     console.log('timer'); },0);//0延时 console.log(1); console.log(2); console.log(3);

浏览器打印依然打印的是 1 2 3 timer

也许有同学知道,旧版浏览器,setTimeout定时至少是10ms(即便你设置了0ms),

H5新规范是定时至少4ms(我读书少不知道为什么),改变DOM也是至少16ms

也许这是因为这个原因

那么我再改动一下代码

 [code]setTimeout(function(){     console.log('timer'); },0); var a = +new Date(); for(var i = 0; i < 1e5; i++){     console.log(1); } var b = +new Date(); console.log(b - a);

这回够刺激了吧,输出10w次,我浏览器都假死了(心疼我chrome)

不仅如此,我还打印了循环所用时间

来看看控制台

输出了10w个1,用了将近7s

timer依然是最后打印的

这就证明了我前面说的话:同步任务总是会在异步任务之前执行

只有我执行栈空了,才会去你任务队列中取任务执行

实例

最后我举一个例子加深一下理解

 [code]demo.onclick = function(){     console.log('click'); } function foo(a){     var b = 1;     bar(200); } function bar(c){     var d = 2;     click//伪代码 此时触发了click事件(这里我假装程序运行到这里手动点击了demo)     setTimeout(function(){         console.log('timer');     }, 0); } foo(100);

怕大家蒙我就不写Ajax了

Ajax如果处理结束后(通过Http请求线程),也会将回调函数放在任务队列中

还有一点click那一行伪代码我最开始是想用demo.click()模拟触发事件

后来在测试过程中,发现它好像跟真实触发事件不太一样

应该是不通过触发事件线程,而是存在于执行栈中,就相当于单纯地执行click回调函数

不过这只是我自己的想法有待考证,不过这不是重点,重点是我们理解这个过程,请大家不要吐槽我t( ̄ ̄)q

下面看看执行这段代码时发生了什么(主要说栈和队列的问题,不会赘述预编译过程)

主线程开始执行,产生了栈、堆、队列

demo节点绑定了事件click,交给事件触发线程异步监听

执行foo函数(之前同样有预编译过程),创建了帧包括foo函数的形参、局部变量压入执行栈中

foo函数内执行bar函数,创建帧包括bar函数的形参、局部变量压入执行栈中

触发了click事件,事件触发线程将回调事件处理函数放到js线程的任务队列中

触发了定时器事件,事件触发线程立即(4ms)将回调处理函数放到js线程的任务队列中

bar函数执行完毕,弹出栈

foo函数执行完毕,弹出栈

此时执行栈为空

执行栈向任务队列中获取一个任务:click回调函数,输出‘click’

执行栈项任务队列中获取一个任务:定时器回调函数,输出‘timer’

执行结束

这里从任务队列里不断取任务的过程就是Event Loop

有一些我的理解,如果发现不对或者有疑问的地方,请联系我

相信大家看了这个例子应该对js底层运行机制有了一个大概的了解

浏览器UI多线程先来上一张图

一般情况下浏览器至少有三个常驻线程

GUI渲染线程(渲染页面)

JS引擎线程(处理脚本)

事件触发线程(控制交互)

包括浏览器有时候会开辟新的线程比如用完就扔的Http请求线程(Ajax)等等其他线程

它们共同构成了浏览器的UI线程

这些线程在UI线程的控制下井然有序的工作着

关于这个常驻线程网上观点不一致,各个浏览器实现可能也不一样,这里就不深考究了

虽然我把js引擎线程放在了右下角,但是它是浏览器的主线程

而且它和GUI渲染线程是水火不容的,不能够同时工作

道理很简单,因为它们都要操作DOM,如果js线程想要某个DOM的样式,渲染引擎必须停止工作(霸道总裁让你站那儿别动)

js单线程

为什么JavaScript是单线程

单线程就是同一时间只能干一件事

那么JavaScript多线程不好吗,那效率多高啊

不好

js设计出来就是为了与用户交互,处理DOM

如果JavaScript多线程了,那就必须处理多线程同步的问题(依稀记得曾经被C++线程同步支配的恐怖)

假如js是多线程,同一时间一个线程想要修改DOM,另一个线程想要删除DOM

问题就变得复杂许多,浏览器不知道听谁的,如果引入“锁”的机制,那就麻烦死了(那我就不学前端了( ̄_, ̄ ))

所以我们这样一个脚本语言根本没有必要搞得那么复杂,所以JavaScript诞生起就是单线程执行

虽然H5提出了Web Worker,但是它不能够操作DOM,还是要委托个大哥js主线程解决

这个Web Worker我还没有了解太多,这里就不多说了

这些子线程完全受主线程大哥控制的,所以没有改变JavaScript单线程的本质

执行栈

我们先来看看什么是执行栈

栈是先进后出(FILO)的数据结构

执行栈中存放正在执行的任务,每一个任务叫做“帧”

举个例子

 [code]function foo(c){     var a = 1;     bar(200); } function bar(d){     var b = 2; } foo(100);

我们来看看执行栈发生了怎样的变化

最开始,代码没有执行的时候,执行栈为空栈

foo函数执行时,创建了一帧,这帧中包含了形参、局部变量(预编译过程),然后把这一帧压入栈中

然后执行foo函数内代码,执行bar函数

创建新帧,同样有形参、局部变量,压入栈中

bar函数执行完毕,弹出栈

foo函数执行完毕,弹出栈

执行栈为空

执行栈其实相当于js主线程

任务队列

队列是先入先出(FIFO)的数据结构

js线程中还存在着一个任务队列

任务队列包含了一系列待处理的任务

单线程就意味着所有任务需要一个接一个的执行,如果一个任务执行的时间太长,那后面的任务就不得不等着

就好比护士阿姨给排队的小朋友打针,如果最前面的小朋友一直滚针,那就一直扎,后面的小朋友就得等着(这比喻好像不恰当)

可是如果最前面的小朋友晕针昏倒了

那么护士阿姨不可能坐那里了等到他醒来,一定是先给后面的小朋友扎针

也就是相当于把那位小朋友“挂起”(异步)

所以,任务可以分为两种

同步任务

异步任务

同步任务就是正在主线程执行栈中执行的任务(在屋子内打针的小朋友)

而异步任务是在任务队列等候处理的任务(在屋子外等候打针的小朋友)

一旦执行栈中没有任务了,它就会从执行队列中获取任务执行

事件与回调

任务队列是一个事件的队列,IO设备(输入/输出设备)每完成一项任务,就会在任务队列中添加事件处理

用户触发了事件,也同样会将回调添加到任务队列中去

主线程执行异步任务,便是执行回调函数(事件处理函数)

只要执行栈一空,排在执行队列前面的会被优先读取执行,

不过主线程会检查时间,某些事件需要到了规定时间才能进入主线程处理(定时器事件)

事件循环

主线程从执行队列不断地获取任务,这个过程是循环不断地,叫做“Event Loop”事件循环

同步任务总是会在异步任务之前执行

只有当前的脚本执行完,才能够去拿任务队列中的任务执行

前面也说到了,任务队列中的事件可以是定时器事件

定时器分为两种 setTimeout() 和 setInterval()

前者是定时执行一次,后者定时重复执行

第一个参数为执行的回调函数,第二个参数是间隔时间(ms)

来看这样一个例子

 [code]setTimeout(function(){     console.log('timer'); },1000); console.log(1); console.log(2); console.log(3);

这个没什么问题,浏览器打印的是 1 2 3 timer

但是这样呢

 [code]setTimeout(function(){     console.log('timer'); },0);//0延时 console.log(1); console.log(2); console.log(3);

浏览器打印依然打印的是 1 2 3 timer

也许有同学知道,旧版浏览器,setTimeout定时至少是10ms(即便你设置了0ms),

H5新规范是定时至少4ms(我读书少不知道为什么),改变DOM也是至少16ms

也许这是因为这个原因

那么我再改动一下代码

 [code]setTimeout(function(){     console.log('timer'); },0); var a = +new Date(); for(var i = 0; i < 1e5; i++){     console.log(1); } var b = +new Date(); console.log(b - a);

这回够刺激了吧,输出10w次,我浏览器都假死了(心疼我chrome)

不仅如此,我还打印了循环所用时间

来看看控制台

输出了10w个1,用了将近7s

timer依然是最后打印的

这就证明了我前面说的话:同步任务总是会在异步任务之前执行

只有我执行栈空了,才会去你任务队列中取任务执行

实例

最后我举一个例子加深一下理解

 [code]demo.onclick = function(){     console.log('click'); } function foo(a){     var b = 1;     bar(200); } function bar(c){     var d = 2;     click//伪代码 此时触发了click事件(这里我假装程序运行到这里手动点击了demo)     setTimeout(function(){         console.log('timer');     }, 0); } foo(100);

怕大家蒙我就不写Ajax了

Ajax如果处理结束后(通过Http请求线程),也会将回调函数放在任务队列中

还有一点click那一行伪代码我最开始是想用demo.click()模拟触发事件

后来在测试过程中,发现它好像跟真实触发事件不太一样

它应该是不通过触发事件线程,而是存在于执行栈中,就相当于单纯地执行click回调函数

不过这只是我自己的想法有待考证,不过这不是重点,重点是我们理解这个过程,请大家不要吐槽我t( ̄ ̄)q

下面看看执行这段代码时发生了什么(主要说栈和队列的问题,不会赘述预编译过程)

主线程开始执行,产生了栈、堆、队列

demo节点绑定了事件click,交给事件触发线程异步监听

执行foo函数(之前同样有预编译过程),创建了帧包括foo函数的形参、局部变量压入执行栈中

foo函数内执行bar函数,创建帧包括bar函数的形参、局部变量压入执行栈中

触发了click事件,事件触发线程将回调事件处理函数放到js线程的任务队列中

触发了定时器事件,事件触发线程立即(4ms)将回调处理函数放到js线程的任务队列中

bar函数执行完毕,弹出栈

foo函数执行完毕,弹出栈

此时执行栈为空

执行栈向任务队列中获取一个任务:click回调函数,输出‘click’

执行栈项任务队列中获取一个任务:定时器回调函数,输出‘timer’

执行结束

这里从任务队列里不断取任务的过程就是Event Loop

有一些我的理解,如果发现不对或者有疑问的地方,请联系我

相信大家看了这个例子应该对js底层运行机制有了一个大概的了解

觉得可用,就经常来吧!Javascript技巧 脚本宝典 欢迎评论哦!&nbsp;js技巧,巧夺天工,精雕玉琢。小宝典献丑了!

脚本宝典总结

以上是脚本宝典为你收集整理的js实例教程-浏览器UI多线程及对JavaScript单线程底层运行机制详解全部内容,希望文章能够帮你解决js实例教程-浏览器UI多线程及对JavaScript单线程底层运行机制详解所遇到的问题。

如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。