大家好,今天来为大家解答测试网站访问速度的js源码分享这个问题的一些问题点,包括网站访问测试工具也一样很多人还不知道,因此呢,今天就来为大家分析分析,现在让我们一起来看看吧!如果解决了您的问题,还望您关注下本站哦,谢谢~
浏览器基础
进程架构
进程是操作系统资源分配的最小单位;线程是计算机中独立运行、CPU任务调度的最小单位。
进程是程序的运行实例,启动一个程序时,操作系统会为该程序创建一块内存,用于存放代码段、数据段和进程控制块(PCB)。
代码段存储程序代码;数据段存储程序运行时使用、产生的运算数据,如全局变量、局部变量等;进程控制块是操作系统管理和控制进程的数据结构,具体包括进程标识符信息、处理机状态信息、进程调度信息和进程控制信息。
同一个进程的任意线程执行出错,都会导致整个进程的崩溃。线程之间共享进程的数据。当一个进程关闭后,操作系统会回收进程所占用的内存。进程之间相互隔离,进程间通信需要使用IPC机制。
CPU被视为计算机的大脑。CPU的每个核心可以逐一处理许多不同的任务。在现代硬件中,CPU通常会存在多个核心,从而为计算机提供更强的算力。
与CPU不同,GPU擅长同时处理多个简单任务,GPU通常拥有数千个流处理器(StreamProcessor)和数百个内存控制器,每个流处理器都可以处理一个数据元素,从而实现高度并行化的计算。
由于GPU的并行计算结构、高速内存、专用计算单元、更高的时钟频率等硬件优势,使得GPU可以更好地处理大规模简单相似数据,应用于如图像和视频处理、深度学习和人工智能、科学计算和数值模拟、游戏和虚拟现实等大数据计算场景。
CPU缓存主要分为三个级别:L1、L2和L3。其中L1高速缓存是计算机系统内最快的缓存,内存大小一般为256KB到2MB左右;L2缓存比L1缓存稍慢,但内存更大,通常在256KB到8MB之间;L3缓存是最大的CPU缓存单元,内存大小为4MB到50MB。多核CPU允许线程并行执行,每个CPU核心拥有单独的L1和L2缓存,而L3缓存可以核心间共享。
单进程浏览器
单进程浏览器是指浏览器的所有功能模块都是运行在同一个进程里,这些模块包含了网络、插件、JavaScript运行环境、渲染引擎和页面等。在2007年之前,市面上浏览器都是单进程的。多个功能模块运行在同一进程必然造成不稳定、不流畅和不安全的问题。
不稳定性,插件模块和渲染模块都是不稳定的,一旦某个模块发生意外则整个浏览器都会崩溃。不流畅性,所有页面的渲染模块、JavaScript执行环境以及插件都是运行在同一个线程中的,这就意味着同一时刻只能有一个模块可以执行。除此之外,内存泄露也是导致浏览器卡顿的重要原因。不安全性,插件可以使用C/C++等代码编写,意味着插件可以获取到操作系统的任意资源,如果是恶意插件,那么它就可能会释放病毒、窃取账号密码,引发安全性问题。
多进程浏览器
a)早期Chrome进程架构图
b)当前Chrome进程架构图
多进程架构的浏览器解决了单进程浏览器存在的三大问题:进程是相互隔离的,所以当一个页面或者插件崩溃时,不会影响到浏览器和其他页面;JavaScript运行在渲染进程,而渲染进程间是相互隔离的,出错只会影响当前的渲染页面,关闭一个页面时,整个渲染进程也会被关闭,该进程所占用的内存都会被系统回收,内存泄露问题也不会相互影响;渲染进程和插件进程使用安全沙箱,即使执行恶意程序,也无法突破沙箱去获取系统权限。
浏览器主进程:控制包括地址栏、书签、后退和前进按钮的“chrome”应用程序。处理Web浏览器的不可见的特权部分,例如网络请求和文件访问。渲染进程:控制除Tab栏之外的所有浏览器展示相关部分。插件进程:控制网站使用的任何插件,如Flash插件。GPU进程:独立于其他进程,用于处理GPU任务。被单独拆分出一个进程,因为GPU处理来自多个应用程序的请求并将它们绘制在同一个表面上。
Chrome使用多渲染进程的好处是当打开3个Tab页,其中一个失去响应时,将其关闭也不会影响其他Tab页的正常运行。而在单渲染进程里,任何一个Tab页出错,都会使整个渲染进程失去响应,所有的页面都会报错。
分成多个渲染进程额外的优势是安全性和沙盒化。因为操作系统提供限制权限的方法,浏览器可以对具体进程的某些功能进行沙盒化,以确保其安全性。
网络请求
从输入URL到页面最终展示的整个流程需要多个进程的配合。这个过程大致可以描述为如下。
浏览器进程的UI线程接收到用户输入的URL请求,浏览器进程便将该URL转发给网络线程/进程。网络线程发起URL请求。网络线程接收到响应头数据,解析响应头数据,并将数据转发给浏览器进程。浏览器进程接收到网络线程的响应头数据之后,发送“提交导航”(CommitNavigation)消息到渲染进程;渲染进程接收到“提交导航”的消息之后,和网络线程建立数据管道,准备接收HTML数据;渲染进程向浏览器进程“确认提交”,准备接受和解析页面数据。浏览器进程接收到渲染进程“提交文档”的消息后,开始移除旧文档,然后更新浏览器进程中的页面状态。网络线程-内存资源受限的设备呈现为线程,充足的设备为进程。
V8引擎基础
数据存储
类型
描述
分类
boolean
只有true和false两个值
基本类型
undefined
没有被赋值的变量的默认值
基本类型
number
到
基本类型
string
字符串用于表示文本数据,不可修改
基本类型
bigint
支持更大范围的整数值
基本类型
symbol
符号类型唯一并且不可修改
基本类型
object
对象类型是一组属性的集合,包括null
引用类型
function
函数是可重复使用的代码块
引用类型
安全数字
JavaScript的数值类型是基于IEEE754标准[1]的双精度64位二进制格式,其中1位代表符号位(sign),11位指数(E),52位尾数(F)。规约数阶码范围
,有效数字范围。指数不全为1且指数不全为0的浮点数称作规约化浮点数,其二进制科学计数法的隐式整数位为1,所以实际精度为53位尾数。
在IEEE754标准的浮点数表示除NaN外的浮点数据,包括
有符号零有限非零规约浮点数(normalizednumber),指数部分的二进制位非全0或者非全1非规约浮点数(denormalizednumber),指数部分的二进制位为全0有符号无穷大NaNQuietNaNSignalingNaN
规约浮点数计算公式
非规约浮点数计算公式
非规约数,这类数字指数部分全为0,尾数部分不全为0。非规约数的偏移量比规约数偏移量小1,64位非规约浮点数偏移量为1022。
综上所述Number.MAX_VALUE是规约浮点数值为
Number.MIN_VALUE是非规约浮点数值为
Number.EPSILON表示1与Number可表示的大于1的最小的浮点数之间的差值为
特殊值
指数位全0,尾数位全0,表示±0。指数位全1,尾数位全0,表示±∞。指数位全1,尾数位不全为0,表示NaN。
0.1+0.2===0.3//false0.30000000000000004误差绝对值小于Number.EPSILON\n18.366667-16.466667===1.9//false1.8999999999999986误差绝对值大于Number.EPSILON\n
-指数11位,可以表示范围,但指数部分存在负数情况,64位浮点数定义偏移量为1023,由于指数全1和全0有特殊用处,所以实际取值范围为-1022到1023。非规约浮点数计算公式-用非规约数表示更接近0的数字。所以最小值指数始终为-1022。Number.MIN_VALUE-并非代表最小负数,而是JavaScript所能表示大于0的最小浮点数。
属性顺序
在ES6之前,一个对象键/属性的排列顺序是依赖于浏览器的具体实现。尽管绝大多数的浏览器引擎都是按照创建的顺序进行枚举的,但开发者们一直被强烈建议不要依赖于这个顺序。
从ES6开始,属性排列顺序是由[[OwnPropertyKeys]]算法定义的,这个规范定义[2]适用于对象的所有属性(字符串或符号),不管是否可枚举。但这个顺序只对Reflect.ownKeys、Object.getOwnPropertyNames和Object.getOwnPropertySymbols有保证。
规范定义对象属性的具体顺序为:
首先,按照数字属性名升序排列;其次,按照创建顺序枚举字符串属性名;最后,按照创建顺序枚举符号属性名。
获取对象属性列表的API遵循的算法各不一样。
for…in和Reflect.enumerate使用[[Enumerate]]算法,属性顺序与浏览器的具体实现相关,不由规范约束。Object.keys虽然先使用[[OwnPropertyKeys]]算法获取属性列表,但是会过滤不可枚举属性,还会重新排序以遵循具体实现相关的行为特性,所以属性排序同for…in一致,细节可以参考规范[3]。
//literaldeclarationofanobject\nconstdata={a:0,c:1,b:2,&34;:3,0:4,[Symbol.for(1)]:5,&34;:6,&34;:7,[Symbol.for(0)]:8};\n\n//useObject.keys()toshowallpropertiesoftheobject\n[&39;,&39;,&39;,&39;,&39;,&39;,&39;]\n\n//useReflect.ownKeys()toshowallpropertiesoftheobject\n[&39;,&39;,&39;,&39;,&39;,&39;,&39;,Symbol(1),Symbol(0)]\n\n//useJSON.stringify()toshowallpropertiesoftheobject\n&34;0&34;1&34;2&34;a&34;c&34;b&34;-1&39;\n\n//foldmode.hint:Thisvaluewasevaluateduponfirstexpanding.Itmayhavechangedsincethen.\n{0:4,1:3,2:6,a:0,c:1,b:2,-1:7,Symbol(1):5,Symbol(0):8}\n//unfoldmode\n{\n0:4,\n1:3,\n2:6,\n-1:7\na:0,\nb:2,\nc:1,\nSymbol(0):8,\nSymbol(1):5\n}\n
Reflect.ownKeys、Object.getOwnPropertyNames和Object.getOwnPropertySymbols的属性顺序是可预测且可靠的,由ECMAScript规范保证。而for…in、Object.keys和JSON.stringify的属性顺序是根据浏览器具体实现相关的排序算法所决定的,是不可预测且不可靠的。
数字属性名-字符串形式的数字属性,会先转换成数字,但不包括负数。Reflect.enumerate-已废弃,较新版本的浏览器已经移除该方法。
对象属性
JavaScript对象的数字属性被称为排序属性,字符串属性被称为命名属性。隐藏类由指针map寻址,排序属性由指针elements寻址,命名属性由指针properties寻址。
与map、elements和properties同层级的其他属性被称为对象内属性,保存在对象本身,访问速度最快。当命名属性数量比较少时,指针properties会以线性结构有序保存属性,被称为快属性;当命名属性数量比较多时,指针properties会以非线性结构无序保存属性,被称为慢属性。
对象是一系列键值对的集合,通常是以字典的形式进行存储的。但字典是非线性的数据结构,查询效率会低于线性数据结构。考虑到绝大多数对象的可枚举属性比较有限,因此V8为这类对象开辟线性的存储空间以提升存储和访问的效率。
为解析V8的对象属性管理机制,在新版本Chrome浏览器做以下论证。
图a与图b均是正整数形式的字符串,区别在于后者会有前缀。如图所示,由中括号包裹的属性为排序属性,否则为命名属性。前者被认定为数字属性,后者被认定为字符串属性。也就是说,数字属性并不是根据类型转换后的结果才判定的。
a)无前缀正整数字符串
b)有前缀正整数字符串
考虑到数字属性的有序性,所以排序属性均由指针element寻址,但相较于对象内属性会多一次索引查询。当命名属性数量小于等于9个时,见图c,会以对象内属性的形式直接存储到对象本身,大于9个的部分则根据创建顺序有序存储于指针properties指向的内存区域。
c)循环创建命名属性20个
d)循环创建排序属性和命名属性各20个
在静态代码里,目标对象每存在一个数字属性,则会增加命名属性的对象内属性的额度。这里需要注意的是静态代码,可以对比图c、图d和图e得出上述结论。图c与图d的区别在于多了20次生成数字属性的循环操作,假设是运行时的动态扩容,那么对象内属性个数应该会增加20个,而不是1个。
e)静态代码生成数字属性
f)快属性降级慢属性个数边界
图d与图e的区别在于前者是利用for循环生成20个数字属性,而后者是静态代码里手写了5个数字属性。不难察觉,前者只扩容了1个对象内属性额度,而后者扩容了5个对象内属性额度。
在没有触发扩容的前提下,快属性降级慢属性的命名属性边界值为25个,如图c和图f所示,即命名属性数量小于25时,properties指向内存的存储形式为线性结构,大于等于25个时,存储形式会变为非线性结构。
//图a代码\nfunctionFoo(){\nfor(leti=0;i<20;i++){\nthis[`${i}`]=`property-${i}`;\n}\n}\n//图b代码\nfunctionFoo(){\nfor(leti=0;i<20;i++){\nthis[`0${i}`]=`property-${i}`;\n}\n}\n//图c代码\nfunctionFoo(){\nfor(leti=0;i<20;i++){\nthis[`ByteDance-${i}`]=`property-${i}`;\n}\n}\n//图d代码\nfunctionFoo(){\nfor(leti=0;i<20;i++){\nthis[`${i}`]=`element-${i}`;\n}\nfor(leti=0;i<20;i++){\nthis[`ByteDance-${i}`]=`property-${i}`;\n}\n}\n//图e代码\nfunctionFoo(){\nthis[&39;]=&39;;\nthis[&39;]=&39;;\nthis[&39;]=&39;;\nthis[&39;]=&39;;\nthis[&39;]=&39;;\nfor(leti=0;i<20;i++){\nthis[`ByteDance-${i}`]=`property-${i}`;\n}\n}\n//图f代码\nfunctionFoo(){\nfor(leti=0;i<25;i++){\nthis[`ByteDance-${i}`]=`property-${i}`;\n}\n}\n\nconstfoo=newFoo();\n
综上所述,总结归纳为以下几点:
排序属性总由指针element寻址。快属性和慢属性是针对于命名属性的底层优化。最早创建的9个命名属性以对象内属性的形式存储。触发快属性降级慢属性的条件是命名属性大于等于25个。静态代码每次增加数字属性都会扩容对象内属性的数量。
排序属性-亦称为数组索引属性,包括数值类型、BigInt类型、无前缀数字字符串类型。
命名属性-亦称为常规属性,非数值型字符串,包括Symbol。
对象内属性-In-objectProperties,与properties、elements处于同一层级,因此少一次寻址操作。
线性结构有序-elements为排序后的顺序,properties为创建时的顺序。
新版本Chrome浏览器-114.0.5735.198(正式版本)(x86_64)
内存分配
基本类型的值存储于栈空间,引用类型的值存储于堆空间。通常情况下,调用栈用于维护代码执行时的上下文状态,直接影响代码的执行效率,所以内存都不会设置太大,主要用来存放一些基本类型的值和引用类型的地址。而堆空间很大,引用类型占用的空间普遍比较大,所以这一类数据会被存放到堆中。但堆空间分配内存和回收内存会占用较长的时间。为了节约内存开销,基本类型的赋值会完整复制变量值,而引用类型的赋值是复制引用地址。
constfoo=()=>{\nconsta=&39;;\nconstb=a;\nconstc={team:&39;};\nconstd=c;\n};\nfoo();\n
内存管理
垃圾回收算法被分为两种,一个是MajorGC,主要使用了Mark-Sweep&Mark-Compact算法,针对的是堆内存中的老生代进行垃圾回收;另外一个是MinorGC,主要使用了Scavenger算法,针对于堆内存中的新生代进行垃圾回收。V8引擎的新生代内存大小32MB(64位)、16MB(32位);老生代初始内存大小为512MB(64位)、256MB(32位),默认配置下最大可以增加到4GB。
constintkSystemPointerSize=sizeof(void*);//32位->4;64位->8\nstaticconstintkHeapLimitMultiplier=kSystemPointerSize/4;\n\n//老生代初始极值\nstaticconstsize_tkMaxInitialOldGenerationSize=256*MB*kHeapLimitMultiplier;\nstaticconstsize_tkOldGenerationLowMemory=128*MB*kHeapLimitMultiplier;\n
新生代(newspace),大多数的对象开始都会被分配在这里,这个区域相对较小但是垃圾回收特别频繁,该区域被分为两半,一半用来分配内存,另一半用于在垃圾回收时将需要保留的对象复制过来。SemiSpace,fromspace和tospace动态更换。采用Scavenge算法(复制算法)进行垃圾回收。对象晋升:对象是否经历过一次Scavenge算法;To空间的内存占比是否已经超过25%。老生代(oldspace),新生代中的对象在存活一段时间后就会被转移到老生代内存区,相对于新生代该内存区域的垃圾回收频率较低。老生代又分为老生代指针区和老生代数据区,前者包含大多数可能存在指向其他对象的指针的对象,后者只保存原始数据对象,这些对象没有指向其他对象的指针。大对象区(largeobjectspace):存放体积超越其他区域大小的对象,每个对象都会有自己的内存,垃圾回收不会移动大对象区。代码区(codespace):代码对象,会被分配在这里,唯一拥有执行权限的内存区域。Map区(mapspace):存放Cell和Map,每个区域都是存放相同大小的元素,结构简单,可以理解为隐藏类。
JavaScript在运行时,对象的属性是可以被修改的,这对于V8是存在不确定性的。像C++这类静态语言,在编译阶段就确定对象的结构,可以直接通过偏移量来查询目标对象的各项属性值,因此运行效率非常高。V8对每个对象做出两个假设:
对象创建完成后不会添加新的属性。对象创建完成后不会删除属性。
基于上述假设,V8会给每个对象创建隐藏类(HideenClass),用于记录该对象的基础布局信息,具体包括:
对象的所有属性。所有属性的相对偏移值。
那么当V8访问某个对象的某个属性时,就会先去隐藏类中查找该属性相对于该对象的偏移量,也就能去内存中直接取值,从而跳过一系列的查找过程,大大提升V8查找对象的属性值的效率。
V8的每个对象都有map属性,该字段指向该对象的隐藏类。当两个对象的结构相同时,就会复用同一个隐藏类,这样可以减少隐藏类的创建次数以及减少存储空间。而当结构发生变更时,就会重新创建隐藏类。因此在开发过程中,为提高V8引擎性能,需要注意以下几点:
尽量使用字面量一次性初始化完整的对象属性。尽量保证初始化时属性的顺序一致。尽量避免使用delete方法。
//–allow-natives-synta指向同一地址,故复用同一个隐藏类\nconstJnQ={name:&39;,owner:&39;,TL:&39;};\nconstTCSplus={name:&39;,owner:&39;,TL:&39;};\n\n//重新创建隐藏类Case1\nconstJnQInfo={};//新建隐藏类第1次\nJnQInfo.platform=[&39;,&39;,&39;];//新建隐藏类第2次\nJnQInfo.member=13;//新建隐藏类第3次\nJnQInfo.meeting=&39;;//新建隐藏类第4次\n\n//重新创建隐藏类Case2\nconstJnQInfo={platform:[&39;,&39;,&39;],member:13,meeting:&39;};//新建隐藏类第1次\ndeleteJnQInfo.meeting;//新建隐藏类第2次\ndeleteJnQInfo.platform;//新建隐藏类第3次\n\n//重新创建隐藏类Case3\nconstjimu={member:8,owner:&39;};//新建隐藏类第1次\nconstquality={owner:&39;,member:4};//新建隐藏类第2次\n
新生代内存大小-网传主流说法,没有找到具体的源码,仅做参考。
结构相同-相同的属性名称;相等的属性个数;一致的属性顺序。
垃圾回收
根节点认定:全局对象;本地函数的局部变量和参数;当前嵌套调用链上的其他函数的变量和参数。标记-整理经历一次标记-清除后,内存空间可能会出现不连续的状态,即内存碎片;假设在老生代中有A、B、C、D四个对象;在垃圾回收的标记阶段,将对象A和对象C标记为活动的;在垃圾回收的整理阶段,将活动的对象往堆内存的一端移动;在垃圾回收的清除阶段,将活动对象左侧的内存全部回收。增量标记由于JS的单线程机制,垃圾回收的过程会阻碍主线程同步任务的执行,待执行完垃圾回收后才会再次恢复执行主任务的逻辑,这种行为被称为全停顿(stop-the-world)。在标记阶段同样会阻碍主线程的执行,一般来说,老生代会保存大量存活的对象,如果在标记阶段将整个堆内存遍历一遍,那么势必会造成严重的卡顿。因此,为了减少垃圾回收带来的停顿时间,V8引擎又引入了IncrementalMarking(增量标记)的概念,即将原本需要一次性遍历堆内存的操作改为增量标记的方式,先标记堆内存中的一部分对象,然后暂停,将执行权重新交给JS主线程,待主线程任务执行完毕后再从原来暂停标记的地方继续标记,直到标记完整个堆内存。这个理念其实有点像React框架中的Fiber架构,只有在浏览器的空闲时间才会去遍历FiberTree执行对应的任务,否则延迟执行,尽可能少地影响主线程的任务,避免应用卡顿,提升应用性能。得益于增量标记的好处,V8引擎后续继续引入了延迟清理(lazysweeping)和增量式整理(incrementalcompaction),让清理和整理的过程也变成增量式的。同时为了充分利用多核CPU的性能,也将引入并行标记和并行清理,进一步地减少垃圾回收对主线程的影响,为应用提升更多的性能。
编译解析
编译型语言在程序执行之前,需要经过编译器的编译过程,并且编译之后会直接保留机器能读懂的二进制文件,这样每次运行程序时,都可以直接运行该二进制文件,而不需要再次重新编译了。比如C/C++、GO等都是编译型语言。而由解释型语言编写的程序,在每次运行时都需要通过解释器对程序进行动态解释和执行。比如JavaScript、Python等都属于解释型语言。
V8在执行过程中既有解释器Ignition[4],又有编译器TurboFan[5],此外较新版本的Chrome增加了一种中间层编译器Maglev[6],V8可以使用jsvu[7]进行本地调试。
解释编译具体步骤包括:
生成抽象语法树和执行上下文词法分析,即分词(tokenize),根据预设规则将每一行代码拆分成不可再分的tokens。语法分析,即解析(parse),根据语法规则将tokens组合转化为抽象语法树。执行上下文,代码执行过程中的环境信息。生成字节码字节码[8]介于AST和机器码之间。字节码需要通过解析器将其转换为机器码后才能执行。解释器Ignition会根据AST生成字节码,并解释执行字节码。V8最早并没有字节码,直接将AST转换为机器码效率更加高效,但机器码的内存占用远远大于字节码,这在移动端的问题更加突出。
//JavaScriptCode-8linesofcode\nconstfoo=(day)=>{\nconstdepartment=&39;;\nconstteam=&39;;\nreturnday%2===0?department:team;\n};\nfor(letday=0;day<0x20227;day++){\nfoo(day);\n}\n\n//V8bytecode-19linesofcode\n//–print-bytecode\nCreateClosure[0],[0],39;root&39;li&39;JnQForever&sec-ordinary-object-internal-methods-and-internal-slots-ownpropertykeys
[3]
https://tc39.es/ecma262/event-loop-processing-model
[11]
https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/modules/scheduler/dom_timer.cc
[12]
https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_positioned_layout/Understanding_z-index/Stacking_context
[13]
DrawQuad命令:https://juejin.cn/post/7147706156968837150
[14]
ChromiumViz:https://zhuanlan.zhihu.com/p/61416139
[15]
RTT:https://blog.csdn.net/weixin_44446626/article/details/124576767?spm=1001.2101.3001.6650.3&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-3-124576767-blog-104130496.235%5Ev38%5Epc_relevant_anti_vip&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-3-124576767-blog-104130496.235%5Ev38%5Epc_relevant_anti_vip&utm_relevant_index=4
[16]
Insidelookatmodernwebbrowser:https://developer.chrome.com/blog/inside-browser-part1/
[17]
从Number.MAX_VALUE探秘JavaScript世界的神秘数字:https://juejin.cn/post/7008069852082470919
[18]
JavaScript对象遍历方法及其遍历顺序的总结:https://blog.csdn.net/weixin_50290666/article/details/124219626
[19]
V8内存管理:https://zhuanlan.zhihu.com/p/550025142
[20]
Gettersforspaces:https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:v8/src/heap/heap.h
[21]
GoogleCTF2022d8:FromV8BytecodetoCodeExecution:https://mem2019.github.io/jekyll/update/2022/07/03/Google-CTF.html
[22]
内存回收机制:https://juejin.cn/post/6844904182512615432
[23]
事件循环和任务调度:https://juejin.cn/post/7215145804033818682
[24]
ChromeDevTools面板全攻略:https://juejin.cn/post/6854573212412575757
作者:TnS-FE
来源:微信公众号:字节前端ByteFE
出处:https://mp.weixin.qq.com/s/wjrcO2Ej7BEThWVsCnXEtA
关于测试网站访问速度的js源码分享,网站访问测试工具的介绍到此结束,希望对大家有所帮助。
