-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.json
2 lines (1 loc) · 78 KB
/
index.json
1
2
[{"content":"","date":"2024/11/19","externalUrl":null,"permalink":"/posts/","section":"","summary":"","title":"","type":"posts"},{"content":"欢迎来到我的网站!我很高兴你的来访。\n","date":"2024/11/19","externalUrl":null,"permalink":"/","section":"欢迎来到 Blowfish !","summary":"","title":"欢迎来到 Blowfish !","type":"page"},{"content":" 浅谈 ssr,ssg,csr,rsc # 前言 # 在文章正式开始之前我想澄清一个概念,我们所写的程序有开发,构建,生产等阶段,所谓的 ssr,ssg, csr,rsc,只是在这些特定的阶段做了特定的事情,进而形成的一种开发模式,澄清了这个概念,让我们正式进入正文\ncsr # csr(client side render),客户端渲染,它是前后端分离的经典产物,通过 spa 开发,实现在客户端的局部数据请求,让用户告别了点击刷新整页网页的时代,不仅优化了用户体验,同时对服务器的压力也更小,但伴随的是 seo 效果差,和首屏渲染速度过慢的问题\nssr # ssr(server side render),服务端渲染,它的出现解决了 seo 优化和首屏渲染过慢的问题,这里我通过 vite 和 react 实现 ssr 的过程简单讲讲为什么它可以解决上述问题。\n- index.html - server.js # main application server - src/ - main.js # 导出环境无关的(通用的)应用代码 - entry-client.js # 将应用挂载到一个 DOM 元素上 - entry-server.js # 使用某框架的 SSR API 渲染该应用 这是 vite 官方实现的 ssr 的文件目录,我们 ssr 的主要逻辑其实都是在 server.js 中。\n// entry-client.ts import React from \u0026#34;react\u0026#34;; import { hydrateRoot } from \u0026#34;react-dom/client\u0026#34;; import App from \u0026#34;./App\u0026#34;; import { fetchData } from \u0026#34;./entry-server\u0026#34;; import axios from \u0026#34;axios\u0026#34;; const rootDOM = document.getElementById(\u0026#34;root\u0026#34;); if (rootDOM) { hydrateRoot( rootDOM, \u0026lt;React.StrictMode\u0026gt; \u0026lt;App data={data} /\u0026gt; \u0026lt;/React.StrictMode\u0026gt; ); } else { throw new Error(\u0026#34;hydrate error\u0026#34;); } // entry-server.ts import App from \u0026#34;./App\u0026#34;; import \u0026#34;./index.css\u0026#34;; export async function fetchData() { return { user: \u0026#34;xxx\u0026#34;, }; } export function ServerEntry(props: any) { return \u0026lt;App data={props.data} /\u0026gt;; } // server.ts async function createSsrMiddleware(app: Express): Promise\u0026lt;RequestHandler\u0026gt; { ... return async (req, res, next) =\u0026gt; { // SSR 的逻辑 // 1. 加载服务端入口模块 // 2. 数据预取 // 3. 「核心」渲染组件 // 4. 拼接 HTML,返回响应 try { ... const { ServerEntry, fetchData } = await loadSsrEntryModule(vite); const data = await fetchData(); // 获取组件的html字符串 const appHtml = renderToString( React.createElement(ServerEntry, { data }) ); // 解析template.html文件 const templatePath = resolveTemplatePath(); let template = await memoryFsRead(templatePath); const html = template .replace(\u0026#34;\u0026lt;!-- SSR_APP --\u0026gt;\u0026#34;, appHtml) // 注入数据标签,用于客户端 hydrate .replace( \u0026#34;\u0026lt;!-- SSR_DATA --\u0026gt;\u0026#34;, `\u0026lt;script\u0026gt;window.__SSR_DATA__=${JSON.stringify(data)}\u0026lt;/script\u0026gt;` ); res.status(200).setHeader(\u0026#34;Content-Type\u0026#34;, \u0026#34;text/html\u0026#34;).end(html); } catch (e: any) { vite?.ssrFixStacktrace(e); console.error(e); res.status(500).end(e.message); } }; } async function createServer() { const app = express(); // 加入 Vite SSR 中间件 app.use(await createSsrMiddleware(app)); app.listen(3000, () =\u0026gt; { console.log(\u0026#34;Node 服务器已启动~\u0026#34;); console.log(\u0026#34;http://localhost:3000\u0026#34;); }); } createServer(); 这里就是对于 ssr 的一个简单实现,中间只包含了重要代码,具体实现的话可以参考 源码,我们不难发现,其实 ssr 的实现就是在生产阶段,通过中间件来对我们返回到客户端端代码进行处理,动态生成 html 框架,同时将要水合的 js 代码发送到客户端进行水合(因为有些 js 运行在浏览器的代码是不能在 nodejs 的环境中运行的,所以需要发送到客户端水合),当然如果服务器出现问题,也可以在中间件中进行 csr 降级,这里就不展开讨论,其实对于 ssr 来说,它解决首屏渲染过慢的方式其实是将渲染压力放到了服务端,通过在服务端提前组装 html 来减少客户端通过 js 生成 dom 节点的压力,同时这样生成的 html 结构也有助于 seo 优化\nssg # ssg(static site generation),静态站点生成,它总结一下其实就是构建阶段的 ssr,它生成 html 的时机是在 build 打包阶段,进行 html 的拼接,这样能减少服务器的渲染压力,让服务器有更快的响应速度,但同时 ssg 开发的网站一般都是静态的,不能有大范围的页面更改,所以 ssg 一般用于博客网站,以下是 ssg 构建的核心代码\nexport async function build(root: string = process.cwd(), config: SiteConfig) { // 1. bundle - client 端 + server 端 const [clientBundle] = await bundle(root, config); // 2. 引入 server-entry 模块 const serverEntryPath = join(root, \u0026#34;.temp\u0026#34;, \u0026#34;ssr-entry.js\u0026#34;); const { render, routes } = await import(serverEntryPath); // 3. 服务端渲染,产出 HTML try { await renderPages(render, routes, root, clientBundle); } catch (e) { console.log(\u0026#34;Render page error.\\n\u0026#34;, e); } } 其实对于 ssg 来说,开发的重点主要是插件,通过对插件进行开发,对 build 阶段的各个模块进行定制化处理,来提 ssg 的性能,和功能的扩展,同样,这里的具体代码也能参考 源码,在 ssg 框架中,有一个相对来说比较重要的概念,就是 island 架构,我们通过对在客户端需要进行水合的组件进行标记,在打包的阶段单独打包出来 bundle,在向客户端发送 js 代码的时候可以分包处理,达到渐进式增强的效果,而不是在客户端进行全量水合,不仅可以细粒度的控制水合,还可以减少 TTI 的时间,那么,ssr 有没有类似的升级呢,答案是有的,虽然没有 island 架构这样性能这么高,但是对比原始的 ssr 有了不少的提升。\nrsc # rsc(react server component),服务端组件,这是 next.js 提出来的一个重要理念,相对于传统的 ssr,next.js 将组件区分为服务端组件和客户端组件,对于服务端组件来说,是不需要进行水合的组件,而对于客户端组件来说,则是需要水合的,next.js 会将需要水合的 js 流式传输,渐进式增强的水合,以下是对客户端发送请求之后客户端返回的部分代码\n1d:[\u0026#34;$\u0026#34;,\u0026#34;div\u0026#34;,null,{\u0026#34;className\u0026#34;:\u0026#34;note-preview\u0026#34;,\u0026#34;children\u0026#34;:[\u0026#34;$\u0026#34;,\u0026#34;div\u0026#34;,null,{\u0026#34;className\u0026#34;:\u0026#34;text-with-markdown\u0026#34;,\u0026#34;dangerouslySetInnerHTML\u0026#34;:{\u0026#34;__html\u0026#34;:\u0026#34;\u0026lt;p\u0026gt;et iusto sed quo iure\u0026lt;/p\u0026gt;\\n\u0026#34;}},\u0026#34;$1e\u0026#34;]},\u0026#34;$1e\u0026#34;] 这个就是 rsc payload 的一部分,对于 rsc 来说,服务端组件是在服务端直接生成的,所以在 payload 中直接包含,而客户端组件则是通过一个占位符\u0026quot;$\u0026ldquo;来表示,等到了客户端再进行水合,同时这里的 rsc payload 的 Transfer-Encoding 是 chunked,通过分片上传来渐进式的水合客户端组件,细粒度的控制 js 水合的逻辑,当然,对于 next.js 来说,向服务端发送请求的时候还可以带上 next-router-state-tree 等参数,这样 rsc payload 可以记住之前的状态,达到状态保存的效果。\n再谈 ssg # 通过对比,我们不难发现,island 架构和 rsc 都是强调只为交互部分加载 js 文件,都是渐进式增强的水合,不同的是 rsc 是将客户端逻辑和服务端逻辑进行分离达到部分水合的效果,而 island 架构则是在框架层面通过特殊标识来控制水合效果\n小结 # 好了,说了这么多,其实最重要的不是哪种开发模式最好,而是要看具体的使用场景,面对不同的业务使用不同的开发模式,甚至可以混合使用,例如 rsc 何尝不是 ssr 和 csr 的结合呢,我们在开发过程中也要有自己独立的思考,敢于创新,才是正解,而不是一味的遵循某种开发模式,一头走到黑。 😉\n","date":"2024/11/19","externalUrl":null,"permalink":"/posts/%E6%B5%85%E8%B0%88-ssrssgcsrrsc/","section":"","summary":"","title":"浅谈 ssr,ssg,csr,rsc","type":"posts"},{"content":" 深入理解 bfc # 什么是 bfc # BFC(Block Formatting Context,块级格式化上下文)是 CSS 中的一个重要概念,用于描述页面上的一个独立的渲染区域,其中块级盒子按照特定的规则进行布局。BFC 的形成可以影响盒子的布局、浮动、清除浮动以及外边距塌陷等方面。\nbfc 的特性 # 内部的浮动元素不会影响外部的元素: 在 BFC 内部的浮动元素不会改变外部元素的布局。这意味着外部元素不会围绕 BFC 内部的浮动元素进行调整。\n外边距的合并: 在 BFC 内部,元素的垂直外边距不会与外部元素的垂直外边距合并。这可以避免意外的布局问题。\n包含浮动元素: BFC 可以包含浮动的元素,使其在 BFC 的高度中得到包含,从而避免父元素的高度为 0 的问题。\n避免外部的影响: BFC 中的元素不受外部元素的影响,例如外部的外边距、浮动等。\nbfc 的形成条件 # overflow 属性设置为 hidden、auto 或 scroll display 设置为 flex 或 grid position 设置为 absolute 或 fixed float 设置为 left 或 right 存在 border,padding 等属性 讨论 bfc 与边界坍塌的关系 # 对于早期的浏览器来说,我们为了让内容显示的更紧凑,浏览器会自动采用外边距合并的操作,取两个外边距较大的那个,以此来使用更少的页面展示更多的内容,而我们通过出发 bfc,让浏览器判断我们这里的内容是一个独立的块,或者让我们的内容脱离正常的文本流,都可以解决边界坍塌的问题(当然后者其实也不需要解决边界坍塌的问题)。\n代码实践 # \u0026lt;!DOCTYPE html\u0026gt; \u0026lt;html lang=\u0026#34;en\u0026#34;\u0026gt; \u0026lt;head\u0026gt; \u0026lt;meta charset=\u0026#34;UTF-8\u0026#34; /\u0026gt; \u0026lt;meta name=\u0026#34;viewport\u0026#34; content=\u0026#34;width=device-width, initial-scale=1.0\u0026#34; /\u0026gt; \u0026lt;title\u0026gt;BFC Example\u0026lt;/title\u0026gt; \u0026lt;style\u0026gt; .contain { border: 1px solid black; /* 为了方便观察 */ overflow: hidden; /* 触发 BFC */ } .flex-contain { border: 1px solid black; display: flex; flex-direction: column; } .float-box { float: left; width: 100px; height: 100px; background-color: lightblue; margin: 10px; } .normal-box { width: 100px; height: 100px; background-color: lightcoral; margin: 10px; } .fixed-box { position: fixed; width: 100px; height: 100px; background-color: lightcoral; margin: 10px; right: 0; } .absolute-box { position: absolute; width: 100px; height: 100px; background-color: lightcoral; margin: 10px; right: 0; } \u0026lt;/style\u0026gt; \u0026lt;/head\u0026gt; \u0026lt;body\u0026gt; \u0026lt;div class=\u0026#34;contain\u0026#34;\u0026gt; \u0026lt;div class=\u0026#34;float-box\u0026#34;\u0026gt;Float Box 1\u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;float-box\u0026#34;\u0026gt;Float Box 2\u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;contain\u0026#34;\u0026gt; \u0026lt;div class=\u0026#34;normal-box\u0026#34;\u0026gt;Normal Box 1\u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;normal-box\u0026#34;\u0026gt;Normal Box 2\u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;flex-contain\u0026#34;\u0026gt; \u0026lt;div class=\u0026#34;normal-box\u0026#34;\u0026gt;Normal Box 1\u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;normal-box\u0026#34;\u0026gt;Normal Box 2\u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;contain\u0026#34; style=\u0026#34;position: relative; height: 150px; width: 50vw\u0026#34;\u0026gt; \u0026lt;div class=\u0026#34;fixed-box\u0026#34;\u0026gt;Normal Box 1\u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;absolute-box\u0026#34;\u0026gt;Normal Box 2\u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/body\u0026gt; \u0026lt;/html\u0026gt; ","date":"2024/10/31","externalUrl":null,"permalink":"/posts/bfc/","section":"","summary":"","title":"深入理解 bfc","type":"posts"},{"content":" 前端代码规范 # 代码规范(记得将 node_modules 放到.gitignore 中) # Eslint # 项目集成\npnpm i eslint -D pnpm create @eslint/config@latest 配置文件\ninit 命令会自动生成 eslint 配置文件 prettier # 项目集成\npnpm i prettier eslint-config-prettier eslint-plugin-prettier -D 配置文件\n创建.prettierrc { \u0026#34;semi\u0026#34;: false, \u0026#34;tabWidth\u0026#34;: 2, \u0026#34;trailingComma\u0026#34;: \u0026#34;none\u0026#34;, \u0026#34;singleQuote\u0026#34;: true, \u0026#34;arrowParens\u0026#34;: \u0026#34;avoid\u0026#34; } git 规范 # Git 有很多的 hooks, 让我们在不同的阶段,对代码进行不同的操作,控制提交到仓库的代码的规范性,和准确性, 以下只是几个常用的钩子\npre-commit: 通过钩子函数,判断提交的代码是否符合规范\ncommit-msg: 通过钩子函数,判断 commit 信息是否符合规范\npre-push: 通过钩子,执行测试,避免对以前的内容造成影响\n工具 # husky: 操作 git 钩子的工具 lint-staged: 本地暂存代码检查工具 commitlint: commit 信息校验工具 commitizen: 辅助 commit 信息 ,就像这样,通过选择输入,规范提交信息 安装流程 # 安装代码校验依赖 pnpm i lint-staged husky -D pnpm set-script prepare \u0026#34;husky\u0026#34; # 在package.json中添加脚本,不过这个在npm 8.19.4之后就被废弃了,所以可以直接在package.json中添加脚本 pnpm prepare # 初始化husky,将 git hooks 钩子交由,husky执行 { \u0026#34;devDependencies\u0026#34;: { // ...其他配置 }, \u0026#34;scripts\u0026#34;: { \u0026#34;prepare\u0026#34;: \u0026#34;husky || true\u0026#34; // 如果只安装 dependencies(不是 devDependencies),\u0026#34;prepare\u0026#34;: \u0026#34;husky\u0026#34; 脚本可能会失败,因为 Husky 不会被安装。这里通过设置true可以避免出错 } } 初始化 husky, 会在根目录创建 .husky 文件夹\necho \u0026#34;npx lint-staged\u0026#34; \u0026gt; .husky/pre-commit pre-commit 执行 npx lint-staged 指令\n根目录创建 .lintstagedrc.json 文件控制检查和操作方式\n// .lintstagedrc.json { \u0026#34;*.{js,jsx,ts,tsx}\u0026#34;: [\u0026#34;prettier --write .\u0026#34;, \u0026#34;eslint --fix\u0026#34;], \u0026#34;*.md\u0026#34;: [\u0026#34;prettier --write\u0026#34;] } 安装提交信息依赖 pnpm i commitlint @commitlint/config-conventional -D echo \u0026#39;npx --no-install commitlint --edit \u0026#34;$1\u0026#34;\u0026#39; \u0026gt; .husky/commit-msg 这里我们需要新建 .commitlintrc.js 文件,写入我们的 commitlint/config-conventional 规范\n// .commitlintrc.js module.exports = { extends: [\u0026#34;@commitlint/config-conventional\u0026#34;], }; @commitlint/config-conventional 这是一个规范配置,标识采用什么规范来执行消息校验, 这个默认是 Angular 的提交规范,这里我们也可以用我们自己的方法来校验\necho \u0026#39;node [dir]/filename.js\u0026#39; \u0026gt; .husky/commit-msg # 指定目录文件 安装辅助提交依赖 安装指令和命令行展示信息\npnpm i commitizen cz-conventional-changelog -D 编写 commit 指令\n\u0026#34;scripts\u0026#34;: { \u0026#34;commit\u0026#34;: \u0026#34;git-cz\u0026#34; } 自定义提交规范 pnpm i -D commitlint-config-cz cz-customizable 修改.commitlintrc.js配置文件,用来配置提交辅助信息\nmodule.export = { extend: [\u0026#34;cz\u0026#34;], rules: { // 自定义规则 }, }; 配置.cz-config.js\n\u0026#34;use strict\u0026#34;; module.exports = { types: [ { value: \u0026#34;✨新增\u0026#34;, name: \u0026#34;新增: 新的内容\u0026#34; }, { value: \u0026#34;🐛修复\u0026#34;, name: \u0026#34;修复: 修复一个Bug\u0026#34; }, { value: \u0026#34;📝文档\u0026#34;, name: \u0026#34;文档: 变更的只有文档\u0026#34; }, { value: \u0026#34;💄格式\u0026#34;, name: \u0026#34;格式: 空格, 分号等格式修复\u0026#34; }, { value: \u0026#34;♻️重构\u0026#34;, name: \u0026#34;重构: 代码重构,注意和特性、修复区分开\u0026#34; }, { value: \u0026#34;⚡️性能\u0026#34;, name: \u0026#34;性能: 提升性能\u0026#34; }, { value: \u0026#34;✅测试\u0026#34;, name: \u0026#34;测试: 添加一个测试\u0026#34; }, { value: \u0026#34;🔧工具\u0026#34;, name: \u0026#34;工具: 开发工具变动(构建、脚手架工具等)\u0026#34; }, { value: \u0026#34;⏪回滚\u0026#34;, name: \u0026#34;回滚: 代码回退\u0026#34; }, ], scopes: [ { name: \u0026#34;leetcode\u0026#34; }, { name: \u0026#34;javascript\u0026#34; }, { name: \u0026#34;typescript\u0026#34; }, { name: \u0026#34;Vue\u0026#34; }, { name: \u0026#34;node\u0026#34; }, ], // it needs to match the value for field type. Eg.: \u0026#39;fix\u0026#39; /* scopeOverrides: { fix: [ {name: \u0026#39;merge\u0026#39;}, {name: \u0026#39;style\u0026#39;}, {name: \u0026#39;e2eTest\u0026#39;}, {name: \u0026#39;unitTest\u0026#39;} ] }, */ // override the messages, defaults are as follows messages: { type: \u0026#34;选择一种你的提交类型:\u0026#34;, scope: \u0026#34;选择一个scope (可选):\u0026#34;, // used if allowCustomScopes is true customScope: \u0026#34;Denote the SCOPE of this change:\u0026#34;, subject: \u0026#34;短说明:\\n\u0026#34;, body: \u0026#39;长说明,使用\u0026#34;|\u0026#34;换行(可选):\\n\u0026#39;, breaking: \u0026#34;非兼容性说明 (可选):\\n\u0026#34;, footer: \u0026#34;关联关闭的issue,例如:#31, #34(可选):\\n\u0026#34;, confirmCommit: \u0026#34;确定提交说明?(yes/no)\u0026#34;, }, allowCustomScopes: true, allowBreakingChanges: [\u0026#34;特性\u0026#34;, \u0026#34;修复\u0026#34;], // limit subject length subjectLimit: 100, }; 在package.json中修改原来的commit配置\n\u0026#34;config\u0026#34;: { \u0026#34;commitizen\u0026#34;: { \u0026#34;path\u0026#34;: \u0026#34;./node_modules/cz-customizable\u0026#34; } } 测试 # 如果有测试文件可以在 pre-push 的时候运行,当然如果想运行的话需要在 package.json 文件中添加 test 脚本\necho \u0026#39;pnpm test\u0026#39; \u0026gt; .husky/pre-push 小结:由于 eslint 等前端代码规范工具版本大更新,新增了扁平化配置,导致之前的很多命令现在无法使用,本文主要针对前端规范工具的一次简单配置,集成代码编写规范,代码提交规范,测试功能。\n","date":"2024/10/28","externalUrl":null,"permalink":"/posts/%E5%89%8D%E7%AB%AF%E4%BB%A3%E7%A0%81%E8%A7%84%E8%8C%83/","section":"","summary":"","title":"前端代码规范","type":"posts"},{"content":" 实现 ajax # 什么是 ajax # Ajax(Asynchronous JavaScript and XML)是一种用于创建交互式网页应用程序的技术。Ajax 利用 JavaScript、XML(现在更常用 JSON)、HTML 和 CSS 等技术,通过在后台与服务器进行少量数据交换,实现无需刷新整个页面的动态加载内容的功能。ajax 的出现标志着前后端分离的开始,正是 ajax 与各种前端框架的出现,使得前端可以应对复杂的开发情况,可以局部更新网页,通过 diff 算法更新虚拟 dom,极大的优化了用户体验,并衍生了 spa 等开发模式。\nhttp 请求 # 请求报⽂有 4 部分组成:\n请求⾏ 请求头部 空⾏ 请求体 所谓的 ajax,说的其实是发送 http 请求,拿到数据之后局部更新网页,ajax 代表的是一种思想,它的本质还是发送 http 请求,只不过在这里我们从服务端获取到的不再是完整的 html,而是一个可以在前端被我们解析的数据,通过处理,更新 html 节点,来达到局部更新的效果,所以我们的目的就是通过写 http 请求拿到我们想要的数据。\najax 获取的数据 # 当谈论到 ajax 获取的数据时,大多数时候指的都是 XML 和 JSON,以下是关于 XML 和 JSON 的一些重要信息:\nXML(Extensible Markup Language) # 结构化数据:XML 是一种标记语言,用于描述数据的结构。它由标签、属性和文本组成,可以表示复杂的数据结构。 可扩展性:XML 具有很高的扩展性,可以定义自定义标签和结构,适用于各种不同领域和应用。 繁琐性:XML 的语法相对冗长,标签需要成对出现,可能会导致文件体积较大。 解析:在 JavaScript 中,可以使用XMLHttpRequest对象或现代浏览器提供的DOMParser或XMLSerializer来解析和处理 XML 数据。 \u0026lt;!-- 书籍信息示例 --\u0026gt; \u0026lt;bookstore\u0026gt; \u0026lt;book category=\u0026#34;fiction\u0026#34;\u0026gt; \u0026lt;title\u0026gt;Harry Potter\u0026lt;/title\u0026gt; \u0026lt;author\u0026gt;J.K. Rowling\u0026lt;/author\u0026gt; \u0026lt;year\u0026gt;2005\u0026lt;/year\u0026gt; \u0026lt;price\u0026gt;29.99\u0026lt;/price\u0026gt; \u0026lt;/book\u0026gt; \u0026lt;book category=\u0026#34;non-fiction\u0026#34;\u0026gt; \u0026lt;title\u0026gt;Learning XML\u0026lt;/title\u0026gt; \u0026lt;author\u0026gt;Erik T. Ray\u0026lt;/author\u0026gt; \u0026lt;year\u0026gt;2003\u0026lt;/year\u0026gt; \u0026lt;price\u0026gt;39.95\u0026lt;/price\u0026gt; \u0026lt;/book\u0026gt; \u0026lt;/bookstore\u0026gt; JSON(JavaScript Object Notation) # 轻量级:JSON 是一种轻量级的数据交换格式,易于阅读和编写,也易于解析和生成。 数据结构:JSON 由键值对构成,数据可以是数组或对象,非常适合表示结构化数据。 可读性:与 XML 相比,JSON 的语法更为简洁,易于理解和处理,也更适合在网络传输中使用。 解析:在 JavaScript 中,可以使用JSON.parse()方法将 JSON 字符串解析为 JavaScript 对象,使用JSON.stringify()方法将 JavaScript 对象序列化为 JSON 字符串。 { \u0026#34;bookstore\u0026#34;: { \u0026#34;books\u0026#34;: [ { \u0026#34;category\u0026#34;: \u0026#34;fiction\u0026#34;, \u0026#34;title\u0026#34;: \u0026#34;Harry Potter\u0026#34;, \u0026#34;author\u0026#34;: \u0026#34;J.K. Rowling\u0026#34;, \u0026#34;year\u0026#34;: 2005, \u0026#34;price\u0026#34;: 29.99 }, { \u0026#34;category\u0026#34;: \u0026#34;non-fiction\u0026#34;, \u0026#34;title\u0026#34;: \u0026#34;Learning XML\u0026#34;, \u0026#34;author\u0026#34;: \u0026#34;Erik T. Ray\u0026#34;, \u0026#34;year\u0026#34;: 2003, \u0026#34;price\u0026#34;: 39.95 } ] } } 在现代 Web 开发中,JSON 通常被认为是更优选的数据交换格式,因为它更简洁、轻量且易于处理。XML 则在某些特定场景仍然有其用武之地,特别是需要严格结构化数据或复杂文档的情况下。\n用 xmlHttpRequest 实现 ajax # const SERVER_URL = \u0026#34;/sever\u0026#34;; // 创建xhr对象 const xhr = new xmlHttpRequest(); // 配置请求行,这里的true是指异步请求 xhr.open(\u0026#34;GET\u0026#34;, SERVER_URL, true); // 监听xhr请求状态变化 xhr.onreadystatechange = function () { if (this.readyStatus !== 4) { return; } if (this.status === 200) { // 请求成功 handle(this.response); } else { // 请求失败,这里失败的原因有很多种,可能是206,也有可能是3xx,总之就是服务器返回了状态码,但并不受我们想要的状态码 console.error(this.statusText); } }; // 监听xhr请求失败,这里失败的原因可能是超时等问题,服务器没有正常返回状态码 xhr.onerror = function () { console.error(this.statusText); }; // 配置请求头 xhr.responseType = \u0026#34;json\u0026#34;; xhr.setRequestHeader(\u0026#34;accept\u0026#34;, \u0026#34;application/json\u0026#34;); xhr.send(null); 用 Promise 封装 ajax 请求 # 在实际应用中,我们习惯对网路请求对方法进行一次封装,这样可以达到解耦的目的,简单来说,就是如果那一天xmlHttpRequest或是axios不能用了,或者是 api 接口改了,我们更方便的更新我们的业务代码,所以这里我们讲用 promise 对 ajax 请求进行一次封装\nfunction getJSON(url) { return new Promise((resolve, reject) =\u0026gt; { // 创建xhr对象 const xhr = new xmlHttpRequest(); // 配置请求行,这里的true是指异步请求 xhr.open(\u0026#34;GET\u0026#34;, url, true); // 监听xhr请求状态变化 xhr.onreadystatechange = function () { if (this.readyStatus !== 4) { return; } if (this.status === 200) { // 请求成功 resolve(this.response); } else { // 请求失败,这里失败的原因有很多种,可能是206,也有可能是3xx,总之就是服务器返回了状态码,但并不受我们想要的状态码 reject(this.statusText); } }; // 监听xhr请求失败,这里失败的原因可能是超时等问题,服务器没有正常返回状态码 xhr.onerror = function () { reject(this.statusText); }; // 配置请求头 xhr.responseType = \u0026#34;json\u0026#34;; xhr.setRequestHeader(\u0026#34;accept\u0026#34;, \u0026#34;application/json\u0026#34;); xhr.send(null); }); } 小结 # 总的来说,ajax 的出现确实推动的前后端分离的发展,将一部分后端逻辑放在前端实现,让前端可以应对复杂情况的开发,同时优化用户体验\n","date":"2024/10/16","externalUrl":null,"permalink":"/posts/%E5%AE%9E%E7%8E%B0ajax/","section":"","summary":"","title":"实现ajax","type":"posts"},{"content":" JavaScript 正则表达式用法整理大纲 # 介绍 # 正则表达式(Regex)是一种用于匹配字符串中字符模式的工具,它可以用于搜索、替换和验证文本,例如检查邮箱格式、提取特定数据或清洗数据。正则表达式的灵活性使其在文本处理和数据分析中非常有用,它在 JavaScript 中的文本处理非常重要,因其可以高效地搜索、匹配和替换字符串。通过正则表达式,开发者可以快速找到特定模式的文本,如电子邮件地址、电话号码等,或者对文本进行复杂的替换操作。此外,正则表达式在数据清洗、格式验证和日志分析中也广泛应用,帮助简化和优化代码逻辑。 基础语法 # 元字符\n.:匹配除换行符之外的任意字符。 \\d:匹配数字。 \\w:匹配字母、数字、下划线。 \\s:匹配空白字符。 字符类:\n方括号 []:匹配方括号内的任意一个字符,例如 [abc] 匹配 a、b 或 c。 取反字符 ^:在方括号的开头,表示匹配不在方括号内的字符,例如 [^abc] 匹配除了 a、b、c 之外的任何字符。 数量词:\n*:匹配零个或多个前面的字符,例如 a* 匹配空字符串或多个 a。 +:匹配一个或多个前面的字符,例如 a+ 匹配至少一个 a。 ?:匹配零个或一个前面的字符,例如 a? 匹配空字符串或一个 a。 {n}:匹配恰好 n 个前面的字符,例如 a{3} 匹配 aaa。 {n,}:匹配至少 n 个前面的字符,例如 a{2,} 匹配两个或更多 a。 {n,m}:匹配 n 到 m 个前面的字符,例如 a{2,4} 匹配 aa、aaa 或 aaaa。 锚点:\n^:匹配字符串的开头。 $:匹配字符串的结尾。 转义字符:\n反斜杠 \\:用于转义特殊字符,例如 \\. 匹配字面上的点号。 分组和选择:\n圆括号 ():用于分组,例如 (abc) 可以作为一个整体进行匹配。 竖线 |:表示“或”,例如 a|b 匹配 a 或 b。 常用修饰符:\ni 修饰符:忽略大小写。 g 修饰符:全局匹配。 m 修饰符:多行匹配。 常用方法 # test()方法:检测字符串是否匹配正则表达式。 # 在 JavaScript 的正则表达式中,test() 方法是一个用于测试一个字符串是否匹配某个正则表达式的方法。这个方法返回一个布尔值,如果字符串与正则表达式匹配,则返回 true,否则返回 false。\n语法: # regex.test(string); 参数: # regex: 要匹配的正则表达式。 string: 要测试的字符串。 返回值: # 如果字符串与正则表达式匹配,则返回 true,否则返回 false。 示例: # const regex = /hello/; const str1 = \u0026#34;Hello, world!\u0026#34;; const str2 = \u0026#34;Hi there!\u0026#34;; console.log(regex.test(str1)); // 输出: true,因为 \u0026#34;Hello\u0026#34; 在字符串中被找到 console.log(regex.test(str2)); // 输出: false,因为 \u0026#34;hello\u0026#34; 在字符串中未被找到 exec()方法:在字符串中执行正则表达式搜索,并返回匹配的结果。 # 在 JavaScript 的正则表达式中,exec() 方法是一个用于在字符串中执行一个搜索匹配的方法。当一个字符串与正则表达式匹配时,exec() 方法会返回一个数组,其中存储了匹配的结果。如果没有找到匹配,exec() 方法返回 null。\n语法: # regex.exec(string); 参数: # regex: 要匹配的正则表达式。 string: 要进行匹配的字符串。 返回值: # 如果匹配成功,exec() 方法返回一个数组,其中第一个元素是与整个正则表达式匹配的文本,后续元素是正则表达式中每个捕获组所匹配的文本。如果没有找到匹配,则返回 null。 示例: # const regex = /l\\w{2}/g; const str = \u0026#34;Hello, world!\u0026#34;; let result; while ((result = regex.exec(str)) !== null) { console.log(\u0026#34;Found: \u0026#34; + result[0] + \u0026#34;, Index: \u0026#34; + result.index); } // 这里输出的内容是 // Found: llo, Index: 2 // Found: ld, Index: 10 高级技巧 # 贪婪与非贪婪匹配。 # 在正则表达式中,贪婪匹配和非贪婪匹配是指正则表达式在匹配过程中的两种不同的行为。这两种匹配方式会影响正则表达式匹配的行为和结果。\n贪婪匹配:\n贪婪匹配是指正则表达式尽可能多地匹配字符。 当使用贪婪匹配时,正则表达式会尽可能匹配更多的字符,直到无法继续匹配为止。 贪婪匹配通常在量词后面加上一个 ? 来表示非贪婪匹配。 示例:\n/.+/ 这个正则表达式是贪婪匹配,它会匹配尽可能多的字符,直到遇到换行符为止。 非贪婪匹配(也称为懒惰匹配或最小匹配):\n非贪婪匹配是指正则表达式尽可能少地匹配字符。 当使用非贪婪匹配时,正则表达式会尽可能匹配更少的字符,以满足匹配条件。 非贪婪匹配通常在量词后面加上一个 ? 来表示非贪婪匹配。 示例:\n/.+?/ 这个正则表达式是非贪婪匹配,它会匹配尽可能少的字符,直到满足匹配为止。 示例\nconst text = \u0026#34;\u0026lt;p\u0026gt;Hello, \u0026lt;b\u0026gt;world\u0026lt;/b\u0026gt;\u0026lt;/p\u0026gt;\u0026#34;; // 贪婪匹配示例 const greedyRegex = /\u0026lt;.*\u0026gt;/; // 贪婪匹配,匹配尽可能多的字符 const greedyMatch = text.match(greedyRegex); console.log(\u0026#34;Greedy match:\u0026#34;, greedyMatch[0]); // 输出: \u0026#34;\u0026lt;p\u0026gt;Hello, \u0026lt;b\u0026gt;world\u0026lt;/b\u0026gt;\u0026lt;/p\u0026gt;\u0026#34; // 非贪婪匹配示例 const lazyRegex = /\u0026lt;.*?\u0026gt;/; // 非贪婪匹配,匹配尽可能少的字符 const lazyMatch = text.match(lazyRegex); console.log(\u0026#34;Lazy match:\u0026#34;, lazyMatch[0]); // 输出: \u0026#34;\u0026lt;p\u0026gt;\u0026#34; 在上面的示例中,我们使用了两种正则表达式。\u0026lt;.*\u0026gt; 表示贪婪匹配,它会匹配尽可能多的字符,所以整个 \u0026lt;p\u0026gt;Hello, \u0026lt;b\u0026gt;world\u0026lt;/b\u0026gt;\u0026lt;/p\u0026gt; 被匹配了。而 \u0026lt;.*?\u0026gt; 表示非贪婪匹配,它会匹配尽可能少的字符,因此只匹配到了第一个\u0026lt;p\u0026gt;。\n前瞻断言和后顾断言。 # 在正则表达式中,前瞻断言(positive lookahead)和后顾断言(positive lookbehind)是一种零宽断言(zero-width assertions),它们用于在匹配字符串时指定必须满足但并不包含在最终匹配结果中的条件。\n前瞻断言(Positive Lookahead): 前瞻断言用于查找在其后跟随指定模式的位置。 语法:(?=pattern) 示例:/foo(?=bar)/ 匹配后面紧跟着 \u0026ldquo;bar\u0026rdquo; 的 \u0026ldquo;foo\u0026rdquo;,但只匹配 \u0026ldquo;foo\u0026rdquo;,不匹配 \u0026ldquo;bar\u0026rdquo;。 后顾断言(Positive Lookbehind): 后顾断言用于查找在其前面紧跟指定模式的位置。 语法:(?\u0026lt;=pattern) 示例:/(?\u0026lt;=foo)bar/ 匹配前面紧跟着 \u0026ldquo;foo\u0026rdquo; 的 \u0026ldquo;bar\u0026rdquo;,但只匹配 \u0026ldquo;bar\u0026rdquo;,不匹配 \u0026ldquo;foo\u0026rdquo;。 示例 1:使用前瞻断言\nconst str = \u0026#34;Hello, world!\u0026#34;; const regex = /Hello, (?=world)/; const result = str.match(regex); console.log(result); // 输出: [\u0026#34;Hello, \u0026#34;] 在这个示例中,正则表达式 /Hello, (?=world)/ 使用了前瞻断言,匹配后面紧跟着 \u0026ldquo;world\u0026rdquo; 的 \u0026ldquo;Hello, \u0026ldquo;,但只匹配 \u0026ldquo;Hello, \u0026ldquo;,并将结果存储在 result 中。\n示例 2:使用后顾断言\nconst str = \u0026#34;Hello, world!\u0026#34;; const regex = /(?\u0026lt;=Hello, )world/; const result = str.match(regex); console.log(result); // 输出: [\u0026#34;world\u0026#34;] 在这个示例中,正则表达式 /(?\u0026lt;=Hello, )world/ 使用了后顾断言,匹配前面紧跟着 \u0026ldquo;Hello, \u0026quot; 的 \u0026ldquo;world\u0026rdquo;,但只匹配 \u0026ldquo;world\u0026rdquo;,并将结果存储在 result 中。\n替换和捕获组。 # 在正则表达式中,替换和捕获组是两个重要的概念,它们用于在文本处理中查找、匹配和替换特定模式的字符串。\n捕获组(Capturing Groups):\n捕获组是用圆括号 () 包围的正则表达式部分,它用于将匹配的子字符串分组。 可以通过捕获组从匹配的文本中提取所需的部分,或者在替换操作中引用捕获组中的内容。 捕获组可以使用索引或名称来访问。 替换操作(Replacement):\n在正则表达式中,可以使用替换操作将匹配到的文本替换为指定的字符串。 替换操作通常使用 replace() 方法来实现。 替换操作可以包括捕获组中的内容,通过使用特殊的字符引用捕获组。 示例:\nconst str = \u0026#34;2022-10-05\u0026#34;; const regex = /(\\d{4})-(\\d{2})-(\\d{2})/; const result = str.replace(regex, \u0026#34;$2/$3/$1\u0026#34;); console.log(result); // 输出: \u0026#34;10/05/2022\u0026#34; 在这个示例中,正则表达式 (\\d{4})-(\\d{2})-(\\d{2}) 匹配日期格式 \u0026ldquo;YYYY-MM-DD\u0026rdquo;,并将年、月、日分别作为捕获组。在替换操作中,$1、$2、$3 分别表示第一个、第二个和第三个捕获组的内容,从而实现将日期格式转换为 \u0026ldquo;MM/DD/YYYY\u0026rdquo; 的形式。\n补充示例:\nconst str = \u0026#34;apple, orange, banana\u0026#34;; const newStr = str.replace(/(\\w+)/g, (match, content, index, str) =\u0026gt; match.toUpperCase() ); console.log(newStr); // 输出: \u0026#34;APPLE, ORANGE, BANANA\u0026#34; 在这里 match 表示匹配到的字符串,content 表示捕获组中的内容,这里捕获组可以有多个,取决于正则中捕获组()的个数,其中如果是(?:pattern),则是非捕获组,str 表示原始字符串,index 表示匹配到正则表达式字符串的索引值\n总结 # 正则表达式在 JavaScript 中非常重要且灵活。它们强大地支持模式匹配,可用于字符串操作和文本处理。正则表达式具有灵活性和可扩展性,能够优化性能并提高效率。通过将常用表达式封装,可以实现表达式的复用和维护,使代码更简洁、可读性更强。\n","date":"2024/10/06","externalUrl":null,"permalink":"/posts/%E5%85%B3%E4%BA%8E%E6%AD%A3%E5%88%99/","section":"","summary":"","title":"关于正则的整理","type":"posts"},{"content":" 前端性能的重要性和影响 # 用户体验:\n快速加载时间和流畅的用户体验是用户对网站或应用程序的首要期待。优化前端性能可以显著加速页面加载速度,减少等待时间,提高用户满意度。\n用户留存率和转化率:\n用户在面对加载缓慢的网站时往往会选择离开,这会导致较高的跳出率。通过提升前端性能,可以降低跳出率,提高用户留存率,增加转化率。\n搜索引擎优化(SEO):\n搜索引擎对网站的加载速度有一定程度的考量,加载速度快的网站更有可能在搜索结果中获得更好的排名。通过优化前端性能,可以提升网站的 SEO 表现。\n移动设备体验:\n在移动设备上访问网站的用户越来越多,而移动设备的网络连接和处理能力通常相对较弱。因此,优化前端性能对移动设备用户体验至关重要。\n节约成本:\n随着用户对网站性能的要求提高,性能较差的网站可能会导致用户流失和损失的收入。通过前端性能优化,可以提高网站的效率,减少服务器负载,节约服务器成本和流量费用。\n品牌形象: 网站的性能直接影响用户对品牌的印象。一个加载缓慢的网站可能会给用户留下不良印象,反之,一个加载快速、响应迅速的网站有助于提升品牌形象。\n前端性能分析的几个重要指标 # 页面渲染流程图: First Contentful Paint (FCP) 首次内容绘制,表示浏览器从用户的角度首次渲染出有意义的内容的时间。这个时间点通常是页面中第一个文本、图片或其他 DOM 元素的显示时间。FCP 的时间越短,用户越早能看到页面内容,感知到页面开始加载。\nLargest Contentful Paint (LCP) 最大内容绘制,表示页面加载过程中最大的可见内容(如图片或大块文本)被渲染完成的时间。这个指标反映了页面的主要内容什么时候加载完成,通常是影响用户感知页面加载速度的关键指标。LCP 理想时间应小于 2.5 秒。\nTime to Interactive (TTI) 可交互时间,表示页面完全加载并可以响应用户交互的时间。这是页面从开始加载到完全准备好处理用户输入的时间段。如果 TTI 时间过长,用户虽然看到了内容,但可能点击按钮或链接时不会有响应。\nTotal Blocking Time (TBT) 总阻塞时间,指的是页面的加载过程被长任务阻塞的时间(超过 50ms 的任务)。长任务通常是导致用户交互迟缓的原因,因此减少 TBT 是提升页面响应速度的关键。\nCumulative Layout Shift (CLS) 累积布局偏移,用于衡量页面上元素的视觉稳定性。页面内容加载时,可能会因为图片、广告或字体加载导致布局发生变化。如果页面内容频繁移动,会导致用户体验差。CLS 越低越好,通常建议保持在 0.1 以下。\nFirst Input Delay (FID) 首次输入延迟,指的是用户第一次尝试与页面进行交互(如点击按钮、链接)到浏览器能够响应这个交互之间的时间。FID 反映了页面的交互性,较低的 FID 说明用户体验更流畅,理想的 FID 应小于 100 毫秒。\nSpeed Index (SI) 速度指数,用于衡量页面内容在视窗内的加载速度。Speed Index 反映了页面加载的视觉进度,它越低,页面加载看起来越快。这个指标结合了多个时间节点来确定用户看到完整页面内容的时间。\nDOM Content Loaded (DCL) DOM 内容加载完成时间,指的是 HTML 文档被完全解析且不包括样式表、图片等资源的加载时间。当 DCL 事件触发时,页面的结构已经构建完成,但可能还有其他外部资源未完全加载。\nJavaScript Execution Time 这个指标反映了页面加载过程中,JavaScript 代码的执行时间。如果 JavaScript 执行时间过长,可能会导致页面加载和交互变慢。\nwindow.performance 的 timing 属性 # timing: { // 同一个浏览器上一个页面卸载(unload)结束时的时间戳。如果没有上一个页面,这个值会和fetchStart相同。 navigationStart: 1543806782096, // 上一个页面unload事件抛出时的时间戳。如果没有上一个页面,这个值会返回0。 unloadEventStart: 1543806782523, // 和 unloadEventStart 相对应,unload事件处理完成时的时间戳。如果没有上一个页面,这个值会返回0。 unloadEventEnd: 1543806782523, // 第一个HTTP重定向开始时的时间戳。如果没有重定向,或者重定向中的一个不同源,这个值会返回0。 redirectStart: 0, // 最后一个HTTP重定向完成时(也就是说是HTTP响应的最后一个比特直接被收到的时间)的时间戳。 // 如果没有重定向,或者重定向中的一个不同源,这个值会返回0. redirectEnd: 0, // 浏览器准备好使用HTTP请求来获取(fetch)文档的时间戳。这个时间点会在检查任何应用缓存之前。 fetchStart: 1543806782096, // DNS 域名查询开始的UNIX时间戳。 //如果使用了持续连接(persistent connection),或者这个信息存储到了缓存或者本地资源上,这个值将和fetchStart一致。 domainLookupStart: 1543806782096, // DNS 域名查询完成的时间. //如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等 domainLookupEnd: 1543806782096, // HTTP(TCP) 域名查询结束的时间戳。 //如果使用了持续连接(persistent connection),或者这个信息存储到了缓存或者本地资源上,这个值将和 fetchStart一致。 connectStart: 1543806782099, // HTTP(TCP) 返回浏览器与服务器之间的连接建立时的时间戳。 // 如果建立的是持久连接,则返回值等同于fetchStart属性的值。连接建立指的是所有握手和认证过程全部结束。 connectEnd: 1543806782227, // HTTPS 返回浏览器与服务器开始安全链接的握手时的时间戳。如果当前网页不要求安全连接,则返回0。 secureConnectionStart: 1543806782162, // 返回浏览器向服务器发出HTTP请求时(或开始读取本地缓存时)的时间戳。 requestStart: 1543806782241, // 返回浏览器从服务器收到(或从本地缓存读取)第一个字节时的时间戳。 //如果传输层在开始请求之后失败并且连接被重开,该属性将会被数制成新的请求的相对应的发起时间。 responseStart: 1543806782516, // 返回浏览器从服务器收到(或从本地缓存读取,或从本地资源读取)最后一个字节时 //(如果在此之前HTTP连接已经关闭,则返回关闭时)的时间戳。 responseEnd: 1543806782537, // 当前网页DOM结构开始解析时(即Document.readyState属性变为“loading”、相应的 readystatechange事件触发时)的时间戳。 domLoading: 1543806782573, // 当前网页DOM结构结束解析、开始加载内嵌资源时(即Document.readyState属性变为“interactive”、相应的readystatechange事件触发时)的时间戳。 domInteractive: 1543806783203, // 当解析器发送DOMContentLoaded 事件,即所有需要被执行的脚本已经被解析时的时间戳。 domContentLoadedEventStart: 1543806783203, // 当所有需要立即执行的脚本已经被执行(不论执行顺序)时的时间戳。 domContentLoadedEventEnd: 1543806783216, // 当前文档解析完成,即Document.readyState 变为 \u0026#39;complete\u0026#39;且相对应的readystatechange 被触发时的时间戳 domComplete: 1543806783796, // load事件被发送时的时间戳。如果这个事件还未被发送,它的值将会是0。 loadEventStart: 1543806783796, // 当load事件结束,即加载事件完成时的时间戳。如果这个事件还未被发送,或者尚未完成,它的值将会是0. loadEventEnd: 1543806783802 } 通过以上数据,我们能得到一些性能指标数据\n// 重定向耗时 redirect: timing.redirectEnd - timing.redirectStart, // DOM 渲染耗时 dom: timing.domComplete - timing.domLoading, // 页面加载耗时 load: timing.loadEventEnd - timing.navigationStart, // 页面卸载耗时 unload: timing.unloadEventEnd - timing.unloadEventStart, // 请求耗时 request: timing.responseEnd - timing.requestStart, // 获取性能信息时当前时间 time: new Date().getTime(), 还有一个比较重要的时间就是白屏时间,它指从输入网址,到页面开始显示内容的时间。\n将以下脚本放在 \u0026lt;/head\u0026gt; 前面就能获取白屏时间。\n\u0026lt;script\u0026gt; whiteScreen = new Date() - performance.timing.navigationStart; // 通过 domLoading 和 navigationStart 也可以 whiteScreen = performance.timing.domLoading - performance.timing.navigationStart; \u0026lt;/script\u0026gt; 前端性能优化的策略 # 减少 HTTP 请求:\n减少页面中的资源文件(如 CSS、JavaScript、图片等)数量,可以通过合并文件、使用 CSS Sprites(CSS 精灵)、将多个脚本文件合并为一个等方式来减少 HTTP 请求次数,从而加快页面加载速度。\n压缩资源:\n压缩 CSS、JavaScript 和图片等资源文件,减小文件大小,可以通过工具如 Gzip、Brotli 等进行压缩,以减少文件传输时间和页面加载时间。\n使用缓存:\n启用浏览器缓存机制,设置合适的缓存策略,利用浏览器缓存静态资源,减少重复的网络请求,提高页面加载速度。\n代码优化:\n优化 JavaScript 代码,减少不必要的代码、避免过度深度嵌套、减少全局变量使用等,以提高代码的执行效率。\n异步加载资源:\n将不影响页面渲染的资源(如广告、统计代码等)使用异步加载或延迟加载的方式,避免阻塞页面渲染过程,提高用户体验。\n优化图片:\n选择合适的图片格式(如 WebP、JPEG、PNG)和优化图片大小,使用响应式图片,以减小图片文件大小,提高页面加载速度。\n延迟加载:\n对于长页面或需要滚动才能看到的内容,可以延迟加载图片和其他资源,延迟加载非关键资源,优先加载可见部分的内容,提高页面加载速度。\n减少重绘和回流:\n避免频繁的 DOM 操作和样式变更,合理使用 CSS 布局,尽量减少引起浏览器重绘和回流的操作,提高页面渲染性能。\n服务端渲染(SSR): 对于需要 SEO 优化的页面,考虑使用服务端渲染(SSR),提高页面首屏加载速度和 SEO 表现。\n小结:总之,前端性能优化至关重要,直接影响用户体验、留存率和品牌形象。通过分析关键性能指标如 FCP、LCP 和 TTI,我们可以深入了解加载过程的各个环节。优化策略包括减少 HTTP 请求、压缩资源、利用缓存、异步加载、图片优化等,旨在提高页面加载速度和响应能力。最终,通过系统的性能优化,网站不仅能提升用户满意度,还能在搜索引擎中获得更好的排名,实现可持续的发展。\n","date":"2024/10/05","externalUrl":null,"permalink":"/posts/%E6%B5%8F%E8%A7%88%E5%99%A8%E6%80%A7%E8%83%BD%E5%88%86%E6%9E%90/","section":"","summary":"","title":"浏览器性能分析","type":"posts"},{"content":" 实习经验分享 # 引言 # 我实习的公司是上海刻行时空科技有限公司,所在的部门是web组,岗位是前端开发实习生,我的职责主要是协助完成页面整改,对页面样式进行调整,修改页面bug,对页面已有组件进行封装。\n学习和成长 # 在本次实习过程中,我了解到了维护和开发一个项目的逻辑是怎样的,学会了和后端、测试人员协同开发,共同推进工作进度,就技术层面上来说,我学会了怎么开发和封装一个组件,怎么让的渲染效率变得更高,怎么调整组件的样式来做响应式适配,技术栈也得到了不小的提升,学习了tailwind,ts等开发语言和预处理语言,对前端的项目组织逻辑也有了新的认知。\n项目和任务 # 我主要参与了web-platform和登录界面的开发,主要参与页面样式调整,和登录验证逻辑的调整,一开始写的时候样式没有办法做响应式,然后还会和设计稿有出入,但在琦哥的指导下,不断优化,找到不合理的样式代码进行调整,慢慢的也可以做到响应式,和产品的设计稿一致,同时也方便后期调整,我也学习到有些样式不能不能为了项目需求随意添加,要注意样式之间的相互影响。然后就是对于登录验证的逻辑判断,要确保与后端交互的完整性和安全性,注意逻辑严谨性,避免出现安全漏洞\n团队合作 # 通过这次实习我也明白遇到问题要及时沟通,对于一个功能的实现是前端,后端,产品一起合作完成的,不应该自己一个做而不去交流,这样不仅会导致做出来的功能和产品想法不一致,在网页对数据处理的效率也会降低,导致用户体验差,同时遇到问题也要学会去自己解决,学会查文档,而不是问一些很基础很重复的问题,然后就是自己对自己的工作能力应该有一定的判断,及时告知团队自己能完成多少任务,方便大家进行合作安排,及时推进进度\n个人成长 # 总的来说这次实习我学会了如何进行团队合作,积累了项目经验,然后也和不少优秀的人学习和交流了很多,学习了技术,也学习了怎么学习技术,然后也明白自己其实要学习要成长的地方还有很多,要坚持学习和不断更新自己的技术栈\n结语 # 很感谢宇靖哥能给我这份实习的机会,让我有机会成长和学习,公司的氛围也很好,每个人都很和蔼友善,这段时间有受大家很多照顾,感谢琦哥耐心严谨的教导我,让我学习了很多东西,军辉总会给我讲前沿的技术,家琪总是很细心的纠正我的样式上的错误,玲怡姐很和蔼,每次都很耐心的给我讲设计思路,越进很靠谱,总是很及时的给我要用的接口,巨超很幽默,虽然没有什么工作上的来往,但是经常有调侃我哈哈,大家玩的时候也会带我,每周三会举行例跑,释放压力,锻炼身体,很感谢coScene这个大家庭,感谢大家对我的照顾。\n","date":"2024/09/18","externalUrl":null,"permalink":"/posts/coscene%E5%AE%9E%E4%B9%A0%E7%BB%8F%E9%AA%8C%E5%88%86%E4%BA%AB/","section":"","summary":"","title":"coScene实习经验分享","type":"posts"},{"content":" 引言:为什么需要异步 # 异步编程在现代应用程序中具有重要性,尤其是在处理长时间运行的任务和网络请求时。JavaScript作为一种单线程语言,意味着它一次只能执行一个任务。这就是为什么异步编程在JavaScript中变得至关重要的原因。\n避免阻塞 提高性能 处理回调函数 事件驱动编程模型(响应式) 异步通信 并发执行 尽管异步编程带来了许多好处,但也带来了一些挑战:\n回调地狱 异常处理 并发管理 为了克服这些挑战,JavaScript引入了Promise、异步/等待和其他异步编程模式,以提供更好的代码组织、错误处理和并发管理。这些技术使异步编程更加可读、可维护和高效。\njs中的异步机制 # 回调函数 # 回调函数是最早的异步编程模式之一,它在处理异步操作时起到关键作用。在JavaScript中,回调函数是将一个函数作为参数传递给另一个函数,并在异步操作完成后执行的方式。\n回调地狱 # 但同时回调函数由于每个异步函数的结果都依赖于前一个函数的结果,导致回调层级增加,导致代码难以理解,这就是所谓的回调地狱。\nasyncFunc1(param1, function(result1) { asyncFunc2(result1, function(result2) { asyncFunc3(result2, function(result3) { // More nested callbacks... }); }); }); 因此js引入了其他异步机制来解决这些问题\nPromise函数 # Promises提供了一种结构化的方式来处理异步操作的结果,并且更易于阅读、编写和维护。\n基本概念 # Promise(承诺):一个Promise是一个代表异步操作最终完成或失败的对象。它可以处于三种状态之一:pending(进行中)、fulfilled(已完成)或rejected(已失败)。 状态转换:一个Promise最初是pending状态,当异步操作完成时,它可以变为fulfilled状态,表示操作成功完成;或者变为rejected状态,表示操作失败。 处理:通过使用.then()和.catch()方法,可以附加回调函数来处理Promise的结果。.then()用于处理成功的情况,.catch()用于处理失败的情况。 代码示例 # function fetchData() { return new Promise((resolve, reject) =\u0026gt; { setTimeout(() =\u0026gt; { const data = \u0026#34;你好,Promises!\u0026#34;; // 模拟一个成功的异步操作 resolve(data); // 模拟一个失败的异步操作 // reject(new Error(\u0026#34;出错了!\u0026#34;)); }, 2000); }); } fetchData() .then((result) =\u0026gt; { console.log(result); // 输出:你好,Promises! }) .catch((error) =\u0026gt; { console.error(error); // 输出:Error: 出错了! }); 异步/等待(async/await) # 异步/等待(async/await)是现代JavaScript中更高级的异步编程模式,它建立在Promises之上,并提供了更简洁、可读性更高的语法。异步/等待使开发人员可以以同步的方式编写异步代码,而不需要显式地处理Promise的.then()和.catch()。\n基本概念 # 异步函数:异步函数是使用async关键字声明的函数。异步函数内部可以包含一个或多个使用await关键字进行异步操作的语句。异步函数始终返回一个Promise对象。 await关键字:await关键字用于等待一个返回Promise的异步操作完成,并暂停异步函数的执行,直到该Promise被解决(fulfilled)或拒绝(rejected)。在等待期间,异步函数会暂停执行,并将等待的结果作为表达式的值返回。 代码示例 # function fetchData() { return new Promise((resolve, reject) =\u0026gt; { setTimeout(() =\u0026gt; { const data = \u0026#34;Hello, Async/Await!\u0026#34;; // 模拟一个成功的异步操作 resolve(data); // 模拟一个失败的异步操作 // reject(new Error(\u0026#34;Something went wrong!\u0026#34;)); }, 2000); }); } async function main() { try { const result = await fetchData(); console.log(result); // 输出:Hello, Async/Await! } catch (error) { console.error(error); // 输出:Error: Something went wrong! } } main(); 事件驱动编程 # 事件驱动编程模式是一种常见的编程范式,其中程序的执行流程由事件的发生和相应的事件处理函数驱动。它基于事件和事件处理器之间的关系,通过监听事件的发生并触发相应的处理函数来实现异步和非阻塞的程序设计。\n关键概念: # 事件:事件是程序中发生的特定动作或状态变化,可以是用户交互、网络请求、定时器触发等。 事件触发器(Event Trigger):事件触发器是负责检测和通知事件发生的组件。它负责将事件传递给相应的事件处理函数。 事件监听器(Event Listener):事件监听器是注册在事件触发器上的回调函数。它们用于定义事件发生时要执行的操作。 事件循环 # JavaScript引擎执行全局同步代码,并创建初始的调用栈(Call Stack)。 当遇到异步任务(例如定时器、网络请求、事件监听器等)时,将其放入相应的任务队列(Task Queue)中,等待执行。 当调用栈为空时,事件循环开始执行。 事件循环会检查任务队列,如果队列中有任务,则将其中的第一个任务(宏任务)取出并放入调用栈中执行。 如果在执行宏任务的过程中,产生了新的异步任务(例如Promise的回调、DOM事件等),则将这些任务放入微任务队列(Microtask Queue)中。 宏任务执行完毕后,检查微任务队列,如果队列中有任务,则按照先进先出的顺序执行其中的所有任务。执行微任务的过程中,可能会产生新的微任务,继续放入微任务队列中。 当微任务队列为空时,事件循环会检查是否有浏览器渲染的工作,如果有则执行渲染工作。 重复步骤4-7,不断循环执行,直到没有任务需要执行。 常见用例: # 用户交互:例如,点击按钮、鼠标移动等用户操作会触发相应的事件。 网络请求:发送HTTP请求后,可以注册回调函数来处理请求成功或失败的事件。 定时器:可以使用定时器触发事件,在指定的时间间隔后执行相应的处理函数。 文件读写:当文件读取或写入完成时,可以触发相应的事件进行处理。 事件驱动编程模式在处理异步操作和事件处理方面非常强大和灵活,它允许开发人员以非阻塞的方式编写代码,实现更高效的程序设计。\n异步错误处理 # 处理异步代码中的错误和异常是构建健壮的应用程序的重要方面。错误处理的主要目标是捕获和处理潜在的异常情况,以便应用程序可以适当地响应并维护其稳定性。\n错误处理的重要性:\n避免应用程序崩溃:合理的错误处理可以防止应用程序因未处理的异常而崩溃。 提供良好的用户体验:通过适当地处理错误,应用程序可以向用户提供有意义的错误消息,帮助用户理解并解决问题。 收集和记录错误信息:错误处理可以帮助捕获和记录错误信息,以便进行故障排除和日志记录。 常见的错误处理模式:\n使用try-catch语句:在异步操作的回调函数或Promise链中使用try-catch语句来捕获和处理异常。 Promise链中的.catch()方法:通过在Promise链中使用.catch()方法,可以捕获并处理Promise链中的任何拒绝(rejected)状态。 错误优先回调模式(Error-First Callback Pattern):在回调函数的第一个参数中传递错误对象,以便处理错误。 总结 # JavaScript作为一门单线程语言,通过引入异步机制,来解决线程阻塞的问题,提高用户体验,同时又通过引入Promise、async/await机制来解决回调地狱的问题,进一步完善了js的异步机制。\n","date":"2024/07/19","externalUrl":null,"permalink":"/posts/js%E4%B8%AD%E7%9A%84%E5%BC%82%E6%AD%A5%E6%9C%BA%E5%88%B6/","section":"","summary":"","title":"js中的异步机制","type":"posts"},{"content":"","date":"2024/06/04","externalUrl":null,"permalink":"/authors/","section":"Authors","summary":"","title":"Authors","type":"authors"},{"content":"An all-round web worker\n","date":"2024/06/04","externalUrl":null,"permalink":"/authors/coderwhy/","section":"Authors","summary":"","title":"coderwhy","type":"authors"},{"content":" 项目打包和自动化部署 # 一. 项目部署和 DevOps # 1.1. 传统的开发模式 # 在传统的开发模式中,开发的整个过程是按部就班就行:\n但是这种模式存在很大的弊端:\n工作的不协调:开发人员在开发阶段,测试和运维人员其实是处于等待的状态。等到测试阶段,开发人员等待测试反馈 bug,也会处于等待状态。 线上 bug 的隐患:项目准备交付时,突然出现了 bug,所有人员需要加班、等待问题的处理; 1.2. DevOps 开发模式 # DevOps 是 Development 和 Operations 两个词的结合,将开发和运维结合起来的模式:\n1.3. 持续集成和持续交付 # 伴随着 DevOps 一起出现的两个词就是持续集成和持续交付(部署):\nCI 是 Continuous Integration(持续集成); CD 是两种翻译:Continuous Delivery(持续交付)或 Continuous Deployment(持续部署); 持续集成 CI:\n持续交付和持续部署:\n1.4. 自动化部署流程 # 二. 购买云服务器 # 2.1. 注册阿里云的账号 # 云服务器我们可以有很多的选择:阿里云、腾讯云、华为云。\n目前在公司使用比较多的是阿里云; 我自己之前也一直使用阿里云,也在使用腾讯云; 之前华为云也有找我帮忙推广他们的活动; 但是在我们的课程中,我选择目前使用更加广泛的阿里云来讲解:\n我们需要注册阿里云账号\nhttps://aliyun.com/\n注册即可,非常简单\n2.2. 购买云服务器 # 购买云服务器其实是购买一个实例。\n来到控制台: 创建实例,选择类型和配置 配置网络安全组 设置登录密码 三. 搭建服务器环境 # 3.1. jenkins 自动化部署 # 3.1.1. 安装 Java 环境 # Jenkins 本身是依赖 Java 的,所以我们需要先安装 Java 环境:\n这里我安装了 Java1.8 的环境 dnf search java-1.8 dnf install java-1.8.0-openjdk.x86_64 3.1.2. 安装 Jenkins # 因为 Jenkins 本身是没有在 dnf 的软件仓库包中的,所以我们需要连接 Jenkins 仓库:\nwget 是 Linux 中下载文件的一个工具,-O 表示输出到某个文件夹并且命名为什么文件; rpm:全称为The RPM Package Manage,是 Linux 下一个软件包管理器; wget -O /etc/yum.repos.d/jenkins.repo https://pkg.jenkins.io/redhat-stable/jenkins.repo; # 导入GPG密钥以确保您的软件合法 rpm --import https://pkg.jenkins.io/redhat-stable/jenkins.io-2023.key; 编辑一下文件/etc/yum.repos.d/jenkins.repo\n可以通过 vim 编辑 [jenkins] name=Jenkins-stable baseurl=http://pkg.jenkins.io/redhat gpgcheck=1 安装 Jenkins\ndnf install jenkins # --nogpgcheck(可以不加) 启动 Jenkins 的服务:\nsystemctl start jenkins systemctl status jenkins systemctl enable jenkins Jenkins 默认使用 8080 端口提供服务,所以需要加入到安全组中:\n3.1.3. Jenkins 用户 # 我们后面会访问 centos 中的某些文件夹,默认 Jenkins 使用的用户是 jenkins,可能会没有访问权限,所以我们需要修改一下它的用户:\n修改文件的路径:/etc/sysconfig/jenkins\n之后需要重启一下 Jenkins:\n# 也可以将Jenkins添加到root组中 sudo usermod -a -G root jenkins # 也可以给Jenkins目录权限 chown -R jenkins /xxx/xxx systemctl restart jenkins 3.1.4. Jenkins 配置 # 打开浏览器,输入:http://8.134.60.235:8080/\n注意:你输入自己的 IP 地址 获取输入管理员密码:\n在下面的地址中 cat /var/lib/jenkins/secrets/initialAdminPassword ![image-20201203173047824](/Users/coderwhy/Library/Application Support/typora-user-images/image-20201203173047824.png)\n可以安装推荐的插件:\n3.1.5. Jenkins 任务 # 新建任务:\n配置项目和保留策略:\n源码管理:\n构建触发器:\n这里的触发器规则是这样的:\n定时字符串从左往右分别是:分 时 日 月 周 #每半小时构建一次OR每半小时检查一次远程代码分支,有更新则构建 H/30 * * * * #每两小时构建一次OR每两小时检查一次远程代码分支,有更新则构建 H H/2 * * * #每天凌晨两点定时构建 H 2 * * * #每月15号执行构建 H H 15 * * #工作日,上午9点整执行 H 9 * * 1-5 #每周1,3,5,从8:30开始,截止19:30,每4小时30分构建一次 H/30 8-20/4 * * 1,3,5 构建环境:\n注意:我们需要搭建 Node 的环境\n第一步:配置 Node 的环境; 第二步:安装 Node 的插件; 第一步:配置 Node 的环境\n第二步:安装 Node 的插件\n这里因为我已经安装过了,所以没有搜索到; 构建执行的任务:\n查看 Node 的版本等是否有问题; 执行 npm install 安装项目的依赖; 移除原来 mall_cms 文件的所有内容; 将打包的 dist 文件夹内容移动到 mall_cms 文件夹; pwd node -v npm -v npm install npm run build pwd echo \u0026#39;构建成功\u0026#39; ls # 删除/root/mall_cms文件夹里所有的内容 rm -rf /root/mall_cms/* cp -rf ./dist/* /root/mall_cms/ 3.2. nginx 安装和配置 # 3.2.1. 安装 nginx # 后续我们部署会使用 nginx,所以需要先安装一下 nginx:\ndnf install nginx 启动 nginx:\nsystemctl start nginx systemctl status nginx systemctl enable nginx 3.2.2. 配置 nginx # 我们这里主要配置 nginx 的用户和默认访问目录:\n/etc/nginx/nginx.conf 配置用户:\n通过 Linux 命令创建文件夹和文件:\nmkdir /root/mall_cms cd /root/mall_cms touch index.html vi index.html 配置访问目录:\n","date":"2024/06/04","externalUrl":null,"permalink":"/posts/%E9%A1%B9%E7%9B%AE%E6%89%93%E5%8C%85%E5%92%8C%E8%87%AA%E5%8A%A8%E5%8C%96%E9%83%A8%E7%BD%B2/","section":"","summary":"","title":"项目打包和自动化部署","type":"posts"},{"content":" 引言 # 在项目中直接使用Axios或其他第三方库来发送网络请求获取数据时,会导致代码与网络请求的逻辑耦合度过高,导致难以维护。 本文将讲解如何将网路请求的代码进行封装来进行解耦操作 理解耦合度 # 代码耦合分为两种,直接依赖的结构耦合和间接依赖的内容耦合,这两种耦合都会导致可维护性下降、可测试性下降、可复用性下降、可扩展性下降。 这里在项目中直接使用Axios发送网络请求就是结构耦合,如果Axios的第三方库发生更新或者废弃,会导致我们的项目非常难以维护,这时将Axios封装到一个类中,就可以降低这种直接依赖带来的影响 Axios 概述 # Axios 是一个流行的用于发起 HTTP 请求的 JavaScript 库。它提供了一种简洁、灵活且易于使用的方式来处理网络请求,并且可以在浏览器和 Node.js 环境中使用。\n以下是 Axios 的一些主要功能:\n支持多种请求方式:Axios 支持常见的 HTTP 请求方法,如 GET、POST、PUT、DELETE 等,可以满足不同类型的请求需求。\n拦截器支持:Axios 提供了拦截器(Interceptors)功能,可以在请求发送或响应返回之前对它们进行拦截和处理。这使得可以在请求和响应的不同阶段添加全局的处理逻辑,例如认证、错误处理、请求/响应转换等。\nPromise API:Axios 基于 Promise 提供了一致的 API,可以使用链式调用来处理请求和响应。这使得可以更容易地处理异步操作,并使用 Promise 的特性,如 .then()、.catch()、.finally() 等。\n请求和响应的转换:Axios 允许自定义请求和响应的数据转换逻辑。可以通过拦截器将请求数据格式化为特定格式(如 JSON),或者将响应数据进行解析和转换,以适应项目的需求。\n错误处理:Axios 提供了全面的错误处理机制。它会自动检测和处理 HTTP 错误状态码,并将其包装为可读的错误对象。此外,还可以添加自定义的错误处理逻辑,以便更好地处理错误情况。\n取消请求:Axios 允许取消尚未完成的请求。这对于需要中止或忽略之前发出的请求非常有用,例如在用户取消操作或页面导航时。\nAxios 成为流行的发起 HTTP 请求的工具有以下原因:\n易于使用:Axios 提供了简洁而直观的 API,使得发送和处理 HTTP 请求变得简单和容易上手。\n广泛的应用:Axios 可以在浏览器和 Node.js 环境中使用,使得它在前端和后端开发中都具有广泛的适用性。\n功能丰富:Axios 提供了许多实用的功能,如拦截器、请求和响应的转换、错误处理等,使得开发人员能够更好地控制和处理网络请求。\n活跃的社区支持:Axios 有一个活跃的社区,拥有广泛的用户群体,因此可以获得广泛的支持和资源。这包括文档、示例代码、问题解答等。\n总而言之,Axios 是一个功能强大、易于使用且受欢迎的用于发起 HTTP 请求的工具,它提供了许多便捷的功能和良好的开发体验,使得处理网络请求变得更加简单和高效。\n封装Axios # 在项目目录中创建一个 services 文件夹来封装网络请求的逻辑。 在 services 中创建 modules 文件夹来编写复杂的网络请求逻辑,在 request 中封装Axios逻辑,创建 index 文件作为 services 统一出口。 在 request 中配置 index.js 文件封装一个类来处理网络请求,在 config.js 文件中配置基本选项,例如 BASE_URL、TIMEOUT。 配置 request 中的 index.js 文件 import axios from \u0026#34;axios\u0026#34;; import { BASE_URL, TIMEOUT } from \u0026#34;./config\u0026#34;; class HYRequest { // 创建构造函数 constructor(baseURL, timeout) { // 创建instance实例 this.instance = axios.create({ baseURL, timeout, }); // 配置拦截器,对获取数据进行响应 this.instance.interceptors.response.use( (res) =\u0026gt; { return res.data; }, (err) =\u0026gt; { return err; } ); } // request请求 request(config) { return this.instance.request(config); } // 配置get请求方法 get(config) { return this.request({ ...config, method: \u0026#34;get\u0026#34; }); } // 配置post请求方法 post(config) { return this.request({ ...config, method: \u0026#34;post\u0026#34; }); } } const hyRequest = new HYRequest(BASE_URL, TIMEOUT); export default hyRequest; 这里通过类的内聚性将网络请求的逻辑汇集到一起,用axios.create函数创建instance实例,构造函数接收baseUrl和timeout来配置instance,通过interceptor拦截器拦截response结果,在通过配置request、get、post实现对Axios的调用来完成网络请求,最后用创建好的类来创建一个实例,接收的参数为在config文件中配置好的基本选项,然后导出这个实例即可在项目代码中进行使用。\n配置 request 中的 config 文件 export const BASE_URL = \u0026#34;http://codercba.com:1888/airbnb/api\u0026#34;; export const TIMEOUT = 10000; 这里简单配置request的基本选项来方便我们发送网络请求\n对封装好的 hyRequest 进行导出 import hyRequest from \u0026#34;./request\u0026#34;; export default hyRequest; 这里是services文件夹的统一导出出口,方便进行代码维护\n使用这里封装的类进行网络请求 # import React, { memo, useEffect } from \u0026#34;react\u0026#34;; import hyRequest from \u0026#34;@/services\u0026#34;; const Home = memo(() =\u0026gt; { // 网络请求的代码 useEffect(() =\u0026gt; { hyRequest.get({ url: \u0026#34;/home/highscore\u0026#34; }).then((res) =\u0026gt; { console.log(res); }); }, []); return \u0026lt;div\u0026gt;Home\u0026lt;/div\u0026gt;; }); export default Home; 这里在 home 组件中先进行导入 hyRequest,即可发送网络请求,配置 config 参数,传入{ url: \u0026quot;/home/highscore\u0026quot; }来发送网络请求。\n这便是通过封装好的hyRequest类发送网络请求得到的结果\n总结 # 封装 Axios 的好处:\n降低代码耦合度:通过封装 Axios,可以将网络请求的具体实现细节隐藏在封装的模块或类中,其他模块只需要与封装后的接口进行交互,从而降低了代码的耦合度。 提高可维护性:封装 Axios 可以将网络请求的逻辑集中在一个地方,使得对网络请求的修改和维护更加方便和一致。如果需要更换或升级网络请求库,只需在封装层进行修改,而不需要在整个项目中的各个地方进行修改。 增强可测试性:通过封装 Axios,可以更容易地进行单元测试。由于网络请求的逻辑被封装在一个独立的模块或类中,可以方便地模拟请求和响应,编写针对封装层的单元测试。 提升代码的可复用性:封装 Axios 可以使得网络请求的代码在不同的项目中更易于复用。封装后的模块或类可以被多个模块或项目共享,而不需要重复编写发送网络请求的代码。 结束语 # 通过封装 Axios 来降低项目代码对于 Axios 的直接依赖,即使后面要更换使用网络请求的第三方库,也可以更加方便的修改和维护代码,在编写项目的时候我们也应该多应用这种思路,合理抽取代码逻辑,使代码更容易维护,提高代码复用性。\n","date":"2024/06/03","externalUrl":null,"permalink":"/posts/%E5%B0%81%E8%A3%85axios%E9%99%8D%E4%BD%8E%E4%BB%A3%E7%A0%81%E8%80%A6%E5%90%88%E5%BA%A6/","section":"","summary":"","title":"关于封装axios网络请求降低代码耦合度","type":"posts"},{"content":" 速通 jQuery # 一、基础概念和选择器 # 1.基本概念及作用,如何简化 js 编程 # 简介的语法:简化 DOM 操作 跨浏览器兼容性 丰富的功能库:DOM 操作、事件处理、动画效果、AJAX 请求 强大的选择器:类似于 CSS 选择器,使开发人员轻松选取 DOM 元素 插件生态系统 社区支持和文档资源 2.jQuery 选择器语法 # 元素选择器 选择所有 p 元素:$(\u0026ldquo;p\u0026rdquo;) 选择所有 div 元素:$(\u0026ldquo;div\u0026rdquo;) 类选择器 选择所有 example 类的元素:$(\u0026quot;.example\u0026quot;) 选择同时具有 class1 和 class2 类的元素:$(\u0026quot;.class1.class2\u0026quot;) ID 选择器 选择具有 myId ID 的元素:$(\u0026quot;#myId\u0026quot;) 属性选择器 选择具有 data-name 属性的元素:$(\u0026quot;[data-name]\u0026quot;) 选择具有 data-cagetory 属性且值为 books 的元素:$(\u0026quot;[data-category=\u0026lsquo;books\u0026rsquo;]\u0026quot;) 子元素选择器: 选择所有 ul 元素下的 li 子元素:$(\u0026ldquo;ul \u0026gt; li\u0026rdquo;) 后代元素选择器: 选择所有 div 元素下的 p 后代元素:$(\u0026ldquo;div p\u0026rdquo;) 兄弟元素选择器: 选择紧接在 h2 元素后的所有 p 元素的兄弟元素:$(\u0026ldquo;h2 + p\u0026rdquo;) 过滤选择器 选择第一个 div 元素:$(\u0026ldquo;div:first\u0026rdquo;) 选择最后一个 p 元素:$(\u0026ldquo;p:last\u0026rdquo;) 选择所有偶数位置的 li 元素:$(\u0026ldquo;li:even\u0026rdquo;) 选择所有包含文本 example 的元素:$(\u0026quot;:contains(\u0026rsquo;example\u0026rsquo;)\u0026quot;) 实践 # 二、DOM 操作和事件处理 # 1.jQuery 常见操作 DOM 元素方法 # 添加元素\nappend():挂载节点末尾(子节点) prepend():挂载节点开头(子节点) after():挂载节点之后(兄弟节点) before():挂载节点之前(兄弟节点) 删除元素\nremove():移除节点 empty():清空节点内容(元素、文本) 移动元素\nappendTo():移动到节点末尾(子节点) prependTo():移动到节点开头(子节点) insertAfter():移动到节点之后(兄弟节点) insertBefore():移动到节点之前(兄弟节点) 替换元素\nreplaceWith():替换节点(文本/元素) 2.jQuery 常见的 DOM 操作方法 # addClass():增加 class removeClass():移除 class toggleClass():切换 class 属性 attr():获取节点属性 removeAttr():移除节点属性 val():获取表单值 text():获取文本节点内容 html():获取节点内容,包括子节点,返回一个字符串 3.jQuery 处理事件的基本概念和用法 # 绑定事件处理 事件委托 解除事件绑定 事件对象 三、动画效果和特效 # 1.动画效果 # 淡入淡出(fadeIn/fadeOut) 滑动效果(slideUp / slideDown) 动态调整样式(animate) 2.与用户交互添加特效和动态效果 # hover() click() scroll() 四、AJAX 和数据交互 # 1.使用 jQuery 来进行 AJAX 请求,从服务器获取数据并更新页面内容 # $.ajax(url, method, dataType, success(), error()) $.get(url, data, function) $.post(url, data, function) 2.实践通过 AJAX 请求获取数据,处理响应并动态更新页面内容 # 五、插件和扩展 # 1.jQuery 插件的概念和用法 # 选择合适的插件 引入 jQuery 和插件 配置和初始化插件 调用插件方法 处理插件回调 样式和定制 2.编写自定义的 jQuery 插件,以便根据自己的需求扩展 jQuery 的功能 # 引入 jQuery 库\n创建插件函数\n$.fn.myPlugin = function(options) { // 插件的代码逻辑 }; })(jQuery); 处理选项\n$.fn.myPlugin = function(options) { var settings = $.extend({ option1: defaultValue1, option2: defaultValue2 }, options); // 使用 settings.option1 和 settings.option2 处理插件逻辑 }; })(jQuery); 遍历元素集合\n$.fn.myPlugin = function(options) { var settings = $.extend({ option1: defaultValue1, option2: defaultValue2 }, options); return this.each(function() { // 在这里处理每个元素的逻辑,使用 $(this) 引用当前元素 }); }; })(jQuery); 创建插件函数\n(function($) { $.fn.myPlugin = function(options) { var settings = $.extend({ option1: defaultValue1, option2: defaultValue2 }, options); return this.each(function() { var $element = $(this); // 操作元素,绑定事件等等 $element.text(settings.option1); $element.on(\u0026#39;click\u0026#39;, function() { // 处理点击事件 }); }); }; })(jQuery); 使用插件 \u0026lt;script src=\u0026#34;your-plugin.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; $(document).ready(function() { $(\u0026#39;.target-elements\u0026#39;).myPlugin({ option1: value1, option2: value2 }); }); \u0026lt;/script\u0026gt; ","date":"2024/06/03","externalUrl":null,"permalink":"/posts/%E9%80%9F%E9%80%9Ajquery/","section":"","summary":"","title":"速通jQuery","type":"posts"},{"content":" Introduction # This is bold text, and this is emphasized text.\nHello! \u0026#x1f44b;\nVisit the Hugo website!\n","date":"2024/06/01","externalUrl":null,"permalink":"/posts/second-post/","section":"","summary":"","title":"My First Post","type":"posts"},{"content":"","date":"0001/01/01","externalUrl":null,"permalink":"/about/","section":"欢迎来到 Blowfish !","summary":"","title":"","type":"page"},{"content":"","date":"0001/01/01","externalUrl":null,"permalink":"/categories/","section":"Categories","summary":"","title":"Categories","type":"categories"},{"content":"","date":"0001/01/01","externalUrl":null,"permalink":"/series/","section":"Series","summary":"","title":"Series","type":"series"},{"content":"","date":"0001/01/01","externalUrl":null,"permalink":"/tags/","section":"Tags","summary":"","title":"Tags","type":"tags"},{"content":"","date":"0001/01/01","externalUrl":null,"permalink":"/topics/","section":"Topics","summary":"","title":"Topics","type":"topics"},{"content":"本节包含了我所有的当前项目。\n","date":"0001/01/01","externalUrl":null,"permalink":"/projects/","section":"项目","summary":"","title":"项目","type":"projects"},{"content":"本节包含了我所有的 React 项目。\n","date":"0001/01/01","externalUrl":null,"permalink":"/projects/reactprojects/","section":"项目","summary":"","title":"项目","type":"projects"},{"content":"本节包含了我所有的 Vue 项目。\n","date":"0001/01/01","externalUrl":null,"permalink":"/projects/vueprojects/","section":"项目","summary":"","title":"项目","type":"projects"}]