JS完美收官 js运行机制底层原理
一、浏览器常驻的线程
我们常说js是单线程,其实在浏览器器中包含了很多种线程
1、浏览器中都是具有这5个线程
01/
js引擎线程
用来解释和执行js代码,用户输入、网络请求
02/
GUI线程
绘制用户界面(与js主线程是互斥的)
03/
HTTP网络请求线程
前端用ajax发起异步请求(get、post等),服务器返回请求数据以后,要触发的回调函数,将回调函数推入任务队列
04/
定时器触发线程
当setTimeout 和 setTnterval等待时间结束以后,把执行函数推入任务队列中
05/
浏览器事件处理线程
将click、mouse等交互事件发生后,将这些事件放入事件队列中
2、UI主线程负责协调运转这5个线程
大部分浏览器中都是具有这5个线程的,这5个线程都是通过UI主线程负责协调运作的,UI主线程是如何来进行协调工作呢?
javaScript引擎线程里面,每一个矩形都可以当做一个任务,可以理解为函数的执行
无论是当触发浏览器事件处理线程,把任务推到js主线程之中,
还是定时器隔了一段时间,把任务放到js主线程里面去,
或者是网络请求之后数据往回传,回调函数被触发也是当做一个任务,
这几种任务最后都会推到js主线程中,然后进行处理
二、JS引擎线程和GUI线程互斥
点击按钮时,发生两件事
var oBtn = document.getElementById('btn'); oBtn.onclick = function(){ console.log(); }
第一件事:点击按钮会触发事件,把对应事件处理函数当做一个任务,并放到JavaScript引擎线程里等待执行
第二件事:同时点击一个按钮时,有一个浏览器默认样式的变化,浏览器GUI处理渲染线程,进行按钮样式的变化的处理
不过多说其它线程,只说JavaScript引擎线程,因为它是执行js代码的灵魂
js引擎线程和GUI线程是互斥是什么意思?
比如在点击按钮之前,js在执行一个死循环,这时候由于js线程在不断的工作始终没有停,这时候再点击按钮时没有默认样式变化
var oBtn = document.getElementById('btn'); dieLoop(); // 调用死循环 oBtn.onclick = function(){ console.log('无所谓,因为上面死循环,不会往下执行。这是js单线程所带来的状况'); } function dieLoop(){ while(true){ } }
在说一下互斥的意思,当点击按钮的时候,js死循环已经在执行了,js循环不执行完,点击按钮不会有样式的变化
PPT概念
JS可以操作DOM元素,进而会影响到GUI的渲染结果,因此JS引擎线程与GUI渲染线程是互斥的。也就是说当JS引擎线程处于运行状态时,GUI渲染线程将处于冻结状态。
三、JS是单线程
单线程的意思:
同一个时间段只能做一件事,
浏览器的工作基本是以线程为最小单元,js既然是单线程,说明同一时间段不可能做多个事件
多线程不好吗?(ppt概念)
js设计出来就是为了与前端用户交互,大部分工作是用来处理DOM,
假如js是多线程,同一时间段内,一个线程想要修改DOM,另一个线程想要删除DOM,这时候会衍生出很多复杂的问题,
浏览器不知道听谁的,如果引入"锁"的机制,这不就又回到了被其他语言尴尬的困境了,比如c++ java
如果需要大量数据渲染的时候怎么办?
单线程计算能力有限,比如同时十万条数据需要用GUI来渲染,并且频繁的进行dom删改,
这种情况可以配合后端进行操作,比如VUE与nodejs配合,有一个技术叫SSR,服务器端渲染技术,
铺垫完了下面是重点,Js引擎执线程的行机制是什么样子?
四、Js引擎执线程的行机制
JavaScript是基于单线程运行的,同时又可以异步执行(计算机中的异步,同一事件段你能干两件事)
既然是单线程,为什么又可以异步执行,同一时间段能干两件事?
js引擎主线程是单线程的,确实一个时间段只能干一件事情,一般来说这种既是单线程,又是异步的语言都是基于事件来驱动的
恰好浏览器就给JavaScript提供了这么一个环境,这个环境除了js引擎线程运行,还有其它线程进行配合,
由于其它线程的介入或者说其它线程的辅助,最后呈现出来js在浏览器中运行的时候,既可以同步又可以异步
JS任务的执行机制是什么样的呢?
首先如果任务是一个普通函数的执行,不涉及到异步网络请求、事件触发、定时器,这样一个普通的任务,就是一个同步的任务,
同步任务放到主线程中,主线程是单线程的,任务必须一个一个的执行,一个执行完再执行下一个,当主线程中的任务一个一个都执行完毕
执行完毕以后会怎么样呢?
看绿色的方块,读取任务队列中的结果,进入主线程执行
什么是任务队列?
如果一开始任务不是普通任务是异步任务,看右边紫色部分
异步任务会进入到Event Table中,先注册一下有这样的一个函数(在之后要在某一个时间段被触发),
注册完以后,
等待定时器延迟事件到了,函数或者说任务要被执行了,
或者发送网络请求数据返回到前端,要执行回调函数了
这时候
要把函数或者说任务放到Event Queue里面(事件队列里面),队列的概念是先进先出(羽毛球桶),
主线程所有的执行完以后,会看事件队列里面的任务,如果有任务就拿出来执行
上述整个不断去看任务队列的过程,就是常说的Event Loop(事件循环)。
同步任务
除了ajax异步网络请求、定时器、事件的触发以外,所有的任务都叫做同步任务,
function foo(fo){ function bar(ba){ console.log(ba) } bar(10); console.log(fo); } foo(20);
0). 代码没有执行的时候,执行栈为空栈
1). foo函数执行时,创建了一帧,这帧中包含了形参、局部变量(预编译过程),然后把这一帧压入栈中
2). 然后执行foo函数内代码,执行bar函数
3). 创建新帧,同样有形参、局部变量,也被压入栈中
4). bar函数执行完毕,弹出栈
5). foo函数执行完毕,弹出栈
6). 执行栈为空
栈为空以后,看事件队列中有没有新的任务,有任务放到栈中在执行
执行栈其实相当于js主线程,也就是说执行栈同一时间段只能干一件事件
异步任务
Ajax异步任务会进入Event Table里面,注册回调函数success
$.ajax({ url: 'localhost:/js/demo.json', data: {}, success: function (data) { console.log(data); } }); console.log(‘run’);
1). Ajax异步任务会进入Event Table里面,注册回调函数success
2). 一定会先执行 console.log('run')
3). ajax事件请求数据回来,http网络请求线程把任务放入Event Queue队列里以后,
4). 主线程(调用栈)也空了,读取任务队列到栈里面,再执行success函数
五、重新理解定时器
var starTime = +new Date(); var timer = setInterval(function(){ console.log('这里是定时器:' , +new Date() - starTime); clearInterval(timer); }, 0); sleep(100000000); function sleep(t){ for(var i = 0; i <= t; i++){ if(i === t){ console.log('已循环' + i); } } }
setTimeout的等待时间结束后,并不是直接执行的而是先推入浏览器的一个任务队列,在同步队列结束后在依次调用任务队列中的任务。
setTimeout(function(){}, 0)Js主线程中的执行栈为空时,0毫秒实际上也达不到的,根据HTML标准,最低4毫秒。
setInterval是每隔一段时间把任务放到Event Queue之中
定时器不准,
定时器底部是红黑数写的,
js的执行机制也导致定时器不准