浏览器
浏览器内核
- 内核由很多模块组成。
(主线程) JS引擎:负责JS程序的编译与运行。 HTML解析器,CSS解析器:负责页面文本的解析。 布局和渲染模块:负责页面的布局和效果的绘制。 DOM/CSS模块:负责DOM/CSS在内存中的相关处理。 (分线程) 定时器模块:负责定时器的管理。 DOM事件相应模块:负责事件的管理。 网络请求模块:负责ajax请求。
- 常见浏览器内核
Trident内核:IE、360和搜狗等国内浏览器。 Gecko内核:firefox。 Webkit内核:Safari。 Blink内核:Chorm(Chromium=>Blink) Presto内核:Opera(2013年2月宣布放弃=>Chromium=>Blink)。
浏览器渲染过程
HTML解析器
CSS解析器
HTML解析器和CSS解析器将DOM树和CSS树交给JS引擎。(DOMContentLoaded)
JS引擎
- 处理JS代码,根据JS代码完成绑定事件、修改DOM树和CSS树等操作。
- 先执行初始化代码(设置定时器、绑定事件监听、发送ajax请求),再执行回调代码。
布局模块(layout)
- 将修改后的DOM树和CSS树合并成一个渲染树,得到节点的几何信息,交给绘图模块。
浏览器
绘图模块(painting)
- 根据渲染树和资源,得到节点的绝对像素,完成绘图。(onload)
CSS阻塞
- style标签中的样式:由HTML解析器进行解析,不会阻塞浏览器渲染(可能会产生“闪屏现象”),不会阻塞DOM解析
- link引入的CSS样式:由CSS解析器进行解析,会阻塞浏览器渲染,会阻塞后面的js语句执行,不阻塞DOM的解析
优化
- 使用CDN节点进行外部资源加速。
- 对外部样式文件进行压缩(使用打包工具)。
- 优化样式文件代码。
JS阻塞
JS会阻塞后续DOM的解析,会阻塞页面渲染,会阻塞后续JS的执行。
优化
<script async></script> 加上async属性,JS代码会异步加载并执行(所有script标签, 谁先加载完谁执行)。<script defer></script> 加上defer属性,JS代码会异步加载(所有script标签按顺序在DOM节点加载完后执行)- 把script标签放在页面尾部。
- 优化JS代码。
资源加载阻塞
无论是CSS阻塞,还是JS阻塞,都不会阻塞浏览器加载外部资源。浏览器始终处于一种”先把请求发出去“的工作模式,只要是涉及到网络请求的内容,都会先发送请求去获取资源,至于资源到本地后什么时候用,由浏览器自己协调。
重绘回流
重绘
- 节点需要改变外观,而不会影响布局。
- 如:
改变颜色、背景颜色。
回流
- 布局或几何属性发生改变。
- 如:
页面首次渲染; 浏览器窗口大小发生改变(resize事件); 元素尺寸或位置发生改变; 元素内容改变(文字数量或图片大小); 元素字体大小改变(font-size); 添加或删除可见的DOM元素; 激活CSS伪类(hover);
重绘不一定会发生回流,回流一定会发生重绘。
减少重绘回流
- 合并css修改样式,采用class来修改。
- 避免使用table布局,可能一个小的改动会造成整个table的重新布局。
- 使用visibility代替display。
- 把DOM离线(display:none)后再修改,修改后再显示。
- 使CSS3硬件加速,如transform、 opacity、filters这些属性。
- 脱离文档流(float、绝对定位absolute、固定定位fixed)。
Event Loop中的渲染UI
- 当 Event loop 执行完 Microtasks 后,会判断 document 是否需要更新。因为浏览器是 60Hz 的刷新率,每 16ms 才会更新一次。
- 然后判断是否有
resize 或者 scroll ,有的话会去触发事件,所以 resize 和 scroll 事件也是至少 16ms 才会触发一次,并且自带节流功能。 - 判断是否触发了 media query。
- 更新动画并且发送事件。
- 判断是否有全屏操作事件。
- 执行
requestAnimationFrame 回调。 - 执行
IntersectionObserver 回调,该方法用于判断元素是否可见,可以用于懒加载上,但是兼容性不好。 - 更新界面。
- 以上就是一帧中可能会做的事情。如果在一帧中有空闲时间,就会去执行
requestIdleCallback 回调。
Event Loop
https://juejin.cn/post/6868849475008331783#heading-14
- 代码开始执行,script标签作为宏任务执行。
- 执行过程,遇到同步代码就执行,遇到微任务放入微任务队列,遇到宏任务放入宏任务队列。
- 执行微任务队列。过程中遇到微任务队列放入微任务队列,遇到宏任务放入宏任务队列。在此过程中产生的微任务会一并清空。
- 渲染UI。
- 执行宏任务队列。过程中遇到微任务队列放入微任务队列,遇到宏任务放入宏任务队列。在此过程中产生的微任务会一并清空。
Promise的executor 是同步的。
宏任务、微任务
宏任务
- script
- setTimeout
- setInterval
- I/O
- UI rendering
- requestAnimationFrame
- setImmediate(node)
微任务
- promise.then().catch().finally()
- MutationObserver
- Object.observe
- process.nextTick(node)
DOM事件流
1、事件捕获阶段(从上往下) 2、目标阶段 3、事件冒泡阶段(从下往上)
addEventListener
事件代理(委托)
如果一个节点中的子节点是动态生成的,那么子节点需要注册事件的话应该注册在父节点上。
优点
- 绑定在父节点上只需要绑定一次,节省内存。
- 不需要一个个给子节点绑定事件。
进程和线程
进程是除了CPU之外系统资源分配的基本单位。 线程是CPU调度分配的基本单位。
JS是单线程还是多线程?
- JS是单线程的。比如定时器的回调函数只有等运行栈中的代码全部执行完后才有可能执行。
但是使用H5中的Web Workers可以多线程运行。
为什么JS要用单线程,而不用多线程?
- 作为浏览器脚本语言,JS的主要用途是与用户互动以及操作DOM,这就决定了它只能是单线程,否则会带来复杂的同步问题。
浏览器运行是单进程还是多进程?
Cookie、Session、Token、JWT
Cookie
- 用户数据存储在浏览器。
- 只支持存储字符串数据,每个Cookie不超过4KB。
- 有效时间长。
- 只能用在
单个节点的域 或者它的子域 中有效。
Session
- 用户数据存储在服务器。相当于浏览器发送SessionID给服务器,服务器查询是否符合,如果符合就根据数据库里对应Session对象的用户数据去进行操作。
- 支持存储任何类型的数据。
- 有效时间短(客户端关闭或者会话时间结束失效)。
- 只能用在
单个节点的域 或者它的子域 中有效。 - 占用服务器资源。
Token
- 部分用户数据存储在浏览器。
- 服务器收到token后需要查库验证token是否有效。
JWT(Json Web Token)
- 用户数据存储在浏览器。
- header.payload.signiture
header:指定加密算法。(alg表示签名的加密算法;typ表示token的类型) payload:用户数据。 signature:数字签名。header和payload用base64串行算法处理过结果+指定的加密算法+服务器公钥=>数字签名; - 服务器端只需要用服务器私钥解密即可得到用户数据,无需查询数据库。
- 可以跨域使用(因为放在header里面,而cookie和session是放在response)。
https://juejin.cn/post/6844904034181070861#heading-10
cookie、localStorage、sessionStorage
document.cookie= "username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 GMT;path=/";
window.localStorage.username = 'hehe'
Web Worker
注意点
- 同源限制:分配给Worker线程运行的脚本文件,必须与主线程的脚本文件同源。
- DOM限制:Worker线程无法使用document、window、parent这些对象,但是可以使用navigator、location对象。
- 通信联系:Worker线程和主线程不在同一个上下文环境,不能直接通信,必须通过消息完成。
- 脚本限制:不能执行alert()和confirm()方法,但是可以使用XMLHttpRequest对象发出AJAX请求。
- 文件限制:无法读取本地文件,所加载的脚本必须来自网络。
var worker = new Worker('Worker线程.js');
worker.postMessage('hello');
worker.onmessage = function(event){
console.log(event);
}
worker.onerror(function (event) {
console.log([
'ERROR: Line ', e.lineno, ' in ', e.filename, ': ', e.message
].join(''));
});
worker.terminate();
self.addEventListener('message', function(e){
self.postMessage(e);
self.close();
}, false);
importScripts('j1.js', 'j2.js');
实现浏览器内多个标签页之间的通信
WebSocket协议
- 因为HTTP通信只能由客户端发起,服务器无法主动向客户端发送消息。当服务器有变化时,只能通过客户端不断地轮询访问来获取消息。而WebSocket是全双工通信,则可以实现多个标签页之间的通信。
localStorage
sharedWorker
let myWorker = new SharedWorker('worker.js');
myWorker.port.postMessage('哼哼');
myWorker.port.onmessage = (e) => console.log(e.data);
onconnect = function(e) {
const port = e.ports[0];
port.postMessage('哈嘿');
port.onmessage = (e) => {
console.log(e.data);
}
}
|