浏览器中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) [导读]随着深度学习方法的应用,浏览器调用人脸识别技术已经得到了更广泛的应用与提升.在 ...
随机推荐
- jpype-python调用java的方法
环境准备: 部署环境准备: sed -i.ori '$a export JAVA_HOME=/opt/jdk\nexport PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bi ...
- Github打不开解决办法(最新有效)
Github打不开解决办法(最新有效) 1. 先看没解决之前的截图: 2. 解决方法(手动修改DNS): 2.1 以win11为例,第一步:打开 设置 - 网络和Internet,找到 高级网络 ...
- JS 实现鼠标框选(页面选择)时返回对应的代码或文本内容
JS 实现鼠标框选(页面选择)时返回对应的代码或文案内容 一.需求背景 1.项目需求 当用户进行鼠标框选选择了页面上的内容时,把选择的内容进行上报. 2.需求解析 虽然这需求就一句话的事,但是很显然, ...
- vue2前端导出带背景色表格 xlsx xlsx-style
vue2 +elmentui+xlsx10.0.0+xlsx-style 坑有点多. xlsx10.0.0以后的版本 用require导入或者使用什么导入什么,不要import * xlsx全部导入 ...
- HTML——table表格标签
一.table表格的完整写法 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> ...
- conda错误 创建新环境conda create -n TF117 python=3.5时报错 An unexpected error has occurred. Conda has prepared the above report.
创建新环境conda create -n TF117 python=3.5时报错 An unexpected error has occurred. Conda has prepared the ab ...
- unaipp 发送验证码倒计时
view代码 <view class="margin-top" @tap="getCheckNum()"> <view class=" ...
- 2023 Hive 面试大纲
先说一些废话 总结一下Hive面试宝典中的要点,方便读者快速过一遍Hive面试所需要的知识点. 本文请搭配 Hive面试宝典 来食用更美味哟 ┗( ▔, ▔ )┛ 方便自己系统性回忆,根据*的数量来标 ...
- THUWC 2024 游记
其实这个游记也没啥好写的-- day 0 上午做动车两个小时到重庆,路上玩了 1.5h 的星露谷. 下午去巴蜀中学报道试机,系统是 Ubuntu Jammy,大大的好评,只是桌面是 Xubuntu/X ...
- The solution of P5339
problem 容斥好题,结果题解里面一堆 \(\text{NTT}\). 如果我们去掉有多少个人喜欢什么东西的条件,那么这个题就直接枚举有 \(i\) 组同学会一起讨论蔡徐坤.这一个问题十分容易. ...