剖析浏览器执行JavaScript

您所在的位置:网站首页 浏览器对javascript脚本程序进行什么执行 剖析浏览器执行JavaScript

剖析浏览器执行JavaScript

#剖析浏览器执行JavaScript| 来源: 网络整理| 查看: 265

JavaScript 单线程

单线程,即只有一个主线程。同一时刻只能运行一行代码、同一时刻不能处理多个任务(不支持并行)。

假设JS同时有2个线程,线程A在某个DOM节点添加内容,线程B删除该节点,浏览器应该以哪个线程为准?为避免复杂性,JavaScript设计之初就确定为“单线程”。

浏览器是事件驱动(Event Driven),JS运行在浏览器中是单线程的(JS也必须遵循事件驱动的规则)。

JavaScript引擎

JavaScript是专门处理JS脚本的虚拟机,通常会附在浏览器里面,例如Chrome的V8引擎。

任务队列

单线程意味着所有任务执行前需要排队,前一个任务执行结束,后一个任务才会执行。如果前一个任务耗时很长,则后一个任务就必须等待,从而无法执行。

所有任务分为同步任务和异步任务:

同步任务(synchronous)

主线程中排队执行的任务,只有前一个任务执行完成,才能执行后一个任务。

JS中常见的同步行为:if-else、for、while等

JS Promise Study console.log("start..."); let str = "hello world"; console.log(str); if (str.length > 0) { console.log(str.length) } else { console.log(-1); } var cur= Date.now(); for (var i = 0; i < 10000; i++) {} // 每次输出结果都不一样,每次for循环时间不一样 console.log(Date.now()-cur); console.log("Today is a good day."); console.log("end...");

浏览器输入index.html之后,打开调试界面运行结果如下图所示:

index.hml代码解析:

异步任务(asynchronous) 不进入主线程,而进入“任务队列(task queue)”,只有等主线程任务全部执行完成之后,“任务队列”开始通知主线程,请求执行任务,“任务队列”中的任务才会进入主线程执行。   JS中常见的异步行为:定时器(setTimeout函数,setInterval函数)、ajax(有异步,也有同步)、事件(onload onclick)、promise、async、await。   执行栈

函数执行时会生成新的执行上下文(execution context),执行上下文包含当前函数的参数、局部变量之类的信息,这些信息被推入栈中。正在执行的上下文(running execution context)始终处于栈顶,函数执行结束后,它的执行上下文从栈中弹出。

  function bar() { console.log('bar'); } function foo() { console.log('foo'); bar(); } foo();

执行过程中栈的变化:

  异步运行机制 异步运行机制一般由4步组成: 注:同步执行可被视为没有异步任务的异步执行,是 特殊的异步运行。   第一步:所有同步任务都在主线程上执行,形成一个 执行栈( 概念如上)。 第二步:主线程之外,存在一个“任务队列(task queue)”。只要异步任务有了运行结果,就在“任务队列”中放置一个事件。 第三步:一旦“执行栈”中所有任务执行完毕,系统会读取“任务队列”。开始执行任务。 第四步:主线程不断重复第三步。   只要主线程空了,就会读取“任务队列”。这个过程会不断重复,这就是JavaScript的运行机制。   任务队列也可以理解为消息队列。   “回调函数”:被主线程挂起来的代码。异步任务必须指定回调函数,主线程开始执行异步任务,就是执行对应的回调函数。例如,ajax的success, complete, error都是指定各自的回调函数。这些函数加入“任务队列”,等待执行。   事件循环机制

上图解释:

同步和异步任务分别进入不同的执行"场所",同步的进入主线程,异步的进入Event Table并注册函数。当指定的事情完成时,Event Table会将这个函数移入Event Queue。当栈中的代码执行完毕,执行栈(call stack)中的任务为空时,就会读取任务队列(Event quene)中的事件,去执行对应的回调如此循环,形成js的事件循环机制(Event Loop)

Event Table是个注册站:调用栈让Event Table注册一个函数。例如,注册某函数A(该函数5s之后被调用)。

Event Queue:任务队列,实质是个缓冲区域。例如,5s之后函数A会被移至Event Queue,A等着被调用并移到调用栈。

Event Loop:事件循环。JavaScript引擎有monitoring process(监督器)会持续不断检查执行栈是否为空,一旦为空,就会检查Event Queue中是否有被等待调用的函数。如果存在,监督器就会调用此函数并移至执行栈中。如果Event Queue为空,监督器会继续不定期检查。整个过程就是Event Loop。

另外一种解释

事件循环描述如下:

1. 函数入栈,当Stack中执行到异步任务的时候,就将他丢给WebAPIs,接着执行同步任务,直到Stack为空;

2. 在此期间WebAPIs完成这个事件,把回调函数放入CallbackQueue中等待;

3. 当执行栈为空时,Event Loop把Callback Queue中的一个任务放入Stack中,回到第1步。

Call Stack (栈):存储同步任务(立即执行、不耗时的任务,例如:初始化变量、绑定事件等。)

Memory Heap(堆):存储声明的变量、对象。

Callback Queue(消息队列):一旦某个异步任务有了响应就会被添加至队列中,即存放异步任务的回调函数。例如,点击事件、浏览器收到服务请求响应等。

Event Loop(事件循环):由JavaScript宿主环境(例如浏览器)实现。

Web APIs:Web API的统称,主要用于浏览器交互效果。Web API是浏览器提供的一套操作浏览器功能和页面元素的API(BOM和DOM)。

两种解释不矛盾,二者关系如下图所示:

解释1为从JS引擎角度出发,解释2从浏览器角度出发。可以理解为一个是内部,一个是外部。

实例说明1 var start=new Date(); setTimeout(function cb(){ console.log("时间间隔:",new Date()-start+'ms'); },500); while(new Date()-start JS Promise Study console.log("start..."); setTimeout(function() { console.log("第一次异步调用。。。"); }, 1000); let str = "hello world"; console.log(str); if (str.length > 0) { console.log(str.length) } else { console.log(-1); } var cur= Date.now(); for (var i = 0; i < 10000; i++) {} // 每次输出结果都不一样,每次for循环时间不一样 console.log(Date.now()-cur); setTimeout(function() { console.log("第二次异步调用。。。"); }, 1000); console.log("Today is a good day."); setTimeout(function() { console.log("第三次异步调用。。。"); }, 500); setTimeout(function() { console.log("第四次异步调用。。。"); }, 200); console.log("end..."); setTimeout(function() { console.log("第五次异步调用。。。"); }, 100); setTimeout(function() { console.log("第六次异步调用。。。"); }, 1600); setTimeout(function() { console.log("第七次异步调用。。。"); }, 2500); setTimeout(function() { console.log("第八次异步调用。。。"); });

运行结果:

等待时间越短,CPU响应优先级越高。

消息1-消息8,

时间响应内容:消息1(1000),消息2(1000), 消息3(500),消息4 (200),消息5( 100),消息6( 1600), 消息7(2500),消息8( 默认为0)

响应优先级(从大到小):消息8( 默认为0),消息5( 100),消息4 (200),消息3(500),消息1(1000),消息2(1000),消息6( 1600), 消息7(2500)

消息1:为函数设定响应时间1000ms

消息2:为函数设定响应时间1000ms

。。。。

注:响应时间相同时,越在上面越早等待,优先级越高。

尽管我们设置了setTimeout(function,time)中的等待时间为0,结果其中的function还是后执行。

火狐浏览器的api文档有这样一句话:Because even though setTimeout was called with a delay of zero, it's placed on a queue and scheduled to run at the next opportunity, not immediately. Currently executing code must complete before functions on the queue are executed, the resulting execution order may not be as expected.

意思就是:尽管setTimeout的time延迟时间为0,其中的function也会被放入一个队列中,等待下一个机会执行,当前的代码(指不需要加入队列中的程序)必须在该队列的程序完成之前完成,因此结果可能不与预期结果相同。

宏任务(Macrotasks)

每次执行栈执行的代码就是一个宏任务[macro-task(Task) ]。执行栈代码:同步任务和异步任务(event loop)。

异步任务来源:(事件队列中的每一个事件都是一个macrotask)

setTimeoutsetIntervalsetImmediateI/OUI rendering

宏任务执行期间不会执行其他任务(JS单线程本质)。

浏览器为了使JS内部的task与DOM任务能够有序执行,在一个task执行结束后,在下一个task执行开始前,重新渲染页面。

(task1 -> 渲染 -> task2 ...)

微任务(Microtasks)

当前任务执行结束后立即执行的任务[micro-task(Job) 微任务],在渲染之前。宏任务无需等待渲染,因此宏任务执行完成之后,在其执行期间产生的微任务都会在渲染前执行完毕。一个event loop中只有一个microtask队列。

微任务的任务源

process.nextTickPromiseObject.observeMutationObserver 宏任务执行结束之后,继续执行相关的微任务(process.nextTick 、 Promise、Object.observe、MutationObserver)部分, 微任务执行结束之后,继续执行下一个宏任务。   流程图如下所示:     执行一个宏任务(栈中没有就从事件队列中获取)执行过程中如果遇到微任务,就将它添加到微任务的任务队列中宏任务执行完毕后,立即执行当前微任务队列中的所有微任务(依次执行)当前宏任务执行完毕,开始检查渲染,然后GUI线程接管渲染渲染完毕后,JS线程继续接管,开始下一个宏任务(从事件队列中获取) 实例说明 console.log("start..."); console.log("test 01"); Promise.resolve().then(function promise1 () { console.log('promise1'); }); console.log("test 02"); Promise.resolve().then(function promise1 () { console.log('promise2'); }); setTimeout(function setTimeout1 () { console.log('setTimeout1'); Promise.resolve().then(function promise2 () { console.log('promise5'); }); console.log('setTimeout1-1'); }, 0); Promise.resolve().then(function promise1 () { console.log('promise3'); }); console.log("test 03"); setTimeout(function setTimeout2 () { console.log('setTimeout2') }, 0); Promise.resolve().then(function promise1 () { console.log('promise4'); }); console.log("end...");

运行结果:

参考链接: https://www.jianshu.com/p/5686c1f80f49 (浏览器线程)   https://www.cnblogs.com/mininice/p/4298952.html (浏览器常驻线程)   http://www.ruanyifeng.com/blog/2014/10/event-loop.html (JavaScript 运行机制详解:再谈Event Loop)   https://www.cnblogs.com/wxcbg/p/11040362.html(浏览器事件循环机制(event loop))   https://www.jianshu.com/p/667a20d008cf (JS JavaScript事件循环机制)   https://www.bilibili.com/video/av82229058/ (浏览器多进程与JS单线程-深入浅出)


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3