blog's Introduction
blog's People
blog's Issues
HTTP 状态码和浏览器缓存
概述
1xx:临时响应,并需要请求者继续执行操作的状态代码
2xx:成功类,表示请求已成功接受
3xx:重定向,表示要完成请求必须进行更近一步的操作
4xx:客户端错误,请求有语法错误或请求无法实现
5xx:服务器错误,服务器未能实现合法的请求
其中常见的有:
- 100:请求者应当继续提出请求。为了让服务器检查请求的首部,客户端必须在发送请求实体前,在初始化请求中发送 Expect: 100-continue 首部并接收 100 Continue 响应状态码。
- 200:从客户端发来的请求在服务器端被正确处理
- 304:所请求的资源并未修改(命中协商缓存)
- 403:服务器拒绝执行客户端的请求
- 404:在服务器上没有找到请求的资源
- 500:服务器端在执行请求时发生了错误
304 状态码的具体流程
304 过程中提到的缓存为:协商缓存
第一次请求
第二次请求
服务器决策过程
Etag/If-None-Match
Etag 值的含义:对资源的索引节(INode),大小(Size)和最后修改时间(MTime)进行 Hash 后得到的,是资源的唯一标识
服务器接收到 If-None-Match 后,会跟服务器上该资源的 ETag 进行比对:
- 不一致:返回200,返回新的资源
- 一致:返回304,告诉客户端缓存可用
Last-Modifed/If-Modified-Since
Last-Modifed 值的含义:浏览器向服务器发送资源最后的修改时间
服务端在拿到 If-Modified-Since 字段后,与服务器中资源最后修改时间进行比对,如果 If-Modified-Since 值小于服务器资源最后修改时间,证明资源已经更新:
- 小于:返回200,返回最新的资源与最新的修改时间
- 大于:返回304,告诉客户端缓存可用
浏览器缓存
当浏览器准备向服务器发起请求时,首先会通过校验强缓存是否可用,如果可用则直接使用(此时请求依旧会返回200状态,但并无与服务端交互)。否则进入协议缓存,即发送http请求。
强缓存
浏览器不会向服务器发送任何请求,直接从本地缓存中读取文件并返回Status Code: 200 OK
- 200 form memory cache : 不访问服务器,一般已经加载过该资源且缓存在了内存当中,直接从内存中读取缓存。浏览器关闭后,数据将不存在(资源被释放),再次打开相同的页面时,不会出现from memory cache。
- 200 from disk cache: 不访问服务器,已经在之前的某个时间加载过该资源,直接从硬盘中读取缓存,关闭浏览器后,数据依然存在,此资源不会随着该页面的关闭而释放掉下次打开仍然会是from disk cache。
- 优先访问memory cache,其次是disk cache,最后是请求网络资源
协商缓存
向服务器发送请求,服务器会根据这个请求的request header的一些参数来判断是否命中协商缓存,如果命中,则返回304状态码并带上新的response header通知浏览器从缓存中读取资源
强缓存和协商缓存的header参数
强缓存
-
Expires:过期时间,如果设置了时间,则浏览器会在设置的时间内直接读取缓存,不再请求
-
Cache-Control:当值设为max-age=300时,则代表在这个请求正确返回时间(浏览器也会记录下来)的5分钟内再次加载资源,就会命中强缓存。
协商缓存
Last-Modifed/If-Modified-Since和Etag/If-None-Match是分别成对出现的,呈一一对应关系。
-
Etag/If-None-Match
-
Last-Modifed/If-Modified-Since
跨标签页通讯
本质原理
共享的中间介质
限制:同源限制
同源策略用于限制网页中脚本访问来自不同源的资源和数据。当且仅当两个页面具有相同的协议(例如,HTTP 或 HTTPS)、域名(例如,example.com)和端口号(例如,80 或 443)时,被视为同源页面。
通讯方式
1. 使用浏览器存储
a. 使用 localStorage
和 storage
事件
当 localStorage
中的数据发生变化时,会触发 storage
事件。通过监听此事件,标签页之间可实现通信。
使用限制:同源页面
存储限制:5M 左右
// 发送方
localStorage.setItem('key', 'message');
// 接收方
window.addEventListener('storage', function(event) {
if (event.key === 'key') {
console.log('Received message:', event.newValue);
}
});
存在的问题:
- 事件不会在设置值的页面中触发:当在一个页面中设置
localStorage
值时,只有其他页面会收到storage
事件。设置值的页面需要单独处理。 - 重复写入相同值时不触发:当向
localStorage
写入相同的值时,storage
事件不会触发。需要确保每次写入的值不同,或者使用其他方法(如window.postMessage
)通知其他页面。
b. 共享 Cookie
与轮询 setInterval
Cookie 可以在同一域名下的不同页面之间共享,因此可以用作跨页面通信的一种方式。但是Cookie 不提供事件系统,需要使用轮询或其他方法来检查数据更改。
使用限制:同源页面
存储限制:4KB
// 在页面 A 中设置 Cookie
document.cookie = 'message=Hello from Page A';
// 在页面 B 中轮询检查 Cookie 更改
function pollCookie() {
const cookieValue = document.cookie.replace(/(?:(?:^|.*;\s*)message\s*\=\s*([^;]*).*$)|^.*$/, '$1');
console.log('Received message:', cookieValue);
// 继续轮询
setTimeout(pollCookie, 1000);
}
pollCookie();
存在的问题:
- 性能:Cookie 随每个 HTTP 请求发送给服务器,可能导致性能问题及增加服务器负担
- 安全:Cookie 可能受到跨站请求伪造(CSRF)等攻击。要确保使用安全的设置(如
Secure
和HttpOnly
标志)。
c. IndexedDB
IndexedDB 是一个允许在浏览器中存储结构化数据的 API,提供了更加强大和灵活的数据存储能力。IndexedDB 本身没有事件系统来通知其他页面数据的变化,需要结合其他方法(如 window.postMessage
或轮询)实现跨页面通信。
使用限制:使用 window.postMessage
可以越过同源限制
使用轮询:
// 在页面 A 中存储数据
const dbRequest = window.indexedDB.open('myDatabase', 1);
dbRequest.onsuccess = function(event) {
const db = event.target.result;
const transaction = db.transaction(['messages'], 'readwrite');
const objectStore = transaction.objectStore('messages');
objectStore.add({ id: 1, message: 'Hello from Page A' });
};
// 在页面 B 中轮询检查数据更改
function pollIndexedDB() {
const dbRequest = window.indexedDB.open('myDatabase', 1);
dbRequest.onsuccess = function(event) {
const db = event.target.result;
const transaction = db.transaction(['messages'], 'readonly');
const objectStore = transaction.objectStore('messages');
const getRequest = objectStore.get(1);
getRequest.onsuccess = function(event) {
console.log('Received message:', event.target.result.message);
};
// 继续轮询
setTimeout(pollIndexedDB, 1000);
};
}
pollIndexedDB();
使用 postMessage
发送方:
// 存储数据到 IndexedDB
const dbRequest = window.indexedDB.open('myDatabase', 1);
dbRequest.onupgradeneeded = function (event) {
const db = event.target.result;
db.createObjectStore('messages', { keyPath: 'id' });
};
dbRequest.onsuccess = function (event) {
const db = event.target.result;
const transaction = db.transaction(['messages'], 'readwrite');
const objectStore = transaction.objectStore('messages');
objectStore.add({ id: 1, message: 'Hello from Source Page' });
};
// 通知 middle.html
const middleWindow = window.open('http://example.com/middle.html');
middleWindow.postMessage('DataSaved', '*');
接受方:
// 监听来自 source.html 的消息
window.addEventListener('message', function (event) {
if (event.data === 'DataSaved') {
// 从 IndexedDB 获取数据
const dbRequest = window.indexedDB.open('myDatabase', 1);
dbRequest.onsuccess = function (event) {
const db = event.target.result;
const transaction = db.transaction(['messages'], 'readonly');
const objectStore = transaction.objectStore('messages');
const getRequest = objectStore.get(1);
getRequest.onsuccess = function (event) {
console.log(event.target.result.message)
};
};
}
});
2. 服务器推送
a. Websocket
WebSocket 是一个双向通信协议,允许浏览器和服务器之间进行实时通信。它不受同源策略的限制,可以在不同源的客户端和服务器之间建立连接。
// 服务器端(Node.js 示例)
const WebSocket = require('ws');
const server = new WebSocket.Server({ port: 8080 });
server.on('connection', function(socket) {
// 广播消息给所有连接的客户端
socket.on('message', function(message) {
server.clients.forEach(function(client) {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
});
});
// 客户端
const socket = new WebSocket('ws://localhost:8080');
socket.addEventListener('message', function(event) {
console.log('Received message:', event.data);
});
// 发送消息
socket.send('message');
b. Server Sent Events
Server Sent Events 允许跨源通信,但需要服务器发送适当的 CORS(跨源资源共享)响应头信息。这些响应头允许指定哪些源可以接收服务器推送的事件。在服务器端添加 Access-Control-Allow-Origin
响应头可以实现跨源 SSE 连接。
const express = require('express');
const sseExpress = require('sse-express');
const app = express();
// 设置跨源响应头
app.use((req, res, next) => {
res.header('Access-Control-Allow-Origin', '*'); // 允许任何源,或者指定允许的源列表
next();
});
// SSE 路由
app.get('/events', sseExpress(), (req, res) => {
// 发送事件给客户端
res.sse('event_name', { data: 'Your data here' });
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
3. 浏览器API
a. BroadcastChannel
API
BroadcastChannel
API 允许来自同一源的不同标签页、窗口或 iframe 之间进行通信。通过创建一个共享的通道,标签页可以发送和接收消息。
使用限制:同源页面
// 发送方
const channel = new BroadcastChannel('channel_name');
channel.postMessage('message');
// 接收方
const channel = new BroadcastChannel('channel_name');
channel.addEventListener('message', function(event) {
console.log('Received message:', event.data);
});
浏览器渲染过程与重绘回流
渲染过程
- 获取HTML文件并通过 HTML parser 解析,形成DOM Tree
- 通过分词器将字节流转换为 Token
- HTML 解析器维护了一个Token 栈,将 Token 解析为 DOM 节点,并将 DOM 节点添加到 DOM 树中
- CSS 解析,计算 DOM 节点的样式
- 将 CSS 文本转换为 styleSheets 结构中的数据
- 转换样式表中的属性值,使其标准化
- 计算出DOM树中每个节点的具体样式
- 创建布局树,并计算元素的布局信息
- 对布局树进行分层,并生成分层树
- 为每个图层生成绘制列表,并将其提交到合成线程
- 合成线程将图层分成图块,并在光栅化线程池中将图块转换成位图
- 合成线程发送绘制图块命令 DrawQuad 给浏览器进程
- 浏览器进程根据 DrawQuad 消息生成页面,并显示到显示器上
重绘与回流
概念
当页面中样式发生变化,浏览器需要重新绘制元素,其中有两种类型的操作,即重绘与回流。回流必定触发重绘,重绘不一定触发回流。重绘的开销较小,回流的代价较高。
名称 | 元素变化触发 | 表现为 | 消耗 |
---|---|---|---|
重绘 Repaint | 外观 | 元素外观被改变 | 小 |
回流(重排)Reflow | 尺寸、位置 | 重新生成布局 | 大 |
最佳实践
- css
- 避免使用
table
布局 - 尽量只修改
position
属性为absolute
或fixed
的元素上,并将动画效果放在这些元素上
- 避免使用
- javascript
- 避免频繁操作样式,可汇总后统一一次修改
- 尽量使用
class
进行样式修改 - 减少
dom
的增删次数,可使用 字符串 或者documentFragment
一次性插入 - 极限优化时,修改样式可将其
display: none
后修改 - 不要把 DOM 结点的属性值放在循环里当成循环里的变量
微信小程序集成思源宋体
需求背景
- UI 要求在微信小程序里加入思源宋体,作为部分按钮和标识的字体
- 字体的字重 90% 的比例为 700,其余有一些其他的字重值
- 字体内容包含中文、英文和数字
- 微信小程序打包限制
弃用方案
字体文件转 base64
- 转码之后的文件依然无法在 20M 以内
wx.loadFontFace(Object object)
- 在部分真机失效
- 依赖网络请求
实现思路
字体子集化
工具和方案
- 工具
- python
- fonttools
- 实现
options = subset.Options()
# 读入原字体文件
font = subset.load_font('source.ttf', options)
# 使用子集工具
subsetter = subset.Subsetter(options)
# 输入需要取哪些字
subsetter.populate(text = '需要取的内容')
subsetter.subset(font)
# 输出子集字体文件
subset.save_font(font, 'target.ttf', options)
无效工具
- 各类在线子集化网站
- font-carrier2:3年前的轮子,已经无人维护,且对思源(包括宋体和黑体)无效
- fontmin:对思源无效
- 相关issue:ecomfe/fontmin-app#6
- 猜测是用的核心font-carrier导致的
- FontSmaller:对思源无效
字体文件转 base64
-
配置
- base64 encode: on
- formats: ttf
引入 base64
- 位置:app.scss
- 提升效率的方式
- 修改 font-family,用缩写或者约定的称呼,思源宋体的全称非常长
- 原子化字体的样式,比如:
.custom-font {
font-family: taget-font, PingFang SC, sans-serif;
}
提升空间
- 目前取子集、转 base64、引入base 是分开三步手动处理的,当界面发生变化的时候,就要重新再取一次,可以考虑把整个工作流做成脚本,当修改了子集内容的时候,由 python 获取内容、创建子集 TTF 文件、调用网络请求制作子集 TTF 文件的 base64 文件、OS 操作将 base64 的 font-face 引入 app.scss
- 当 TTF 文件做了子集化之后,font-weight 不再生效,目前是统一了大部分字体的字重,特殊部分用切图实现
层叠上下文(Stacking Context)
概念
我们假定用户正面向(浏览器)视窗或网页,而 HTML 元素沿着其相对于用户的一条虚构的 z 轴排开,层叠上下文就是对这些 HTML 元素的一个三维构想。众 HTML 元素基于其元素属性按照优先级顺序占据这个空间。
特性
- 层叠上下文的层叠水平要比普通元素高
- 每个层叠上下文都完全独立于它的兄弟元素:当处理层叠时只考虑子元素
- 每个层叠上下文和兄弟元素独立,也就是当进行层叠变化或渲染的时候,只需要考虑后代元素。
- 每个层叠上下文是自成体系的,当元素发生层叠的时候,整个元素被认为是在父层叠上下文的层叠顺序中。
- 层叠上下文可以嵌套,内部层叠上下文及其所有子元素均受制于外部的层叠上下文。
- 层叠上下文可以阻断元素的混合模式
层叠准则
- 当具有明显的层叠水平标示的时候,如识别的z-index值,在同一个层叠上下文领域,层叠水平值大的那一个覆盖小的那一个。
- 当元素的层叠水平一致、层叠顺序相同的时候,在DOM流中处于后面的元素会覆盖前面的元素。
创建
- 文档根元素(
<html>
) - position
- 为 absolute(绝对定位)或 relative(相对定位)且 z-index 值不为 auto 的元素
- 为 fixed(固定定位)或 sticky(粘滞定位)的元素
- flex (flexbox (en-US)) 容器的子元素,且 z-index 值不为 auto
- grid (grid) 容器的子元素,且 z-index 值不为 auto
- opacity 属性值小于 1 的元素
- 以下任意属性值不为 none 的元素:
- transform
- filter
- perspective
- clip-path
- mask / mask-image / mask-border
范例
HTML部分
<div class="box">
<div>
<img src="@/assets/logo.png" />
</div>
</div>
CSS初始设定
.box > div {
background-color: blue;
z-index: 1;
} /* 此时该div是普通元素,z-index无效 */
.box > div > img {
position: relative;
z-index: -1;
right: -150px; /* 注意这里是负值z-index */
}
渲染结果:图片被 box 中的 div 覆盖
原因:负值z-index的层叠顺序在block水平元素的下面,而蓝色背景div元素是个普通元素,img 就被覆盖了。
注意点:
- 把内部 img 换成其他标签,如div、span、p 都不会复现,因为 img 是一个行内(display 属性的默认值是 inline)可替换元素,和它类似的是 video标签。
- 可替换元素展现效果不是由 CSS 来控制的。这些元素是一种外部对象,它们外观的渲染,是独立于 CSS 的。
display:flex | inline-flex
修改HTML
<div class="box flex">
<div>
<img src="@/assets/logo.png" />
</div>
</div>
添加CSS
.flex {
display: flex;
}
渲染结果
原因
flex 给父元素 box 的子 div 创造了层叠上下文,变成了被负 z-index 覆盖
注意
flex 产生层叠上下文的条件有 2 条:
- 父级需要是display:flex或者display:inline-flex
- 子元素的z-index不是auto,必须是数值
此时,这个子元素为层叠上下文元素,注意是子元素,不是flex父级元素
opacity 属性
HTML
<div class="opa">
<img src="@/assets/logo.png" />
</div>
CSS
.opa {
background-color: blue;
opacity: 0.5;
}
渲染结果
原因
半透明元素本身具有层叠上下文
transform
HTML
<div class="trans">
<img src="@/assets/logo.png" />
</div>
CSS
.trans {
background-color: blue;
transform: rotate(15deg);
}
渲染结果
原因
transform 只不为 none 的元素本身具有层叠上下文
事件循环
JS 运行时任务
一个任务就是指计划由标准机制来执行的任何 JavaScript,如程序的初始化、事件触发的回调等。
宏任务(MacroTask)/ 任务(Task)
包含
- script(整体代码)
- setTimeout
- setInterval
- setImmediate
- I/O
- UI 交互事件
微任务(MicroTask)
包含
- Promise
- MutationObserver
区别
- 当执行来自任务队列中的任务时,在每一次新的事件循环开始迭代的时候运行时都会执行队列中的每个任务。在每次迭代开始之后加入到队列中的任务需要在下一次迭代开始之后才会被执行。
- 每次当一个任务退出且执行上下文栈为空的时候,微任务队列中的每一个微任务会依次被执行。不同的是它会等到微任务队列为空才会停止执行——即使中途有微任务加入。换句话说,微任务可以添加新的微任务到队列中,这些新的微任务将在下一个任务开始运行之前,在当前事件循环迭代结束之前执行。
示例
// task1
Promise.resolve().then(() => {
// 微任务1
console.log("Promise1");
setTimeout(() => {
// 任务2
console.log("setTimeout2");
}, 0);
});
// task2
setTimeout(() => {
// 任务1
console.log("setTimeout1");
Promise.resolve().then(() => {
// 微任务2
console.log("Promise2");
});
}, 0);
- 按照脚本顺序执行task1,打印console,并把 setTimeout 推进宏任务队列
- 按照脚本顺序执行task2,并把Promise推进微任务队列
- 任务退出且执行上下文栈为空,执行微任务队列
- 微任务队列全部执行完毕,�循环执行宏任务队列
最后输出顺序为:Promise1 => setTimeout1 => Promise2 => setTimeout2
。
常见正则表达式规则
符号
规则 | 描述 |
---|---|
\ | 转义 |
^ | 匹配输入的开始 |
$ | 匹配输入的结束 |
* | 匹配前一个表达式 0 次或多次 |
+ | 匹配前面一个表达式 1 次或者多次。等价于 {1,} |
? | 匹配前面一个表达式 0 次或者 1 次。等价于{0,1} |
. | 默认匹配除换行符之外的任何单个字符 |
字母
规则 | 描述 |
---|---|
\d | 匹配一个数字 |
\D | 匹配一个非数字字符 |
\w | 匹配一个单字字符(字母、数字或者下划线) |
\W | 匹配一个非单字字符 |
\s | 匹配一个空白字符,包括空格、制表符、换页符和换行符 |
\S | 匹配一个非空白字符 |
范围
规则 | 描述 |
---|---|
{n} | n 是一个正整数,匹配了前面一个字符刚好出现了 n 次 |
{n,} | n是一个正整数,匹配前一个字符至少出现了n次 |
{n,m} | n 和 m 都是整数。匹配前面的字符至少n次,最多m次 |
[xyz] | 匹配方括号中的任意字符 |
[^xyz] | 匹配任何没有包含在方括号中的字符 |
逻辑
规则 | 描述 |
---|---|
x(?=y) | 匹配'x'仅仅当'x'后面跟着'y' |
(?<=y)x | 匹配'x'仅当'x'前面是'y' |
x(?!y) | 仅仅当'x'后面不跟着'y'时匹配'x' |
(?<!y)x | 仅仅当'x'前面不是'y'时匹配'x' |
x|y | 匹配‘x’或‘y’ |
参考资料
regex101: https://regex101.com/
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.