浏览器中JS的执行
JS是在浏览器中运行的,浏览器为了运行JS, 必须要编译或解释JS,因为JS是高级语言,计算机不认识,必须把它编译或解释成机器语言,其次,在运行JS的过程,浏览器还要创建堆栈,因为程序是在栈中执行,执行过程中的创建的对象是在堆中。浏览器的JS引擎,比如V8,就是做这些事的。JS引擎负责编译或解释JS,并创建堆栈来运行JS。
比如,执行以下代码,
function multiply (x, y) {
return x * y
} function printSquare (x) {
const s = multiply(x, x)
console.log(s)
} printSquare(5)
程序初始化,栈为空;程序开始执行,调用printSquare(5),printSquare函数入栈并执行,它调用了multiply(x, x), multiply函数入栈并执行,执行完毕返回25,multiply函数弹栈,回到printSquare, 执行它后面的代码,也就是console.log , console.log 也是函数,进栈,执行完,弹栈,然后回到printSquare,,执行consoe.log 后面的代码,后面没有代码了,printSquare也就执行完了,弹栈,回到调用printSquare的地方,执行它后面的代码,它后面也没有代码,所有程序执行完毕,栈为空。整个调用栈的情况如下图所示,
在JS中,栈就是记录了程序执行到了什么地方,如果调用一个函数,这个函数就放到栈中,如果从函数中返回,就把该函数弹出栈。每一次的调用,都会创建stack frame。程序执行出错,也可以通过调用栈,追踪到程序在什么地方出错。
function foo() {
throw new Error('SessionStack will help you resolve crashes :)');
}
function bar() {
foo();
}
function start() {
bar();
}
start();
错误信息如下,start调用了bar,bar调用了foo,foo报错了。
如果函数一直调用呢? 那就栈溢出了。因为栈在内存中开辟的,内存不可能无限大,内存是有限的,栈也就是有限。递归处理不好,容易栈溢出。
function f () {
return f()
} f()
函数的调用栈如下
通过上面的例子,你会发现,只有一个栈在执行程序,这就是JS的单线程,JS引擎中只有一个调用栈,一次只能处理一件事情。调用栈并不属于JS,它是JS引擎的一部分。
如果仅仅是运行JS,作用也不大,因为JS本身没有输入或输出等与外界交互的能力,因此浏览器除了包含JS引擎,还提供了JS与外界交互的能力。这些能力是通过API提供的,比如document, fetch等等,把它们注入到JS的全局作用域中,在JS运行时,可以直接使用它们。这些API统称为 web API,或外部API,因为它们也不属于JS。运行JS并能和外部交互,这很好,但也会带来一个问题, 比如,fetch() 向服务器请求数据,可能要很长时间,JS是单线程也就意味着,要等到它执行结束,才能执行它后面的代码,如果一直等,那后面的代码就不用执行了,浏览器也就卡死了。如果某件事情执行时间过长,怎么办?异步处理。为了支持异步,浏览器提供了事件循环和事件队列,以及向事件队列中插入事件的功能。因此JS的运行时,也就是浏览器,要包含以下几部分
假设执行如下代码
console.log('js')
setTimeout(function cb() { console.log(' awesome!') }, 5000)
console.log(' is')
console.log(‘js’),函数入调用栈并执行,控制台输出js, 函数执行完毕,弹栈,
setTimeout()执行,这是一个Web API,是浏览器内部实现的,调用Web API,只是告诉浏览器帮我们做事情,setTimeout是告诉浏览器5s之后执行cb函数,
告诉完浏览器,setTimeout也就执行完了,弹栈,此时浏览器设置定时器,并开始倒数计时,
console.log('is')执行,进栈,出栈,浏览器控制台输出is.
5s 过后,计时器完成计时,浏览器把回调函数cb放到了事件队列中
此时,事件循环发现事件队列中有一个事件,它就会检查调用栈是不是空,如果调用栈为空,它就会把事件拿出来,放到调有栈中。
回调函数cb执行,console.log(‘awesome’) 进栈,出栈,控制台输出awesome,回调函数执行完了, 出栈。
整个程序执行完毕。在整个程序的执行过程中,异步的实现或异步代码的执行,是浏览器帮我们安排的,浏览器安排异步代码,插入到事件队列中,事件循环则调用JS引擎,从事件队列中取出要执行的代码发给它。JS引擎只不过是一个按需执行的环境来执行JS代码。从事件队列中取出事件到调用栈中执行,也称为一个tick.
到了ES6,增加了Promise,情况有所变化。Promise异步的处理方式和传统的回调函数处理方式不一样,promise中注册的回调函数称为Job或Micortask,所以JS从概念上定义了两个队列,Microtask或Job队列和Macrotask队列,而不再是一个队列。Promise完成后的回调,是放到Microtask或Job队列中,传统回调函数放到Macrotask队列,当然,它们不仅仅处理这些。因为有了两个队列,tick的定义也要改一下,从Macrotask队列中取出事件并执行,称为一个tick。主程序执行完毕,先检查Micortask队列中有没有micortask或job(回调函数),如果有,就会执行该micortask,执行完毕后,还是检查Micortask队列中有没有事microtask,直到Micortask队列中所有microtask执行完毕,它才执行Macrotask队列中的macrotask,从中取出一个开始执行(tick),如果在一个tick的执行过程中,有一个Promise完成了,这个Prmise注册的回调函数(microtask),并不是插入到整个Macrotask队列的后面,而是插入到当前tick后面的Micortask队列中,Micotask队列就是附在事件循环中每一个tick后面的队列。当tick执行完毕,从它后面的Microtask队列中取出microtask,进行执行,由于事件中还可能有promise完成,promise注册的回调函数,又会插入到当前tick后的Microtask中,形成一个Microtask队列,所以要等到后面的Microtask队列中所有microtask执行完毕,再从Macrotask取出一个事件执行。
这里要注意,由于Microtask可能执行其它Microtask,Microtask队列可以一直增加下去,如要是这样的话,事件循环就不能从当前tick中跳出,后面的Macrotask就无法执行。为了阻止这种情况发生,浏览器内置了保护机制,一个tick最多执行1000个microtasks,执行完成后,执行下一个macrotask.
console.log('script start') const interval = setInterval(() => {
console.log('setInterval')
}, 0) setTimeout(() => {
console.log('setTimeout 1') Promise.resolve()
.then(() => console.log('promise 3'))
.then(() => console.log('promise 4'))
.then(() => {
setTimeout(() => {
console.log('setTimeout 2')
Promise.resolve().then(() => console.log('promise 5'))
.then(() => console.log('promise 6'))
.then(() => clearInterval(interval))
}, 0)
})
}, 0) Promise.resolve()
.then(() => console.log('promise 1'))
.then(() => console.log('promise 2'))
程序执行,也可以称为第一个tick。console.log(), 进栈,执行,出栈,控制台输出script start。setInterval进栈,告诉浏览器每隔0s,控制台输出setInterval,浏览器设置定时器,setInterval执行完毕,出栈。setTimeout进栈,告诉浏览器0s后,执行一段代码,浏览器设置定时器,setTimeout执行完毕,出栈. Promise.resovle执行,两个then回调函数放入到microtasks队列中。程序执行完毕,第一个tick执行完毕,此时要检查当前tick后面的microtasks队列有没有task。有,就是Promise.resovle的两个回调,依次执行,控制台输出promise 1 和 promise 2。0s肯定过了,浏览器把setInterval和setTimeout放入到macrotask队列中。
每二个tick,从macrotask队列中取出settInterval 的回调函数,控制台输出settInterval ,它没有产生microtask,也就没有microtasks队列,0s过了,浏览器又到macrotask队列中放入settInterval 。此时macrotask队列中 [setTimeout, settInterval]
第三个tick,setTimeout注册的回调函数执行,控制台输出 setTimeout 1,Promise.resovle执行,三个then放入到microtasks,microtasks是放到当前tick后,tick执行完毕,检查它后面的microtasks队列,有。依次执行,控制台输出Promise 3和 Promise 4,另外一个setTimeout放到macrotask队列中,称它为setTimeout2。此时,macrotask队列[settInterval, setTimeout ]
第四个tick,从macrotask队列中取出settInterval 的回调函数,控制台输出settInterval ,它没有产生microtask,也就没有microtasks队列,0s过了,浏览器又到macrotask队列中放入settInterval 。此时macrotask队列中 [setTimeout2, settInterval]
第五个tick,setTimeout2注册的回调函数执行,控制台输出 setTimeout 2,Promise.resovle执行,三个then放入到microtasks,microtasks是放到当前tick后,tick执行完毕,检查它后面的microtasks队列,有。依次执行,控制台输出Promise 5和 Promise 6,同时清除掉了setInterval,此时,macrotask队列[]。
浏览器中JS的执行的更多相关文章
- 浏览器中js执行机制学习笔记
浏览器中js执行机制学习笔记 RiverSouthMan关注 0.0772019.05.15 20:56:37字数 872阅读 291 同步任务 当一个脚本第一次执行的时候,js引擎会解析这段代码,并 ...
- 浏览器中 JS 的事件循环机制
目录 事件循环机制 宏任务与微任务 实例分析 参考 1.事件循环机制 浏览器执行JS代码大致可以分为三个步骤,而这三个步骤的往复构成了JS的事件循环机制(如图). 第一步:主线程(JS引擎线程)中执行 ...
- 解决webkit浏览器中js方法中使用window.event提示未定义的问题
这实际上是一个浏览器兼容性问题,根源百度中一大堆,简要说就是ie中event对象是全局变量,所以哪里都能使用到,但是webkit内核的浏览器中却不存在这个全局变量event,而是以一个隐式的局部变量的 ...
- 解决在IE浏览器中JQuery.resize()执行多次的方法(转)
最近在做前台效果的时候用到了JQuery提供的resize()事件.resize 这个事件是监听浏览器窗口的放大与缩小,也就是说浏览器窗口大小的变化. 我在W3CSCHOOL上面查阅的时候,提供了一个 ...
- 浏览器中js怎么将图片下载而不是直接打开
网上找了好多方法都是不能用的,经过试验在Chrome中都是直接打开. 经过自己的摸索,找到了一套能用的解决方案 var database = "data:image/jpg;base64,& ...
- js 在浏览器中的event loop事件队列
目录 前言 认识一个栈两个队列 执行过程 异步任务怎么分配 简单例子 难一点的例子 前言 以下内容是js在浏览器中的事件队列执行,与在nodejs中有所区别,请注意. 都说js是单线程的,不过它本身其 ...
- 权威指南学习心得-浏览器中的js
window对象:表示web了浏览器的一个窗口或窗体(winow属性引用自身) 含有以下属性:location包含Location对象,指定当前显示在窗口中URL,允许脚本往窗口里载入新的URL 含有 ...
- 浏览器中Javascript的加载和执行
在刚学习Javascript时曾对该问题在小组内做个一次StudyReport,发现其中的基础还是值得分析的. 从标题分析,可以加个Javascript的加载和执行分为两个阶段:加载.执行.而加载即浏 ...
- 浏览器中JavaScript执行原理
本章我们讨论javascript在浏览器中是如果工作的,包括:下载.解析.执行的全过程.javascript的这些讨人嫌的地方我们是知道的: i.需要串行下载 ii.需要解析 iii.需要串行执行 而 ...
- 转《在浏览器中使用tensorflow.js进行人脸识别的JavaScript API》
作者 | Vincent Mühle 编译 | 姗姗 出品 | 人工智能头条(公众号ID:AI_Thinker) [导读]随着深度学习方法的应用,浏览器调用人脸识别技术已经得到了更广泛的应用与提升.在 ...
随机推荐
- 用 Certbot-auto 在 letsencrypt.org申请免费 SSL 证书实现 HTTPS
参考帖子 https://www.cnblogs.com/lzpong/p/6433189.html https://www.cnblogs.com/756623607-zhang/p/1163850 ...
- leaflet 加载geojson叠加显示
geojson需要先制作shp,然后导入下面网站生成geojson. https://mapshaper.org/ geojson,最好放后台,前台通过异步请求去加载json,然后显示. getGeo ...
- salesforce零基础学习(一百三十六)零碎知识点小总结(八)
本篇参考: Salesforce LWC学习(七) Navigation & Toast https://developer.salesforce.com/docs/platform/lwc/ ...
- 我的书《Unity3D动作游戏开发实战》出版了
首先感谢帮助和参与前期检阅的朋友们.本书是我经验积累的提炼,书中既有干货分享也有对基础内容的详解补充. 同时由于是第一次撰写书籍,许多地方仍有不足还请读者朋友们见谅. 在京东或当当等都可以购买到本书: ...
- Mono 支持LoongArch架构
近期,著名的.NET开源社区Mono正式支持LoongArch(龙架构),目前LoongArch64架构已出现在.NET社区主干分支上. 详细内容可以跟踪 https://github.com/mon ...
- CSS——组合选择器
1.后代选择器(包括儿子和孙子) .c1 .c2{ color: red; } 2.子代选择器(只选择儿子) .c3 > .c5{ color: red; } 3.与选择器 选择p标签下面的.c ...
- nginx启动流程
nginx启动流程 1. 根据命令行决定配置文件路径 2. 如果处于升级中则监听环境变量里传递的监听句柄 3. 调用所有核心模块的create_conf方法生成存放配置项的结构体 4. 针对所有核心模 ...
- 记一次bug排除心得
问题背景 要做一个需求,大概是检测到某输入重启,于是写一个demo调试一下 c语言程序,交叉编译后在adb shell下运行 思路 用 am 命令直接重启 我们先手动验证一下,发现这个设备不支持am命 ...
- vue 的时间格式化
大江东去,浪淘尽,千古风流人物.故垒西边,人道是,三国周郎赤壁.乱石穿空,惊涛拍岸,卷起千堆雪.江山如画,一时多少豪杰.遥想公瑾当年,小乔初嫁了,雄姿英发.羽扇纶巾,谈笑间,樯橹灰飞烟灭.故国神游,多 ...
- 关于 CSDN 的恶臭嘴脸
我有段时间确实希望通过 CSDN 增加我文章的阅读量.但是我怎么使用 CSDN,我想要的大概也就 cnblogs 相对符合预期,CSDN 真是垃圾. 为了钱真是什么都不要了,让我们这种没有自主经济来源 ...