diff --git a/.DS_Store b/.DS_Store index a2ab6f0..78497b9 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/code/findPath.js b/code/findPath.js new file mode 100644 index 0000000..10422a8 --- /dev/null +++ b/code/findPath.js @@ -0,0 +1,48 @@ +const arr = [ + { + id: 1, + child: [ + { id: 3 }, + { + id: 4, + child: [ + { + id: 5, + child: [{ id: 6 }], + }, + ], + }, + ], + }, + { + id: 2, + child: [ + { + id: 7, + }, + ], + }, +]; + +function findPathById(arr, id) { + let resPath; + function fn(arr, path = []) { + for (const item of arr) { + if (item.id === id) { + path.push(id); + resPath = path; + return; + } + if (item.child) { + path.push(item.id); + fn(item.child, [...path]); + } + } + } + fn(arr); + return resPath; +} + +// findPathById(arr, 6); +console.log("findPathById(arr, 6)", findPathById(arr, 6)); +console.log("findPathById(arr, 7)", findPathById(arr, 7)); diff --git a/code/limit-fetch.js b/code/limit-fetch.js new file mode 100644 index 0000000..2dc41ed --- /dev/null +++ b/code/limit-fetch.js @@ -0,0 +1,44 @@ +// 2 为并发最大数 +const limit = pLimit(2); +const inputs = [ + limit(() => fetchSomething(1000), "param1"), + limit(() => fetchSomething(2000), "param2"), + limit(() => fetchSomething(3000), "param3"), +]; +function fetchSomething(time) { + return new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, time); + }); +} +const time = Date.now(); +Promise.all(inputs).then((results) => { + console.log(results); + console.log(Date.now() - time); // 需要打印为 4s。(解释:因为并发数限制为2,第一个和第二个请求并发进行,第三个等待,第一个结束后开始第三个请求。总体时间为4s) +}); + +// 实现 pLimit 函数? +function pLimit(limit) { + let count = 0; + const list = []; + function schedule() { + if (count < limit && list.length > 0) { + const { cb, resolve, reject, args } = list.shift(); + count++; + cb(...args) + .then(resolve) + .catch(reject) + .finally(() => { + count--; + schedule(); + }); + } + } + return function (cb, ...args) { + return new Promise((resolve, reject) => { + list.push({ cb, args, resolve, reject }); + schedule(); + }); + }; +} diff --git a/code/timu.js b/code/timu.js new file mode 100644 index 0000000..aefc59b --- /dev/null +++ b/code/timu.js @@ -0,0 +1,8 @@ +Object.prototype[Symbol.iterator] = function () { + return Object.values(this)[Symbol.iterator](); +}; + +// 面试题:如何结构? +const [a, b] = { a: 1, b: 2 }; + +console.log(a, b); // 1,2 diff --git "a/pages/6-\345\211\215\347\253\257\347\237\245\350\257\206\347\202\271/\346\265\217\350\247\210\345\231\250\346\270\262\346\237\223\345\216\237\347\220\206.md" "b/pages/6-\345\211\215\347\253\257\347\237\245\350\257\206\347\202\271/1.\346\265\217\350\247\210\345\231\250\346\270\262\346\237\223\345\216\237\347\220\206.md" similarity index 100% rename from "pages/6-\345\211\215\347\253\257\347\237\245\350\257\206\347\202\271/\346\265\217\350\247\210\345\231\250\346\270\262\346\237\223\345\216\237\347\220\206.md" rename to "pages/6-\345\211\215\347\253\257\347\237\245\350\257\206\347\202\271/1.\346\265\217\350\247\210\345\231\250\346\270\262\346\237\223\345\216\237\347\220\206.md" diff --git "a/pages/6-\345\211\215\347\253\257\347\237\245\350\257\206\347\202\271/HTTP\350\257\267\346\261\202.md" "b/pages/6-\345\211\215\347\253\257\347\237\245\350\257\206\347\202\271/2.HTTP\350\257\267\346\261\202.md" similarity index 65% rename from "pages/6-\345\211\215\347\253\257\347\237\245\350\257\206\347\202\271/HTTP\350\257\267\346\261\202.md" rename to "pages/6-\345\211\215\347\253\257\347\237\245\350\257\206\347\202\271/2.HTTP\350\257\267\346\261\202.md" index 03a22ef..b530a61 100644 --- "a/pages/6-\345\211\215\347\253\257\347\237\245\350\257\206\347\202\271/HTTP\350\257\267\346\261\202.md" +++ "b/pages/6-\345\211\215\347\253\257\347\237\245\350\257\206\347\202\271/2.HTTP\350\257\267\346\261\202.md" @@ -99,3 +99,33 @@ HTTP 协议的请求流程: 在 HTTP/1.1 及更高版本中,可以通过 Keep-Alive 复用连接,减少 TCP 连接的建立和断开。 +## HTTP2 + + +在 HTTP 1.1 中,虽然支持 Keep-Alive 复用 TCP,但是每个域名有 6 个 TCP 的限制,所以并发需要比较多的 hack 操作,比如申请多个不同的域名以支持开启更多的 TCP 链接,然后通过服务端的反向代理这些域名到真正的服务器;之后 HTTP2 解决了 HTTP1 的单个 TCP 只能同时处理一个HTTP 请求的问题,HTTP2 使用二进制帧的概念,多个 Frame 组合成 Stream,Stream是TCP上的逻辑传输单元,能做到一个 TCP 多路复用,减少 TCP 的连接,并且支持头部压缩,相比之下做了不小的优化。 + +但是 HTTP2 也有 TCP 的致命缺点,假设第一个 Stream 上丢失了 Frame,后面 N 个 Stream 即使到达了服务器,也是不能被处理的,TCP 需要等待前面发出的包有回应才能处理后序的包,所以这个是 TCP 本身的头部阻塞问题,没有办法根本解决,并且在弱网环境下,HTTP2 性能比 HTTP1 还要低。 + +移动时代,如果用户的 IP 时刻变化,就需要频繁的进行 TCP 连接和反复握手。 + + + +## HTTP3 + +我们先来了解一下 TCP 的拥塞控制: + +引用自:[HTTP/3正式发布,深入理解HTTP/3协议](https://www.51cto.com/article/713935.html) +- 慢启动: 发送方像接收方发送一个单位的数据, 收到确认后发送2个单位, 然后是4个, 8个依次指数增长, 这个过程中不断试探网络的拥塞程度. +- 避免拥塞: 指数增长到某个限制之后, 指数增长变为线性增长。 +- 快速重传: 发送方每一次发送都会设置一个超时计时器, 超时后认为丢失, 需要重发。 +- 快速恢复: 在上面快速重传的基础上, 发送方重新发送数据时, 也会启动一个超时定时器, 如果收到确认消息则进入拥塞避免阶段, 如果仍然超时, 则回到慢启动阶段。 + +HTTP3 做了相当多的根本升级: +- 放弃了 TCP,使用自己内部开发的 QUIC 协议,底层是 UDP +- QIUCK通过递增的 Packet Number,精准计算 RTT(Round Trip Time) +- QUIC 不需要像 TCP 那样,每个三个数据包就要返回ACK,QUIC 最多可以带 256 个 ACK Block,减少数据包重传问题 +- 通过 connectId 来保持连接,即使用户切换了 IP,也能继续复用连接 + +# 参考连接 + +1. [HTTP/3正式发布,深入理解HTTP/3协议](https://www.51cto.com/article/713935.html) \ No newline at end of file diff --git "a/pages/6-\345\211\215\347\253\257\347\237\245\350\257\206\347\202\271/3.\347\247\273\345\212\250\347\253\257\351\200\202\351\205\215.md" "b/pages/6-\345\211\215\347\253\257\347\237\245\350\257\206\347\202\271/3.\347\247\273\345\212\250\347\253\257\351\200\202\351\205\215.md" new file mode 100644 index 0000000..346aefd --- /dev/null +++ "b/pages/6-\345\211\215\347\253\257\347\237\245\350\257\206\347\202\271/3.\347\247\273\345\212\250\347\253\257\351\200\202\351\205\215.md" @@ -0,0 +1,123 @@ +# 移动端适配 + +## 1. 移动端适配方案 + +- 方案一:使用viewport +- 方案二:使用rem +- 方案三:使用flexible.js +- 方案四:使用postcss-pxtorem + + +## 2. viewport + +viewport是指浏览器的可视区域,它决定了网页的最终显示效果。 + +viewport的设置方式有两种: + +1. meta标签: + +```html + +``` + +2. CSS: + +```css +html { + width: device-width; + height: 100%; +} +``` + + +- width: device-width:设置viewport宽度为设备宽度,这样可以保证页面的宽度适应不同设备的屏幕大小。 +- initial-scale=1.0:设置初始缩放比例为1.0,这样可以保证页面的初始显示效果。 + +我们布局时,能使用 vw,vh 去作为单位,这样就可以完美适配不同设备的屏幕大小。 + + +## 3. rem + +rem是相对于根元素html的font-size的单位,它可以实现不同设备的适配。 + +使用rem的步骤: + +1. 设置根元素html的font-size: + +```js +const $html = document.querySelector('html'); +$html.style.fontSize = $html.clientWidth / 10 + 'px'; +``` + + +2. 利用rem单位来设置元素的font-size: + +```css +.item { + font-size: 1.6rem; +} +``` + +## 4. flexible 方案 + +flexible 方案的原理是指定设计稿的宽度,然后根据 dpr 进行缩放,以达到适配不同屏幕的效果。 + +假设设计稿宽度为 750px,我们可以设置如下代码: + + +在 HTML 的 head 标签里配置 meta 如下: ` `。 + +```js +const width = 750; +const dpr = window.devicePixelRatio || 1; +const scale = window.innerWidth / width; + +let meta = document.querySelector('meta[name="viewport"]'); +const content = `width=${width}, initial-scale=${scale} user-scalable=no` +if (!meta) { + meta = document.createElement('meta'); + meta.setAttribute('name', 'viewport') + document.head.appendChild(meta); +} +meta.setAttribute('content', content); + + +``` +那么我们就可以在项目里面愉快的使用 px 啦,并且不需要任何的单位转换。 + +## 5. postcss-pxtorem + +postcss-pxtorem是一个PostCSS插件,它可以将px单位转换为rem单位,实现不同设备的适配。 + +使用postcss-pxtorem的步骤: + +1. 安装postcss-pxtorem: + +``` +npm install postcss-pxtorem --save-dev +``` + +2. 在postcss.config.js中配置postcss-pxtorem: + +```js +module.exports = { + plugins: [ + require('postcss-pxtorem')({ + rootValue: 16, // 1rem = 16px + propList: ['*'] + }) + ] +} +``` + +3. 在需要适配的元素的样式中使用rem单位: + +```css +.container { + font-size: 1.6rem; +} +``` + +## 6. 总结 + +移动端适配方案有很多,每种方案都有其优缺点,根据项目的实际情况选择适合的方案即可。 \ No newline at end of file diff --git "a/pages/6-\345\211\215\347\253\257\347\237\245\350\257\206\347\202\271/4.\345\211\215\347\253\257\346\200\247\350\203\275\344\274\230\345\214\226.md" "b/pages/6-\345\211\215\347\253\257\347\237\245\350\257\206\347\202\271/4.\345\211\215\347\253\257\346\200\247\350\203\275\344\274\230\345\214\226.md" new file mode 100644 index 0000000..2dff335 --- /dev/null +++ "b/pages/6-\345\211\215\347\253\257\347\237\245\350\257\206\347\202\271/4.\345\211\215\347\253\257\346\200\247\350\203\275\344\274\230\345\214\226.md" @@ -0,0 +1,144 @@ +# 4.前端性能优化 + +前端性能优化无非是优化服务器加载 HTML 到 生成页面的过程,当然我们说的是传统的 web 页面,小程序,APP 的方向不太一样,可以单独开一个话题,这里主要介绍一下 web 前端性能优化的一些常用方法。 + +我们先来看一张浏览器渲染页面的流程图: + +![浏览器渲染流水线](./images/浏览器渲染架构图.drawio.png) + + +## Chrome 性能指标 + +- FP First Paint 首次绘制,标记页面第一次绘制像素的时间,比如页面的背景色 +- FCP First Contentful Paint 首次内容绘制,如最大文本块或者图片显示到页面的时间,2s 内完成算优秀 +- LCP Largest Contentful Paint 最大内容绘制,记录视窗内最大元素的绘制时间,并随着时间变化而变化(因为最大元素会随着页面变化而改变),另外会在用户第一次交互后停止记录,2.5s内算优秀 +- TTI Time to Interactive 可交互时间,记录页面的可交互时间 +- FID First Input Delay 首次输入延迟,记录用户第一次输入响应的时间,记录在 FCP 到 TTI 之间,交互相应延迟 +- CLS Cumulative Layout Shift 累积布局偏移,记录页面布局变化的累积量,布局偏移越大,页面加载速度越慢,CLS 指标越高 + +在这些指标里面,有比较核心的几个指标用于判断页面性能: + +LCP、FID、CLS,最大内容绘制影响用户的第一感知,用户输入延迟影响用户的交互体验,累积布局偏移影响用户的阅读体验。 + +### LCP (最大内容绘制)性能指标 + +最大内容绘制,那些元素会影响 LCP 呢? + +- img 标签 +- svg 内的 image 标签 +- video 标签 +- css 的 background-image +- 包含文本节点或者其他内嵌文本子元素的块级元素 + +如果要在 JS 中触发 LCP,可以用以下方式: + +```javascript +const observer = new PerformanceObserver((list) => { + const entries = list.getEntries(); + entries.forEach((entry) => { + if (entry.entryType === 'largest-contentful-paint') { + console.log('LCP', entry.startTime, entry); + } + }); +}); +observer.observe({ type: 'largest-contentful-paint', buffered: true }); +``` + +如何优化 LCP ? + +参考渲染流水线图,我们的 FCP 是在图层绘制阶段,这个阶段之前我们都有优化的机会: + +- 压缩 css 的体积,推迟非关键 css +- 内联样式,减少请求 +- 推迟脚本加载和执行 +- SSR 服务端渲染 + +### FID (用户输入延迟)性能指标 +衡量用户首次与网页互动(输入,点击,滚动等)浏览器的响应时间,FID 越低,用户体验越好。 + +```javascript +const observer = new PerformanceObserver((list) => { + const entries = list.getEntries(); + entries.forEach((entry) => { + if (entry.entryType === 'first-input') { + console.log('FID', entry.processingStart - entry.startTime, entry); + } + }); +}); +observer.observe({ type: 'first-input', buffered: true }); +``` + +如何优化 FID ? + +- 减少脚本执行时间,优化脚本执行效率 +- 减少收评请求数和文件大小 +- 防止回流 + +### CLS (累积布局偏移)性能指标 + +CLS 衡量页面布局的连续性,CLS 越低,页面的加载速度越快。 + +为了计算布局偏移得分,浏览器会考虑视口大小,以及视口中不稳定元素在两个渲染帧之间的移动。布局偏移分数是该移动的两种度量的乘积:影响分数和距离分数。 + +比如首帧渲染图片在顶部,下一帧图片移动到了底部,那么布局偏移分数就是影响分数(图片移动了位置)乘以距离分数(图片移动了整个视口)。 + +```javascript +const observer = new PerformanceObserver((list) => { + const entries = list.getEntries(); + entries.forEach((entry) => { + if (entry.entryType === 'layout-shift') { + console.log('CLS', entry.value, entry); + } + }); +}); +observer.observe({ type: 'layout-shift', buffered: true }); +``` + + +如何优化 CLS ? + +- 避免布局抖动,减少布局复杂度 +- 初始化的位移,可以使用 transform 添加,不会触发布局偏移 + +总结: + +- 做性能优化之前,优先使用 LightHouse 等工具进行测试,找出瓶颈点 +- 使用 PageSpeed Insights 分析网站性能,找出优化的方向 +- 使用Chrome User Experience Report API 获取网站用户的真实体验数据 +- 也可以使用 Performance 面板查看页面的性能指标,分析出哪些地方需要优化 + + +## 前端性能优化方案 + +**我们回顾下面这张图:** + +![浏览器渲染流水线](./images/浏览器渲染架构图.drawio.png) + + +### 1. 减少 HTTP 请求 + +在从服务器请求 HTML 到生成页面的过程中,HTTP 请求是影响性能的主要因素。 + +- CDN 加速,减少不同地区的链路延迟 +- 请求协议升级 http2 ,http2优势:二进制传输,多路复用,减少 TCP 连接,首部压缩,服务器推送(请求 HTML 的时候可以把 CSS,JS,图片等资源一起请求) +- 代码压缩(webpack 压缩插件,terser),gzip压缩 +- 图片懒加载 lazy-load,使用 IntersectionObserver API 实现图片懒加载 +- 图片使用 webp,并使用回退模式(浏览器请求头) +- 启用 http 缓存(no-cache 是重新验证缓存,no-store 表示永远不使用缓存) + +### 2. JS 优化 + +- 使用模块化开发,避免全局污染,并能做到按需加载和 tree-shaking +- 懒加载路由,动态导入组件 +- 开启层叠上下文分层渲染,如 transform,z-index 等属性,避免重绘和回流 +- iconfont 字体图标,使用字体文件压缩,减少请求数 + + +## 总结 + +前端性能优化是一个长期的过程,需要不断的学习和实践,才能提升用户的体验。 + +我们可以从流水线图中去获取优化方案,比如获取 html,可以从网络(CDN),缓存,压缩去做优化,并且在请求的时候去除未用到 的资源,做懒加载,如路由懒加载,图片懒加载等,在新技术方面,http2,webp 图片也是不错的方案。 +渲染的时候尽量减少 js 对渲染主进程的阻塞,如使用 web worker,异步加载,懒加载组件等。 + +加载完整后,可以从浏览器的渲染着手优化,如分层,减少重绘,回流,使用 webgl,css 动画,使用 IntersectionObserver API 实现图片懒加载等。 \ No newline at end of file diff --git "a/pages/6-\345\211\215\347\253\257\347\237\245\350\257\206\347\202\271/WebAssembly.md" "b/pages/6-\345\211\215\347\253\257\347\237\245\350\257\206\347\202\271/5.WebAssembly.md" similarity index 100% rename from "pages/6-\345\211\215\347\253\257\347\237\245\350\257\206\347\202\271/WebAssembly.md" rename to "pages/6-\345\211\215\347\253\257\347\237\245\350\257\206\347\202\271/5.WebAssembly.md" diff --git "a/pages/6-\345\211\215\347\253\257\347\237\245\350\257\206\347\202\271/images/\346\265\217\350\247\210\345\231\250\346\270\262\346\237\223\346\236\266\346\236\204\345\233\276.drawio.png" "b/pages/6-\345\211\215\347\253\257\347\237\245\350\257\206\347\202\271/images/\346\265\217\350\247\210\345\231\250\346\270\262\346\237\223\346\236\266\346\236\204\345\233\276.drawio.png" index e69de29..fada4c8 100644 Binary files "a/pages/6-\345\211\215\347\253\257\347\237\245\350\257\206\347\202\271/images/\346\265\217\350\247\210\345\231\250\346\270\262\346\237\223\346\236\266\346\236\204\345\233\276.drawio.png" and "b/pages/6-\345\211\215\347\253\257\347\237\245\350\257\206\347\202\271/images/\346\265\217\350\247\210\345\231\250\346\270\262\346\237\223\346\236\266\346\236\204\345\233\276.drawio.png" differ