浏览器工作原理与实践
2021-06-28 22:58:22 193 举报
AI智能生成
浏览器是用于检索、展示以及传递Web信息资源的应用程序。其工作原理主要包括输入URL,DNS解析,发送HTTP请求,服务器处理请求并返回HTTP报文,浏览器解析渲染页面等步骤。实践上,浏览器通过渲染引擎解析HTML、CSS和JavaScript,构建DOM树和Render Tree,最后将Render Tree绘制到屏幕上。此外,浏览器还提供了各种API供开发者使用,如BOM、DOM、Canvas等,以实现更丰富的Web应用。同时,为了提高用户体验,浏览器还会进行缓存优化、预加载、预渲染等操作。
作者其他创作
大纲/内容
浏览器发展历史
网景浏览器崛起
IE垄断
Chrome打破垄断
浏览器发展 三大路线
应用程序web化
原因
云计算的普及
HTML5 技术的发展
如何体现
客户端/服务器 转化为 浏览器/服务器(B/S)架构
场景
视频
音频
游戏
软件应用
....
Web应用移动化
场景
PWA
Web操作系统化
场景
ChromeOS
为啥要学习浏览器工作原理
准确评估 Web 开发项目的可行性
从更高维度审视页面
在快节奏的技术迭代中把握本质
宏观视角下的浏览器
前置知识
并行处理
what
计算机中的并行处理就是同一时刻处理多个任务
why
大大提升性能。
how
多线程
进程
what
一个进程就是一个程序的运行实例
线程
what
线程是不能单独存在的,它是由进程来启动和管理的
线程是依附于进程的,而进程中使用多线程并行处理能提升运算效率
进程与线程的关系
进程中的任意一线程执行出错,都会导致整个进程的崩溃
线程之间共享进程中的数据,线程之间可以对进程的公共数据进行读写操作
当一个进程关闭之后,操作系统会回收进程所占用的内存
进程之间的内容相互隔离
浏览器发展历程
单进程浏览器
what
单进程浏览器是指浏览器的所有功能模块都是运行在同一个进程里
缺点
不稳定
一个插件的意外崩溃会引起整个浏览器的崩溃
一些复杂的 JavaScript 代码就有可能引起渲染引擎模块的崩溃,从而导致浏览器崩溃
不流畅
所有页面的渲染模块、JavaScript 执行环境以及插件都是运行在同一个线程中的,
这就意味着同一时刻只能有一个模块可以执行 ,
就会导致整个浏览器失去响应,变卡顿
这就意味着同一时刻只能有一个模块可以执行 ,
就会导致整个浏览器失去响应,变卡顿
页面的内存泄漏
哪些可能造成内存泄漏
不安全
插件可以获取到操作系统的任意资源
页面脚本
多进程浏览器
组成
浏览器进程
主要负责界面显示、用户交互、子进程管理,同时提供存储等功能
渲染进程
核心任务是将 HTML、CSS 和 JavaScript 转换为用户可以与之交互的网页
GPU 进程
网络进程
主要负责页面的网络资源加载
插件进程
主要是负责插件的运行,
优点
解决不稳定的问题
解决不流畅的问题
解决不安全的问题
缺点
更高的资源占用
更复杂的体系架构
TCP/IP
为什么要了解TCP/IP
通过 一个数据包的“旅程” 分析TCP/IP
IP:把数据包送达目的主机
what
计算机的地址就称为 IP 地址,访问任何网站实际上只是你的计算机向另外一台计算机请求信息
why
数据包要在互联网上进行传输,就要符合网际协议(Internet Protocol,简称 IP)标准
how
UDP:把数据包送达应用程序
what
基于 IP 之上开发和应用打交道的协议,最常见的是“用户数据包协议(User Datagram Protocol)”,简称 UDP。
why
IP 是非常底层的协议,只负责把数据包传送到对方电脑,但是对方电脑并不知道把数据包交给哪个程序,是交给浏览器还是交给王者荣耀?
how
特点
UDP 不能保证数据可靠性,但是传输速度却非常快
数据包在传输过程中容易丢失
大文件会被拆分成很多小的数据包来传输,这些小的数据包会经过不同的路由,并在不同的时间到达接收端,
而 UDP 协议并不知道如何组装这些数据包,从而把这些数据包还原成完整的文件。
而 UDP 协议并不知道如何组装这些数据包,从而把这些数据包还原成完整的文件。
使用场景
UDP 会应用在一些关注速度、但不那么严格要求数据完整性的领域,如在线视频、互动游戏等
TCP:把数据完整地送达应用程序
what
TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议
why
解决UPD 的问题
how
和 UDP 头一样,TCP 头除了包含了目标端口和本机端口号外,还提供了用于排序的序列号,以便接收端通过序号来重排数据包
特点
对于数据包丢失的情况,TCP 提供重传机制
TCP 引入了数据包排序机制,用来保证把乱序的数据包组合成一个完整的文件
使用场景-要求数据传输可靠性(reliability)的应用
浏览器请求
邮件
TCP/IP五层模型
应用层
HTTP
传输层/运输层
TCP
UDP
网络层
IP
链路层
物理层
一个完整的 TCP 连接过程
建立连接-三次握手
在建立一个 TCP 连接时,客户端和服务器总共要发送三个数据包以确认连接的建立
为什么TCP要进行的是三次握手,不是其他次数?
传输数据
断开连接-四次挥手
HTTP-应用层协议
what
HTTP 协议,正是建立在 TCP 连接基础之上的。HTTP 是一种允许浏览器向服务器获取资源的协议,是 Web 的基础,通常由浏览器发起请求,用来获取不同类型的文件
HTTP与TCP的关系
why
用来获取不同类型的文件
浏览器使用最广的协议
how-HTTP请求流程
浏览器发起HTTP请求流程
构建请求
浏览器构建请求行信息
查找缓存
what
浏览器缓存
why
缓解服务器端压力,提升性能(获取资源的耗时更短了)
对于网站来说,缓存是实现快速资源加载的重要组成部分
how
当浏览器发现请求的资源已经在浏览器缓存中存有副本,它会拦截请求,返回该资源的副本,并直接结束请求,而不会再去源服务器重新下载
分类
协商缓存
强缓存
相关头部
Cache-Control
If-None-Match
Max-age
........
准备 IP 地址和端口
浏览器会请求 DNS 返回域名对应的 IP
等待 TCP 队列
建立 TCP 连接
发送 HTTP 请求
发送请求行
请求方法
请求 URI
HTTP 版本协议
发送请求头
发送请求体
如果使用 POST 方法,那么浏览器还要准备数据给服务器,这里准备的数据是通过请求体来发送
服务器处理HTTP请求流程
返回请求
响应行
协议版本
状态码
响应头
响应体
判断状态码
200
301
如实状态码是300,则会根据响应头的Location字段重定向,重新发起新的HTTP请求
404
断开连接
导航流程
面试题开端
在浏览器里,从输入 URL 到页面展示,这中间发生了什么
导航
概念
用户发出 URL 请求到页面开始解析的这个过程
流程
用户输入
URL 请求过程
查找缓存
没有缓存则利用 IP 地址和服务器建立 TCP 连接
请求数据
响应数据
浏览器中的JavaScript执行机制
变量提升
JavaScript 代码的执行流程
输入一段JS代码
编译阶段
编译后的代码由下面几部分组成
执行上下文
变量环境
如果是变量环境中存在同名的函数,JavaScript编译阶段会选择最后声明的那个
如果变量环境中存在变量和函数同名,那么在编译阶段,变量的声明会被忽略
词法环境
外部环境
this
可执行代码
执行阶段
执行代码时,JavaScript 引擎便开始在变量环境对象中查找对应的函数/变量
变量提升
在 JavaScript 代码执行过程中,JavaScript 引擎把变量的声明部分和函数的声明部分提升到代码开头的“行为”。
变量被提升后,会给变量设置默认值,这个默认值就是我们熟悉的 undefined。
变量被提升后,会给变量设置默认值,这个默认值就是我们熟悉的 undefined。
作用域
闭包
调用栈
总结
JavaScript 代码执行过程中,需要先做变量提升,而之所以需要实现变量提升,是因为 JavaScript 代码在执行之前需要先编译。
在编译阶段,变量和函数会被存放到变量环境中,变量的默认值会被设置为 undefined;在代码执行阶段,JavaScript 引擎会从变量环境中去查找自定义的变量和函数
如果在编译阶段,存在两个相同的函数,那么最终存放在变量环境中的是最后定义的那个,这是因为后定义的会覆盖掉之前定义的
问题
变量容易在不被察觉的情况下被覆盖掉
本应销毁的变量没有被销毁
调用栈
前置知识
执行上下文分类
全局执行上下文
函数执行上下文
eval执行上下文
函数调用是什么
函数调用就是运行一个函数,具体使用方式是使用函数名称跟着一对小括号
函数的调用过程是什么
如果是全局代码,则创建全局执行上下文,包含了声明的函数和变量
代码中全局变量和函数都保存在全局上下文的变量环境中
代码中全局变量和函数都保存在全局上下文的变量环境中
当遇到函数调用时,则创建函数级别的执行上下文
在执行 JavaScript 时,可能会存在多个执行上下文,那么 JavaScript 引擎是如何管理这些执行上下文的呢?
栈结构是什么
先进后出
调用栈是什么
是什么
JavaScript 中有很多函数,经常会出现在一个函数中调用另外一个函数的情况,调用栈就是用来管理函数调用关系的一种数据结构
有什么用
调用栈是 JavaScript 引擎追踪函数执行的一个机制,当一次有多个函数被调用时,通过调用栈就能够追踪到哪个函数正在被执行以及各函数之间的调用关系
分析在代码的执行过程中,调用栈的状态变化情况
第一步,创建全局上下文,并将其压入栈底
第二步,调用 addAll 函数
当调用该函数时,JavaScript 引擎会编译该函数,并为其创建一个执行上下文,
最后还将该函数的执行上下文压入栈中
当调用该函数时,JavaScript 引擎会编译该函数,并为其创建一个执行上下文,
最后还将该函数的执行上下文压入栈中
第三步,当执行到 add 函数调用语句时,同样会为其创建执行上下文,并将其压入调用栈
当 add 函数返回时,该函数的执行上下文就会从栈顶弹出,并将 result 的值设置为 add 函数的返回值,也就是 9
紧接着 addAll 执行最后一个相加操作后并返回,addAll 的执行上下文也会从栈顶部弹出,此时调用栈中就只剩下全局上下文了
如何使用Chrome开发者工具分析调用栈的变化过程
栈溢出
调用栈是有大小的,当入栈的执行上下文超过一定数目,JavaScript 引擎就会报错,我们把这种错误叫做栈溢出。
调用栈有两个指标,最大栈容量和最大调用深度,满足其中任意一个就会栈溢出
呈现的是什么样子
抛出的错误信息为:超过了最大栈调用大小(Maximum call stack size exceeded)
场景
比如递归,死循环
每调用一个函数,JavaScript 引擎会为其创建执行上下文,并把该执行上下文压入调用栈,然后 JavaScript 引擎开始执行函数代码
如果在一个函数 A 中调用了另外一个函数 B,那么 JavaScript 引擎会为 B 函数创建执行上下文,并将 B 函数的执行上下文压入栈顶
当前函数执行完毕后,JavaScript 引擎会将该函数的执行上下文弹出栈
当分配的调用栈空间被占满时,会引发“堆栈溢出”问题
块级作用域
作用域(scope)
是什么
变量与函数的可访问范围,即作用域控制着变量和函数的可见性和生命周期
分类
全局作用域
对象在代码中的任何地方都能访问,其生命周期伴随着页面的生命周期
函数作用域
在函数内部定义的变量或者函数,并且定义的变量或者函数只能在函数内部被访问
块级作用域
一对大括号包裹的一段代码
例子
函数
判断语句
循环语句
甚至单独的一个{}
特点
代码块内部定义的变量在代码块外部是访问不到的,
并且等该代码块中的代码执行完成之后,
代码块中定义的变量会被销毁
并且等该代码块中的代码执行完成之后,
代码块中定义的变量会被销毁
作用域块内声明的变量不影响块外面的变量
作用
解决变量提升带来的缺陷
如何实现块级作用域
let
const
在同一段代码中,ES6 是如何做到既要支持变量提升的特性,又要支持块级作用域的呢?
函数内部通过 var 声明的变量,在编译阶段全都被存放到变量环境里面了
通过 let 声明的变量,在编译阶段会被存放到词法环境(Lexical Environment)中
执行上下文里面的词法环境是什么
在词法环境内部,维护了一个小型栈结构,栈底是函数最外层的变量,
进入一个作用域块后,就会把该作用域块内部的变量压到栈顶
进入一个作用域块后,就会把该作用域块内部的变量压到栈顶
当作用域执行完成之后,该作用域的信息就会从栈顶弹出,这就是词法环境的结构
查找某个变量时,沿着词法环境的栈顶向下查询,
如果在词法环境中的某个块中查找到了,就直接返回给 JavaScript 引擎,
如果没有查找到,那么继续在变量环境中查找
如果在词法环境中的某个块中查找到了,就直接返回给 JavaScript 引擎,
如果没有查找到,那么继续在变量环境中查找
作用域链相关
作用域链
是什么
当一段代码使用了一个变量时,JavaScript 引擎首先会在“当前的执行上下文”中查找该变量
依次往外, 最后去全局执行上下文中查找 把这个查找的链条就称为作用域链
依次往外, 最后去全局执行上下文中查找 把这个查找的链条就称为作用域链
子主题
词法作用域
重要性
在 JavaScript 执行过程中,其作用域链是由词法作用域决定的
概念
词法作用域就是指作用域是由代码中函数声明的位置来决定的,
所以词法作用域是静态的作用域,
通过它就能够预测代码在执行过程中如何查找标识符
所以词法作用域是静态的作用域,
通过它就能够预测代码在执行过程中如何查找标识符
特点
词法作用域是代码编译阶段就决定好的,和函数是怎么调用的没有关系
闭包
概念
在 JavaScript 中,根据词法作用域的规则,内部函数总是可以访问其外部函数中声明的变量,
当通过调用一个外部函数返回一个内部函数后,
即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,
我们就把这些变量的集合称为闭包
当通过调用一个外部函数返回一个内部函数后,
即使该外部函数已经执行结束了,但是内部函数引用外部函数的变量依然保存在内存中,
我们就把这些变量的集合称为闭包
使用原则
如果引用闭包的函数是一个全局变量,那么闭包会一直存在直到页面关闭
因此如果该闭包会一直使用,那么它可以作为全局变量而存在;
因此如果该闭包会一直使用,那么它可以作为全局变量而存在;
如果引用闭包的函数是个局部变量,等函数销毁后,在下次 JavaScript 引擎执行垃圾回收时,
判断闭包这块内容如果已经不再被使用了,那么 JavaScript 引擎的垃圾回收器就会回收这块内存
因此如果使用频率不高,而且占用内存又比较大的话,那就尽量让它成为一个局部变量
判断闭包这块内容如果已经不再被使用了,那么 JavaScript 引擎的垃圾回收器就会回收这块内存
因此如果使用频率不高,而且占用内存又比较大的话,那就尽量让它成为一个局部变量
回收机制
this相关
this 是和执行上下文绑定的,也就是说每个执行上下文中都有一个 this
分类
全局执行上下文中的 this
全局执行上下文中的 this 是指向 window 对象的。
这也是 this 和作用域链的唯一交点,作用域链的最底端包含了 window 对象,全局执行上下文中的 this 也是指向 window 对象。
这也是 this 和作用域链的唯一交点,作用域链的最底端包含了 window 对象,全局执行上下文中的 this 也是指向 window 对象。
函数中的 this
eval 中的 this
特点
默认情况下调用一个函数,其执行上下文中的 this 也是指向 window 对象
如何设置执行上下文中的 this 来指向其他对象
call 方法
bind方法
apply方法
通过对象调用的方式
通过一个对象来调用其内部的一个方法,该方法的执行上下文中的 this 指向对象本身
在全局环境中调用一个函数,函数内部的 this 指向的是全局变量 window
通过构造函数中设置
当执行 new CreateObj() 的时候,JavaScript 引擎做了如下四件事
首先创建了一个空对象 tempObj
接着调用 CreateObj.call 方法,并将 tempObj 作为 call 方法的参数,
这样当 CreateObj 的执行上下文创建时,它的 this 就指向了 tempObj 对象
这样当 CreateObj 的执行上下文创建时,它的 this 就指向了 tempObj 对象
然后执行 CreateObj 函数,此时的 CreateObj 函数执行上下文中的 this 指向了 tempObj 对象
最后返回 tempObj 对象
构造函数中的 this 其实就是新对象本身
this的设计缺陷
问题1
描述
嵌套函数中的 this 不会从外层函数中继承
解决
方式1
声明一个变量 self 用来保存 this
本质是把 this 体系转换为了作用域的体系
本质是把 this 体系转换为了作用域的体系
方式二
ES6 中的箭头函数
ES6 中的箭头函数并不会创建其自身的执行上下文,所以箭头函数中的 this 取决于它的外部函数
ES6 中的箭头函数并不会创建其自身的执行上下文,所以箭头函数中的 this 取决于它的外部函数
问题2
描述
普通函数中的 this 默认指向全局对象 window
为什么这是一个缺陷
在实际工作中,我们并不希望函数执行上下文中的 this 默认指向全局对象,因为这样会打破数据的边界,造成一些误操作
解决
方式1
通过 call 方法来显示调用
方式2
设置 JavaScript 的“严格模式”来解决。在严格模式下,默认执行一个函数,其函数的执行上下文中的 this 值是 undefined
总结
当函数作为对象的方法调用时,函数中的 this 就是该对象
当函数被正常调用时,在严格模式下,this 值是 undefined,非严格模式下 this 指向的是全局对象 window
嵌套函数中的 this 不会继承外层函数的 this 值
v8工作原理
想成为行业专家,并打造高性能前端应用,那么你就必须要搞清楚 JavaScript 的内存机制
栈空间
堆空间
垃圾回收
编译器(Compiler)
解释器(Interpreter)
抽象语法树(AST)
字节码(Bytecode)
即时编译器(JIT)
JavaScript 中的数据是如何存储在内存中的
语言类型
静态语言
在使用之前就需要确认其变量数据类型的语言
强类型语言
不支持隐式类型转换的语言
隐式类型转换
动态语言
在运行过程中需要检查数据类型的语言
弱类型语言
支持隐式类型转换的语言
JavaScript 是一种弱类型的、动态的语言
弱类型
不需要告诉 JavaScript 引擎这个或那个变量是什么数据类型,JavaScript 引擎在运行代码的时候自己会计算出来
动态
可以使用同一个变量保存不同类型的数据
如何检查JS值的类型
基础类型检查
typeof
注意
只能检查原始数据类型
使用 typeof 检测 Null 类型时,返回的是 object
使用 typeof 检测引用类型时,返回的是 object
引用类型检查
instanceof
constructor
Array.isArray
Object.prototype.toString
getPrototypeOf
Object.prototype.isPrototypeOf
为什么JS中分为基础类型和引用类型两个大类
因为它们在内存中存放的位置不一样
存储空间的种类
JavaScript 内存模型
JavaScript 内存模型
分类
代码空间
作用
存储可执行代码
栈空间
作用
即调用栈,是用来存储执行上下文的
栈空间是如何存储数据的
堆空间
代码分析
当执行一段代码时,需要先编译,并创建执行上下文
执行上下文存储在调用栈中
当执行到第 3 行时,变量 a 和变量 b 的值都被保存在执行上下文中,
而执行上下文又被压入到栈中,所以你也可以认为变量 a 和变量 b 的值都是存放在栈中的
而执行上下文又被压入到栈中,所以你也可以认为变量 a 和变量 b 的值都是存放在栈中的
接下来继续执行第 4 行代码,由于 JavaScript 引擎判断右边的值是一个引用类型,
这时候处理的情况就不一样了,
JavaScript 引擎并不是直接将该对象存放到变量环境中,而是将它分配到堆空间里面,
分配后该对象会有一个在“堆”中的地址,然后再将该数据的地址写进 c 的变量值
这时候处理的情况就不一样了,
JavaScript 引擎并不是直接将该对象存放到变量环境中,而是将它分配到堆空间里面,
分配后该对象会有一个在“堆”中的地址,然后再将该数据的地址写进 c 的变量值
d=c的操作就是把 c 的引用地址赋值给 d
变量 c 和变量 d 都指向了同一个堆中的对象,
通过 c 修改 name 的值,变量 d 的值也跟着改变,归根结底它们是同一个对象
通过 c 修改 name 的值,变量 d 的值也跟着改变,归根结底它们是同一个对象
总结
原始类型的赋值会完整复制变量值
引用类型的赋值是复制引用地址
为什么基础类型,原始类型要分别存储
JavaScript 引擎需要用栈来维护程序执行期间上下文的状态,
如果栈空间大了话,所有的数据都存放在栈空间里面,那么会影响到上下文切换的效率,进而又影响到整个程序的执行效率
如果栈空间大了话,所有的数据都存放在栈空间里面,那么会影响到上下文切换的效率,进而又影响到整个程序的执行效率
栈空间都不会设置太大,主要用来存放一些原始类型的小数据
引用类型的数据占用的空间都比较大,所以这一类数据会被存放到堆中,堆空间很大,能存放很多大的数据,
不过缺点是分配内存和回收内存都会占用一定的时间
不过缺点是分配内存和回收内存都会占用一定的时间
总结
JS八种数据类型可以分为两大类
原始类型
存储在栈中
原始类型互相赋值,依然相互独立,互不影响
引用类型
存储在堆中
堆中的数据是通过引用和变量关联起来的
将引用类型的变量 a 赋值给变量 b,那会导致 a、b 两个变量都同时指向了堆中的同一块数据,二者会相互影响
JavaScript 的变量是没有数据类型的,值才有数据类型,变量可以随时持有任何类型的数据
如何实现深拷贝
垃圾数据是如何自动回收的
垃圾数据
是什么
有些数据被使用之后,可能就不再需要了
有什么坏处
为什么要回收
为什么要回收
内存泄漏
如果这些垃圾数据一直保存在内存中,那么内存会越用越多,
所以我们需要对这些垃圾数据进行回收,以释放有限的内存空间
所以我们需要对这些垃圾数据进行回收,以释放有限的内存空间
垃圾回收策略
手动回收
代表语言
C/C++
是什么
何时分配内存、何时销毁内存都是由代码控制的
产生的垃圾数据由手动通过代码来释放
产生的垃圾数据由手动通过代码来释放
自动回收
代表语言
JavaScript、Java、Python
是什么
产生的垃圾数据由垃圾回收器来释放
栈中的垃圾数据是如何回收的
有一个记录当前执行状态的指针(称为 ESP),指向调用栈中 当前 函数的执行上下文,表示当前正在执行的函数
当前函数执行完成后,需要被销毁,JS会将 ESP 下移到 最新需要执行的 函数的执行上下文,
这个下移操作就是销毁前一个 函数执行上下文的过程。
这个下移操作就是销毁前一个 函数执行上下文的过程。
总结
当一个函数执行结束之后,JavaScript 引擎在调用栈中
会通过向下移动 ESP 来销毁该函数保存在栈中的执行上下文。
会通过向下移动 ESP 来销毁该函数保存在栈中的执行上下文。
堆中的垃圾数据是如何回收的
要回收堆中的垃圾数据,就需要用到 JavaScript 中的垃圾回收器了
分类
代际假说
特点
大部分对象在内存中存在的时间很短,简单来说,就是很多对象一经分配内存,很快就变得不可访问
不死的对象,会活得更久
分代收集
垃圾回收算法
在 V8 中会把堆分为新生代和老生代两个区域,
新生代中存放的是生存时间短的对象,
老生代中存放的生存时间久的对象。
并分别使用两个不同的垃圾回收器
新生代中存放的是生存时间短的对象,
老生代中存放的生存时间久的对象。
并分别使用两个不同的垃圾回收器
新生代区域
副垃圾回收器,主要负责新生代的垃圾回收。
老生代区域
主垃圾回收器,主要负责老生代的垃圾回收。
垃圾回收器的工作流程
标记空间中活动对象和非活动对象。所谓活动对象就是还在使用的对象,非活动对象就是可以进行垃圾回收的对象。
回收非活动对象所占据的内存。其实就是在所有的标记完成之后,统一清理内存中所有被标记为可回收的对象
内存整理
新生代垃圾回收器(副垃圾回收器)是如何处理垃圾回收的
老生代垃圾回收器(主垃圾回收器)是如何处理垃圾回收的
你是如何判断 JavaScript 中内存泄漏的?
可以结合一些你在工作中避免内存泄漏的方法
可以结合一些你在工作中避免内存泄漏的方法
使用 chrome 的 Performance 面板,观察内存变化 如果多次垃圾回收后,
整体趋势是向上,就存在内部泄漏的可能!
整体趋势是向上,就存在内部泄漏的可能!
V8是如何执行一段JavaScript代码的
浏览器中的页面循环系统
消息队列
事件循环
setTimeout
宏任务
微任务
Promise
async await
浏览器中的页面
DOM树
渲染流水线
分层
合成机制
浏览器中的网络
HTTP1
HTTP2
HTTP3
浏览器安全
同源策略
跨站脚本攻击(XSS)
csrf 攻击
HTTPS
0 条评论
下一页