diff --git a/404.html b/404.html new file mode 100644 index 0000000..36a0086 --- /dev/null +++ b/404.html @@ -0,0 +1,37 @@ + + + + + + + + + 🍰 小雨的学习记录 + + + + + +

404

Looks like we've got some broken links.
Take me home
+ + + diff --git a/advance/Performance.html b/advance/Performance.html new file mode 100644 index 0000000..c25d217 --- /dev/null +++ b/advance/Performance.html @@ -0,0 +1,389 @@ + + + + + + + + + Performance | 🍰 小雨的学习记录 + + + + + +

Performance

什么是前端性能优化?

前端性能是指⻚⾯信息加⼯(⽐如数据展现、动画、操作效率等)的效率。

优化是指借助相关技术⼿段提⾼这样的效率。

文章参考
前端性能优化之利用 Chrome Dev Tools 进行页面性能分析 - 知乎open in new window
FP、FCP、FMP、LCP 都是什么 P? - 知乎open in new window

为什么前端性能如此重要?

我们知道,现在就是⼀个“流量为王”的时代,⼀个⽹站最重要的的就是⽤⼾,有了⽤⼾你才能有 业务,打⽐⽅,你是⼀个电商⽹站,那么你肯定希望你的⽤⼾越多越好,这样才会有更多的⼈去浏 览你的商品,从⽽在你的⽹站上花钱,买东西,这样你才能产⽣收益,但假如你的⽹站打开要⼗⼏ 秒,请求接⼝要⼗⼏秒,那⽤⼾还愿意等么?

看⼀下以下的⽤⼾体验图:

国外⼀些著名公司的调研:

  • BBC ⻚⾯加载时⻓每增加 1 秒,⽤⼾流失 10%
  • Pinterest减少⻚⾯加载时⻓ 40%,提⾼了搜索和注册数 15%
  • DoubleClick 发现如果移动⽹站加载时⻓超过 3 秒,53%的⽤⼾会放弃

所以说,做好性能优化,提⾼⽤⼾体验很重要!

⽹⻚性能指标及影响因素

Timing

⻚⾯运⾏的时间线(统计了从浏览器从⽹址开始导航到 window.onload 事件触发的⼀系列关键的时间点): 时间线

关于 Performance API

Performance API是⼀组⽤于衡量 web 应⽤性能的标准接⼝,学习链接:Performance APIopen in new window

常⽤ Performance API:

// ⻚⾯导航时间
+performance.getEntriesByType("navigation");
+// 静态资源
+performance.getEntriesByType("resource");
+// 绘制指标
+performance.getEntriesByType("paint");
+
+/*需要定时轮询, 才能持续获取性能指标*/
+
performance.getEntriesByName(
+  "https://i0.hdslb.com/bfs/svgnext/BDC/danmu_square_line/v1.json"
+);
+
+performance.getEntriesByName(
+  "https://cloud.tencent.com/developer/api/user/session"
+);
+
+/*需要定时轮询, 才能持续获取性能指标*/
+
console.log(performance.now());
+// 5483324.099999994
+
/* 写法⼀ */
+//直接往 PerformanceObserver() ⼊参匿名回调函数,成功 new 了⼀个PerformanceObserver 类的,名为 observer 的对象
+var observer = new PerformanceObserver(function (list, obj) {
+  var entries = list.getEntries();
+  for (var i = 0; i < entries.length; i++) {
+    //处理“navigation”和“resource”事件
+  }
+});
+//调⽤ observer 对象的 observe() ⽅法
+observer.observe({ entryTypes: ["navigation", "resource"] });
+
+/* 写法⼆ */
+//预先声明回调函数 perf_observer
+function perf_observer(list, observer) {
+  //处理“navigation”事件
+}
+//再将其传⼊ PerformanceObserver(),成功 new 了⼀个 PerformanceObserver 类的,名为observer2 的对象
+var observer2 = new PerformanceObserver(perf_observer);
+//调⽤ observer2 对象的 observe() ⽅法
+observer2.observe({ entryTypes: ["navigation"] });
+

实例化 PerformanceObserver 对象,observe ⽅法的 entryTypes 主要性能类型有哪些?

console.log(PerformanceObserver.supportedEntryTypes);
+/*
+['element', 'event', 'first-input', 'largest-contentful-paint', 'layoutshift',
+'longtask', 'mark', 'measure', 'navigation', 'paint', 'resource',
+'visibility-state']
+*/
+

具体每个性能类型的含义:

类型描述
element元素加载时间,实例项是 PerformanceElementTiming 对象。
event事件延迟,实例项是 PerformanceEventTiming 对象。
first-input⽤⼾第⼀次与⽹站交互(即点击链接、点击按钮或使⽤⾃定义的 JavaScript 控件时)到浏览器实际能够响应该交互的时间,称之为 Firstinputdelay‒FID。
largest-contentful-paint屏幕上触发的最⼤绘制元素,实例项是 LargestContentfulPaint 对象。
layout-shift元素移动时候的布局稳定性,实例项是 LayoutShift 对象。
long-animation-frame⻓动画关键帧。
longtask⻓任务实例,归属于 PerformanceLongTaskTiming 对象。
mark⽤⼾⾃定义的性能标记。实例项是 PerformanceMark 对象。
measure⽤⼾⾃定义的性能测量。实例项是 PerformanceMeasure 对象。
navigation⻚⾯导航出去的时间,实例项是 PerformancePaintTiming 对象。
pain⻚⾯加载时内容渲染的关键时刻(第⼀次绘制,第⼀次有内容的绘制,实例项是 PerformancePaintTiming 对象。
resource⻚⾯中资源的加载时间信息,实例项是 PerformanceResourceTiming 对象。
visibility-state⻚⾯可⻅性状态更改的时间,即选项卡何时从前台更改为后台,反之亦然。实例项是 VisibilityStateEntry 对象。
soft-navigation-

用户为导向性能指标介绍

piant

⾸次绘制(First Paint)和⾸次内容绘制(First Contentful Paint)

⾸次绘制(FP)和⾸次内容绘制(FCP)。在浏览器导航并渲染出像素点后,这些性能指标点⽴即被标记。 这些点对于⽤⼾⽽⾔⼗分重要,直乎感官体验!
⾸次绘制(FP),⾸次渲染的时间点。FP 和 FCP 有点像,但 FP ⼀定先于 FCP 发⽣,例如⼀个⻚⾯加载时,第⼀个 DOM 还没绘制完成,但是可能这时⻚⾯的背景颜⾊已经出来了,这时 FP 指标就被记录下来了。⽽ FCP 会在⻚⾯绘制完第⼀个 DOM 内容后记录。
⾸次内容绘制(FCP),⾸次内容绘制的时间,指⻚⾯从开始加载到⻚⾯内容的任何部分在屏幕上完成渲染的时间。

/* PerformanceObserver监控 */
+const observer = new PerformanceObserver((list) => {
+  const entries = list.getEntries();
+  entries.forEach((entry) => {
+    if (entry.name === "first-paint") {
+      console.log("FP(⾸次绘制):", entry.startTime);
+    } else if (entry.name === "first-contentful-paint") {
+      console.log("FCP(⾸次内容绘制):", entry.startTime);
+    }
+  });
+});
+observer.observe({ entryTypes: ["paint"] });
+
+/* performance.getEntriesByName*/
+console.log(
+  "FP(⾸次绘制):" + performance.getEntriesByName("first-paint")[0].startTime
+);
+console.log(
+  "FCP(⾸次内容绘制):" +
+    performance.getEntriesByName("first-contentful-paint")[0].startTime
+);
+

⾸次有效绘制(First Meaningful Paint)

有效内容,这种⼀般很难清晰地界定哪些元素的加载是「有⽤」的(因此⽬前尚⽆规范),但对于开发者他们⾃⼰⽽⾔,他们更知道⻚⾯的哪些部分对于⽤⼾⽽⾔是最为有⽤的,所以这样的衡量标准更多的时候是掌握在开发者⼿上!

const observer = new PerformanceObserver((list) => {
+  const entries = list.getEntries();
+  entries.forEach((entry) => {
+    if (entry.name === "https://xxxxxx.xxx.jpg") {
+      console.log(entry.startTime);
+    }
+  });
+});
+observer.observe({ entryTypes: ["resource"] }); // 可以是图⽚、某个Dom元素
+

可交互时间(TTI

指标测量⻚⾯从开始加载(FCP)到主要⼦资源完成渲染,并能够快速、可靠地响应⽤⼾输⼊所需的时间。阻塞会影响正常可交互的时间,浏览器主线程⼀次只能处理⼀个任务,如果主线程⻓时间被占⽤,那么可交互时间也会变⻓,所以更多的 TTI 都是发⽣在主线程处于空闲的时间点
良好的TTI应该控制在 5 秒以内。
测量 TTI 的最佳⽅法是在⽹站上运⾏ Lighthouse 性能审核

console.log(performance.timing.domInteractive); // 可交互时间点
+

⻓任务(Long Task)

浏览器主线程⼀次只能处理⼀个任务。 某些情况下,⼀些任务将可能会花费很⻓的时间来执⾏,持续占⽤主进程资源,如果这种情况发⽣了,主线程阻塞,剩下的任务只能在队列中等待。
⽤⼾所感知到的可能是输⼊的延迟,或者是哐当⼀下全部出现。这些是当今⽹⻚糟糕体验的主要来源之⼀。
Long Tasks API 认为任何超过 50 毫秒的任务(Task)都可能存在潜在的问题,并将这些任务相关信息回调给给前端。
把 long task 时间定义为 50ms 的主要理论依据是 Chrome 提出的 RAIL 模型,RAIL 认为事件响应应该在 100ms 以内,滚动和动画处理应该在 16ms 以内,才能保证好的⽤⼾体验,⽽如果⼀个 task 执⾏超过 50ms,则很有可能让体验达不到 RAIL 的标准,故我们需要重点关注执⾏时间超过 50ms 的任务。

const observer = new PerformanceObserver((list) => {
+  const entries = list.getEntries();
+  entries.forEach((entry) => {
+    console.log("Long Task(⻓任务):", entry);
+  });
+});
+observer.observe({ entryTypes: ["longtask"] });
+

核心网页指标

target

  • Largest Contentful Paint (LCP):最⼤内容绘制, ⽤于衡量加载性能。 为了提供良好的⽤⼾体 验,LCP 应在⽹⻚⾸次开始加载后的 2.5 秒内发⽣。
  • First Input Delay (FID):⾸次输⼊延迟,⽤于衡量可交互性。为了提供良好的⽤⼾体验,⻚⾯的 FID 应不超过 100 毫秒。
  • Cumulative Layout Shift (CLS):累积布局偏移,⽤于衡量视觉稳定性。为了提供良好的⽤⼾体 验,⻚⾯应保持 0.1 或更低的 CLS

Lighthouse-知名测评⼯具LightHouse

常⻅优化⼿段

异步加载

说起异步加载,我们需要先了解⼀下什么是同步加载?

// 默认就是同步加载
+<script src="http://abc.com/script.js"></script>
+
  • 同步加载: 同步模式⼜称阻塞模式,会阻⽌浏览器的后续处理,停⽌了后续的⽂件的解析,执⾏, 如图像的渲染。流览器之所以会采⽤同步模式,是因为加载的 js ⽂件中有对 dom 的操作,重定向, 输出 document 等默认⾏为,所以同步才是最安全的。所以⼀般我们都会把 script 标签放置在 body 结束标签之前,减少阻塞。
  • 所以异步加载,其实就是⼀种⾮阻塞加载模式的⽅式,就是浏览器在下载执⾏ js 的同时,还会继续 进⾏后续⻚⾯的处理。

⼏种常⻅的异步加载脚本⽅式:

async 和 defer
在 JavaScript 脚本增加 async 或者 defer 属性

// ⾯试经常问: script标签的defer和async的区别? //
+defer要等到html解析完成之后执⾏脚本
+<script src="main.js" defer></script>
+// async异步加载脚本后便会执⾏脚本
+<script src="main.js" async></script>
+

动态添加 script 标签

// js代码中动态添加script标签,并将其插⼊⻚⾯
+const script = document.createElement("script");
+script.src = "a.js";
+document.head.appendChild(script);
+

通过 XHR 异步加载 js

// ⾯试经常问: 谈谈JS中的 XMLHttpRequest 对象的理解?
+var xhr = new XMLHttpRequest();
+/*
+第⼀个参数是请求类型
+第⼆个参数是请求的URL
+第三个参数是是否为异步请求
+*/
+xhr.open("get", "/getUser", true); // true代表我们需要异步加载该脚本
+xhr.setRequestHeader("testHeader", "1111"); // ⾃定义Header
+xhr.send(null); // 参数为请求主体发送的数据,为必填项,当不需要发送数据时,使⽤null
+xhr.onreadyStateChange = function () {
+  if (xhr.readystate === 4) {
+    // ⾯试经常问: 说出你知道的哪些HTTP状态码?
+    if (xhr.status === 304 || (xhr.status >= 200 && xhr.status < 300)) {
+      console.log("成功, result: ", xhr.responseText);
+    } else {
+      console.log("错误, errCode:", xhr.status);
+    }
+  }
+};
+

按需打包与按需加载

随着 Webpack 等构建⼯具的能⼒越来越强,开发者在构建阶段可以随⼼所欲地打造项⽬流程,与此同 时按需加载和按需打包的技术曝光度也越来越⾼,甚⾄决定着⼯程化构建的结果,直接影响应⽤的性 能优化。

两者的概念:

  • 按需打包表⽰的是针对第三⽅依赖库及业务模块。只打包真正在运⾏时可能会⽤到的代码。
  • 按需加载表⽰的是代码模块在交互的时候需要动态导⼊。

按需打包
按需打包⼀般通过两种⽅法来实现:

  1. 使⽤ ES Module ⽀持的 Tree Shaking ⽅案,使⽤构建⼯具时候完成按需打包。 我们看⼀下这种场景:
import { Button } from "antd";
+// 假设我们的业务使⽤了Button组件,同时该组件库没有提供ES Module版本,
+// 那么这样的引⽤会导致最终打包的代码是所有antd导出的内容,这样会⼤⼤增加代码的体积
+
+// 但是如果我们组件库提供了ES Module版本(静态分析能⼒),并且开启了Tree Shaking功能,
+// 那么我们就可以通过“摇树”特性
+// 将不会被使⽤的代码在构建阶段移除。
+

正确使⽤ Tree Shaking 的姿势:
antd 组件库

// package.json
+{
+    // ...
+    "main": "lib/index.js", // 暴露CommonJS规范代码lib/index.js
+    "module": "es/index.js", // ⾮package.json标准字段,打包⼯具专⽤字段,指定符合ESM规范的⼊⼝⽂件
+    // 副作⽤配置字段,告诉打包⼯具遇到sideEffects匹配到的资源,均为⽆副作⽤的模块呢?
+    "sideEffects": [
+        "*.css",
+        " expample.js"
+    ],
+}
+
// 啥叫作副作⽤模块
+// expample.js
+const b = 2;
+export const a = 1;
+console.log(b);
+

项⽬:
Tree Shaking ⼀般与 Babel 搭配使⽤,需要在项⽬⾥⾯配置 Babel,因为 Babel 默认会把 ESM 规范打包 成 CommonJs 代码,所以需要通过配置 babel-preset-env#moudles 编译降级

production: {
+    presets: [
+        '@babel/preset-env',
+        {
+            modules: false
+        }
+    ]
+}
+

webpack4.0 以上在 mode 为 production 的时候会⾃动开启 Tree Shaking,实际就是依赖了、UglifyJS 等压缩插件,默认配置

const config = {
+    mode: 'production',
+    optimization: {
+        // 三类标记:
+        // used export: 被使⽤过的export会这样标记
+        // unused ha by rmony export: 没有被使⽤过的export被这样标记
+        // harmony import: 所有import会被这样标记
+        usedExports: true, // 使⽤usedExports进⾏标记
+        minimizer: {
+            new TerserPlugin({...}) // ⽀持删除未引⽤代码的压缩器
+        }
+    }
+}
+
  1. 使⽤以 babel-plugin-import 为主的 Babel 插件完成按需打包。
[
+  {
+    libraryName: "antd",
+    libraryDirectory: "lib", // default: lib
+    style: true,
+  },
+  {
+    libraryName: "antd",
+  },
+];
+
import { TimePicker } from "antd"
+↓ ↓ ↓ ↓ ↓ ↓
+var _button = require('antd/lib/time-picker');
+

按需加载
如何才能动态地按需导⼊模块呢?
动态导⼊ import(module) ⽅法加载模块并返回⼀个 promise,该 promise resolve 为⼀个包含其所有导出的模块对象。我们可以在代码中的任意位置调⽤这个表达式。不兼容浏览器,可以⽤ Babel 进⾏转换(@babel/plugin-syntax-dynamic-import

// say.js
+export function hi() {
+    alert(`你好`);
+}
+export function bye() {
+    alert(`拜拜`);
+}
+export default function() {
+    alert("默认到处");
+}
+{
+hi: () => {},
+bye: () => {},
+default:"sdsd"
+}
+
<!DOCTYPE html>
+<script>
+  async function load() {
+    let say = await import("./say.js");
+    say.hi(); // 你好
+    say.bye(); // 拜拜
+    say.default(); // 默认导出
+  }
+</script>
+<button onclick="load()">Click me</button>
+

如果让你⼿写⼀个不考虑兼容性的 import(module)⽅法,你会怎么写?可以看下以下 Function-like

// 利⽤ES6模块化来实现
+const dynamicImport = (url) => {
+    return new Promise((resolve, reject) => {
+        // 创建script标签
+        const script = document.createElement("script");
+        const tempGlobal = "__tempModuleVariable" + Math.random().toString(32).substring(2);
+        // 通过设置 type="module",告诉浏览器该脚本是⼀个 ES6 模块,需要按照
+        模块规范进⾏导⼊和导出
+        script.type = "module";
+        script.crossorigin="anonymous"; // 跨域
+        script.textContent = `import * as m from "${url}";window.${tempGlobal} = m;`;
+        // load 回调
+        script.onload = () => {
+            resolve(window[tempGlobal]);
+            delete window[tempGlobal];
+            script.remove();
+        };
+        // error回调
+        script.onerror = () => {
+            reject(new Error(`Fail to load module script with URL:${url}`));
+            delete window[tempGlobal];
+            script.remove();
+        };
+        document.documentElement.appendChild(script);
+    });
+}
+

Vue性能优化常见策略

可以从代码分割、服务端渲染、组件缓存、⻓列表优化等⻆度去分析Vue性能优化常⻅的策略。

  • 最常⻅的路由懒加载:有效拆分App体积⼤⼩,访问时异步加载
const router = createRouter({
+    routes: [
+        // 借助import()实现异步组件
+        { path: '/foo', component: () => import('./Foo.vue') }
+    ]
+})
+
  • keep-alive 缓存⻚⾯:避免重复创建组件实例,且能保留缓存组件状态
<keep-alive>
+    <component :is="Component"></component>
+</keep-alive>
+
  • 使⽤ v-show 复⽤DOM:避免重复创建组件
<template>
+    <div class="cell">
+    <!-- 这种情况⽤v-show复⽤DOM,⽐v-if效果好 -->
+        <div v-show="value" class="on">
+            <Count :num="10000"/> display:none
+        </div>
+        <section v-show="!value" class="off">
+            <Count :num="10000"/>
+        </section>
+    </div>
+</template>
+
  • 不再变化的数据使⽤ v-once
<!-- single element -->
+<span v-once>This will never change: {{msg}}</span>
+<!-- the element have children -->
+<div v-once>
+    <h1>comment</h1>
+    <p>{{msg}}</p>
+</div>
+<!-- component -->
+<my-component v-once :comment="msg"></my-component>
+<!-- `v-for` directive -->
+<ul>
+    <li v-for="i in list" v-once>{{i}}</li>
+</ul>
+
  • ⻓列表性能优化:如果是⼤数据⻓列表,可采⽤虚拟滚动,只渲染少部分区域的内容,第三库vuevirtual-scrollervue-virtual-scroll-grid

  • 图片懒加载

<!-- vue-lazyload -->
+<img v-lazy="/static/img/1.png">
+
  • 第三⽅插件按需引⼊
import { createApp } from 'vue';
+import { Button, Select } from 'element-plus';
+
+const app = createApp()
+app.use(Button)
+app.use(Select)
+
  • 服务端渲染/静态⽹站⽣成:SSR/SSG
  • ……

React性能优化常⻅策略

React性能优化

####【render过程】避免不必要的Render

  • 类组件跳过没有必要的组件更新, 对应的技巧⼿段:PureComponent、React.memo、 shouldComponentUpdate。

PureComponent 是对类组件的 Props 和 State 进⾏浅⽐较
React.memo是对函数组件的 Props 进⾏浅⽐较
shouldComponentUpdate是React类组件的钩⼦,在该钩⼦函数我们可以对前后props进⾏深⽐对,返回false可以禁⽌更新组件,我们可以⼿动控制组件的更新

  • Hook的useMemo、useCallback 获得稳定的 Props 值

传给⼦组件的派⽣状态或函数,每次都是新的引⽤,这样会导致⼦组件重新刷新

import { useCallback, useState, useMemo } from 'react';
+const [count, setCount] = useState(0);
+// 保证函数引⽤是⼀样的,在将该函数作为props往下传递给其他组件的时候,不会导致
+// 其他组件像PureComponent、shouldComponentUpdate、React.memo等相关优化失效
+// const oldFunc = () => setCount(count => count + 1)
+const newFunc = useCallback(() => setCount(count => count + 1), [])
+// useMemo与useCallback ⼏乎是99%相似,只是useMemo⼀般⽤于密集型计算⼤的⼀些缓存,
+// 它得到的是函数执⾏的结果
+const calcValue = useMemo(() => {
+    return Array(100000).fill('').map(v => /*耗时计算*/ v);
+}, [count]);
+
  • state状态下沉,减⼩影响范围

如果⼀个P组件,它有4个⼦组件ABCD,本⾝有个状态state p, 该状态只影响到AB ,那么我们可以把AB组件进⾏封装, state p 维护⾥⾯,那么state p变化了,也不会影响到CD组件的渲染

  • ⽤redux、React上下⽂ContextAPI 跳过中间组件Render
import ReactDOM from "react-dom";
+import { createContext, useState, useContext, useMemo } from "react";
+const Context = createContext({ val: 0 });
+const MyProvider = ({ children }) => {
+    const [val, setVal] = useState(0);
+    const handleClick = useCallback(() => {
+        setVal(val + 1);
+    },[val]);
+    const value = useMemo(() => {
+        return {
+        val: val
+        };
+    }, [val]);
+    return (
+        <Context.Provider value={value}>
+            {children}
+            <button onClick={handleClick}>context change</button>
+        </Context.Provider>
+   );
+};
+
+const useVal = () => useContext(Context);
+const Child1 = () => {
+    const { val } = useVal();
+    console.log("Child1重新渲染", val);
+    return <div>Child1</div>;
+};
+const Child2 = () => {
+    console.log("Child2只渲染⼀次");
+    return <div>Child2</div>;
+};
+function App() {
+return (
+    <MyProvider>
+        <Child1 />
+        <Child2 />
+    </MyProvider>
+    );
+} 
+const rootElement = document.getElementById("root");
+ReactDOM.render(<App/>, rootElement);
+
  • 避免使⽤内联函数
  • 使⽤ Immutable,减少渲染的次数
  • 列表项使⽤ key 属性,React 官⽅推荐将每项数据的 ID 作为组件的 key

那我如果使⽤索引值index作为key,为啥不推荐?⾯试题

// ⽆⽤更新
+<!-- 更新前 -->
+<li key="0">Tom</li>
+<li key="1">Sam</li>
+<li key="2">Ben</li>
+<li key="3">Pam</li>
+<!-- 删除后更新 -->
+<li key="0">Sam</li>
+<li key="1">Ben</li>
+<li key="2">Pam</li>
+
+// 输⼊错乱
+<!-- 更新前 -->
+<input key="0" value="1" id="id1"/>
+<input key="1" value="2" id="id2"/>
+<input key="3" value="3" id="id3"/>
+<input key="4" value="4" id="id4"/>
+<!-- 删除后更新 -->
+<input key="1" value="1" id="id2"/>
+<input key="3" value="2" id="id3"/>
+<input key="4" value="3" id="id4"/>
+

其他优化

  • 组件懒加载,可以是通过 Webpack 的动态导⼊和 React.lazy ⽅法
import { lazy, Suspense, Component } from "react"
+const Com = lazy(() => {
+    return new Promise((resolve, reject) => {
+    setTimeout(() => {
+        if (Math.random() > 0.5) {
+        reject(new Error("error"))
+        } else {
+        resolve(import("./Component"))
+        }
+    }, 1000)
+  })
+})
+// ...
+<Suspense fallback="加载...">
+    <Com />
+</Suspense>
+
  • 虚拟滚动,react-window 和 react-virtualized, 常⻅⾯试题是:给你10000条数据⼀次性展⽰,怎么才不会卡,虚拟滚动的原理?
  • debounce、throttle 优化触发的回调,如input组件onChange防抖 Lodash
  • 善⽤缓存,如上⾯⽤的useMemo,可以做⼀些耗时计算并保持引⽤不变,减少重新渲染
Last Updated:
Contributors: xiaoyu
+ + + diff --git "a/advance/Webpack\346\211\223\345\214\205\345\216\237\347\220\206.html" "b/advance/Webpack\346\211\223\345\214\205\345\216\237\347\220\206.html" new file mode 100644 index 0000000..4ca3c73 --- /dev/null +++ "b/advance/Webpack\346\211\223\345\214\205\345\216\237\347\220\206.html" @@ -0,0 +1,37 @@ + + + + + + + + + 说Webpack 打包原理 | 🍰 小雨的学习记录 + + + + + +

说Webpack 打包原理

Webpack 介绍

参考文章
webpack 打包原理及流程解析,超详细!open in new window
webpack打包原理 ? 看完这篇你就懂了 !open in new window

  • 本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图,其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。
  • webpack 就像一条生产线,要经过一系列处理流程后才能将源文件转换成输出结果。 这条生产线上的每个处理流程的职责都是单一的,多个流程之间有存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。
  • 插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。
  • webpack 通过 Tapable 来组织这条复杂的生产线。 webpack 在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运作。 webpack 的事件流机制保证了插件的有序性,使得整个系统扩展性很好。

在目前的项目中,我们会有很多依赖包,webpack负责将浏览器不能识别的文件类型、语法等转化为可识别的前端三剑客(html,css,js),并在这个过程中充当组织者与优化者的角色。

webpack 核心概念

bundle

  • Bundle(捆绑包)是指将所有相关的模块和资源打包在一起形成的单个文件。它是应用程序的最终输出,可以在浏览器中加载和执行。
  • 捆绑包通常由Webpack根据入口点(entry)和它们的依赖关系自动创建。当你运行Webpack构建时,它会根据配置将所有模块和资源打包成一个或多个捆绑包。

Chunk

  • Chunk(代码块)是Webpack在打包过程中生成的中间文件,它代表着一个模块的集合。
  • Webpack 根据代码的拓扑结构和配置将模块组织成不同的代码块。每个代码块可以是一个独立的文件,也可以与其他代码块组合成一个捆绑包。
  • Webpack使用代码分割(code splitting)技术将应用程序代码拆分成更小的代码块,以便在需要时进行按需加载。这有助于减小初始加载的文件大小,提高应用程序的性能。
  • 在Webpack中,捆绑包和代码块之间存在一对多的关系。一个捆绑包可以包含多个代码块,而一个代码块也可以属于多个不同的捆绑包。这取决于Webpack配置中的拆分点(split points)和代码块的依赖关系。
  • 总结起来,bundle 是Webpack打包过程的最终输出文件,而chunk是Webpack在打包过程中生成的中间文件,用于组织和按需加载模块。

Entry

  • 入口起点(entry point)指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。
  • 进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。
  • 每个依赖项随即被处理,最后输出到称之为 bundles 的文件中。

Output

  • output 属性告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件,默认值为 ./dist。
  • 基本上,整个应用程序结构,都会被编译到你指定的输出路径的文件夹中。

Module

模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。

Chunk

代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。

Loader

  • loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。
  • loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。
  • 本质上,webpack loader 将所有类型的文件,转换为应用程序的依赖图(和最终的 bundle)可以直接引用的模块。

Plugin

  • loader 被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。
  • 插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。插件接口功能极其强大,可以用来处理各种各样的任务。

webpack 构建流程

Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程 :

  1. 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数。
  2. 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译。
  3. 确定入口:根据配置中的 entry 找出所有的入口文件。
  4. 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理。
  5. 完成模块编译:在经过第 4 步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系。
  6. 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会。
  7. 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。

在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果。

Last Updated:
Contributors: xiaoyu
+ + + diff --git a/advance/index.html b/advance/index.html new file mode 100644 index 0000000..1be939b --- /dev/null +++ b/advance/index.html @@ -0,0 +1,37 @@ + + + + + + + + + 概要介绍 | 🍰 小雨的学习记录 + + + + + +
Last Updated:
Contributors: xiaoyu
+ + + diff --git "a/advance/\345\211\215\347\253\257\346\200\247\350\203\275\344\274\230\345\214\226.html" "b/advance/\345\211\215\347\253\257\346\200\247\350\203\275\344\274\230\345\214\226.html" new file mode 100644 index 0000000..c736ce2 --- /dev/null +++ "b/advance/\345\211\215\347\253\257\346\200\247\350\203\275\344\274\230\345\214\226.html" @@ -0,0 +1,92 @@ + + + + + + + + + 前端性能优化 | 🍰 小雨的学习记录 + + + + + +

前端性能优化

性能

1.重要性:

关注前端可以很好地提高性能。如果我们可以将后端响应时间缩短一半,整体响应时间只能减少 5%~10%。而如果关注前端性能,同样是将其响应时间减少一半,则整体响应时间可以减少 40%~45%。

改进前端通常只需要较少的时间和资源,减少后端延迟会带来很大的改动。

只有 10%~20%的最终用户响应时间花在了下载 HTML 文档上,其余的 80%~90%时间花在了下载页面中的所有组件上。

2.定位:

2.1 技术上的选择

在前端日常开发中,技术上的选择是非常重要的。为什么要讲这个呢?因为现象频发。

前端工程化严重的当下,轻量化的框架慢慢被遗忘掉了。并不是所有的业务场景都适合使用工程化框架,react/vue 并不轻量。

复杂的框架是为了解决复杂的业务

如果研发 h5、PC 展示等场景简单的业务时候,javascript 原生 配合一些轻量化插件更适合。

多页面应用也并不都是缺点。根据业务不同而选择不一样的技术是非常重要的,是每个前端都应该反思的事情。

这方面是导致卡顿的关键问题。

2.2 NetWork

我们的老朋友 NetWork 想必前端同学都很熟悉。我们先来看一下 network 面板 NetWork 从面板上我们可以看出一些信息:

  • 请求资源 size
  • 请求资源时长
  • 请求资源数量
  • 接口响应时长
  • 接口发起数量
  • 接口报文 size
  • 接口响应状态
  • 瀑布图

瀑布图是什么呢?

瀑布图就是上方图片后面的 waterfall 纵列

瀑布图是一个级联图, 展示了浏览器如何加载资源并渲染成网页. 图中的每一行都是一次单独的浏览器请求. 这个图越长, 说明加载网页过程中所发的请求越多. 每一行的宽度, 代表浏览器发出请求并下载该资源的过程中所耗费的时间。它的侧重点在于分析网路链路

瀑布图颜色说明:

  • DNS Lookup [深绿色] - 在浏览器和服务器进行通信之前, 必须经过 DNS 查询, 将域名转换成 IP 地址. 在这个阶段, 你可以处理的东西很少. 但幸运的是, 并非所有的请求都需要经过这一阶段.

  • Initial Connection [橙色] - 在浏览器发送请求之前, 必须建立 TCP 连接. 这个过程仅仅发生在瀑布图中的开头几行, 否则这就是个性能问题(后边细说).

  • SSL/TLS Negotiation [紫色] - 如果你的页面是通过 SSL/TLS 这类安全协议加载资源, 这段时间就是浏览器建立安全连接的过程. 目前 Google 将 HTTPS 作为其 搜索排名因素 之一, SSL/TLS 协商的使用变得越来越普遍了.

  • Time To First Byte (TTFB) [绿色] - TTFB 是浏览器请求发送到服务器的时间+服务器处理请求时间+响应报文的第一字节到达浏览器的时间. 我们用这个指标来判断你的 web 服务器是否性能不够, 或者说你是否需要使用 CDN.

  • Downloading (蓝色) - 这是浏览器用来下载资源所用的时间. 这段时间越长, 说明资源越大. 理想情况下, 你可以通过控制资源的大小来控制这段时间的长度.

那么除了瀑布图的长度外,我们如何才能判断一个瀑布图的状态是健康的呢?

  • 首先, 减少所有资源的加载时间. 亦即减小瀑布图的宽度. 瀑布图越窄, 网站的访问速度越快.

  • 其次, 减少请求数量 也就是降低瀑布图的高度. 瀑布图越矮越好.

  • 最后, 通过优化资源请求顺序来加快渲染时间. 从图上看, 就是将绿色的"开始渲染"线向左移. 这条线向左移动的越远越好.

这样,我们就可以从 network 的角度去排查“慢”的问题。

2.3 webpack-bundle-analyzer

项目构建后生成的 bundle 包是压缩后的。webpack-bundle-analyzer 是一款包分析工具。

我们先来看一下它能带来的效果。如下图: 打包分析

从上图来看,我们的 bundle 包被解析的一览无余。其中模块面积占的越大说明在 bundle 包中 size 越大。就值得注意了,重点优化一下。

它能够排查出来的信息有

显示包中所有打入的模块 显示模块 size 及 gzip 后的 size 排查包中的模块情形是非常有必要的,通过 webpack-bundle-analyzer 来排查出一些无用的模块,过大的模块。然后进行优化。以减少我们的 bundle 包 size,减少加载时长。

安装

# NPM
+npm install --save-dev webpack-bundle-analyzer
+# Yarn
+yarn add -D webpack-bundle-analyzer
+

使用(as a Webpack-Plugin)

const BundleAnalyzerPlugin =
+  require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
+
+module.exports = {
+  plugins: [new BundleAnalyzerPlugin()],
+};
+

然后构建包完毕后会自动弹出一个窗口展示上图信息。

2.4 Performance

chrome 自带的 performance 模块。先附上一个官网文档传送门:Performance

可以检测很多方面的数据,多数情况的性能排查上用的比较多。如果想要深入了解的同学建议去看一下官方文档。

接下来我们来说一下在 performance 面板中如何排差“慢”的问题,它给我们提供了哪些信息呢。先附上一张 performance 的面板图片。 Performance 从上图中可以分析出一些指标

  • FCP/LCP 时间是否过长?
  • 请求并发情况 是否并发频繁?
  • 请求发起顺序 请求发起顺序是否不对?
  • javascript 执行情况 javascript 执行是否过慢?

这些指标就是我们需要重点关注的,当然 performance 的功能并不止于此。

先记住如何获取到这些指标,后面来一一进行解析优化。

2.5 PerformanceNavigationTiming

获取各个阶段的响应时间,我们所要用到的接口是 PerformanceNavigationTiming 接口。

PerformanceNavigationTiming 提供了用于存储和检索有关浏览器文档事件的指标的方法和属性。 例如,此接口可用于确定加载或卸载文档需要多少时间。

function showNavigationDetails() {
+  const [entry] = performance.getEntriesByType("navigation");
+  console.table(entry.toJSON());
+}
+

使用这个函数,我们就可以获取各个阶段的响应时间,如图: hh 参数说明

  • navigationStart 加载起始时间
  • redirectStart 重定向开始时间(如果发生了 HTTP 重定向,每次重定向都和当前文档同域的话,就返回开始重定向的 fetchStart 的值。其他情况,则返回 0)
  • redirectEnd 重定向结束时间(如果发生了 HTTP 重定向,每次重定向都和当前文档同域的话,就返回最后一次重定向接受完数据的时间。其他情况则返回 0)
  • fetchStart 浏览器发起资源请求时,如果有缓存,则返回读取缓存的开始时间
  • domainLookupStart 查询 DNS 的开始时间。如果请求没有发起 DNS 请求,如 keep-alive,缓存等,则返回 fetchStart
  • domainLookupEnd 查询 DNS 的结束时间。如果没有发起 DNS 请求,同上
  • connectStart 开始建立 TCP 请求的时间。如果请求是 keep-alive,缓存等,则返回 domainLookupEnd
  • (secureConnectionStart) 如果在进行 TLS 或 SSL,则返回握手时间
  • connectEnd 完成 TCP 链接的时间。如果是 keep-alive,缓存等,同 connectStart
  • requestStart 发起请求的时间
  • responseStart 服务器开始响应的时间
  • domLoading 从图中看是开始渲染 dom 的时间,具体未知
  • domInteractive 未知
  • domContentLoadedEventStart 开始触发 DomContentLoadedEvent 事件的时间
  • domContentLoadedEventEnd DomContentLoadedEvent 事件结束的时间
  • domComplete 从图中看是 dom 渲染完成时间,具体未知
  • loadEventStart 触发 load 的时间,如没有则返回 0
  • loadEventEnd load 事件执行完的时间,如没有则返回 0
  • unloadEventStart unload 事件触发的时间
  • unloadEventEnd unload 事件执行完的时间

关于我们的 Web 性能,我们会用到的时间参数:

  • DNS 解析时间: domainLookupEnd - domainLookupStart
  • TCP 建立连接时间: connectEnd - connectStart
  • 白屏时间: responseStart - navigationStart
  • dom 渲染完成时间: domContentLoadedEventEnd - navigationStart
  • 页面 onload 时间: loadEventEnd - navigationStart

根据这些时间参数,我们就可以判断哪一阶段对性能有影响。

2.6 抓包

有一些业务状况是没有上述的一些调试工具该怎么办呢?我们可以利用抓包工具进行对页面信息对抓取,上述我们通过 chrome 工具排查出来的指标,也可以通过抓包工具进行抓取。

这里我推荐一款抓包工具 charles。

2.7 性能测试工具

  • Pingdom
  • Load Impact
  • WebPage Test
  • Octa Gate Site Timer
  • Free Speed Test

3.优化:

前端的优化种类繁多,主要包含三个方面的优化:网络优化(对加载时所消耗的网络资源优化),代码优化(资源加载完后,脚本解释执行的速度),框架优化(选择性能较好的框架,比如 benchmark)。

3.1 tree shaking

中文(摇树),webpack 构建优化中重要一环。摇树用于清除我们项目中的一些无用代码,它依赖于 ES 中的模块语法。

比如日常使用 lodash 的时候

import _ from "lodash";
+

如果如上引用 lodash 库,在构建包的时候是会把整个 lodash 包打入到我们的 bundle 包中的。

import _isEmpty from "lodash/isEmpty";
+

如果如上引用 lodash 库,在构建包的时候只会把 isEmpty 这个方法抽离出来再打入到我们的 bundle 包中。

这样的化就会大大减少我们包的 size。所以在日常引用第三方库的时候,需要注意导入的方式。

如何开启摇树

在 webpack4.x 中默认对 tree-shaking 进行了支持。 在 webpack2.x 中使用 tree-shaking:传送门

3.2 split chunks

中文(分包)

在没配置任何东西的情况下,webpack 4 就智能的帮你做了代码分包。入口文件依赖的文件都被打包进了 main.js,那些大于 30kb 的第三方包,如:echarts、xlsx、dropzone 等都被单独打包成了一个个独立 bundle。

其它被我们设置了异步加载的页面或者组件变成了一个个 chunk,也就是被打包成独立的 bundle。

它内置的代码分割策略是这样的:

  • 新的 chunk 是否被共享或者是来自 node_modules 的模块
  • 新的 chunk 体积在压缩之前是否大于 30kb
  • 按需加载 chunk 的并发请求数量小于等于 5 个
  • 页面初始加载时的并发请求数量小于等于 3 个

大家可以根据自己的项目环境来更改配置。配置代码如下:

splitChunks({
+  cacheGroups: {
+    vendors: {
+      name: `chunk-vendors`,
+      test: /[\\/]node_modules[\\/]/,
+      priority: -10,
+      chunks: "initial",
+    },
+    dll: {
+      name: `chunk-dll`,
+      test: /[\\/]bizcharts|[\\/]\@antv[\\/]data-set/,
+      priority: 15,
+      chunks: "all",
+      reuseExistingChunk: true,
+    },
+    common: {
+      name: `chunk-common`,
+      minChunks: 2,
+      priority: -20,
+      chunks: "all",
+      reuseExistingChunk: true,
+    },
+  },
+});
+

没有使用 webpack4.x 版本的项目,依然可以通过按需加载的形式进行分包,使得我们的包分散开,提升加载性能。

按需加载也是以前分包的重要手段之一

这里推荐一篇非常好的文章:webpack 如何使用按需加载

3.3 拆包

与 3.2 的分包不同。大家可能没发现,上面 2.3 的 bundle 包解析中有个有趣的现象,上面项目的技术栈是 react,但是 bundle 包中并没有 react、react-dom、react-router 等。

因为把这些插件“拆”开了。并没有一起打在 bundle 中。而是放在了 CDN 上。下面我举一个例子来解释一下。

假设:原本 bundle 包为 2M,一次请求拉取。拆分为 bundle(1M) + react 桶(CDN)(1M) 两次请求并发拉取。

从这个角度来看,1+1 的模式拉取资源更快。

换一个角度来说,全量部署项目的情况,每次部署 bundle 包都将重新拉取。比较浪费资源。react 桶的方式可以命中强缓存,这样的化,就算全量部署也只需要重新拉取左侧 1M 的 bundle 包即可,节省了服务器资源。优化了加载速度。

注意:在本地开发过程中,react 等资源建议不要引入 CDN,开发过程中刷新频繁,会增加 CDN 服务其压力,走本地就好。

3.4 gzip

服务端配置 gzip 压缩后可大大缩减资源大小。

Nginx 配置方式

http {
+  gzip on;
+  gzip_buffers 32 4K;
+  gzip_comp_level 6;
+  gzip_min_length 100;
+  gzip_types application/javascript text/css text/xml;
+  gzip_disable "MSIE [1-6]\.";
+  gzip_vary on;
+}
+

配置完成后在 response header 中可以查看。

3.5 图片压缩

开发中比较重要的一个环节,我司自己的图床工具是自带压缩功能的,压缩后直接上传到 CDN 上。

如果公司没有图床工具,我们该如何压缩图片呢?我推荐几种我常用的方式

  • 智图压缩 (百度很难搜到官网了,免费、批量、好用)
  • tinypng(免费、批量、速度块)
  • fireworks 工具压缩像素点和尺寸 (自己动手,掌握尺度)
  • 找 UI 压缩后发给你

图片压缩是常用的手法,因为设备像素点的关系,UI 给予的图片一般都是 x2,x4 的,所以压缩就非常有必要。

3.6 图片分割

如果页面中有一张效果图,比如真机渲染图,UI 手拿着刀不让你压缩。这时候不妨考虑一下分割图片。

建议单张土图片的大小不要超过 100k,我们在分割完图片后,通过布局再拼接在一起。可以图片加载效率。

这里注意一点,分割后的每张图片一定要给 height,否则网速慢的情况下样式会塌陷。

3.7 sprite

南方叫精灵图,北方叫雪碧图。这个现象就很有趣。

在网站中有很多小图片的时候,一定要把这些小图片合并为一张大的图片,然后通过 background 分割到需要展示的图片。

这样的好处是什么呢?先来普及一个规则

浏览器请求资源的时候,同源域名请求资源的时候有最大并发限制,chrome 为 6 个,就比如你的页面上有 10 个相同 CDN 域名小图片,那么需要发起 10 次请求去拉取,分两次并发。第一次并发请求回来后,发起第二次并发。

如果你把 10 个小图片合并为一张大图片的画,那么只用一次请求即可拉取下来 10 个小图片的资源。减少服务器压力,减少并发,减少请求次数。

附上一个 sprite 的例子。 雪碧

3.8 CDN

中文(内容分发网络),服务器是中心化的,CDN 是“去中心化的”。

在项目中有很多东西都是放在 CDN 上的,比如:静态文件,音频,视频,js 资源,图片。那么为什么用 CDN 会让资源加载变快呢?

举个简单的例子:

以前买火车票大家都只能去火车站买,后来我们买火车票就可以在楼下的火车票代售点买了。

你细品。

所以静态资源度建议放在 CDN 上,可以加快资源加载的速度。

3.9 懒加载

懒加载也叫延迟加载,指的是在长网页中延迟加载图像,是一种非常好的优化网页性能的方式。

当可视区域没有滚到资源需要加载的地方时候,可视区域外的资源就不会加载。

可以减少服务器负载,常适用于图片很多,页面较长的业务场景中。

如何使用懒加载呢?

图片懒加载 layzr.js

3.10 iconfont

中文(字体图表),现在比较流行的一种用法。使用字体图表有几种好处

  • 矢量
  • 轻量
  • 易修改
  • 不占用图片资源请求。

就像上面说的雪碧图,如果都用字体图标来替换的画,一次请求都免了,可以直接打到 bundle 包中。

使用前提是 UI 给点力,设计趋向于字体图标,提前给好资源,建立好字体图标库。

3.11 逻辑后移

逻辑后移是一种比较常见的优化手段。用一个打开文章网站的操作来举个例子。

没有逻辑后移处理的请求顺序是这个样子的 first 页面的展示主体是文章展示,如果文章展示的请求靠后了,那么渲染文章出来的时间必然靠后,因为有可能因为请求阻塞等情况,影响请求响应情况,如果超过一次并发的情况的话,会更加的慢。如图的这种情况也是在我们项目中发生过的。

很明显我们应该把主体“请求文章”接口前移,把一些非主体的请求逻辑后移。这样的话可以尽快的把主体渲染出来,就会快很多。

优化后的顺序是这个样子的。 second 在平常的开发中建议时常注意逻辑后移的情况,突出主体逻辑。可以极大的提升用户体验。

3.12 算法复杂度

在数据量大的应用场景中,需要着重注意算法复杂度问题。

在这个方面可以参考 Javascript 算法之复杂度分析这篇文章。

如上面 Performance 解析出的 Javascript 执行指标上,可以推测出来你的 code 执行效率如何,如果执行时间过长就要考虑一下是否要优化一下复杂度了。

在时间换空间,空间换时间的选择上,要根据业务场景来进行取舍。

3.13 组件渲染

拿 react 举例,组件分割方面不要太深。需要控制组件的渲染,尤其是深层组件的 render。

老生常谈的话题,我们可以一些方式来优化组件渲染

  • 声明周期控制 - 比如 react 的 shouldComponentUpdate 来控制组件渲染。
  • 官网提供的 api- PureComponent
  • 控制注入组件的参数
  • 分配组件唯一 key
  • 没有必要的渲染是对性能的极大浪费。

3.14 node middleware

中文(node 中间件)

中间件主要是指封装所有 Http 请求细节处理的方法。一次 Http 请求通常包含很多工作,如记录日志、ip 过滤、查询字符串、请求体解析、Cookie 处理、权限验证、参数验证、异常处理等,但对于 Web 应用而言,并不希望接触到这么多细节性的处理,因此引入中间件来简化和隔离这些基础设施与业务逻辑之间的细节,让我们能够关注在业务的开发上,以达到提升开发效率的目的。

使用 node middleware 合并请求。减少请求次数。这种方式也是非常实用的。

3.15 web worker

Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。这样的好处是,一些计算密集型或高延迟的任务,被 Worker 线程负担了,主线程(通常负责 UI 交互)就会很流畅,不会被阻塞或拖慢。

合理实用 web worker 可以优化复杂计算任务。这里直接抛阮一峰的入门文章:传送门

3.16 缓存

缓存的原理就是更快读写的存储介质+减少 IO+减少 CPU 计算=性能优化。而性能优化的第一定律就是:优先考虑使用缓存。

缓存的主要手段有:浏览器缓存、CDN、反向代理、本地缓存、分布式缓存、数据库缓存。

3.17 GPU 渲染

每个网页或多或少都涉及到一些 CSS 动画,通常简单的动画对于性能的影响微乎其微,然而如果涉及到稍显复杂的动画,不当的处理方式会使性能问题变得十分突出。

像 Chrome, FireFox, Safari, IE9+和最新版本的 Opera 都支持 GPU 加速,当它们检测到页面中某个 DOM 元素应用了某些 CSS 规则时就会开启。

虽然我们可能不想对元素应用 3D 变换,可我们一样可以开启 3D 引擎。例如我们可以用 transform: translateZ(0) 来开启 GPU 加速 。

只对我们需要实现动画效果的元素应用以上方法,如果仅仅为了开启硬件加速而随便乱用,那是不合理的。

3.18 Ajax 可缓存

Ajax 在发送的数据成功后,为了提高页面的响应速度和用户体验,会把请求的 URL 和返回的响应结果保存在缓存内,当下一次调用 Ajax 发送相同的请求(URL 和参数完全相同)时,它就会直接从缓存中拿数据。

在进行 Ajax 请求的时候,可以选择尽量使用 get 方法,这样可以使用客户端的缓存,提高请求速度。

3.19 Resource Hints

Resource Hints(资源预加载)是非常好的一种性能优化方法,可以大大降低页面加载时间,给用户更加流畅的用户体验。

现代浏览器使用大量预测优化技术来预测用户行为和意图,这些技术有预连接、资源与获取、资源预渲染等。

Resource Hints 的思路有如下两个:

  • 当前将要获取资源的列表
  • 通过当前页面或应用的状态、用户历史行为或 session 预测用户行为及必需的资源

实现 Resource Hints 的方法有很多种,可分为基于 link 标签的 DNS-prefetch、subresource、preload、 prefetch、preconnect、prerender,和本地存储 localStorage。

3.20 SSR

渲染过程在服务器端完成,最终的渲染结果 HTML 页面通过 HTTP 协议发送给客户端,又被认为是‘同构'或‘通用',如果你的项目有大量的 detail 页面,相互特别频繁,建议选择服务端渲染。

服务端渲染(SSR)除了 SEO 还有很多时候用作首屏优化,加快首屏速度,提高用户体验。但是对服务器有要求,网络传输数据量大,占用部分服务器运算资源。

Vue 的 Nuxt.js 和 React 的 next.js 都是服务端渲染的方法。

3.21 UNPKG

UNPKG 是一个提供 npm 包进行 CDN 加速的站点,因此,可以将一些比较固定了依赖写入 html 模版中,从而提高网页的性能。首先,需要将这些依赖声明为 external,以便 webpack 打包时不从 node_modules 中加载这些资源,配置如下:

externals: { 'react': 'React' }
+

其次,你需要将所依赖的资源写在 html 模版中,这一步需要用到 html-webpack-plugin。下面是一段示例:

<% if (htmlWebpackPlugin.options.node_env === 'development') { %>
+  <script src="https://unpkg.com/react@16.7.0/umd/react.development.js"></script>
+<% } else { %>
+  <script src="https://unpkg.com/react@16.7.0/umd/react.production.min.js"></script>
+<% } %>
+

这段代码需要注入 node_env,以便在开发的时候能够获得更友好的错误提示。也可以选择一些比较自动的库,来帮助我们完成这个过程,比如 webpack-cdn-plugin,或者 dynamic-cdn-webpack-plugin。

4.总结:

还有一些比较常用的优化方法我没有列举出来,例如将样式表放在顶部,将脚本放在底部,减少重绘,按需加载,模块化等。方法很多,对症下药才是关键。

借鉴了很多大佬最后总结出来的文章,希望自己和同为菜鸟的小伙伴可以永远怀着一颗学徒的心。

Last Updated:
Contributors: xiaoyu
+ + + diff --git "a/advance/\345\211\215\347\253\257\350\267\257\347\224\261\347\232\204\345\256\236\347\216\260\345\216\237\347\220\206.html" "b/advance/\345\211\215\347\253\257\350\267\257\347\224\261\347\232\204\345\256\236\347\216\260\345\216\237\347\220\206.html" new file mode 100644 index 0000000..59b7f57 --- /dev/null +++ "b/advance/\345\211\215\347\253\257\350\267\257\347\224\261\347\232\204\345\256\236\347\216\260\345\216\237\347\220\206.html" @@ -0,0 +1,547 @@ + + + + + + + + + 前端路由的实现原理 | 🍰 小雨的学习记录 + + + + + +

前端路由的实现原理

基本的原理先看看

  • hash模式: hashchange + location.hash
  • history模式: history对象
<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>hash路由</title>
+  </head>
+  <body>
+    <!-- hash路由 -->
+
+    <a href="#/a">跳转 A 页面</a>
+    <a href="#/b">跳转 B 页面</a>
+    <div id="box" style="border: 10px solid #000; height: 200px"></div>
+
+    <button onclick="to('/a')">跳转到 A路由</button>
+    <button onclick="to('/b')">跳转到 B路由</button>
+    <button onclick="to('/c')">跳转到 C路由</button>
+    <script>
+      let box = document.getElementById("box");
+      window.addEventListener("hashchange", function (e) {
+        //hashchange
+        box.innerHTML = location.hash;
+        console.log(e);
+      });
+
+      function to(path) {
+        box.innerHTML = path;
+        history.pushState({}, null, path);
+      }
+    </script>
+  </body>
+</html>
+

在 HTML 文档中,history.pushState() 方法向浏览器的会话历史栈增加了一个条目。

该方法是异步的。为 popstate 事件增加监听器,以确定导航何时完成。state 参数将在其中可用。

语法

pushState(state, unused)
+pushState(state, unused, url)
+

参数

state
state 对象是一个 JavaScript 对象,其与通过 pushState() 创建的新历史条目相关联。每当用户导航到新的 state,都会触发 popstate 事件,并且该事件的 state 属性包含历史条目 state 对象的副本。
state 对象可以是任何可以序列化的对象。因为 Firefox 将 state 对象保存到用户的磁盘上,以便用户重启浏览器可以恢复,我们对 state 对象序列化的表示施加了 16 MiB 的限制。如果你传递的 state 对象的序列化表示超出了 pushState() 可接受的大小,该方法将抛出异常。如果你需要更多的空间,建议使用 sessionStorage 和/或 localStorage。

unused
由于历史原因,该参数存在且不能忽略;传递一个空字符串是安全的,以防将来对该方法进行更改。

url 可选
新历史条目的 URL。请注意,浏览器不会在调用 pushState() 之后尝试加载该 URL,但是它可能会在以后尝试加载该 URL,例如,在用户重启浏览器之后。新 URL 可以不是绝对路径;如果它是相对的,它将相对于当前的 URL 进行解析。新的 URL 必须与当前 URL 同源;否则,pushState() 将抛出异常。如果该参数没有指定,则将其设置为当前文档的 URL。


在正式开始看路由的实现之前,先来看看自定义元素和自定义事件

自定义事件

<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>自定义事件</title>
+  </head>
+  <body>
+    <h1>用一个构造函数 CustomEvent</h1>
+    <script>
+      // 创建自定义事件
+      const catFound = new CustomEvent("animalfound", {
+        detail: {
+          name: "猫",
+        },
+      });
+      const dogFound = new CustomEvent("animalfound", {
+        detail: {
+          name: "狗",
+        },
+      });
+
+      // 添加合适的事件监听器
+      window.addEventListener("animalfound", (e) => console.log(e.detail.name));
+
+      // 触发事件
+      window.dispatchEvent(catFound);
+      window.dispatchEvent(dogFound);
+
+      // 控制台中输出“猫”和“狗” 
+    </script>
+  </body>
+</html>
+

自定义元素

自定义内置元素

<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Document</title>
+  </head>
+  <body>
+    <!-- 注意这个 is  !!!! -->
+    <p is="word-count">xiaoyu</p>
+    <script>
+      // Create a class for the element
+      class WordCount extends HTMLParagraphElement {
+        constructor() {
+          // Always call super first in constructor
+          super();
+
+          // count words in element's parent element
+          var wcParent = this.parentNode;
+          console.log(wcParent);
+
+          function countWords(node) {
+            var text = node.innerText || node.textContent;
+            return text.length;
+          }
+
+          var count = "Words: " + countWords(wcParent);
+
+          // Create a shadow root
+          var shadow = this.attachShadow({ mode: "open" });
+
+          // Create text node and add word count to it
+          var text = document.createElement("span");
+          text.textContent = count;
+
+          // Append it to the shadow root
+          shadow.appendChild(text);
+
+          // Update count when element content changes
+          setInterval(function () {
+            var count = "Words: " + countWords(wcParent);
+            text.textContent = count;
+          }, 200);
+        }
+      }
+
+      // Define the new element
+      customElements.define("word-count", WordCount, { extends: "p" });
+
+      let ctor = customElements.get("word-count");
+      console.log(ctor);//获取构造函数
+      console.log(customElements.getName(WordCount) === "word-count");//比较
+    </script>
+  </body>
+</html>
+

自主定义元素

<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>自定义元素</title>
+    <style>
+        body{
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            min-height: 80vh;
+        }
+    </style>
+  </head>
+  <body>
+    <popup-info
+    img="../../../exer/photo/图片1.png"
+    text="Your card validation code (CVC) is an extra
+                                      security feature — it is the last 3 or 4
+                                      numbers on the back of your card."></popup-info>
+  
+    <script>
+      // Create a class for the element
+      // 自主定制元素的构造函数必须扩展HTMLElement。@@@@!!!!
+      class PopUpInfo extends HTMLElement {
+        constructor() {
+          // Always call super first in constructor
+          super();
+
+          // Create a shadow root
+        //   Element.attachShadow() 方法给指定的元素挂载一个 Shadow DOM,并且返回对 ShadowRoot 的引用。@@@@!!!
+          var shadow = this.attachShadow({ mode: "open" });
+        //   console.log(this.shadowRoot);
+        //   console.log(shadow);
+
+          // Create spans
+          var wrapper = document.createElement("span");
+          wrapper.setAttribute("class", "wrapper");
+          var icon = document.createElement("span");
+          icon.setAttribute("class", "icon");
+          icon.setAttribute("tabindex", 0);
+          var info = document.createElement("span");
+          info.setAttribute("class", "info");
+
+          // Take attribute content and put it inside the info span
+          var text = this.getAttribute("text");
+          info.textContent = text;
+
+          // Insert icon
+          var imgUrl;
+          if (this.hasAttribute("img")) {
+            imgUrl = this.getAttribute("img");
+          } else {
+            imgUrl = "img/default.png";
+          }
+          var img = document.createElement("img");
+          img.src = imgUrl;
+          icon.appendChild(img);
+
+          // Create some CSS to apply to the shadow dom
+          var style = document.createElement("style");
+
+          style.textContent =
+            ".wrapper {" +
+            "position: relative;" +
+            "}" +
+            ".info {" +
+            "font-size: 0.8rem;" +
+            "width: 200px;" +
+            "display: inline-block;" +
+            "border: 1px solid black;" +
+            "padding: 10px;" +
+            "background: white;" +
+            "border-radius: 10px;" +
+            "opacity: 0;" +
+            "transition: 0.6s all;" +
+            "position: absolute;" +
+            "bottom: 20px;" +
+            "left: 10px;" +
+            "z-index: 3;" +
+            "}" +
+            "img {" +
+            "width: 1.2rem" +
+            "}" +
+            ".icon:hover + .info, .icon:focus + .info {" +
+            "opacity: 1;" +
+            "}";
+
+          // attach the created elements to the shadow dom
+
+          shadow.appendChild(style);
+          shadow.appendChild(wrapper);
+          wrapper.appendChild(icon);
+          wrapper.appendChild(info);
+        }
+      }
+
+      // Define the new element
+      customElements.define("popup-info", PopUpInfo);
+    </script>
+  </body>
+</html>
+

Vue-router 的实现

路由界面文件

<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Document</title>
+    <style>
+      .c-link{
+        background-color: yellow;
+        width: 100px;
+        line-height: 40px;
+        margin: 20px;
+        cursor: pointer;
+        display: inline-block;
+        text-align: center;
+      }
+      .c-link:active{
+        transform: scale(0.8);
+      }
+    </style>
+  </head>
+  <body>
+    <div class="product-item">测试的产品</div>
+    <h3>原始路径在页面加载时写到了你的剪切板上!!!意味着你可以直接在URL地址栏进行粘贴</h1>
+    <div class="flex">
+      <ul class="menu-x">
+        <c-link to="/" class="c-link">首页</c-link>
+        <c-link to="/about" class="c-link">关于</c-link>
+      </ul>
+    </div>
+    <div>
+      <c-router>
+        <c-route path="/" component="home" default></c-route>
+        <c-route path="/detail/:id" component="detail"></c-route>
+        <c-route path="/about" component="about"></c-route>
+      </c-router>
+    </div>
+    <!-- 记录开始渲染的地址 -->
+    <script>
+      navigator.clipboard.writeText(location.href)
+    </script>
+
+    <script src="./router.js"></script>
+  </body>
+</html>
+

router.js

const oriPushState = history.pushState;
+/* 
+不借助第三方工具库实现路由,我们需要思考以下几个问题:
+如何实现自定义标签,如vue的<router-view>,React的<Router>
+如何实现业务组件
+如何动态切换路由
+*/
+
+/* 如果想监听 pushState 和 replaceState 行为,可以通过在方法里面主动去触发 popstate 事件,
+另一种是重写history.pushState,通过创建自己的eventedPushState自定义事件,并手动派发,实际使用过程中就可以监听了。 */
+// 重写pushState
+history.pushState = function (state, title, url) {
+    // 触发原事件
+    oriPushState.apply(history, [state, title, url]);
+    // 自定义事件
+    var event = new CustomEvent("c-popstate", {
+        detail: {
+            state,
+            title,
+            url
+        }
+    });
+    //触发这个事件
+    window.dispatchEvent(event);
+}
+
+// <c-link to="/" class="c-link">首页</c-link>
+class CustomLink extends HTMLElement {
+    connectedCallback() {
+        this.addEventListener("click", ev => {
+            ev.preventDefault();
+            const to = this.getAttribute("to");
+            // 更新浏览历史记录
+            history.pushState("", "", to);
+        })
+    }
+}
+window.customElements.define("c-link", CustomLink);
+
+// 优先于c-router注册
+// <c-toute path="/" component="home" default></c-toute>
+class CustomRoute extends HTMLElement {
+    #data = null;
+    getData() {
+        return {
+            default: this.hasAttribute("default"),
+            path: this.getAttribute("path"),
+            component: this.getAttribute("component")
+        }
+    }
+}
+window.customElements.define("c-route", CustomRoute);
+
+// 容器组件
+class CustomComponent extends HTMLElement {
+    async connectedCallback() {
+        // 获取组件的path,即html的路径
+        const strPath = this.getAttribute("path");
+        // 加载html
+        const cInfos = await loadComponent(strPath);
+        const shadow = this.attachShadow({ mode: "closed" });
+        // 添加html对应的内容
+        this.#addElement(shadow, cInfos);
+    }
+    #addElement(shadow, info) {
+        // 添加模板内容
+        if (info.template) {
+            shadow.appendChild(info.template.content.cloneNode(true));
+        }
+        // 添加脚本
+        if (info.script) {
+            // 防止全局污染,并获得根节点
+            var fun = new Function(`${info.script.textContent}`);
+            // 绑定脚本的this为当前的影子根节点
+            fun.bind(shadow)();
+        }
+        // 添加样式
+        if (info.style) {
+            shadow.appendChild(info.style);
+        }
+    }
+}
+window.customElements.define("c-component", CustomComponent);
+
+// <c-router></c-router>
+class CustomRouter extends HTMLElement {
+    #routes
+    connectedCallback() {
+        const routeNodes = this.querySelectorAll("c-route");
+
+        // 获取子节点的路由信息
+        this.#routes = Array.from(routeNodes).map(node => node.getData());
+        // 查找默认的路由
+        const defaultRoute = this.#routes.find(r => r.default) || this.#routes[0];
+        // 渲染对应的路由
+        this.#onRenderRoute(defaultRoute);
+        // 监听路由变化
+        this.#listenerHistory();
+    }
+
+    // 渲染路由对应的内容
+    #onRenderRoute(route) {
+        var el = document.createElement("c-component");
+        el.setAttribute("path", `/${route.component}`);
+        el.id = "_route_";
+        this.append(el);
+    }
+
+    // 卸载路由清理工作
+    #onUploadRoute(route) {
+        this.removeChild(this.querySelector("#_route_"));
+    }
+
+    // 监听路由变化
+    #listenerHistory() {
+        // 导航的路由切换
+        window.addEventListener("popstate", ev => {
+            console.log("onpopstate:", ev);
+            const url = location.pathname.endsWith(".html") ? "/" : location.pathname;
+            const route = this.#getRoute(this.#routes, url);
+            console.log(route);
+            this.#onUploadRoute();
+            this.#onRenderRoute(route);
+        });
+        // pushStat或replaceSate
+        window.addEventListener("c-popstate", ev => {
+            console.log("c-popstate:", ev);
+            const detail = ev.detail;
+            const route = this.#getRoute(this.#routes, detail.url);
+            this.#onUploadRoute();
+            this.#onRenderRoute(route);
+        })
+    }
+
+    // 路由查找
+    #getRoute(routes, url) {
+        console.log(routes,url);
+        return routes.find(function (r) {
+            const path = r.path;
+            const strPaths = path.split('/');
+            const strUrlPaths = url.split("/");
+            //注意这里有点关键!!!
+            let match = true;
+            for (let i = 0; i < strPaths.length; i++) {
+                if (strPaths[i].startsWith(":")) {
+                    continue;
+                }
+                match = strPaths[i] === strUrlPaths[i];
+                if (!match) {
+                    break;
+                }
+            }
+            return match;
+        })
+    }
+}
+window.customElements.define("c-router", CustomRouter);
+
+// 动态加载组件并解析
+async function loadComponent(path) {
+    const defaultPath="http://localhost:5000"
+    this.caches = this.caches || {};
+    // 缓存存在,直接返回
+    if (this.caches[path]) {
+        return this.caches[path];
+    }
+    console.log(path);
+    const res = await fetch(defaultPath+path).then(res => res.text());
+    console.log(res);
+    // 利用DOMParser校验
+    // DOMParser 可以将存储在字符串中的 XML 或 HTML 源代码解析为一个 DOM Document。
+    const parser = new DOMParser();
+    const doc = parser.parseFromString(res, "text/html");
+    // 解析模板,脚本,样式
+    const template = doc.querySelector("template");
+    const script = doc.querySelector("script");
+    const style = doc.querySelector("style");
+    // 缓存内容
+    this.caches[path] = {
+        template,
+        script,
+        style
+    }
+    return this.caches[path];
+}
+

pages文件夹中的页面文件,模仿远程的文件
about.html

<template>
+    About Me!
+</template>
+

detail.html

<template>
+    <div>商品详情</div>
+    <div id="detail">
+        商品ID:<span id="product-id" class="product-id"></span>
+    </div>
+</template>
+
+<script>
+    this.querySelector("#product-id").textContent=history.state.id;
+</script>
+
+<style>
+    .product-id{
+        color:red;
+    }
+</style>
+

home.html

<template>
+    <div>商品清单</div>
+    <div id="product-list">
+        <div>
+            <a data-id="10" class="product-item c-link">香蕉</a>
+        </div>
+        <div>
+            <a data-id="11" class="product-item c-link">苹果</a>
+        </div>
+        <div>
+            <a data-id="12" class="product-item c-link">葡萄</a>
+        </div>
+    </div>
+</template>
+
+<script>
+    let container = this.querySelector("#product-list");
+    // 触发历史更新
+    // 事件代理
+    container.addEventListener("click", function (ev) {
+        console.log("item clicked");
+        if (ev.target.classList.contains("product-item")) {
+            const id = +ev.target.dataset.id;
+            history.pushState({
+                    id
+            }, "", `/detail/${id}`)
+        }
+    })
+</script>
+
+<style>
+    .product-item {
+        cursor: pointer;
+        color: blue;
+    }
+</style>
+
Last Updated:
Contributors: xiaoyu
+ + + diff --git "a/advance/\346\225\260\346\215\256\344\273\243\347\220\206Proxy.html" "b/advance/\346\225\260\346\215\256\344\273\243\347\220\206Proxy.html" new file mode 100644 index 0000000..db30058 --- /dev/null +++ "b/advance/\346\225\260\346\215\256\344\273\243\347\220\206Proxy.html" @@ -0,0 +1,203 @@ + + + + + + + + + 数据代理 Proxy | 🍰 小雨的学习记录 + + + + + +

数据代理 Proxy

简单的数据双向绑定

<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Document</title>
+</head>
+<body>
+    <div>
+        <h1>Proxy实现的双向数据绑定</h1>
+        <input type="text"  id="input">
+        <p id="show"></p>
+    </div>
+    <script>
+        let obj={}
+        const input=document.getElementById('input')
+        const show=document.getElementById('show')
+        // 设置代理
+        let newObj=new Proxy(obj,{
+            get(target,key){
+                return Reflect.get(target,key)
+            },
+            set(target,key,value){
+                if(key==='text'){
+                    input.value=value
+                    show.innerHTML=value//这不实现了双向绑定
+                }
+                return Reflect.set(target,key,value)
+            }
+        })
+
+        input.addEventListener('keyup',function(e){//'input'
+            newObj.text=e.target.value
+        })
+    </script>
+</body>
+</html>
+

Object.defineProperty

<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Object.defineProperty方法</title>
+</head>
+<body>
+    <script>
+        let number=18
+        let person={
+            name:'张三',
+            sex:'男',
+            age:number
+        }
+/*
+value和 get 是同一个作用,只能同时用一个。writable和set是同一个作用,用一个。
+所以,set和get 一个阵营 ,而value和writable一个阵营,不能两个阵营同时存在
+*/
+        Object.defineProperty(person,'age',{
+            //基本配置项
+            // value:18,
+            enumerable:true,//控制属性是否可以枚举,默认值为false
+            // writable:true,//控制属性是否可以被修改,默认值为false
+            configurable:true,//控制属性是否可以被删除,默认为false
+
+            //当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
+            get:function(){
+                console.log('有人读取了age属性');
+                return number
+            },
+            //当有人修改person的age属性时,set函数(setter)就会被调用,且返回的值是更改后的值
+            set(value){
+                console.log('有人修改了age属性,且值是'+value);
+                number=value
+            }
+        })
+        console.log(person);
+        console.log(Object.keys(person));
+        console.log(String.fromCharCode(97));//OHohohohoho
+    </script>
+</body>
+</html>
+

Proxy

<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Document</title>
+</head>
+<body>
+    <script>
+        let obj={
+            a:1
+        }
+        let newTarget=new Proxy(obj,{
+            set(target,key,value,receiver){
+                console.log('set',target,key,value,receiver);
+            },
+            get(target,key,receiver){
+                console.log('get',target,key,receiver);
+            }
+        })
+
+        newTarget.a
+        newTarget.a=10
+    </script>
+</body>
+</html>
+

Proxy 和 Reflect

<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Document</title>
+</head>
+<body>
+    Proxy和Reflect
+    <script>
+        let arr=[1,2,3,4]
+        console.log(arr[-1]);//undefined?为什么?
+
+        // 回到原来的问题
+        function createArray(arr){
+            let handle={
+                get(target,index,receiver){
+                    index=Number(index)
+                    if(index<0){
+                        index+=target.length
+                    }
+                    return Reflect.get(target,index,receiver)
+                }
+            }
+            return new Proxy(arr,handle)
+        }
+
+        arr=createArray(arr)
+        console.log(arr[-1]);
+console.log("-------------------------");
+        var star={
+            name:'zhoujielun',
+            age:18,
+            phone:'13287950909'
+        }
+        //代理陷阱
+        var proxy=new Proxy(star,{
+            get:function(target,key,receiver){
+                console.log(target,key,receiver);//代理对象、代理key值、Proxy代理对象
+                if(key==='phone'){
+                    return "经纪人电话:133333333333"
+                }else{
+                    // return target[key]
+                    return Reflect.get(target,key,receiver)//一样
+                }
+            }
+        })
+
+ 
+        // proxy.name
+        console.log(proxy.name);
+        console.log(proxy.age);
+        console.log(proxy.phone);//原来这里也很重要
+    </script>
+</body>
+</html>
+
Last Updated:
Contributors: xiaoyu
+ + + diff --git a/algorithm/index.html b/algorithm/index.html new file mode 100644 index 0000000..3ca4dae --- /dev/null +++ b/algorithm/index.html @@ -0,0 +1,37 @@ + + + + + + + + + 介绍 | 🍰 小雨的学习记录 + + + + + +

介绍

——小菜鸡一个说法

其实我感觉我在这上面没有太多的话语权,我自己开始课程学算法的时候就觉的难,根本听不进去,然后又有其他事情,就学不进一点算法。但是我觉的我做的好的一个点是我上课的时候还是去了课堂上听课,知道老师讲哪里来了,知道双指针、滑动窗口、并查集、动态规划、二分是啥。后面准备面试了,才发现算法在开发岗面试经常能碰到。后面开始准备还是对咱们这个课上听到的有点印象,虽然说重新学的过程很难,主要你能坚持下来,多刷题,相信会找到学算法的门路的!
面试的算法我挂了一题(美团一面,因为算法挂了),还有一题没完全写出来但讲好了思路(腾讯二面——字符串相乘),主要是刚开始,有些地方确实薄弱

面试中的算法baseline

  • 美团一面:最小深度二叉树
  • 腾讯二面:10000!、二叉树BFS
  • 腾讯云三面:字符串相乘
  • 腾讯金融一面:最大无重复子串
  • 小米二面:接雨水

笔试中的算法一般是出两道到三道:第一道非常的简单,但是还是有点难,能解决的样子。让我印象深的就是恒生电子考的全是金融相关的算法题,贪心、动规,你还得了解一下金融相关的知识,比如买卖股票;哦,还有一个印象深,阿里,三道算法一个不会,第一题我本来用JS内置方法是能解决的,但超时,这个题首先就是你要回溯出各个项,然后还有遍历判断,这遍历可有门道了,不会!

算法很重要

先来聊聊面试,这是大家从学校走向社会的重要一步。校招和社招的面试,一般来说有2-3轮技术面试和1轮HR面试。技术面试可能现场也可能电话,HR面试有些公司还不一定有,这种情况就是三轮技术面,当然可能有的公司面试跟上面说的不太一样,但正常来说是这样的。

对于技术面试来说,基本可以这样讲:技术面试=基础知识和业务逻辑面试+算法面试。所谓基础知识和业务逻辑面试,就是对你应聘岗位进行相关知识的考察,通俗地讲就是看你有没有干这份工作的专业能力。比如你要应聘前端岗位,那js、css、html和 jQuery的一些问题肯定会问。第一步如果你过了的话,那就来到了算法面试,通常会以代码的形式考察,很少会单讲算法。

从上面的:技术面试=基础知识和业务逻辑面试+算法面试 来看,对于业务逻辑知识层面的,那没的说,你想从事这个岗位的工作,那这一部分知识是必备的。但我们可以看出算法的普遍性,这也正是算法重要的原因之一:它是一种通用的考察点,不管你应聘哪个岗位都可以进行考察;

另外考察算法的另一个非常重要的原因是:它包含了太多的逻辑思维,可以考察你思考问题的逻辑和解决问题的能力;这一点也是面试官比较看重的,因为它可以反映出你的潜力,我曾经听阿里一位资深面试官这样讲过:当一个人逻辑思维和能力不错的情况下,你还会担心专业的业务知识方面他不行或者学不会吗?”管中窥豹,算法的重要性我想大家都应该明白了。

其实想说的算法重要的原因是:它是你扎实基本功的反映之一,这些东西很大程度上会决定你未来在IT这条路上到底能走多远。 现实点说,由于现在互联网行业薪酬较高的实际情况,很多人会报班或者半路出家去学IT,其实这变相拉低了广义上程序员的门槛,似乎大家都可以通过这条路来寻求高薪。那作为想或者已经从事这个行业的我们,如果你是科班的,那再好不过了,请珍惜这个机会;如果你不是,但也想干这行,在竞争越来越激烈的今天,必须要有点硬功夫,而上面说的算法就是其中之一,当然还包括类似于数据结构、汇编、组原、计网、数学等等,如果这些学好的话,它们是和别人竞争的一项无形的资本,也就是我们说的会让你有区分度。

计算机相关专业出来的,大学四年数据结构与算法都学不好,有什么能拿出来的呢,我是这样的想法。

开始系统学算法+日常刷题

leetcode.png

我的学习路径:

  • 首先就是了解基础的数据结构,用自己擅长的语言手写数据结构
  • 刷算法题不要从动态规划和简单题开始刷,避免很难或者漫无目的的刷题
  • 从二叉树、链表题开始、到二分双指针矩阵、再到动态规划贪心回溯等。慢慢来,可以跟着代码随想录网站来的!
  • 找到适合自己的算法学习方法,你的效率会变高的。比如我就是刷力扣Hot100,不会的就看题解或者视频。

成果:连续的两个月刷题,自己确实在这上面学到了很多,我自己更有想法好好去学去刷算法题。面试算法基本能过了现在!

Last Updated:
Contributors: xiaoyu
+ + + diff --git "a/algorithm/\344\272\214\345\210\206\346\237\245\346\211\276\360\237\215\260.html" "b/algorithm/\344\272\214\345\210\206\346\237\245\346\211\276\360\237\215\260.html" new file mode 100644 index 0000000..c79311a --- /dev/null +++ "b/algorithm/\344\272\214\345\210\206\346\237\245\346\211\276\360\237\215\260.html" @@ -0,0 +1,142 @@ + + + + + + + + + 二分查找🍰 | 🍰 小雨的学习记录 + + + + + +

二分查找🍰

35. 搜索插入位置

/**
+ * @param {number[]} nums
+ * @param {number} target
+ * @return {number}
+ */
+var searchInsert = function(nums, target) {
+    let left=0,right=nums.length-1
+    while(left<=right){
+        const mid=Math.floor((left+right)/2)
+        if(nums[mid]==target){
+            return mid
+        }else if(nums[mid]>target){
+            right=mid-1
+        }else{
+            left=mid+1
+        }
+    }
+    return left//left就是安插的那个点!!!
+};
+

34. 在排序数组中查找元素的第一个和最后一个位置

/**
+ * @param {number[]} nums
+ * @param {number} target
+ * @return {number[]}
+ */
+var searchRange = function(nums, target) {
+    let index=search(nums,target)
+    if(index==-1)return [-1,-1]
+
+    let left=index,rigth=index
+    while(nums[left]==target || nums[rigth]==target){
+        if(nums[left]==target)left--
+        if(nums[rigth]==target)rigth++
+    }
+    return [left+1,rigth-1]
+};
+
+//又是开始进行二分查找!!!
+const search=(nums,target)=>{
+    let low=0,high=nums.length
+    while(low<=high){
+        const mid=Math.floor((low+high)/2)
+        if(nums[mid]==target){
+            return mid
+        }else if(nums[mid]>target){
+            high=mid-1
+        }else{
+            low=mid+1
+        }
+    }
+    return -1
+}
+

69. x 的平方根

/**
+ * @param {number} x
+ * @return {number}
+ */
+var mySqrt = function (x) {
+    //用二分法进行求解:左闭右开区间
+    let low = 0, high = Math.ceil(x / 2)//优化方案点
+    while (low < high) {
+        const mid = Math.ceil((high + low) / 2)
+        const res = mid * mid
+        if (res == x) {
+            return mid
+        } else if (res > x) {
+            high = mid - 1
+        } else {
+            low = mid 
+        }
+    }
+    return low
+};
+

367. 有效的完全平方数

/**
+ * @param {number} num
+ * @return {boolean}
+ */
+var isPerfectSquare = function(num) {
+    //4=1+3 9=1+3+5 16=1+3+5+7以此类推,模仿它可以使用一个while循环,
+    // 不断减去一个从1开始不断增大的奇数,若最终减成了0,说明是完全平方数,否则,不是。
+    let num1=1;
+    while(num>0){
+        num-=num1
+        num1+=2
+    }
+    return num==0
+};
+
/**
+ * @param {number} num
+ * @return {boolean}
+ */
+var isPerfectSquare = function (num) {
+    //用二分法来进行求解:和上面的那个题目差不多!!!
+    let low = 0, high = Math.ceil(num / 2)
+    while (low < high) {
+        const mid = Math.ceil((low + high) / 2)
+        const res = mid * mid
+        if(res==num){
+            return true
+        }else if(res<num){
+            low=mid
+        }else{
+            high=mid-1
+        }
+    }
+    return false
+};
+
Last Updated:
Contributors: xiaoyu
+ + + diff --git "a/algorithm/\344\272\214\345\217\211\346\240\221\360\237\215\210.html" "b/algorithm/\344\272\214\345\217\211\346\240\221\360\237\215\210.html" new file mode 100644 index 0000000..8d07d70 --- /dev/null +++ "b/algorithm/\344\272\214\345\217\211\346\240\221\360\237\215\210.html" @@ -0,0 +1,569 @@ + + + + + + + + + 二叉树🍈 | 🍰 小雨的学习记录 + + + + + +

二叉树🍈

94. 二叉树的中序遍历

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {number[]}
+ */
+var inorderTraversal = function (root) {
+    const result = []
+    const traverse = (root) => {
+        if (root == null) return;
+        traverse(root.left)
+        result.push(root.val)
+        traverse(root.right)
+    }
+    traverse(root)
+    return result
+};
+

104. 二叉树的最大深度

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {number}
+ */
+var maxDepth = function(root) {
+    if(root==null)return 0
+    return Math.max(maxDepth(root.left),maxDepth(root.right))+1
+};
+

226. 翻转二叉树

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {TreeNode}
+ */
+var invertTree = function(root) {
+    //判断不存在直接返回
+    if(root==null)return root
+    //存在进行下面的处理
+    const tmp=root.left
+    root.left=root.right
+    root.right=tmp
+    //遍历
+    invertTree(root.left)
+    invertTree(root.right)
+
+    return root
+};
+

101. 对称二叉树

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {boolean}
+ */
+var isSymmetric = function(root) {
+    return dfs(root.left,root.right)
+};
+
+function dfs(left,right){
+    if(left==null && right==null)return true //都没有
+    if(left==null || right==null)return false //只有一个
+    if(left.val!=right.val)return false //两者都有
+    return dfs(left.left,right.right) && dfs(left.right,right.left)
+}   
+

543. 二叉树的直径

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {number}
+ */
+var diameterOfBinaryTree = function(root) {
+    let maxLen=0
+    // 二叉树最大深度的变种!
+    const maxline=(root)=>{
+        if(root==null)return 0
+        const left=maxline(root.left)
+        const right=maxline(root.right)
+        maxLen=Math.max(maxLen,right+left)
+        return Math.max(left,right)+1
+    }
+    maxline(root)
+    return maxLen
+};
+

102. 二叉树的层序遍历

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {number[][]}
+ */
+var levelOrder = function (root) {
+    const queue = [root], res = [] //是类似队列的操作
+    if(root==null)return res// 注意这里还有一个条件判断
+  
+    while (queue.length) {
+        const len = queue.length
+        const arr = []
+        for (let i = 0; i < len; i++) {
+            const node = queue.shift()
+            arr.push(node.val)
+            if (node.left) {
+                queue.push(node.left)
+            }
+            if (node.right) {
+                queue.push(node.right)
+            }
+        }
+        res.push(arr)
+    }
+    return res
+};
+

108. 将有序数组转换为二叉搜索树

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {number[]} nums
+ * @return {TreeNode}
+ */
+var sortedArrayToBST = function (nums) {
+    // 注意这个函数带上的两个参数
+    function buildTree(low, high) {
+        if (low > high) return null//注意这里的终止条件
+        // 下面进行树的生成
+        const mid = Math.floor((low + high) / 2)
+        const root = new TreeNode(nums[mid])
+        root.left = buildTree(low, mid - 1)
+        root.right = buildTree(mid + 1, high)
+        return root
+    }
+    return buildTree(0, nums.length - 1)
+};
+

98. 验证二叉搜索树

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {boolean}
+ */
+var isValidBST = function(root) {
+    //中序遍历求解!!!
+    // 二叉搜索树「中序遍历」得到的值构成的序列一定是升序的,
+    // 这启示我们在中序遍历的时候实时检查当前节点的值是否大于前一个中序遍历到的节点的值即可。
+    let stack = [];
+    let inorder = -Infinity;
+
+    while (stack.length || root !== null) {
+        // 入栈节点
+        while (root !== null) {
+            stack.push(root);
+            root = root.left;
+        }
+        root = stack.pop();
+        // 如果中序遍历得到的节点的值小于等于前一个 inorder,说明不是二叉搜索树
+        if (root.val <= inorder) {
+            return false;
+        }
+        inorder = root.val;
+        root = root.right;
+    }
+    return true;
+};
+

Error! 没有考虑子树的所有节点都必须大于或小于根节点

例如:[5,4,6,null,null,3,7],这样的做法只是考虑在两层间的对比!

var isValidBST = function (root) {
+    if (root == null) return true
+    if ((root.left != null && root.val <= root.left.val) 
+        || (root.right != null && root.right.val <= root.val)) {
+        return false
+    }
+    return isValidBST(root.left) && isValidBST(root.right)
+};
+

另一种解题思路:先遍历收集在进行对比

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {boolean}
+ */
+var isValidBST = function (root) {
+    //直接可以遍历出来再进行对比
+    const res = []
+    const travel = (root) => {
+        if (root == null) return
+        travel(root.left)
+        res.push(root.val)
+        travel(root.right)
+    }
+    travel(root)
+    let flag = true;
+    for (let i = 1; i < res.length; i++) {
+        if (res[i - 1] >= res[i]) flag = false
+    }
+    return flag
+};
+

230. 二叉搜索树中第K小的元素

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @param {number} k
+ * @return {number}
+ */
+var kthSmallest = function (root, k) {
+    //中序遍历找第k个元素
+    let i = 0, value
+    const travel = (root) => {
+        if (root == null) return
+        travel(root.left)
+        if (++i == k) {
+            value = root.val;
+            return
+        }
+        travel(root.right)
+    }
+    travel(root)
+    return value
+};
+

199. 二叉树的右视图

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {number[]}
+ */
+var rightSideView = function (root) {
+    return levelOrder(root)
+};
+
+
+function levelOrder(root) {
+    let res = [], stack = [root]
+    if (root == null) return []
+
+    while (stack.length) {
+        let arr = []
+        let len = stack.length
+        for (let i = 0; i < len; i++) {
+            const node = stack.shift()
+            arr.push(node.val)
+            if (node.left) {
+                stack.push(node.left)
+            }
+            if (node.right) {
+                stack.push(node.right)
+            }
+        }
+        //这里直接push最后一个就行了!!!
+        res.push(arr[arr.length-1])
+    }
+    return res
+}
+

103. 二叉树的锯齿形层序遍历

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {number[][]}
+ */
+var zigzagLevelOrder = function (root) {
+    if(root==null)return []//注意这里的一个条件判断!!!
+
+    const res = [], queue = [root]
+    let order = true
+    while (queue.length) {
+        let arr = []
+        let len = queue.length
+        for (let i = 0; i < len; i++) {
+            const node = queue.shift()
+            if (order) {
+                arr.push(node.val)
+            } else {
+                arr.unshift(node.val)
+            }
+            if (node.left) {
+                queue.push(node.left)
+            }
+            if (node.right) {
+                queue.push(node.right)
+            }
+        }
+        res.push(arr)
+        order = !order
+    }
+    return res
+};
+

114. 二叉树展开为链表

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {void} Do not return anything, modify root in-place instead.
+ */
+var flatten = function(root) {
+    let list=[]
+    travel(root,list)
+    for(let i=1;i<list.length;i++){
+        const prev=list[i-1],cur=list[i]
+        prev.left=null
+        prev.right=cur
+    }
+};
+
+function travel(root,list){
+    //先序遍历进行收集!!!
+    if(root==null)return 
+    list.push(root)
+    travel(root.left,list)
+    travel(root.right,list)
+}
+

105. 从前序与中序遍历序列构造二叉树

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {number[]} preorder
+ * @param {number[]} inorder
+ * @return {TreeNode}
+ */
+// 优化方案
+var buildTree = function (preorder, inorder) {
+    const helper = (p_start, p_end, i_start, i_end) => {
+        if (p_start > p_end) return null
+        const rootVal = preorder[p_start]//根节点的值
+        const root = new TreeNode(rootVal)//根节点
+        const mid = inorder.indexOf(rootVal)//根节点在
+        let leftNum = mid - i_start //左子树的节点数
+        root.left = helper(p_start + 1, p_start + leftNum, i_start, mid - 1)
+        root.right = helper(p_start + leftNum + 1, p_end, mid + 1, i_end)
+        return root
+    }
+    return helper(0, preorder.length - 1, 0, inorder.length - 1)
+};
+
+
+//第一种写法
+function buildTree(preorder, inorder) {
+    if (preorder.length == 0 || inorder.length == 0) return null
+    const root = new TreeNode(preorder[0])
+    const mid = inorder.indexOf(preorder[0])
+    root.left = buildTree(preorder.slice(1, mid + 1), inorder.slice(0, mid))
+    root.right = buildTree(preorder.slice(mid + 1), inorder.slice(mid + 1))
+    return root
+}
+

437. 路径总和 III

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @param {number} targetSum
+ * @return {number}
+ */
+var pathSum = function(root, targetSum) {
+    let ans=0
+    const map=new Map()
+    dfs(root,0)
+    return ans
+// 前缀和定义
+// 用它干什么
+// HashMap存的是什么
+// 恢复状态代码的意义:题目中可以拿 node 值为5的节点来说
+
+    function dfs(root,preSum){
+        if(root==null)return
+        let target=preSum+root.val
+        map.set(preSum,(map.get(preSum)||0)+1)
+        ans+=(map.get(target-targetSum)||0)
+
+        dfs(root.left,target)
+        dfs(root.right,target)
+
+        map.set(preSum,map.get(preSum)-1)
+    }
+};
+

236. 二叉树的最近公共祖先

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val) {
+ *     this.val = val;
+ *     this.left = this.right = null;
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @param {TreeNode} p
+ * @param {TreeNode} q
+ * @return {TreeNode}
+ */
+var lowestCommonAncestor = function(root, p, q) {
+    
+    const travel=(root,p,q)=>{
+        if(root==null ||root==p ||root==q)return root
+        let left=travel(root.left,p,q)
+        let right=travel(root.right,p,q)
+        
+        // 后续遍历中进行处理!需要进行往上返回!
+        if(left!=null &&right!=null)return root
+        if(left==null)return right
+        if(right==null)return left
+        
+    }
+    
+    return travel(root,p,q)
+};
+

124. 二叉树中的最大路径和

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {number}
+ */
+const maxPathSum = (root) => {
+    let maxSum = Number.MIN_SAFE_INTEGER; // 最大路径和
+
+    const dfs = (root) => {
+        if (root == null) { // 遍历到null节点,收益0
+           return 0;
+        }
+        const left = dfs(root.left);   // 左子树提供的最大路径和
+        const right = dfs(root.right); // 右子树提供的最大路径和
+
+        const innerMaxSum = left + root.val + right; // 当前子树内部的最大路径和
+        maxSum = Math.max(maxSum, innerMaxSum);      // 挑战最大纪录
+
+        const outputMaxSum = root.val + Math.max( left, right); // 当前子树对外提供的最大和
+
+        // 如果对外提供的路径和为负,直接返回0。否则正常返回
+        return outputMaxSum < 0 ? 0 : outputMaxSum;
+    };
+
+    dfs(root);  // 递归的入口
+
+    return maxSum; 
+};
+

100. 相同的树

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} p
+ * @param {TreeNode} q
+ * @return {boolean}
+ */
+//如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
+var isSameTree = function (p, q) {
+    if (p == null && q == null) return true
+    if (p == null || q == null) return false
+    if (p.val != q.val) return false
+    return isSameTree(p.left, q.left) && isSameTree(p.right, q.right)
+};
+
Last Updated:
Contributors: xiaoyu
+ + + diff --git "a/algorithm/\345\212\250\346\200\201\350\247\204\345\210\222\360\237\215\223.html" "b/algorithm/\345\212\250\346\200\201\350\247\204\345\210\222\360\237\215\223.html" new file mode 100644 index 0000000..6cd8135 --- /dev/null +++ "b/algorithm/\345\212\250\346\200\201\350\247\204\345\210\222\360\237\215\223.html" @@ -0,0 +1,195 @@ + + + + + + + + + 动态规划🍓 | 🍰 小雨的学习记录 + + + + + +

动态规划🍓

70.爬楼梯

/**
+ * @param {number} n
+ * @return {number}
+ */
+var climbStairs = function (n) {
+    const dp=[1,2]
+    for(let i=2;i<n;i++){
+        dp[i]=dp[i-1]+dp[i-2]
+    }
+    return dp[n-1]
+};
+

118.杨辉三角

/**
+ * @param {number} numRows
+ * @return {number[][]}
+ */
+var generate = function (numRows) {
+    // 打印输出一个杨辉三角
+    const dp = Array.from({ length: numRows }, () => new Array(numRows).fill(1))
+    for (let i = 0; i < numRows; i++) {
+        for (let j = 0; j < numRows; j++) {
+            if (j > 0 && j < i) {
+                dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1]
+            }
+        }
+    }
+    //进行push进去
+    let res = []
+    for (let i = 0; i < numRows; i++) {
+        res.push(dp[i].slice(0, i + 1))
+    }
+    return res
+};
+

198.打家劫舍

/**
+ * @param {number[]} nums
+ * @return {number}
+ */
+var rob = function (nums) {
+    //如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
+    let len = nums.length
+    if (len <= 2) return Math.max.apply(null, nums)
+    let max = nums[0];
+    let M = nums[0]
+    let dp = [nums[0], nums[1]]
+    if (max < dp[1]) max = dp[1]
+    for (let i = 2; i < len; i++) {
+        //这个M表示间隔两位以上的最大数
+        if (dp[i - 2] > M) M = dp[i - 2]
+        dp[i] = M + nums[i]
+        if (max < dp[i]) max = dp[i]
+    }
+    return max
+};
+

279.完全平方数

/**
+ * @param {number} n
+ * @return {number}
+ */
+var numSquares = function (n) {
+    //用动态规划求解
+    let dp = new Array(n + 1).fill(0)//其实主要是初始化dp[0]
+    //每一个对应的位置最大可以是本身1+1+1……
+    for (let i = 1; i <= n; i++) {
+        dp[i] = i;//每次都将当前数字先更新为最大的结果,最坏的结果
+        //这里的j是平方数的底子
+        for (let j = 1; i - j * j >= 0; j++) {
+            dp[i] = Math.min(dp[i], dp[i - j * j] + 1)
+        }
+    }
+    return dp[n]
+};
+

322.零钱兑换

/**
+ * @param {number[]} coins
+ * @param {number} amount
+ * @return {number}
+ */
+var coinChange = function(coins, amount) {
+    // 定义dp数组
+    let dp=new Array(amount+1).fill(Infinity)
+    dp[0]=0
+    //注意两层for循环的遍历,分别遍历的是啥?
+    for(let i=0;i<coins.length;i++){
+        for(let j=coins[i];j<=amount;j++){
+            dp[j]=Math.min(dp[j],dp[j-coins[i]]+1)
+        }
+    }
+    if(dp[amount]==Infinity)return -1
+    return dp[amount]
+};
+

300.最长递增子序列

/**
+ * @param {number[]} nums
+ * @return {number}
+ */
+var lengthOfLIS = function(nums) {
+    let n=nums.length;
+    let dp=new Array(n).fill(1)
+    for(let i=1;i<n;i++){
+        for(let j=0;j<i;j++){
+            if(nums[i]>nums[j]){
+                dp[i]=Math.max(dp[i],dp[j]+1)
+            }
+        }
+    }
+    return Math.max.apply(null,dp)
+};
+

416.分割等和子集

/**
+ * @param {number[]} nums
+ * @return {boolean}
+ */
+var canPartition = function(nums) {
+    // 显然是0-1背包问题
+    let n=nums.length
+    let target=nums.reduce((p,v)=>p+v,0)/2
+    if(!Number.isInteger(target))return false
+    let dp=new Array(target+1).fill(0)
+    for(let i=0;i<n;i++){
+        for(let j=target;j>=nums[i];j--){
+            dp[j]=Math.max(dp[j],dp[j-nums[i]]+nums[i])
+        }
+    }
+    return dp[target]==target
+};
+

62.不同路径

/**
+ * @param {number} m
+ * @param {number} n
+ * @return {number}
+ */
+var uniquePaths = function(m, n) {
+    let dp=Array.from({length:m},()=>new Array(n).fill(1))
+    for(let i=1;i<m;i++){
+        for(let j=1;j<n;j++){
+            dp[i][j]=dp[i][j-1]+dp[i-1][j]
+        }
+    }
+    return dp[m-1][n-1]
+};
+

64.最小路径

/**
+ * @param {number[][]} grid
+ * @return {number}
+ */
+var minPathSum = function(grid) {
+    let m=grid.length
+    let n=grid[0].length
+    let dp=Array.from({length:m},()=>new Array(n))
+    dp[0][0]=grid[0][0]
+    for(let i=0;i<m;i++){
+        for(let j=0;j<n;j++){
+            if(i==0 && j>0){
+                dp[i][j]=dp[i][j-1]+grid[i][j]
+            }
+            if(j==0 && i>0){
+                dp[i][j]=dp[i-1][j]+grid[i][j]
+            }
+            if(j>0 && i>0){
+                dp[i][j]=Math.min(dp[i-1][j],dp[i][j-1])+grid[i][j]
+            }
+        }
+    }
+    return dp[m-1][n-1]
+};
+
Last Updated:
Contributors: xiaoyu
+ + + diff --git "a/algorithm/\345\217\214\346\214\207\351\222\210_\346\273\221\345\212\250\347\252\227\345\217\243\360\237\215\250.html" "b/algorithm/\345\217\214\346\214\207\351\222\210_\346\273\221\345\212\250\347\252\227\345\217\243\360\237\215\250.html" new file mode 100644 index 0000000..af5fbc6 --- /dev/null +++ "b/algorithm/\345\217\214\346\214\207\351\222\210_\346\273\221\345\212\250\347\252\227\345\217\243\360\237\215\250.html" @@ -0,0 +1,458 @@ + + + + + + + + + 双指针_滑动窗口🍨 | 🍰 小雨的学习记录 + + + + + +

双指针_滑动窗口🍨

27. 移除元素

/**
+ * @param {number[]} nums
+ * @param {number} val
+ * @return {number}
+ */
+var removeElement = function(nums, val) {
+    let slow=0
+    for(let fast=0;fast<nums.length;fast++){
+        if(nums[fast]!=val){
+            nums[slow]=nums[fast]
+            slow++
+        }
+    }
+    return slow
+};
+

26. 删除有序数组中的重复项

/**
+ * @param {number[]} nums
+ * @return {number}
+ */
+var removeDuplicates = function(nums) {
+    let slow=0
+    for(let fast=0;fast<nums.length;fast++){
+        if(nums[fast]!=nums[slow]){
+            slow++
+            nums[slow]=nums[fast]
+        }
+    }
+    return slow+1
+};
+

283. 移动零

/**
+ * @param {number[]} nums
+ * @return {void} Do not return anything, modify nums in-place instead.
+ */
+var moveZeroes = function(nums) {
+    let slow=0;
+    for(let fast=0;fast<nums.length;fast++){
+        if(nums[fast]!=0){
+            nums[slow]=nums[fast]
+            slow++
+        }
+    }
+    for(let i=slow;i<nums.length;i++){
+        nums[i]=0
+    }
+};
+

209. 长度最小的子数组

/**
+ * @param {number} target
+ * @param {number[]} nums
+ * @return {number}
+ */
+var minSubArrayLen = function (target, nums) {
+    //用滑动窗口进行求解
+    let start = 0, end = 0;
+    const n = nums.length;
+    let sum = 0, ans = n + 1;//这个尽量设一个较大的值
+    while (end < n) {
+        sum += nums[end]
+        end++
+        while (sum >= target) {
+            ans = Math.min(ans, end - start)
+            sum -= nums[start]
+            start++
+        }
+    }
+    return ans == n + 1 ? 0 : ans
+};
+

904. 水果成篮

/**
+ * @param {number[]} fruits
+ * @return {number}
+ */
+var totalFruit = function (fruits) {
+    if (fruits.length <= 2) return fruits.length
+    //用滑动窗口+哈希来求解
+    const map = new Map()
+    let right = 0, left = 0//left来进行标记
+    let max = -Infinity
+    while (right < fruits.length) {
+        const type = fruits[right]
+        right++
+        //注意这个位置求值后下面结尾也要注意一下!!!
+        const it = Array.from(map.values())
+        max = Math.max(max, it.reduce((p, v) => p + v, 0))
+
+        map.set(type, (map.get(type) || 0) + 1)
+
+
+        while (map.size > 2) {
+            const ty = fruits[left]
+            left++
+            map.set(ty, map.get(ty) - 1)
+
+            if (map.get(ty) == 0) {
+                map.delete(ty)
+            }
+        }
+
+    }
+    const it = Array.from(map.values())
+    max = Math.max(max, it.reduce((p, v) => p + v, 0))
+
+    return max == -Infinity ? fruits.length : max
+};
+

11. 盛最多水的容器

/**
+ * @param {number[]} height
+ * @return {number}
+ */
+var maxArea = function(height) {
+    let maxA=0
+    let left=0,right=height.length-1
+    while(left<right){
+        let area=Math.min(height[left],height[right])*(right-left)
+        maxA=Math.max(maxA,area)
+        if(height[left]<height[right]){
+            left++
+        }else{
+            right--
+        }
+    }
+    return maxA
+};
+

15. 三数之和

/**
+ * @param {number[]} nums
+ * @return {number[][]}
+ */
+var threeSum = function(nums) {
+    //这里有三个去重的点子
+    const len=nums.length;
+    const res=[];
+    nums.sort((a,b)=>a-b)
+    for(let i=0;i<len-2;i++){
+        //第一点优化:但凡开始记录,后面的三数和绝对大于0
+        if(nums[i]>0)break;
+        //第二点优化:跳过重复的点  
+        if(i>0 && nums[i]==nums[i-1])continue;
+        let L=i+1,R=len-1
+        while(L<R){
+            const target=nums[i]+nums[L]+nums[R];
+            if(target==0){
+                res.push([nums[i],nums[L],nums[R]])
+                //第三层优化:
+                while(L<R && nums[L]==nums[L+1])L++
+                while(L<R && nums[R]==nums[R+1])R--
+                L++
+                R--
+            }else if(target<0){
+                L++
+            }else{
+                R--
+            }
+        }
+    }
+    return res
+};
+

这个写得差点时间超限:

/**
+ * @param {number[]} nums
+ * @return {number[][]}
+ */
+var threeSum = function(nums) {
+    nums.sort((a,b)=>a-b)
+    const res=[]
+    for(let i=1;i<nums.length;i++){
+        const first=nums[i-1];
+        let left=i,right=nums.length-1
+        while(left<right){
+            const target=nums[left]+nums[right]+first;
+            if(target==0){
+                res.push([first,nums[left],nums[right]]+"")
+                left++
+                right--
+            }
+            else if(target<0){
+                left++
+            }else{
+                right--
+            }
+        }
+    }
+    let result=[...new Set(res)].map(str=>str.split(",").map(s=>Number(s)))
+    return result
+};
+

42. 接雨水

/**
+ * @param {number[]} height
+ * @return {number}
+ */
+var trap = function (height) {
+    //利用双指针方法进行求解!!!这个方法比较简单!!!
+    let ans = 0;
+    let left = 0, right = height.length - 1;
+    let leftMax = 0, rightMax = 0
+    while (left < right) {
+        leftMax = Math.max(leftMax, height[left])
+        rightMax = Math.max(rightMax, height[right])
+        if (height[left] < height[right]) {
+            ans += leftMax - height[left]
+            left++
+        } else {
+            ans += rightMax - height[right]
+            right--
+        }
+    }
+    return ans
+};
+

3. 无重复字符的最长子串

/**
+ * @param {string} s
+ * @return {number}
+ */
+var lengthOfLongestSubstring = function (s) {
+    let res = 0;
+    let left = 0, right = 0;
+    let window = {}
+    while (right < s.length) {
+        const c = s[right]
+        right++;
+        window[c] = (window[c] || 0) + 1
+
+        while (window[c] > 1) {
+            const d = s[left];
+            left++;
+            window[d]--
+        }
+        res = Math.max(res, right - left)
+    }
+    return res
+};
+

前面用了一个JS方法来求解的

/**
+ * @param {string} s
+ * @return {number}
+ */
+var lengthOfLongestSubstring = function (s) {
+    //滑动窗口问题
+    let max = 0;
+    let slow = 0, fast = 0;
+    while (fast < s.length) {
+        if (!s.slice(slow, fast).includes(s[fast])) {
+            fast++
+        } else {
+            slow++
+        }
+        max = Math.max(fast - slow, max)
+    }
+    return max
+};
+

76.最小覆盖子串

/**
+ * @param {string} s
+ * @param {string} t
+ * @return {string}
+ */
+var minWindow = function (s, t) {
+    // 哈希表 need 记录需要匹配的字符及对应的出现次数
+    // 哈希表 window 记录窗口中满足 need 条件的字符及其出现次数
+    let need = new Map();
+    let window = new Map();
+    //先将need填充好
+    for (let i = 0; i < t.length; i++) {
+        if (need.has(t[i])) {
+            need.set(t[i], need.get(t[i]) + 1)
+        } else {
+            need.set(t[i], 1)
+        }
+    }
+
+    let left = 0, right = 0;
+    let valid = 0;
+    // 记录最小覆盖子串的起始索引及长度
+    let start = 0, len = Infinity;
+    while (right < s.length) {
+        // c 是将移入窗口的字符
+        const c = s[right]
+        // 扩大窗口
+        right++
+        // 进行窗口内数据的一系列更新
+        if (need.has(c)) {
+            if (window.has(c)) {
+                window.set(c, window.get(c) + 1)
+            } else {
+                window.set(c, 1)
+            }
+            if (window.get(c) === need.get(c)) {
+                valid++
+            }
+        }
+        // 判断左侧窗口是否要收缩
+        while (valid === need.size) {
+            // 在这里更新最小覆盖子串
+            if (right - left < len) {
+                start = left
+                len = right - left
+            }
+            // d 是将移出窗口的字符
+            const d = s[left]
+            // 缩小窗口
+            left++
+            // 进行窗口内数据的一系列更新
+            if(need.has(d)){
+                if(window.get(d)==need.get(d)){
+                    valid--
+                }
+                window.set(d,window.get(d)-1)
+            }
+        }
+    }
+    // 返回最小覆盖子串
+    return len==Infinity ?'':s.substr(start,len)
+};
+

438. 找到字符串中所有字母异位词

/**
+ * @param {string} s
+ * @param {string} p
+ * @return {number[]}
+ */
+var findAnagrams = function (s, p) {
+    // 定义需求和窗口
+    const need = new Map()
+    const window = new Map()
+    // 收集需求
+    for (const c of p) {
+        need.set(c, (need.get(c) || 0) + 1);
+    }
+    let left = 0, right = 0, ans = [];//存储结果
+    let valid = 0;//统计是否达到需求数
+    while (right < s.length) {
+        const c = s[right]
+        right++;
+        // 对窗口内数据进行更新
+        if (need.has(c)) {
+            window.set(c, (window.get(c) || 0) + 1)
+            if (need.get(c) === window.get(c)) {
+                valid++
+            }
+        }
+        // 判断左侧窗口是否需要收缩
+        while (right - left == p.length) {
+            // 符合窗口条件,把索引值加入结果数组
+            if (valid === need.size) {
+                ans.push(left)
+            }
+            const d = s[left]
+            left++;
+            // 条件成立进行窗口收缩!
+            if (need.has(d)) {
+                if (window.get(d) === need.get(d)) {
+                    valid--
+                }
+                window.set(d, window.get(d) - 1)
+            }
+        }
+
+    }
+    return ans
+};
+

567.字符串的排列

/**
+ * @param {string} s1
+ * @param {string} s2
+ * @return {boolean}
+ */
+var checkInclusion = function(s1, s2) {
+    let need=new Map()
+    let window=new Map()
+    //先进行need的收集!!!
+    for(const c of s1){
+        need.set(c,(need.get(c)||0)+1)
+    }
+    let left=0,right=0
+    let valid=0
+    while(right<s2.length){
+        const c=s2[right]//目前探寻的是最右边的填充
+        right++
+        //进行窗口的一系列更新
+        if(need.has(c)){
+            window.set(c,(window.get(c)||0)+1)
+            if(window.get(c)==need.get(c)){
+                valid++
+            }
+        }
+        //判断窗口是否需要进行缩放
+        while(right-left>=s1.length){
+            //找到了合法的子串
+            if(valid==need.size){
+                return true
+            }
+            const d=s2[left]
+            left++
+            //进行窗口的一系列更新
+            if(need.has(d)){
+                if(window.get(d)==need.get(d)){
+                    valid--
+                }
+                window.set(d,window.get(d)-1)
+            }
+        }
+    }
+    return false
+
+};
+

239. 滑动窗口最大值

/**
+ * @param {number[]} nums
+ * @param {number} k
+ * @return {number[]}
+ */
+var maxSlidingWindow = function(nums, k) {
+    const n = nums.length;
+    const q = [];
+    for (let i = 0; i < k; i++) {
+        while (q.length && nums[i] >= nums[q[q.length - 1]]) {
+            q.pop();
+        }
+        q.push(i);
+    }
+
+    const ans = [nums[q[0]]];
+    for (let i = k; i < n; i++) {
+        while (q.length && nums[i] >= nums[q[q.length - 1]]) {
+            q.pop();
+        }
+        q.push(i);
+        while (q[0] <= i - k) {
+            q.shift();
+        }
+        ans.push(nums[q[0]]);
+    }
+    return ans;
+};
+
Last Updated:
Contributors: xiaoyu
+ + + diff --git "a/algorithm/\346\211\213\346\222\225\346\225\260\346\215\256\347\273\223\346\236\204.html" "b/algorithm/\346\211\213\346\222\225\346\225\260\346\215\256\347\273\223\346\236\204.html" new file mode 100644 index 0000000..c398d63 --- /dev/null +++ "b/algorithm/\346\211\213\346\222\225\346\225\260\346\215\256\347\273\223\346\236\204.html" @@ -0,0 +1,422 @@ + + + + + + + + + 手撕数据结构 | 🍰 小雨的学习记录 + + + + + +

手撕数据结构

class Stack {
+  constructor() {
+    this.stack = [];
+  }
+  pop() {
+    return this.stack.pop();
+  }
+  push(item) {
+    this.stack.push(item);
+  }
+  peek() {
+    return this.stack[this.getCount() - 1];
+  }
+  getCount() {
+    return this.stack.length;
+  }
+  isEmpty() {
+    return this.getCount() === 0;
+  }
+}
+//栈的几种操作:出栈、入栈、栈顶、栈是否为空、栈的大小
+

单链队列

export default class Queue{
+    constructor(){
+        this.queue=[]
+    }
+    enQueue(item){
+        this.queue.push(item)
+    }
+    deQueue(){
+        this.queue.shift()
+    }
+    getHeader(){
+        return this.queue[0]
+    }
+    getLength(){
+        return this.queue.length
+    }
+    isEmpty(){
+        return this.getLength()===0
+    }
+}
+

循环队列

class SqQueue {
+  constructor(length) {
+    this.queue = new Array(length + 1); //预留空位
+    //队头
+    this.first = 0;
+    //队尾
+    this.last = 0;
+    //当前队列的大小
+    this.size = 0;
+  }
+  enQueue(item) {
+    //判断队尾 + 1 是否为队头
+    //如果是就代表需要扩容数组(下面的一个判断条件是队列已满)
+    // % this.queue.length 是为了防止数组越界
+    if (this.isFull()) {
+      this.resize(this.getLength() * 2 + 1);
+    }
+    this.queue[this.last] = item;
+    this.size++;
+    this.last = (this.last + 1) % this.queue.length;
+  }
+  deQueue() {
+    let r = this.getHeader();
+    this.queue[this.first] = null;
+    this.first = (this.first + 1) % this.queue.length;
+    this.size--;
+    //判断当前队列大小是否过小
+    //为了保证不浪费空间,在队列空间等于总长度的四分之一时 且不为2时缩小总长度为当前的一半
+    if (this.size <= this.getLength() / 4 && this.getLength() % 2 === 0) {
+      this.resize(this.getLength() / 2 + 1);
+    }
+    return r;
+  }
+  getHeader() {
+    if (this.isEmpty()) {
+      throw Error("Queue is empty");
+    }
+    return this.queue[this.first];
+  }
+  getLength() {
+    return this.queue.length - 1;
+  }
+  isEmpty() {
+    return this.first === this.last;
+  }
+  isFull() {
+    return this.first === (this.last + 1) % this.queue.length;
+  }
+  resize(length) {
+    let q = new Array(length);
+    for (let i = 0; i < length; i++) {
+      q[i] = this.queue[(i + this.first) % this.queue.length];
+    }
+    this.queue = q;
+    this.first = 0;
+    this.last = this.size;
+  }
+}
+

单向链表

class Node{
+    constructor(value,next=null) {
+        this.value=value
+        this.next=next
+    }   
+}
+
+class LinkedList{
+    constructor(value) {
+        this.head=new Node(value)
+    }
+    //查找节点
+    findNode(value){
+        let currentNode=this.head
+        while(currentNode.value !==value  && currentNode!=null){
+            currentNode=currentNode.next
+        }
+        return currentNode;
+    }
+    //指定位置插入节点
+    insertAfter(value,newValue){
+        const newNode=new Node(newValue)
+        const currentNode=this.findNode(value)
+
+        newNode.next=currentNode.next
+        currentNode.next=newNode
+    }
+    //在尾部插入节点
+    append(value){
+        const newNode=new Node(value)
+        let currentNode=this.head;
+        while(currentNode.next){
+            currentNode=currentNode.next
+        }
+        currentNode.next=newNode
+    }
+    //在头部插入节点
+    prepend(value){
+        const newNode=new Node(value)
+        newNode.next=this.head
+        this.head=newNode
+    }
+    //删除指定节点
+    remove(value){
+        let currentNode=this.head;
+        let previousNode=null;
+
+        while(currentNode.value!=value){
+            previousNode=currentNode;
+            currentNode=currentNode.next
+        }
+        if(currentNode===this.head){
+            this.head=currentNode.next
+        }else{
+            previousNode.next=currentNode.next
+        }
+    }
+    //删除头部节点
+    removeHead(){
+        this.head=this.head.next
+    }
+    //删除尾部节点
+    removeTail(){
+        let currentNode=this.head;
+        let previousNode=null
+        while(currentNode.next){
+            previousNode=currentNode
+            currentNode=currentNode.next
+        }
+        previousNode.next=null
+    }
+    //遍历链表节点
+    traverse(){
+        let currentNode=this.head
+        while(currentNode){
+            console.log(currentNode.value);
+            currentNode=currentNode.next
+        }
+    }
+
+}
+
+
+//操作实例
+let list=new LinkedList(1)
+list.append(2)
+list.append(3)
+list.append(4)
+
+list.insertAfter(2,5)
+list.prepend(6)
+list.remove(3)
+list.removeHead()
+list.removeTail()
+

class MaxHeap {
+  constructor() {
+    this.heap = [];
+  }
+  size() {
+    return this.heap.length;
+  }
+  empty() {
+    return this.size() === 0;
+  }
+  add(item) {
+    this.heap.push(item);
+    this._shiftUp(this.size() - 1);
+  }
+  removeMax() {
+    this._shiftDown(0);
+  }
+  getParentIndex(k) {
+    return parseInt((k - 1) / 2);
+  }
+  getLeftIndex(k) {
+    return k * 2 + 1;
+  }
+  getRightIndex(k) {
+    return k * 2 + 2;
+  }
+  _shiftUp(k) {
+    //如果当前节点比父节点大,就交换
+    while (this.heap[k] > this.heap[this.getParentIndex(k)]) {
+      this._swap(k, this.getParentIndex(k));
+      //将索引变成父节点
+      k = this.getParentIndex(k);
+    }
+  }
+  _shiftDown(k) {
+    //交换首位并删除末尾
+    this._swap(k, this.size() - 1);
+    this.heap.splice(this.size() - 1, 1);
+    //判断节点是否有左孩子,因为二叉堆的特性,有右必有左
+    while (this.getLeftIndex(k) < this.size()) {
+      let j = this.getLeftIndex(k);
+      //判断是否有右孩子,并且右孩子是否大于左孩子
+      if (j + 1 < this.size() && this.heap[j + 1] > this.heap[j]) j++;
+      //判断父节点是否已经比子节点都大
+      if (this.heap[k] >= this.heap[j]) break;
+      this._swap(k, j);
+      k = j;
+    }
+  }
+  _swap(left, right) {
+    let rightValue = this.heap[right];
+    this.heap[right] = this.heap[left];
+    this.heap[left] = rightValue;
+  }
+}
+
+/* 
+堆的插入操作是单一节点的上浮,时间复杂度 O(logn)
+堆的删除操作是单一节点的下沉,时间复杂度 O(logn)
+注意建堆操作的时间复杂度是 O(n) // 不要误认为是O(nlogn),有两种建立堆的方式。。
+*/
+

二分搜索树

import Queue from "./3.实现一个单链队列";
+class Node{
+    constructor(value){
+        this.value=value;
+        this.left=null
+        this.right=null
+    }
+}
+
+class BST{
+    constructor(){
+        this.root=null
+        this.size=0;
+    }
+    getSize(){
+        return this.size
+    }
+    isEmpty(){
+        return this.size===0
+    }
+    addNode(v){
+        this.root=this._addChild(this.root,v)
+    }
+    //添加节点时,需要比较添加的节点值和当前节点值的大小
+    _addChild(node,v){
+        if(!node){
+            this.size++
+            return new Node(v)
+        }
+        if(node.value>v){
+            node.left=this._addChild(node.left,v)
+        }else if(node.value<v){
+            node.right=this._addChild(node.right,v)
+        }
+        return node 
+    }
+    //先序遍历:可以用于打印树的结构
+    preTraversal(){
+        this._pre(this.root)
+    }
+    _pre(node){
+        if(node){
+            console.log(node.value);
+            this._pre(node.left)
+            this._pre(node.right)
+        }
+    }
+    //中序遍历:可以用于排序,对于BST来说,中序遍历可以实现一次遍历就得到有序值
+    midTraversal(){
+        this._mid(this.root)
+    }
+    _mid(node){
+        if(node){
+            this._mid(node.left)
+            console.log(node.value);
+            this._mid(node.right)
+        }
+    }
+    //后续遍历:可以用于先操作子节点再操作父节点的场景
+    backTraversal(){
+        this._back(this.root)
+    }
+    _back(node){
+        if(node){
+            this._back(node.left);
+            this._back(node.right);
+            console.log(node.value);
+        }
+    }
+    //广度遍历
+    breadthTraversal(){
+        if(!this.root)return null
+        let q=new Queue()
+        //将根节点入队
+        q.enQueue(this.root)
+        //循环判断队列是否为空,为空代表树遍历完毕
+        while(!q.isEmpty()){
+            //将队首出队,判断是否有左右子树,有的话,就先左后右入队
+            let n=q.deQueue()
+            console.log(n.value);
+            if(n.left)q.enQueue(n.left)
+            if(n.right)q.enQueue(n.right)
+        }
+    }
+    getMin(){
+        return this._getMin(this.root).value
+    }
+    _getMin(node){
+        if(!node.left)return node
+        return this._getMin(node.left)
+    }
+    getMax(){
+        return this._getMax(this.root).value
+    }
+    _getMax(node){
+        if(!node.right)return node
+        return this._getMax(node.right)
+    }
+    //向下取整
+    floor(v){
+        let node =this._floor(this.root,v)
+        return node ?node.value:null
+    }
+    _floor(node,v){
+        if(!node)return null
+        if(node.value==v)return v
+        //如果当前节点值还比需要的值大,就继续递归
+        if(node.value>v){
+            return this._floor(node.left,v)
+        }
+        //如果节点还拥有右子树
+        let right=this._floor(node.right,v)
+        if(right)return right
+        return node
+    }
+    //向上取整的基本操作一样的
+    cell(v){
+        let node =this._floor(this.root,v)
+        return node?node.value:null
+    }
+    _cell(node,v){
+        if(!node)return null
+        if(node.value==v)return v
+        if(node.value<v){
+            return this._cell(node.right,v)
+        }
+        let left=this._floor(node.left,v)
+        if(left)return left
+        return node
+    }
+}
+
Last Updated:
Contributors: xiaoyu
+ + + diff --git "a/algorithm/\346\212\200\345\267\247_\346\225\260\345\255\246\360\237\215\214.html" "b/algorithm/\346\212\200\345\267\247_\346\225\260\345\255\246\360\237\215\214.html" new file mode 100644 index 0000000..9c4219d --- /dev/null +++ "b/algorithm/\346\212\200\345\267\247_\346\225\260\345\255\246\360\237\215\214.html" @@ -0,0 +1,176 @@ + + + + + + + + + 技巧_数学🍌 | 🍰 小雨的学习记录 + + + + + +

技巧_数学🍌

136.只出现一次的数字

/**
+ * @param {number[]} nums
+ * @return {number}
+ */
+var singleNumber = function(nums) {
+    //这里的空间复杂度挺高的!!!
+    // let obj={}
+    // nums.forEach((num,index)=>{
+    //     if(obj[num]){
+    //         obj[num]=obj[num]+1
+    //     }else{
+    //         obj[num]=1
+    //     }
+    // })
+    // for(let key in obj){
+    //     if(obj[key]==1){
+    //         return key
+    //     }
+    // }
+
+    //进行异或操作
+    // 2 ^ 2 ^ 1 = 0 ^ 1 = 1
+    let res=0
+    for(const num of nums){
+        res^=num
+    }
+    return res
+};
+

31.下一个排列

/**
+ * @param {number[]} nums
+ * @return {void} Do not return anything, modify nums in-place instead.
+ */
+var nextPermutation = function(nums) {
+    let len=nums.length
+    let i=len-2//注意这里为什么是这样!
+    //从后开始寻找非降序的元素
+    while(i>=0 && nums[i]>=nums[i+1]){
+        i--
+    }
+    if(i>=0){
+        let j=len-1
+        while(j>=0 && nums[i]>=nums[j]){//从后往前走找到大于之前的那个数,进行交换
+             j--
+        }
+        swap(nums,i,j)
+    }
+    reverse(nums,i+1)//翻转最开始找到数后面的的一些数字
+};
+
+function swap(nums,i,j){
+    let tmp=nums[i]
+    nums[i]=nums[j]
+    nums[j]=tmp
+}
+
+function reverse(nums,start){
+    let end=nums.length-1
+    while(start<end){
+        swap(nums,start,end)
+        start++
+        end--
+    }
+}
+

560. 和为 K 的子数组

/**
+ * @param {number[]} nums
+ * @param {number} k
+ * @return {number}
+ */
+const subarraySum = (nums, k) => {
+    /* 
+    遍历 nums 之前,我们让 -1 对应的前缀和为 0,这样通式在边界情况也成立。
+    即在遍历之前,map 初始放入 0:1 键值对(前缀和为0出现1次了)。
+     */
+    const map = { 0: 1 };//可以想象一下前几个的前缀和正好等于k
+    let prefixSum = 0;
+    let count = 0;
+
+    for (let i = 0; i < nums.length; i++) {
+        prefixSum += nums[i];
+        /* 前缀和之差等于k,只关心等于 k 的前缀和之差出现的次数c,就知道了有c个子数组求和等于k。 */
+        if (map[prefixSum - k]) {
+            count += map[prefixSum - k];
+        }
+
+        if (map[prefixSum]) {
+            map[prefixSum]++;
+        } else {
+            map[prefixSum] = 1;
+        }
+    }
+    return count;
+};
+

1. 两数之和

/**
+ * @param {number[]} nums
+ * @param {number} target
+ * @return {number[]}
+ */
+var twoSum = function (nums, target) {
+    const map = new Map()
+    for (let i = 0; i < nums.length; i++) {
+        if (map.has(target - nums[i])) {
+            return [map.get(target - nums[i]), i]
+        }
+        map.set(nums[i], i)
+    }
+};
+

49. 字母异位词分组

/**
+ * @param {string[]} strs
+ * @return {string[][]}
+ */
+var groupAnagrams = function(strs) {
+    //要点,就是将字符串排序后就是相同的东西了
+    //还有,就是map来进行存储,key 为排序后的字符串,value为一个数组
+    let map=new Map();
+    for(const str of strs){
+        //sort排序一下key
+        let key = str.split("").sort().join("");
+        if(map.has(key))map.get(key).push(str)
+        else map.set(key,[str])
+    }
+    let arr=Array.from(map.values())
+    return arr
+};
+

128. 最长连续序列

/**
+ * @param {number[]} nums
+ * @return {number}
+ */
+var longestConsecutive = function(nums) {
+    //用之前两数之和的那个思想
+    nums.sort((a,b)=>a-b)
+    let map=new Map()
+    let max=0//最长连续的个数!!!
+    for(let i=0;i<nums.length;i++){
+        if(map.has(nums[i]-1))map.set(nums[i],map.get(nums[i]-1)+1)
+        else map.set(nums[i],1)
+        max=Math.max(max,map.get(nums[i]))
+    }
+    return max
+};
+
Last Updated:
Contributors: xiaoyu
+ + + diff --git "a/algorithm/\346\240\210_\345\240\206\360\237\215\212.html" "b/algorithm/\346\240\210_\345\240\206\360\237\215\212.html" new file mode 100644 index 0000000..02fb5bb --- /dev/null +++ "b/algorithm/\346\240\210_\345\240\206\360\237\215\212.html" @@ -0,0 +1,422 @@ + + + + + + + + + 栈_堆🍊 | 🍰 小雨的学习记录 + + + + + +

栈_堆🍊

20.有效的括号

/**
+ * @param {string} s
+ * @return {boolean}
+ */
+var isValid = function (s) {
+    const map = {
+        '(': -1,
+        ')': 1,
+        '{': -2,
+        '}': 2,
+        '[': -3,
+        ']': 3
+    }
+    const stack=[]
+    for(const c of s){
+        if(map[c]<0){
+            stack.push(map[c])
+        }else if(map[c]>0){
+            let top=stack.pop()
+            if(top+map[c]!=0)return false
+        }
+    }
+    if(stack.length!=0)return false
+    return true
+};
+

155.最小栈


+var MinStack = function() {
+    //搞了一个辅助栈!!!
+    this.stack=[]
+    this.min_stack=[Infinity]
+};
+
+/** 
+ * @param {number} val
+ * @return {void}
+ */
+MinStack.prototype.push = function(val) {
+    this.stack.push(val)
+    this.min_stack.push(Math.min(this.min_stack[this.min_stack.length-1],val))
+};
+
+/**
+ * @return {void}
+ */
+MinStack.prototype.pop = function() {
+    this.stack.pop()
+    this.min_stack.pop()
+};
+
+/**
+ * @return {number}
+ */
+MinStack.prototype.top = function() {
+    return this.stack[this.stack.length-1]
+};
+
+/**
+ * @return {number}
+ */
+MinStack.prototype.getMin = function() {
+    return this.min_stack[this.min_stack.length-1]
+};
+
+/**
+ * Your MinStack object will be instantiated and called as such:
+ * var obj = new MinStack()
+ * obj.push(val)
+ * obj.pop()
+ * var param_3 = obj.top()
+ * var param_4 = obj.getMin()
+ */
+

394.字符串解码

const decodeString = (s) => {
+    let numStack = [];        // 存倍数的栈
+    let strStack = [];        // 存 待拼接的str 的栈
+    let num = 0;              // 倍数的“搬运工”
+    let result = '';          // 字符串的“搬运工”
+    for (const char of s) {   // 逐字符扫描
+        if (!isNaN(char)) {   // 遇到数字
+            num = num * 10 + Number(char); // 算出倍数
+        } else if (char == '[') {  // 遇到 [
+            strStack.push(result); // result串入栈
+            result = '';           // 入栈后清零
+            numStack.push(num);    // 倍数num进入栈等待
+            num = 0;               // 入栈后清零
+        } else if (char == ']') {  // 遇到 ],两个栈的栈顶出栈
+            let repeatTimes = numStack.pop(); // 获取拷贝次数
+            result = strStack.pop() + result.repeat(repeatTimes); // 构建子串
+        } else {                   
+            result += char;        // 遇到字母,追加给result串
+        }
+    }
+    return result;
+};
+

739.每日温度

/**
+ * @param {number[]} temperatures
+ * @return {number[]}
+ */
+var dailyTemperatures = function (temperatures) {
+    // 单调递减栈
+    let stack = [];
+    let n = temperatures.length;
+    let res = new Array(n).fill(0);
+
+    // 遍历每日温度,维护一个单调栈,存储下标
+    for (let i = 0; i < n; i++) {
+        // 当日温度大于栈顶温度,说明栈顶温度的升温日找到了,栈顶出栈并计算天数;继续判断栈顶元素
+        while (stack.length && temperatures[i] > temperatures[stack[stack.length - 1]]) {
+            const top = stack.pop();
+            res[top] = i - top;
+        }
+        // 栈为空 或 每日温度小于等于栈顶温度 => 直接入栈
+        stack.push(i)
+    }
+
+    return res;
+};
+

84.柱状图中最大的矩形(不会)

/**
+ * @param {number[]} heights
+ * @return {number}
+ */
+var largestRectangleArea = function (heights) {
+    let maxArea = 0, stack = [];
+    let len = heights.length
+    //单调递减栈
+    for (let i = 0; i <= len; i++) {
+        while (stack.length > 0 && (heights[i] < heights[stack[stack.length - 1]] || i === len)) {
+            let height = heights[stack.pop()],
+                width = stack.length > 0 ? i - stack[stack.length - 1] - 1 : i;
+
+            maxArea = Math.max(maxArea, width * height);
+        }
+
+        stack.push(i);
+    }
+
+    return maxArea;
+};
+
+

844. 比较含退格的字符串

/**
+ * @param {string} s
+ * @param {string} t
+ * @return {boolean}
+ */
+var backspaceCompare = function(s, t) {
+    let sStack=[],tStack=[]
+    for(const c of s){
+        if(c=='##'){
+            sStack.pop()
+        }else{
+            sStack.push(c)
+        }
+    }
+    for(const c of t){
+        if(c=='##'){
+            tStack.pop()
+        }else{
+            tStack.push(c)
+        }
+    }
+    return sStack.join("")==tStack.join("")
+};
+

215. 数组中的第K个最大元素

/**
+ * @param {number[]} nums
+ * @param {number} k
+ * @return {number}
+ */
+var findKthLargest = function(nums, k) {
+    // 初始化小顶堆
+    // 请注意:我们将堆中所有元素取反,从而用大顶堆来模拟小顶堆
+    const maxHeap = new MaxHeap([]);
+    // 将数组的前 k 个元素入堆
+    for (let i = 0; i < k; i++) {
+        pushMinHeap(maxHeap, nums[i]);
+    }
+    // 从第 k+1 个元素开始,保持堆的长度为 k
+    for (let i = k; i < nums.length; i++) {
+        // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆
+        if (nums[i] > peekMinHeap(maxHeap)) {
+            popMinHeap(maxHeap);
+            pushMinHeap(maxHeap, nums[i]);
+        }
+    }
+    // 返回堆中元素
+    return getMinHeap(maxHeap)[0];
+};
+
+/* 元素入堆 */
+function pushMinHeap(maxHeap, val) {
+    // 元素取反
+    maxHeap.push(-val);
+}
+
+/* 元素出堆 */
+function popMinHeap(maxHeap) {
+    // 元素取反
+    return -maxHeap.pop();
+}
+
+/* 访问堆顶元素 */
+function peekMinHeap(maxHeap) {
+    // 元素取反
+    return -maxHeap.peek();
+}
+
+/* 取出堆中元素 */
+function getMinHeap(maxHeap) {
+    // 元素取反
+    return maxHeap.getMaxHeap().map((num) => -num);
+}
+
+class MaxHeap {
+  constructor(arr) {
+    this.heap = arr;
+  }
+  size() {
+    return this.heap.length;
+  }
+  isEmpty() {
+    return this.size() === 0;
+  }
+  peek(){
+    return this.heap[0]
+  }
+  getLeftChild(i) {
+    return i * 2 + 1;
+  }
+  getRightChild(i) {
+    return i * 2 + 2;
+  }
+  getParent(i) {
+    return parseInt((i - 1) / 2);
+  }
+  push(item) {
+    this.heap.push(item);
+    this.shiftUp(this.size() - 1);
+  }
+  pop() {
+    this.shiftDown(0);
+  }
+  shiftUp(i) {
+    while (this.heap[i] > this.heap[this.getParent(i)]) {
+      this.swap(i, this.getParent(i));
+      i = this.getParent(i);
+    }
+  }
+  shiftDown(i) {
+    //交换值并且删除最后一个值
+    this.swap(i, this.size() - 1);
+    this.heap.pop();
+
+    while (this.getLeftChild(i) < this.size()) {
+      let j = this.getLeftChild(i);
+      if (j + 1 < this.size() && this.heap[j] < this.heap[j + 1]) j++;
+      if (this.heap[i] >= this.heap[j]) break;
+      this.swap(i, j);
+      i = j;
+    }
+  }
+  swap(i, j) {
+    const tmp = this.heap[i];
+    this.heap[i] = this.heap[j];
+    this.heap[j] = tmp;
+  }
+  getMaxHeap(){
+    return this.heap
+  }
+}
+
+

347. 前 K 个高频元素

/**
+ * @param {number[]} nums
+ * @param {number} k
+ * @return {number[]}
+ */
+var topKFrequent = function (nums, k) {
+    const map = {}
+    for (const it of nums) {
+        map[it] = (map[it] || 0) + 1
+    }
+    let numArr = Object.values(map)
+    const maxHeap = new MaxHeap([]);
+    // 将数组的前 k 个元素入堆
+    for (let i = 0; i < k; i++) {
+        pushMinHeap(maxHeap, numArr[i]);
+    }
+    // 从第 k+1 个元素开始,保持堆的长度为 k
+    for (let i = k; i < numArr.length; i++) {
+        // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆
+        if (numArr[i] > peekMinHeap(maxHeap)) {
+            popMinHeap(maxHeap);
+            pushMinHeap(maxHeap, numArr[i]);
+        }
+    }
+    // 返回堆中元素
+    let res = []
+    let narr = getMinHeap(maxHeap);
+    for (const key in map) {
+        if (narr.includes(map[key])) {
+            res.push(key)
+        }
+    }
+    return res
+};
+
+
+function pushMinHeap(maxHeap, val) {
+    // 元素取反
+    maxHeap.push(-val);
+}
+
+/* 元素出堆 */
+function popMinHeap(maxHeap) {
+    // 元素取反
+    return -maxHeap.pop();
+}
+
+/* 访问堆顶元素 */
+function peekMinHeap(maxHeap) {
+    // 元素取反
+    return -maxHeap.peek();
+}
+
+/* 取出堆中元素 */
+function getMinHeap(maxHeap) {
+    // 元素取反
+    return maxHeap.getMaxHeap().map((num) => -num);
+}
+
+class MaxHeap {
+    constructor(arr) {
+        this.heap = arr;
+    }
+    size() {
+        return this.heap.length;
+    }
+    isEmpty() {
+        return this.size() === 0;
+    }
+    peek() {
+        return this.heap[0]
+    }
+    getLeftChild(i) {
+        return i * 2 + 1;
+    }
+    getRightChild(i) {
+        return i * 2 + 2;
+    }
+    getParent(i) {
+        return parseInt((i - 1) / 2);
+    }
+    push(item) {
+        this.heap.push(item);
+        this.shiftUp(this.size() - 1);
+    }
+    pop() {
+        this.shiftDown(0);
+    }
+    shiftUp(i) {
+        while (this.heap[i] > this.heap[this.getParent(i)]) {
+            this.swap(i, this.getParent(i));
+            i = this.getParent(i);
+        }
+    }
+    shiftDown(i) {
+        //交换值并且删除最后一个值
+        this.swap(i, this.size() - 1);
+        this.heap.pop();
+
+        while (this.getLeftChild(i) < this.size()) {
+            let j = this.getLeftChild(i);
+            if (j + 1 < this.size() && this.heap[j] < this.heap[j + 1]) j++;
+            if (this.heap[i] >= this.heap[j]) break;
+            this.swap(i, j);
+            i = j;
+        }
+    }
+    swap(i, j) {
+        const tmp = this.heap[i];
+        this.heap[i] = this.heap[j];
+        this.heap[j] = tmp;
+    }
+    getMaxHeap() {
+        return this.heap
+    }
+}
+
+
Last Updated:
Contributors: xiaoyu
+ + + diff --git "a/algorithm/\347\237\251\351\230\265\360\237\215\207.html" "b/algorithm/\347\237\251\351\230\265\360\237\215\207.html" new file mode 100644 index 0000000..7b5399e --- /dev/null +++ "b/algorithm/\347\237\251\351\230\265\360\237\215\207.html" @@ -0,0 +1,184 @@ + + + + + + + + + 矩阵🍇 | 🍰 小雨的学习记录 + + + + + +

矩阵🍇

1329. 将矩阵按对角线排序

/**
+ * @param {number[][]} mat
+ * @return {number[][]}
+ */
+var diagonalSort = function (mat) {
+    const n = mat.length;
+    const m = mat[0].length;
+    const diag = new Array(m + n).fill().map(() => []);
+    //先进行收集管道数据
+    for (let i = 0; i < n; i++) {
+        for (let j = 0; j < m; j++) {
+            diag[i - j + m].push(mat[i][j]);
+        }
+    }
+    //进行排序
+    diag.forEach(d => d.sort((a, b) => b - a));
+    //进行填充
+    for (let i = 0; i < n; i++) {
+        for (let j = 0; j < m; j++) {
+            mat[i][j] = diag[i - j + m].pop();
+        }
+    }
+    return mat;
+};
+

54. 螺旋矩阵

/**
+ * @param {number[][]} matrix
+ * @return {number[]}
+ */
+var spiralOrder = function (matrix) {
+    if (matrix.length == 0 || matrix[0].length == 0) { return [] }
+    //数据的准备
+    let row = matrix.length, col = matrix[0].length
+    const direction = [[0, 1], [1, 0], [0, -1], [-1, 0]]
+    const visited = Array.from({ length: row }, () => new Array(col).fill(false))
+    let total=row*col
+    let curRow=0,curCol=0,directionIndex=0
+    let order=new Array(total)
+
+    for(let i=0;i<total;i++){
+        //具体操作
+        visited[curRow][curCol]=true
+        order[i]=matrix[curRow][curCol]
+        //方便下一次操作
+        let newRow=curRow+direction[directionIndex][0]
+        let newCol=curCol+direction[directionIndex][1]
+        
+        //进行范围的判断
+        if(!(newRow>=0 &&newRow<row && newCol>=0 && newCol<col && !(visited[newRow][newCol]))){
+            directionIndex=(directionIndex+1)%4
+        }
+
+        curRow+=direction[directionIndex][0]
+        curCol+=direction[directionIndex][1]
+
+    }
+    return order
+};
+

289. 生命游戏

/**
+ * @param {number[][]} board
+ * @return {void} Do not return anything, modify board in-place instead.
+ */
+var gameOfLife = function (board) {
+    // 在增加一个数组
+    const m = board.length, n = board[0].length
+    const other = Array.from({ length: m }, () => new Array(n).fill(0))
+    for(let i=0;i<m;i++){
+        for(let j=0;j<n;j++){
+            if(getOnes(board,i,j)<2){
+                other[i][j]=0
+            }else if(getOnes(board,i,j)==2){
+                other[i][j]=board[i][j]
+            }else if(getOnes(board,i,j)==3){
+                other[i][j]=1
+            }else if(getOnes(board,i,j)>3){
+                other[i][j]=0
+            }
+        }
+    }
+    //身上复制
+    for(let i=0;i<m;i++){
+        for(let j=0;j<n;j++){
+            board[i][j]=other[i][j]
+        }
+    }
+};
+//获取周围的一些 "1"
+function getOnes(board, i, j) {
+    const arr = [
+        board[i]?.[j + 1],
+        board[i]?.[j - 1],
+        board[i + 1]?.[j],
+        board[i + 1]?.[j - 1],
+        board[i + 1]?.[j + 1],
+        board[i - 1]?.[j],
+        board[i - 1]?.[j + 1],
+        board[i - 1]?.[j - 1]
+    ]
+    return arr.filter(item => item == 1).length
+}
+

48. 旋转图像

/**
+ * @param {number[][]} matrix
+ * @return {void} Do not return anything, modify matrix in-place instead.
+ */
+var rotate = function (matrix) {
+   //两遍循环的问题
+   let n=matrix.length
+   for(let i=0;i<Math.floor(n/2);i++){
+    for(let j=0;j<Math.floor((n+1)/2);j++){
+        let tmp=matrix[i][j]
+        matrix[i][j]=matrix[n-1-j][i]
+        matrix[n-1-j][i]=matrix[n-1-i][n-1-j]
+        matrix[n-1-i][n-1-j]=matrix[j][n-1-i]
+        matrix[j][n-1-i]=tmp
+    }
+   }
+};
+

73. 矩阵置零

/**
+ * @param {number[][]} matrix
+ * @return {void} Do not return anything, modify matrix in-place instead.
+ */
+var setZeroes = function(matrix) {
+    //把零的位置全部存下啦
+    let m=matrix.length;
+    let n=matrix[0].length
+    obj={
+        i:new Set(),
+        j:new Set()
+    }
+    
+    //把零的位置遍历一遍
+    for(let i=0;i<m;i++){
+        for(let j=0;j<n;j++){
+            if(matrix[i][j]==0){
+                obj.i.add(i)
+                obj.j.add(j)
+            }
+        }
+    }
+
+    for(let i=0;i<m;i++){
+        for(let j=0;j<n;j++){
+            if(obj.i.has(i) ||obj.j.has(j)){
+                matrix[i][j]=0
+            }
+        }
+    }  
+};
+
Last Updated:
Contributors: xiaoyu
+ + + diff --git "a/algorithm/\350\264\252\345\277\203\360\237\215\211.html" "b/algorithm/\350\264\252\345\277\203\360\237\215\211.html" new file mode 100644 index 0000000..7849334 --- /dev/null +++ "b/algorithm/\350\264\252\345\277\203\360\237\215\211.html" @@ -0,0 +1,94 @@ + + + + + + + + + 贪心🍉 | 🍰 小雨的学习记录 + + + + + +

贪心🍉

55.跳跃游戏

/**
+ * @param {number[]} nums
+ * @return {boolean}
+ */
+//  由题目描述,我们需要达到最后一个下标,那么最后一个下标的数字其实是可以不用考虑的。
+//  那么我们可以假设只有两个数字(比如 [2,4][2, 4][2,4]),这个时候第一个数字如果是大于等于 111 的数就成立;
+//  如果是三个数字的话(比如 [3,0,4][3, 0, 4][3,0,4]),第一个数字大于等于 222 时成立。
+//  依此类推,一个数字可以到达的位置必须是这个数字标记的长度值,
+//  有:nums[i]>=jnums[i] >= jnums[i]>=j 成立时才可以到达后面第 jjj 个目标。
+
+var canJump = function(nums) {
+    // 必须到达end下标的数字
+    let end = nums.length - 1;
+
+    for (let i = nums.length - 2; i >= 0; i--) {
+        if (end - i <= nums[i]) {
+            end = i;
+        }
+    }
+
+    return end == 0;
+};
+

121. 买卖股票的最佳时机

/**
+ * @param {number[]} prices
+ * @return {number}
+ */
+var maxProfit = function (prices) {
+    let maxprofit = 0
+    let minprice = Infinity
+    for (let i = 0; i < prices.length; i++) {
+        if (minprice > prices[i]) {
+            minprice = prices[i]
+        } else if (maxprofit < prices[i] - minprice) {
+            maxprofit = prices[i] - minprice
+        }
+    }
+    return maxprofit
+};
+

45. 跳跃游戏 II

/**
+ * @param {number[]} nums
+ * @return {number}
+ */
+var jump = function(nums) {
+    let curIndex = 0
+    let nextIndex = 0
+    let steps = 0
+    // 以最小的步数增加最大的覆盖范围,直到覆盖范围覆盖了终点
+    for(let i = 0; i < nums.length - 1; i++) {
+        nextIndex = Math.max(nums[i] + i, nextIndex)
+        if(i === curIndex) {
+            curIndex = nextIndex
+            steps++
+        }
+    }
+
+    return steps
+};
+
Last Updated:
Contributors: xiaoyu
+ + + diff --git a/assets/404.html-BbKhE9lX.js b/assets/404.html-BbKhE9lX.js new file mode 100644 index 0000000..812c735 --- /dev/null +++ b/assets/404.html-BbKhE9lX.js @@ -0,0 +1 @@ +import{_ as t,o as e,c as o,a}from"./app-B-BkP2m_.js";const n={},s=a("p",null,"404 Not Found",-1),c=[s];function l(r,_){return e(),o("div",null,c)}const d=t(n,[["render",l],["__file","404.html.vue"]]),m=JSON.parse('{"path":"/404.html","title":"","lang":"zh-CN","frontmatter":{"layout":"NotFound"},"headers":[],"filePathRelative":null,"git":{},"readingTime":{"minutes":0.01,"words":3}}');export{d as comp,m as data}; diff --git a/assets/AJAX.html-B6IZZan4.js b/assets/AJAX.html-B6IZZan4.js new file mode 100644 index 0000000..d29e385 --- /dev/null +++ b/assets/AJAX.html-B6IZZan4.js @@ -0,0 +1,259 @@ +import{_ as e,r as o,o as c,c as l,a as n,b as s,d as t,e as p}from"./app-B-BkP2m_.js";const i={},u=p(`

异步请求 AJAX

XMLHttpRequest

手写一个基本且常规的原生请求模板

// 创建一个XMLHttpRequest对象
+const xhr = new XMLHttpRequest();
+// 打开一个 URL
+xhr.open("get", "http://127.0.0.1:8000/server");
+// 最后发送请求
+xhr.send();
+
+//两种进行处理的方式
+// >>>>>>>>>>>>> first-start
+xhr.onreadystatechange = function () {
+  if (xhr.readyState === 4) {
+    if (xhr.status >= 200 && xhr.status < 300) {
+      result.innerHTML = xhr.response;
+    }
+  }
+};
+// >>>>>>>>>>>>> first-end
+// >>>>>>>>>>>>> second-start
+xhr.addEventListener("load", reqListener);
+function reqListener() {
+  console.log(this.responseText);
+}
+// >>>>>>>>>>>>> second-end
+

重要属性介绍:

`,6),r={href:"https://developer.mozilla.org/zh-CN/docs/Web/API/XMLHttpRequest",target:"_blank",rel:"noopener noreferrer"},k=p(`

GET 请求

// 1.创建对象
+const xhr = new XMLHttpRequest();
+// 设置响应体为json
+xhr.responseType = "json"; //如果要获取JSON数据的,就这样设置
+//get方式设置请求参数
+xhr.open("get", "http://127.0.0.1:8000/server?a=100&b=200&c=300");
+// 3.发送
+xhr.send();
+// 4.事件绑定,处理服务器端的返回数据 onreadystatechange
+xhr.onreadystatechange = function () {
+  // 判断(服务端反悔了所有的结果)
+  if (xhr.readyState === 4) {
+    // 判断响应状态码 200 403 404 500 401
+    // 2XX:成功
+    if (xhr.status >= 200 && xhr.status < 300) {
+      // 处理结果  行 头 空行 体
+      // 1.响应行
+      console.log(xhr.status); //状态码
+      console.log(xhr.statusText); //状态字符串
+      console.log(xhr.getAllResponseHeaders); //所有响应头
+      console.log(xhr.response); //响应体
+      result.innerHTML = xhr.response;
+    }
+  }
+};
+

POST 请求

// 1.创建对象
+const xhr = new XMLHttpRequest();
+// 2.初始化,设置请求方法和URL
+xhr.open("post", "http://127.0.0.1:8000/server"); //post匹配服务器也应该为post
+
+// 设置请求头
+xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
+xhr.setRequestHeader("name", "luoyu"); //自定义请求头
+
+// 3.发送(请求体)
+xhr.send("a=100&b=200&c=300"); //post的参数在send()中可以有,注意是post
+xhr.onreadystatechange = function () {
+  // 判断(服务端返回了所有的结果)
+  if (xhr.readyState === 4) {
+    if (xhr.status >= 200 && xhr.status < 300) {
+      result.innerHTML = xhr.response;
+    }
+  }
+};
+

超时与网络异常

const xhr = new XMLHttpRequest();
+// 超时设置 2s 设置,2s钟还没响应就取消
+xhr.timeout = 2000;
+// 设置超时回调
+xhr.ontimeout = function () {
+  alert("你的网络请求超时了!");
+};
+// 网络异常回调
+xhr.onerror = function () {
+  alert("你的网络出现了异常");
+};
+xhr.open("get", "http://127.0.0.1:8000/delay");
+xhr.send();
+xhr.onreadystatechange = function () {
+  if (xhr.readyState === 4) {
+    if (xhr.status >= 200 && xhr.status < 300) {
+      result.innerHTML = xhr.response;
+    }
+  }
+};
+

请求重复问题

let isSending = false; //标识是否在发送AJAX请求
+let x = null;
+const btns = document.querySelectorAll("button");
+btns[0].onclick = function () {
+  if (isSending) x.abort();
+  x = new XMLHttpRequest(); //现在发现个问题,这个要作为对象来new一个
+  isSending = true;
+  x.open("get", "http://127.0.0.1:8000/delay");
+  x.send();
+  // 这里的话我给请求的地方进行了三秒的延迟
+  x.onreadystatechange = function () {
+    if (x.readyState === 4) {
+      isSending = false;
+    }
+  };
+};
+

监测进度

var oReq = new XMLHttpRequest();
+
+// 在请求调用 open() 之前添加事件监听。否则 progress 事件将不会被触发。
+oReq.addEventListener("progress", updateProgress);
+oReq.addEventListener("load", transferComplete);
+oReq.addEventListener("error", transferFailed);
+oReq.addEventListener("abort", transferCanceled);
+
+oReq.open();
+
+// ...
+
+// 服务端到客户端的传输进程(下载)
+function updateProgress(oEvent) {
+  if (oEvent.lengthComputable) {
+    var percentComplete = (oEvent.loaded / oEvent.total) * 100;
+    // ...
+  } else {
+    // 总大小未知时不能计算进程信息
+  }
+}
+
+function transferComplete(evt) {
+  console.log("The transfer is complete.");
+}
+
+function transferFailed(evt) {
+  console.log("An error occurred while transferring the file.");
+}
+
+function transferCanceled(evt) {
+  console.log("The transfer has been canceled by the user.");
+}
+

绕过缓存

在面试的时候,面试官问过我,关于强缓存,在没到达过期时间之前如何更新呢?因为你可能更改了服务端资源。——更新路径

有一个跨浏览器兼容的方法,就是给 URL 添加时间戳。请确保你酌情地添加了 "?" or "&" 。例如,将:

http://example.com/bar.html -> http://example.com/bar.html?12345
+http://example.com/bar.html?foobar=baz -> http://example.com/bar.html?foobar=baz&12345
+

因为本地缓存都是以 URL 作为索引的,这样就可以使每个请求都是唯一的,也就可以这样来绕开缓存。

你也可以用下面的方法自动更改缓存:

Copy to Clipboard
+const req = new XMLHttpRequest();
+
+req.open("GET", url + (/\\?/.test(url) ? "&" : "?") + new Date().getTime());
+req.send(null);
+

同步请求和异步请求

XMLHttpRequest 支持同步和异步通信。但是,一般来说,出于性能原因,异步请求应优先于同步请求。 同步请求阻止代码的执行,这会导致屏幕上出现“冻结”和无响应的用户体验。

xhr.open("GET", "/bar/foo.txt", true); //异步,默认
+xhr.open("GET", "http://www.mozilla.org/", false); //同步
+

注意:当您使用 async=false 时,请不要编写 onreadystatechange 函数 - 把代码放到 send() 语句后面即可:

xmlhttp.open("GET","/try/ajax/ajax_info.txt",false);
+xmlhttp.send();
+document.getElementById("myDiv").innerHTML=xmlhttp.responseText;
+

备注: 从 Gecko 30.0,Blink 39.0 和 Edge 13 开始,主线程上的同步请求由于对用户体验的负面影响而被弃用。同步 XHR 不允许所有新的 XHR 功能(如 timeout 或 abort)。这样做会调用 InvalidAccessError。

fetch

Fetch有几个特点的了解一下,面试可能问,深挖你的知识点!

fetch 发送 GET 请求

fetch("http://ajax-base-api-t.itheima.net/api/getbooks")
+  .then((response) => {
+    //这个response是一个Response对象,需要通过一个异步操作取出其中的内容
+    return response.json();
+  })
+  .then((data) => {
+    //经过response.json()处理过的数据
+    console.log(data);
+  })
+  .catch((err) => {
+    console.log(err);
+  });
+
+/* 下面使用async-await改写 :把代码封装成async异步函数 */
+async function getData() {
+  try {
+    //先获取Response对象
+    let response = await fetch(
+      "http://ajax-base-api-t.itheima.net/api/getbooks"
+    );
+    console.log(response);
+
+    //需要通过response.json() 取出response对象中的结果
+    let json = await response.json();
+    console.log(json);
+  } catch (error) {
+    console.log(error);
+  }
+}
+getData();
+

fetch 发送 POST 请求

//post发送:json格式
+async function add() {
+  let obj = {
+    bookname: "魔法书之如何学好前端",
+    author: "阿利亚",
+    publisher: "格兰芬多",
+  };
+  let res = await fetch("http://ajax-base-api-t.itheima.net/api/addbook", {
+    method: "post",
+    headers: {
+      "Content-Type": "application/json",
+    },
+    body: JSON.stringify(obj),
+  });
+  let json = await res.json();
+  console.log(json);
+}
+add();
+

fetch 封装

//封装http函数(fetch请求)
+async function http(obj) {
+  let { method, url, params, data } = obj;
+  let res;
+  //params需要处理-->转化成key1=value1&key2=value2的形式
+  if (params) {
+    //固定写法:将params参数拼接成参数字符串
+    let str = new URLSearchParams(params).toString();
+    //拼接到url上去
+    url += "?" + str;
+  }
+
+  //data需要处理-->如果有data,此时需要写完整的代码headers...
+  if (data) {
+    res = await fetch(url, {
+      method: method,
+      headers: {
+        "Content-Type": "application/json",
+      },
+      body: JSON.stringify(data),
+    });
+  } else {
+    res = await fetch(url);
+  }
+  //把获取的结果经过处理之后,返回出去
+  return res.json();
+}
+
+//测试代码1
+async function fn1() {
+  //通过http函数发送get请求获取数据
+  let result = await http({
+    method: "get",
+    url: "http://ajax-base-api-t.itheima.net/api/getbooks",
+    params: {
+      id: 2,
+    },
+  });
+  console.log(result);
+}
+fn1();
+
+//测试代码2
+async function fn2() {
+  //通过http函数发送post请求获取数据
+  let result = await http({
+    method: "post",
+    url: "http://ajax-base-api-t.itheima.net/api/addbook",
+    data: {
+      bookname: "如何高新就业",
+      author: "大佬",
+      publisher: "哈哈出版社",
+    },
+  });
+  console.log(result);
+}
+fn2();
+
`,32),d={href:"https://developer.mozilla.org/zh-CN/docs/Web/API/Fetch_API",target:"_blank",rel:"noopener noreferrer"},v=n("h2",{id:"axiso-请求库",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#axiso-请求库"},[n("span",null,"axiso 请求库")])],-1),m={href:"https://www.axios-http.cn/",target:"_blank",rel:"noopener noreferrer"};function b(g,h){const a=o("ExternalLinkIcon");return c(),l("div",null,[u,n("p",null,[s("这里还有一些实例方法和事件,在实际遇到了可以去"),n("a",r,[s("XMLHttpRequest"),t(a)]),s("上进行查看")]),k,n("p",null,[n("strong",null,[s("更多的详情,请求响应和相关方法可以查看"),n("a",d,[s("MDN-Fetch"),t(a)])])]),v,n("p",null,[s("由于项目中经常会用这个库来进行请求,有问题一般是去查看"),n("a",m,[s("Axiso文档"),t(a)])])])}const y=e(i,[["render",b],["__file","AJAX.html.vue"]]),q=JSON.parse('{"path":"/base/AJAX.html","title":"异步请求 AJAX","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"XMLHttpRequest","slug":"xmlhttprequest","link":"#xmlhttprequest","children":[]},{"level":2,"title":"fetch","slug":"fetch","link":"#fetch","children":[]},{"level":2,"title":"axiso 请求库","slug":"axiso-请求库","link":"#axiso-请求库","children":[]}],"filePathRelative":"base/AJAX.md","git":{"createdTime":1715780535000,"updatedTime":1716560799000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":2}]},"readingTime":{"minutes":7.08,"words":2123}}');export{y as comp,q as data}; diff --git a/assets/CSRF.html-KRQIao9r.js b/assets/CSRF.html-KRQIao9r.js new file mode 100644 index 0000000..cd681ef --- /dev/null +++ b/assets/CSRF.html-KRQIao9r.js @@ -0,0 +1,54 @@ +import{_ as o,r as p,o as i,c as l,a as n,b as a,d as e,e as t}from"./app-B-BkP2m_.js";const c={},r=n("h1",{id:"csrf-如何防御csrf攻击",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#csrf-如何防御csrf攻击"},[n("span",null,"CSRF,如何防御CSRF攻击")])],-1),u={href:"https://owasp.org/www-community/attacks/csrf",target:"_blank",rel:"noopener noreferrer"},k=t(`

一个典型的CSRF攻击有着如下的流程

几种常见的攻击类型

GET类型的CSRF GET类型的CSRF利用非常简单,只需要一个HTTP请求,一般会这样利用:

![](http://bank.example/withdraw?account=xiaoming&amount=10000&for=hacker)
+

在受害者访问含有这个img的页面后,浏览器会自动向http://bank.example/withdraw?account=xiaoming&amount=10000&for=hacker发出一次HTTP请求。bank.example就会收到包含受害者登录信息的一次跨域请求。 POST类型的CSRF 这种类型的CSRF利用起来通常使用的是一个自动提交的表单,如:

<form action="http://bank.example/withdraw" method=POST>
+    <input type="hidden" name="account" value="xiaoming" />
+    <input type="hidden" name="amount" value="10000" />
+    <input type="hidden" name="for" value="hacker" />
+</form>
+<script> document.forms[0].submit(); </script>
+

访问该页面后,表单会自动提交,相当于模拟用户完成了一次POST操作。 POST类型的攻击通常比GET要求更加严格一点,但仍并不复杂。任何个人网站、博客,被黑客上传页面的网站都有可能是发起攻击的来源,后端接口不能将安全寄托在仅允许POST上面。 链接类型的CSRF 链接类型的CSRF并不常见,比起其他两种用户打开页面就中招的情况,这种需要用户点击链接才会触发。这种类型通常是在论坛中发布的图片中嵌入恶意链接,或者以广告的形式诱导用户中招,攻击者通常会以比较夸张的词语诱骗用户点击,例如:

<a href="http://test.com/csrf/withdraw.php?amount=1000&for=hacker" taget="_blank">
+  重磅消息!!
+  <a/>
+

由于之前用户登录了信任的网站A,并且保存登录状态,只要用户主动访问上面的这个PHP页面,则表示攻击成功。

如何进行防御

CSRF通常从第三方网站发起,被攻击的网站无法防止攻击发生,只能通过增强自己网站针对CSRF的防护能力来提升安全性。 上文中讲了CSRF的两个特点:

针对这两点,我们可以专门制定防护策略,如下:

同源检测

既然CSRF大多来自第三方网站,那么我们就直接禁止外域(或者不受信任的域名)对我们发起请求。 那么问题来了,我们如何判断请求是否来自外域呢? 在HTTP协议中,每一个异步请求都会携带两个Header,用于标记来源域名:

这两个Header在浏览器发起请求时,大多数情况会自动带上,并且不能由前端自定义内容。 服务器可以通过解析这两个Header中的域名,确定请求的来源域。

使用Origin Header确定来源域名

在部分与CSRF有关的请求中,请求的Header中会携带Origin字段。字段内包含请求的域名(不包含path及query)。 如果Origin存在,那么直接使用Origin中的字段确认来源域名就可以。 但是Origin在以下两种情况下并不存在:

`,19),d=n("strong",null,"IE11同源策略:",-1),m={href:"https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy#IE_Exceptions",target:"_blank",rel:"noopener noreferrer"},h=n("li",null,[n("strong",null,"302重定向:"),a(" 在302重定向之后Origin不包含在重定向的请求中,因为Origin可能会被认为是其他来源的敏感信息。对于302重定向的情况来说都是定向到新的服务器上的URL,因此浏览器不想将Origin泄漏到新的服务器上。")],-1),v=t(`

使用Referer Header确定来源域名

根据HTTP协议,在HTTP头中有一个字段叫Referer,记录了该HTTP请求的来源地址。 对于Ajax请求,图片和script等资源请求,Referer为发起请求的页面地址。对于页面跳转,Referer为打开页面历史记录的前一个页面地址。因此我们使用Referer中链接的Origin部分可以得知请求的来源域名。 这种方法并非万无一失,Referer的值是由浏览器提供的,虽然HTTP协议上有明确的要求,但是每个浏览器对于Referer的具体实现可能有差别,并不能保证浏览器自身没有安全漏洞。使用验证 Referer 值的方法,就是把安全性都依赖于第三方(即浏览器)来保障,从理论上来讲,这样并不是很安全。在部分情况下,攻击者可以隐藏,甚至修改自己请求的Referer。 2014年,W3C的Web应用安全工作组发布了Referrer Policy草案,对浏览器该如何发送Referer做了详细的规定。截止现在新版浏览器大部分已经支持了这份草案,我们终于可以灵活地控制自己网站的Referer策略了。新版的Referrer Policy规定了五种Referer策略:No Referrer、No Referrer When Downgrade、Origin Only、Origin When Cross-origin、和 Unsafe URL。之前就存在的三种策略:never、default和always,在新标准里换了个名称。他们的对应关系如下:

策略名称属性值(新)属性值(旧)
No Referrerno-Referrernever
No Referrer When Downgradeno-Referrer-when-downgradedefault
Origin Only(same or strict) originorigin
Origin When Cross Origin(strict) origin-when-crossorigin-
Unsafe URLunsafe-urlalways

根据上面的表格因此需要把Referrer Policy的策略设置成same-origin,对于同源的链接和引用,会发送Referer,referer值为Host不带Path;跨域访问则不携带Referer。例如:aaa.com引用bbb.com的资源,不会发送Referer。 设置Referrer Policy的方法有三种:

  1. 在CSP设置
  2. 页面头部增加meta标签
  3. a标签增加referrerpolicy属性

上面说的这些比较多,但我们可以知道一个问题:攻击者可以在自己的请求中隐藏Referer。如果攻击者将自己的请求这样填写:

![](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2018b/ff0cdbee.example/withdraw?amount=10000&for=hacker)
+

那么这个请求发起的攻击将不携带Referer。 另外在以下情况下Referer没有或者不可信:

  1. IE6、7下使用window.location.href=url进行界面的跳转,会丢失Referer。
  2. IE6、7下使用window.open,也会缺失Referer。
  3. HTTPS页面跳转到HTTP页面,所有浏览器Referer都丢失。
  4. 点击Flash上到达另外一个网站的时候,Referer的情况就比较杂乱,不太可信。

无法确认来源域名情况

当Origin和Referer头文件不存在时该怎么办?如果Origin和Referer都不存在,建议直接进行阻止,特别是如果您没有使用随机CSRF Token(参考下方)作为第二次检查。

如何阻止外域请求

通过Header的验证,我们可以知道发起请求的来源域名,这些来源域名可能是网站本域,或者子域名,或者有授权的第三方域名,又或者来自不可信的未知域名。 我们已经知道了请求域名是否是来自不可信的域名,我们直接阻止掉这些的请求,就能防御CSRF攻击了吗? 且慢!当一个请求是页面请求(比如网站的主页),而来源是搜索引擎的链接(例如百度的搜索结果),也会被当成疑似CSRF攻击。所以在判断的时候需要过滤掉页面请求情况,通常Header符合以下情况:

Accept: text/html
+Method: GET
+

但相应的,页面请求就暴露在了CSRF的攻击范围之中。如果你的网站中,在页面的GET请求中对当前用户做了什么操作的话,防范就失效了。 例如,下面的页面请求:

GET https://example.com/addComment?comment=XXX&dest=orderId
+

注:这种严格来说并不一定存在CSRF攻击的风险,但仍然有很多网站经常把主文档GET请求挂上参数来实现产品功能,但是这样做对于自身来说是存在安全风险的。 另外,前面说过,CSRF大多数情况下来自第三方域名,但并不能排除本域发起。如果攻击者有权限在本域发布评论(含链接、图片等,统称UGC),那么它可以直接在本域发起攻击,这种情况下同源策略无法达到防护的作用。 综上所述:同源验证是一个相对简单的防范方法,能够防范绝大多数的CSRF攻击。但这并不是万无一失的,对于安全性要求较高,或者有较多用户输入内容的网站,我们就要对关键的接口做额外的防护措施。

CSRF Token

前面讲到CSRF的另一个特征是,攻击者无法直接窃取到用户的信息(Cookie,Header,网站内容等),仅仅是冒用Cookie中的信息。 而CSRF攻击之所以能够成功,是因为服务器误把攻击者发送的请求当成了用户自己的请求。那么我们可以要求所有的用户请求都携带一个CSRF攻击者无法获取到的Token。服务器通过校验请求是否携带正确的Token,来把正常的请求和攻击的请求区分开,也可以防范CSRF的攻击。

原理

`,20),g=n("strong",null,"1. 将CSRF Token输出到页面中",-1),f=n("strong",null,"2. 页面提交的请求携带这个Token",-1),S={href:"http://url/?csrftoken=tokenvalue%E3%80%82",target:"_blank",rel:"noopener noreferrer"},C=t(`
<input type=”hidden” name=”csrftoken” value=”tokenvalue”/>
+

这样,就把Token以参数的形式加入请求了。 3. 服务器验证Token是否正确 当用户从客户端得到了Token,再次提交给服务器的时候,服务器需要判断Token的有效性,验证过程是先解密Token,对比加密字符串以及时间戳,如果加密字符串一致且时间未过期,那么这个Token就是有效的。 这种方法要比之前检查Referer或者Origin要安全一些,Token可以在产生并放于Session之中,然后在每次请求时把Token从Session中拿出,与请求中的Token进行比对,但这种方法的比较麻烦的在于如何把Token以参数的形式加入请求。 下面将以Java为例,介绍一些CSRF Token的服务端校验逻辑,代码如下:

HttpServletRequest req = (HttpServletRequest)request; 
+HttpSession s = req.getSession(); 
+ 
+// 从 session 中得到 csrftoken 属性
+String sToken = (String)s.getAttribute("csrftoken"); 
+if(sToken == null){ 
+   // 产生新的 token 放入 session 中
+   sToken = generateToken(); 
+   s.setAttribute("csrftoken",sToken); 
+   chain.doFilter(request, response); 
+} else{ 
+   // 从 HTTP 头中取得 csrftoken 
+   String xhrToken = req.getHeader(“csrftoken”); 
+   // 从请求参数中取得 csrftoken 
+   String pToken = req.getParameter(“csrftoken”); 
+   if(sToken != null && xhrToken != null && sToken.equals(xhrToken)){ 
+       chain.doFilter(request, response); 
+   }else if(sToken != null && pToken != null && sToken.equals(pToken)){ 
+       chain.doFilter(request, response); 
+   }else{ 
+       request.getRequestDispatcher(“error.jsp”).forward(request,response); 
+   } 
+}
+
`,3),b={href:"https://www.ibm.com/developerworks/cn/web/1102_niugang_csrf/",target:"_blank",rel:"noopener noreferrer"},T=t(`

分布式校验

在大型网站中,使用Session存储CSRF Token会带来很大的压力。访问单台服务器session是同一个。但是现在的大型网站中,我们的服务器通常不止一台,可能是几十台甚至几百台之多,甚至多个机房都可能在不同的省份,用户发起的HTTP请求通常要经过像Ngnix之类的负载均衡器之后,再路由到具体的服务器上,由于Session默认存储在单机服务器内存中,因此在分布式环境下同一个用户发送的多次HTTP请求可能会先后落到不同的服务器上,导致后面发起的HTTP请求无法拿到之前的HTTP请求存储在服务器中的Session数据,从而使得Session机制在分布式环境下失效,因此在分布式集群中CSRF Token需要存储在Redis之类的公共存储空间。 由于使用Session存储,读取和验证CSRF Token会引起比较大的复杂度和性能问题,目前很多网站采用Encrypted Token Pattern方式。这种方法的Token是一个计算出来的结果,而非随机生成的字符串。这样在校验时无需再去读取存储的Token,只用再次计算一次即可。 这种Token的值通常是使用UserID、时间戳和随机数,通过加密的方法生成。这样既可以保证分布式服务的Token一致,又能保证Token不容易被破解。 在token解密成功之后,服务器可以访问解析值,Token中包含的UserID和时间戳将会被拿来被验证有效性,将UserID与当前登录的UserID进行比较,并将时间戳与当前时间进行比较。

总结

Token是一个比较有效的CSRF防护方法,只要页面没有XSS漏洞泄露Token,那么接口的CSRF攻击就无法成功。 但是此方法的实现比较复杂,需要给每一个页面都写入Token(前端无法使用纯静态页面),每一个Form及Ajax请求都携带这个Token,后端对每一个接口都进行校验,并保证页面Token及请求Token一致。这就使得这个防护策略不能在通用的拦截上统一拦截处理,而需要每一个页面和接口都添加对应的输出和校验。这种方法工作量巨大,且有可能遗漏。 验证码和密码其实也可以起到CSRF Token的作用哦,而且更安全。为什么很多银行等网站会要求已经登录的用户在转账时再次输入密码,现在是不是有一定道理了?

双重Cookie验证

在会话中存储CSRF Token比较繁琐,而且不能在通用的拦截上统一处理所有的接口。 那么另一种防御措施是使用双重提交Cookie。利用CSRF攻击不能获取到用户Cookie的特点,我们可以要求Ajax和表单请求携带一个Cookie中的值。 双重Cookie采用以下流程:

此方法相对于CSRF Token就简单了许多。可以直接通过前后端拦截的的方法自动化实现。后端校验也更加方便,只需进行请求中字段的对比,而不需要再进行查询和存储Token。 当然,此方法并没有大规模应用,其在大型网站上的安全性还是没有CSRF Token高,原因我们举例进行说明。 由于任何跨域都会导致前端无法获取Cookie中的字段(包括子域名之间),于是发生了如下情况:

总结:

用双重Cookie防御CSRF的优点:

缺点:

Samesite Cookie属性

防止CSRF攻击的办法已经有上面的预防措施。为了从源头上解决这个问题,Google起草了一份草案来改进HTTP协议,那就是为Set-Cookie响应头新增Samesite属性,它用来标明这个 Cookie是个“同站 Cookie”,同站Cookie只能作为第一方Cookie,不能作为第三方Cookie,Samesite 有两个属性值,分别是 Strict 和 Lax,下面分别讲解:

Samesite=Strict

这种称为严格模式,表明这个 Cookie 在任何情况下都不可能作为第三方 Cookie,绝无例外。比如说 b.com 设置了如下 Cookie:

Set-Cookie: foo=1; Samesite=Strict
+Set-Cookie: bar=2; Samesite=Lax
+Set-Cookie: baz=3
+

我们在 a.com 下发起对 b.com 的任意请求,foo 这个 Cookie 都不会被包含在 Cookie 请求头中,但 bar 会。举个实际的例子就是,假如淘宝网站用来识别用户登录与否的 Cookie 被设置成了 Samesite=Strict,那么用户从百度搜索页面甚至天猫页面的链接点击进入淘宝后,淘宝都不会是登录状态,因为淘宝的服务器不会接受到那个 Cookie,其它网站发起的对淘宝的任意请求都不会带上那个 Cookie。

Samesite=Lax

这种称为宽松模式,比 Strict 放宽了点限制:假如这个请求是这种请求(改变了当前页面或者打开了新页面)且同时是个GET请求,则这个Cookie可以作为第三方Cookie。比如说 b.com设置了如下Cookie:

Set-Cookie: foo=1; Samesite=Strict
+Set-Cookie: bar=2; Samesite=Lax
+Set-Cookie: baz=3
+

当用户从 a.com 点击链接进入 b.com 时,foo 这个 Cookie 不会被包含在 Cookie 请求头中,但 bar 和 baz 会,也就是说用户在不同网站之间通过链接跳转是不受影响了。但假如这个请求是从 a.com 发起的对 b.com 的异步请求,或者页面跳转是通过表单的 post 提交触发的,则bar也不会发送。 生成Token放到Cookie中并且设置Cookie的Samesite,Java代码如下:

private void addTokenCookieAndHeader(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
+        //生成token
+        String sToken = this.generateToken();
+        //手动添加Cookie实现支持“Samesite=strict”
+        //Cookie添加双重验证
+        String CookieSpec = String.format("%s=%s; Path=%s; HttpOnly; Samesite=Strict", this.determineCookieName(httpRequest), sToken, httpRequest.getRequestURI());
+        httpResponse.addHeader("Set-Cookie", CookieSpec);
+        httpResponse.setHeader(CSRF_TOKEN_NAME, token);
+    }
+
`,25),R={href:"https://www.owasp.org/index.php/Cross-Site_Request_Forgery_%28CSRF%29_Prevention_Cheat_Sheet#Implementation_example",target:"_blank",rel:"noopener noreferrer"},x=n("strong",null,"OWASP Cross-Site_Request_Forgery #Implementation example",-1),q=t('

我们应该如何使用SamesiteCookie

如果SamesiteCookie被设置为Strict,浏览器在任何跨域请求中都不会携带Cookie,新标签重新打开也不携带,所以说CSRF攻击基本没有机会。 但是跳转子域名或者是新标签重新打开刚登陆的网站,之前的Cookie都不会存在。尤其是有登录的网站,那么我们新打开一个标签进入,或者跳转到子域名的网站,都需要重新登录。对于用户来讲,可能体验不会很好。 如果SamesiteCookie被设置为Lax,那么其他网站通过页面跳转过来的时候可以使用Cookie,可以保障外域连接打开页面时用户的登录状态。但相应的,其安全性也比较低。 另外一个问题是Samesite的兼容性不是很好,现阶段除了从新版Chrome和Firefox支持以外,Safari以及iOS Safari都还不支持,现阶段看来暂时还不能普及。 而且,SamesiteCookie目前有一个致命的缺陷:不支持子域。例如,种在topic.a.com下的Cookie,并不能使用a.com下种植的SamesiteCookie。这就导致了当我们网站有多个子域名时,不能使用SamesiteCookie在主域名存储用户登录信息。每个子域名都需要用户重新登录一次。 总之,SamesiteCookie是一个可能替代同源验证的方案,但目前还并不成熟,其应用场景有待观望。

防止网站被利用

前面所说的,都是被攻击的网站如何做好防护。而非防止攻击的发生,CSRF的攻击可以来自:

对于来自黑客自己的网站,我们无法防护。但对其他情况,那么如何防止自己的网站被利用成为攻击的源头呢?

',7);function F(w,_){const s=p("ExternalLinkIcon");return i(),l("div",null,[r,n("blockquote",null,[n("p",null,[a("跨站请求伪造(CSRF)是一种冒充受信任用户,向服务器发送非预期请求的攻击方式。攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。 当恶意网站、电子邮件、博客、即时消息或程序诱骗经过身份验证的用户的 Web 浏览器在受信任的站点上执行不需要的操作时,就会发生"),n("a",u,[a("跨站点请求伪造 (CSRF)"),e(s)]),a(" 攻击。如果目标用户已通过站点身份验证,则未受保护的目标站点无法区分合法的授权请求和伪造的经过身份验证的请求。 由于浏览器请求会自动包含所有 cookie,包括会话 cookie,因此除非使用适当的授权,否则此攻击会起作用,这意味着目标站点的质询-响应机制不会验证请求者的身份和权限。实际上,CSRF 攻击使目标系统在受害者不知情的情况下通过受害者的浏览器执行攻击者指定的功能(通常直到提交未经授权的操作之后)。 但是,成功的 CSRF 攻击只能利用易受攻击的应用程序暴露的功能和用户的权限。根据用户的凭据,攻击者可以转移资金、更改密码、进行未经授权的购买、提升目标帐户的权限或执行允许用户执行的任何操作。")])]),k,n("ul",null,[n("li",null,[d,a(" IE 11 不会在跨站CORS请求上添加Origin标头,Referer头将仍然是唯一的标识。最根本原因是因为IE 11对同源的定义和其他浏览器有不同,有两个主要的区别,可以参考"),n("a",m,[a("MDN Same-origin_policy#IE_Exceptions"),e(s)])]),h]),v,n("p",null,[a("CSRF Token的防护策略分为三个步骤: "),g,a(" 首先,用户打开页面的时候,服务器需要给这个用户生成一个Token,该Token通过加密算法对数据进行加密,一般Token都包括随机字符串和时间戳的组合,显然在提交时Token不能再放在Cookie中了,否则又会被攻击者冒用。因此,为了安全起见Token最好还是存在服务器的Session中,之后在每次页面加载时,使用JS遍历整个DOM树,对于DOM中所有的a和form标签后加入Token。这样可以解决大部分的请求,但是对于在页面加载之后动态生成的HTML代码,这种方法就没有作用,还需要程序员在编码时手动添加Token。 "),f,a(" 对于GET请求,Token将附在请求地址之后,这样URL 就变成 "),n("a",S,[a("http://url?csrftoken=tokenvalue。"),e(s)]),a(" 而对于 POST 请求来说,要在 form 的最后加上:")]),C,n("p",null,[a("代码源自"),n("a",b,[a("IBM developerworks CSRF"),e(s)]),a(" 这个Token的值必须是随机生成的,这样它就不会被攻击者猜到,考虑利用Java应用程序的java.security.SecureRandom类来生成足够长的随机标记,替代生成算法包括使用256位BASE64编码哈希,选择这种生成算法的开发人员必须确保在散列数据中使用随机性和唯一性来生成随机标识。通常,开发人员只需为当前会话生成一次Token。在初始生成此Token之后,该值将存储在会话中,并用于每个后续请求,直到会话过期。当最终用户发出请求时,服务器端必须验证请求中Token的存在性和有效性,与会话中找到的Token相比较。如果在请求中找不到Token,或者提供的值与会话中的值不匹配,则应中止请求,应重置Token并将事件记录为正在进行的潜在CSRF攻击。")]),T,n("p",null,[a("代码源自"),n("a",R,[x,e(s)])]),q])}const y=o(c,[["render",F],["__file","CSRF.html.vue"]]),P=JSON.parse('{"path":"/interview/CSRF.html","title":"CSRF,如何防御CSRF攻击","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"一个典型的CSRF攻击有着如下的流程","slug":"一个典型的csrf攻击有着如下的流程","link":"#一个典型的csrf攻击有着如下的流程","children":[]},{"level":2,"title":"几种常见的攻击类型","slug":"几种常见的攻击类型","link":"#几种常见的攻击类型","children":[]},{"level":2,"title":"如何进行防御","slug":"如何进行防御","link":"#如何进行防御","children":[{"level":3,"title":"同源检测","slug":"同源检测","link":"#同源检测","children":[]},{"level":3,"title":"CSRF Token","slug":"csrf-token","link":"#csrf-token","children":[]},{"level":3,"title":"双重Cookie验证","slug":"双重cookie验证","link":"#双重cookie验证","children":[]},{"level":3,"title":"Samesite Cookie属性","slug":"samesite-cookie属性","link":"#samesite-cookie属性","children":[]}]},{"level":2,"title":"防止网站被利用","slug":"防止网站被利用","link":"#防止网站被利用","children":[]}],"filePathRelative":"interview/CSRF.md","git":{"createdTime":1715780535000,"updatedTime":1715780535000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":1}]},"readingTime":{"minutes":21.77,"words":6530}}');export{y as comp,P as data}; diff --git a/assets/CSS3.html-CII7l4IS.js b/assets/CSS3.html-CII7l4IS.js new file mode 100644 index 0000000..3fbbfea --- /dev/null +++ b/assets/CSS3.html-CII7l4IS.js @@ -0,0 +1,328 @@ +import{_ as t,r as p,o as e,c,a as n,b as o,d as l,e as s}from"./app-B-BkP2m_.js";const u={},i=n("h1",{id:"css3",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#css3"},[n("span",null,"CSS3")])],-1),k=n("h2",{id:"grid-布局",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#grid-布局"},[n("span",null,"Grid 布局")])],-1),r=n("strong",null,"参考文章",-1),d=n("br",null,null,-1),v={href:"https://juejin.cn/post/6854573220306255880",target:"_blank",rel:"noopener noreferrer"},g=s(`
  1. grid-template-columns grid-gap repeat() minmax() auto-fill fr
<!DOCTYPE html>
+<html lang="en-us">
+  <head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width">
+    <title>CSS Grid starting point</title>
+    <style>
+        body {
+            width: 90%;
+            max-width: 900px;
+            margin: 2em auto;
+            font: .9em/1.2 Arial, Helvetica, sans-serif;
+        }
+
+        .container > div {
+            border-radius: 5px;
+            padding: 10px;
+            background-color: rgb(207,232,220);
+            border: 2px solid rgb(79,185,227);
+        }
+        /* 补充 */
+        .container {
+            display: grid;
+            /* 加三个宽度为200px的列。 */
+            grid-template-columns: 200px 200px 200px ;
+            /* 除了长度和百分比,我们也可以用fr这个单位来灵活地定义网格的行与列的大小。
+            fr这个单位表示了可用空间的一个比例 */
+            grid-template-columns: 2fr 1fr 1fr;
+            /* 使用 grid-column-gap (en-US) 属性来定义列间隙;使用 grid-row-gap (en-US) 来定义行间隙;
+            使用 grid-gap (en-US) 可以同时设定两者。
+            !!!!注意:间隙距离可以用任何长度单位包括百分比来表示,但不能使用fr单位 */
+            grid-gap: 20px 10px;/* 行 列 */
+            gap: 20px 10px;/* 两个一样 */
+            /* 使用repeat来重复构建具有某些宽度配置的某些列
+           repeat(3, 1fr):得到了 3 个1fr的列 
+           repeat(2, 2fr 1fr): 相当于填入了2fr 1fr 2fr 1fr*/
+            grid-template-columns: repeat(3, 1fr);
+            /* 隐式网格中生成的行/列大小是参数默认是auto,大小会根据放入的内容自动调整。
+            当然,你也可以使用grid-auto-rows和grid-auto-columns属性手动设定隐式网格的大小。
+            下面的例子将grid-auto-rows设为了100px,然后你可以看到那些隐式网格中的行
+            (因为这个例子里没有设定grid-template-rows,因此,所有行都位于隐式网格内)现在都是 100 像素高了。
+            简单来说,隐式网格就是为了放显式网格放不下的元素,浏览器根据已经定义的显式网格自动生成的网格部分。 */
+            grid-auto-rows: 100px;
+            /* minmax 函数为一个行/列的尺寸设置了取值范围。比如设定为 minmax(100px, auto),那么尺寸就至少为 100 像素,
+            并且如果内容尺寸大于 100 像素则会根据内容自动调整。 */
+            grid-auto-rows: minmax(100px,auto);
+            /* 
+            自动使用多列填充
+              grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+            */
+        }
+    </style>
+  </head>
+
+<body>
+    <h1>Simple grid example</h1>
+    <div class="container">
+        <div>就能(´ڡ\`ლ)好吃的.∑(っ°Д°;)っ卧槽,不见了</div>
+        <div>Two</div>
+        <div>Three</div>
+        <div>Four Lorem ipsum dolor sit amet consectetur adipisicing elit. At doloribus error animi eius labore rerum quo saepe nihil veritatis! Necessitatibus, similique facere? Voluptatem eos consequatur tempora non alias. Repudiandae, nihil!</div>
+        <div>Five</div>
+        <div>Six</div>
+        <div>Seven Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequuntur blanditiis exercitationem eos veritatis officia, enim dolores nostrum minima iure, repellat eius, similique suscipit dolorem eaque? Porro dolores quidem consequuntur facilis!</div>
+    </div>
+</body>
+</html>
+
`,2),m=n("p",null,[n("button",{onclick:"location.href='/demo/grid-one.html'"},"查看页面")],-1),b=n("iframe",{id:"iframe",height:"350",width:"100%",frameborder:"0",allowfullscreen:"true",src:"/demo/grid-one.html"},null,-1),q=s(`
  1. grid-template-areas

grid-template-areas属性的使用规则如下:

<!DOCTYPE html>
+<html lang="en-us">
+  <head>
+    <meta charset="utf-8" />
+    <meta name="viewport" content="width=device-width" />
+    <title>CSS Grid - line-based placement starting point</title>
+    <style>
+      body {
+        width: 90%;
+        max-width: 900px;
+        margin: 2em auto;
+        font: 0.9em/1.2 Arial, Helvetica, sans-serif;
+      }
+
+      header,
+      footer {
+        border-radius: 5px;
+        padding: 10px;
+        background-color: rgb(207, 232, 220);
+        border: 2px solid rgb(79, 185, 227);
+      }
+
+      aside {
+        border-right: 1px solid #999;
+      }
+      .container {
+        display: grid;
+        grid-template-areas:
+          "header header"
+          "sidebar content"
+          "footer footer";
+        grid-template-columns: 1fr 3fr;
+        grid-gap: 20px;
+      }
+      /* 
+选择器(目标){
+  grid-area:区域名字;
+}
+*/
+      header {
+        grid-area: header;
+      }
+
+      article {
+        grid-area: content;
+      }
+
+      aside {
+        grid-area: sidebar;
+      }
+
+      footer {
+        grid-area: footer;
+      }
+    </style>
+  </head>
+
+  <body>
+    <div class="container">
+      <header>This is my lovely blog</header>
+      <article>
+        <h1>My article</h1>
+        <p>
+          Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras
+          porttitor imperdiet nunc, at ultricies tellus laoreet sit amet. Sed
+          auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet
+          orci vel, viverra egestas ligula. Curabitur vehicula tellus neque, ac
+          ornare ex malesuada et. In vitae convallis lacus. Aliquam erat
+          volutpat. Suspendisse ac imperdiet turpis. Aenean finibus sollicitudin
+          eros pharetra congue. Duis ornare egestas augue ut luctus. Proin
+          blandit quam nec lacus varius commodo et a urna. Ut id ornare felis,
+          eget fermentum sapien.
+        </p>
+
+        <p>
+          Nam vulputate diam nec tempor bibendum. Donec luctus augue eget
+          malesuada ultrices. Phasellus turpis est, posuere sit amet dapibus ut,
+          facilisis sed est. Nam id risus quis ante semper consectetur eget
+          aliquam lorem. Vivamus tristique elit dolor, sed pretium metus
+          suscipit vel. Mauris ultricies lectus sed lobortis finibus. Vivamus eu
+          urna eget velit cursus viverra quis vestibulum sem. Aliquam tincidunt
+          eget purus in interdum. Cum sociis natoque penatibus et magnis dis
+          parturient montes, nascetur ridiculus mus.
+        </p>
+      </article>
+      <aside>
+        <h2>Other things</h2>
+        <p>
+          Nam vulputate diam nec tempor bibendum. Donec luctus augue eget
+          malesuada ultrices. Phasellus turpis est, posuere sit amet dapibus ut,
+          facilisis sed est.
+        </p>
+      </aside>
+      <footer>Contact me@mysite.com</footer>
+    </div>
+  </body>
+</html>
+
`,4),h=n("p",null,[n("button",{onclick:"location.href='/demo/grid-template-areas.html'"},"查看页面")],-1),y=n("iframe",{height:"350",width:"100%",frameborder:"0",allowfullscreen:"true",src:"/demo/grid-template-areas.html"},null,-1),f=s(`
  1. grid-template
<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>grid-template</title>
+    <style>
+        body{
+            min-height: 100vh;
+            display: flex;
+            align-items: center;
+            justify-content:space-around;
+        }
+        .content{
+            width: 600px;
+            height: 400px;
+            border: 1px solid red;
+            display: grid;
+            gap: 10px;
+            /* 这个属性是所简写属性:grid-template-rows、grid-template-columns与grid-template-areas。 */
+            /* 下面这个是缩写!!! 最下面一行是  grid-template-columns*/
+            grid-template:
+            "a a a" 1fr
+            "b c c" 1fr
+            "b c c" 1fr /
+            1fr 1fr 1fr;
+           }
+        .a{
+            grid-area: a;
+            background-color: lime;
+        }
+        .b{
+            grid-area: b;
+            background-color: aqua;
+        }
+        .c{
+            grid-area: c;
+            background-color: blueviolet;
+        }
+
+        .wrap{
+            width: 600px;
+            height: 400px;
+            border: 1px solid red;
+            display: grid;
+            gap: 10px;
+            grid-template:100px auto / 100px 1fr 1fr;/* 先是规定行,后是规定列!!! */
+        }
+        .plot{
+            background-color: brown;
+        }
+    </style>
+</head>
+<body>
+    <div class="content">
+        <div class="a">小雨</div>
+        <div class="b">好哇好哇</div>
+        <div class="c">华为</div>
+    </div>
+    <div class="wrap">
+        <div class="plot">1</div>
+        <div class="plot">2</div>
+        <div class="plot">3</div>
+        <div class="plot">4</div>
+        <div class="plot">5</div>
+        <div class="plot">6</div>
+    </div>
+</body>
+</html>
+
`,2),x=n("p",null,[n("button",{onclick:"location.href='/demo/grid-template.html'"},"查看页面")],-1),_=n("iframe",{height:"350",width:"100%",frameborder:"0",allowfullscreen:"true",src:"/demo/grid-template.html"},null,-1),w=s(`
  1. grid-area grid-row grid-column
<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>grid-area</title>
+    <style>
+        body{
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            min-height: 100vh;
+            margin: 0;
+            padding: 0;
+        }
+        .content{
+            width:500px ;
+            height: 500px;
+            /* border: 1px salmon solid; */
+            position: relative;
+        }
+        .bg,.cover{
+            width: inherit;
+            height: inherit;
+            display: grid;
+            grid-template: repeat(5,1fr)/repeat(5,1fr);
+            gap: 5px 5px;
+            position: absolute;
+            z-index: 10;
+        }
+        .bg>.plot{
+            background-color: lawngreen;
+        }
+        .cover{
+            z-index: 20;
+        }
+        .cover>div{
+            border-radius: 50%;
+        }
+        .item1{
+            background-color: blue;
+            grid-row: 1/3;
+            grid-column: 1/3;
+        }
+        .item2{
+            background-color: palevioletred;
+        }
+        .item3{
+            background-color: yellow;
+            grid-area: 3/3/6/6;/* top / left / bottom / right */
+        }
+    </style>
+</head>
+<body>
+    <!-- 
+        CSS 属性 grid-area 是一种对于 grid-row-start (en-US)、grid-column-start (en-US)、grid-row-end (en-US) 和 grid-column-end (en-US) 的简写,
+        通过基线(line),跨度(span)或没有(自动)的网格放置在 grid row 中指定一个网格项的大小和位置,继而确定 grid area 的边界。
+     -->
+     <div class="content">
+        <div class="bg">
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+        </div>
+        <div class="cover">
+            <div class="item1"></div>
+            <div class="item2"></div>
+            <div class="item3"></div>
+        </div>
+     </div>
+</body>
+</html>
+
`,2),S=n("p",null,[n("button",{onclick:"location.href='/demo/grid-area.html'"},"查看页面")],-1),C=n("iframe",{height:"350",width:"100%",frameborder:"0",allowfullscreen:"true",src:"/demo/grid-area.html"},null,-1);function T(N,U){const a=p("ExternalLinkIcon");return e(),c("div",null,[i,k,n("p",null,[r,d,n("a",v,[o("最强大的 CSS 布局 —— Grid 布局"),l(a)])]),g,m,b,q,h,y,f,x,_,w,S,C])}const D=t(u,[["render",T],["__file","CSS3.html.vue"]]),E=JSON.parse('{"path":"/base/CSS3.html","title":"CSS3","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"Grid 布局","slug":"grid-布局","link":"#grid-布局","children":[]}],"filePathRelative":"base/CSS3.md","git":{"createdTime":1716306105000,"updatedTime":1716306105000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":1}]},"readingTime":{"minutes":0.89,"words":267}}');export{D as comp,E as data}; diff --git a/assets/Git.html-ECcs3y5K.js b/assets/Git.html-ECcs3y5K.js new file mode 100644 index 0000000..0c3fd31 --- /dev/null +++ b/assets/Git.html-ECcs3y5K.js @@ -0,0 +1,22 @@ +import{_ as a,r as n,o as i,c as o,a as t,b as s,d as r,e as c}from"./app-B-BkP2m_.js";const l={},d=t("h1",{id:"git",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#git"},[t("span",null,"Git")])],-1),g={href:"https://www.ruanyifeng.com/blog/2015/12/git-cheat-sheet.html",target:"_blank",rel:"noopener noreferrer"},h=c(`

Git合作开发场景

使用 stash 的一个场景

问题场景:
+甲和乙同时修改master分支代码。
+甲修改了一部分,在本地,未提交
+乙修改了一部分代码,提交到了远程
+甲如何更新到乙修改的代码,同时本地修改保留?
+
+解决:
+1、执行git stash #暂存这些变更
+2、git pull origin #拉取远程代码
+3、git stash pop #重新应用储藏的变更
+4、再次提交自己的代码到远程
+    git commit -a -m "提交说明"
+    git push origin master
+

使用 stash 的另一个场景

问题场景:
+甲同学在自己的分支上开发进行一半了。
+但是代码还不想进行提交(切换分支要清空工作区)。
+现在要修改别的分支问题的时候。
+
+1、git stash:保存开发到一半的代码
+2、git commit -m '修改问题'
+3、git stash pop:将代码追加到最新的提交之后
+
`,5);function m(p,u){const e=n("ExternalLinkIcon");return i(),o("div",null,[d,t("p",null,[t("a",g,[s("常用 Git 命令清单"),r(e)])]),h])}const x=a(l,[["render",m],["__file","Git.html.vue"]]),f=JSON.parse('{"path":"/computer/Git.html","title":"Git","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"Git合作开发场景","slug":"git合作开发场景","link":"#git合作开发场景","children":[]}],"filePathRelative":"computer/Git.md","git":{"createdTime":1715941349000,"updatedTime":1716560799000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":3}]},"readingTime":{"minutes":0.87,"words":261}}');export{x as comp,f as data}; diff --git "a/assets/JS\346\250\241\345\235\227\345\214\226\345\216\206\347\250\213.html-B50QRgEH.js" "b/assets/JS\346\250\241\345\235\227\345\214\226\345\216\206\347\250\213.html-B50QRgEH.js" new file mode 100644 index 0000000..d0dc2cd --- /dev/null +++ "b/assets/JS\346\250\241\345\235\227\345\214\226\345\216\206\347\250\213.html-B50QRgEH.js" @@ -0,0 +1,386 @@ +import{_ as p,r as e,o,c,a as n,b as s,d as l,e as a}from"./app-B-BkP2m_.js";const i={},u=a('

JS 模块化历程

JS模块化.png

模块化的历程

',3),k={href:"https://blog.csdn.net/hangao233/article/details/122868611",target:"_blank",rel:"noopener noreferrer"},d=a(`

全局的 function 模式

<!-- test.html -->
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Document</title>
+  </head>
+  <body>
+    <script src="./module.js"></script>
+    <script>
+      foo();
+      bar();
+
+      msg = "小雨呀";
+      foo();
+    </script>
+  </body>
+</html>
+
// module.js
+/* 全局函数模式:将不同的功能封装成不同的全局函数…… */
+let msg="xiaoyu"
+function foo(){
+    console.log("foo()",msg);
+}
+
+function bar(){
+    console.log("bar()",msg);
+}
+

Namespace 模式

<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Document</title>
+</head>
+<body>
+    <script src="./module.js"></script>
+    <script>
+        obj.foo()
+        obj.msg="小雨"
+        obj.foo()
+    </script>
+</body>
+</html>
+
// module.js
+/* namespace 模式:简单对象封装…… */
+let obj={
+    msg:"xiaoyu",
+    foo(){
+        console.log("foo()",this.msg);
+    }
+}
+

IIFE 模式

<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Document</title>
+</head>
+<body>
+    <script src="./index.js"></script>
+    <script>
+        util.foo()
+    </script>
+</body>
+</html>
+
// index.js
+/* IIFE模式:匿名函数自调用(闭包) */
+// IIFE模式
+
+let util=(function(){
+    let msg="xiaoyu";
+    function foo(){
+        console.log("foo()",msg);
+    }
+    var module={
+        foo
+    }
+    
+    return module
+})()
+

IIFE 增强模式

<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Document</title>
+</head>
+<body>
+    <script src="./index.js"></script>
+    <script>
+        module()
+    </script>
+</body>
+</html>
+
// index.js
+/* IIFE增强模式:引入依赖  这是现代模块化实现的基石 */
+
+(function(window,document){
+    let msg="xiaoyu";
+    function foo(){
+        console.log("foo()",msg);
+    }
+    window.module=foo
+    document.querySelector("body").style.backgroundColor="red"
+})(window,document)
+

AMD

NO-AMD

<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Document</title>
+</head>
+<body>
+    <!-- 注意顺序不能变化,这种依赖一开始就写好的!!! -->
+    <script src="./js/dataService.js"></script>
+    <script src="./js/alerter.js"></script>
+    <script src="./app.js"></script>
+</body>
+</html>
+
// js/dataService.js
+// 定义一个没有依赖的模块
+(function(){
+    let name="dataService.js"
+    function getName(){
+        return name;
+    }
+    window.dataService={getName}
+})(window)
+
// js/alerter.js
+//定义一个有依赖的模块
+(function(window,dataService){
+    let msg="alerter.js"
+    function showMsg(){
+        console.log(msg,dataService.getName());
+    }
+    window.alerter={showMsg}
+})(window,dataService)
+
// app.js
+(function(alerter){
+    alerter.showMsg()
+})(alerter)
+

require.js

<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Document</title>
+</head>
+<body>
+    <!-- 入口,运用!!! 这个require.js自己去下载-->
+    <script data-main="./js/main.js" src="./js/lib/require.js"></script>
+</body>
+</html> 
+
// js/main.js
+(function(){
+    //66666 注意这个配置!!!
+    requirejs.config({
+        baseUrl:"js/",//基本的路径,出发点在根目录下
+        paths:{//配置路径
+            alerter:"./modules/alerter",
+            dataService:"./modules/dataService"
+        }
+    })
+
+    require(["alerter"],function(alerter){
+        alerter.showMsg();
+    })
+})()
+
+// https://www.runoob.com/w3cnote/requirejs-tutorial-1.html
+
// js/modules/dataService.js
+// 定义没有依赖的模块
+
+define(function() {
+    // 'use strict';
+    let name="dataService.js"
+    function getName(){
+        return name;
+    }
+
+    //暴露模块
+    return {getName}
+});
+
// js/modules/alerter.js
+// 定义有依赖的模块
+define([
+    "dataService"
+], function(dataService) {
+    // 'use strict';
+    let msg="alerter.js";
+    function showMsg(){
+        console.log(msg,dataService.getName());
+    }
+
+    return {showMsg}
+});
+

CMD - sea.js

<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Document</title>
+</head>
+<body>
+    <script src="./js/libs/sea.js"></script>
+    <script>
+        seajs.use("./js/modules/main.js")
+    </script>
+</body>
+</html>
+
// ./js/modules/main.js
+//定义没有依赖的模块
+define(function(require){
+    let module1=require("./module1")
+    module1.foo()
+    let module4=require("./module4")
+    module4.fun2()
+})
+
// ./js/modules/module1.js
+//定义没有依赖的模块
+define(function(require,exports,module){
+    let msg="module1";
+    function foo(){
+        return msg;
+    }
+    //暴露模块
+    module.exports={foo}
+})
+
// ./js/modules/module4.js
+//定义有依赖的模块
+define(function(require,exports,module){
+    let msg="module4";
+    //  同步
+    let module2=require("./module2")
+    module2()
+    //异步引用
+    require.async("./module3.js",function(module3){
+        module3.fun()
+    })
+    function fun2(){
+        console.log(msg);
+    }
+    exports.fun2=fun2
+})
+
// module2.js
+//定义没有依赖的模块
+define(function(require,exports,module){
+    let msg="module2";
+    function bar(){
+        console.log(msg);
+    }
+    //暴露模块
+    module.exports=bar
+})
+
// module3.js
+//定义没有依赖的模块
+define(function(require,exports,module){
+    let msg="module3";
+    function fun(){
+        console.log(msg);
+    }
+    //暴露模块
+    exports.fun=fun
+})
+

CommonJS

{
+  "name": "commonjs-node",
+  "version": "1.0.0",
+  "description": "",
+  "main": "main.js",
+  "scripts": {
+    "test": "echo \\"Error: no test specified\\" && exit 1"
+  },
+  "author": "",
+  "license": "ISC"
+}
+
// main.js
+// 将其他的模块汇集到主模块
+let module1 =require("./modules/module1")
+let module2 =require("./modules/module2")
+let module3 =require("./modules/module3")
+
+module1.foo()
+module2()
+module3.bar()
+module3.foo()
+
// modules/module1.js
+// module.exports = value 暴露一个对象
+
+module.exports={
+    msg:"module1",
+    foo(){
+        console.log("foo()",this.msg);
+    }
+}
+
// modules/module2.js
+// 暴露一个函数   module.exports = function(){}
+
+module.exports=function(){
+    console.log("module2");
+}
+
// modules/module3.js
+// exports.xxx = value 
+exports.foo = function(){
+    console.log("foo() module3");
+}
+
+exports.bar = function(){
+    console.log("bar() module3");
+}
+

ES Module

{
+  "name": "es6",
+  "version": "1.0.0",
+  "description": "",
+  "main": "main.js",
+  "scripts": {
+    "test": "echo \\"Error: no test specified\\" && exit 1"
+  },
+  "keywords": [],
+  "author": "",
+  "license": "ISC",
+  "type": "module"
+}
+
<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Document</title>
+</head>
+<body>
+    <!-- ES6 注意路径哈  node 和 浏览器环境-->
+    <script type="module" src="./main.js"></script>
+</body>
+</html>
+
// main.js
+//注意js类型为module
+// 默认导出不带括号,其他带括号
+// 括号里面的名字得匹配引入的,括号外的可以变名字
+import Person,{name,age,sayName,obj} from './js/count.js'
+
+console.log(name,age,sayName(),obj);
+const p1=new Person('xiaoma',18)
+console.log(p1);
+
+import hh from "./js/test.js"
+hh()
+
// js/count.js
+// ES6模块功能主要由两个命令构成:export 和 import
+// export用于规定模块的对外接口 import用于输入其他模块提供的功能
+// 一个模块就是一个独立的文件
+export const name='zhnagsan'
+export const age=18
+export function sayName(){
+    return 'my name is 小马哥'
+}
+/* export{
+    name,
+    age,
+    sayName
+} */
+export const obj={
+    foo:'foo'
+}
+
+
+class Person{
+    constructor(name,age){
+        this.name=name;
+        this.age=age;
+    }
+    sayName(){
+        return this.name
+    }
+    sayAge(){
+        return this.age
+    }
+}
+// 默认导出
+export default Person
+
// js/test.js
+export default function(){
+    console.log("sb");
+}
+
`,42);function r(v,m){const t=e("ExternalLinkIcon");return o(),c("div",null,[u,n("blockquote",null,[n("p",null,[s("这个是面试过程中面试官经常问到的点,需要好好准备一下! 分享一个不错的博客链接 "),n("a",k,[s("模块化"),l(t)])])]),d])}const b=p(i,[["render",r],["__file","JS模块化历程.html.vue"]]),q=JSON.parse('{"path":"/base/JS%E6%A8%A1%E5%9D%97%E5%8C%96%E5%8E%86%E7%A8%8B.html","title":"JS 模块化历程","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"模块化的历程","slug":"模块化的历程","link":"#模块化的历程","children":[{"level":3,"title":"全局的 function 模式","slug":"全局的-function-模式","link":"#全局的-function-模式","children":[]},{"level":3,"title":"Namespace 模式","slug":"namespace-模式","link":"#namespace-模式","children":[]},{"level":3,"title":"IIFE 模式","slug":"iife-模式","link":"#iife-模式","children":[]},{"level":3,"title":"IIFE 增强模式","slug":"iife-增强模式","link":"#iife-增强模式","children":[]}]},{"level":2,"title":"AMD","slug":"amd","link":"#amd","children":[{"level":3,"title":"NO-AMD","slug":"no-amd","link":"#no-amd","children":[]},{"level":3,"title":"require.js","slug":"require-js","link":"#require-js","children":[]}]},{"level":2,"title":"CMD - sea.js","slug":"cmd-sea-js","link":"#cmd-sea-js","children":[]},{"level":2,"title":"CommonJS","slug":"commonjs","link":"#commonjs","children":[]},{"level":2,"title":"ES Module","slug":"es-module","link":"#es-module","children":[]}],"filePathRelative":"base/JS模块化历程.md","git":{"createdTime":1715780535000,"updatedTime":1715941349000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":2}]},"readingTime":{"minutes":4.11,"words":1232}}');export{b as comp,q as data}; diff --git a/assets/Linux.html-BPXSEsF-.js b/assets/Linux.html-BPXSEsF-.js new file mode 100644 index 0000000..8c6e2d5 --- /dev/null +++ b/assets/Linux.html-BPXSEsF-.js @@ -0,0 +1 @@ +import{_ as t,o as n,c as o,a as e}from"./app-B-BkP2m_.js";const a={},i=e("h1",{id:"linux",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#linux"},[e("span",null,"Linux")])],-1),c=e("p",null,"正在更新……",-1),s=[i,c];function r(l,u){return n(),o("div",null,s)}const d=t(a,[["render",r],["__file","Linux.html.vue"]]),_=JSON.parse('{"path":"/computer/Linux.html","title":"Linux","lang":"zh-CN","frontmatter":{},"headers":[],"filePathRelative":"computer/Linux.md","git":{"createdTime":1715780535000,"updatedTime":1715780535000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":1}]},"readingTime":{"minutes":0.02,"words":5}}');export{d as comp,_ as data}; diff --git a/assets/Performance.html-DvwCq2TN.js b/assets/Performance.html-DvwCq2TN.js new file mode 100644 index 0000000..1b95852 --- /dev/null +++ b/assets/Performance.html-DvwCq2TN.js @@ -0,0 +1,353 @@ +import{_ as e,r as o,o as c,c as l,a as n,b as s,d as t,e as p}from"./app-B-BkP2m_.js";const i={},u=n("h1",{id:"performance",tabindex:"-1"},[n("a",{class:"header-anchor",href:"#performance"},[n("span",null,"Performance")])],-1),r=n("blockquote",null,[n("p",null,[n("strong",null,"什么是前端性能优化?")]),n("p",null,[n("code",null,"前端性能"),s("是指⻚⾯信息加⼯(⽐如数据展现、动画、操作效率等)的效率。")]),n("p",null,[n("code",null,"优化"),s("是指借助相关技术⼿段提⾼这样的效率。")])],-1),k=n("strong",null,"文章参考",-1),d=n("br",null,null,-1),v={href:"https://zhuanlan.zhihu.com/p/105561186",target:"_blank",rel:"noopener noreferrer"},m=n("br",null,null,-1),g={href:"https://zhuanlan.zhihu.com/p/495649475",target:"_blank",rel:"noopener noreferrer"},b=p('

为什么前端性能如此重要?

我们知道,现在就是⼀个“流量为王”的时代,⼀个⽹站最重要的的就是⽤⼾,有了⽤⼾你才能有 业务,打⽐⽅,你是⼀个电商⽹站,那么你肯定希望你的⽤⼾越多越好,这样才会有更多的⼈去浏 览你的商品,从⽽在你的⽹站上花钱,买东西,这样你才能产⽣收益,但假如你的⽹站打开要⼗⼏ 秒,请求接⼝要⼗⼏秒,那⽤⼾还愿意等么?

看⼀下以下的⽤⼾体验图:

国外⼀些著名公司的调研:

所以说,做好性能优化,提⾼⽤⼾体验很重要!

⽹⻚性能指标及影响因素

Timing

⻚⾯运⾏的时间线(统计了从浏览器从⽹址开始导航到 window.onload 事件触发的⼀系列关键的时间点): 时间线

关于 Performance API

',10),f=n("code",null,"Performance API",-1),y={href:"https://developer.mozilla.org/zh-CN/docs/Web/API/Performance",target:"_blank",rel:"noopener noreferrer"},h=n("p",null,[n("strong",null,"常⽤ Performance API:"),n("br")],-1),q={href:"https://developer.mozilla.org/zh-CN/docs/Web/API/PerformanceTiming",target:"_blank",rel:"noopener noreferrer"},w=n("strong",null,"废弃",-1),j=n("img",{src:"https://img2.imgtp.com/2024/05/18/KA0L3QiR.jpg",alt:"Timing"},null,-1),x={href:"https://developer.mozilla.org/zh-CN/docs/Web/API/Performance/getEntries",target:"_blank",rel:"noopener noreferrer"},P={href:"https://developer.mozilla.org/en-US/docs/Web/API/Performance/getEntriesByType",target:"_blank",rel:"noopener noreferrer"},_=n("strong",null,"navigation(⻚⾯导航)、resource(资源加载)、paint(绘制指标) 等",-1),C=p(`
// ⻚⾯导航时间
+performance.getEntriesByType("navigation");
+// 静态资源
+performance.getEntriesByType("resource");
+// 绘制指标
+performance.getEntriesByType("paint");
+
+/*需要定时轮询, 才能持续获取性能指标*/
+
`,1),T={href:"https://developer.mozilla.org/en-US/docs/Web/API/Performance/getEntriesByName",target:"_blank",rel:"noopener noreferrer"},E=p(`
performance.getEntriesByName(
+  "https://i0.hdslb.com/bfs/svgnext/BDC/danmu_square_line/v1.json"
+);
+
+performance.getEntriesByName(
+  "https://cloud.tencent.com/developer/api/user/session"
+);
+
+/*需要定时轮询, 才能持续获取性能指标*/
+
`,1),S={href:"https://developer.mozilla.org/en-US/docs/Web/API/Performance/now",target:"_blank",rel:"noopener noreferrer"},B=n("code",null,"performance.timing.navigationStart",-1),I=p(`
console.log(performance.now());
+// 5483324.099999994
+
`,1),M={href:"https://developer.mozilla.org/en-US/docs/Web/API/PerformanceObserver",target:"_blank",rel:"noopener noreferrer"},F=n("code",null,"(观察者模式)推荐,主要⽤于监测性能度量事件",-1),R=p(`
/* 写法⼀ */
+//直接往 PerformanceObserver() ⼊参匿名回调函数,成功 new 了⼀个PerformanceObserver 类的,名为 observer 的对象
+var observer = new PerformanceObserver(function (list, obj) {
+  var entries = list.getEntries();
+  for (var i = 0; i < entries.length; i++) {
+    //处理“navigation”和“resource”事件
+  }
+});
+//调⽤ observer 对象的 observe() ⽅法
+observer.observe({ entryTypes: ["navigation", "resource"] });
+
+/* 写法⼆ */
+//预先声明回调函数 perf_observer
+function perf_observer(list, observer) {
+  //处理“navigation”事件
+}
+//再将其传⼊ PerformanceObserver(),成功 new 了⼀个 PerformanceObserver 类的,名为observer2 的对象
+var observer2 = new PerformanceObserver(perf_observer);
+//调⽤ observer2 对象的 observe() ⽅法
+observer2.observe({ entryTypes: ["navigation"] });
+

实例化 PerformanceObserver 对象,observe ⽅法的 entryTypes 主要性能类型有哪些?

console.log(PerformanceObserver.supportedEntryTypes);
+/*
+['element', 'event', 'first-input', 'largest-contentful-paint', 'layoutshift',
+'longtask', 'mark', 'measure', 'navigation', 'paint', 'resource',
+'visibility-state']
+*/
+

具体每个性能类型的含义:

类型描述
element元素加载时间,实例项是 PerformanceElementTiming 对象。
event事件延迟,实例项是 PerformanceEventTiming 对象。
first-input⽤⼾第⼀次与⽹站交互(即点击链接、点击按钮或使⽤⾃定义的 JavaScript 控件时)到浏览器实际能够响应该交互的时间,称之为 Firstinputdelay‒FID。
largest-contentful-paint屏幕上触发的最⼤绘制元素,实例项是 LargestContentfulPaint 对象。
layout-shift元素移动时候的布局稳定性,实例项是 LayoutShift 对象。
long-animation-frame⻓动画关键帧。
longtask⻓任务实例,归属于 PerformanceLongTaskTiming 对象。
mark⽤⼾⾃定义的性能标记。实例项是 PerformanceMark 对象。
measure⽤⼾⾃定义的性能测量。实例项是 PerformanceMeasure 对象。
navigation⻚⾯导航出去的时间,实例项是 PerformancePaintTiming 对象。
pain⻚⾯加载时内容渲染的关键时刻(第⼀次绘制,第⼀次有内容的绘制,实例项是 PerformancePaintTiming 对象。
resource⻚⾯中资源的加载时间信息,实例项是 PerformanceResourceTiming 对象。
visibility-state⻚⾯可⻅性状态更改的时间,即选项卡何时从前台更改为后台,反之亦然。实例项是 VisibilityStateEntry 对象。
soft-navigation-

用户为导向性能指标介绍

piant

⾸次绘制(First Paint)和⾸次内容绘制(First Contentful Paint)

⾸次绘制(FP)和⾸次内容绘制(FCP)。在浏览器导航并渲染出像素点后,这些性能指标点⽴即被标记。 这些点对于⽤⼾⽽⾔⼗分重要,直乎感官体验!
⾸次绘制(FP),⾸次渲染的时间点。FP 和 FCP 有点像,但 FP ⼀定先于 FCP 发⽣,例如⼀个⻚⾯加载时,第⼀个 DOM 还没绘制完成,但是可能这时⻚⾯的背景颜⾊已经出来了,这时 FP 指标就被记录下来了。⽽ FCP 会在⻚⾯绘制完第⼀个 DOM 内容后记录。
⾸次内容绘制(FCP),⾸次内容绘制的时间,指⻚⾯从开始加载到⻚⾯内容的任何部分在屏幕上完成渲染的时间。

/* PerformanceObserver监控 */
+const observer = new PerformanceObserver((list) => {
+  const entries = list.getEntries();
+  entries.forEach((entry) => {
+    if (entry.name === "first-paint") {
+      console.log("FP(⾸次绘制):", entry.startTime);
+    } else if (entry.name === "first-contentful-paint") {
+      console.log("FCP(⾸次内容绘制):", entry.startTime);
+    }
+  });
+});
+observer.observe({ entryTypes: ["paint"] });
+
+/* performance.getEntriesByName*/
+console.log(
+  "FP(⾸次绘制):" + performance.getEntriesByName("first-paint")[0].startTime
+);
+console.log(
+  "FCP(⾸次内容绘制):" +
+    performance.getEntriesByName("first-contentful-paint")[0].startTime
+);
+

⾸次有效绘制(First Meaningful Paint)

有效内容,这种⼀般很难清晰地界定哪些元素的加载是「有⽤」的(因此⽬前尚⽆规范),但对于开发者他们⾃⼰⽽⾔,他们更知道⻚⾯的哪些部分对于⽤⼾⽽⾔是最为有⽤的,所以这样的衡量标准更多的时候是掌握在开发者⼿上!

const observer = new PerformanceObserver((list) => {
+  const entries = list.getEntries();
+  entries.forEach((entry) => {
+    if (entry.name === "https://xxxxxx.xxx.jpg") {
+      console.log(entry.startTime);
+    }
+  });
+});
+observer.observe({ entryTypes: ["resource"] }); // 可以是图⽚、某个Dom元素
+

可交互时间(TTI

指标测量⻚⾯从开始加载(FCP)到主要⼦资源完成渲染,并能够快速、可靠地响应⽤⼾输⼊所需的时间。阻塞会影响正常可交互的时间,浏览器主线程⼀次只能处理⼀个任务,如果主线程⻓时间被占⽤,那么可交互时间也会变⻓,所以更多的 TTI 都是发⽣在主线程处于空闲的时间点
良好的TTI应该控制在 5 秒以内。
测量 TTI 的最佳⽅法是在⽹站上运⾏ Lighthouse 性能审核

console.log(performance.timing.domInteractive); // 可交互时间点
+

⻓任务(Long Task)

浏览器主线程⼀次只能处理⼀个任务。 某些情况下,⼀些任务将可能会花费很⻓的时间来执⾏,持续占⽤主进程资源,如果这种情况发⽣了,主线程阻塞,剩下的任务只能在队列中等待。
⽤⼾所感知到的可能是输⼊的延迟,或者是哐当⼀下全部出现。这些是当今⽹⻚糟糕体验的主要来源之⼀。
Long Tasks API 认为任何超过 50 毫秒的任务(Task)都可能存在潜在的问题,并将这些任务相关信息回调给给前端。
把 long task 时间定义为 50ms 的主要理论依据是 Chrome 提出的 RAIL 模型,RAIL 认为事件响应应该在 100ms 以内,滚动和动画处理应该在 16ms 以内,才能保证好的⽤⼾体验,⽽如果⼀个 task 执⾏超过 50ms,则很有可能让体验达不到 RAIL 的标准,故我们需要重点关注执⾏时间超过 50ms 的任务。

const observer = new PerformanceObserver((list) => {
+  const entries = list.getEntries();
+  entries.forEach((entry) => {
+    console.log("Long Task(⻓任务):", entry);
+  });
+});
+observer.observe({ entryTypes: ["longtask"] });
+

核心网页指标

target

Lighthouse-知名测评⼯具LightHouse

常⻅优化⼿段

异步加载

说起异步加载,我们需要先了解⼀下什么是同步加载?

// 默认就是同步加载
+<script src="http://abc.com/script.js"></script>
+

⼏种常⻅的异步加载脚本⽅式:

async 和 defer
在 JavaScript 脚本增加 async 或者 defer 属性

// ⾯试经常问: script标签的defer和async的区别? //
+defer要等到html解析完成之后执⾏脚本
+<script src="main.js" defer></script>
+// async异步加载脚本后便会执⾏脚本
+<script src="main.js" async></script>
+

动态添加 script 标签

// js代码中动态添加script标签,并将其插⼊⻚⾯
+const script = document.createElement("script");
+script.src = "a.js";
+document.head.appendChild(script);
+

通过 XHR 异步加载 js

// ⾯试经常问: 谈谈JS中的 XMLHttpRequest 对象的理解?
+var xhr = new XMLHttpRequest();
+/*
+第⼀个参数是请求类型
+第⼆个参数是请求的URL
+第三个参数是是否为异步请求
+*/
+xhr.open("get", "/getUser", true); // true代表我们需要异步加载该脚本
+xhr.setRequestHeader("testHeader", "1111"); // ⾃定义Header
+xhr.send(null); // 参数为请求主体发送的数据,为必填项,当不需要发送数据时,使⽤null
+xhr.onreadyStateChange = function () {
+  if (xhr.readystate === 4) {
+    // ⾯试经常问: 说出你知道的哪些HTTP状态码?
+    if (xhr.status === 304 || (xhr.status >= 200 && xhr.status < 300)) {
+      console.log("成功, result: ", xhr.responseText);
+    } else {
+      console.log("错误, errCode:", xhr.status);
+    }
+  }
+};
+

按需打包与按需加载

随着 Webpack 等构建⼯具的能⼒越来越强,开发者在构建阶段可以随⼼所欲地打造项⽬流程,与此同 时按需加载和按需打包的技术曝光度也越来越⾼,甚⾄决定着⼯程化构建的结果,直接影响应⽤的性 能优化。

两者的概念:

按需打包
按需打包⼀般通过两种⽅法来实现:

  1. 使⽤ ES Module ⽀持的 Tree Shaking ⽅案,使⽤构建⼯具时候完成按需打包。 我们看⼀下这种场景:
import { Button } from "antd";
+// 假设我们的业务使⽤了Button组件,同时该组件库没有提供ES Module版本,
+// 那么这样的引⽤会导致最终打包的代码是所有antd导出的内容,这样会⼤⼤增加代码的体积
+
+// 但是如果我们组件库提供了ES Module版本(静态分析能⼒),并且开启了Tree Shaking功能,
+// 那么我们就可以通过“摇树”特性
+// 将不会被使⽤的代码在构建阶段移除。
+

正确使⽤ Tree Shaking 的姿势:
antd 组件库

// package.json
+{
+    // ...
+    "main": "lib/index.js", // 暴露CommonJS规范代码lib/index.js
+    "module": "es/index.js", // ⾮package.json标准字段,打包⼯具专⽤字段,指定符合ESM规范的⼊⼝⽂件
+    // 副作⽤配置字段,告诉打包⼯具遇到sideEffects匹配到的资源,均为⽆副作⽤的模块呢?
+    "sideEffects": [
+        "*.css",
+        " expample.js"
+    ],
+}
+
// 啥叫作副作⽤模块
+// expample.js
+const b = 2;
+export const a = 1;
+console.log(b);
+

项⽬:
Tree Shaking ⼀般与 Babel 搭配使⽤,需要在项⽬⾥⾯配置 Babel,因为 Babel 默认会把 ESM 规范打包 成 CommonJs 代码,所以需要通过配置 babel-preset-env#moudles 编译降级

production: {
+    presets: [
+        '@babel/preset-env',
+        {
+            modules: false
+        }
+    ]
+}
+

webpack4.0 以上在 mode 为 production 的时候会⾃动开启 Tree Shaking,实际就是依赖了、UglifyJS 等压缩插件,默认配置

const config = {
+    mode: 'production',
+    optimization: {
+        // 三类标记:
+        // used export: 被使⽤过的export会这样标记
+        // unused ha by rmony export: 没有被使⽤过的export被这样标记
+        // harmony import: 所有import会被这样标记
+        usedExports: true, // 使⽤usedExports进⾏标记
+        minimizer: {
+            new TerserPlugin({...}) // ⽀持删除未引⽤代码的压缩器
+        }
+    }
+}
+
  1. 使⽤以 babel-plugin-import 为主的 Babel 插件完成按需打包。
[
+  {
+    libraryName: "antd",
+    libraryDirectory: "lib", // default: lib
+    style: true,
+  },
+  {
+    libraryName: "antd",
+  },
+];
+
import { TimePicker } from "antd"
+↓ ↓ ↓ ↓ ↓ ↓
+var _button = require('antd/lib/time-picker');
+

按需加载
如何才能动态地按需导⼊模块呢?
动态导⼊ import(module) ⽅法加载模块并返回⼀个 promise,该 promise resolve 为⼀个包含其所有导出的模块对象。我们可以在代码中的任意位置调⽤这个表达式。不兼容浏览器,可以⽤ Babel 进⾏转换(@babel/plugin-syntax-dynamic-import

// say.js
+export function hi() {
+    alert(\`你好\`);
+}
+export function bye() {
+    alert(\`拜拜\`);
+}
+export default function() {
+    alert("默认到处");
+}
+{
+hi: () => {},
+bye: () => {},
+default:"sdsd"
+}
+
<!DOCTYPE html>
+<script>
+  async function load() {
+    let say = await import("./say.js");
+    say.hi(); // 你好
+    say.bye(); // 拜拜
+    say.default(); // 默认导出
+  }
+</script>
+<button onclick="load()">Click me</button>
+

如果让你⼿写⼀个不考虑兼容性的 import(module)⽅法,你会怎么写?可以看下以下 Function-like

// 利⽤ES6模块化来实现
+const dynamicImport = (url) => {
+    return new Promise((resolve, reject) => {
+        // 创建script标签
+        const script = document.createElement("script");
+        const tempGlobal = "__tempModuleVariable" + Math.random().toString(32).substring(2);
+        // 通过设置 type="module",告诉浏览器该脚本是⼀个 ES6 模块,需要按照
+        模块规范进⾏导⼊和导出
+        script.type = "module";
+        script.crossorigin="anonymous"; // 跨域
+        script.textContent = \`import * as m from "\${url}";window.\${tempGlobal} = m;\`;
+        // load 回调
+        script.onload = () => {
+            resolve(window[tempGlobal]);
+            delete window[tempGlobal];
+            script.remove();
+        };
+        // error回调
+        script.onerror = () => {
+            reject(new Error(\`Fail to load module script with URL:\${url}\`));
+            delete window[tempGlobal];
+            script.remove();
+        };
+        document.documentElement.appendChild(script);
+    });
+}
+

Vue性能优化常见策略

可以从代码分割、服务端渲染、组件缓存、⻓列表优化等⻆度去分析Vue性能优化常⻅的策略。

const router = createRouter({
+    routes: [
+        // 借助import()实现异步组件
+        { path: '/foo', component: () => import('./Foo.vue') }
+    ]
+})
+
<keep-alive>
+    <component :is="Component"></component>
+</keep-alive>
+
<template>
+    <div class="cell">
+    <!-- 这种情况⽤v-show复⽤DOM,⽐v-if效果好 -->
+        <div v-show="value" class="on">
+            <Count :num="10000"/> display:none
+        </div>
+        <section v-show="!value" class="off">
+            <Count :num="10000"/>
+        </section>
+    </div>
+</template>
+
<!-- single element -->
+<span v-once>This will never change: {{msg}}</span>
+<!-- the element have children -->
+<div v-once>
+    <h1>comment</h1>
+    <p>{{msg}}</p>
+</div>
+<!-- component -->
+<my-component v-once :comment="msg"></my-component>
+<!-- \`v-for\` directive -->
+<ul>
+    <li v-for="i in list" v-once>{{i}}</li>
+</ul>
+
<!-- vue-lazyload -->
+<img v-lazy="/static/img/1.png">
+
import { createApp } from 'vue';
+import { Button, Select } from 'element-plus';
+
+const app = createApp()
+app.use(Button)
+app.use(Select)
+

React性能优化常⻅策略

React性能优化

####【render过程】避免不必要的Render

PureComponent 是对类组件的 Props 和 State 进⾏浅⽐较
React.memo是对函数组件的 Props 进⾏浅⽐较
shouldComponentUpdate是React类组件的钩⼦,在该钩⼦函数我们可以对前后props进⾏深⽐对,返回false可以禁⽌更新组件,我们可以⼿动控制组件的更新

传给⼦组件的派⽣状态或函数,每次都是新的引⽤,这样会导致⼦组件重新刷新

import { useCallback, useState, useMemo } from 'react';
+const [count, setCount] = useState(0);
+// 保证函数引⽤是⼀样的,在将该函数作为props往下传递给其他组件的时候,不会导致
+// 其他组件像PureComponent、shouldComponentUpdate、React.memo等相关优化失效
+// const oldFunc = () => setCount(count => count + 1)
+const newFunc = useCallback(() => setCount(count => count + 1), [])
+// useMemo与useCallback ⼏乎是99%相似,只是useMemo⼀般⽤于密集型计算⼤的⼀些缓存,
+// 它得到的是函数执⾏的结果
+const calcValue = useMemo(() => {
+    return Array(100000).fill('').map(v => /*耗时计算*/ v);
+}, [count]);
+

如果⼀个P组件,它有4个⼦组件ABCD,本⾝有个状态state p, 该状态只影响到AB ,那么我们可以把AB组件进⾏封装, state p 维护⾥⾯,那么state p变化了,也不会影响到CD组件的渲染

import ReactDOM from "react-dom";
+import { createContext, useState, useContext, useMemo } from "react";
+const Context = createContext({ val: 0 });
+const MyProvider = ({ children }) => {
+    const [val, setVal] = useState(0);
+    const handleClick = useCallback(() => {
+        setVal(val + 1);
+    },[val]);
+    const value = useMemo(() => {
+        return {
+        val: val
+        };
+    }, [val]);
+    return (
+        <Context.Provider value={value}>
+            {children}
+            <button onClick={handleClick}>context change</button>
+        </Context.Provider>
+   );
+};
+
+const useVal = () => useContext(Context);
+const Child1 = () => {
+    const { val } = useVal();
+    console.log("Child1重新渲染", val);
+    return <div>Child1</div>;
+};
+const Child2 = () => {
+    console.log("Child2只渲染⼀次");
+    return <div>Child2</div>;
+};
+function App() {
+return (
+    <MyProvider>
+        <Child1 />
+        <Child2 />
+    </MyProvider>
+    );
+} 
+const rootElement = document.getElementById("root");
+ReactDOM.render(<App/>, rootElement);
+

那我如果使⽤索引值index作为key,为啥不推荐?⾯试题

// ⽆⽤更新
+<!-- 更新前 -->
+<li key="0">Tom</li>
+<li key="1">Sam</li>
+<li key="2">Ben</li>
+<li key="3">Pam</li>
+<!-- 删除后更新 -->
+<li key="0">Sam</li>
+<li key="1">Ben</li>
+<li key="2">Pam</li>
+
+// 输⼊错乱
+<!-- 更新前 -->
+<input key="0" value="1" id="id1"/>
+<input key="1" value="2" id="id2"/>
+<input key="3" value="3" id="id3"/>
+<input key="4" value="4" id="id4"/>
+<!-- 删除后更新 -->
+<input key="1" value="1" id="id2"/>
+<input key="3" value="2" id="id3"/>
+<input key="4" value="3" id="id4"/>
+

其他优化

import { lazy, Suspense, Component } from "react"
+const Com = lazy(() => {
+    return new Promise((resolve, reject) => {
+    setTimeout(() => {
+        if (Math.random() > 0.5) {
+        reject(new Error("error"))
+        } else {
+        resolve(import("./Component"))
+        }
+    }, 1000)
+  })
+})
+// ...
+<Suspense fallback="加载...">
+    <Com />
+</Suspense>
+
`,91);function A(L,z){const a=o("ExternalLinkIcon");return c(),l("div",null,[u,r,n("p",null,[k,d,n("a",v,[s("前端性能优化之利用 Chrome Dev Tools 进行页面性能分析 - 知乎"),t(a)]),s(),m,n("a",g,[s("FP、FCP、FMP、LCP 都是什么 P? - 知乎"),t(a)])]),b,n("blockquote",null,[n("p",null,[f,s("是⼀组⽤于衡量 web 应⽤性能的标准接⼝,学习链接:"),n("a",y,[s("Performance API"),t(a)])])]),h,n("ul",null,[n("li",null,[n("p",null,[n("a",q,[s("performance.timing"),t(a)]),s("可以获取⽹⻚运⾏过程中每个时间点对应的时间戳(绝对时间,ms),但却即将"),w,j])]),n("li",null,[n("p",null,[n("a",x,[s("performance.getEntries()"),t(a)]),s(",以对象数组的⽅式返回所有资源的数据,包括 css,img,script, xmlhttprequest,link 等等")])]),n("li",null,[n("p",null,[n("a",P,[s("performance.getEntriesByType(:string)"),t(a)]),s(",和上⾯的 getEntries ⽅法类似,不过是多了⼀层类型 的筛选,常⻅性能类型可以有"),_])])]),C,n("ul",null,[n("li",null,[n("a",T,[s("performance.getEntriesByName(name:string,type?:string)"),t(a)]),s(",原理和上⾯的 getEntries ⽅法类似,多了⼀层名字的筛选,也可以传第⼆个参数再加⼀层类型的筛选")])]),E,n("ul",null,[n("li",null,[n("a",S,[s("performance.now()"),t(a)]),s(",返回当前时间与"),B,s("的时间差")])]),I,n("ul",null,[n("li",null,[n("a",M,[s("PerformanceObserver"),t(a)]),F])]),R])}const N=e(i,[["render",A],["__file","Performance.html.vue"]]),D=JSON.parse('{"path":"/advance/Performance.html","title":"Performance","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"为什么前端性能如此重要?","slug":"为什么前端性能如此重要","link":"#为什么前端性能如此重要","children":[]},{"level":2,"title":"⽹⻚性能指标及影响因素","slug":"网⻚性能指标及影响因素","link":"#网⻚性能指标及影响因素","children":[{"level":3,"title":"Timing","slug":"timing","link":"#timing","children":[]},{"level":3,"title":"关于 Performance API","slug":"关于-performance-api","link":"#关于-performance-api","children":[]},{"level":3,"title":"用户为导向性能指标介绍","slug":"用户为导向性能指标介绍","link":"#用户为导向性能指标介绍","children":[]},{"level":3,"title":"核心网页指标","slug":"核心网页指标","link":"#核心网页指标","children":[]},{"level":3,"title":"常⻅优化⼿段","slug":"常⻅优化手段","link":"#常⻅优化手段","children":[]},{"level":3,"title":"Vue性能优化常见策略","slug":"vue性能优化常见策略","link":"#vue性能优化常见策略","children":[]},{"level":3,"title":"React性能优化常⻅策略","slug":"react性能优化常⻅策略","link":"#react性能优化常⻅策略","children":[]}]}],"filePathRelative":"advance/Performance.md","git":{"createdTime":1715955258000,"updatedTime":1716035520000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":2}]},"readingTime":{"minutes":15.09,"words":4527}}');export{N as comp,D as data}; diff --git "a/assets/Webpack\346\211\223\345\214\205\345\216\237\347\220\206.html-D_Vm0kh6.js" "b/assets/Webpack\346\211\223\345\214\205\345\216\237\347\220\206.html-D_Vm0kh6.js" new file mode 100644 index 0000000..2bc8f80 --- /dev/null +++ "b/assets/Webpack\346\211\223\345\214\205\345\216\237\347\220\206.html-D_Vm0kh6.js" @@ -0,0 +1 @@ +import{_ as n,r as t,o as i,c as o,a as e,b as l,d as c,e as p}from"./app-B-BkP2m_.js";const r={},s=e("h1",{id:"说webpack-打包原理",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#说webpack-打包原理"},[e("span",null,"说Webpack 打包原理")])],-1),b=e("h2",{id:"webpack-介绍",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#webpack-介绍"},[e("span",null,"Webpack 介绍")])],-1),k=e("strong",null,"参考文章",-1),h=e("br",null,null,-1),u={href:"https://blog.csdn.net/qq_35942348/article/details/131847520",target:"_blank",rel:"noopener noreferrer"},d=e("br",null,null,-1),_={href:"https://segmentfault.com/a/1190000021494964#item-5-10",target:"_blank",rel:"noopener noreferrer"},w=e("br",null,null,-1),m=p('

在目前的项目中,我们会有很多依赖包,webpack负责将浏览器不能识别的文件类型、语法等转化为可识别的前端三剑客(html,css,js),并在这个过程中充当组织者与优化者的角色。

webpack 核心概念

bundle

Chunk

Entry

Output

Module

模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 Entry 开始递归找出所有依赖的模块。

Chunk

代码块,一个 Chunk 由多个模块组合而成,用于代码合并与分割。

Loader

Plugin

webpack 构建流程

Webpack 的运行流程是一个串行的过程,从启动到结束会依次执行以下流程 :

  1. 初始化参数:从配置文件和 Shell 语句中读取与合并参数,得出最终的参数。
  2. 开始编译:用上一步得到的参数初始化 Compiler 对象,加载所有配置的插件,执行对象的 run 方法开始执行编译。
  3. 确定入口:根据配置中的 entry 找出所有的入口文件。
  4. 编译模块:从入口文件出发,调用所有配置的 Loader 对模块进行翻译,再找出该模块依赖的模块,再递归本步骤直到所有入口依赖的文件都经过了本步骤的处理。
  5. 完成模块编译:在经过第 4 步使用 Loader 翻译完所有模块后,得到了每个模块被翻译后的最终内容以及它们之间的依赖关系。
  6. 输出资源:根据入口和模块之间的依赖关系,组装成一个个包含多个模块的 Chunk,再把每个 Chunk 转换成一个单独的文件加入到输出列表,这步是可以修改输出内容的最后机会。
  7. 输出完成:在确定好输出内容后,根据配置确定输出的路径和文件名,把文件内容写入到文件系统。

在以上过程中,Webpack 会在特定的时间点广播出特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,并且插件可以调用 Webpack 提供的 API 改变 Webpack 的运行结果。

',23);function f(W,q){const a=t("ExternalLinkIcon");return i(),o("div",null,[s,b,e("p",null,[k,h,e("a",u,[l("webpack 打包原理及流程解析,超详细!"),c(a)]),d,e("a",_,[l("webpack打包原理 ? 看完这篇你就懂了 !"),c(a)]),w]),m])}const g=n(r,[["render",f],["__file","Webpack打包原理.html.vue"]]),v=JSON.parse('{"path":"/advance/Webpack%E6%89%93%E5%8C%85%E5%8E%9F%E7%90%86.html","title":"说Webpack 打包原理","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"Webpack 介绍","slug":"webpack-介绍","link":"#webpack-介绍","children":[]},{"level":2,"title":"webpack 核心概念","slug":"webpack-核心概念","link":"#webpack-核心概念","children":[]},{"level":2,"title":"webpack 构建流程","slug":"webpack-构建流程","link":"#webpack-构建流程","children":[]}],"filePathRelative":"advance/Webpack打包原理.md","git":{"createdTime":1716636128000,"updatedTime":1716636128000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":1}]},"readingTime":{"minutes":5.4,"words":1620}}');export{g as comp,v as data}; diff --git "a/assets/Web\345\272\224\347\224\250\345\256\211\345\205\250.html-C-um36wI.js" "b/assets/Web\345\272\224\347\224\250\345\256\211\345\205\250.html-C-um36wI.js" new file mode 100644 index 0000000..9b618fc --- /dev/null +++ "b/assets/Web\345\272\224\347\224\250\345\256\211\345\205\250.html-C-um36wI.js" @@ -0,0 +1 @@ +import{_ as t,o,c as a,a as e}from"./app-B-BkP2m_.js";const c={},s=e("h1",{id:"web应用安全",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#web应用安全"},[e("span",null,"Web应用安全")])],-1),n=e("p",null,"正在更新……",-1),r=[s,n];function i(_,l){return o(),a("div",null,r)}const d=t(c,[["render",i],["__file","Web应用安全.html.vue"]]),h=JSON.parse('{"path":"/computer/Web%E5%BA%94%E7%94%A8%E5%AE%89%E5%85%A8.html","title":"Web应用安全","lang":"zh-CN","frontmatter":{},"headers":[],"filePathRelative":"computer/Web应用安全.md","git":{"createdTime":1715780535000,"updatedTime":1715780535000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":1}]},"readingTime":{"minutes":0.03,"words":9}}');export{d as comp,h as data}; diff --git a/assets/app-B-BkP2m_.js b/assets/app-B-BkP2m_.js new file mode 100644 index 0000000..4e87704 --- /dev/null +++ b/assets/app-B-BkP2m_.js @@ -0,0 +1,26 @@ +/** +* @vue/shared v3.4.27 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**//*! #__NO_SIDE_EFFECTS__ */function Rl(e,t){const n=new Set(e.split(","));return r=>n.has(r)}const Ee={},cn=[],et=()=>{},ca=()=>!1,qn=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),Pl=e=>e.startsWith("onUpdate:"),Te=Object.assign,Ol=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},ua=Object.prototype.hasOwnProperty,he=(e,t)=>ua.call(e,t),te=Array.isArray,un=e=>Or(e)==="[object Map]",ki=e=>Or(e)==="[object Set]",oe=e=>typeof e=="function",Re=e=>typeof e=="string",nn=e=>typeof e=="symbol",ke=e=>e!==null&&typeof e=="object",wi=e=>(ke(e)||oe(e))&&oe(e.then)&&oe(e.catch),Ai=Object.prototype.toString,Or=e=>Ai.call(e),fa=e=>Or(e).slice(8,-1),Ci=e=>Or(e)==="[object Object]",Il=e=>Re(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,fn=Rl(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),Ir=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},da=/-(\w)/g,nt=Ir(e=>e.replace(da,(t,n)=>n?n.toUpperCase():"")),ha=/\B([A-Z])/g,rn=Ir(e=>e.replace(ha,"-$1").toLowerCase()),Gn=Ir(e=>e.charAt(0).toUpperCase()+e.slice(1)),Jr=Ir(e=>e?`on${Gn(e)}`:""),Bt=(e,t)=>!Object.is(e,t),Yr=(e,t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:r,value:n})},pa=e=>{const t=parseFloat(e);return isNaN(t)?e:t},ma=e=>{const t=Re(e)?Number(e):NaN;return isNaN(t)?e:t};let ao;const Li=()=>ao||(ao=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function Jn(e){if(te(e)){const t={};for(let n=0;n{if(n){const r=n.split(va);r.length>1&&(t[r[0].trim()]=r[1].trim())}}),t}function Ge(e){let t="";if(Re(e))t=e;else if(te(e))for(let n=0;nRe(e)?e:e==null?"":te(e)||ke(e)&&(e.toString===Ai||!oe(e.toString))?JSON.stringify(e,Ti,2):String(e),Ti=(e,t)=>t&&t.__v_isRef?Ti(e,t.value):un(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((n,[r,l],o)=>(n[Xr(r,o)+" =>"]=l,n),{})}:ki(t)?{[`Set(${t.size})`]:[...t.values()].map(n=>Xr(n))}:nn(t)?Xr(t):ke(t)&&!te(t)&&!Ci(t)?String(t):t,Xr=(e,t="")=>{var n;return nn(e)?`Symbol(${(n=e.description)!=null?n:t})`:e};/** +* @vue/reactivity v3.4.27 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/let Xe;class ka{constructor(t=!1){this.detached=t,this._active=!0,this.effects=[],this.cleanups=[],this.parent=Xe,!t&&Xe&&(this.index=(Xe.scopes||(Xe.scopes=[])).push(this)-1)}get active(){return this._active}run(t){if(this._active){const n=Xe;try{return Xe=this,t()}finally{Xe=n}}}on(){Xe=this}off(){Xe=this.parent}stop(t){if(this._active){let n,r;for(n=0,r=this.effects.length;n=4))break}this._dirtyLevel===1&&(this._dirtyLevel=0),jt()}return this._dirtyLevel>=4}set dirty(t){this._dirtyLevel=t?4:0}run(){if(this._dirtyLevel=0,!this.active)return this.fn();let t=Ft,n=Qt;try{return Ft=!0,Qt=this,this._runnings++,co(this),this.fn()}finally{uo(this),this._runnings--,Qt=n,Ft=t}}stop(){this.active&&(co(this),uo(this),this.onStop&&this.onStop(),this.active=!1)}}function Ca(e){return e.value}function co(e){e._trackId++,e._depsLength=0}function uo(e){if(e.deps.length>e._depsLength){for(let t=e._depsLength;t{const n=new Map;return n.cleanup=e,n.computed=t,n},Er=new WeakMap,Zt=Symbol(""),ml=Symbol("");function Je(e,t,n){if(Ft&&Qt){let r=Er.get(e);r||Er.set(e,r=new Map);let l=r.get(n);l||r.set(n,l=$i(()=>r.delete(n))),Ii(Qt,l)}}function bt(e,t,n,r,l,o){const i=Er.get(e);if(!i)return;let s=[];if(t==="clear")s=[...i.values()];else if(n==="length"&&te(e)){const a=Number(r);i.forEach((c,u)=>{(u==="length"||!nn(u)&&u>=a)&&s.push(c)})}else switch(n!==void 0&&s.push(i.get(n)),t){case"add":te(e)?Il(n)&&s.push(i.get("length")):(s.push(i.get(Zt)),un(e)&&s.push(i.get(ml)));break;case"delete":te(e)||(s.push(i.get(Zt)),un(e)&&s.push(i.get(ml)));break;case"set":un(e)&&s.push(i.get(Zt));break}$l();for(const a of s)a&&Fi(a,4);Bl()}function xa(e,t){const n=Er.get(e);return n&&n.get(t)}const La=Rl("__proto__,__v_isRef,__isVue"),Bi=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>e!=="arguments"&&e!=="caller").map(e=>Symbol[e]).filter(nn)),fo=Sa();function Sa(){const e={};return["includes","indexOf","lastIndexOf"].forEach(t=>{e[t]=function(...n){const r=pe(this);for(let o=0,i=this.length;o{e[t]=function(...n){Ht(),$l();const r=pe(this)[t].apply(this,n);return Bl(),jt(),r}}),e}function Ta(e){nn(e)||(e=String(e));const t=pe(this);return Je(t,"has",e),t.hasOwnProperty(e)}class Di{constructor(t=!1,n=!1){this._isReadonly=t,this._isShallow=n}get(t,n,r){const l=this._isReadonly,o=this._isShallow;if(n==="__v_isReactive")return!l;if(n==="__v_isReadonly")return l;if(n==="__v_isShallow")return o;if(n==="__v_raw")return r===(l?o?za:ji:o?Hi:Ni).get(t)||Object.getPrototypeOf(t)===Object.getPrototypeOf(r)?t:void 0;const i=te(t);if(!l){if(i&&he(fo,n))return Reflect.get(fo,n,r);if(n==="hasOwnProperty")return Ta}const s=Reflect.get(t,n,r);return(nn(n)?Bi.has(n):La(n))||(l||Je(t,"get",n),o)?s:je(s)?i&&Il(n)?s:s.value:ke(s)?l?$r(s):Yn(s):s}}class Mi extends Di{constructor(t=!1){super(!1,t)}set(t,n,r,l){let o=t[n];if(!this._isShallow){const a=Bn(o);if(!kr(r)&&!Bn(r)&&(o=pe(o),r=pe(r)),!te(t)&&je(o)&&!je(r))return a?!1:(o.value=r,!0)}const i=te(t)&&Il(n)?Number(n)e,Fr=e=>Reflect.getPrototypeOf(e);function lr(e,t,n=!1,r=!1){e=e.__v_raw;const l=pe(e),o=pe(t);n||(Bt(t,o)&&Je(l,"get",t),Je(l,"get",o));const{has:i}=Fr(l),s=r?Dl:n?Hl:Dn;if(i.call(l,t))return s(e.get(t));if(i.call(l,o))return s(e.get(o));e!==l&&e.get(t)}function or(e,t=!1){const n=this.__v_raw,r=pe(n),l=pe(e);return t||(Bt(e,l)&&Je(r,"has",e),Je(r,"has",l)),e===l?n.has(e):n.has(e)||n.has(l)}function ir(e,t=!1){return e=e.__v_raw,!t&&Je(pe(e),"iterate",Zt),Reflect.get(e,"size",e)}function ho(e){e=pe(e);const t=pe(this);return Fr(t).has.call(t,e)||(t.add(e),bt(t,"add",e,e)),this}function po(e,t){t=pe(t);const n=pe(this),{has:r,get:l}=Fr(n);let o=r.call(n,e);o||(e=pe(e),o=r.call(n,e));const i=l.call(n,e);return n.set(e,t),o?Bt(t,i)&&bt(n,"set",e,t):bt(n,"add",e,t),this}function mo(e){const t=pe(this),{has:n,get:r}=Fr(t);let l=n.call(t,e);l||(e=pe(e),l=n.call(t,e)),r&&r.call(t,e);const o=t.delete(e);return l&&bt(t,"delete",e,void 0),o}function go(){const e=pe(this),t=e.size!==0,n=e.clear();return t&&bt(e,"clear",void 0,void 0),n}function sr(e,t){return function(r,l){const o=this,i=o.__v_raw,s=pe(i),a=t?Dl:e?Hl:Dn;return!e&&Je(s,"iterate",Zt),i.forEach((c,u)=>r.call(l,a(c),a(u),o))}}function ar(e,t,n){return function(...r){const l=this.__v_raw,o=pe(l),i=un(o),s=e==="entries"||e===Symbol.iterator&&i,a=e==="keys"&&i,c=l[e](...r),u=n?Dl:t?Hl:Dn;return!t&&Je(o,"iterate",a?ml:Zt),{next(){const{value:f,done:d}=c.next();return d?{value:f,done:d}:{value:s?[u(f[0]),u(f[1])]:u(f),done:d}},[Symbol.iterator](){return this}}}}function Ct(e){return function(...t){return e==="delete"?!1:e==="clear"?void 0:this}}function Fa(){const e={get(o){return lr(this,o)},get size(){return ir(this)},has:or,add:ho,set:po,delete:mo,clear:go,forEach:sr(!1,!1)},t={get(o){return lr(this,o,!1,!0)},get size(){return ir(this)},has:or,add:ho,set:po,delete:mo,clear:go,forEach:sr(!1,!0)},n={get(o){return lr(this,o,!0)},get size(){return ir(this,!0)},has(o){return or.call(this,o,!0)},add:Ct("add"),set:Ct("set"),delete:Ct("delete"),clear:Ct("clear"),forEach:sr(!0,!1)},r={get(o){return lr(this,o,!0,!0)},get size(){return ir(this,!0)},has(o){return or.call(this,o,!0)},add:Ct("add"),set:Ct("set"),delete:Ct("delete"),clear:Ct("clear"),forEach:sr(!0,!0)};return["keys","values","entries",Symbol.iterator].forEach(o=>{e[o]=ar(o,!1,!1),n[o]=ar(o,!0,!1),t[o]=ar(o,!1,!0),r[o]=ar(o,!0,!0)}),[e,n,t,r]}const[$a,Ba,Da,Ma]=Fa();function Ml(e,t){const n=t?e?Ma:Da:e?Ba:$a;return(r,l,o)=>l==="__v_isReactive"?!e:l==="__v_isReadonly"?e:l==="__v_raw"?r:Reflect.get(he(n,l)&&l in r?n:r,l,o)}const Na={get:Ml(!1,!1)},Ha={get:Ml(!1,!0)},ja={get:Ml(!0,!1)};const Ni=new WeakMap,Hi=new WeakMap,ji=new WeakMap,za=new WeakMap;function Va(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function Ua(e){return e.__v_skip||!Object.isExtensible(e)?0:Va(fa(e))}function Yn(e){return Bn(e)?e:Nl(e,!1,Pa,Na,Ni)}function zi(e){return Nl(e,!1,Ia,Ha,Hi)}function $r(e){return Nl(e,!0,Oa,ja,ji)}function Nl(e,t,n,r,l){if(!ke(e)||e.__v_raw&&!(t&&e.__v_isReactive))return e;const o=l.get(e);if(o)return o;const i=Ua(e);if(i===0)return e;const s=new Proxy(e,i===2?r:n);return l.set(e,s),s}function Sn(e){return Bn(e)?Sn(e.__v_raw):!!(e&&e.__v_isReactive)}function Bn(e){return!!(e&&e.__v_isReadonly)}function kr(e){return!!(e&&e.__v_isShallow)}function Vi(e){return e?!!e.__v_raw:!1}function pe(e){const t=e&&e.__v_raw;return t?pe(t):e}function Wa(e){return Object.isExtensible(e)&&xi(e,"__v_skip",!0),e}const Dn=e=>ke(e)?Yn(e):e,Hl=e=>ke(e)?$r(e):e;class Ui{constructor(t,n,r,l){this.getter=t,this._setter=n,this.dep=void 0,this.__v_isRef=!0,this.__v_isReadonly=!1,this.effect=new Fl(()=>t(this._value),()=>Tn(this,this.effect._dirtyLevel===2?2:3)),this.effect.computed=this,this.effect.active=this._cacheable=!l,this.__v_isReadonly=r}get value(){const t=pe(this);return(!t._cacheable||t.effect.dirty)&&Bt(t._value,t._value=t.effect.run())&&Tn(t,4),jl(t),t.effect._dirtyLevel>=2&&Tn(t,2),t._value}set value(t){this._setter(t)}get _dirty(){return this.effect.dirty}set _dirty(t){this.effect.dirty=t}}function Ka(e,t,n=!1){let r,l;const o=oe(e);return o?(r=e,l=et):(r=e.get,l=e.set),new Ui(r,l,o||!l,n)}function jl(e){var t;Ft&&Qt&&(e=pe(e),Ii(Qt,(t=e.dep)!=null?t:e.dep=$i(()=>e.dep=void 0,e instanceof Ui?e:void 0)))}function Tn(e,t=4,n){e=pe(e);const r=e.dep;r&&Fi(r,t)}function je(e){return!!(e&&e.__v_isRef===!0)}function ue(e){return Wi(e,!1)}function _n(e){return Wi(e,!0)}function Wi(e,t){return je(e)?e:new qa(e,t)}class qa{constructor(t,n){this.__v_isShallow=n,this.dep=void 0,this.__v_isRef=!0,this._rawValue=n?t:pe(t),this._value=n?t:Dn(t)}get value(){return jl(this),this._value}set value(t){const n=this.__v_isShallow||kr(t)||Bn(t);t=n?t:pe(t),Bt(t,this._rawValue)&&(this._rawValue=t,this._value=n?t:Dn(t),Tn(this,4))}}function X(e){return je(e)?e.value:e}const Ga={get:(e,t,n)=>X(Reflect.get(e,t,n)),set:(e,t,n,r)=>{const l=e[t];return je(l)&&!je(n)?(l.value=n,!0):Reflect.set(e,t,n,r)}};function Ki(e){return Sn(e)?e:new Proxy(e,Ga)}class Ja{constructor(t){this.dep=void 0,this.__v_isRef=!0;const{get:n,set:r}=t(()=>jl(this),()=>Tn(this));this._get=n,this._set=r}get value(){return this._get()}set value(t){this._set(t)}}function Ya(e){return new Ja(e)}function Br(e){const t=te(e)?new Array(e.length):{};for(const n in e)t[n]=Qa(e,n);return t}class Xa{constructor(t,n,r){this._object=t,this._key=n,this._defaultValue=r,this.__v_isRef=!0}get value(){const t=this._object[this._key];return t===void 0?this._defaultValue:t}set value(t){this._object[this._key]=t}get dep(){return xa(pe(this._object),this._key)}}function Qa(e,t,n){const r=e[t];return je(r)?r:new Xa(e,t,n)}/** +* @vue/runtime-core v3.4.27 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/function $t(e,t,n,r){try{return r?e(...r):e()}catch(l){Xn(l,t,n)}}function tt(e,t,n,r){if(oe(e)){const l=$t(e,t,n,r);return l&&wi(l)&&l.catch(o=>{Xn(o,t,n)}),l}if(te(e)){const l=[];for(let o=0;o>>1,l=He[r],o=Nn(l);odt&&He.splice(t,1)}function nc(e){te(e)?dn.push(...e):(!Tt||!Tt.includes(e,e.allowRecurse?Gt+1:Gt))&&dn.push(e),Gi()}function vo(e,t,n=Mn?dt+1:0){for(;nNn(n)-Nn(r));if(dn.length=0,Tt){Tt.push(...t);return}for(Tt=t,Gt=0;Gte.id==null?1/0:e.id,rc=(e,t)=>{const n=Nn(e)-Nn(t);if(n===0){if(e.pre&&!t.pre)return-1;if(t.pre&&!e.pre)return 1}return n};function Ji(e){gl=!1,Mn=!0,He.sort(rc);try{for(dt=0;dtRe(m)?m.trim():m)),f&&(l=n.map(pa))}let s,a=r[s=Jr(t)]||r[s=Jr(nt(t))];!a&&o&&(a=r[s=Jr(rn(t))]),a&&tt(a,e,6,l);const c=r[s+"Once"];if(c){if(!e.emitted)e.emitted={};else if(e.emitted[s])return;e.emitted[s]=!0,tt(c,e,6,l)}}function Yi(e,t,n=!1){const r=t.emitsCache,l=r.get(e);if(l!==void 0)return l;const o=e.emits;let i={},s=!1;if(!oe(e)){const a=c=>{const u=Yi(c,t,!0);u&&(s=!0,Te(i,u))};!n&&t.mixins.length&&t.mixins.forEach(a),e.extends&&a(e.extends),e.mixins&&e.mixins.forEach(a)}return!o&&!s?(ke(e)&&r.set(e,null),null):(te(o)?o.forEach(a=>i[a]=null):Te(i,o),ke(e)&&r.set(e,i),i)}function Mr(e,t){return!e||!qn(t)?!1:(t=t.slice(2).replace(/Once$/,""),he(e,t[0].toLowerCase()+t.slice(1))||he(e,rn(t))||he(e,t))}let Pe=null,Xi=null;function Ar(e){const t=Pe;return Pe=e,Xi=e&&e.type.__scopeId||null,t}function Le(e,t=Pe,n){if(!t||e._n)return e;const r=(...l)=>{r._d&&To(-1);const o=Ar(t);let i;try{i=e(...l)}finally{Ar(o),r._d&&To(1)}return i};return r._n=!0,r._c=!0,r._d=!0,r}function Qr(e){const{type:t,vnode:n,proxy:r,withProxy:l,propsOptions:[o],slots:i,attrs:s,emit:a,render:c,renderCache:u,props:f,data:d,setupState:m,ctx:g,inheritAttrs:y}=e,w=Ar(e);let T,x;try{if(n.shapeFlag&4){const k=l||r,N=k;T=lt(c.call(N,k,u,f,m,d,g)),x=s}else{const k=t;T=lt(k.length>1?k(f,{attrs:s,slots:i,emit:a}):k(f,null)),x=t.props?s:oc(s)}}catch(k){In.length=0,Xn(k,e,1),T=ie(qe)}let v=T;if(x&&y!==!1){const k=Object.keys(x),{shapeFlag:N}=v;k.length&&N&7&&(o&&k.some(Pl)&&(x=ic(x,o)),v=Mt(v,x,!1,!0))}return n.dirs&&(v=Mt(v,null,!1,!0),v.dirs=v.dirs?v.dirs.concat(n.dirs):n.dirs),n.transition&&(v.transition=n.transition),T=v,Ar(w),T}const oc=e=>{let t;for(const n in e)(n==="class"||n==="style"||qn(n))&&((t||(t={}))[n]=e[n]);return t},ic=(e,t)=>{const n={};for(const r in e)(!Pl(r)||!(r.slice(9)in t))&&(n[r]=e[r]);return n};function sc(e,t,n){const{props:r,children:l,component:o}=e,{props:i,children:s,patchFlag:a}=t,c=o.emitsOptions;if(t.dirs||t.transition)return!0;if(n&&a>=0){if(a&1024)return!0;if(a&16)return r?_o(r,i,c):!!i;if(a&8){const u=t.dynamicProps;for(let f=0;fe.__isSuspense;function Zi(e,t){t&&t.pendingBranch?te(e)?t.effects.push(...e):t.effects.push(e):nc(e)}const dc=Symbol.for("v-scx"),hc=()=>Ve(dc);function pc(e,t){return Vl(e,null,t)}const cr={};function Me(e,t,n){return Vl(e,t,n)}function Vl(e,t,{immediate:n,deep:r,flush:l,once:o,onTrack:i,onTrigger:s}=Ee){if(t&&o){const O=t;t=(...D)=>{O(...D),N()}}const a=$e,c=O=>r===!0?O:Yt(O,r===!1?1:void 0);let u,f=!1,d=!1;if(je(e)?(u=()=>e.value,f=kr(e)):Sn(e)?(u=()=>c(e),f=!0):te(e)?(d=!0,f=e.some(O=>Sn(O)||kr(O)),u=()=>e.map(O=>{if(je(O))return O.value;if(Sn(O))return c(O);if(oe(O))return $t(O,a,2)})):oe(e)?t?u=()=>$t(e,a,2):u=()=>(m&&m(),tt(e,a,3,[g])):u=et,t&&r){const O=u;u=()=>Yt(O())}let m,g=O=>{m=v.onStop=()=>{$t(O,a,4),m=v.onStop=void 0}},y;if(er)if(g=et,t?n&&tt(t,a,3,[u(),d?[]:void 0,g]):u(),l==="sync"){const O=hc();y=O.__watcherHandles||(O.__watcherHandles=[])}else return et;let w=d?new Array(e.length).fill(cr):cr;const T=()=>{if(!(!v.active||!v.dirty))if(t){const O=v.run();(r||f||(d?O.some((D,_)=>Bt(D,w[_])):Bt(O,w)))&&(m&&m(),tt(t,a,3,[O,w===cr?void 0:d&&w[0]===cr?[]:w,g]),w=O)}else v.run()};T.allowRecurse=!!t;let x;l==="sync"?x=T:l==="post"?x=()=>Ke(T,a&&a.suspense):(T.pre=!0,a&&(T.id=a.uid),x=()=>Dr(T));const v=new Fl(u,et,x),k=Ri(),N=()=>{v.stop(),k&&Ol(k.effects,v)};return t?n?T():w=v.run():l==="post"?Ke(v.run.bind(v),a&&a.suspense):v.run(),y&&y.push(N),N}function mc(e,t,n){const r=this.proxy,l=Re(e)?e.includes(".")?es(r,e):()=>r[e]:e.bind(r,r);let o;oe(t)?o=t:(o=t.handler,n=t);const i=Zn(this),s=Vl(l,o.bind(r),n);return i(),s}function es(e,t){const n=t.split(".");return()=>{let r=e;for(let l=0;l{Yt(r,t,n)});else if(Ci(e))for(const r in e)Yt(e[r],t,n);return e}function Cr(e,t){if(Pe===null)return e;const n=Vr(Pe)||Pe.proxy,r=e.dirs||(e.dirs=[]);for(let l=0;l{e.isMounted=!0}),Hr(()=>{e.isUnmounting=!0}),e}const Qe=[Function,Array],ts={mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:Qe,onEnter:Qe,onAfterEnter:Qe,onEnterCancelled:Qe,onBeforeLeave:Qe,onLeave:Qe,onAfterLeave:Qe,onLeaveCancelled:Qe,onBeforeAppear:Qe,onAppear:Qe,onAfterAppear:Qe,onAppearCancelled:Qe},vc={name:"BaseTransition",props:ts,setup(e,{slots:t}){const n=zr(),r=gc();return()=>{const l=t.default&&rs(t.default(),!0);if(!l||!l.length)return;let o=l[0];if(l.length>1){for(const d of l)if(d.type!==qe){o=d;break}}const i=pe(e),{mode:s}=i;if(r.isLeaving)return Zr(o);const a=yo(o);if(!a)return Zr(o);const c=vl(a,i,r,n);_l(a,c);const u=n.subTree,f=u&&yo(u);if(f&&f.type!==qe&&!Jt(a,f)){const d=vl(f,i,r,n);if(_l(f,d),s==="out-in"&&a.type!==qe)return r.isLeaving=!0,d.afterLeave=()=>{r.isLeaving=!1,n.update.active!==!1&&(n.effect.dirty=!0,n.update())},Zr(o);s==="in-out"&&a.type!==qe&&(d.delayLeave=(m,g,y)=>{const w=ns(r,f);w[String(f.key)]=f,m[Rt]=()=>{g(),m[Rt]=void 0,delete c.delayedLeave},c.delayedLeave=y})}return o}}},_c=vc;function ns(e,t){const{leavingVNodes:n}=e;let r=n.get(t.type);return r||(r=Object.create(null),n.set(t.type,r)),r}function vl(e,t,n,r){const{appear:l,mode:o,persisted:i=!1,onBeforeEnter:s,onEnter:a,onAfterEnter:c,onEnterCancelled:u,onBeforeLeave:f,onLeave:d,onAfterLeave:m,onLeaveCancelled:g,onBeforeAppear:y,onAppear:w,onAfterAppear:T,onAppearCancelled:x}=t,v=String(e.key),k=ns(n,e),N=(_,G)=>{_&&tt(_,r,9,G)},O=(_,G)=>{const L=G[1];N(_,G),te(_)?_.every(U=>U.length<=1)&&L():_.length<=1&&L()},D={mode:o,persisted:i,beforeEnter(_){let G=s;if(!n.isMounted)if(l)G=y||s;else return;_[Rt]&&_[Rt](!0);const L=k[v];L&&Jt(e,L)&&L.el[Rt]&&L.el[Rt](),N(G,[_])},enter(_){let G=a,L=c,U=u;if(!n.isMounted)if(l)G=w||a,L=T||c,U=x||u;else return;let E=!1;const B=_[ur]=ne=>{E||(E=!0,ne?N(U,[_]):N(L,[_]),D.delayedLeave&&D.delayedLeave(),_[ur]=void 0)};G?O(G,[_,B]):B()},leave(_,G){const L=String(e.key);if(_[ur]&&_[ur](!0),n.isUnmounting)return G();N(f,[_]);let U=!1;const E=_[Rt]=B=>{U||(U=!0,G(),B?N(g,[_]):N(m,[_]),_[Rt]=void 0,k[L]===e&&delete k[L])};k[L]=e,d?O(d,[_,E]):E()},clone(_){return vl(_,t,n,r)}};return D}function Zr(e){if(Qn(e))return e=Mt(e),e.children=null,e}function yo(e){if(!Qn(e))return e;const{shapeFlag:t,children:n}=e;if(n){if(t&16)return n[0];if(t&32&&oe(n.default))return n.default()}}function _l(e,t){e.shapeFlag&6&&e.component?_l(e.component.subTree,t):e.shapeFlag&128?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function rs(e,t=!1,n){let r=[],l=0;for(let o=0;o1)for(let o=0;o!!e.type.__asyncLoader;/*! #__NO_SIDE_EFFECTS__ */function bc(e){oe(e)&&(e={loader:e});const{loader:t,loadingComponent:n,errorComponent:r,delay:l=200,timeout:o,suspensible:i=!0,onError:s}=e;let a=null,c,u=0;const f=()=>(u++,a=null,d()),d=()=>{let m;return a||(m=a=t().catch(g=>{if(g=g instanceof Error?g:new Error(String(g)),s)return new Promise((y,w)=>{s(g,()=>y(f()),()=>w(g),u+1)});throw g}).then(g=>m!==a&&a?a:(g&&(g.__esModule||g[Symbol.toStringTag]==="Module")&&(g=g.default),c=g,g)))};return me({name:"AsyncComponentWrapper",__asyncLoader:d,get __asyncResolved(){return c},setup(){const m=$e;if(c)return()=>el(c,m);const g=x=>{a=null,Xn(x,m,13,!r)};if(i&&m.suspense||er)return d().then(x=>()=>el(x,m)).catch(x=>(g(x),()=>r?ie(r,{error:x}):null));const y=ue(!1),w=ue(),T=ue(!!l);return l&&setTimeout(()=>{T.value=!1},l),o!=null&&setTimeout(()=>{if(!y.value&&!w.value){const x=new Error(`Async component timed out after ${o}ms.`);g(x),w.value=x}},o),d().then(()=>{y.value=!0,m.parent&&Qn(m.parent.vnode)&&(m.parent.effect.dirty=!0,Dr(m.parent.update))}).catch(x=>{g(x),w.value=x}),()=>{if(y.value&&c)return el(c,m);if(w.value&&r)return ie(r,{error:w.value});if(n&&!T.value)return ie(n)}}})}function el(e,t){const{ref:n,props:r,children:l,ce:o}=t.vnode,i=ie(e,r,l);return i.ref=n,i.ce=o,delete t.vnode.ce,i}const Qn=e=>e.type.__isKeepAlive;function yc(e,t){ls(e,"a",t)}function Ec(e,t){ls(e,"da",t)}function ls(e,t,n=$e){const r=e.__wdc||(e.__wdc=()=>{let l=n;for(;l;){if(l.isDeactivated)return;l=l.parent}return e()});if(Nr(t,r,n),n){let l=n.parent;for(;l&&l.parent;)Qn(l.parent.vnode)&&kc(r,t,n,l),l=l.parent}}function kc(e,t,n,r){const l=Nr(t,e,r,!0);jr(()=>{Ol(r[t],l)},n)}function Nr(e,t,n=$e,r=!1){if(n){const l=n[e]||(n[e]=[]),o=t.__weh||(t.__weh=(...i)=>{if(n.isUnmounted)return;Ht();const s=Zn(n),a=tt(t,n,e,i);return s(),jt(),a});return r?l.unshift(o):l.push(o),o}}const Et=e=>(t,n=$e)=>(!er||e==="sp")&&Nr(e,(...r)=>t(...r),n),wc=Et("bm"),Ue=Et("m"),Ac=Et("bu"),Cc=Et("u"),Hr=Et("bum"),jr=Et("um"),xc=Et("sp"),Lc=Et("rtg"),Sc=Et("rtc");function Tc(e,t=$e){Nr("ec",e,t)}function Dt(e,t,n,r){let l;const o=n;if(te(e)||Re(e)){l=new Array(e.length);for(let i=0,s=e.length;it(i,s,void 0,o));else{const i=Object.keys(e);l=new Array(i.length);for(let s=0,a=i.length;sSr(t)?!(t.type===qe||t.type===ye&&!os(t.children)):!0)?e:null}const bl=e=>e?Es(e)?Vr(e)||e.proxy:bl(e.parent):null,Rn=Te(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>bl(e.parent),$root:e=>bl(e.root),$emit:e=>e.emit,$options:e=>Ul(e),$forceUpdate:e=>e.f||(e.f=()=>{e.effect.dirty=!0,Dr(e.update)}),$nextTick:e=>e.n||(e.n=bn.bind(e.proxy)),$watch:e=>mc.bind(e)}),tl=(e,t)=>e!==Ee&&!e.__isScriptSetup&&he(e,t),Rc={get({_:e},t){if(t==="__v_skip")return!0;const{ctx:n,setupState:r,data:l,props:o,accessCache:i,type:s,appContext:a}=e;let c;if(t[0]!=="$"){const m=i[t];if(m!==void 0)switch(m){case 1:return r[t];case 2:return l[t];case 4:return n[t];case 3:return o[t]}else{if(tl(r,t))return i[t]=1,r[t];if(l!==Ee&&he(l,t))return i[t]=2,l[t];if((c=e.propsOptions[0])&&he(c,t))return i[t]=3,o[t];if(n!==Ee&&he(n,t))return i[t]=4,n[t];yl&&(i[t]=0)}}const u=Rn[t];let f,d;if(u)return t==="$attrs"&&Je(e.attrs,"get",""),u(e);if((f=s.__cssModules)&&(f=f[t]))return f;if(n!==Ee&&he(n,t))return i[t]=4,n[t];if(d=a.config.globalProperties,he(d,t))return d[t]},set({_:e},t,n){const{data:r,setupState:l,ctx:o}=e;return tl(l,t)?(l[t]=n,!0):r!==Ee&&he(r,t)?(r[t]=n,!0):he(e.props,t)||t[0]==="$"&&t.slice(1)in e?!1:(o[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:r,appContext:l,propsOptions:o}},i){let s;return!!n[i]||e!==Ee&&he(e,i)||tl(t,i)||(s=o[0])&&he(s,i)||he(r,i)||he(Rn,i)||he(l.config.globalProperties,i)},defineProperty(e,t,n){return n.get!=null?e._.accessCache[t]=0:he(n,"value")&&this.set(e,t,n.value,null),Reflect.defineProperty(e,t,n)}};function Eo(e){return te(e)?e.reduce((t,n)=>(t[n]=null,t),{}):e}let yl=!0;function Pc(e){const t=Ul(e),n=e.proxy,r=e.ctx;yl=!1,t.beforeCreate&&ko(t.beforeCreate,e,"bc");const{data:l,computed:o,methods:i,watch:s,provide:a,inject:c,created:u,beforeMount:f,mounted:d,beforeUpdate:m,updated:g,activated:y,deactivated:w,beforeDestroy:T,beforeUnmount:x,destroyed:v,unmounted:k,render:N,renderTracked:O,renderTriggered:D,errorCaptured:_,serverPrefetch:G,expose:L,inheritAttrs:U,components:E,directives:B,filters:ne}=t;if(c&&Oc(c,r,null),i)for(const J in i){const W=i[J];oe(W)&&(r[J]=W.bind(n))}if(l){const J=l.call(n,n);ke(J)&&(e.data=Yn(J))}if(yl=!0,o)for(const J in o){const W=o[J],Oe=oe(W)?W.bind(n,n):oe(W.get)?W.get.bind(n,n):et,De=!oe(W)&&oe(W.set)?W.set.bind(n):et,We=I({get:Oe,set:De});Object.defineProperty(r,J,{enumerable:!0,configurable:!0,get:()=>We.value,set:Ne=>We.value=Ne})}if(s)for(const J in s)is(s[J],r,n,J);if(a){const J=oe(a)?a.call(n):a;Reflect.ownKeys(J).forEach(W=>{en(W,J[W])})}u&&ko(u,e,"c");function P(J,W){te(W)?W.forEach(Oe=>J(Oe.bind(n))):W&&J(W.bind(n))}if(P(wc,f),P(Ue,d),P(Ac,m),P(Cc,g),P(yc,y),P(Ec,w),P(Tc,_),P(Sc,O),P(Lc,D),P(Hr,x),P(jr,k),P(xc,G),te(L))if(L.length){const J=e.exposed||(e.exposed={});L.forEach(W=>{Object.defineProperty(J,W,{get:()=>n[W],set:Oe=>n[W]=Oe})})}else e.exposed||(e.exposed={});N&&e.render===et&&(e.render=N),U!=null&&(e.inheritAttrs=U),E&&(e.components=E),B&&(e.directives=B)}function Oc(e,t,n=et){te(e)&&(e=El(e));for(const r in e){const l=e[r];let o;ke(l)?"default"in l?o=Ve(l.from||r,l.default,!0):o=Ve(l.from||r):o=Ve(l),je(o)?Object.defineProperty(t,r,{enumerable:!0,configurable:!0,get:()=>o.value,set:i=>o.value=i}):t[r]=o}}function ko(e,t,n){tt(te(e)?e.map(r=>r.bind(t.proxy)):e.bind(t.proxy),t,n)}function is(e,t,n,r){const l=r.includes(".")?es(n,r):()=>n[r];if(Re(e)){const o=t[e];oe(o)&&Me(l,o)}else if(oe(e))Me(l,e.bind(n));else if(ke(e))if(te(e))e.forEach(o=>is(o,t,n,r));else{const o=oe(e.handler)?e.handler.bind(n):t[e.handler];oe(o)&&Me(l,o,e)}}function Ul(e){const t=e.type,{mixins:n,extends:r}=t,{mixins:l,optionsCache:o,config:{optionMergeStrategies:i}}=e.appContext,s=o.get(t);let a;return s?a=s:!l.length&&!n&&!r?a=t:(a={},l.length&&l.forEach(c=>xr(a,c,i,!0)),xr(a,t,i)),ke(t)&&o.set(t,a),a}function xr(e,t,n,r=!1){const{mixins:l,extends:o}=t;o&&xr(e,o,n,!0),l&&l.forEach(i=>xr(e,i,n,!0));for(const i in t)if(!(r&&i==="expose")){const s=Ic[i]||n&&n[i];e[i]=s?s(e[i],t[i]):t[i]}return e}const Ic={data:wo,props:Ao,emits:Ao,methods:xn,computed:xn,beforeCreate:ze,created:ze,beforeMount:ze,mounted:ze,beforeUpdate:ze,updated:ze,beforeDestroy:ze,beforeUnmount:ze,destroyed:ze,unmounted:ze,activated:ze,deactivated:ze,errorCaptured:ze,serverPrefetch:ze,components:xn,directives:xn,watch:$c,provide:wo,inject:Fc};function wo(e,t){return t?e?function(){return Te(oe(e)?e.call(this,this):e,oe(t)?t.call(this,this):t)}:t:e}function Fc(e,t){return xn(El(e),El(t))}function El(e){if(te(e)){const t={};for(let n=0;n1)return n&&oe(t)?t.call(r&&r.proxy):t}}const as={},cs=()=>Object.create(as),us=e=>Object.getPrototypeOf(e)===as;function Mc(e,t,n,r=!1){const l={},o=cs();e.propsDefaults=Object.create(null),fs(e,t,l,o);for(const i in e.propsOptions[0])i in l||(l[i]=void 0);n?e.props=r?l:zi(l):e.type.props?e.props=l:e.props=o,e.attrs=o}function Nc(e,t,n,r){const{props:l,attrs:o,vnode:{patchFlag:i}}=e,s=pe(l),[a]=e.propsOptions;let c=!1;if((r||i>0)&&!(i&16)){if(i&8){const u=e.vnode.dynamicProps;for(let f=0;f{a=!0;const[d,m]=ds(f,t,!0);Te(i,d),m&&s.push(...m)};!n&&t.mixins.length&&t.mixins.forEach(u),e.extends&&u(e.extends),e.mixins&&e.mixins.forEach(u)}if(!o&&!a)return ke(e)&&r.set(e,cn),cn;if(te(o))for(let u=0;u-1,m[1]=y<0||g-1||he(m,"default"))&&s.push(f)}}}const c=[i,s];return ke(e)&&r.set(e,c),c}function Co(e){return e[0]!=="$"&&!fn(e)}function xo(e){return e===null?"null":typeof e=="function"?e.name||"":typeof e=="object"&&e.constructor&&e.constructor.name||""}function Lo(e,t){return xo(e)===xo(t)}function So(e,t){return te(t)?t.findIndex(n=>Lo(n,e)):oe(t)&&Lo(t,e)?0:-1}const hs=e=>e[0]==="_"||e==="$stable",Wl=e=>te(e)?e.map(lt):[lt(e)],Hc=(e,t,n)=>{if(t._n)return t;const r=Le((...l)=>Wl(t(...l)),n);return r._c=!1,r},ps=(e,t,n)=>{const r=e._ctx;for(const l in e){if(hs(l))continue;const o=e[l];if(oe(o))t[l]=Hc(l,o,r);else if(o!=null){const i=Wl(o);t[l]=()=>i}}},ms=(e,t)=>{const n=Wl(t);e.slots.default=()=>n},jc=(e,t)=>{const n=e.slots=cs();if(e.vnode.shapeFlag&32){const r=t._;r?(Te(n,t),xi(n,"_",r,!0)):ps(t,n)}else t&&ms(e,t)},zc=(e,t,n)=>{const{vnode:r,slots:l}=e;let o=!0,i=Ee;if(r.shapeFlag&32){const s=t._;s?n&&s===1?o=!1:(Te(l,t),!n&&s===1&&delete l._):(o=!t.$stable,ps(t,l)),i=t}else t&&(ms(e,t),i={default:1});if(o)for(const s in l)!hs(s)&&i[s]==null&&delete l[s]};function Lr(e,t,n,r,l=!1){if(te(e)){e.forEach((d,m)=>Lr(d,t&&(te(t)?t[m]:t),n,r,l));return}if(hn(r)&&!l)return;const o=r.shapeFlag&4?Vr(r.component)||r.component.proxy:r.el,i=l?null:o,{i:s,r:a}=e,c=t&&t.r,u=s.refs===Ee?s.refs={}:s.refs,f=s.setupState;if(c!=null&&c!==a&&(Re(c)?(u[c]=null,he(f,c)&&(f[c]=null)):je(c)&&(c.value=null)),oe(a))$t(a,s,12,[i,u]);else{const d=Re(a),m=je(a);if(d||m){const g=()=>{if(e.f){const y=d?he(f,a)?f[a]:u[a]:a.value;l?te(y)&&Ol(y,o):te(y)?y.includes(o)||y.push(o):d?(u[a]=[o],he(f,a)&&(f[a]=u[a])):(a.value=[o],e.k&&(u[e.k]=a.value))}else d?(u[a]=i,he(f,a)&&(f[a]=i)):m&&(a.value=i,e.k&&(u[e.k]=i))};i?(g.id=-1,Ke(g,n)):g()}}}let xt=!1;const Vc=e=>e.namespaceURI.includes("svg")&&e.tagName!=="foreignObject",Uc=e=>e.namespaceURI.includes("MathML"),fr=e=>{if(Vc(e))return"svg";if(Uc(e))return"mathml"},dr=e=>e.nodeType===8;function Wc(e){const{mt:t,p:n,o:{patchProp:r,createText:l,nextSibling:o,parentNode:i,remove:s,insert:a,createComment:c}}=e,u=(v,k)=>{if(!k.hasChildNodes()){n(null,v,k),wr(),k._vnode=v;return}xt=!1,f(k.firstChild,v,null,null,null),wr(),k._vnode=v,xt&&console.error("Hydration completed but contains mismatches.")},f=(v,k,N,O,D,_=!1)=>{_=_||!!k.dynamicChildren;const G=dr(v)&&v.data==="[",L=()=>y(v,k,N,O,D,G),{type:U,ref:E,shapeFlag:B,patchFlag:ne}=k;let se=v.nodeType;k.el=v,ne===-2&&(_=!1,k.dynamicChildren=null);let P=null;switch(U){case mn:se!==3?k.children===""?(a(k.el=l(""),i(v),v),P=v):P=L():(v.data!==k.children&&(xt=!0,v.data=k.children),P=o(v));break;case qe:x(v)?(P=o(v),T(k.el=v.content.firstChild,v,N)):se!==8||G?P=L():P=o(v);break;case On:if(G&&(v=o(v),se=v.nodeType),se===1||se===3){P=v;const J=!k.children.length;for(let W=0;W{_=_||!!k.dynamicChildren;const{type:G,props:L,patchFlag:U,shapeFlag:E,dirs:B,transition:ne}=k,se=G==="input"||G==="option";if(se||U!==-1){B&&ft(k,null,N,"created");let P=!1;if(x(v)){P=gs(O,ne)&&N&&N.vnode.props&&N.vnode.props.appear;const W=v.content.firstChild;P&&ne.beforeEnter(W),T(W,v,N),k.el=v=W}if(E&16&&!(L&&(L.innerHTML||L.textContent))){let W=m(v.firstChild,k,v,N,O,D,_);for(;W;){xt=!0;const Oe=W;W=W.nextSibling,s(Oe)}}else E&8&&v.textContent!==k.children&&(xt=!0,v.textContent=k.children);if(L)if(se||!_||U&48)for(const W in L)(se&&(W.endsWith("value")||W==="indeterminate")||qn(W)&&!fn(W)||W[0]===".")&&r(v,W,null,L[W],void 0,void 0,N);else L.onClick&&r(v,"onClick",null,L.onClick,void 0,void 0,N);let J;(J=L&&L.onVnodeBeforeMount)&&Ze(J,N,k),B&&ft(k,null,N,"beforeMount"),((J=L&&L.onVnodeMounted)||B||P)&&Zi(()=>{J&&Ze(J,N,k),P&&ne.enter(v),B&&ft(k,null,N,"mounted")},O)}return v.nextSibling},m=(v,k,N,O,D,_,G)=>{G=G||!!k.dynamicChildren;const L=k.children,U=L.length;for(let E=0;E{const{slotScopeIds:G}=k;G&&(D=D?D.concat(G):G);const L=i(v),U=m(o(v),k,L,N,O,D,_);return U&&dr(U)&&U.data==="]"?o(k.anchor=U):(xt=!0,a(k.anchor=c("]"),L,U),U)},y=(v,k,N,O,D,_)=>{if(xt=!0,k.el=null,_){const U=w(v);for(;;){const E=o(v);if(E&&E!==U)s(E);else break}}const G=o(v),L=i(v);return s(v),n(null,k,L,G,N,O,fr(L),D),G},w=(v,k="[",N="]")=>{let O=0;for(;v;)if(v=o(v),v&&dr(v)&&(v.data===k&&O++,v.data===N)){if(O===0)return o(v);O--}return v},T=(v,k,N)=>{const O=k.parentNode;O&&O.replaceChild(v,k);let D=N;for(;D;)D.vnode.el===k&&(D.vnode.el=D.subTree.el=v),D=D.parent},x=v=>v.nodeType===1&&v.tagName.toLowerCase()==="template";return[u,f]}const Ke=Zi;function Kc(e){return qc(e,Wc)}function qc(e,t){const n=Li();n.__VUE__=!0;const{insert:r,remove:l,patchProp:o,createElement:i,createText:s,createComment:a,setText:c,setElementText:u,parentNode:f,nextSibling:d,setScopeId:m=et,insertStaticContent:g}=e,y=(h,p,b,S=null,A=null,F=null,j=void 0,$=null,M=!!p.dynamicChildren)=>{if(h===p)return;h&&!Jt(h,p)&&(S=C(h),Ne(h,A,F,!0),h=null),p.patchFlag===-2&&(M=!1,p.dynamicChildren=null);const{type:R,ref:K,shapeFlag:Q}=p;switch(R){case mn:w(h,p,b,S);break;case qe:T(h,p,b,S);break;case On:h==null&&x(p,b,S,j);break;case ye:E(h,p,b,S,A,F,j,$,M);break;default:Q&1?N(h,p,b,S,A,F,j,$,M):Q&6?B(h,p,b,S,A,F,j,$,M):(Q&64||Q&128)&&R.process(h,p,b,S,A,F,j,$,M,q)}K!=null&&A&&Lr(K,h&&h.ref,F,p||h,!p)},w=(h,p,b,S)=>{if(h==null)r(p.el=s(p.children),b,S);else{const A=p.el=h.el;p.children!==h.children&&c(A,p.children)}},T=(h,p,b,S)=>{h==null?r(p.el=a(p.children||""),b,S):p.el=h.el},x=(h,p,b,S)=>{[h.el,h.anchor]=g(h.children,p,b,S,h.el,h.anchor)},v=({el:h,anchor:p},b,S)=>{let A;for(;h&&h!==p;)A=d(h),r(h,b,S),h=A;r(p,b,S)},k=({el:h,anchor:p})=>{let b;for(;h&&h!==p;)b=d(h),l(h),h=b;l(p)},N=(h,p,b,S,A,F,j,$,M)=>{p.type==="svg"?j="svg":p.type==="math"&&(j="mathml"),h==null?O(p,b,S,A,F,j,$,M):G(h,p,A,F,j,$,M)},O=(h,p,b,S,A,F,j,$)=>{let M,R;const{props:K,shapeFlag:Q,transition:Y,dirs:le}=h;if(M=h.el=i(h.type,F,K&&K.is,K),Q&8?u(M,h.children):Q&16&&_(h.children,M,null,S,A,nl(h,F),j,$),le&&ft(h,null,S,"created"),D(M,h,h.scopeId,j,S),K){for(const _e in K)_e!=="value"&&!fn(_e)&&o(M,_e,null,K[_e],F,h.children,S,A,Ie);"value"in K&&o(M,"value",null,K.value,F),(R=K.onVnodeBeforeMount)&&Ze(R,S,h)}le&&ft(h,null,S,"beforeMount");const ae=gs(A,Y);ae&&Y.beforeEnter(M),r(M,p,b),((R=K&&K.onVnodeMounted)||ae||le)&&Ke(()=>{R&&Ze(R,S,h),ae&&Y.enter(M),le&&ft(h,null,S,"mounted")},A)},D=(h,p,b,S,A)=>{if(b&&m(h,b),S)for(let F=0;F{for(let R=M;R{const $=p.el=h.el;let{patchFlag:M,dynamicChildren:R,dirs:K}=p;M|=h.patchFlag&16;const Q=h.props||Ee,Y=p.props||Ee;let le;if(b&&Vt(b,!1),(le=Y.onVnodeBeforeUpdate)&&Ze(le,b,p,h),K&&ft(p,h,b,"beforeUpdate"),b&&Vt(b,!0),R?L(h.dynamicChildren,R,$,b,S,nl(p,A),F):j||W(h,p,$,null,b,S,nl(p,A),F,!1),M>0){if(M&16)U($,p,Q,Y,b,S,A);else if(M&2&&Q.class!==Y.class&&o($,"class",null,Y.class,A),M&4&&o($,"style",Q.style,Y.style,A),M&8){const ae=p.dynamicProps;for(let _e=0;_e{le&&Ze(le,b,p,h),K&&ft(p,h,b,"updated")},S)},L=(h,p,b,S,A,F,j)=>{for(let $=0;${if(b!==S){if(b!==Ee)for(const $ in b)!fn($)&&!($ in S)&&o(h,$,b[$],null,j,p.children,A,F,Ie);for(const $ in S){if(fn($))continue;const M=S[$],R=b[$];M!==R&&$!=="value"&&o(h,$,R,M,j,p.children,A,F,Ie)}"value"in S&&o(h,"value",b.value,S.value,j)}},E=(h,p,b,S,A,F,j,$,M)=>{const R=p.el=h?h.el:s(""),K=p.anchor=h?h.anchor:s("");let{patchFlag:Q,dynamicChildren:Y,slotScopeIds:le}=p;le&&($=$?$.concat(le):le),h==null?(r(R,b,S),r(K,b,S),_(p.children||[],b,K,A,F,j,$,M)):Q>0&&Q&64&&Y&&h.dynamicChildren?(L(h.dynamicChildren,Y,b,A,F,j,$),(p.key!=null||A&&p===A.subTree)&&vs(h,p,!0)):W(h,p,b,K,A,F,j,$,M)},B=(h,p,b,S,A,F,j,$,M)=>{p.slotScopeIds=$,h==null?p.shapeFlag&512?A.ctx.activate(p,b,S,j,M):ne(p,b,S,A,F,j,M):se(h,p,M)},ne=(h,p,b,S,A,F,j)=>{const $=h.component=nu(h,S,A);if(Qn(h)&&($.ctx.renderer=q),ru($),$.asyncDep){if(A&&A.registerDep($,P),!h.el){const M=$.subTree=ie(qe);T(null,M,p,b)}}else P($,h,p,b,A,F,j)},se=(h,p,b)=>{const S=p.component=h.component;if(sc(h,p,b))if(S.asyncDep&&!S.asyncResolved){J(S,p,b);return}else S.next=p,tc(S.update),S.effect.dirty=!0,S.update();else p.el=h.el,S.vnode=p},P=(h,p,b,S,A,F,j)=>{const $=()=>{if(h.isMounted){let{next:K,bu:Q,u:Y,parent:le,vnode:ae}=h;{const on=_s(h);if(on){K&&(K.el=ae.el,J(h,K,j)),on.asyncDep.then(()=>{h.isUnmounted||$()});return}}let _e=K,we;Vt(h,!1),K?(K.el=ae.el,J(h,K,j)):K=ae,Q&&Yr(Q),(we=K.props&&K.props.onVnodeBeforeUpdate)&&Ze(we,le,K,ae),Vt(h,!0);const Fe=Qr(h),rt=h.subTree;h.subTree=Fe,y(rt,Fe,f(rt.el),C(rt),h,A,F),K.el=Fe.el,_e===null&&ac(h,Fe.el),Y&&Ke(Y,A),(we=K.props&&K.props.onVnodeUpdated)&&Ke(()=>Ze(we,le,K,ae),A)}else{let K;const{el:Q,props:Y}=p,{bm:le,m:ae,parent:_e}=h,we=hn(p);if(Vt(h,!1),le&&Yr(le),!we&&(K=Y&&Y.onVnodeBeforeMount)&&Ze(K,_e,p),Vt(h,!0),Q&&ve){const Fe=()=>{h.subTree=Qr(h),ve(Q,h.subTree,h,A,null)};we?p.type.__asyncLoader().then(()=>!h.isUnmounted&&Fe()):Fe()}else{const Fe=h.subTree=Qr(h);y(null,Fe,b,S,h,A,F),p.el=Fe.el}if(ae&&Ke(ae,A),!we&&(K=Y&&Y.onVnodeMounted)){const Fe=p;Ke(()=>Ze(K,_e,Fe),A)}(p.shapeFlag&256||_e&&hn(_e.vnode)&&_e.vnode.shapeFlag&256)&&h.a&&Ke(h.a,A),h.isMounted=!0,p=b=S=null}},M=h.effect=new Fl($,et,()=>Dr(R),h.scope),R=h.update=()=>{M.dirty&&M.run()};R.id=h.uid,Vt(h,!0),R()},J=(h,p,b)=>{p.component=h;const S=h.vnode.props;h.vnode=p,h.next=null,Nc(h,p.props,S,b),zc(h,p.children,b),Ht(),vo(h),jt()},W=(h,p,b,S,A,F,j,$,M=!1)=>{const R=h&&h.children,K=h?h.shapeFlag:0,Q=p.children,{patchFlag:Y,shapeFlag:le}=p;if(Y>0){if(Y&128){De(R,Q,b,S,A,F,j,$,M);return}else if(Y&256){Oe(R,Q,b,S,A,F,j,$,M);return}}le&8?(K&16&&Ie(R,A,F),Q!==R&&u(b,Q)):K&16?le&16?De(R,Q,b,S,A,F,j,$,M):Ie(R,A,F,!0):(K&8&&u(b,""),le&16&&_(Q,b,S,A,F,j,$,M))},Oe=(h,p,b,S,A,F,j,$,M)=>{h=h||cn,p=p||cn;const R=h.length,K=p.length,Q=Math.min(R,K);let Y;for(Y=0;YK?Ie(h,A,F,!0,!1,Q):_(p,b,S,A,F,j,$,M,Q)},De=(h,p,b,S,A,F,j,$,M)=>{let R=0;const K=p.length;let Q=h.length-1,Y=K-1;for(;R<=Q&&R<=Y;){const le=h[R],ae=p[R]=M?Pt(p[R]):lt(p[R]);if(Jt(le,ae))y(le,ae,b,null,A,F,j,$,M);else break;R++}for(;R<=Q&&R<=Y;){const le=h[Q],ae=p[Y]=M?Pt(p[Y]):lt(p[Y]);if(Jt(le,ae))y(le,ae,b,null,A,F,j,$,M);else break;Q--,Y--}if(R>Q){if(R<=Y){const le=Y+1,ae=leY)for(;R<=Q;)Ne(h[R],A,F,!0),R++;else{const le=R,ae=R,_e=new Map;for(R=ae;R<=Y;R++){const Ye=p[R]=M?Pt(p[R]):lt(p[R]);Ye.key!=null&&_e.set(Ye.key,R)}let we,Fe=0;const rt=Y-ae+1;let on=!1,oo=0;const En=new Array(rt);for(R=0;R=rt){Ne(Ye,A,F,!0);continue}let ut;if(Ye.key!=null)ut=_e.get(Ye.key);else for(we=ae;we<=Y;we++)if(En[we-ae]===0&&Jt(Ye,p[we])){ut=we;break}ut===void 0?Ne(Ye,A,F,!0):(En[ut-ae]=R+1,ut>=oo?oo=ut:on=!0,y(Ye,p[ut],b,null,A,F,j,$,M),Fe++)}const io=on?Gc(En):cn;for(we=io.length-1,R=rt-1;R>=0;R--){const Ye=ae+R,ut=p[Ye],so=Ye+1{const{el:F,type:j,transition:$,children:M,shapeFlag:R}=h;if(R&6){We(h.component.subTree,p,b,S);return}if(R&128){h.suspense.move(p,b,S);return}if(R&64){j.move(h,p,b,q);return}if(j===ye){r(F,p,b);for(let Q=0;Q$.enter(F),A);else{const{leave:Q,delayLeave:Y,afterLeave:le}=$,ae=()=>r(F,p,b),_e=()=>{Q(F,()=>{ae(),le&&le()})};Y?Y(F,ae,_e):_e()}else r(F,p,b)},Ne=(h,p,b,S=!1,A=!1)=>{const{type:F,props:j,ref:$,children:M,dynamicChildren:R,shapeFlag:K,patchFlag:Q,dirs:Y}=h;if($!=null&&Lr($,null,b,h,!0),K&256){p.ctx.deactivate(h);return}const le=K&1&&Y,ae=!hn(h);let _e;if(ae&&(_e=j&&j.onVnodeBeforeUnmount)&&Ze(_e,p,h),K&6)ct(h.component,b,S);else{if(K&128){h.suspense.unmount(b,S);return}le&&ft(h,null,p,"beforeUnmount"),K&64?h.type.remove(h,p,b,A,q,S):R&&(F!==ye||Q>0&&Q&64)?Ie(R,p,b,!1,!0):(F===ye&&Q&384||!A&&K&16)&&Ie(M,p,b),S&&wt(h)}(ae&&(_e=j&&j.onVnodeUnmounted)||le)&&Ke(()=>{_e&&Ze(_e,p,h),le&&ft(h,null,p,"unmounted")},b)},wt=h=>{const{type:p,el:b,anchor:S,transition:A}=h;if(p===ye){At(b,S);return}if(p===On){k(h);return}const F=()=>{l(b),A&&!A.persisted&&A.afterLeave&&A.afterLeave()};if(h.shapeFlag&1&&A&&!A.persisted){const{leave:j,delayLeave:$}=A,M=()=>j(b,F);$?$(h.el,F,M):M()}else F()},At=(h,p)=>{let b;for(;h!==p;)b=d(h),l(h),h=b;l(p)},ct=(h,p,b)=>{const{bum:S,scope:A,update:F,subTree:j,um:$}=h;S&&Yr(S),A.stop(),F&&(F.active=!1,Ne(j,h,p,b)),$&&Ke($,p),Ke(()=>{h.isUnmounted=!0},p),p&&p.pendingBranch&&!p.isUnmounted&&h.asyncDep&&!h.asyncResolved&&h.suspenseId===p.pendingId&&(p.deps--,p.deps===0&&p.resolve())},Ie=(h,p,b,S=!1,A=!1,F=0)=>{for(let j=F;jh.shapeFlag&6?C(h.component.subTree):h.shapeFlag&128?h.suspense.next():d(h.anchor||h.el);let V=!1;const H=(h,p,b)=>{h==null?p._vnode&&Ne(p._vnode,null,null,!0):y(p._vnode||null,h,p,null,null,null,b),V||(V=!0,vo(),wr(),V=!1),p._vnode=h},q={p:y,um:Ne,m:We,r:wt,mt:ne,mc:_,pc:W,pbc:L,n:C,o:e};let fe,ve;return t&&([fe,ve]=t(q)),{render:H,hydrate:fe,createApp:Dc(H,fe)}}function nl({type:e,props:t},n){return n==="svg"&&e==="foreignObject"||n==="mathml"&&e==="annotation-xml"&&t&&t.encoding&&t.encoding.includes("html")?void 0:n}function Vt({effect:e,update:t},n){e.allowRecurse=t.allowRecurse=n}function gs(e,t){return(!e||e&&!e.pendingBranch)&&t&&!t.persisted}function vs(e,t,n=!1){const r=e.children,l=t.children;if(te(r)&&te(l))for(let o=0;o>1,e[n[s]]0&&(t[r]=n[o-1]),n[o]=r)}}for(o=n.length,i=n[o-1];o-- >0;)n[o]=i,i=t[i];return n}function _s(e){const t=e.subTree.component;if(t)return t.asyncDep&&!t.asyncResolved?t:_s(t)}const Jc=e=>e.__isTeleport,ye=Symbol.for("v-fgt"),mn=Symbol.for("v-txt"),qe=Symbol.for("v-cmt"),On=Symbol.for("v-stc"),In=[];let ot=null;function z(e=!1){In.push(ot=e?null:[])}function Yc(){In.pop(),ot=In[In.length-1]||null}let Hn=1;function To(e){Hn+=e}function bs(e){return e.dynamicChildren=Hn>0?ot||cn:null,Yc(),Hn>0&&ot&&ot.push(e),e}function Z(e,t,n,r,l,o){return bs(re(e,t,n,r,l,o,!0))}function xe(e,t,n,r,l){return bs(ie(e,t,n,r,l,!0))}function Sr(e){return e?e.__v_isVNode===!0:!1}function Jt(e,t){return e.type===t.type&&e.key===t.key}const ys=({key:e})=>e??null,_r=({ref:e,ref_key:t,ref_for:n})=>(typeof e=="number"&&(e=""+e),e!=null?Re(e)||je(e)||oe(e)?{i:Pe,r:e,k:t,f:!!n}:e:null);function re(e,t=null,n=null,r=0,l=null,o=e===ye?0:1,i=!1,s=!1){const a={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&ys(t),ref:t&&_r(t),scopeId:Xi,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetAnchor:null,staticCount:0,shapeFlag:o,patchFlag:r,dynamicProps:l,dynamicChildren:null,appContext:null,ctx:Pe};return s?(Kl(a,n),o&128&&e.normalize(a)):n&&(a.shapeFlag|=Re(n)?8:16),Hn>0&&!i&&ot&&(a.patchFlag>0||o&6)&&a.patchFlag!==32&&ot.push(a),a}const ie=Xc;function Xc(e,t=null,n=null,r=0,l=null,o=!1){if((!e||e===cc)&&(e=qe),Sr(e)){const s=Mt(e,t,!0);return n&&Kl(s,n),Hn>0&&!o&&ot&&(s.shapeFlag&6?ot[ot.indexOf(e)]=s:ot.push(s)),s.patchFlag|=-2,s}if(au(e)&&(e=e.__vccOpts),t){t=Qc(t);let{class:s,style:a}=t;s&&!Re(s)&&(t.class=Ge(s)),ke(a)&&(Vi(a)&&!te(a)&&(a=Te({},a)),t.style=Jn(a))}const i=Re(e)?1:fc(e)?128:Jc(e)?64:ke(e)?4:oe(e)?2:0;return re(e,t,n,r,l,i,o,!0)}function Qc(e){return e?Vi(e)||us(e)?Te({},e):e:null}function Mt(e,t,n=!1,r=!1){const{props:l,ref:o,patchFlag:i,children:s,transition:a}=e,c=t?wl(l||{},t):l,u={__v_isVNode:!0,__v_skip:!0,type:e.type,props:c,key:c&&ys(c),ref:t&&t.ref?n&&o?te(o)?o.concat(_r(t)):[o,_r(t)]:_r(t):o,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:s,target:e.target,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==ye?i===-1?16:i|16:i,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:a,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&Mt(e.ssContent),ssFallback:e.ssFallback&&Mt(e.ssFallback),el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce};return a&&r&&(u.transition=a.clone(u)),u}function pt(e=" ",t=0){return ie(mn,null,e,t)}function Zc(e,t){const n=ie(On,null,e);return n.staticCount=t,n}function Se(e="",t=!1){return t?(z(),xe(qe,null,e)):ie(qe,null,e)}function lt(e){return e==null||typeof e=="boolean"?ie(qe):te(e)?ie(ye,null,e.slice()):typeof e=="object"?Pt(e):ie(mn,null,String(e))}function Pt(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:Mt(e)}function Kl(e,t){let n=0;const{shapeFlag:r}=e;if(t==null)t=null;else if(te(t))n=16;else if(typeof t=="object")if(r&65){const l=t.default;l&&(l._c&&(l._d=!1),Kl(e,l()),l._c&&(l._d=!0));return}else{n=32;const l=t._;!l&&!us(t)?t._ctx=Pe:l===3&&Pe&&(Pe.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else oe(t)?(t={default:t,_ctx:Pe},n=32):(t=String(t),r&64?(n=16,t=[pt(t)]):n=8);e.children=t,e.shapeFlag|=n}function wl(...e){const t={};for(let n=0;n$e||Pe;let Tr,Al;{const e=Li(),t=(n,r)=>{let l;return(l=e[n])||(l=e[n]=[]),l.push(r),o=>{l.length>1?l.forEach(i=>i(o)):l[0](o)}};Tr=t("__VUE_INSTANCE_SETTERS__",n=>$e=n),Al=t("__VUE_SSR_SETTERS__",n=>er=n)}const Zn=e=>{const t=$e;return Tr(e),e.scope.on(),()=>{e.scope.off(),Tr(t)}},Ro=()=>{$e&&$e.scope.off(),Tr(null)};function Es(e){return e.vnode.shapeFlag&4}let er=!1;function ru(e,t=!1){t&&Al(t);const{props:n,children:r}=e.vnode,l=Es(e);Mc(e,n,l,t),jc(e,r);const o=l?lu(e,t):void 0;return t&&Al(!1),o}function lu(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=new Proxy(e.ctx,Rc);const{setup:r}=n;if(r){const l=e.setupContext=r.length>1?iu(e):null,o=Zn(e);Ht();const i=$t(r,e,0,[e.props,l]);if(jt(),o(),wi(i)){if(i.then(Ro,Ro),t)return i.then(s=>{Po(e,s,t)}).catch(s=>{Xn(s,e,0)});e.asyncDep=i}else Po(e,i,t)}else ks(e,t)}function Po(e,t,n){oe(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:ke(t)&&(e.setupState=Ki(t)),ks(e,n)}let Oo;function ks(e,t,n){const r=e.type;if(!e.render){if(!t&&Oo&&!r.render){const l=r.template||Ul(e).template;if(l){const{isCustomElement:o,compilerOptions:i}=e.appContext.config,{delimiters:s,compilerOptions:a}=r,c=Te(Te({isCustomElement:o,delimiters:s},i),a);r.render=Oo(l,c)}}e.render=r.render||et}{const l=Zn(e);Ht();try{Pc(e)}finally{jt(),l()}}}const ou={get(e,t){return Je(e,"get",""),e[t]}};function iu(e){const t=n=>{e.exposed=n||{}};return{attrs:new Proxy(e.attrs,ou),slots:e.slots,emit:e.emit,expose:t}}function Vr(e){if(e.exposed)return e.exposeProxy||(e.exposeProxy=new Proxy(Ki(Wa(e.exposed)),{get(t,n){if(n in t)return t[n];if(n in Rn)return Rn[n](e)},has(t,n){return n in t||n in Rn}}))}function su(e,t=!0){return oe(e)?e.displayName||e.name:e.name||t&&e.__name}function au(e){return oe(e)&&"__vccOpts"in e}const I=(e,t)=>Ka(e,t,er);function ce(e,t,n){const r=arguments.length;return r===2?ke(t)&&!te(t)?Sr(t)?ie(e,null,[t]):ie(e,t):ie(e,null,t):(r>3?n=Array.prototype.slice.call(arguments,2):r===3&&Sr(n)&&(n=[n]),ie(e,t,n))}const cu="3.4.27";/** +* @vue/runtime-dom v3.4.27 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/const uu="http://www.w3.org/2000/svg",fu="http://www.w3.org/1998/Math/MathML",Ot=typeof document<"u"?document:null,Io=Ot&&Ot.createElement("template"),du={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,r)=>{const l=t==="svg"?Ot.createElementNS(uu,e):t==="mathml"?Ot.createElementNS(fu,e):Ot.createElement(e,n?{is:n}:void 0);return e==="select"&&r&&r.multiple!=null&&l.setAttribute("multiple",r.multiple),l},createText:e=>Ot.createTextNode(e),createComment:e=>Ot.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>Ot.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,r,l,o){const i=n?n.previousSibling:t.lastChild;if(l&&(l===o||l.nextSibling))for(;t.insertBefore(l.cloneNode(!0),n),!(l===o||!(l=l.nextSibling)););else{Io.innerHTML=r==="svg"?`${e}`:r==="mathml"?`${e}`:e;const s=Io.content;if(r==="svg"||r==="mathml"){const a=s.firstChild;for(;a.firstChild;)s.appendChild(a.firstChild);s.removeChild(a)}t.insertBefore(s,n)}return[i?i.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}},Lt="transition",kn="animation",jn=Symbol("_vtc"),tr=(e,{slots:t})=>ce(_c,hu(e),t);tr.displayName="Transition";const ws={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String};tr.props=Te({},ts,ws);const Ut=(e,t=[])=>{te(e)?e.forEach(n=>n(...t)):e&&e(...t)},Fo=e=>e?te(e)?e.some(t=>t.length>1):e.length>1:!1;function hu(e){const t={};for(const E in e)E in ws||(t[E]=e[E]);if(e.css===!1)return t;const{name:n="v",type:r,duration:l,enterFromClass:o=`${n}-enter-from`,enterActiveClass:i=`${n}-enter-active`,enterToClass:s=`${n}-enter-to`,appearFromClass:a=o,appearActiveClass:c=i,appearToClass:u=s,leaveFromClass:f=`${n}-leave-from`,leaveActiveClass:d=`${n}-leave-active`,leaveToClass:m=`${n}-leave-to`}=e,g=pu(l),y=g&&g[0],w=g&&g[1],{onBeforeEnter:T,onEnter:x,onEnterCancelled:v,onLeave:k,onLeaveCancelled:N,onBeforeAppear:O=T,onAppear:D=x,onAppearCancelled:_=v}=t,G=(E,B,ne)=>{Wt(E,B?u:s),Wt(E,B?c:i),ne&&ne()},L=(E,B)=>{E._isLeaving=!1,Wt(E,f),Wt(E,m),Wt(E,d),B&&B()},U=E=>(B,ne)=>{const se=E?D:x,P=()=>G(B,E,ne);Ut(se,[B,P]),$o(()=>{Wt(B,E?a:o),St(B,E?u:s),Fo(se)||Bo(B,r,y,P)})};return Te(t,{onBeforeEnter(E){Ut(T,[E]),St(E,o),St(E,i)},onBeforeAppear(E){Ut(O,[E]),St(E,a),St(E,c)},onEnter:U(!1),onAppear:U(!0),onLeave(E,B){E._isLeaving=!0;const ne=()=>L(E,B);St(E,f),St(E,d),vu(),$o(()=>{E._isLeaving&&(Wt(E,f),St(E,m),Fo(k)||Bo(E,r,w,ne))}),Ut(k,[E,ne])},onEnterCancelled(E){G(E,!1),Ut(v,[E])},onAppearCancelled(E){G(E,!0),Ut(_,[E])},onLeaveCancelled(E){L(E),Ut(N,[E])}})}function pu(e){if(e==null)return null;if(ke(e))return[rl(e.enter),rl(e.leave)];{const t=rl(e);return[t,t]}}function rl(e){return ma(e)}function St(e,t){t.split(/\s+/).forEach(n=>n&&e.classList.add(n)),(e[jn]||(e[jn]=new Set)).add(t)}function Wt(e,t){t.split(/\s+/).forEach(r=>r&&e.classList.remove(r));const n=e[jn];n&&(n.delete(t),n.size||(e[jn]=void 0))}function $o(e){requestAnimationFrame(()=>{requestAnimationFrame(e)})}let mu=0;function Bo(e,t,n,r){const l=e._endId=++mu,o=()=>{l===e._endId&&r()};if(n)return setTimeout(o,n);const{type:i,timeout:s,propCount:a}=gu(e,t);if(!i)return r();const c=i+"end";let u=0;const f=()=>{e.removeEventListener(c,d),o()},d=m=>{m.target===e&&++u>=a&&f()};setTimeout(()=>{u(n[g]||"").split(", "),l=r(`${Lt}Delay`),o=r(`${Lt}Duration`),i=Do(l,o),s=r(`${kn}Delay`),a=r(`${kn}Duration`),c=Do(s,a);let u=null,f=0,d=0;t===Lt?i>0&&(u=Lt,f=i,d=o.length):t===kn?c>0&&(u=kn,f=c,d=a.length):(f=Math.max(i,c),u=f>0?i>c?Lt:kn:null,d=u?u===Lt?o.length:a.length:0);const m=u===Lt&&/\b(transform|all)(,|$)/.test(r(`${Lt}Property`).toString());return{type:u,timeout:f,propCount:d,hasTransform:m}}function Do(e,t){for(;e.lengthMo(n)+Mo(e[r])))}function Mo(e){return e==="auto"?0:Number(e.slice(0,-1).replace(",","."))*1e3}function vu(){return document.body.offsetHeight}function _u(e,t,n){const r=e[jn];r&&(t=(t?[t,...r]:[...r]).join(" ")),t==null?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}const Rr=Symbol("_vod"),As=Symbol("_vsh"),Pr={beforeMount(e,{value:t},{transition:n}){e[Rr]=e.style.display==="none"?"":e.style.display,n&&t?n.beforeEnter(e):wn(e,t)},mounted(e,{value:t},{transition:n}){n&&t&&n.enter(e)},updated(e,{value:t,oldValue:n},{transition:r}){!t!=!n&&(r?t?(r.beforeEnter(e),wn(e,!0),r.enter(e)):r.leave(e,()=>{wn(e,!1)}):wn(e,t))},beforeUnmount(e,{value:t}){wn(e,t)}};function wn(e,t){e.style.display=t?e[Rr]:"none",e[As]=!t}const bu=Symbol(""),yu=/(^|;)\s*display\s*:/;function Eu(e,t,n){const r=e.style,l=Re(n);let o=!1;if(n&&!l){if(t)if(Re(t))for(const i of t.split(";")){const s=i.slice(0,i.indexOf(":")).trim();n[s]==null&&br(r,s,"")}else for(const i in t)n[i]==null&&br(r,i,"");for(const i in n)i==="display"&&(o=!0),br(r,i,n[i])}else if(l){if(t!==n){const i=r[bu];i&&(n+=";"+i),r.cssText=n,o=yu.test(n)}}else t&&e.removeAttribute("style");Rr in e&&(e[Rr]=o?r.display:"",e[As]&&(r.display="none"))}const No=/\s*!important$/;function br(e,t,n){if(te(n))n.forEach(r=>br(e,t,r));else if(n==null&&(n=""),t.startsWith("--"))e.setProperty(t,n);else{const r=ku(e,t);No.test(n)?e.setProperty(rn(r),n.replace(No,""),"important"):e[r]=n}}const Ho=["Webkit","Moz","ms"],ll={};function ku(e,t){const n=ll[t];if(n)return n;let r=nt(t);if(r!=="filter"&&r in e)return ll[t]=r;r=Gn(r);for(let l=0;lol||(Tu.then(()=>ol=0),ol=Date.now());function Pu(e,t){const n=r=>{if(!r._vts)r._vts=Date.now();else if(r._vts<=n.attached)return;tt(Ou(r,n.value),t,5,[r])};return n.value=e,n.attached=Ru(),n}function Ou(e,t){if(te(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map(r=>l=>!l._stopped&&r&&r(l))}else return t}const Uo=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,Iu=(e,t,n,r,l,o,i,s,a)=>{const c=l==="svg";t==="class"?_u(e,r,c):t==="style"?Eu(e,n,r):qn(t)?Pl(t)||Lu(e,t,n,r,i):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):Fu(e,t,r,c))?Au(e,t,r,o,i,s,a):(t==="true-value"?e._trueValue=r:t==="false-value"&&(e._falseValue=r),wu(e,t,r,c))};function Fu(e,t,n,r){if(r)return!!(t==="innerHTML"||t==="textContent"||t in e&&Uo(t)&&oe(n));if(t==="spellcheck"||t==="draggable"||t==="translate"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA")return!1;if(t==="width"||t==="height"){const l=e.tagName;if(l==="IMG"||l==="VIDEO"||l==="CANVAS"||l==="SOURCE")return!1}return Uo(t)&&Re(n)?!1:t in e}const $u={esc:"escape",space:" ",up:"arrow-up",left:"arrow-left",right:"arrow-right",down:"arrow-down",delete:"backspace"},Bu=(e,t)=>{const n=e._withKeys||(e._withKeys={}),r=t.join(".");return n[r]||(n[r]=l=>{if(!("key"in l))return;const o=rn(l.key);if(t.some(i=>i===o||$u[i]===o))return e(l)})},Du=Te({patchProp:Iu},du);let il,Wo=!1;function Mu(){return il=Wo?il:Kc(Du),Wo=!0,il}const Nu=(...e)=>{const t=Mu().createApp(...e),{mount:n}=t;return t.mount=r=>{const l=ju(r);if(l)return n(l,!0,Hu(l))},t};function Hu(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function ju(e){return Re(e)?document.querySelector(e):e}var zu=["link","meta","script","style","noscript","template"],Vu=["title","base"],Uu=([e,t,n])=>Vu.includes(e)?e:zu.includes(e)?e==="meta"&&t.name?`${e}.${t.name}`:e==="template"&&t.id?`${e}.${t.id}`:JSON.stringify([e,Object.entries(t).map(([r,l])=>typeof l=="boolean"?l?[r,""]:null:[r,l]).filter(r=>r!=null).sort(([r],[l])=>r.localeCompare(l)),n]):null,Wu=e=>{const t=new Set,n=[];return e.forEach(r=>{const l=Uu(r);l&&!t.has(l)&&(t.add(l),n.push(r))}),n},nr=e=>/^(https?:)?\/\//.test(e),Cs=e=>/^[a-z][a-z0-9+.-]*:/.test(e),ql=e=>Object.prototype.toString.call(e)==="[object Object]",Ku=e=>{const[t,...n]=e.split(/(\?|#)/);if(!t||t.endsWith("/"))return e;let r=t.replace(/(^|\/)README.md$/i,"$1index.html");return r.endsWith(".md")?r=r.substring(0,r.length-3)+".html":r.endsWith(".html")||(r=r+".html"),r.endsWith("/index.html")&&(r=r.substring(0,r.length-10)),r+n.join("")},xs=e=>e[e.length-1]==="/"?e.slice(0,-1):e,Ls=e=>e[0]==="/"?e.slice(1):e,Ss=(e,t)=>{const n=Object.keys(e).sort((r,l)=>{const o=l.split("/").length-r.split("/").length;return o!==0?o:l.length-r.length});for(const r of n)if(t.startsWith(r))return r;return"/"},it=e=>typeof e=="string";const qu="modulepreload",Gu=function(e){return"/"+e},Ko={},ee=function(t,n,r){let l=Promise.resolve();if(n&&n.length>0){document.getElementsByTagName("link");const o=document.querySelector("meta[property=csp-nonce]"),i=(o==null?void 0:o.nonce)||(o==null?void 0:o.getAttribute("nonce"));l=Promise.all(n.map(s=>{if(s=Gu(s),s in Ko)return;Ko[s]=!0;const a=s.endsWith(".css"),c=a?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${s}"]${c}`))return;const u=document.createElement("link");if(u.rel=a?"stylesheet":qu,a||(u.as="script",u.crossOrigin=""),u.href=s,i&&u.setAttribute("nonce",i),document.head.appendChild(u),a)return new Promise((f,d)=>{u.addEventListener("load",f),u.addEventListener("error",()=>d(new Error(`Unable to preload CSS for ${s}`)))})}))}return l.then(()=>t()).catch(o=>{const i=new Event("vite:preloadError",{cancelable:!0});if(i.payload=o,window.dispatchEvent(i),!i.defaultPrevented)throw o})},Ju=JSON.parse("{}"),Yu=Object.fromEntries([["/",{loader:()=>ee(()=>import("./index.html-D8U3dmyr.js"),[]),meta:{title:"首页"}}],["/advance/Performance.html",{loader:()=>ee(()=>import("./Performance.html-DvwCq2TN.js"),[]),meta:{title:"Performance"}}],["/advance/",{loader:()=>ee(()=>import("./index.html-CwYlRy8M.js"),[]),meta:{title:"概要介绍"}}],["/advance/Webpack%E6%89%93%E5%8C%85%E5%8E%9F%E7%90%86.html",{loader:()=>ee(()=>import("./Webpack打包原理.html-D_Vm0kh6.js"),[]),meta:{title:"说Webpack 打包原理"}}],["/advance/%E5%89%8D%E7%AB%AF%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96.html",{loader:()=>ee(()=>import("./前端性能优化.html-COZTg7ue.js"),[]),meta:{title:"前端性能优化"}}],["/advance/%E5%89%8D%E7%AB%AF%E8%B7%AF%E7%94%B1%E7%9A%84%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86.html",{loader:()=>ee(()=>import("./前端路由的实现原理.html-Cpum1TjA.js"),[]),meta:{title:"前端路由的实现原理"}}],["/advance/%E6%95%B0%E6%8D%AE%E4%BB%A3%E7%90%86Proxy.html",{loader:()=>ee(()=>import("./数据代理Proxy.html-BncGCkYs.js"),[]),meta:{title:"数据代理 Proxy"}}],["/algorithm/",{loader:()=>ee(()=>import("./index.html-DVAmJa9t.js"),[]),meta:{title:"介绍"}}],["/algorithm/%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%F0%9F%8D%B0.html",{loader:()=>ee(()=>import("./二分查找🍰.html-CGobr-E8.js"),[]),meta:{title:"二分查找🍰"}}],["/algorithm/%E4%BA%8C%E5%8F%89%E6%A0%91%F0%9F%8D%88.html",{loader:()=>ee(()=>import("./二叉树🍈.html-mIpyovbq.js"),[]),meta:{title:"二叉树🍈"}}],["/algorithm/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%F0%9F%8D%93.html",{loader:()=>ee(()=>import("./动态规划🍓.html-DaiZxN1P.js"),[]),meta:{title:"动态规划🍓"}}],["/algorithm/%E5%8F%8C%E6%8C%87%E9%92%88_%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%F0%9F%8D%A8.html",{loader:()=>ee(()=>import("./双指针_滑动窗口🍨.html-Bm2XjH9A.js"),[]),meta:{title:"双指针_滑动窗口🍨"}}],["/algorithm/%E6%89%8B%E6%92%95%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84.html",{loader:()=>ee(()=>import("./手撕数据结构.html-CZWs24TO.js"),[]),meta:{title:"手撕数据结构"}}],["/algorithm/%E6%8A%80%E5%B7%A7_%E6%95%B0%E5%AD%A6%F0%9F%8D%8C.html",{loader:()=>ee(()=>import("./技巧_数学🍌.html-De2mi0NT.js"),[]),meta:{title:"技巧_数学🍌"}}],["/algorithm/%E6%A0%88_%E5%A0%86%F0%9F%8D%8A.html",{loader:()=>ee(()=>import("./栈_堆🍊.html-BzrWtMIf.js"),[]),meta:{title:"栈_堆🍊"}}],["/algorithm/%E7%9F%A9%E9%98%B5%F0%9F%8D%87.html",{loader:()=>ee(()=>import("./矩阵🍇.html-DaUTaOmB.js"),[]),meta:{title:"矩阵🍇"}}],["/algorithm/%E8%B4%AA%E5%BF%83%F0%9F%8D%89.html",{loader:()=>ee(()=>import("./贪心🍉.html-DiD3SKMy.js"),[]),meta:{title:"贪心🍉"}}],["/base/AJAX.html",{loader:()=>ee(()=>import("./AJAX.html-B6IZZan4.js"),[]),meta:{title:"异步请求 AJAX"}}],["/base/CSS3.html",{loader:()=>ee(()=>import("./CSS3.html-CII7l4IS.js"),[]),meta:{title:"CSS3"}}],["/base/JS%E6%A8%A1%E5%9D%97%E5%8C%96%E5%8E%86%E7%A8%8B.html",{loader:()=>ee(()=>import("./JS模块化历程.html-B50QRgEH.js"),[]),meta:{title:"JS 模块化历程"}}],["/base/",{loader:()=>ee(()=>import("./index.html-SEv6fOr9.js"),[]),meta:{title:"前言"}}],["/base/%E5%93%A6%EF%BC%81%E5%8F%88%E5%AD%A6%E5%88%B0%E4%BA%86%EF%BC%81.html",{loader:()=>ee(()=>import("./哦!又学到了!.html-C9i-xBd2.js"),[]),meta:{title:"哦!又学到了!"}}],["/base/%E6%89%8B%E5%86%99%E9%A2%98.html",{loader:()=>ee(()=>import("./手写题.html-DlxoGdIN.js"),[]),meta:{title:"手写题"}}],["/base/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F.html",{loader:()=>ee(()=>import("./正则表达式.html-DiuLzUKa.js"),[]),meta:{title:"正则表达式"}}],["/computer/Git.html",{loader:()=>ee(()=>import("./Git.html-ECcs3y5K.js"),[]),meta:{title:"Git"}}],["/computer/Linux.html",{loader:()=>ee(()=>import("./Linux.html-BPXSEsF-.js"),[]),meta:{title:"Linux"}}],["/computer/",{loader:()=>ee(()=>import("./index.html-DpEQMVKn.js"),[]),meta:{title:"介绍"}}],["/computer/Web%E5%BA%94%E7%94%A8%E5%AE%89%E5%85%A8.html",{loader:()=>ee(()=>import("./Web应用安全.html-C-um36wI.js"),[]),meta:{title:"Web应用安全"}}],["/computer/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F_%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86.html",{loader:()=>ee(()=>import("./操作系统_编译原理.html-CRyC5gvZ.js"),[]),meta:{title:"操作系统与编译原理"}}],["/computer/%E6%95%B0%E6%8D%AE%E5%BA%93.html",{loader:()=>ee(()=>import("./数据库.html-BgkTBb__.js"),[]),meta:{title:"数据库"}}],["/computer/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C.html",{loader:()=>ee(()=>import("./计算机网络.html-OxIjlNSK.js"),[]),meta:{title:"计算机网络"}}],["/computer/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F.html",{loader:()=>ee(()=>import("./设计模式.html-B3u9fd1m.js"),[]),meta:{title:"设计模式"}}],["/interview/codeReview.html",{loader:()=>ee(()=>import("./codeReview.html-DIjhiR9Y.js"),[]),meta:{title:"会做代码的Review吗?"}}],["/interview/coding.html",{loader:()=>ee(()=>import("./coding.html-C-Uholnr.js"),[]),meta:{title:"项目的编码规范"}}],["/interview/codingStyle.html",{loader:()=>ee(()=>import("./codingStyle.html-BCFhKJEg.js"),[]),meta:{title:"前端代码风格上的工具"}}],["/interview/CSRF.html",{loader:()=>ee(()=>import("./CSRF.html-KRQIao9r.js"),[]),meta:{title:"CSRF,如何防御CSRF攻击"}}],["/interview/other.html",{loader:()=>ee(()=>import("./other.html-CPOBY2mK.js"),[]),meta:{title:"其他的一些小问题"}}],["/interview/",{loader:()=>ee(()=>import("./index.html-D_jBZyL5.js"),[]),meta:{title:"面试经历及问题"}}],["/interview/statusCode.html",{loader:()=>ee(()=>import("./statusCode.html-Cc4EJ3fp.js"),[]),meta:{title:"项目中状态码的设置?设置在HTTP状态码还是返回业务状态码?"}}],["/intro/asset.html",{loader:()=>ee(()=>import("./asset.html-Dg4uFOz9.js"),[]),meta:{title:"学习资料"}}],["/intro/group.html",{loader:()=>ee(()=>import("./group.html-vsCGLjwS.js"),[]),meta:{title:"我的网页收藏"}}],["/intro/learn.html",{loader:()=>ee(()=>import("./learn.html-vWb14hW0.js"),[]),meta:{title:"我能学到什么"}}],["/intro/pre.html",{loader:()=>ee(()=>import("./pre.html-Oos9G24k.js"),[]),meta:{title:"前序"}}],["/intro/",{loader:()=>ee(()=>import("./index.html-B2q_J4Ev.js"),[]),meta:{title:"学习路线"}}],["/project/react-cli.html",{loader:()=>ee(()=>import("./react-cli.html-UqdOc3bu.js"),[]),meta:{title:"React 脚手架"}}],["/project/",{loader:()=>ee(()=>import("./index.html-3w8DbZRK.js"),[]),meta:{title:"介绍"}}],["/project/summary.html",{loader:()=>ee(()=>import("./summary.html-BY-_yyB5.js"),[]),meta:{title:"总结"}}],["/project/vue-cli.html",{loader:()=>ee(()=>import("./vue-cli.html-COG2D5XS.js"),[]),meta:{title:"Vue 脚手架"}}],["/404.html",{loader:()=>ee(()=>import("./404.html-BbKhE9lX.js"),[]),meta:{title:""}}]]);/*! + * vue-router v4.3.2 + * (c) 2024 Eduardo San Martin Morote + * @license MIT + */const an=typeof document<"u";function Xu(e){return e.__esModule||e[Symbol.toStringTag]==="Module"}const ge=Object.assign;function sl(e,t){const n={};for(const r in t){const l=t[r];n[r]=st(l)?l.map(e):e(l)}return n}const Fn=()=>{},st=Array.isArray,Ts=/#/g,Qu=/&/g,Zu=/\//g,ef=/=/g,tf=/\?/g,Rs=/\+/g,nf=/%5B/g,rf=/%5D/g,Ps=/%5E/g,lf=/%60/g,Os=/%7B/g,of=/%7C/g,Is=/%7D/g,sf=/%20/g;function Gl(e){return encodeURI(""+e).replace(of,"|").replace(nf,"[").replace(rf,"]")}function af(e){return Gl(e).replace(Os,"{").replace(Is,"}").replace(Ps,"^")}function Cl(e){return Gl(e).replace(Rs,"%2B").replace(sf,"+").replace(Ts,"%23").replace(Qu,"%26").replace(lf,"`").replace(Os,"{").replace(Is,"}").replace(Ps,"^")}function cf(e){return Cl(e).replace(ef,"%3D")}function uf(e){return Gl(e).replace(Ts,"%23").replace(tf,"%3F")}function ff(e){return e==null?"":uf(e).replace(Zu,"%2F")}function zn(e){try{return decodeURIComponent(""+e)}catch{}return""+e}const df=/\/$/,hf=e=>e.replace(df,"");function al(e,t,n="/"){let r,l={},o="",i="";const s=t.indexOf("#");let a=t.indexOf("?");return s=0&&(a=-1),a>-1&&(r=t.slice(0,a),o=t.slice(a+1,s>-1?s:t.length),l=e(o)),s>-1&&(r=r||t.slice(0,s),i=t.slice(s,t.length)),r=vf(r??t,n),{fullPath:r+(o&&"?")+o+i,path:r,query:l,hash:zn(i)}}function pf(e,t){const n=t.query?e(t.query):"";return t.path+(n&&"?")+n+(t.hash||"")}function qo(e,t){return!t||!e.toLowerCase().startsWith(t.toLowerCase())?e:e.slice(t.length)||"/"}function mf(e,t,n){const r=t.matched.length-1,l=n.matched.length-1;return r>-1&&r===l&&gn(t.matched[r],n.matched[l])&&Fs(t.params,n.params)&&e(t.query)===e(n.query)&&t.hash===n.hash}function gn(e,t){return(e.aliasOf||e)===(t.aliasOf||t)}function Fs(e,t){if(Object.keys(e).length!==Object.keys(t).length)return!1;for(const n in e)if(!gf(e[n],t[n]))return!1;return!0}function gf(e,t){return st(e)?Go(e,t):st(t)?Go(t,e):e===t}function Go(e,t){return st(t)?e.length===t.length&&e.every((n,r)=>n===t[r]):e.length===1&&e[0]===t}function vf(e,t){if(e.startsWith("/"))return e;if(!e)return t;const n=t.split("/"),r=e.split("/"),l=r[r.length-1];(l===".."||l===".")&&r.push("");let o=n.length-1,i,s;for(i=0;i1&&o--;else break;return n.slice(0,o).join("/")+"/"+r.slice(i).join("/")}var Vn;(function(e){e.pop="pop",e.push="push"})(Vn||(Vn={}));var $n;(function(e){e.back="back",e.forward="forward",e.unknown=""})($n||($n={}));function _f(e){if(!e)if(an){const t=document.querySelector("base");e=t&&t.getAttribute("href")||"/",e=e.replace(/^\w+:\/\/[^\/]+/,"")}else e="/";return e[0]!=="/"&&e[0]!=="#"&&(e="/"+e),hf(e)}const bf=/^[^#]+#/;function yf(e,t){return e.replace(bf,"#")+t}function Ef(e,t){const n=document.documentElement.getBoundingClientRect(),r=e.getBoundingClientRect();return{behavior:t.behavior,left:r.left-n.left-(t.left||0),top:r.top-n.top-(t.top||0)}}const Ur=()=>({left:window.scrollX,top:window.scrollY});function kf(e){let t;if("el"in e){const n=e.el,r=typeof n=="string"&&n.startsWith("#"),l=typeof n=="string"?r?document.getElementById(n.slice(1)):document.querySelector(n):n;if(!l)return;t=Ef(l,e)}else t=e;"scrollBehavior"in document.documentElement.style?window.scrollTo(t):window.scrollTo(t.left!=null?t.left:window.scrollX,t.top!=null?t.top:window.scrollY)}function Jo(e,t){return(history.state?history.state.position-t:-1)+e}const xl=new Map;function wf(e,t){xl.set(e,t)}function Af(e){const t=xl.get(e);return xl.delete(e),t}let Cf=()=>location.protocol+"//"+location.host;function $s(e,t){const{pathname:n,search:r,hash:l}=t,o=e.indexOf("#");if(o>-1){let s=l.includes(e.slice(o))?e.slice(o).length:1,a=l.slice(s);return a[0]!=="/"&&(a="/"+a),qo(a,"")}return qo(n,e)+r+l}function xf(e,t,n,r){let l=[],o=[],i=null;const s=({state:d})=>{const m=$s(e,location),g=n.value,y=t.value;let w=0;if(d){if(n.value=m,t.value=d,i&&i===g){i=null;return}w=y?d.position-y.position:0}else r(m);l.forEach(T=>{T(n.value,g,{delta:w,type:Vn.pop,direction:w?w>0?$n.forward:$n.back:$n.unknown})})};function a(){i=n.value}function c(d){l.push(d);const m=()=>{const g=l.indexOf(d);g>-1&&l.splice(g,1)};return o.push(m),m}function u(){const{history:d}=window;d.state&&d.replaceState(ge({},d.state,{scroll:Ur()}),"")}function f(){for(const d of o)d();o=[],window.removeEventListener("popstate",s),window.removeEventListener("beforeunload",u)}return window.addEventListener("popstate",s),window.addEventListener("beforeunload",u,{passive:!0}),{pauseListeners:a,listen:c,destroy:f}}function Yo(e,t,n,r=!1,l=!1){return{back:e,current:t,forward:n,replaced:r,position:window.history.length,scroll:l?Ur():null}}function Lf(e){const{history:t,location:n}=window,r={value:$s(e,n)},l={value:t.state};l.value||o(r.value,{back:null,current:r.value,forward:null,position:t.length-1,replaced:!0,scroll:null},!0);function o(a,c,u){const f=e.indexOf("#"),d=f>-1?(n.host&&document.querySelector("base")?e:e.slice(f))+a:Cf()+e+a;try{t[u?"replaceState":"pushState"](c,"",d),l.value=c}catch(m){console.error(m),n[u?"replace":"assign"](d)}}function i(a,c){const u=ge({},t.state,Yo(l.value.back,a,l.value.forward,!0),c,{position:l.value.position});o(a,u,!0),r.value=a}function s(a,c){const u=ge({},l.value,t.state,{forward:a,scroll:Ur()});o(u.current,u,!0);const f=ge({},Yo(r.value,a,null),{position:u.position+1},c);o(a,f,!1),r.value=a}return{location:r,state:l,push:s,replace:i}}function Sf(e){e=_f(e);const t=Lf(e),n=xf(e,t.state,t.location,t.replace);function r(o,i=!0){i||n.pauseListeners(),history.go(o)}const l=ge({location:"",base:e,go:r,createHref:yf.bind(null,e)},t,n);return Object.defineProperty(l,"location",{enumerable:!0,get:()=>t.location.value}),Object.defineProperty(l,"state",{enumerable:!0,get:()=>t.state.value}),l}function Tf(e){return typeof e=="string"||e&&typeof e=="object"}function Bs(e){return typeof e=="string"||typeof e=="symbol"}const _t={path:"/",name:void 0,params:{},query:{},hash:"",fullPath:"/",matched:[],meta:{},redirectedFrom:void 0},Ds=Symbol("");var Xo;(function(e){e[e.aborted=4]="aborted",e[e.cancelled=8]="cancelled",e[e.duplicated=16]="duplicated"})(Xo||(Xo={}));function vn(e,t){return ge(new Error,{type:e,[Ds]:!0},t)}function vt(e,t){return e instanceof Error&&Ds in e&&(t==null||!!(e.type&t))}const Qo="[^/]+?",Rf={sensitive:!1,strict:!1,start:!0,end:!0},Pf=/[.+*?^${}()[\]/\\]/g;function Of(e,t){const n=ge({},Rf,t),r=[];let l=n.start?"^":"";const o=[];for(const c of e){const u=c.length?[]:[90];n.strict&&!c.length&&(l+="/");for(let f=0;ft.length?t.length===1&&t[0]===80?1:-1:0}function Ff(e,t){let n=0;const r=e.score,l=t.score;for(;n0&&t[t.length-1]<0}const $f={type:0,value:""},Bf=/[a-zA-Z0-9_]/;function Df(e){if(!e)return[[]];if(e==="/")return[[$f]];if(!e.startsWith("/"))throw new Error(`Invalid path "${e}"`);function t(m){throw new Error(`ERR (${n})/"${c}": ${m}`)}let n=0,r=n;const l=[];let o;function i(){o&&l.push(o),o=[]}let s=0,a,c="",u="";function f(){c&&(n===0?o.push({type:0,value:c}):n===1||n===2||n===3?(o.length>1&&(a==="*"||a==="+")&&t(`A repeatable param (${c}) must be alone in its segment. eg: '/:ids+.`),o.push({type:1,value:c,regexp:u,repeatable:a==="*"||a==="+",optional:a==="*"||a==="?"})):t("Invalid state to consume buffer"),c="")}function d(){c+=a}for(;s{i(x)}:Fn}function i(u){if(Bs(u)){const f=r.get(u);f&&(r.delete(u),n.splice(n.indexOf(f),1),f.children.forEach(i),f.alias.forEach(i))}else{const f=n.indexOf(u);f>-1&&(n.splice(f,1),u.record.name&&r.delete(u.record.name),u.children.forEach(i),u.alias.forEach(i))}}function s(){return n}function a(u){let f=0;for(;f=0&&(u.record.path!==n[f].record.path||!Ms(u,n[f]));)f++;n.splice(f,0,u),u.record.name&&!ti(u)&&r.set(u.record.name,u)}function c(u,f){let d,m={},g,y;if("name"in u&&u.name){if(d=r.get(u.name),!d)throw vn(1,{location:u});y=d.record.name,m=ge(ei(f.params,d.keys.filter(x=>!x.optional).concat(d.parent?d.parent.keys.filter(x=>x.optional):[]).map(x=>x.name)),u.params&&ei(u.params,d.keys.map(x=>x.name))),g=d.stringify(m)}else if(u.path!=null)g=u.path,d=n.find(x=>x.re.test(g)),d&&(m=d.parse(g),y=d.record.name);else{if(d=f.name?r.get(f.name):n.find(x=>x.re.test(f.path)),!d)throw vn(1,{location:u,currentLocation:f});y=d.record.name,m=ge({},f.params,u.params),g=d.stringify(m)}const w=[];let T=d;for(;T;)w.unshift(T.record),T=T.parent;return{name:y,path:g,params:m,matched:w,meta:zf(w)}}return e.forEach(u=>o(u)),{addRoute:o,resolve:c,removeRoute:i,getRoutes:s,getRecordMatcher:l}}function ei(e,t){const n={};for(const r of t)r in e&&(n[r]=e[r]);return n}function Hf(e){return{path:e.path,redirect:e.redirect,name:e.name,meta:e.meta||{},aliasOf:void 0,beforeEnter:e.beforeEnter,props:jf(e),children:e.children||[],instances:{},leaveGuards:new Set,updateGuards:new Set,enterCallbacks:{},components:"components"in e?e.components||null:e.component&&{default:e.component}}}function jf(e){const t={},n=e.props||!1;if("component"in e)t.default=n;else for(const r in e.components)t[r]=typeof n=="object"?n[r]:n;return t}function ti(e){for(;e;){if(e.record.aliasOf)return!0;e=e.parent}return!1}function zf(e){return e.reduce((t,n)=>ge(t,n.meta),{})}function ni(e,t){const n={};for(const r in e)n[r]=r in t?t[r]:e[r];return n}function Ms(e,t){return t.children.some(n=>n===e||Ms(e,n))}function Vf(e){const t={};if(e===""||e==="?")return t;const r=(e[0]==="?"?e.slice(1):e).split("&");for(let l=0;lo&&Cl(o)):[r&&Cl(r)]).forEach(o=>{o!==void 0&&(t+=(t.length?"&":"")+n,o!=null&&(t+="="+o))})}return t}function Uf(e){const t={};for(const n in e){const r=e[n];r!==void 0&&(t[n]=st(r)?r.map(l=>l==null?null:""+l):r==null?r:""+r)}return t}const Wf=Symbol(""),li=Symbol(""),Wr=Symbol(""),Jl=Symbol(""),Ll=Symbol("");function An(){let e=[];function t(r){return e.push(r),()=>{const l=e.indexOf(r);l>-1&&e.splice(l,1)}}function n(){e=[]}return{add:t,list:()=>e.slice(),reset:n}}function It(e,t,n,r,l,o=i=>i()){const i=r&&(r.enterCallbacks[l]=r.enterCallbacks[l]||[]);return()=>new Promise((s,a)=>{const c=d=>{d===!1?a(vn(4,{from:n,to:t})):d instanceof Error?a(d):Tf(d)?a(vn(2,{from:t,to:d})):(i&&r.enterCallbacks[l]===i&&typeof d=="function"&&i.push(d),s())},u=o(()=>e.call(r&&r.instances[l],t,n,c));let f=Promise.resolve(u);e.length<3&&(f=f.then(c)),f.catch(d=>a(d))})}function cl(e,t,n,r,l=o=>o()){const o=[];for(const i of e)for(const s in i.components){let a=i.components[s];if(!(t!=="beforeRouteEnter"&&!i.instances[s]))if(Kf(a)){const u=(a.__vccOpts||a)[t];u&&o.push(It(u,n,r,i,s,l))}else{let c=a();o.push(()=>c.then(u=>{if(!u)return Promise.reject(new Error(`Couldn't resolve component "${s}" at "${i.path}"`));const f=Xu(u)?u.default:u;i.components[s]=f;const m=(f.__vccOpts||f)[t];return m&&It(m,n,r,i,s,l)()}))}}return o}function Kf(e){return typeof e=="object"||"displayName"in e||"props"in e||"__vccOpts"in e}function oi(e){const t=Ve(Wr),n=Ve(Jl),r=I(()=>{const a=X(e.to);return t.resolve(a)}),l=I(()=>{const{matched:a}=r.value,{length:c}=a,u=a[c-1],f=n.matched;if(!u||!f.length)return-1;const d=f.findIndex(gn.bind(null,u));if(d>-1)return d;const m=ii(a[c-2]);return c>1&&ii(u)===m&&f[f.length-1].path!==m?f.findIndex(gn.bind(null,a[c-2])):d}),o=I(()=>l.value>-1&&Yf(n.params,r.value.params)),i=I(()=>l.value>-1&&l.value===n.matched.length-1&&Fs(n.params,r.value.params));function s(a={}){return Jf(a)?t[X(e.replace)?"replace":"push"](X(e.to)).catch(Fn):Promise.resolve()}return{route:r,href:I(()=>r.value.href),isActive:o,isExactActive:i,navigate:s}}const qf=me({name:"RouterLink",compatConfig:{MODE:3},props:{to:{type:[String,Object],required:!0},replace:Boolean,activeClass:String,exactActiveClass:String,custom:Boolean,ariaCurrentValue:{type:String,default:"page"}},useLink:oi,setup(e,{slots:t}){const n=Yn(oi(e)),{options:r}=Ve(Wr),l=I(()=>({[si(e.activeClass,r.linkActiveClass,"router-link-active")]:n.isActive,[si(e.exactActiveClass,r.linkExactActiveClass,"router-link-exact-active")]:n.isExactActive}));return()=>{const o=t.default&&t.default(n);return e.custom?o:ce("a",{"aria-current":n.isExactActive?e.ariaCurrentValue:null,href:n.href,onClick:n.navigate,class:l.value},o)}}}),Gf=qf;function Jf(e){if(!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)&&!e.defaultPrevented&&!(e.button!==void 0&&e.button!==0)){if(e.currentTarget&&e.currentTarget.getAttribute){const t=e.currentTarget.getAttribute("target");if(/\b_blank\b/i.test(t))return}return e.preventDefault&&e.preventDefault(),!0}}function Yf(e,t){for(const n in t){const r=t[n],l=e[n];if(typeof r=="string"){if(r!==l)return!1}else if(!st(l)||l.length!==r.length||r.some((o,i)=>o!==l[i]))return!1}return!0}function ii(e){return e?e.aliasOf?e.aliasOf.path:e.path:""}const si=(e,t,n)=>e??t??n,Xf=me({name:"RouterView",inheritAttrs:!1,props:{name:{type:String,default:"default"},route:Object},compatConfig:{MODE:3},setup(e,{attrs:t,slots:n}){const r=Ve(Ll),l=I(()=>e.route||r.value),o=Ve(li,0),i=I(()=>{let c=X(o);const{matched:u}=l.value;let f;for(;(f=u[c])&&!f.components;)c++;return c}),s=I(()=>l.value.matched[i.value]);en(li,I(()=>i.value+1)),en(Wf,s),en(Ll,l);const a=ue();return Me(()=>[a.value,s.value,e.name],([c,u,f],[d,m,g])=>{u&&(u.instances[f]=c,m&&m!==u&&c&&c===d&&(u.leaveGuards.size||(u.leaveGuards=m.leaveGuards),u.updateGuards.size||(u.updateGuards=m.updateGuards))),c&&u&&(!m||!gn(u,m)||!d)&&(u.enterCallbacks[f]||[]).forEach(y=>y(c))},{flush:"post"}),()=>{const c=l.value,u=e.name,f=s.value,d=f&&f.components[u];if(!d)return ai(n.default,{Component:d,route:c});const m=f.props[u],g=m?m===!0?c.params:typeof m=="function"?m(c):m:null,w=ce(d,ge({},g,t,{onVnodeUnmounted:T=>{T.component.isUnmounted&&(f.instances[u]=null)},ref:a}));return ai(n.default,{Component:w,route:c})||w}}});function ai(e,t){if(!e)return null;const n=e(t);return n.length===1?n[0]:n}const Qf=Xf;function Zf(e){const t=Nf(e.routes,e),n=e.parseQuery||Vf,r=e.stringifyQuery||ri,l=e.history,o=An(),i=An(),s=An(),a=_n(_t);let c=_t;an&&e.scrollBehavior&&"scrollRestoration"in history&&(history.scrollRestoration="manual");const u=sl.bind(null,C=>""+C),f=sl.bind(null,ff),d=sl.bind(null,zn);function m(C,V){let H,q;return Bs(C)?(H=t.getRecordMatcher(C),q=V):q=C,t.addRoute(q,H)}function g(C){const V=t.getRecordMatcher(C);V&&t.removeRoute(V)}function y(){return t.getRoutes().map(C=>C.record)}function w(C){return!!t.getRecordMatcher(C)}function T(C,V){if(V=ge({},V||a.value),typeof C=="string"){const p=al(n,C,V.path),b=t.resolve({path:p.path},V),S=l.createHref(p.fullPath);return ge(p,b,{params:d(b.params),hash:zn(p.hash),redirectedFrom:void 0,href:S})}let H;if(C.path!=null)H=ge({},C,{path:al(n,C.path,V.path).path});else{const p=ge({},C.params);for(const b in p)p[b]==null&&delete p[b];H=ge({},C,{params:f(p)}),V.params=f(V.params)}const q=t.resolve(H,V),fe=C.hash||"";q.params=u(d(q.params));const ve=pf(r,ge({},C,{hash:af(fe),path:q.path})),h=l.createHref(ve);return ge({fullPath:ve,hash:fe,query:r===ri?Uf(C.query):C.query||{}},q,{redirectedFrom:void 0,href:h})}function x(C){return typeof C=="string"?al(n,C,a.value.path):ge({},C)}function v(C,V){if(c!==C)return vn(8,{from:V,to:C})}function k(C){return D(C)}function N(C){return k(ge(x(C),{replace:!0}))}function O(C){const V=C.matched[C.matched.length-1];if(V&&V.redirect){const{redirect:H}=V;let q=typeof H=="function"?H(C):H;return typeof q=="string"&&(q=q.includes("?")||q.includes("#")?q=x(q):{path:q},q.params={}),ge({query:C.query,hash:C.hash,params:q.path!=null?{}:C.params},q)}}function D(C,V){const H=c=T(C),q=a.value,fe=C.state,ve=C.force,h=C.replace===!0,p=O(H);if(p)return D(ge(x(p),{state:typeof p=="object"?ge({},fe,p.state):fe,force:ve,replace:h}),V||H);const b=H;b.redirectedFrom=V;let S;return!ve&&mf(r,q,H)&&(S=vn(16,{to:b,from:q}),We(q,q,!0,!1)),(S?Promise.resolve(S):L(b,q)).catch(A=>vt(A)?vt(A,2)?A:De(A):W(A,b,q)).then(A=>{if(A){if(vt(A,2))return D(ge({replace:h},x(A.to),{state:typeof A.to=="object"?ge({},fe,A.to.state):fe,force:ve}),V||b)}else A=E(b,q,!0,h,fe);return U(b,q,A),A})}function _(C,V){const H=v(C,V);return H?Promise.reject(H):Promise.resolve()}function G(C){const V=At.values().next().value;return V&&typeof V.runWithContext=="function"?V.runWithContext(C):C()}function L(C,V){let H;const[q,fe,ve]=ed(C,V);H=cl(q.reverse(),"beforeRouteLeave",C,V);for(const p of q)p.leaveGuards.forEach(b=>{H.push(It(b,C,V))});const h=_.bind(null,C,V);return H.push(h),Ie(H).then(()=>{H=[];for(const p of o.list())H.push(It(p,C,V));return H.push(h),Ie(H)}).then(()=>{H=cl(fe,"beforeRouteUpdate",C,V);for(const p of fe)p.updateGuards.forEach(b=>{H.push(It(b,C,V))});return H.push(h),Ie(H)}).then(()=>{H=[];for(const p of ve)if(p.beforeEnter)if(st(p.beforeEnter))for(const b of p.beforeEnter)H.push(It(b,C,V));else H.push(It(p.beforeEnter,C,V));return H.push(h),Ie(H)}).then(()=>(C.matched.forEach(p=>p.enterCallbacks={}),H=cl(ve,"beforeRouteEnter",C,V,G),H.push(h),Ie(H))).then(()=>{H=[];for(const p of i.list())H.push(It(p,C,V));return H.push(h),Ie(H)}).catch(p=>vt(p,8)?p:Promise.reject(p))}function U(C,V,H){s.list().forEach(q=>G(()=>q(C,V,H)))}function E(C,V,H,q,fe){const ve=v(C,V);if(ve)return ve;const h=V===_t,p=an?history.state:{};H&&(q||h?l.replace(C.fullPath,ge({scroll:h&&p&&p.scroll},fe)):l.push(C.fullPath,fe)),a.value=C,We(C,V,H,h),De()}let B;function ne(){B||(B=l.listen((C,V,H)=>{if(!ct.listening)return;const q=T(C),fe=O(q);if(fe){D(ge(fe,{replace:!0}),q).catch(Fn);return}c=q;const ve=a.value;an&&wf(Jo(ve.fullPath,H.delta),Ur()),L(q,ve).catch(h=>vt(h,12)?h:vt(h,2)?(D(h.to,q).then(p=>{vt(p,20)&&!H.delta&&H.type===Vn.pop&&l.go(-1,!1)}).catch(Fn),Promise.reject()):(H.delta&&l.go(-H.delta,!1),W(h,q,ve))).then(h=>{h=h||E(q,ve,!1),h&&(H.delta&&!vt(h,8)?l.go(-H.delta,!1):H.type===Vn.pop&&vt(h,20)&&l.go(-1,!1)),U(q,ve,h)}).catch(Fn)}))}let se=An(),P=An(),J;function W(C,V,H){De(C);const q=P.list();return q.length?q.forEach(fe=>fe(C,V,H)):console.error(C),Promise.reject(C)}function Oe(){return J&&a.value!==_t?Promise.resolve():new Promise((C,V)=>{se.add([C,V])})}function De(C){return J||(J=!C,ne(),se.list().forEach(([V,H])=>C?H(C):V()),se.reset()),C}function We(C,V,H,q){const{scrollBehavior:fe}=e;if(!an||!fe)return Promise.resolve();const ve=!H&&Af(Jo(C.fullPath,0))||(q||!H)&&history.state&&history.state.scroll||null;return bn().then(()=>fe(C,V,ve)).then(h=>h&&kf(h)).catch(h=>W(h,C,V))}const Ne=C=>l.go(C);let wt;const At=new Set,ct={currentRoute:a,listening:!0,addRoute:m,removeRoute:g,hasRoute:w,getRoutes:y,resolve:T,options:e,push:k,replace:N,go:Ne,back:()=>Ne(-1),forward:()=>Ne(1),beforeEach:o.add,beforeResolve:i.add,afterEach:s.add,onError:P.add,isReady:Oe,install(C){const V=this;C.component("RouterLink",Gf),C.component("RouterView",Qf),C.config.globalProperties.$router=V,Object.defineProperty(C.config.globalProperties,"$route",{enumerable:!0,get:()=>X(a)}),an&&!wt&&a.value===_t&&(wt=!0,k(l.location).catch(fe=>{}));const H={};for(const fe in _t)Object.defineProperty(H,fe,{get:()=>a.value[fe],enumerable:!0});C.provide(Wr,V),C.provide(Jl,zi(H)),C.provide(Ll,a);const q=C.unmount;At.add(C),C.unmount=function(){At.delete(C),At.size<1&&(c=_t,B&&B(),B=null,a.value=_t,wt=!1,J=!1),q()}}};function Ie(C){return C.reduce((V,H)=>V.then(()=>G(H)),Promise.resolve())}return ct}function ed(e,t){const n=[],r=[],l=[],o=Math.max(t.matched.length,e.matched.length);for(let i=0;ign(c,s))?r.push(s):n.push(s));const a=e.matched[i];a&&(t.matched.find(c=>gn(c,a))||l.push(a))}return[n,r,l]}function ln(){return Ve(Wr)}function zt(){return Ve(Jl)}var Yl=Symbol(""),gt=()=>{const e=Ve(Yl);if(!e)throw new Error("useClientData() is called without provider.");return e},td=()=>gt().pageComponent,pn=()=>gt().pageData,ht=()=>gt().pageFrontmatter,nd=()=>gt().pageHead,rd=()=>gt().pageLang,ld=()=>gt().pageLayout,yn=()=>gt().routeLocale,od=()=>gt().routes,Ns=()=>gt().siteData,Xl=()=>gt().siteLocaleData,id=Symbol(""),Sl=_n(Ju),Un=_n(Yu),Hs=e=>{const t=Ku(e);if(Un.value[t])return t;const n=encodeURI(t);return Un.value[n]?n:Sl.value[t]||Sl.value[n]||t},Wn=e=>{const t=Hs(e),n=Un.value[t]??{...Un.value["/404.html"],notFound:!0};return{path:t,notFound:!1,...n}},Ql=me({name:"ClientOnly",setup(e,t){const n=ue(!1);return Ue(()=>{n.value=!0}),()=>{var r,l;return n.value?(l=(r=t.slots).default)==null?void 0:l.call(r):null}}}),sd=me({name:"Content",props:{path:{type:String,required:!1,default:""}},setup(e){const t=td(),n=I(()=>{if(!e.path)return t.value;const r=Wn(e.path);return bc(()=>r.loader().then(({comp:l})=>l))});return()=>ce(n.value)}}),kt=(e={})=>e,Kr=e=>nr(e)?e:`/${Ls(e)}`,ad=e=>{if(!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)&&!e.defaultPrevented&&!(e.button!==void 0&&e.button!==0)){if(e.currentTarget){const t=e.currentTarget.getAttribute("target");if(t!=null&&t.match(/\b_blank\b/i))return}return e.preventDefault(),!0}},rr=({active:e=!1,activeClass:t="route-link-active",to:n,...r},{slots:l})=>{var a;const o=ln(),i=Hs(n),s=i.startsWith("#")||i.startsWith("?")?i:Kr(i);return ce("a",{...r,class:["route-link",{[t]:e}],href:s,onClick:(c={})=>{ad(c)?o.push(n).catch():Promise.resolve()}},(a=l.default)==null?void 0:a.call(l))};rr.displayName="RouteLink";rr.props={active:Boolean,activeClass:String,to:String};var cd="Layout",ud="en-US",Kt=Yn({resolveLayouts:e=>e.reduce((t,n)=>({...t,...n.layouts}),{}),resolvePageHead:(e,t,n)=>{const r=it(t.description)?t.description:n.description,l=[...Array.isArray(t.head)?t.head:[],...n.head,["title",{},e],["meta",{name:"description",content:r}]];return Wu(l)},resolvePageHeadTitle:(e,t)=>[e.title,t.title].filter(n=>!!n).join(" | "),resolvePageLang:(e,t)=>e.lang||t.lang||ud,resolvePageLayout:(e,t)=>{const n=it(e.frontmatter.layout)?e.frontmatter.layout:cd;if(!t[n])throw new Error(`[vuepress] Cannot resolve layout: ${n}`);return t[n]},resolveRouteLocale:(e,t)=>Ss(e,t),resolveSiteLocaleData:(e,t)=>{var n;return{...e,...e.locales[t],head:[...((n=e.locales[t])==null?void 0:n.head)??[],...e.head??[]]}}});function qr(e){return Ri()?(Aa(e),!0):!1}function mt(e){return typeof e=="function"?e():X(e)}const Zl=typeof window<"u"&&typeof document<"u";typeof WorkerGlobalScope<"u"&&globalThis instanceof WorkerGlobalScope;const fd=Object.prototype.toString,dd=e=>fd.call(e)==="[object Object]",Tl=()=>{};function js(e,t){function n(...r){return new Promise((l,o)=>{Promise.resolve(e(()=>t.apply(this,r),{fn:t,thisArg:this,args:r})).then(l).catch(o)})}return n}const zs=e=>e();function hd(e,t={}){let n,r,l=Tl;const o=s=>{clearTimeout(s),l(),l=Tl};return s=>{const a=mt(e),c=mt(t.maxWait);return n&&o(n),a<=0||c!==void 0&&c<=0?(r&&(o(r),r=null),Promise.resolve(s())):new Promise((u,f)=>{l=t.rejectOnCancel?f:u,c&&!r&&(r=setTimeout(()=>{n&&o(n),r=null,u(s())},c)),n=setTimeout(()=>{r&&o(r),r=null,u(s())},a)})}}function pd(e=zs){const t=ue(!0);function n(){t.value=!1}function r(){t.value=!0}const l=(...o)=>{t.value&&e(...o)};return{isActive:$r(t),pause:n,resume:r,eventFilter:l}}function md(e){let t;function n(){return t||(t=e()),t}return n.reset=async()=>{const r=t;t=void 0,r&&await r},n}function gd(e){return zr()}function vd(e,t=200,n={}){return js(hd(t,n),e)}function _d(e,t,n={}){const{eventFilter:r=zs,...l}=n;return Me(e,js(r,t),l)}function bd(e,t,n={}){const{eventFilter:r,...l}=n,{eventFilter:o,pause:i,resume:s,isActive:a}=pd(r);return{stop:_d(e,t,{...l,eventFilter:o}),pause:i,resume:s,isActive:a}}function eo(e,t=!0,n){gd()?Ue(e,n):t?e():bn(e)}function yd(e,t,n={}){const{immediate:r=!0}=n,l=ue(!1);let o=null;function i(){o&&(clearTimeout(o),o=null)}function s(){l.value=!1,i()}function a(...c){i(),l.value=!0,o=setTimeout(()=>{l.value=!1,o=null,e(...c)},mt(t))}return r&&(l.value=!0,Zl&&a()),qr(s),{isPending:$r(l),start:a,stop:s}}function Ed(e=!1,t={}){const{truthyValue:n=!0,falsyValue:r=!1}=t,l=je(e),o=ue(e);function i(s){if(arguments.length)return o.value=s,o.value;{const a=mt(n);return o.value=o.value===a?mt(r):a,o.value}}return l?i:[o,i]}function Xt(e){var t;const n=mt(e);return(t=n==null?void 0:n.$el)!=null?t:n}const Nt=Zl?window:void 0,Vs=Zl?window.navigator:void 0;function at(...e){let t,n,r,l;if(typeof e[0]=="string"||Array.isArray(e[0])?([n,r,l]=e,t=Nt):[t,n,r,l]=e,!t)return Tl;Array.isArray(n)||(n=[n]),Array.isArray(r)||(r=[r]);const o=[],i=()=>{o.forEach(u=>u()),o.length=0},s=(u,f,d,m)=>(u.addEventListener(f,d,m),()=>u.removeEventListener(f,d,m)),a=Me(()=>[Xt(t),mt(l)],([u,f])=>{if(i(),!u)return;const d=dd(f)?{...f}:f;o.push(...n.flatMap(m=>r.map(g=>s(u,m,g,d))))},{immediate:!0,flush:"post"}),c=()=>{a(),i()};return qr(c),c}function kd(){const e=ue(!1),t=zr();return t&&Ue(()=>{e.value=!0},t),e}function Gr(e){const t=kd();return I(()=>(t.value,!!e()))}function Us(e,t={}){const{window:n=Nt}=t,r=Gr(()=>n&&"matchMedia"in n&&typeof n.matchMedia=="function");let l;const o=ue(!1),i=c=>{o.value=c.matches},s=()=>{l&&("removeEventListener"in l?l.removeEventListener("change",i):l.removeListener(i))},a=pc(()=>{r.value&&(s(),l=n.matchMedia(mt(e)),"addEventListener"in l?l.addEventListener("change",i):l.addListener(i),o.value=l.matches)});return qr(()=>{a(),s(),l=void 0}),o}function ci(e,t={}){const{controls:n=!1,navigator:r=Vs}=t,l=Gr(()=>r&&"permissions"in r);let o;const i=typeof e=="string"?{name:e}:e,s=ue(),a=()=>{o&&(s.value=o.state)},c=md(async()=>{if(l.value){if(!o)try{o=await r.permissions.query(i),at(o,"change",a),a()}catch{s.value="prompt"}return o}});return c(),n?{state:s,isSupported:l,query:c}:s}function wd(e={}){const{navigator:t=Vs,read:n=!1,source:r,copiedDuring:l=1500,legacy:o=!1}=e,i=Gr(()=>t&&"clipboard"in t),s=ci("clipboard-read"),a=ci("clipboard-write"),c=I(()=>i.value||o),u=ue(""),f=ue(!1),d=yd(()=>f.value=!1,l);function m(){i.value&&T(s.value)?t.clipboard.readText().then(x=>{u.value=x}):u.value=w()}c.value&&n&&at(["copy","cut"],m);async function g(x=mt(r)){c.value&&x!=null&&(i.value&&T(a.value)?await t.clipboard.writeText(x):y(x),u.value=x,f.value=!0,d.start())}function y(x){const v=document.createElement("textarea");v.value=x??"",v.style.position="absolute",v.style.opacity="0",document.body.appendChild(v),v.select(),document.execCommand("copy"),v.remove()}function w(){var x,v,k;return(k=(v=(x=document==null?void 0:document.getSelection)==null?void 0:x.call(document))==null?void 0:v.toString())!=null?k:""}function T(x){return x==="granted"||x==="prompt"}return{isSupported:c,text:u,copied:f,copy:g}}const hr=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{},pr="__vueuse_ssr_handlers__",Ad=Cd();function Cd(){return pr in hr||(hr[pr]=hr[pr]||{}),hr[pr]}function xd(e,t){return Ad[e]||t}function Ld(e){return e==null?"any":e instanceof Set?"set":e instanceof Map?"map":e instanceof Date?"date":typeof e=="boolean"?"boolean":typeof e=="string"?"string":typeof e=="object"?"object":Number.isNaN(e)?"any":"number"}const Sd={boolean:{read:e=>e==="true",write:e=>String(e)},object:{read:e=>JSON.parse(e),write:e=>JSON.stringify(e)},number:{read:e=>Number.parseFloat(e),write:e=>String(e)},any:{read:e=>e,write:e=>String(e)},string:{read:e=>e,write:e=>String(e)},map:{read:e=>new Map(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e.entries()))},set:{read:e=>new Set(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e))},date:{read:e=>new Date(e),write:e=>e.toISOString()}},ui="vueuse-storage";function Ws(e,t,n,r={}){var l;const{flush:o="pre",deep:i=!0,listenToStorageChanges:s=!0,writeDefaults:a=!0,mergeDefaults:c=!1,shallow:u,window:f=Nt,eventFilter:d,onError:m=L=>{console.error(L)},initOnMounted:g}=r,y=(u?_n:ue)(typeof t=="function"?t():t);if(!n)try{n=xd("getDefaultStorage",()=>{var L;return(L=Nt)==null?void 0:L.localStorage})()}catch(L){m(L)}if(!n)return y;const w=mt(t),T=Ld(w),x=(l=r.serializer)!=null?l:Sd[T],{pause:v,resume:k}=bd(y,()=>O(y.value),{flush:o,deep:i,eventFilter:d});f&&s&&eo(()=>{at(f,"storage",_),at(f,ui,G),g&&_()}),g||_();function N(L,U){f&&f.dispatchEvent(new CustomEvent(ui,{detail:{key:e,oldValue:L,newValue:U,storageArea:n}}))}function O(L){try{const U=n.getItem(e);if(L==null)N(U,null),n.removeItem(e);else{const E=x.write(L);U!==E&&(n.setItem(e,E),N(U,E))}}catch(U){m(U)}}function D(L){const U=L?L.newValue:n.getItem(e);if(U==null)return a&&w!=null&&n.setItem(e,x.write(w)),w;if(!L&&c){const E=x.read(U);return typeof c=="function"?c(E,w):T==="object"&&!Array.isArray(E)?{...w,...E}:E}else return typeof U!="string"?U:x.read(U)}function _(L){if(!(L&&L.storageArea!==n)){if(L&&L.key==null){y.value=w;return}if(!(L&&L.key!==e)){v();try{(L==null?void 0:L.newValue)!==x.write(y.value)&&(y.value=D(L))}catch(U){m(U)}finally{L?bn(k):k()}}}}function G(L){_(L.detail)}return y}function Td(e){return Us("(prefers-color-scheme: dark)",e)}function Rd(e,t,n={}){const{window:r=Nt,...l}=n;let o;const i=Gr(()=>r&&"ResizeObserver"in r),s=()=>{o&&(o.disconnect(),o=void 0)},a=I(()=>Array.isArray(e)?e.map(f=>Xt(f)):[Xt(e)]),c=Me(a,f=>{if(s(),i.value&&r){o=new ResizeObserver(t);for(const d of f)d&&o.observe(d,l)}},{immediate:!0,flush:"post"}),u=()=>{s(),c()};return qr(u),{isSupported:i,stop:u}}function Pd(e,t={width:0,height:0},n={}){const{window:r=Nt,box:l="content-box"}=n,o=I(()=>{var f,d;return(d=(f=Xt(e))==null?void 0:f.namespaceURI)==null?void 0:d.includes("svg")}),i=ue(t.width),s=ue(t.height),{stop:a}=Rd(e,([f])=>{const d=l==="border-box"?f.borderBoxSize:l==="content-box"?f.contentBoxSize:f.devicePixelContentBoxSize;if(r&&o.value){const m=Xt(e);if(m){const g=r.getComputedStyle(m);i.value=Number.parseFloat(g.width),s.value=Number.parseFloat(g.height)}}else if(d){const m=Array.isArray(d)?d:[d];i.value=m.reduce((g,{inlineSize:y})=>g+y,0),s.value=m.reduce((g,{blockSize:y})=>g+y,0)}else i.value=f.contentRect.width,s.value=f.contentRect.height},n);eo(()=>{const f=Xt(e);f&&(i.value="offsetWidth"in f?f.offsetWidth:t.width,s.value="offsetHeight"in f?f.offsetHeight:t.height)});const c=Me(()=>Xt(e),f=>{i.value=f?t.width:0,s.value=f?t.height:0});function u(){a(),c()}return{width:i,height:s,stop:u}}function Od(e={}){const{window:t=Nt,behavior:n="auto"}=e;if(!t)return{x:ue(0),y:ue(0)};const r=ue(t.scrollX),l=ue(t.scrollY),o=I({get(){return r.value},set(s){scrollTo({left:s,behavior:n})}}),i=I({get(){return l.value},set(s){scrollTo({top:s,behavior:n})}});return at(t,"scroll",()=>{r.value=t.scrollX,l.value=t.scrollY},{capture:!1,passive:!0}),{x:o,y:i}}function Id(e={}){const{window:t=Nt,initialWidth:n=Number.POSITIVE_INFINITY,initialHeight:r=Number.POSITIVE_INFINITY,listenOrientation:l=!0,includeScrollbar:o=!0}=e,i=ue(n),s=ue(r),a=()=>{t&&(o?(i.value=t.innerWidth,s.value=t.innerHeight):(i.value=t.document.documentElement.clientWidth,s.value=t.document.documentElement.clientHeight))};if(a(),eo(a),at("resize",a,{passive:!0}),l){const c=Us("(orientation: portrait)");Me(c,()=>a())}return{width:i,height:s}}const fi=async(e,t)=>{const{path:n,query:r}=e.currentRoute.value,{scrollBehavior:l}=e.options;e.options.scrollBehavior=void 0,await e.replace({path:n,query:r,hash:t}),e.options.scrollBehavior=l},Fd=({headerLinkSelector:e,headerAnchorSelector:t,delay:n,offset:r=5})=>{const l=ln();at("scroll",vd(()=>{var g,y;const i=Math.max(window.scrollY,document.documentElement.scrollTop,document.body.scrollTop);if(Math.abs(i-0)f.some(T=>T.hash===w.hash));for(let w=0;w=(((g=T.parentElement)==null?void 0:g.offsetTop)??0)-r,k=!x||i<(((y=x.parentElement)==null?void 0:y.offsetTop)??0)-r;if(!(v&&k))continue;const O=decodeURIComponent(l.currentRoute.value.hash),D=decodeURIComponent(T.hash);if(O===D)return;if(u){for(let _=w+1;_{const t=yn();return I(()=>e[t.value]??{})},Hd=()=>{const e=od();return I(()=>Object.keys(e.value))},ul=(e,t)=>{var r;const n=(r=zr())==null?void 0:r.appContext.components;return n?e in n||nt(e)in n||Gn(nt(e))in n:!1},jd=(e,t)=>it(e)&&e.startsWith(t),qs=e=>jd(e,"/"),zd="http://.",di=(e,t)=>{if(qs(e)||typeof t!="string")return Wn(e);const n=t.slice(0,t.lastIndexOf("/"));return Wn(new URL(`${n}/${encodeURI(e)}`,zd).pathname)},Gs=e=>new Promise(t=>setTimeout(t,e));var Vd={"/":{backToTop:"返回顶部"}};const Ud=me({name:"BackToTop",setup(){const e=ht(),t=Ks(Vd),n=_n(),{height:r}=Pd(n),{height:l}=Id(),{y:o}=Od(),i=I(()=>e.value.backToTop!==!1&&o.value>100),s=I(()=>o.value/(r.value-l.value)*100);return Ue(()=>{n.value=document.body}),()=>ce(tr,{name:"back-to-top"},()=>i.value?ce("button",{type:"button",class:"vp-back-to-top-button","aria-label":t.value.backToTop,onClick:()=>{window.scrollTo({top:0,behavior:"smooth"})}},[ce("span",{class:"vp-scroll-progress",role:"progressbar","aria-labelledby":"loadinglabel","aria-valuenow":s.value},ce("svg",ce("circle",{cx:"26",cy:"26",r:"24",fill:"none",stroke:"currentColor","stroke-width":"4","stroke-dasharray":`${Math.PI*s.value*.48} ${Math.PI*(100-s.value)*.48}`}))),ce("div",{class:"back-to-top-icon"})]):null)}}),Wd=kt({rootComponents:[Ud]}),Kd=ce("svg",{class:"external-link-icon",xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",x:"0px",y:"0px",viewBox:"0 0 100 100",width:"15",height:"15"},[ce("path",{fill:"currentColor",d:"M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"}),ce("polygon",{fill:"currentColor",points:"45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"})]),qd=me({name:"ExternalLinkIcon",props:{locales:{type:Object,default:()=>({})}},setup(e){const t=yn(),n=I(()=>e.locales[t.value]??{openInNewWindow:"open in new window"});return()=>ce("span",[Kd,ce("span",{class:"external-link-icon-sr-only"},n.value.openInNewWindow)])}});var Gd={"/":{openInNewWindow:"open in new window"}};const Jd=Gd,Yd=kt({enhance({app:e}){e.component("ExternalLinkIcon",ce(qd,{locales:Jd}))}});/*! medium-zoom 1.1.0 | MIT License | https://github.com/francoischalifour/medium-zoom */var qt=Object.assign||function(e){for(var t=1;t1&&arguments[1]!==void 0?arguments[1]:{},r=window.Promise||function(E){function B(){}E(B,B)},l=function(E){var B=E.target;if(B===G){g();return}v.indexOf(B)!==-1&&y({target:B})},o=function(){if(!(N||!_.original)){var E=window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0;Math.abs(O-E)>D.scrollOffset&&setTimeout(g,150)}},i=function(E){var B=E.key||E.keyCode;(B==="Escape"||B==="Esc"||B===27)&&g()},s=function(){var E=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},B=E;if(E.background&&(G.style.background=E.background),E.container&&E.container instanceof Object&&(B.container=qt({},D.container,E.container)),E.template){var ne=yr(E.template)?E.template:document.querySelector(E.template);B.template=ne}return D=qt({},D,B),v.forEach(function(se){se.dispatchEvent(sn("medium-zoom:update",{detail:{zoom:L}}))}),L},a=function(){var E=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{};return e(qt({},D,E))},c=function(){for(var E=arguments.length,B=Array(E),ne=0;ne0?B.reduce(function(P,J){return[].concat(P,pi(J))},[]):v;return se.forEach(function(P){P.classList.remove("medium-zoom-image"),P.dispatchEvent(sn("medium-zoom:detach",{detail:{zoom:L}}))}),v=v.filter(function(P){return se.indexOf(P)===-1}),L},f=function(E,B){var ne=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{};return v.forEach(function(se){se.addEventListener("medium-zoom:"+E,B,ne)}),k.push({type:"medium-zoom:"+E,listener:B,options:ne}),L},d=function(E,B){var ne=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{};return v.forEach(function(se){se.removeEventListener("medium-zoom:"+E,B,ne)}),k=k.filter(function(se){return!(se.type==="medium-zoom:"+E&&se.listener.toString()===B.toString())}),L},m=function(){var E=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},B=E.target,ne=function(){var P={width:document.documentElement.clientWidth,height:document.documentElement.clientHeight,left:0,top:0,right:0,bottom:0},J=void 0,W=void 0;if(D.container)if(D.container instanceof Object)P=qt({},P,D.container),J=P.width-P.left-P.right-D.margin*2,W=P.height-P.top-P.bottom-D.margin*2;else{var Oe=yr(D.container)?D.container:document.querySelector(D.container),De=Oe.getBoundingClientRect(),We=De.width,Ne=De.height,wt=De.left,At=De.top;P=qt({},P,{width:We,height:Ne,left:wt,top:At})}J=J||P.width-D.margin*2,W=W||P.height-D.margin*2;var ct=_.zoomedHd||_.original,Ie=hi(ct)?J:ct.naturalWidth||J,C=hi(ct)?W:ct.naturalHeight||W,V=ct.getBoundingClientRect(),H=V.top,q=V.left,fe=V.width,ve=V.height,h=Math.min(Math.max(fe,Ie),J)/fe,p=Math.min(Math.max(ve,C),W)/ve,b=Math.min(h,p),S=(-q+(J-fe)/2+D.margin+P.left)/b,A=(-H+(W-ve)/2+D.margin+P.top)/b,F="scale("+b+") translate3d("+S+"px, "+A+"px, 0)";_.zoomed.style.transform=F,_.zoomedHd&&(_.zoomedHd.style.transform=F)};return new r(function(se){if(B&&v.indexOf(B)===-1){se(L);return}var P=function We(){N=!1,_.zoomed.removeEventListener("transitionend",We),_.original.dispatchEvent(sn("medium-zoom:opened",{detail:{zoom:L}})),se(L)};if(_.zoomed){se(L);return}if(B)_.original=B;else if(v.length>0){var J=v;_.original=J[0]}else{se(L);return}if(_.original.dispatchEvent(sn("medium-zoom:open",{detail:{zoom:L}})),O=window.pageYOffset||document.documentElement.scrollTop||document.body.scrollTop||0,N=!0,_.zoomed=Zd(_.original),document.body.appendChild(G),D.template){var W=yr(D.template)?D.template:document.querySelector(D.template);_.template=document.createElement("div"),_.template.appendChild(W.content.cloneNode(!0)),document.body.appendChild(_.template)}if(_.original.parentElement&&_.original.parentElement.tagName==="PICTURE"&&_.original.currentSrc&&(_.zoomed.src=_.original.currentSrc),document.body.appendChild(_.zoomed),window.requestAnimationFrame(function(){document.body.classList.add("medium-zoom--opened")}),_.original.classList.add("medium-zoom-image--hidden"),_.zoomed.classList.add("medium-zoom-image--opened"),_.zoomed.addEventListener("click",g),_.zoomed.addEventListener("transitionend",P),_.original.getAttribute("data-zoom-src")){_.zoomedHd=_.zoomed.cloneNode(),_.zoomedHd.removeAttribute("srcset"),_.zoomedHd.removeAttribute("sizes"),_.zoomedHd.removeAttribute("loading"),_.zoomedHd.src=_.zoomed.getAttribute("data-zoom-src"),_.zoomedHd.onerror=function(){clearInterval(Oe),console.warn("Unable to reach the zoom image target "+_.zoomedHd.src),_.zoomedHd=null,ne()};var Oe=setInterval(function(){_.zoomedHd.complete&&(clearInterval(Oe),_.zoomedHd.classList.add("medium-zoom-image--opened"),_.zoomedHd.addEventListener("click",g),document.body.appendChild(_.zoomedHd),ne())},10)}else if(_.original.hasAttribute("srcset")){_.zoomedHd=_.zoomed.cloneNode(),_.zoomedHd.removeAttribute("sizes"),_.zoomedHd.removeAttribute("loading");var De=_.zoomedHd.addEventListener("load",function(){_.zoomedHd.removeEventListener("load",De),_.zoomedHd.classList.add("medium-zoom-image--opened"),_.zoomedHd.addEventListener("click",g),document.body.appendChild(_.zoomedHd),ne()})}else ne()})},g=function(){return new r(function(E){if(N||!_.original){E(L);return}var B=function ne(){_.original.classList.remove("medium-zoom-image--hidden"),document.body.removeChild(_.zoomed),_.zoomedHd&&document.body.removeChild(_.zoomedHd),document.body.removeChild(G),_.zoomed.classList.remove("medium-zoom-image--opened"),_.template&&document.body.removeChild(_.template),N=!1,_.zoomed.removeEventListener("transitionend",ne),_.original.dispatchEvent(sn("medium-zoom:closed",{detail:{zoom:L}})),_.original=null,_.zoomed=null,_.zoomedHd=null,_.template=null,E(L)};N=!0,document.body.classList.remove("medium-zoom--opened"),_.zoomed.style.transform="",_.zoomedHd&&(_.zoomedHd.style.transform=""),_.template&&(_.template.style.transition="opacity 150ms",_.template.style.opacity=0),_.original.dispatchEvent(sn("medium-zoom:close",{detail:{zoom:L}})),_.zoomed.addEventListener("transitionend",B)})},y=function(){var E=arguments.length>0&&arguments[0]!==void 0?arguments[0]:{},B=E.target;return _.original?g():m({target:B})},w=function(){return D},T=function(){return v},x=function(){return _.original},v=[],k=[],N=!1,O=0,D=n,_={original:null,zoomed:null,zoomedHd:null,template:null};Object.prototype.toString.call(t)==="[object Object]"?D=t:(t||typeof t=="string")&&c(t),D=qt({margin:0,background:"#fff",scrollOffset:40,container:null,template:null},D);var G=Qd(D.background);document.addEventListener("click",l),document.addEventListener("keyup",i),document.addEventListener("scroll",o),window.addEventListener("resize",g);var L={open:m,close:g,toggle:y,update:s,clone:a,attach:c,detach:u,on:f,off:d,getOptions:w,getImages:T,getZoomedImage:x};return L};function th(e,t){t===void 0&&(t={});var n=t.insertAt;if(!(typeof document>"u")){var r=document.head||document.getElementsByTagName("head")[0],l=document.createElement("style");l.type="text/css",n==="top"&&r.firstChild?r.insertBefore(l,r.firstChild):r.appendChild(l),l.styleSheet?l.styleSheet.cssText=e:l.appendChild(document.createTextNode(e))}}var nh=".medium-zoom-overlay{position:fixed;top:0;right:0;bottom:0;left:0;opacity:0;transition:opacity .3s;will-change:opacity}.medium-zoom--opened .medium-zoom-overlay{cursor:pointer;cursor:zoom-out;opacity:1}.medium-zoom-image{cursor:pointer;cursor:zoom-in;transition:transform .3s cubic-bezier(.2,0,.2,1)!important}.medium-zoom-image--hidden{visibility:hidden}.medium-zoom-image--opened{position:relative;cursor:pointer;cursor:zoom-out;will-change:transform}";th(nh);const rh=Symbol("mediumZoom");var lh={};const oh=".theme-default-content > img, .theme-default-content :not(a) > img",ih=lh,sh=300,ah=kt({enhance({app:e,router:t}){const n=eh(ih);n.refresh=(r=oh)=>{n.detach(),n.attach(r)},e.provide(rh,n),t.afterEach(()=>{Gs(sh).then(()=>n.refresh())})}});/** + * NProgress, (c) 2013, 2014 Rico Sta. Cruz - http://ricostacruz.com/nprogress + * @license MIT + */const de={settings:{minimum:.08,easing:"ease",speed:200,trickle:!0,trickleRate:.02,trickleSpeed:800,barSelector:'[role="bar"]',parent:"body",template:'
'},status:null,set:e=>{const t=de.isStarted();e=fl(e,de.settings.minimum,1),de.status=e===1?null:e;const n=de.render(!t),r=n.querySelector(de.settings.barSelector),l=de.settings.speed,o=de.settings.easing;return n.offsetWidth,ch(i=>{gr(r,{transform:"translate3d("+mi(e)+"%,0,0)",transition:"all "+l+"ms "+o}),e===1?(gr(n,{transition:"none",opacity:"1"}),n.offsetWidth,setTimeout(function(){gr(n,{transition:"all "+l+"ms linear",opacity:"0"}),setTimeout(function(){de.remove(),i()},l)},l)):setTimeout(()=>i(),l)}),de},isStarted:()=>typeof de.status=="number",start:()=>{de.status||de.set(0);const e=()=>{setTimeout(()=>{de.status&&(de.trickle(),e())},de.settings.trickleSpeed)};return de.settings.trickle&&e(),de},done:e=>!e&&!de.status?de:de.inc(.3+.5*Math.random()).set(1),inc:e=>{let t=de.status;return t?(typeof e!="number"&&(e=(1-t)*fl(Math.random()*t,.1,.95)),t=fl(t+e,0,.994),de.set(t)):de.start()},trickle:()=>de.inc(Math.random()*de.settings.trickleRate),render:e=>{if(de.isRendered())return document.getElementById("nprogress");gi(document.documentElement,"nprogress-busy");const t=document.createElement("div");t.id="nprogress",t.innerHTML=de.settings.template;const n=t.querySelector(de.settings.barSelector),r=e?"-100":mi(de.status||0),l=document.querySelector(de.settings.parent);return gr(n,{transition:"all 0 linear",transform:"translate3d("+r+"%,0,0)"}),l!==document.body&&gi(l,"nprogress-custom-parent"),l==null||l.appendChild(t),t},remove:()=>{vi(document.documentElement,"nprogress-busy"),vi(document.querySelector(de.settings.parent),"nprogress-custom-parent");const e=document.getElementById("nprogress");e&&uh(e)},isRendered:()=>!!document.getElementById("nprogress")},fl=(e,t,n)=>en?n:e,mi=e=>(-1+e)*100,ch=function(){const e=[];function t(){const n=e.shift();n&&n(t)}return function(n){e.push(n),e.length===1&&t()}}(),gr=function(){const e=["Webkit","O","Moz","ms"],t={};function n(i){return i.replace(/^-ms-/,"ms-").replace(/-([\da-z])/gi,function(s,a){return a.toUpperCase()})}function r(i){const s=document.body.style;if(i in s)return i;let a=e.length;const c=i.charAt(0).toUpperCase()+i.slice(1);let u;for(;a--;)if(u=e[a]+c,u in s)return u;return i}function l(i){return i=n(i),t[i]??(t[i]=r(i))}function o(i,s,a){s=l(s),i.style[s]=a}return function(i,s){for(const a in s){const c=s[a];c!==void 0&&Object.prototype.hasOwnProperty.call(s,a)&&o(i,a,c)}}}(),Js=(e,t)=>(typeof e=="string"?e:to(e)).indexOf(" "+t+" ")>=0,gi=(e,t)=>{const n=to(e),r=n+t;Js(n,t)||(e.className=r.substring(1))},vi=(e,t)=>{const n=to(e);if(!Js(e,t))return;const r=n.replace(" "+t+" "," ");e.className=r.substring(1,r.length-1)},to=e=>(" "+(e.className||"")+" ").replace(/\s+/gi," "),uh=e=>{e&&e.parentNode&&e.parentNode.removeChild(e)},fh=()=>{Ue(()=>{const e=ln(),t=new Set;t.add(e.currentRoute.value.path),e.beforeEach(n=>{t.has(n.path)||de.start()}),e.afterEach(n=>{t.add(n.path),de.done()})})},dh=kt({setup(){fh()}}),hh=JSON.parse(`{"navbar":[{"text":"内容介绍","link":"/intro/"},{"text":"前端基础学习","link":"/base/"},{"text":"数据结构与算法","children":[{"text":"介绍","link":"/algorithm/README.md"},{"text":"手撕数据结构","link":"/algorithm/手撕数据结构.md"},{"text":"LeetCode算法题","children":[{"text":"二叉树🍈","link":"/algorithm/二叉树🍈.md"},{"text":"双指针_滑动窗口🍨","link":"/algorithm/双指针_滑动窗口🍨.md"},{"text":"二分查找🍰","link":"/algorithm/二分查找🍰.md"},{"text":"动态规划🍓","link":"/algorithm/动态规划🍓.md"},{"text":"技巧_数学🍌","link":"/algorithm/技巧_数学🍌.md"},{"text":"矩阵🍇","link":"/algorithm/矩阵🍇.md"},{"text":"栈_堆🍊","link":"/algorithm/栈_堆🍊.md"},{"text":"贪心🍉","link":"/algorithm/贪心🍉.md"}]},{"text":"算法推荐学习网站","children":[{"text":"hello算法","link":"https://www.hello-algo.com/"},{"text":"代码随想录","link":"https://www.programmercarl.com/"}]}]},{"text":"计算机基础","children":[{"text":"介绍","link":"/computer/README.md"},"/computer/计算机网络.md","/computer/Web应用安全.md","/computer/设计模式.md","/computer/Linux.md","/computer/数据库.md","/computer/操作系统_编译原理.md",{"text":"软件工程","children":[{"text":"Git","link":"/computer/Git.md"}]}]},{"text":"前端进阶","link":"/advance/"},{"text":"项目","link":"/project/"},{"text":"面试","link":"/interview/"}],"sidebar":{"/intro/":[{"text":"内容介绍","children":["/intro/pre.md","/intro/README.md","/intro/learn.md","/intro/asset.md","/intro/group.md"]}],"/base/":[{"text":"前端基础学习","children":["/base/README.md","/base/JS模块化历程.md","/base/AJAX.md","/base/手写题.md","/base/哦!又学到了!.md","/base/CSS3.md","/base/正则表达式.md"]}],"/algorithm/":[{"text":"数据结构与算法","children":["/algorithm/README.md","/algorithm/手撕数据结构.md","/algorithm/二叉树🍈.md","/algorithm/双指针_滑动窗口🍨.md","/algorithm/二分查找🍰.md","/algorithm/动态规划🍓.md","/algorithm/技巧_数学🍌.md","/algorithm/矩阵🍇.md","/algorithm/贪心🍉.md","/algorithm/栈_堆🍊.md"]}],"/project/":[{"text":"项目","children":["/project/README.md","/project/react-cli.md","/project/vue-cli.md","/project/summary.md"]}],"/interview/":[{"text":"面试","children":["/interview/README.md","/interview/other.md","/interview/CSRF.md","/interview/coding.md","/interview/codingStyle.md","/interview/codeReview.md","/interview/statusCode.md"]}],"/computer/":[{"text":"计算机基础","children":["/computer/README.md","/computer/计算机网络.md","/computer/Web应用安全.md","/computer/设计模式.md","/computer/Linux.md","/computer/数据库.md","/computer/操作系统_编译原理.md","/computer/Git.md"]}],"/advance/":[{"text":"前端进阶","children":["/advance/README.md","/advance/前端路由的实现原理.md","/advance/数据代理Proxy.md","/advance/前端性能优化.md","/advance/Performance.md","/advance/Webpack打包原理.md"]}]},"locales":{"/":{"selectLanguageName":"English"}},"colorMode":"auto","colorModeSwitch":true,"logo":null,"repo":null,"selectLanguageText":"Languages","selectLanguageAriaLabel":"Select language","sidebarDepth":2,"editLink":true,"editLinkText":"Edit this page","lastUpdated":true,"lastUpdatedText":"Last Updated","contributors":true,"contributorsText":"Contributors","notFound":["There's nothing here.","How did we get here?","That's a Four-Oh-Four.","Looks like we've got some broken links."],"backToHome":"Take me home","openInNewWindow":"open in new window","toggleColorMode":"toggle color mode","toggleSidebar":"toggle sidebar"}`),ph=ue(hh),Ys=()=>ph,Xs=Symbol(""),mh=()=>{const e=Ve(Xs);if(!e)throw new Error("useThemeLocaleData() is called without provider.");return e},gh=(e,t)=>{const{locales:n,...r}=e;return{...r,...n==null?void 0:n[t]}},vh=kt({enhance({app:e}){const t=Ys(),n=e._context.provides[Yl],r=I(()=>gh(t.value,n.routeLocale.value));e.provide(Xs,r),Object.defineProperties(e.config.globalProperties,{$theme:{get(){return t.value}},$themeLocale:{get(){return r.value}}})}}),_h=me({__name:"Badge",props:{type:{type:String,required:!1,default:"tip"},text:{type:String,required:!1,default:""},vertical:{type:String,required:!1,default:void 0}},setup(e){return(t,n)=>(z(),Z("span",{class:Ge(["badge",e.type]),style:Jn({verticalAlign:e.vertical})},[be(t.$slots,"default",{},()=>[pt(Ae(e.text),1)])],6))}}),Ce=(e,t)=>{const n=e.__vccOpts||e;for(const[r,l]of t)n[r]=l;return n},bh=Ce(_h,[["__file","Badge.vue"]]),yh=me({name:"CodeGroup",slots:Object,setup(e,{slots:t}){const n=ue([]),r=ue(-1),l=Ws("vuepress-code-group",{}),o=I(()=>n.value.map(c=>c.innerText).join(","));Ue(()=>{Me(()=>l.value[o.value],(c=-1)=>{r.value!==c&&(r.value=c)},{immediate:!0}),Me(r,c=>{l.value[o.value]!==c&&(l.value[o.value]=c)})});const i=(c=r.value)=>{c{c>0?r.value=c-1:r.value=n.value.length-1,n.value[r.value].focus()},a=(c,u)=>{c.key===" "||c.key==="Enter"?(c.preventDefault(),r.value=u):c.key==="ArrowRight"?(c.preventDefault(),i(u)):c.key==="ArrowLeft"&&(c.preventDefault(),s(u))};return()=>{var u;const c=(((u=t.default)==null?void 0:u.call(t))||[]).filter(f=>f.type.name==="CodeGroupItem").map(f=>(f.props===null&&(f.props={}),f));return c.length===0?null:(r.value<0||r.value>c.length-1?(r.value=c.findIndex(f=>f.props.active===""||f.props.active===!0),r.value===-1&&(r.value=0)):c.forEach((f,d)=>{f.props.active=d===r.value}),ce("div",{class:"code-group"},[ce("div",{class:"code-group__nav",role:"tablist"},c.map((f,d)=>{const m=d===r.value;return ce("button",{ref:g=>{g&&(n.value[d]=g)},class:{"code-group__nav-tab":!0,"code-group__nav-tab-active":m},role:"tab",ariaSelected:m,onClick:()=>r.value=d,onKeydown:g=>a(g,d)},f.props.title)})),c]))}}}),Eh=me({name:"CodeGroupItem",__name:"CodeGroupItem",props:{title:{type:String,required:!0},active:{type:Boolean,required:!1,default:!1}},setup(e){return(t,n)=>(z(),Z("div",{class:Ge(["code-group-item",{"code-group-item__active":e.active}]),role:"tabpanel"},[be(t.$slots,"default")],2))}}),kh=Ce(Eh,[["__file","CodeGroupItem.vue"]]),wh=()=>Ys(),Be=()=>mh(),Qs=Symbol(""),no=()=>{const e=Ve(Qs);if(!e)throw new Error("useDarkMode() is called without provider.");return e},Ah=()=>{const e=Be(),t=Td(),n=Ws("vuepress-color-scheme",e.value.colorMode),r=I({get(){return e.value.colorModeSwitch?n.value==="auto"?t.value:n.value==="dark":e.value.colorMode==="dark"},set(l){l===t.value?n.value="auto":n.value=l?"dark":"light"}});en(Qs,r),Ch(r)},Ch=e=>{const t=(n=e.value)=>{const r=window==null?void 0:window.document.querySelector("html");r==null||r.classList.toggle("dark",n)};Ue(()=>{Me(e,t,{immediate:!0})}),jr(()=>t())},xh="http://.",Lh=()=>{const e=ln(),t=zt();return n=>{if(n)if(qs(n))t.path!==n&&e.push(n);else if(Cs(n))window&&window.open(n);else{const r=t.path.slice(0,t.path.lastIndexOf("/"));e.push(new URL(`${r}/${encodeURI(n)}`,xh).pathname)}}};let dl=null,Cn=null;const Sh={wait:()=>dl,pending:()=>{dl=new Promise(e=>Cn=e)},resolve:()=>{Cn==null||Cn(),dl=null,Cn=null}},Zs=()=>Sh,ea=e=>{const{notFound:t,meta:n,path:r}=Wn(e);return t?{text:r,link:r}:{text:n.title||r,link:r}},_i=e=>decodeURI(e).replace(/#.*$/,"").replace(/(index)?\.(md|html)$/,""),Th=(e,t)=>{if(t.hash===e)return!0;const n=_i(t.path),r=_i(e);return n===r},ta=(e,t)=>e.link&&Th(e.link,t)?!0:e.children?e.children.some(n=>ta(n,t)):!1,na=e=>!nr(e)||/github\.com/.test(e)?"GitHub":/bitbucket\.org/.test(e)?"Bitbucket":/gitlab\.com/.test(e)?"GitLab":/gitee\.com/.test(e)?"Gitee":null,Rh={GitHub:":repo/edit/:branch/:path",GitLab:":repo/-/edit/:branch/:path",Gitee:":repo/edit/:branch/:path",Bitbucket:":repo/src/:branch/:path?mode=edit&spa=0&at=:branch&fileviewer=file-view-default"},Ph=({docsRepo:e,editLinkPattern:t})=>{if(t)return t;const n=na(e);return n!==null?Rh[n]:null},Oh=({docsRepo:e,docsBranch:t,docsDir:n,filePathRelative:r,editLinkPattern:l})=>{if(!r)return null;const o=Ph({docsRepo:e,editLinkPattern:l});return o?o.replace(/:repo/,nr(e)?e:`https://github.com/${e}`).replace(/:branch/,t).replace(/:path/,Ls(`${xs(n)}/${r}`)):null},ra=Symbol("sidebarItems"),ro=()=>{const e=Ve(ra);if(!e)throw new Error("useSidebarItems() is called without provider.");return e},Ih=()=>{const e=Be(),t=ht(),n=pn(),r=zt(),l=I(()=>Fh(t.value,e.value,n.value,r.path));en(ra,l)},Fh=(e,t,n,r)=>{const l=e.sidebar??t.sidebar??"auto",o=e.sidebarDepth??t.sidebarDepth??2;return e.home||l===!1?[]:l==="auto"?la(n,o):Array.isArray(l)?oa(n,r,l,o):ql(l)?Bh(n,r,l,o):[]},$h=(e,t)=>({text:e.title,link:e.link,children:lo(e.children,t)}),lo=(e,t)=>t>0?e.map(n=>$h(n,t-1)):[],la=(e,t)=>[{text:e.title,children:lo(e.headers,t)}],oa=(e,t,n,r)=>{const l=o=>{var s;let i;if(it(o)?i=ea(o):i=o,i.children)return{...i,children:i.children.map(a=>l(a))};if(i.link===t){const a=((s=e.headers[0])==null?void 0:s.level)===1?e.headers[0].children:e.headers;return{...i,children:lo(a,r)}}return i};return n.map(o=>l(o))},Bh=(e,t,n,r)=>{const l=Ss(n,t),o=n[l]??[];return o==="heading"?la(e,r):oa(e,t,o,r)},Dh="719px",Mh={mobile:Dh};var Kn;(function(e){e.MOBILE="mobile"})(Kn||(Kn={}));var Ei;const Nh={[Kn.MOBILE]:Number.parseInt((Ei=Mh.mobile)==null?void 0:Ei.replace("px",""),10)},ia=(e,t)=>{const n=Nh[e];Number.isInteger(n)&&(at("orientationchange",()=>t(n),!1),at("resize",()=>t(n),!1),Ue(()=>{t(n)}))},Hh={},jh={class:"theme-default-content"};function zh(e,t){const n=tn("Content");return z(),Z("div",jh,[ie(n)])}const Vh=Ce(Hh,[["render",zh],["__file","HomeContent.vue"]]),Uh={key:0,class:"features"},Wh=me({__name:"HomeFeatures",setup(e){const t=ht(),n=I(()=>Array.isArray(t.value.features)?t.value.features:[]);return(r,l)=>n.value.length?(z(),Z("div",Uh,[(z(!0),Z(ye,null,Dt(n.value,o=>(z(),Z("div",{key:o.title,class:"feature"},[re("h2",null,Ae(o.title),1),re("p",null,Ae(o.details),1)]))),128))])):Se("",!0)}}),Kh=Ce(Wh,[["__file","HomeFeatures.vue"]]),qh=["innerHTML"],Gh=["textContent"],Jh=me({__name:"HomeFooter",setup(e){const t=ht(),n=I(()=>t.value.footer),r=I(()=>t.value.footerHtml);return(l,o)=>n.value?(z(),Z(ye,{key:0},[r.value?(z(),Z("div",{key:0,class:"footer",innerHTML:n.value},null,8,qh)):(z(),Z("div",{key:1,class:"footer",textContent:Ae(n.value)},null,8,Gh))],64)):Se("",!0)}}),Yh=Ce(Jh,[["__file","HomeFooter.vue"]]),Xh=["href","rel","target","aria-label"],Qh=me({inheritAttrs:!1,__name:"AutoLink",props:{item:{type:Object,required:!0}},setup(e){const t=e,n=zt(),r=Ns(),{item:l}=Br(t),o=I(()=>nr(l.value.link)),i=I(()=>!o.value&&Cs(l.value.link)),s=I(()=>{if(!i.value){if(l.value.target)return l.value.target;if(o.value)return"_blank"}}),a=I(()=>s.value==="_blank"),c=I(()=>!o.value&&!i.value&&!a.value),u=I(()=>{if(!i.value){if(l.value.rel)return l.value.rel;if(a.value)return"noopener noreferrer"}}),f=I(()=>l.value.ariaLabel||l.value.text),d=I(()=>{const g=Object.keys(r.value.locales);return g.length?!g.some(y=>y===l.value.link):l.value.link!=="/"}),m=I(()=>c.value?l.value.activeMatch?new RegExp(l.value.activeMatch).test(n.path):d.value?n.path.startsWith(l.value.link):!1:!1);return(g,y)=>{const w=tn("RouteLink"),T=tn("AutoLinkExternalIcon");return c.value?(z(),xe(w,wl({key:0,active:m.value,to:X(l).link,"aria-label":f.value},g.$attrs),{default:Le(()=>[be(g.$slots,"default",{},()=>[be(g.$slots,"before"),pt(" "+Ae(X(l).text)+" ",1),be(g.$slots,"after")])]),_:3},16,["active","to","aria-label"])):(z(),Z("a",wl({key:1,class:"external-link",href:X(l).link,rel:u.value,target:s.value,"aria-label":f.value},g.$attrs),[be(g.$slots,"default",{},()=>[be(g.$slots,"before"),pt(" "+Ae(X(l).text)+" ",1),a.value?(z(),xe(T,{key:0})):Se("",!0),be(g.$slots,"after")])],16,Xh))}}}),yt=Ce(Qh,[["__file","AutoLink.vue"]]),Zh={class:"hero"},ep={key:0,id:"main-title"},tp={key:1,class:"description"},np={key:2,class:"actions"},rp=me({__name:"HomeHero",setup(e){const t=ht(),n=Xl(),r=no(),l=I(()=>r.value&&t.value.heroImageDark!==void 0?t.value.heroImageDark:t.value.heroImage),o=I(()=>t.value.heroAlt||s.value||"hero"),i=I(()=>t.value.heroHeight||280),s=I(()=>t.value.heroText===null?null:t.value.heroText||n.value.title||"Hello"),a=I(()=>t.value.tagline===null?null:t.value.tagline||n.value.description||"Welcome to your VuePress site"),c=I(()=>Array.isArray(t.value.actions)?t.value.actions.map(({text:f,link:d,type:m="primary"})=>({text:f,link:d,type:m})):[]),u=()=>{if(!l.value)return null;const f=ce("img",{src:Kr(l.value),alt:o.value,height:i.value});return t.value.heroImageDark===void 0?f:ce(Ql,()=>f)};return(f,d)=>(z(),Z("header",Zh,[ie(u),s.value?(z(),Z("h1",ep,Ae(s.value),1)):Se("",!0),a.value?(z(),Z("p",tp,Ae(a.value),1)):Se("",!0),c.value.length?(z(),Z("p",np,[(z(!0),Z(ye,null,Dt(c.value,m=>(z(),xe(yt,{key:m.text,class:Ge(["action-button",[m.type]]),item:m},null,8,["class","item"]))),128))])):Se("",!0)]))}}),lp=Ce(rp,[["__file","HomeHero.vue"]]),op={class:"home"},ip=me({__name:"Home",setup(e){return(t,n)=>(z(),Z("main",op,[ie(lp),ie(Kh),ie(Vh),ie(Yh)]))}}),sp=Ce(ip,[["__file","Home.vue"]]),ap=["aria-hidden"],cp=me({__name:"NavbarBrand",setup(e){const t=yn(),n=Xl(),r=Be(),l=no(),o=I(()=>r.value.home||t.value),i=I(()=>n.value.title),s=I(()=>l.value&&r.value.logoDark!==void 0?r.value.logoDark:r.value.logo),a=I(()=>r.value.logoAlt??i.value),c=I(()=>i.value.toLocaleUpperCase().trim()===a.value.toLocaleUpperCase().trim()),u=()=>{if(!s.value)return null;const f=ce("img",{class:"logo",src:Kr(s.value),alt:a.value});return r.value.logoDark===void 0?f:ce(Ql,()=>f)};return(f,d)=>(z(),xe(X(rr),{to:o.value},{default:Le(()=>[ie(u),i.value?(z(),Z("span",{key:0,class:Ge(["site-name",{"can-hide":s.value}]),"aria-hidden":c.value},Ae(i.value),11,ap)):Se("",!0)]),_:1},8,["to"]))}}),up=Ce(cp,[["__file","NavbarBrand.vue"]]),fp=me({__name:"DropdownTransition",setup(e){const t=r=>{r.style.height=r.scrollHeight+"px"},n=r=>{r.style.height=""};return(r,l)=>(z(),xe(tr,{name:"dropdown",onEnter:t,onAfterEnter:n,onBeforeLeave:t},{default:Le(()=>[be(r.$slots,"default")]),_:3}))}}),sa=Ce(fp,[["__file","DropdownTransition.vue"]]),dp=["aria-label"],hp={class:"title"},pp=re("span",{class:"arrow down"},null,-1),mp=["aria-label"],gp={class:"title"},vp={class:"navbar-dropdown"},_p={class:"navbar-dropdown-subtitle"},bp={key:1},yp={class:"navbar-dropdown-subitem-wrapper"},Ep=me({__name:"NavbarDropdown",props:{item:{type:Object,required:!0}},setup(e){const t=e,{item:n}=Br(t),r=I(()=>n.value.ariaLabel||n.value.text),l=ue(!1),o=zt();Me(()=>o.path,()=>{l.value=!1});const i=a=>{a.detail===0?l.value=!l.value:l.value=!1},s=(a,c)=>c[c.length-1]===a;return(a,c)=>(z(),Z("div",{class:Ge(["navbar-dropdown-wrapper",{open:l.value}])},[re("button",{class:"navbar-dropdown-title",type:"button","aria-label":r.value,onClick:i},[re("span",hp,Ae(X(n).text),1),pp],8,dp),re("button",{class:"navbar-dropdown-title-mobile",type:"button","aria-label":r.value,onClick:c[0]||(c[0]=u=>l.value=!l.value)},[re("span",gp,Ae(X(n).text),1),re("span",{class:Ge(["arrow",l.value?"down":"right"])},null,2)],8,mp),ie(sa,null,{default:Le(()=>[Cr(re("ul",vp,[(z(!0),Z(ye,null,Dt(X(n).children,u=>(z(),Z("li",{key:u.text,class:"navbar-dropdown-item"},[u.children?(z(),Z(ye,{key:0},[re("h4",_p,[u.link?(z(),xe(yt,{key:0,item:u,onFocusout:f=>s(u,X(n).children)&&u.children.length===0&&(l.value=!1)},null,8,["item","onFocusout"])):(z(),Z("span",bp,Ae(u.text),1))]),re("ul",yp,[(z(!0),Z(ye,null,Dt(u.children,f=>(z(),Z("li",{key:f.link,class:"navbar-dropdown-subitem"},[ie(yt,{item:f,onFocusout:d=>s(f,u.children)&&s(u,X(n).children)&&(l.value=!1)},null,8,["item","onFocusout"])]))),128))])],64)):(z(),xe(yt,{key:1,item:u,onFocusout:f=>s(u,X(n).children)&&(l.value=!1)},null,8,["item","onFocusout"]))]))),128))],512),[[Pr,l.value]])]),_:1})],2))}}),kp=Ce(Ep,[["__file","NavbarDropdown.vue"]]),wp=["aria-label"],Ap=me({__name:"NavbarItems",setup(e){const t=()=>{const f=zt(),d=Hd(),m=yn(),g=Ns(),y=Xl(),w=wh(),T=Be();return I(()=>{const x=Object.keys(g.value.locales);if(x.length<2)return[];const v=f.path,k=f.fullPath;return[{text:`${T.value.selectLanguageText}`,ariaLabel:`${T.value.selectLanguageAriaLabel??T.value.selectLanguageText}`,children:x.map(O=>{var E,B;const D=((E=g.value.locales)==null?void 0:E[O])??{},_=((B=w.value.locales)==null?void 0:B[O])??{},G=`${D.lang}`,L=_.selectLanguageName??G;if(G===y.value.lang)return{text:L,activeMatch:/./,link:f.hash??"#"};const U=v.replace(m.value,O);return{text:L,link:d.value.some(ne=>ne===U)?k.replace(v,U):_.home??O}})}]})},n=()=>{const f=Be(),d=I(()=>f.value.repo),m=I(()=>d.value?na(d.value):null),g=I(()=>d.value&&!nr(d.value)?`https://github.com/${d.value}`:d.value),y=I(()=>g.value?f.value.repoLabel?f.value.repoLabel:m.value===null?"Source":m.value:null);return I(()=>!g.value||!y.value?[]:[{text:y.value,link:g.value}])},r=f=>it(f)?ea(f):f.children?{...f,children:f.children.map(d=>r(d))}:f,l=()=>{const f=Be();return I(()=>(f.value.navbar||[]).map(d=>r(d)))},o=ue(!1),i=l(),s=t(),a=n(),c=I(()=>[...i.value,...s.value,...a.value]);ia(Kn.MOBILE,f=>{window.innerWidthBe().value.navbarLabel??"site navigation");return(f,d)=>c.value.length?(z(),Z("nav",{key:0,class:"navbar-items","aria-label":u.value},[(z(!0),Z(ye,null,Dt(c.value,m=>(z(),Z("div",{key:m.text,class:"navbar-item"},["children"in m?(z(),xe(kp,{key:0,item:m,class:Ge(o.value?"mobile":"")},null,8,["item","class"])):(z(),xe(yt,{key:1,item:m},null,8,["item"]))]))),128))],8,wp)):Se("",!0)}}),aa=Ce(Ap,[["__file","NavbarItems.vue"]]),Cp=["title"],xp={class:"icon",focusable:"false",viewBox:"0 0 32 32"},Lp=Zc('',9),Sp=[Lp],Tp={class:"icon",focusable:"false",viewBox:"0 0 32 32"},Rp=re("path",{d:"M13.502 5.414a15.075 15.075 0 0 0 11.594 18.194a11.113 11.113 0 0 1-7.975 3.39c-.138 0-.278.005-.418 0a11.094 11.094 0 0 1-3.2-21.584M14.98 3a1.002 1.002 0 0 0-.175.016a13.096 13.096 0 0 0 1.825 25.981c.164.006.328 0 .49 0a13.072 13.072 0 0 0 10.703-5.555a1.01 1.01 0 0 0-.783-1.565A13.08 13.08 0 0 1 15.89 4.38A1.015 1.015 0 0 0 14.98 3z",fill:"currentColor"},null,-1),Pp=[Rp],Op=me({__name:"ToggleColorModeButton",setup(e){const t=Be(),n=no(),r=()=>{n.value=!n.value};return(l,o)=>(z(),Z("button",{class:"toggle-color-mode-button",title:X(t).toggleColorMode,onClick:r},[Cr((z(),Z("svg",xp,Sp,512)),[[Pr,!X(n)]]),Cr((z(),Z("svg",Tp,Pp,512)),[[Pr,X(n)]])],8,Cp))}}),Ip=Ce(Op,[["__file","ToggleColorModeButton.vue"]]),Fp=["title"],$p=re("div",{class:"icon","aria-hidden":"true"},[re("span"),re("span"),re("span")],-1),Bp=[$p],Dp=me({__name:"ToggleSidebarButton",emits:["toggle"],setup(e){const t=Be();return(n,r)=>(z(),Z("div",{class:"toggle-sidebar-button",title:X(t).toggleSidebar,"aria-expanded":"false",role:"button",tabindex:"0",onClick:r[0]||(r[0]=l=>n.$emit("toggle"))},Bp,8,Fp))}}),Mp=Ce(Dp,[["__file","ToggleSidebarButton.vue"]]),Np=me({__name:"Navbar",emits:["toggle-sidebar"],setup(e){const t=Be(),n=ue(null),r=ue(null),l=ue(0),o=I(()=>l.value?{maxWidth:l.value+"px"}:{}),i=(s,a)=>{var f,d,m;const c=(m=(d=(f=s==null?void 0:s.ownerDocument)==null?void 0:f.defaultView)==null?void 0:d.getComputedStyle(s,null))==null?void 0:m[a],u=Number.parseInt(c,10);return Number.isNaN(u)?0:u};return ia(Kn.MOBILE,s=>{var c;const a=i(n.value,"paddingLeft")+i(n.value,"paddingRight");window.innerWidth{const c=tn("NavbarSearch");return z(),Z("header",{ref_key:"navbar",ref:n,class:"navbar"},[ie(Mp,{onToggle:a[0]||(a[0]=u=>s.$emit("toggle-sidebar"))}),re("span",{ref_key:"navbarBrand",ref:r},[ie(up)],512),re("div",{class:"navbar-items-wrapper",style:Jn(o.value)},[be(s.$slots,"before"),ie(aa,{class:"can-hide"}),be(s.$slots,"after"),X(t).colorModeSwitch?(z(),xe(Ip,{key:0})):Se("",!0),ie(c)],4)],512)}}}),Hp=Ce(Np,[["__file","Navbar.vue"]]),jp={class:"vp-page-meta"},zp={key:0,class:"vp-meta-item edit-link"},Vp=re("svg",{class:"icon",viewBox:"0 0 1024 1024"},[re("g",{fill:"currentColor"},[re("path",{d:"M430.818 653.65a60.46 60.46 0 0 1-50.96-93.281l71.69-114.012 7.773-10.365L816.038 80.138A60.46 60.46 0 0 1 859.225 62a60.46 60.46 0 0 1 43.186 18.138l43.186 43.186a60.46 60.46 0 0 1 0 86.373L588.879 565.55l-8.637 8.637-117.466 68.234a60.46 60.46 0 0 1-31.958 11.229z"}),re("path",{d:"M728.802 962H252.891A190.883 190.883 0 0 1 62.008 771.98V296.934a190.883 190.883 0 0 1 190.883-192.61h267.754a60.46 60.46 0 0 1 0 120.92H252.891a69.962 69.962 0 0 0-69.098 69.099V771.98a69.962 69.962 0 0 0 69.098 69.098h475.911A69.962 69.962 0 0 0 797.9 771.98V503.363a60.46 60.46 0 1 1 120.922 0V771.98A190.883 190.883 0 0 1 728.802 962z"})])],-1),Up={class:"vp-meta-item git-info"},Wp={key:0,class:"vp-meta-item last-updated"},Kp={class:"meta-item-label"},qp={class:"meta-item-info"},Gp={key:1,class:"vp-meta-item contributors"},Jp={class:"meta-item-label"},Yp={class:"meta-item-info"},Xp=["title"],Qp=me({__name:"PageMeta",setup(e){const t=()=>{const a=Be(),c=pn(),u=ht();return I(()=>{if(!(u.value.editLink??a.value.editLink??!0))return null;const{repo:d,docsRepo:m=d,docsBranch:g="main",docsDir:y="",editLinkText:w}=a.value;if(!m)return null;const T=Oh({docsRepo:m,docsBranch:g,docsDir:y,filePathRelative:c.value.filePathRelative,editLinkPattern:u.value.editLinkPattern??a.value.editLinkPattern});return T?{text:w??"Edit this page",link:T}:null})},n=()=>{const a=Be(),c=pn(),u=ht();return I(()=>{var m,g;return!(u.value.lastUpdated??a.value.lastUpdated??!0)||!((m=c.value.git)!=null&&m.updatedTime)?null:new Date((g=c.value.git)==null?void 0:g.updatedTime).toLocaleString()})},r=()=>{const a=Be(),c=pn(),u=ht();return I(()=>{var d;return u.value.contributors??a.value.contributors??!0?((d=c.value.git)==null?void 0:d.contributors)??null:null})},l=Be(),o=t(),i=n(),s=r();return(a,c)=>{const u=tn("ClientOnly");return z(),Z("footer",jp,[X(o)?(z(),Z("div",zp,[ie(yt,{class:"label",item:X(o)},{before:Le(()=>[Vp]),_:1},8,["item"])])):Se("",!0),re("div",Up,[X(i)?(z(),Z("div",Wp,[re("span",Kp,Ae(X(l).lastUpdatedText)+": ",1),ie(u,null,{default:Le(()=>[re("span",qp,Ae(X(i)),1)]),_:1})])):Se("",!0),X(s)&&X(s).length?(z(),Z("div",Gp,[re("span",Jp,Ae(X(l).contributorsText)+": ",1),re("span",Yp,[(z(!0),Z(ye,null,Dt(X(s),(f,d)=>(z(),Z(ye,{key:d},[re("span",{class:"contributor",title:`email: ${f.email}`},Ae(f.name),9,Xp),d!==X(s).length-1?(z(),Z(ye,{key:0},[pt(", ")],64)):Se("",!0)],64))),128))])])):Se("",!0)])])}}}),Zp=Ce(Qp,[["__file","PageMeta.vue"]]),em=["aria-label"],tm={class:"hint"},nm=re("span",{class:"arrow left"},null,-1),rm={class:"link"},lm={class:"hint"},om=re("span",{class:"arrow right"},null,-1),im={class:"link"},sm=me({__name:"PageNav",setup(e){const t=(f,d)=>{if(f===!1)return null;if(it(f)){const{notFound:m,meta:g,path:y}=di(f,d);return m?{text:y,link:y}:{text:g.title||y,link:y}}return ql(f)?{...f,link:di(f.link,d).path}:!1},n=(f,d,m)=>{const g=f.findIndex(y=>y.link===d);if(g!==-1){const y=f[g+m];return y!=null&&y.link?y:null}for(const y of f)if(y.children){const w=n(y.children,d,m);if(w)return w}return null},r=ht(),l=ro(),o=Be(),i=zt(),s=Lh(),a=I(()=>{const f=t(r.value.prev,i.path);return f!==!1?f:n(l.value,i.path,-1)}),c=I(()=>{const f=t(r.value.next,i.path);return f!==!1?f:n(l.value,i.path,1)}),u=I(()=>Be().value.pageNavbarLabel??"page navigation");return at("keydown",f=>{f.altKey&&(f.key==="ArrowRight"?c.value&&(s(c.value.link),f.preventDefault()):f.key==="ArrowLeft"&&a.value&&(s(a.value.link),f.preventDefault()))}),(f,d)=>a.value||c.value?(z(),Z("nav",{key:0,class:"vp-page-nav","aria-label":u.value},[a.value?(z(),xe(yt,{key:0,class:"prev",item:a.value},{default:Le(()=>[re("div",tm,[nm,pt(" "+Ae(X(o).prev??"Prev"),1)]),re("div",rm,[re("span",null,Ae(a.value.text),1)])]),_:1},8,["item"])):Se("",!0),c.value?(z(),xe(yt,{key:1,class:"next",item:c.value},{default:Le(()=>[re("div",lm,[pt(Ae(X(o).next??"Next")+" ",1),om]),re("div",im,[re("span",null,Ae(c.value.text),1)])]),_:1},8,["item"])):Se("",!0)],8,em)):Se("",!0)}}),am=Ce(sm,[["__file","PageNav.vue"]]),cm={class:"page"},um={class:"theme-default-content"},fm=me({__name:"Page",setup(e){return(t,n)=>{const r=tn("Content");return z(),Z("main",cm,[be(t.$slots,"top"),re("div",um,[be(t.$slots,"content-top"),ie(r),be(t.$slots,"content-bottom")]),ie(Zp),ie(am),be(t.$slots,"bottom")])}}}),dm=Ce(fm,[["__file","Page.vue"]]),hm={class:"sidebar-item-children"},pm=me({__name:"SidebarItem",props:{item:{type:Object,required:!0},depth:{type:Number,required:!1,default:0}},setup(e){const t=e,{item:n,depth:r}=Br(t),l=zt(),o=ln(),i=I(()=>ta(n.value,l)),s=I(()=>({"sidebar-item":!0,"sidebar-heading":r.value===0,active:i.value,collapsible:n.value.collapsible})),a=I(()=>n.value.collapsible?i.value:!0),[c,u]=Ed(a.value),f=m=>{n.value.collapsible&&(m.preventDefault(),u())},d=o.afterEach(m=>{bn(()=>{c.value=a.value})});return Hr(()=>{d()}),(m,g)=>{var w;const y=tn("SidebarItem",!0);return z(),Z("li",null,[X(n).link?(z(),xe(yt,{key:0,class:Ge(s.value),item:X(n)},null,8,["class","item"])):(z(),Z("p",{key:1,tabindex:"0",class:Ge(s.value),onClick:f,onKeydown:Bu(f,["enter"])},[pt(Ae(X(n).text)+" ",1),X(n).collapsible?(z(),Z("span",{key:0,class:Ge(["arrow",X(c)?"down":"right"])},null,2)):Se("",!0)],34)),(w=X(n).children)!=null&&w.length?(z(),xe(sa,{key:2},{default:Le(()=>[Cr(re("ul",hm,[(z(!0),Z(ye,null,Dt(X(n).children,T=>(z(),xe(y,{key:`${X(r)}${T.text}${T.link}`,item:T,depth:X(r)+1},null,8,["item","depth"]))),128))],512),[[Pr,X(c)]])]),_:1})):Se("",!0)])}}}),mm=Ce(pm,[["__file","SidebarItem.vue"]]),gm={key:0,class:"sidebar-items"},vm=me({__name:"SidebarItems",setup(e){const t=zt(),n=ro();return Ue(()=>{Me(()=>t.hash,r=>{const l=document.querySelector(".sidebar");if(!l)return;const o=document.querySelector(`.sidebar a.sidebar-item[href="${t.path}${r}"]`);if(!o)return;const{top:i,height:s}=l.getBoundingClientRect(),{top:a,height:c}=o.getBoundingClientRect();ai+s&&o.scrollIntoView(!1)})}),(r,l)=>X(n).length?(z(),Z("ul",gm,[(z(!0),Z(ye,null,Dt(X(n),o=>(z(),xe(mm,{key:`${o.text}${o.link}`,item:o},null,8,["item"]))),128))])):Se("",!0)}}),_m=Ce(vm,[["__file","SidebarItems.vue"]]),bm={class:"sidebar"},ym=me({__name:"Sidebar",setup(e){return(t,n)=>(z(),Z("aside",bm,[ie(aa),be(t.$slots,"top"),ie(_m),be(t.$slots,"bottom")]))}}),Em=Ce(ym,[["__file","Sidebar.vue"]]),km=me({__name:"Layout",setup(e){const t=pn(),n=ht(),r=Be(),l=I(()=>n.value.navbar!==!1&&r.value.navbar!==!1),o=ro(),i=ue(!1),s=w=>{i.value=typeof w=="boolean"?w:!i.value},a={x:0,y:0},c=w=>{a.x=w.changedTouches[0].clientX,a.y=w.changedTouches[0].clientY},u=w=>{const T=w.changedTouches[0].clientX-a.x,x=w.changedTouches[0].clientY-a.y;Math.abs(T)>Math.abs(x)&&Math.abs(T)>40&&(T>0&&a.x<=80?s(!0):s(!1))},f=I(()=>[{"no-navbar":!l.value,"no-sidebar":!o.value.length,"sidebar-open":i.value},n.value.pageClass]);let d;Ue(()=>{d=ln().afterEach(()=>{s(!1)})}),jr(()=>{d()});const m=Zs(),g=m.resolve,y=m.pending;return(w,T)=>(z(),Z("div",{class:Ge(["theme-container",f.value]),onTouchstart:c,onTouchend:u},[be(w.$slots,"navbar",{},()=>[l.value?(z(),xe(Hp,{key:0,onToggleSidebar:s},{before:Le(()=>[be(w.$slots,"navbar-before")]),after:Le(()=>[be(w.$slots,"navbar-after")]),_:3})):Se("",!0)]),re("div",{class:"sidebar-mask",onClick:T[0]||(T[0]=x=>s(!1))}),be(w.$slots,"sidebar",{},()=>[ie(Em,null,{top:Le(()=>[be(w.$slots,"sidebar-top")]),bottom:Le(()=>[be(w.$slots,"sidebar-bottom")]),_:3})]),be(w.$slots,"page",{},()=>[X(n).home?(z(),xe(sp,{key:0})):(z(),xe(tr,{key:1,name:"fade-slide-y",mode:"out-in",onBeforeEnter:X(g),onBeforeLeave:X(y)},{default:Le(()=>[(z(),xe(dm,{key:X(t).path},{top:Le(()=>[be(w.$slots,"page-top")]),"content-top":Le(()=>[be(w.$slots,"page-content-top")]),"content-bottom":Le(()=>[be(w.$slots,"page-content-bottom")]),bottom:Le(()=>[be(w.$slots,"page-bottom")]),_:3}))]),_:3},8,["onBeforeEnter","onBeforeLeave"]))])],34))}}),wm=Ce(km,[["__file","Layout.vue"]]),Am={class:"theme-container"},Cm={class:"page"},xm={class:"theme-default-content"},Lm=re("h1",null,"404",-1),Sm=me({__name:"NotFound",setup(e){const t=yn(),n=Be(),r=n.value.notFound??["Not Found"],l=()=>r[Math.floor(Math.random()*r.length)],o=n.value.home??t.value,i=n.value.backToHome??"Back to home";return(s,a)=>(z(),Z("div",Am,[re("main",Cm,[re("div",xm,[Lm,re("blockquote",null,Ae(l()),1),ie(X(rr),{to:X(o)},{default:Le(()=>[pt(Ae(X(i)),1)]),_:1},8,["to"])])])]))}}),Tm=Ce(Sm,[["__file","NotFound.vue"]]),Rm=kt({enhance({app:e,router:t}){ul("Badge")||e.component("Badge",bh),ul("CodeGroup")||e.component("CodeGroup",yh),ul("CodeGroupItem")||e.component("CodeGroupItem",kh),e.component("AutoLinkExternalIcon",()=>{const r=e.component("ExternalLinkIcon");return r?ce(r):null}),e.component("NavbarSearch",()=>{const r=e.component("Docsearch")||e.component("SearchBox");return r?ce(r):null});const n=t.options.scrollBehavior;t.options.scrollBehavior=async(...r)=>(await Zs().wait(),n(...r))},setup(){Ah(),Ih()},layouts:{Layout:wm,NotFound:Tm}}),Pm=/\b(?:Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini)/i,Om=()=>typeof window<"u"&&window.navigator&&"userAgent"in window.navigator&&Pm.test(navigator.userAgent),Im=({delay:e=500,duration:t=2e3,locales:n,selector:r,showInMobile:l})=>{const{copy:o,copied:i}=wd({legacy:!0,copiedDuring:t}),s=Ks(n),a=pn(),c=d=>{if(!d.hasAttribute("copy-code-registered")){const m=document.createElement("button");m.type="button",m.classList.add("vp-copy-code-button"),m.innerHTML='
',m.setAttribute("aria-label",s.value.copy),m.setAttribute("data-copied",s.value.copied),d.parentElement&&d.parentElement.insertBefore(m,d),d.setAttribute("copy-code-registered","")}},u=()=>{bn().then(()=>Gs(e)).then(()=>{r.forEach(d=>{document.querySelectorAll(d).forEach(c)})})},f=(d,m,g)=>{let{innerText:y=""}=m;/language-(shellscript|shell|bash|sh|zsh)/.test(d.classList.toString())&&(y=y.replace(/^ *(\$|>) /gm,"")),o(y).then(()=>{g.classList.add("copied"),Me(i,()=>{g.classList.remove("copied"),g.blur()},{once:!0})})};Ue(()=>{const d=!Om()||l;d&&u(),at("click",m=>{const g=m.target;if(g.matches('div[class*="language-"] > button.copy')){const y=g.parentElement,w=g.nextElementSibling;w&&f(y,w,g)}else if(g.matches('div[class*="language-"] div.vp-copy-icon')){const y=g.parentElement,w=y.parentElement,T=y.nextElementSibling;T&&f(w,T,y)}}),Me(()=>a.value.path,()=>{d&&u()})})};var Fm={"/":{copy:"复制代码",copied:"已复制"}},$m=['.theme-default-content div[class*="language-"] pre'];const Bm=kt({setup:()=>{Im({selector:$m,locales:Fm,duration:2e3,delay:500,showInMobile:!1})}}),Dm=e=>e instanceof Element?document.activeElement===e&&(["TEXTAREA","SELECT","INPUT"].includes(e.tagName)||e.hasAttribute("contenteditable")):!1,Mm=(e,t)=>t.some(n=>{if(it(n))return n===e.key;const{key:r,ctrl:l=!1,shift:o=!1,alt:i=!1}=n;return r===e.key&&l===e.ctrlKey&&o===e.shiftKey&&i===e.altKey}),Nm=/[^\x00-\x7F]/,Hm=e=>e.split(/\s+/g).map(t=>t.trim()).filter(t=>!!t),bi=e=>e.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),yi=(e,t)=>{const n=t.join(" "),r=Hm(e);if(Nm.test(e))return r.some(i=>n.toLowerCase().indexOf(i)>-1);const l=e.endsWith(" ");return new RegExp(r.map((i,s)=>r.length===s+1&&!l?`(?=.*\\b${bi(i)})`:`(?=.*\\b${bi(i)}\\b)`).join("")+".+","gi").test(n)},jm=({input:e,hotKeys:t})=>{if(t.value.length===0)return;const n=r=>{e.value&&Mm(r,t.value)&&!Dm(r.target)&&(r.preventDefault(),e.value.focus())};Ue(()=>{document.addEventListener("keydown",n)}),Hr(()=>{document.removeEventListener("keydown",n)})},zm=[{title:"首页",headers:[],path:"/",pathLocale:"/",extraFields:[]},{title:"Performance",headers:[{level:2,title:"为什么前端性能如此重要?",slug:"为什么前端性能如此重要",link:"#为什么前端性能如此重要",children:[]},{level:2,title:"⽹⻚性能指标及影响因素",slug:"网⻚性能指标及影响因素",link:"#网⻚性能指标及影响因素",children:[{level:3,title:"Timing",slug:"timing",link:"#timing",children:[]},{level:3,title:"关于 Performance API",slug:"关于-performance-api",link:"#关于-performance-api",children:[]},{level:3,title:"用户为导向性能指标介绍",slug:"用户为导向性能指标介绍",link:"#用户为导向性能指标介绍",children:[]},{level:3,title:"核心网页指标",slug:"核心网页指标",link:"#核心网页指标",children:[]},{level:3,title:"常⻅优化⼿段",slug:"常⻅优化手段",link:"#常⻅优化手段",children:[]},{level:3,title:"Vue性能优化常见策略",slug:"vue性能优化常见策略",link:"#vue性能优化常见策略",children:[]},{level:3,title:"React性能优化常⻅策略",slug:"react性能优化常⻅策略",link:"#react性能优化常⻅策略",children:[]}]}],path:"/advance/Performance.html",pathLocale:"/",extraFields:[]},{title:"概要介绍",headers:[],path:"/advance/",pathLocale:"/",extraFields:[]},{title:"说Webpack 打包原理",headers:[{level:2,title:"Webpack 介绍",slug:"webpack-介绍",link:"#webpack-介绍",children:[]},{level:2,title:"webpack 核心概念",slug:"webpack-核心概念",link:"#webpack-核心概念",children:[]},{level:2,title:"webpack 构建流程",slug:"webpack-构建流程",link:"#webpack-构建流程",children:[]}],path:"/advance/Webpack%E6%89%93%E5%8C%85%E5%8E%9F%E7%90%86.html",pathLocale:"/",extraFields:[]},{title:"前端性能优化",headers:[{level:2,title:"1.重要性:",slug:"_1-重要性",link:"#_1-重要性",children:[]},{level:2,title:"2.定位:",slug:"_2-定位",link:"#_2-定位",children:[{level:3,title:"2.1 技术上的选择",slug:"_2-1-技术上的选择",link:"#_2-1-技术上的选择",children:[]},{level:3,title:"2.2 NetWork",slug:"_2-2-network",link:"#_2-2-network",children:[]},{level:3,title:"2.3 webpack-bundle-analyzer",slug:"_2-3-webpack-bundle-analyzer",link:"#_2-3-webpack-bundle-analyzer",children:[]},{level:3,title:"2.4 Performance",slug:"_2-4-performance",link:"#_2-4-performance",children:[]},{level:3,title:"2.5 PerformanceNavigationTiming",slug:"_2-5-performancenavigationtiming",link:"#_2-5-performancenavigationtiming",children:[]},{level:3,title:"2.6 抓包",slug:"_2-6-抓包",link:"#_2-6-抓包",children:[]},{level:3,title:"2.7 性能测试工具",slug:"_2-7-性能测试工具",link:"#_2-7-性能测试工具",children:[]}]},{level:2,title:"3.优化:",slug:"_3-优化",link:"#_3-优化",children:[{level:3,title:"3.1 tree shaking",slug:"_3-1-tree-shaking",link:"#_3-1-tree-shaking",children:[]},{level:3,title:"3.2 split chunks",slug:"_3-2-split-chunks",link:"#_3-2-split-chunks",children:[]},{level:3,title:"3.3 拆包",slug:"_3-3-拆包",link:"#_3-3-拆包",children:[]},{level:3,title:"3.4 gzip",slug:"_3-4-gzip",link:"#_3-4-gzip",children:[]},{level:3,title:"3.5 图片压缩",slug:"_3-5-图片压缩",link:"#_3-5-图片压缩",children:[]},{level:3,title:"3.6 图片分割",slug:"_3-6-图片分割",link:"#_3-6-图片分割",children:[]},{level:3,title:"3.7 sprite",slug:"_3-7-sprite",link:"#_3-7-sprite",children:[]},{level:3,title:"3.8 CDN",slug:"_3-8-cdn",link:"#_3-8-cdn",children:[]},{level:3,title:"3.9 懒加载",slug:"_3-9-懒加载",link:"#_3-9-懒加载",children:[]},{level:3,title:"3.10 iconfont",slug:"_3-10-iconfont",link:"#_3-10-iconfont",children:[]},{level:3,title:"3.11 逻辑后移",slug:"_3-11-逻辑后移",link:"#_3-11-逻辑后移",children:[]},{level:3,title:"3.12 算法复杂度",slug:"_3-12-算法复杂度",link:"#_3-12-算法复杂度",children:[]},{level:3,title:"3.13 组件渲染",slug:"_3-13-组件渲染",link:"#_3-13-组件渲染",children:[]},{level:3,title:"3.14 node middleware",slug:"_3-14-node-middleware",link:"#_3-14-node-middleware",children:[]},{level:3,title:"3.15 web worker",slug:"_3-15-web-worker",link:"#_3-15-web-worker",children:[]},{level:3,title:"3.16 缓存",slug:"_3-16-缓存",link:"#_3-16-缓存",children:[]},{level:3,title:"3.17 GPU 渲染",slug:"_3-17-gpu-渲染",link:"#_3-17-gpu-渲染",children:[]},{level:3,title:"3.18 Ajax 可缓存",slug:"_3-18-ajax-可缓存",link:"#_3-18-ajax-可缓存",children:[]},{level:3,title:"3.19 Resource Hints",slug:"_3-19-resource-hints",link:"#_3-19-resource-hints",children:[]},{level:3,title:"3.20 SSR",slug:"_3-20-ssr",link:"#_3-20-ssr",children:[]},{level:3,title:"3.21 UNPKG",slug:"_3-21-unpkg",link:"#_3-21-unpkg",children:[]}]},{level:2,title:"4.总结:",slug:"_4-总结",link:"#_4-总结",children:[]}],path:"/advance/%E5%89%8D%E7%AB%AF%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96.html",pathLocale:"/",extraFields:[]},{title:"前端路由的实现原理",headers:[{level:2,title:"基本的原理先看看",slug:"基本的原理先看看",link:"#基本的原理先看看",children:[]},{level:2,title:"自定义事件",slug:"自定义事件",link:"#自定义事件",children:[]},{level:2,title:"自定义元素",slug:"自定义元素",link:"#自定义元素",children:[{level:3,title:"自定义内置元素",slug:"自定义内置元素",link:"#自定义内置元素",children:[]},{level:3,title:"自主定义元素",slug:"自主定义元素",link:"#自主定义元素",children:[]}]},{level:2,title:"Vue-router 的实现",slug:"vue-router-的实现",link:"#vue-router-的实现",children:[]}],path:"/advance/%E5%89%8D%E7%AB%AF%E8%B7%AF%E7%94%B1%E7%9A%84%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86.html",pathLocale:"/",extraFields:[]},{title:"数据代理 Proxy",headers:[{level:2,title:"简单的数据双向绑定",slug:"简单的数据双向绑定",link:"#简单的数据双向绑定",children:[]},{level:2,title:"Object.defineProperty",slug:"object-defineproperty",link:"#object-defineproperty",children:[]},{level:2,title:"Proxy",slug:"proxy",link:"#proxy",children:[]},{level:2,title:"Proxy 和 Reflect",slug:"proxy-和-reflect",link:"#proxy-和-reflect",children:[]}],path:"/advance/%E6%95%B0%E6%8D%AE%E4%BB%A3%E7%90%86Proxy.html",pathLocale:"/",extraFields:[]},{title:"介绍",headers:[{level:2,title:"算法很重要",slug:"算法很重要",link:"#算法很重要",children:[]},{level:2,title:"开始系统学算法+日常刷题",slug:"开始系统学算法-日常刷题",link:"#开始系统学算法-日常刷题",children:[]}],path:"/algorithm/",pathLocale:"/",extraFields:[]},{title:"二分查找🍰",headers:[{level:2,title:"35. 搜索插入位置",slug:"_35-搜索插入位置",link:"#_35-搜索插入位置",children:[]},{level:2,title:"34. 在排序数组中查找元素的第一个和最后一个位置",slug:"_34-在排序数组中查找元素的第一个和最后一个位置",link:"#_34-在排序数组中查找元素的第一个和最后一个位置",children:[]},{level:2,title:"69. x 的平方根",slug:"_69-x-的平方根",link:"#_69-x-的平方根",children:[]},{level:2,title:"367. 有效的完全平方数",slug:"_367-有效的完全平方数",link:"#_367-有效的完全平方数",children:[]}],path:"/algorithm/%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%F0%9F%8D%B0.html",pathLocale:"/",extraFields:[]},{title:"二叉树🍈",headers:[{level:2,title:"94. 二叉树的中序遍历",slug:"_94-二叉树的中序遍历",link:"#_94-二叉树的中序遍历",children:[]},{level:2,title:"104. 二叉树的最大深度",slug:"_104-二叉树的最大深度",link:"#_104-二叉树的最大深度",children:[]},{level:2,title:"226. 翻转二叉树",slug:"_226-翻转二叉树",link:"#_226-翻转二叉树",children:[]},{level:2,title:"101. 对称二叉树",slug:"_101-对称二叉树",link:"#_101-对称二叉树",children:[]},{level:2,title:"543. 二叉树的直径",slug:"_543-二叉树的直径",link:"#_543-二叉树的直径",children:[]},{level:2,title:"102. 二叉树的层序遍历",slug:"_102-二叉树的层序遍历",link:"#_102-二叉树的层序遍历",children:[]},{level:2,title:"108. 将有序数组转换为二叉搜索树",slug:"_108-将有序数组转换为二叉搜索树",link:"#_108-将有序数组转换为二叉搜索树",children:[]},{level:2,title:"98. 验证二叉搜索树",slug:"_98-验证二叉搜索树",link:"#_98-验证二叉搜索树",children:[]},{level:2,title:"230. 二叉搜索树中第K小的元素",slug:"_230-二叉搜索树中第k小的元素",link:"#_230-二叉搜索树中第k小的元素",children:[]},{level:2,title:"199. 二叉树的右视图",slug:"_199-二叉树的右视图",link:"#_199-二叉树的右视图",children:[]},{level:2,title:"103. 二叉树的锯齿形层序遍历",slug:"_103-二叉树的锯齿形层序遍历",link:"#_103-二叉树的锯齿形层序遍历",children:[]},{level:2,title:"114. 二叉树展开为链表",slug:"_114-二叉树展开为链表",link:"#_114-二叉树展开为链表",children:[]},{level:2,title:"105. 从前序与中序遍历序列构造二叉树",slug:"_105-从前序与中序遍历序列构造二叉树",link:"#_105-从前序与中序遍历序列构造二叉树",children:[]},{level:2,title:"437. 路径总和 III",slug:"_437-路径总和-iii",link:"#_437-路径总和-iii",children:[]},{level:2,title:"236. 二叉树的最近公共祖先",slug:"_236-二叉树的最近公共祖先",link:"#_236-二叉树的最近公共祖先",children:[]},{level:2,title:"124. 二叉树中的最大路径和",slug:"_124-二叉树中的最大路径和",link:"#_124-二叉树中的最大路径和",children:[]},{level:2,title:"100. 相同的树",slug:"_100-相同的树",link:"#_100-相同的树",children:[]}],path:"/algorithm/%E4%BA%8C%E5%8F%89%E6%A0%91%F0%9F%8D%88.html",pathLocale:"/",extraFields:[]},{title:"动态规划🍓",headers:[{level:2,title:"70.爬楼梯",slug:"_70-爬楼梯",link:"#_70-爬楼梯",children:[]},{level:2,title:"118.杨辉三角",slug:"_118-杨辉三角",link:"#_118-杨辉三角",children:[]},{level:2,title:"198.打家劫舍",slug:"_198-打家劫舍",link:"#_198-打家劫舍",children:[]},{level:2,title:"279.完全平方数",slug:"_279-完全平方数",link:"#_279-完全平方数",children:[]},{level:2,title:"322.零钱兑换",slug:"_322-零钱兑换",link:"#_322-零钱兑换",children:[]},{level:2,title:"300.最长递增子序列",slug:"_300-最长递增子序列",link:"#_300-最长递增子序列",children:[]},{level:2,title:"416.分割等和子集",slug:"_416-分割等和子集",link:"#_416-分割等和子集",children:[]},{level:2,title:"62.不同路径",slug:"_62-不同路径",link:"#_62-不同路径",children:[]},{level:2,title:"64.最小路径",slug:"_64-最小路径",link:"#_64-最小路径",children:[]}],path:"/algorithm/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%F0%9F%8D%93.html",pathLocale:"/",extraFields:[]},{title:"双指针_滑动窗口🍨",headers:[{level:2,title:"27. 移除元素",slug:"_27-移除元素",link:"#_27-移除元素",children:[]},{level:2,title:"26. 删除有序数组中的重复项",slug:"_26-删除有序数组中的重复项",link:"#_26-删除有序数组中的重复项",children:[]},{level:2,title:"283. 移动零",slug:"_283-移动零",link:"#_283-移动零",children:[]},{level:2,title:"209. 长度最小的子数组",slug:"_209-长度最小的子数组",link:"#_209-长度最小的子数组",children:[]},{level:2,title:"904. 水果成篮",slug:"_904-水果成篮",link:"#_904-水果成篮",children:[]},{level:2,title:"11. 盛最多水的容器",slug:"_11-盛最多水的容器",link:"#_11-盛最多水的容器",children:[]},{level:2,title:"15. 三数之和",slug:"_15-三数之和",link:"#_15-三数之和",children:[]},{level:2,title:"42. 接雨水",slug:"_42-接雨水",link:"#_42-接雨水",children:[]},{level:2,title:"3. 无重复字符的最长子串",slug:"_3-无重复字符的最长子串",link:"#_3-无重复字符的最长子串",children:[]},{level:2,title:"76.最小覆盖子串",slug:"_76-最小覆盖子串",link:"#_76-最小覆盖子串",children:[]},{level:2,title:"438. 找到字符串中所有字母异位词",slug:"_438-找到字符串中所有字母异位词",link:"#_438-找到字符串中所有字母异位词",children:[]},{level:2,title:"567.字符串的排列",slug:"_567-字符串的排列",link:"#_567-字符串的排列",children:[]},{level:2,title:"239. 滑动窗口最大值",slug:"_239-滑动窗口最大值",link:"#_239-滑动窗口最大值",children:[]}],path:"/algorithm/%E5%8F%8C%E6%8C%87%E9%92%88_%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%F0%9F%8D%A8.html",pathLocale:"/",extraFields:[]},{title:"手撕数据结构",headers:[{level:2,title:"栈",slug:"栈",link:"#栈",children:[]},{level:2,title:"单链队列",slug:"单链队列",link:"#单链队列",children:[]},{level:2,title:"循环队列",slug:"循环队列",link:"#循环队列",children:[]},{level:2,title:"单向链表",slug:"单向链表",link:"#单向链表",children:[]},{level:2,title:"堆",slug:"堆",link:"#堆",children:[]},{level:2,title:"二分搜索树",slug:"二分搜索树",link:"#二分搜索树",children:[]}],path:"/algorithm/%E6%89%8B%E6%92%95%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84.html",pathLocale:"/",extraFields:[]},{title:"技巧_数学🍌",headers:[{level:2,title:"136.只出现一次的数字",slug:"_136-只出现一次的数字",link:"#_136-只出现一次的数字",children:[]},{level:2,title:"31.下一个排列",slug:"_31-下一个排列",link:"#_31-下一个排列",children:[]},{level:2,title:"560. 和为 K 的子数组",slug:"_560-和为-k-的子数组",link:"#_560-和为-k-的子数组",children:[]},{level:2,title:"1. 两数之和",slug:"_1-两数之和",link:"#_1-两数之和",children:[]},{level:2,title:"49. 字母异位词分组",slug:"_49-字母异位词分组",link:"#_49-字母异位词分组",children:[]},{level:2,title:"128. 最长连续序列",slug:"_128-最长连续序列",link:"#_128-最长连续序列",children:[]}],path:"/algorithm/%E6%8A%80%E5%B7%A7_%E6%95%B0%E5%AD%A6%F0%9F%8D%8C.html",pathLocale:"/",extraFields:[]},{title:"栈_堆🍊",headers:[{level:2,title:"20.有效的括号",slug:"_20-有效的括号",link:"#_20-有效的括号",children:[]},{level:2,title:"155.最小栈",slug:"_155-最小栈",link:"#_155-最小栈",children:[]},{level:2,title:"394.字符串解码",slug:"_394-字符串解码",link:"#_394-字符串解码",children:[]},{level:2,title:"739.每日温度",slug:"_739-每日温度",link:"#_739-每日温度",children:[]},{level:2,title:"84.柱状图中最大的矩形(不会)",slug:"_84-柱状图中最大的矩形-不会",link:"#_84-柱状图中最大的矩形-不会",children:[]},{level:2,title:"844. 比较含退格的字符串",slug:"_844-比较含退格的字符串",link:"#_844-比较含退格的字符串",children:[]},{level:2,title:"215. 数组中的第K个最大元素",slug:"_215-数组中的第k个最大元素",link:"#_215-数组中的第k个最大元素",children:[]},{level:2,title:"347. 前 K 个高频元素",slug:"_347-前-k-个高频元素",link:"#_347-前-k-个高频元素",children:[]}],path:"/algorithm/%E6%A0%88_%E5%A0%86%F0%9F%8D%8A.html",pathLocale:"/",extraFields:[]},{title:"矩阵🍇",headers:[{level:2,title:"1329. 将矩阵按对角线排序",slug:"_1329-将矩阵按对角线排序",link:"#_1329-将矩阵按对角线排序",children:[]},{level:2,title:"54. 螺旋矩阵",slug:"_54-螺旋矩阵",link:"#_54-螺旋矩阵",children:[]},{level:2,title:"289. 生命游戏",slug:"_289-生命游戏",link:"#_289-生命游戏",children:[]},{level:2,title:"48. 旋转图像",slug:"_48-旋转图像",link:"#_48-旋转图像",children:[]},{level:2,title:"73. 矩阵置零",slug:"_73-矩阵置零",link:"#_73-矩阵置零",children:[]}],path:"/algorithm/%E7%9F%A9%E9%98%B5%F0%9F%8D%87.html",pathLocale:"/",extraFields:[]},{title:"贪心🍉",headers:[{level:2,title:"55.跳跃游戏",slug:"_55-跳跃游戏",link:"#_55-跳跃游戏",children:[]},{level:2,title:"121. 买卖股票的最佳时机",slug:"_121-买卖股票的最佳时机",link:"#_121-买卖股票的最佳时机",children:[]},{level:2,title:"45. 跳跃游戏 II",slug:"_45-跳跃游戏-ii",link:"#_45-跳跃游戏-ii",children:[]}],path:"/algorithm/%E8%B4%AA%E5%BF%83%F0%9F%8D%89.html",pathLocale:"/",extraFields:[]},{title:"异步请求 AJAX",headers:[{level:2,title:"XMLHttpRequest",slug:"xmlhttprequest",link:"#xmlhttprequest",children:[]},{level:2,title:"fetch",slug:"fetch",link:"#fetch",children:[]},{level:2,title:"axiso 请求库",slug:"axiso-请求库",link:"#axiso-请求库",children:[]}],path:"/base/AJAX.html",pathLocale:"/",extraFields:[]},{title:"CSS3",headers:[{level:2,title:"Grid 布局",slug:"grid-布局",link:"#grid-布局",children:[]}],path:"/base/CSS3.html",pathLocale:"/",extraFields:[]},{title:"JS 模块化历程",headers:[{level:2,title:"模块化的历程",slug:"模块化的历程",link:"#模块化的历程",children:[{level:3,title:"全局的 function 模式",slug:"全局的-function-模式",link:"#全局的-function-模式",children:[]},{level:3,title:"Namespace 模式",slug:"namespace-模式",link:"#namespace-模式",children:[]},{level:3,title:"IIFE 模式",slug:"iife-模式",link:"#iife-模式",children:[]},{level:3,title:"IIFE 增强模式",slug:"iife-增强模式",link:"#iife-增强模式",children:[]}]},{level:2,title:"AMD",slug:"amd",link:"#amd",children:[{level:3,title:"NO-AMD",slug:"no-amd",link:"#no-amd",children:[]},{level:3,title:"require.js",slug:"require-js",link:"#require-js",children:[]}]},{level:2,title:"CMD - sea.js",slug:"cmd-sea-js",link:"#cmd-sea-js",children:[]},{level:2,title:"CommonJS",slug:"commonjs",link:"#commonjs",children:[]},{level:2,title:"ES Module",slug:"es-module",link:"#es-module",children:[]}],path:"/base/JS%E6%A8%A1%E5%9D%97%E5%8C%96%E5%8E%86%E7%A8%8B.html",pathLocale:"/",extraFields:[]},{title:"前言",headers:[],path:"/base/",pathLocale:"/",extraFields:[]},{title:"哦!又学到了!",headers:[{level:2,title:"append 和 appendchild 方法的区别",slug:"append-和-appendchild-方法的区别",link:"#append-和-appendchild-方法的区别",children:[]},{level:2,title:"特殊的 Array.from",slug:"特殊的-array-from",link:"#特殊的-array-from",children:[]},{level:2,title:"H5 的拖拽事件",slug:"h5-的拖拽事件",link:"#h5-的拖拽事件",children:[]},{level:2,title:"历史记录 localstorage",slug:"历史记录-localstorage",link:"#历史记录-localstorage",children:[]},{level:2,title:"记住我 cookie 实现",slug:"记住我-cookie-实现",link:"#记住我-cookie-实现",children:[]},{level:2,title:"图片懒加载",slug:"图片懒加载",link:"#图片懒加载",children:[]},{level:2,title:"多行省略",slug:"多行省略",link:"#多行省略",children:[]},{level:2,title:"数组扁平化",slug:"数组扁平化",link:"#数组扁平化",children:[]}],path:"/base/%E5%93%A6%EF%BC%81%E5%8F%88%E5%AD%A6%E5%88%B0%E4%BA%86%EF%BC%81.html",pathLocale:"/",extraFields:[]},{title:"手写题",headers:[{level:2,title:"前端面试常考",slug:"前端面试常考",link:"#前端面试常考",children:[{level:3,title:"1.实现一个 call 函数",slug:"_1-实现一个-call-函数",link:"#_1-实现一个-call-函数",children:[]},{level:3,title:"2.实现一个 apply 函数",slug:"_2-实现一个-apply-函数",link:"#_2-实现一个-apply-函数",children:[]},{level:3,title:"3.实现一个 bind 函数",slug:"_3-实现一个-bind-函数",link:"#_3-实现一个-bind-函数",children:[]},{level:3,title:"4.实现 instanceof",slug:"_4-实现-instanceof",link:"#_4-实现-instanceof",children:[]},{level:3,title:"5.实现一个 new",slug:"_5-实现一个-new",link:"#_5-实现一个-new",children:[]},{level:3,title:"6.Generator-id 生成器",slug:"_6-generator-id-生成器",link:"#_6-generator-id-生成器",children:[]},{level:3,title:"7.函数柯里化一道面试题",slug:"_7-函数柯里化一道面试题",link:"#_7-函数柯里化一道面试题",children:[]},{level:3,title:"8.实现一个管道函数",slug:"_8-实现一个管道函数",link:"#_8-实现一个管道函数",children:[]},{level:3,title:"9.手写 loadsh_get 方法",slug:"_9-手写-loadsh-get-方法",link:"#_9-手写-loadsh-get-方法",children:[]},{level:3,title:"10.手写 nextTick 方法",slug:"_10-手写-nexttick-方法",link:"#_10-手写-nexttick-方法",children:[]},{level:3,title:"11.allComplete",slug:"_11-allcomplete",link:"#_11-allcomplete",children:[]},{level:3,title:"12.防抖与节流",slug:"_12-防抖与节流",link:"#_12-防抖与节流",children:[]},{level:3,title:"13.深拷贝浅拷贝",slug:"_13-深拷贝浅拷贝",link:"#_13-深拷贝浅拷贝",children:[]}]},{level:2,title:"手写 Promise",slug:"手写-promise",link:"#手写-promise",children:[{level:3,title:"完整 Promise",slug:"完整-promise",link:"#完整-promise",children:[]},{level:3,title:"01-构造函数",slug:"_01-构造函数",link:"#_01-构造函数",children:[]},{level:3,title:"02-状态及原因",slug:"_02-状态及原因",link:"#_02-状态及原因",children:[]},{level:3,title:"03-then 的方法-成功和失败的回调",slug:"_03-then-的方法-成功和失败的回调",link:"#_03-then-的方法-成功和失败的回调",children:[]},{level:3,title:"04-then 的方法",slug:"_04-then-的方法",link:"#_04-then-的方法",children:[]},{level:3,title:"05-异步任务 API",slug:"_05-异步任务-api",link:"#_05-异步任务-api",children:[]},{level:3,title:"06-异步任务-函数封装",slug:"_06-异步任务-函数封装",link:"#_06-异步任务-函数封装",children:[]},{level:3,title:"7-链式编程-处理返回值异常",slug:"_7-链式编程-处理返回值异常",link:"#_7-链式编程-处理返回值异常",children:[]},{level:3,title:"8-链式编程-处理返回 Promise",slug:"_8-链式编程-处理返回-promise",link:"#_8-链式编程-处理返回-promise",children:[]},{level:3,title:"9-链式编程-处理重复引用",slug:"_9-链式编程-处理重复引用",link:"#_9-链式编程-处理重复引用",children:[]},{level:3,title:"10-链式编程-rejected 状态",slug:"_10-链式编程-rejected-状态",link:"#_10-链式编程-rejected-状态",children:[]},{level:3,title:"11-实例方法-catch-finally",slug:"_11-实例方法-catch-finally",link:"#_11-实例方法-catch-finally",children:[]},{level:3,title:"12-静态方法",slug:"_12-静态方法",link:"#_12-静态方法",children:[]}]},{level:2,title:"设计模式",slug:"设计模式",link:"#设计模式",children:[{level:3,title:"单例模式",slug:"单例模式",link:"#单例模式",children:[]},{level:3,title:"观察者模式",slug:"观察者模式",link:"#观察者模式",children:[]},{level:3,title:"发布订阅",slug:"发布订阅",link:"#发布订阅",children:[]},{level:3,title:"发布订阅另一种",slug:"发布订阅另一种",link:"#发布订阅另一种",children:[]}]}],path:"/base/%E6%89%8B%E5%86%99%E9%A2%98.html",pathLocale:"/",extraFields:[]},{title:"正则表达式",headers:[{level:2,title:"正则表达式速查",slug:"正则表达式速查",link:"#正则表达式速查",children:[]},{level:2,title:"正则应用",slug:"正则应用",link:"#正则应用",children:[]},{level:2,title:"八个常用的正则",slug:"八个常用的正则",link:"#八个常用的正则",children:[]}],path:"/base/%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F.html",pathLocale:"/",extraFields:[]},{title:"Git",headers:[{level:2,title:"Git合作开发场景",slug:"git合作开发场景",link:"#git合作开发场景",children:[]}],path:"/computer/Git.html",pathLocale:"/",extraFields:[]},{title:"Linux",headers:[],path:"/computer/Linux.html",pathLocale:"/",extraFields:[]},{title:"介绍",headers:[],path:"/computer/",pathLocale:"/",extraFields:[]},{title:"Web应用安全",headers:[],path:"/computer/Web%E5%BA%94%E7%94%A8%E5%AE%89%E5%85%A8.html",pathLocale:"/",extraFields:[]},{title:"操作系统与编译原理",headers:[{level:2,title:"参考资料",slug:"参考资料",link:"#参考资料",children:[]}],path:"/computer/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F_%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86.html",pathLocale:"/",extraFields:[]},{title:"数据库",headers:[],path:"/computer/%E6%95%B0%E6%8D%AE%E5%BA%93.html",pathLocale:"/",extraFields:[]},{title:"计算机网络",headers:[{level:2,title:"UDP 和 TCP",slug:"udp-和-tcp",link:"#udp-和-tcp",children:[]},{level:2,title:"HTTP/HTTPS",slug:"http-https",link:"#http-https",children:[]}],path:"/computer/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C.html",pathLocale:"/",extraFields:[]},{title:"设计模式",headers:[],path:"/computer/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F.html",pathLocale:"/",extraFields:[]},{title:"会做代码的Review吗?",headers:[],path:"/interview/codeReview.html",pathLocale:"/",extraFields:[]},{title:"项目的编码规范",headers:[{level:2,title:"前端代码规范",slug:"前端代码规范",link:"#前端代码规范",children:[]},{level:2,title:"一.编程规约",slug:"一-编程规约",link:"#一-编程规约",children:[{level:3,title:"(一) 命名规范",slug:"一-命名规范",link:"#一-命名规范",children:[]},{level:3,title:"(二) HTML 规范 (Vue Template 同样适用)",slug:"二-html-规范-vue-template-同样适用",link:"#二-html-规范-vue-template-同样适用",children:[]},{level:3,title:"(三) CSS 规范",slug:"三-css-规范",link:"#三-css-规范",children:[]},{level:3,title:"(四) LESS 规范",slug:"四-less-规范",link:"#四-less-规范",children:[]},{level:3,title:"(五) Javascript 规范",slug:"五-javascript-规范",link:"#五-javascript-规范",children:[]}]},{level:2,title:"二、Vue 项目规范",slug:"二、vue-项目规范",link:"#二、vue-项目规范",children:[{level:3,title:"(一) Vue 编码基础",slug:"一-vue-编码基础",link:"#一-vue-编码基础",children:[]},{level:3,title:"(二) Vue 项目目录规范",slug:"二-vue-项目目录规范",link:"#二-vue-项目目录规范",children:[]},{level:3,title:"前后端分离必备的接口规范",slug:"前后端分离必备的接口规范",link:"#前后端分离必备的接口规范",children:[]}]}],path:"/interview/coding.html",pathLocale:"/",extraFields:[]},{title:"前端代码风格上的工具",headers:[{level:2,title:"Vue3项目创建时可选用的代码格式化 Prettier",slug:"vue3项目创建时可选用的代码格式化-prettier",link:"#vue3项目创建时可选用的代码格式化-prettier",children:[]},{level:2,title:"项目中引入 ESLint",slug:"项目中引入-eslint",link:"#项目中引入-eslint",children:[]}],path:"/interview/codingStyle.html",pathLocale:"/",extraFields:[]},{title:"CSRF,如何防御CSRF攻击",headers:[{level:2,title:"一个典型的CSRF攻击有着如下的流程",slug:"一个典型的csrf攻击有着如下的流程",link:"#一个典型的csrf攻击有着如下的流程",children:[]},{level:2,title:"几种常见的攻击类型",slug:"几种常见的攻击类型",link:"#几种常见的攻击类型",children:[]},{level:2,title:"如何进行防御",slug:"如何进行防御",link:"#如何进行防御",children:[{level:3,title:"同源检测",slug:"同源检测",link:"#同源检测",children:[]},{level:3,title:"CSRF Token",slug:"csrf-token",link:"#csrf-token",children:[]},{level:3,title:"双重Cookie验证",slug:"双重cookie验证",link:"#双重cookie验证",children:[]},{level:3,title:"Samesite Cookie属性",slug:"samesite-cookie属性",link:"#samesite-cookie属性",children:[]}]},{level:2,title:"防止网站被利用",slug:"防止网站被利用",link:"#防止网站被利用",children:[]}],path:"/interview/CSRF.html",pathLocale:"/",extraFields:[]},{title:"其他的一些小问题",headers:[{level:2,title:"proxy的优缺点?",slug:"proxy的优缺点",link:"#proxy的优缺点",children:[]},{level:2,title:"Vue的双向绑定原理(腾讯)",slug:"vue的双向绑定原理-腾讯",link:"#vue的双向绑定原理-腾讯",children:[]},{level:2,title:"HTTP请求方法:幂等和非幂等?",slug:"http请求方法-幂等和非幂等",link:"#http请求方法-幂等和非幂等",children:[]},{level:2,title:"内存泄漏问题?",slug:"内存泄漏问题",link:"#内存泄漏问题",children:[]},{level:2,title:"前端开发中,使用base64图片的弊端是什么?",slug:"前端开发中-使用base64图片的弊端是什么",link:"#前端开发中-使用base64图片的弊端是什么",children:[]},{level:2,title:"什么是Gzip?",slug:"什么是gzip",link:"#什么是gzip",children:[]}],path:"/interview/other.html",pathLocale:"/",extraFields:[]},{title:"面试经历及问题",headers:[],path:"/interview/",pathLocale:"/",extraFields:[]},{title:"项目中状态码的设置?设置在HTTP状态码还是返回业务状态码?",headers:[{level:2,title:"HTTP 状态码",slug:"http-状态码",link:"#http-状态码",children:[]},{level:2,title:"业务状态码",slug:"业务状态码",link:"#业务状态码",children:[]}],path:"/interview/statusCode.html",pathLocale:"/",extraFields:[]},{title:"学习资料",headers:[{level:3,title:"基础入门",slug:"基础入门",link:"#基础入门",children:[]},{level:3,title:"深入学习",slug:"深入学习",link:"#深入学习",children:[]}],path:"/intro/asset.html",pathLocale:"/",extraFields:[]},{title:"我的网页收藏",headers:[],path:"/intro/group.html",pathLocale:"/",extraFields:[]},{title:"我能学到什么",headers:[],path:"/intro/learn.html",pathLocale:"/",extraFields:[]},{title:"前序",headers:[],path:"/intro/pre.html",pathLocale:"/",extraFields:[]},{title:"学习路线",headers:[],path:"/intro/",pathLocale:"/",extraFields:[]},{title:"React 脚手架",headers:[{level:2,title:"开发模式配置",slug:"开发模式配置",link:"#开发模式配置",children:[]},{level:2,title:"生产模式配置",slug:"生产模式配置",link:"#生产模式配置",children:[]},{level:2,title:"其他配置",slug:"其他配置",link:"#其他配置",children:[]},{level:2,title:"合并开发和生产配置",slug:"合并开发和生产配置",link:"#合并开发和生产配置",children:[]},{level:2,title:"优化配置",slug:"优化配置",link:"#优化配置",children:[]}],path:"/project/react-cli.html",pathLocale:"/",extraFields:[]},{title:"介绍",headers:[],path:"/project/",pathLocale:"/",extraFields:[]},{title:"总结",headers:[],path:"/project/summary.html",pathLocale:"/",extraFields:[]},{title:"Vue 脚手架",headers:[{level:2,title:"开发模式配置",slug:"开发模式配置",link:"#开发模式配置",children:[]},{level:2,title:"生产模式配置",slug:"生产模式配置",link:"#生产模式配置",children:[]},{level:2,title:"其他配置",slug:"其他配置",link:"#其他配置",children:[]},{level:2,title:"合并开发和生产配置",slug:"合并开发和生产配置",link:"#合并开发和生产配置",children:[]},{level:2,title:"优化配置",slug:"优化配置",link:"#优化配置",children:[]}],path:"/project/vue-cli.html",pathLocale:"/",extraFields:[]},{title:"",headers:[],path:"/404.html",pathLocale:"/",extraFields:[]}],Vm=ue(zm),Um=()=>Vm,Wm=({searchIndex:e,routeLocale:t,query:n,maxSuggestions:r})=>{const l=I(()=>e.value.filter(o=>o.pathLocale===t.value));return I(()=>{const o=n.value.trim().toLowerCase();if(!o)return[];const i=[],s=(a,c)=>{yi(o,[c.title])&&i.push({link:`${a.path}#${c.slug}`,title:a.title,header:c.title});for(const u of c.children){if(i.length>=r.value)return;s(a,u)}};for(const a of l.value){if(i.length>=r.value)break;if(yi(o,[a.title,...a.extraFields])){i.push({link:a.path,title:a.title});continue}for(const c of a.headers){if(i.length>=r.value)break;s(a,c)}}return i})},Km=e=>{const t=ue(0);return{focusIndex:t,focusNext:()=>{t.value{t.value>0?t.value-=1:t.value=e.value.length-1}}},qm=me({name:"SearchBox",props:{locales:{type:Object,default:()=>({})},hotKeys:{type:Array,default:()=>[]},maxSuggestions:{type:Number,default:5}},setup(e){const{locales:t,hotKeys:n,maxSuggestions:r}=Br(e),l=ln(),o=yn(),i=Um(),s=ue(null),a=ue(!1),c=ue(""),u=I(()=>t.value[o.value]??{}),f=Wm({searchIndex:i,routeLocale:o,query:c,maxSuggestions:r}),{focusIndex:d,focusNext:m,focusPrev:g}=Km(f);jm({input:s,hotKeys:n});const y=I(()=>a.value&&!!f.value.length),w=()=>{y.value&&g()},T=()=>{y.value&&m()},x=v=>{if(!y.value)return;const k=f.value[v];k&&l.push(k.link).then(()=>{c.value="",d.value=0})};return()=>ce("form",{class:"search-box",role:"search"},[ce("input",{ref:s,type:"search",placeholder:u.value.placeholder,autocomplete:"off",spellcheck:!1,value:c.value,onFocus:()=>a.value=!0,onBlur:()=>a.value=!1,onInput:v=>c.value=v.target.value,onKeydown:v=>{switch(v.key){case"ArrowUp":{w();break}case"ArrowDown":{T();break}case"Enter":{v.preventDefault(),x(d.value);break}}}}),y.value&&ce("ul",{class:"suggestions",onMouseleave:()=>d.value=-1},f.value.map(({link:v,title:k,header:N},O)=>ce("li",{class:["suggestion",{focus:d.value===O}],onMouseenter:()=>d.value=O,onMousedown:()=>x(O)},ce("a",{href:v,onClick:D=>D.preventDefault()},[ce("span",{class:"page-title"},k),N&&ce("span",{class:"page-header"},`> ${N}`)]))))])}});var Gm=["s","/"],Jm={"/":{placeholder:"搜索"}};const Ym=Jm,Xm=Gm,Qm=10,Zm=kt({enhance({app:e}){e.component("SearchBox",t=>ce(qm,{locales:Ym,hotKeys:Xm,maxSuggestions:Qm,...t}))}}),vr=[Nd,Wd,Yd,ah,dh,vh,Rm,Bm,Zm],eg=JSON.parse('{"base":"/","lang":"zh-CN","title":"🍰 小雨的学习记录","description":"在互联网的广阔天地,深知技术日新月异,不进则退,对前端开发的热爱,源于对生活持续学习、不断进步的态度","head":[["link",{"rel":"icon","type":"image/x-icon","href":"/imgs/favicon.ico"}]],"locales":{}}');var Ln=_n(eg),tg=Sf,ng=()=>{const e=Zf({history:tg(xs("/")),routes:[{name:"vuepress-route",path:"/:catchAll(.*)",components:{}}],scrollBehavior:(t,n,r)=>r||(t.hash?{el:t.hash}:{top:0})});return e.beforeResolve(async(t,n)=>{if(t.path!==n.path||n===_t){const r=Wn(t.path);if(r.path!==t.path)return r.path;const l=await r.loader();t.meta={...r.meta,_pageChunk:l}}else t.path===n.path&&(t.meta=n.meta)}),e},rg=e=>{e.component("ClientOnly",Ql),e.component("Content",sd),e.component("RouteLink",rr)},lg=(e,t,n)=>{const r=I(()=>t.currentRoute.value.path),l=Ya((w,T)=>({get(){return w(),t.currentRoute.value.meta._pageChunk},set(x){t.currentRoute.value.meta._pageChunk=x,T()}})),o=I(()=>Kt.resolveLayouts(n)),i=I(()=>Kt.resolveRouteLocale(Ln.value.locales,r.value)),s=I(()=>Kt.resolveSiteLocaleData(Ln.value,i.value)),a=I(()=>l.value.comp),c=I(()=>l.value.data),u=I(()=>c.value.frontmatter),f=I(()=>Kt.resolvePageHeadTitle(c.value,s.value)),d=I(()=>Kt.resolvePageHead(f.value,u.value,s.value)),m=I(()=>Kt.resolvePageLang(c.value,s.value)),g=I(()=>Kt.resolvePageLayout(c.value,o.value)),y={layouts:o,pageData:c,pageComponent:a,pageFrontmatter:u,pageHead:d,pageHeadTitle:f,pageLang:m,pageLayout:g,redirects:Sl,routeLocale:i,routePath:r,routes:Un,siteData:Ln,siteLocaleData:s};return e.provide(Yl,y),Object.defineProperties(e.config.globalProperties,{$frontmatter:{get:()=>u.value},$head:{get:()=>d.value},$headTitle:{get:()=>f.value},$lang:{get:()=>m.value},$page:{get:()=>c.value},$routeLocale:{get:()=>i.value},$site:{get:()=>Ln.value},$siteLocale:{get:()=>s.value},$withBase:{get:()=>Kr}}),y},og=()=>{const e=nd(),t=rd();let n=[];const r=()=>{e.value.forEach(i=>{const s=ig(i);s&&n.push(s)})},l=()=>{const i=[];return e.value.forEach(s=>{const a=sg(s);a&&i.push(a)}),i},o=()=>{document.documentElement.lang=t.value;const i=l();n.forEach((s,a)=>{const c=i.findIndex(u=>s.isEqualNode(u));c===-1?(s.remove(),delete n[a]):i.splice(c,1)}),i.forEach(s=>document.head.appendChild(s)),n=[...n.filter(s=>!!s),...i]};en(id,o),Ue(()=>{r(),Me(e,o,{immediate:!1})})},ig=([e,t,n=""])=>{const r=Object.entries(t).map(([s,a])=>it(a)?`[${s}=${JSON.stringify(a)}]`:a===!0?`[${s}]`:"").join(""),l=`head > ${e}${r}`;return Array.from(document.querySelectorAll(l)).find(s=>s.innerText===n)||null},sg=([e,t,n])=>{if(!it(e))return null;const r=document.createElement(e);return ql(t)&&Object.entries(t).forEach(([l,o])=>{it(o)?r.setAttribute(l,o):o===!0&&r.setAttribute(l,"")}),it(n)&&r.appendChild(document.createTextNode(n)),r},ag=Nu,cg=async()=>{var n;const e=ag({name:"Vuepress",setup(){var o;og();for(const i of vr)(o=i.setup)==null||o.call(i);const r=vr.flatMap(({rootComponents:i=[]})=>i.map(s=>ce(s))),l=ld();return()=>[ce(l.value),r]}}),t=ng();rg(e),lg(e,t,vr);for(const r of vr)await((n=r.enhance)==null?void 0:n.call(r,{app:e,router:t,siteData:Ln}));return e.use(t),{app:e,router:t}};cg().then(({app:e,router:t})=>{t.isReady().then(()=>{e.mount("#app")})});export{Ce as _,re as a,pt as b,Z as c,cg as createVueApp,ie as d,Zc as e,z as o,tn as r,Le as w}; diff --git a/assets/asset.html-Dg4uFOz9.js b/assets/asset.html-Dg4uFOz9.js new file mode 100644 index 0000000..ad0c1bb --- /dev/null +++ b/assets/asset.html-Dg4uFOz9.js @@ -0,0 +1 @@ +import{_ as a,r as l,o as s,c as i,a as e,b as t,d as n,w as c}from"./app-B-BkP2m_.js";const h={},_=e("h1",{id:"学习资料",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#学习资料"},[e("span",null,"学习资料")])],-1),d=e("h3",{id:"基础入门",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#基础入门"},[e("span",null,"基础入门")])],-1),u={href:"http://www.bilibili.com/",target:"_blank",rel:"noopener noreferrer"},m={href:"https://www.w3school.com.cn/",target:"_blank",rel:"noopener noreferrer"},p={href:"https://www.runoob.com/",target:"_blank",rel:"noopener noreferrer"},f={href:"https://www.ruanyifeng.com/",target:"_blank",rel:"noopener noreferrer"},w={href:"https://developer.mozilla.org/zh-CN/",target:"_blank",rel:"noopener noreferrer"},k=e("li",null,"稀土掘金、微信公众号推文",-1),b=e("h3",{id:"深入学习",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#深入学习"},[e("span",null,"深入学习")])],-1),g=e("li",null,"除了广泛学习还需了解技术的底层原理,看博客文章,上手实践!",-1);function x(v,N){const o=l("ExternalLinkIcon"),r=l("RouteLink");return s(),i("div",null,[_,d,e("ul",null,[e("li",null,[t("视频学习:打开 "),e("a",u,[t("哔哩哔哩"),n(o)]),t(" 视频网站,搜索尚硅谷、黑马程序员。")]),e("li",null,[t("文档学习:根据文档做TodoList、Demo "),e("ul",null,[e("li",null,[e("a",m,[t("w3school"),n(o)])]),e("li",null,[e("a",p,[t("菜鸟教程"),n(o)])]),e("li",null,[e("a",f,[t("阮一峰老师的文章"),n(o)])]),e("li",null,[e("a",w,[t("MDN"),n(o)])]),k])])]),b,e("ul",null,[e("li",null,[n(r,{to:"/intro/group.html"},{default:c(()=>[t("我的网页收藏")]),_:1})]),g])])}const T=a(h,[["render",x],["__file","asset.html.vue"]]),C=JSON.parse('{"path":"/intro/asset.html","title":"学习资料","lang":"zh-CN","frontmatter":{},"headers":[{"level":3,"title":"基础入门","slug":"基础入门","link":"#基础入门","children":[]},{"level":3,"title":"深入学习","slug":"深入学习","link":"#深入学习","children":[]}],"filePathRelative":"intro/asset.md","git":{"createdTime":1715588813000,"updatedTime":1715588813000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":1}]},"readingTime":{"minutes":0.39,"words":116}}');export{T as comp,C as data}; diff --git a/assets/codeReview.html-DIjhiR9Y.js b/assets/codeReview.html-DIjhiR9Y.js new file mode 100644 index 0000000..b142921 --- /dev/null +++ b/assets/codeReview.html-DIjhiR9Y.js @@ -0,0 +1 @@ +import{_ as r,r as t,o as a,c,a as e,b as l,d as o,e as n}from"./app-B-BkP2m_.js";const s={},d=n('

会做代码的Review吗?

1、什么是CodeReview?

Code Review(CR)即代码评审,又名代码走查,是一种通过复查代码来提高代码质量的过程,一般体现在一个团队的开发过程中。CR要求团队成员有意识地、系统地检查彼此的代码,从而验证需求、发现错误,同时指出其中不合规范的“低质量”代码,从而提高整个团队的代码质量。

一次 CR 可以是一次 Commit,也可以是一次 Merge Request。因此,实践课系统支持团队内部的 MR 评审以及 Commit 评审,供大家学习和交流。

2、为什么要CodeReview?

(1)旁观者清。

  • 对于同一段业务代码,由于看待问题的角度不同,评审者可能会比开发者更容易发现其中的问题,或是找到更有效的解决方案,共同维护团队的代码质量。
  • 提高代码质量和可维护性, 可读性等。
  • 查漏补缺, 发现一些潜在的问题点等。
  • 最佳实践, 能够更好更快的完成任务的方法。
  • 知识分享, Review他人代码时, 其实也是一个学习的过程, 自己也会反思&总结。

(2)快速了解业务。

  • 理想状态下,团队中的每个人都需要对整个项目的各个部分都很熟悉,当然,在项目很大时这是不现实的。通过代码审查至少可以让每个人了解更多的业务模块,同时也能达到人员互备的目的。
  • 人员互备:通过 CR,评审者也相当于参与了这次开发,相当于一种人力“备份”,当你休假或正在忙别的需求的时候,这时“备份”或许就能帮上你的忙了。

(3)开发者能够获得什么?

  • 对需求的理解得到加深。
  • 表达能力得到加强。
  • 逻辑能力得到训练。
  • 心理承受能力得到提高。 (4)评审者能够获得什么?
  • 快速上手业务需求和全局的架构。
  • 统一大家约定俗成的代码风格。
  • 优秀的设计思路和业务逻辑。
',11),p={href:"https://www.bilibili.com/video/BV19F4m1c76g/?spm_id_from=333.337.search-card.all.click",target:"_blank",rel:"noopener noreferrer"},_={href:"https://www.bilibili.com/video/BV12u411C7TV/?spm_id_from=333.337.search-card.all.click&vd_source=bf3353ad677b1fdc2e25b9a255e71902",target:"_blank",rel:"noopener noreferrer"},m={href:"https://www.bilibili.com/video/BV1Dh4y1J7iG/?spm_id_from=333.337.search-card.all.click",target:"_blank",rel:"noopener noreferrer"};function w(h,v){const i=t("ExternalLinkIcon");return a(),c("div",null,[d,e("p",null,[e("a",p,[l("CodeReview中的常见问题【渡一教育】_哔哩哔哩_bilibili"),o(i)])]),e("p",null,[e("a",_,[l("第一次被代码审查(code review)的经历,太真实了!_哔哩哔哩_bilibili"),o(i)])]),e("p",null,[e("a",m,[l("code review 真的有必要吗,到底在review什么?_哔哩哔哩_bilibili"),o(i)])])])}const b=r(s,[["render",w],["__file","codeReview.html.vue"]]),f=JSON.parse('{"path":"/interview/codeReview.html","title":"会做代码的Review吗?","lang":"zh-CN","frontmatter":{},"headers":[],"filePathRelative":"interview/codeReview.md","git":{"createdTime":1715780535000,"updatedTime":1715780535000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":1}]},"readingTime":{"minutes":2.12,"words":635}}');export{b as comp,f as data}; diff --git a/assets/coding.html-C-Uholnr.js b/assets/coding.html-C-Uholnr.js new file mode 100644 index 0000000..38b5b3c --- /dev/null +++ b/assets/coding.html-C-Uholnr.js @@ -0,0 +1,466 @@ +import{_ as t,r as l,o as d,c,a as e,b as n,d as a,e as i}from"./app-B-BkP2m_.js";const r={},o=i(`

项目的编码规范

前端代码规范

规范的目的是为了编写高质量的代码,让你的团队成员每天得心情都是愉悦的,大家在一起是快乐的。
引自《阿里规约》的开头片段:
---现代软件架构的复杂性需要协同开发完成,如何高效地协同呢?无规矩不成方圆,无规范难以协同,比如,制订交通法规表面上是要限制行车权,实际上是保障公众的人身安全,试想如果没有限速,没有红绿灯,谁还敢上路行驶。对软件来说,适当的规范和标准绝不是消灭代码内容的创造性、优雅性,而是限制过度个性化,以一种普遍认可的统一方式一起做事,提升协作效率,降低沟通成本。代码的字里行间流淌的是软件系统的血液,质量的提升是尽可能少踩坑,杜绝踩重复的坑,切实提升系统稳定性,码出质量。

一.编程规约

(一) 命名规范

1.1.1 项目命名

全部采用小写方式,以中线分隔。

正:mall-management-system
反:mall_management-system / mallManagementSystem

1.1.2 目录命名

全部采用小写方式, 以中划线分隔,有复数结构时,要采用复数命名法, 缩写不用复数。

正例: scripts/styles/components/images/utils/layouts/demo-styles/demo-scripts/img/doc
反例: script/style/demo_scripts/demoStyles/imgs/docs
【特殊】VUE的项目中的components中的组件目录,使用kebab-case命名。
正例: head-search/page-loading/authorized/notice-icon
反例: HeadSearch/PageLoading
【特殊】VUE的项目中的除components组件目录外的所有目录也使用kebab-case命名。
正例: page-one/shopping-car/user-management
反例: ShoppingCar/UserManagement

1.1.3 JS、CSS、SCSS、HTML、PNG 文件命名

全部采用小写方式, 以中划线分隔。

正例: render-dom.js /signup.css/index.html/company-logo.png
反例: renderDom.js/UserManagement.html

1.1.4 命名严谨性

代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。 说明:正确的 英文拼写和语法可以让阅读者易于理解,避免歧义。注意,即使纯拼音命名方式也要避免采用

正例:henan/luoyang/rmb等国际通用的名称,可视同英文
反例:DazhePromotion[打折]/ getPingfenByName()[评分]/ int某变量= 3

杜绝完全不规范的缩写,避免望文不知义:

反例: AbstractClass“缩写"命名成 AbsClass ;
condition“缩写"命名成condi,此类随意缩写严重降低了代码的可阅读性。

(二) HTML 规范 (Vue Template 同样适用)

1.2.1 HTML 类型

推荐使用 HTML5 的文档类型申明:(建议使用text/html格式的 HTML。避免使用XHTML。XHTML 以及它的属性,比如application/xhtml+xml在浏览器中的应用支持与优化空间都十分有限)。

  • 规定字符编码
  • IE兼容模式
  • 规定字符编码
  • doctype大写

正例:

<!DOCTYPE html>
+<html>
+  <head> 
+      <meta http-equiv="X-UA-Compatible" content="IE=Edge" /> 
+	  <meta charset="UTF-8" /> 
+	  <title>Page title</title> 
+  </head>
+  <body> 
+	 <img src="images/company-logo.png" alt="Company">
+ </body> 
+  </html>
+

1.2.2 缩进

缩进使用 2 个空格(一个 tab);

嵌套的节点应该缩进。

1.2.3 分块注释

在每一个块状元素,列表元素和表格元素后,加上一对 HTML 注释。

1.2.4 语义化标签

HTML5 中新增很多语义化标签,所以优先使用语义化标签,避免一个页面都是 div 或者 p 标 签。

正例

<header></header> 
+<footer></footer>
+

反例

<div> 
+  <p></p>
+ </div>
+

1.2.5 引号

使用双引号(" ") 而不是单引号(' ') 。

正例:<div class="box"></div>
反例:<div class='box'></div>

(三) CSS 规范

1.3.1 命名

  • 类名使用小写字母,以中划线分隔
  • id采用驼峰式命名
  • scss中的变量、函数、混合、placeholder采用驼峰式命名

ID和class的名称总是使用可以反应元素目的和用途的名称,或其他通用的名称,代替表象和晦涩难懂的名称。

不推荐:

.fw-800 {
+    font-weight: 800;
+  }
+  .red {
+    color: red; 
+   }
+

推荐:

.heavy {
+   font-weight: 800;
+  }
+.important { 
+  color: red; 
+  }
+

1.3.2 选择器

1) css 选择器中避免使用标签名

从结构、表现、行为分离的原则来看,应该尽量避免css中出现HTML标签,并且在css选择器中出现标签名会存在潜在的问题。

2) 使用直接子选择器

很多前端开发人员写选择器链的时候不使用直接子选择器(注:直接子选择器和后代选择器的区别)。有时,这可能会导致疼痛的设计问题并且有时候可能会很耗性能。然而,在任何情况下,这是一个非常不好的做法。如果你不写很通用的,需要匹配到DOM末端的选择器,你应该总是考虑直接子选择器。

不推荐:

.content .title {
+   font-size: 2rem;
+  }
+ 
+

推荐:

.content > .title {
+   font-size: 2rem;
+ }
+

1.3.3 尽量使用缩写属性

不推荐:

border-top-style: none; 
+font-family: palatino, georgia, serif; 
+font-size: 100%; line-height: 1.6; 
+padding-bottom: 2em; 
+padding-left: 1em;
+ padding-right: 1em; 
+ padding-top: 0;
+

推荐:

border-top: 0; 
+font: 100%/1.6 palatino, georgia, serif; 
+padding: 0 1em 2em;
+

1.3.4 每个选择器及属性独占一行

不推荐:

button { 
+	width: 100px; 
+	height: 50px;
+	color: #fff;
+	background: #00a0e9;
+  }
+

推荐:

button {
+  width: 100px; height: 50px;
+  color: #fff;
+  background: #00a0e9; 
+}
+

1.3.5 省略 0 后面的单位

不推荐:

 div {
+	 padding-bottom: 0px; 
+	 margin: 0em;
+ }
+

推荐:

div {
+    padding-bottom: 0; 
+    margin: 0; 
+}
+

1.3.6 避免使用 ID 选择器及全局标签选择器防止污染全局样式

不推荐:

#header {
+ padding-bottom: 0px; 
+ margin: 0em;
+}
+

推荐:

.header { 
+	padding-bottom: 0px; 
+	margin: 0em; 
+}
+

(四) LESS 规范

1.4.1 代码组织

1) 将公共 less 文件放置在 style/less/common 文件夹

例: // color.less,common.less

2) 按以下顺序组织

1、@import;
2、变量声明;
3、样式声明;

@import "mixins/size.less"; 
+@default-text-color: #333; 
+.page {
+ width: 960px; 
+ margin: 0 auto; 
+}
+

1.4.2 避免嵌套层级过多

将嵌套深度限制在3级。对于超过4级的嵌套,给予重新评估。这可以避免出现过于详实的CSS 选择器。避免大量的嵌套规则。当可读性受到影响时,将之打断。推荐避免出现多于20行的嵌套规则出现。

不推荐:

 .main {
+   .title { 
+      .name { 
+           color: #fff;  
+         } 
+     }
+}
+

推荐:

.main-title {
+   .name { color: #fff; }
+    }
+

(五) Javascript 规范

1.5.1 命名

1) 采用小写驼峰命名 lowerCamelCase,代码中的命名均不能以下划线, 也不能以下划线或美元符号结束

反例: _name / name_ / name$

2) 方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风 格,必须遵从驼峰形式

正例: localValue / getHttpMessage() / inputUserId
其中 method 方法命名必须是 动词 或者 动词+名词 形式
正例: saveShopCarData /openShopCarInfoDialog
反例: save / open / show / go
特此说明,增删查改,详情统一使用如下 5 个单词,不得使用其他(目的是为了统一各个端)

add / update / delete / detail / get 
+附: 函数方法常用的动词: 
+get 获取/set 设置, 
+add 增加/remove 删除, 
+create 创建/destory 销毁, 
+start 启动/stop 停止, 
+open 打开/close 关闭, 
+read 读取/write 写入, 
+load 载入/save 保存,
+begin 开始/end 结束, 
+backup 备份/restore 恢复,
+import 导入/export 导出, 
+split 分割/merge 合并,
+inject 注入/extract 提取,
+attach 附着/detach 脱离, 
+bind 绑定/separate 分离, 
+view 查看/browse 浏览, 
+edit 编辑/modify 修改,
+select 选取/mark 标记, 
+copy 复制/paste 粘贴,
+undo 撤销/redo 重做, 
+insert 插入/delete 移除,
+add 加入/append 添加, 
+clean 清理/clear 清除,
+index 索引/sort 排序,
+find 查找/search 搜索, 
+increase 增加/decrease 减少, 
+play 播放/pause 暂停, 
+launch 启动/run 运行, 
+compile 编译/execute 执行, 
+debug 调试/trace 跟踪, 
+observe 观察/listen 监听,
+build 构建/publish 发布,
+input 输入/output 输出,
+encode 编码/decode 解码, 
+encrypt 加密/decrypt 解密, 
+compress 压缩/decompress 解压缩, 
+pack 打包/unpack 解包,
+parse 解析/emit 生成,
+connect 连接/disconnect 断开,
+send 发送/receive 接收, 
+download 下载/upload 上传, 
+refresh 刷新/synchronize 同步,
+update 更新/revert 复原, 
+lock 锁定/unlock 解锁, 
+check out 签出/check in 签入, 
+submit 提交/commit 交付, 
+push 推/pull 拉,
+expand 展开/collapse 折叠, 
+enter 进入/exit 退出,
+abort 放弃/quit 离开, 
+obsolete 废弃/depreciate 废旧, 
+collect 收集/aggregate 聚集
+
3) 常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚, 不要嫌名字长

正例: MAX_STOCK_COUNT 反例: MAX_COUNT

1.5.2 代码格式

1) 使用 2 个空格进行缩进

正例:

if (x < y) {
+ x += 10;
+  } else {
+   x += 1; 
+}
+
2) 不同逻辑、不同语义、不同业务的代码之间插入一个空行分隔开来以 提升可读性

说明:任何情形,没有必要插入多个空行进行隔开。

1.5.3 字符串

统一使用单引号(‘),不使用双引号(“)。这在创建 HTML 字符串非常有好处: 正例:

   let str = 'foo';
+   let testDiv = '<div id="test"></div>'; 
+

反例:

let str = 'foo'; 
+let testDiv = "<div id='test'></div>";
+

1.5.4 对象声明

1) 使用字面值创建对象

正例: let user = {};反例: let user = new Object();

2) 使用字面量来代替对象构造器

正例: var user = { age: 0, name: 1, city: 3 };反例:

var user = new Object(); 
+user.age = 0; 
+user.name = 0; 
+user.city = 0; 
+

1.5.5 使用 ES6+

必须优先使用ES6+中新增的语法糖和函数。这将简化你的程序,并让你的代码更加灵活和可复用。比如箭头函数、await/async,解构,let , for ...of 等等。

1.5.6 括号

下列关键字后必须有大括号(即使代码块的内容只有一行) : if, else, for,while, do, switch,try,catch, finally, with。

正例:

if (condition) { 
+doSomething();
+ }
+

反例:

if (condition) doSomething();
+

1.5.7 undefined 判断

永远不要直接使用 undefined 进行变量判断;使用 typeof 和字符串’undefined’对变量进行判断。

正例:

 if (typeof person === 'undefined') { ... }
+

反例:

if (person === undefined) { ... }
+

1.5.8 条件判断和循环最多三层

条件判断能使用三目运算符和逻辑运算符解决的,就不要使用条件判断,但是谨记不要写太长的三目运算符。如果超过3层请抽成函数,并写清楚注释。

1.5.9 this 的转换命名

对上下文this的引用只能使用'self来命名。

1.5.10 慎用 console.log

因console.log大量使用会有性能问题,所以在非webpack项目中谨慎使用log 功能。

二、Vue 项目规范

(一) Vue 编码基础

`,137),p={href:"https://cn.vuejs.org/v2/style-guide/%5D",target:"_blank",rel:"noopener noreferrer"},u=e("em",null,"请仔仔细细阅读 Vue 官方规范,切记,此为第一步。",-1),v=i(`

2.1.1. 组件规范

1) 组件名为多个单词。

组件名应该始终是多个单词组成(大于等于 2),且命名规范为KebabCase格式。 这样做可以避免跟现有的以及未来的 HTML 元素相冲突,因为所有的 HTML 元素名称都是单个单词的。

正例:

export default {
+  name: 'TodoItem'
+  // ...
+};
+

反例:


+export default {
+  name: 'Todo',
+  // ...
+}
+export default {
+  name: 'todo-item',
+  // ...
+}
+
2) 组件文件名为 pascal-case 格式

正例:

components/
+|- my-component.vue
+

反例:

components/
+|- myComponent.vue
+|- MyComponent.vue
+
3) 基础组件文件名为 base 开头,使用完整单词而不是缩写。

正例:

components/
+|- base-button.vue
+|- base-table.vue
+|- base-icon.vue
+

反例:

components/
+|- MyButton.vue
+|- VueTable.vue
+|- Icon.vue
+
4) 和父组件紧密耦合的子组件应该以父组件名作为前缀命名

正例:

components/
+|- todo-list.vue
+|- todo-list-item.vue
+|- todo-list-item-button.vue
+|- user-profile-options.vue (完整单词)
+

反例:

components/
+|- TodoList.vue
+|- TodoItem.vue
+|- TodoButton.vue
+|- UProfOpts.vue (使用了缩写)
+
5) 在 Template 模版中使用组件,应使用 PascalCase 模式,并且使用自闭合组件。

正例:

<!-- 在单文件组件、字符串模板和 JSX 中 -->
+<MyComponent />
+<Row><table :column="data"/></Row>
+

反例:

<my-component /> <row><table :column="data"/></row>
+
6) 组件的 data 必须是一个函数

当在组件中使用data属性的时候(除了new Vue外的任何地方),它的值必须是返回一个对象的函数。因为如果直接是一个对象的话,子组件之间的属性值会互相影响。

正例:

export default {
+  data () {
+    return {
+      name: 'jack'
+    }
+  }
+}
+

反例:

export default {
+  data: {
+    name: 'jack'
+  }
+}
+
7) Prop 定义应该尽量详细
  • 必须使用 camelCase 驼峰命名
  • 必须指定类型
  • 必须加上注释,表明其含义
  • 必须加上 required 或者 default,两者二选其一
  • 如果有业务需要,必须加上 validator 验证 正例:
 props: {
+  // 组件状态,用于控制组件的颜色
+   status: {
+     type: String,
+     required: true,
+     validator: function (value) {
+       return [
+         'succ',
+         'info',
+         'error'
+       ].indexOf(value) !== -1
+     }
+   },
+    // 用户级别,用于显示皇冠个数
+   userLevel:{
+      type: String,
+      required: true
+   }
+}
+
8) 为组件样式设置作用域

正例:

<template>
+  <button class="btn btn-close">X</button>
+</template>
+<!-- 使用 \`scoped\` 特性 -->
+<style scoped>
+  .btn-close {
+    background-color: red;
+  }
+</style>
+

反例:

<template>
+  <button class="btn btn-close">X</button>
+</template>
+<!-- 没有使用 \`scoped\` 特性 -->
+<style>
+  .btn-close {
+    background-color: red;
+  }
+</style>
+
9) 如果特性元素较多,应该主动换行。

正例:

<MyComponent foo="a" bar="b" baz="c"
+    foo="a" bar="b" baz="c"
+    foo="a" bar="b" baz="c"
+ />
+

反例:

<MyComponent foo="a" bar="b" baz="c" foo="a" bar="b" baz="c" foo="a" bar="b" baz="c" foo="a" bar="b" baz="c"/>
+

2.1.2. 模板中使用简单的表达式

组件模板应该只包含简单的表达式,复杂的表达式则应该重构为计算属性或方法。复杂表达式会让你的模板变得不那么声明式。我们应该尽量描述应该出现的是什么,而非如何计算那个值。而且计算属性和方法使得代码可以重用。

正例:

<template>
+  <p>{{ normalizedFullName }}</p>
+</template>
+// 复杂表达式已经移入一个计算属性
+computed: {
+  normalizedFullName: function () {
+    return this.fullName.split(' ').map(function (word) {
+      return word[0].toUpperCase() + word.slice(1)
+    }).join(' ')
+  }
+}
+

反例:

<template>
+  <p>
+       {{
+          fullName.split(' ').map(function (word) {
+             return word[0].toUpperCase() + word.slice(1)
+           }).join(' ')
+        }}
+  </p>
+</template>
+

2.1.3 指令都使用缩写形式

指令推荐都使用缩写形式,(用 : 表示 v-bind: 、用 @ 表示 v-on: 和用 # 表示 v-slot:)

正例:

<input
+  @input="onInput"
+  @focus="onFocus"
+>
+

反例:

<input
+  v-on:input="onInput"
+  @focus="onFocus"
+>
+

2.1.4 标签顺序保持一致

单文件组件应该总是让标签顺序保持为

正例:

<template>...</template>
+<script>...</script>
+<style>...</style>
+

反例:

<template>...</template>
+<style>...</style>
+<script>...</script>
+

2.1.5 必须为 v-for 设置键值 key

2.1.6 v-show 与 v-if 选择

如果运行时,需要非常频繁地切换,使用 v-show ;如果在运行时,条件很少改变,使用 v-if。

2.1.7 script 标签内部结构顺序

components > props > data > computed > watch > filter > 钩子函数(钩子函数按其执行顺序) > methods

2.1.8 Vue Router 规范

1) 页面跳转数据传递使用路由参数

页面跳转,例如 A 页面跳转到 B 页面,需要将 A 页面的数据传递到 B 页面,推荐使用 路由参数进行传参,而不是将需要传递的数据保存 vuex,然后在 B 页面取出 vuex 的数据,因为如果在 B 页面刷新会导致 vuex 数据丢失,导致 B 页面无法正常显示数据。 正例:

let id = ' 123';
+this.$router.push({ name: 'userCenter', query: { id: id } });
+
2) 使用路由懒加载(延迟加载)机制
{
+    path: '/uploadAttachment',
+    name: 'uploadAttachment',
+    meta: {
+      title: '上传附件'
+    },
+    component: () => import('@/view/components/uploadAttachment/index.vue')
+  },
+

3) router 中的命名规范 path、childrenPoints 命名规范采用kebab-case命名规范(尽量vue文件的目录结构保持一致,因为目录、文件名都是kebab-case,这样很方便找到对应的文件) name 命名规范采用KebabCase命名规范且和component组件名保持一致!(因为要保持keep-alive特性,keep-alive按照component的name进行缓存,所以两者必须高度保持一致)

// 动态加载
+export const reload = [
+  {
+    path: '/reload',
+    name: 'reload',
+    component: Main,
+    meta: {
+      title: '动态加载',
+      icon: 'icon iconfont'
+    },
+    children: [
+      {
+        path: '/reload/smart-reload-list',
+        name: 'SmartReloadList',
+        meta: {
+          title: 'SmartReload',
+          childrenPoints: [
+            {
+              title: '查询',
+              name: 'smart-reload-search'
+            },
+            {
+              title: '执行reload',
+              name: 'smart-reload-update'
+            },
+            {
+              title: '查看执行结果',
+              name: 'smart-reload-result'
+            }
+          ]
+        },
+        component: () =>
+          import('@/views/reload/smart-reload/smart-reload-list.vue')
+      }
+    ]
+  }
+];
+
4) router 中的 path 命名规范

path除了采用kebab-case命名规范以外,必须以 / 开头,即使是children里的path也要以 / 开头。如下示例 目的: 经常有这样的场景:某个页面有问题,要立刻找到这个vue文件,如果不用以/开头,path为parent和children组成的,可能经常需要在router文件里搜索多次才能找到,而如果以/开头,则能立刻搜索到对应的组件

{
+    path: '/file',
+    name: 'File',
+    component: Main,
+    meta: {
+      title: '文件服务',
+      icon: 'ios-cloud-upload'
+    },
+    children: [
+      {
+        path: '/file/file-list',
+        name: 'FileList',
+        component: () => import('@/views/file/file-list.vue')
+      },
+      {
+        path: '/file/file-add',
+        name: 'FileAdd',
+        component: () => import('@/views/file/file-add.vue')
+      },
+      {
+        path: '/file/file-update',
+        name: 'FileUpdate',
+        component: () => import('@/views/file/file-update.vue')
+      }
+    ]
+  }
+

(二) Vue 项目目录规范

2.2.1 基础

vue 项目中的所有命名一定要与后端命名统一。 比如权限:后端 privilege, 前端无论 router , store, api 等都必须使用 privielege 单词! 2.2.2 使用 Vue-cli 脚手架 使用 vue-cli3 来初始化项目,项目名按照上面的命名规范。 2.2.3 目录说明 目录名按照上面的命名规范,其中 components 组件用大写驼峰,其余除 components 组件目录外的所有目录均使用 kebab-case 命名。

src                                  源码目录
+|-- api                              所有api接口
+|-- assets                           静态资源,images, icons, styles等
+|-- components                       公用组件
+|-- config                           配置信息
+|-- constants                        常量信息,项目所有Enum, 全局常量等
+|-- directives                       自定义指令
+|-- filters                          过滤器,全局工具
+|-- datas                            模拟数据,临时存放
+|-- lib                              外部引用的插件存放及修改文件
+|-- mock                             模拟接口,临时存放
+|-- plugins                          插件,全局使用
+|-- router                           路由,统一管理
+|-- store                            vuex, 统一管理
+|-- themes                           自定义样式主题
+|-- views                            视图目录
+|   |-- role                                 role模块名
+|   |-- |-- role-list.vue                    role列表页面
+|   |-- |-- role-add.vue                     role新建页面
+|   |-- |-- role-update.vue                  role更新页面
+|   |-- |-- index.less                       role模块样式
+|   |-- |-- components                       role模块通用组件文件夹
+|   |-- employee                             employee模块
+
  1. api 目录 文件、变量命名要与后端保持一致。 此目录对应后端 API 接口,按照后端一个 controller 一个 api js 文件。若项目较大时,可以按照业务划分子目录,并与后端保持一致。 api 中的方法名字要与后端 api url 尽量保持语义高度一致性。 对于 api 中的每个方法要添加注释,注释与后端 swagger 文档保持一致。 正例: 后端 url: EmployeeController.java
/employee/add
+/employee/delete/{id}
+/employee/update
+

前端: employee.js

  // 添加员工
+  addEmployee: (data) => {
+    return postAxios('/employee/add', data)
+  },
+  // 更新员工信息
+  updateEmployee: (data) => {
+    return postAxios('/employee/update', data)
+  },
+    // 删除员工
+  deleteEmployee: (employeeId) => {
+    return postAxios('/employee/delete/' + employeeId)
+   },
+
2) assets 目录

assets 为静态资源,里面存放 images, styles, icons 等静态资源,静态资源命名格式为 kebab-case

|assets
+|-- icons
+|-- images
+|   |-- background-color.png
+|   |-- upload-header.png
+|-- styles
+

3) components 目录

此目录应按照组件进行目录划分,目录命名为 KebabCase,组件命名规则也为 KebabCase

|components
+|-- error-log
+|   |-- index.vue
+|   |-- index.less
+|-- markdown-editor
+|   |-- index.vue
+|   |-- index.js
+|-- kebab-case
+
4) constants 目录
`,95),m={href:"https://www.npmjs.com/package/vue-enum",target:"_blank",rel:"noopener noreferrer"},b=i(`
|constants
+|-- index.js
+|-- role.js
+|-- employee.js
+

例子: employee.js

export const EMPLOYEE_STATUS = {
+  NORMAL: {
+    value: 1,
+    desc: '正常'
+  },
+  DISABLED: {
+    value: 1,
+    desc: '禁用'
+  },
+  DELETED: {
+    value: 2,
+    desc: '已删除'
+  }
+};
+export const EMPLOYEE_ACCOUNT_TYPE = {
+  QQ: {
+    value: 1,
+    desc: 'QQ登录'
+  },
+  WECHAT: {
+    value: 2,
+    desc: '微信登录'
+  },
+  DINGDING: {
+    value: 3,
+    desc: '钉钉登录'
+  },
+  USERNAME: {
+    value: 4,
+    desc: '用户名密码登录'
+  }
+};
+export default {
+  EMPLOYEE_STATUS,
+  EMPLOYEE_ACCOUNT_TYPE
+};
+

5) router 与 store 目录

这两个目录一定要将业务进行拆分,不能放到一个 js 文件里。 router 尽量按照 views 中的结构保持一致 store 按照业务进行拆分不同的 js 文件

6) views 目录

命名要与后端、router、api 等保持一致 components 中组件要使用 PascalCase 规则

|-- views                                    视图目录
+|   |-- role                                 role模块名
+|   |   |-- role-list.vue                    role列表页面
+|   |   |-- role-add.vue                     role新建页面
+|   |   |-- role-update.vue                  role更新页面
+|   |   |-- index.less                      role模块样式
+|   |   |-- components                      role模块通用组件文件夹
+|   |   |   |-- role-header.vue             role头部组件
+|   |   |   |-- role-modal.vue              role弹出框组件
+|   |-- employee                            employee模块
+|   |-- behavior-log                        行为日志log模块
+|   |-- code-generator                      代码生成器模块
+

2.2.4 注释说明 整理必须加注释的地方

公共组件使用说明 api 目录的接口 js 文件必须加注释 store 中的 state, mutation, action 等必须加注释 vue 文件中的 template 必须加注释,若文件较大添加 start end 注释 vue 文件的 methods,每个 method 必须添加注释 vue 文件的 data, 非常见单词要加注释

2.2.5 其他

1) 尽量不要手动操作 DOM

因使用 vue 框架,所以在项目开发中尽量使用 vue 的数据驱动更新 DOM,尽量(不到万不得已)不要手动操作 DOM,包括:增删改 dom 元素、以及更改样式、添加事件等。

2) 删除无用代码

因使用了 git/svn 等代码版本工具,对于无用代码必须及时删除,例如:一些调试的 console 语句、无用的弃用功能代码。

前后端分离必备的接口规范

没有任何接口约定规范情况下各自干各自的,导致我们在产品项目开发过程中,前后端的接口联调对接工作量占比在30%-50%左右,甚至会更高,往往前后端接口联调对接及系统间的联调对接都是整个产品项目研发的软肋。 本文的主要初衷就是规范约定先行,尽量避免沟通联调产生的不必要的问题,让大家身心愉快地专注于各自擅长的领域。

`,17),g={href:"https://zhuanlan.zhihu.com/p/334809573",target:"_blank",rel:"noopener noreferrer"},h={href:"https://zhuanlan.zhihu.com/p/508570164",target:"_blank",rel:"noopener noreferrer"};function x(k,f){const s=l("ExternalLinkIcon");return d(),c("div",null,[o,e("p",null,[n("vue 项目规范以 Vue 官方规范 ("),e("a",p,[n("https://cn.vuejs.org/v2/style-guide/"),a(s)]),n(") 中的 A 规范为基础,在其上面进行项目开发,故所有代码均遵守该规范。 "),u]),v,e("p",null,[n("此目录存放项目所有常量,如果常量在 vue 中使用,请使用 vue-enum 插件("),e("a",m,[n("https://www.npmjs.com/package/vue-enum"),a(s)]),n(") 目录结构:")]),b,e("p",null,[e("a",g,[n("一文搞懂什么是RESTful API"),a(s)]),e("a",h,[n("前后端接口规范 - RESTful 版"),a(s)])])])}const q=t(r,[["render",x],["__file","coding.html.vue"]]),y=JSON.parse('{"path":"/interview/coding.html","title":"项目的编码规范","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"前端代码规范","slug":"前端代码规范","link":"#前端代码规范","children":[]},{"level":2,"title":"一.编程规约","slug":"一-编程规约","link":"#一-编程规约","children":[{"level":3,"title":"(一) 命名规范","slug":"一-命名规范","link":"#一-命名规范","children":[]},{"level":3,"title":"(二) HTML 规范 (Vue Template 同样适用)","slug":"二-html-规范-vue-template-同样适用","link":"#二-html-规范-vue-template-同样适用","children":[]},{"level":3,"title":"(三) CSS 规范","slug":"三-css-规范","link":"#三-css-规范","children":[]},{"level":3,"title":"(四) LESS 规范","slug":"四-less-规范","link":"#四-less-规范","children":[]},{"level":3,"title":"(五) Javascript 规范","slug":"五-javascript-规范","link":"#五-javascript-规范","children":[]}]},{"level":2,"title":"二、Vue 项目规范","slug":"二、vue-项目规范","link":"#二、vue-项目规范","children":[{"level":3,"title":"(一) Vue 编码基础","slug":"一-vue-编码基础","link":"#一-vue-编码基础","children":[]},{"level":3,"title":"(二) Vue 项目目录规范","slug":"二-vue-项目目录规范","link":"#二-vue-项目目录规范","children":[]},{"level":3,"title":"前后端分离必备的接口规范","slug":"前后端分离必备的接口规范","link":"#前后端分离必备的接口规范","children":[]}]}],"filePathRelative":"interview/coding.md","git":{"createdTime":1715780535000,"updatedTime":1715780535000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":1}]},"readingTime":{"minutes":17.78,"words":5335}}');export{q as comp,y as data}; diff --git a/assets/codingStyle.html-BCFhKJEg.js b/assets/codingStyle.html-BCFhKJEg.js new file mode 100644 index 0000000..d3072cb --- /dev/null +++ b/assets/codingStyle.html-BCFhKJEg.js @@ -0,0 +1 @@ +import{_ as i,r as c,o as a,c as l,a as t,b as e,d as o,e as r}from"./app-B-BkP2m_.js";const s={},p=r('

前端代码风格上的工具

Vue3项目创建时可选用的代码格式化 Prettier

1.png

',3),d={href:"https://github.com/vuejs/create-vue",target:"_blank",rel:"noopener noreferrer"},g=r('

代码格式化的魅力2.png3.png4.png5.png6.png7.png

由于每次格式化的时候,我们都需要使用指令的话会比较麻烦。我们也可以在VScode上面安装prettier插件。

配置.prettierrc文件:

',3),h=t("li",null,"useTabs:使用tab缩进还是空格缩进,选择false时表示使用空格;",-1),u=t("li",null,"tabWidth:tab是空格的情况下,是几个空格,选择2个;",-1),_=t("li",null,"printWidth:当行字符的长度,推荐80;",-1),m=t("li",null,"singleQuote:使用单引号还是双引号,选择true,使用单引号;",-1),f=t("li",null,"trailingComma:在多行输入的尾逗号是否添加,设置为 none;",-1),b=t("li",null,"semi:语句末尾是否要加分号,默认值true,选择false表示不加;",-1),k={href:"https://link.juejin.cn?target=https%3A%2F%2Fprettier.io%2Fdocs%2Fen%2Foptions.html",target:"_blank",rel:"noopener noreferrer"},S=t("h2",{id:"项目中引入-eslint",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#项目中引入-eslint"},[t("span",null,"项目中引入 ESLint")])],-1),v=t("blockquote",null,[t("p",null,"这个时候通过 npm run dev 跑起来的开发环境就已经开启了 ESlint 的校验,错误的提示将会出现在终端中。预期是希望这些错误能够直接高亮在 VSCode 中,因此需要继续开启 VSCode 的插件。下面这个插件能根据你项目根目录下的ESLint配置文件进行高亮显示!!!")],-1),E=t("img",{src:"http://picture.gptkong.com/20240515/2113fcc028aa474f1e913bb4483466844d.png",alt:"8.png"},null,-1),L=t("strong",null,"文件的配置规则参考:",-1),x={href:"https://zh-hans.eslint.org/docs/latest/rules/",target:"_blank",rel:"noopener noreferrer"},V=t("img",{src:"http://picture.gptkong.com/20240515/2113e89a664bfb40b2aa33f9b9de04cacb.png",alt:"9.png"},null,-1),y=t("strong",null,"配置规则:",-1),N={href:"https://zh-hans.eslint.org/docs/latest/use/configure/rules",target:"_blank",rel:"noopener noreferrer"},C=t("img",{src:"http://picture.gptkong.com/20240515/2113d0c30bbb6f4ffeb32d7523fa4b9dcb.png",alt:"10.png"},null,-1),F=t("img",{src:"http://picture.gptkong.com/20240515/21144456ec25bc4750ae936aa1f68eabbd.png",alt:"11.png"},null,-1),T={href:"https://zh-hans.eslint.org/docs/latest/use/configure/",target:"_blank",rel:"noopener noreferrer"},q=t("img",{src:"http://picture.gptkong.com/20240515/211442bdac5d654595927e459ec2155dde.png",alt:"12.png"},null,-1);function z(J,P){const n=c("ExternalLinkIcon");return a(),l("div",null,[p,t("blockquote",null,[t("p",null,[e("npm create vue@latest这一指令将会安装并执行 "),t("a",d,[e("create-vue"),o(n)]),e(",它是 Vue 官方的项目脚手架工具。 这里引入了ESLint和Prettier代码格式化工具。 npm run format进行代码格式化。")])]),g,t("ul",null,[h,u,_,m,f,b,t("li",null,[e("更多的配置可以查看"),t("a",k,[e("官方文档"),o(n)])])]),S,v,t("p",null,[E,L,t("a",x,[e("规则参考 - ESLint - 插件化的 JavaScript 代码检查工具"),o(n)]),V,y,t("a",N,[e("配置规则 - ESLint - 插件化的 JavaScript 代码检查工具"),o(n)]),C,F,e(" ESLint的几个配置去向:"),t("a",T,[e("配置 ESLint - ESLint - 插件化的 JavaScript 代码检查工具"),o(n)]),q])])}const w=i(s,[["render",z],["__file","codingStyle.html.vue"]]),B=JSON.parse('{"path":"/interview/codingStyle.html","title":"前端代码风格上的工具","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"Vue3项目创建时可选用的代码格式化 Prettier","slug":"vue3项目创建时可选用的代码格式化-prettier","link":"#vue3项目创建时可选用的代码格式化-prettier","children":[]},{"level":2,"title":"项目中引入 ESLint","slug":"项目中引入-eslint","link":"#项目中引入-eslint","children":[]}],"filePathRelative":"interview/codingStyle.md","git":{"createdTime":1715780535000,"updatedTime":1715780535000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":1}]},"readingTime":{"minutes":1.62,"words":486}}');export{w as comp,B as data}; diff --git a/assets/group.html-vsCGLjwS.js b/assets/group.html-vsCGLjwS.js new file mode 100644 index 0000000..2418eec --- /dev/null +++ b/assets/group.html-vsCGLjwS.js @@ -0,0 +1 @@ +import{_ as a,o as t,c as d,a as A}from"./app-B-BkP2m_.js";const e={},g=A("h1",{id:"我的网页收藏",tabindex:"-1"},[A("a",{class:"header-anchor",href:"#我的网页收藏"},[A("span",null,"我的网页收藏")])],-1),E=A("body",null,[A("dl",null,[A("p"),A("dt",null,[A("h3",{add_date:"1702034765",last_modified:"0",personal_toolbar_folder:"true"},"收藏夹栏"),A("dl",null,[A("p"),A("dt",null,[A("h3",{add_date:"1713799731",last_modified:"1715521439"},"2024前端学习"),A("dl",null,[A("p"),A("dt",null,[A("a",{href:"https://eslint.nodejs.cn/",add_date:"1694617376",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACF0lEQVQ4jaWTTUhUYRSGn3PnNjr+TGQ/UJsIy01ScxsqkBmjhdAYLiJqE0EUbYKsSEgyYnIiAokgA/cuy42BykSGaC4Ka4x+qIiyVRH0n6PM3PudFjp61USis/re9zvn5fvecw78Z4gfNERHSjwzcQOlfJH8cXtCTt15HftVIGz/rTHZZpBjs7LyDVSBigLjlchnoLmArcKh3hler0jTFFJP0Y6Ala/ycoFNirQDLoBiTieiw5ULBBDTBoQU+nHV6cvUnog2RKprDm5x+jKxRkW3KpoGisR41+Z4kHAGdwkMoJLsHY1fSqW+b/A82kD2T+d1A03J5PK3e52hcwpXBep6MvF7coBbgXFn7QgQEaOHt+9z7oJ+AC2eZ2AO3MpH3a92gulCeJ4Nu46VddYdASIzrtqBor8UAwTBnuWV6tCP4HFL1StbpGVLhoiWWaW5VR2ovvGRrgi6MBkF4/pe8C77Nd9u3X65OWfQMwBqSUVLS+lHVWqAh776EdeVeDK5YsyIWQmggcDZgbHdkzMjk3AGeyysPSp0elJ8Pv04+qm19echY8SG8s4nXf2rXTt4GeQowv3eTLwO/yjX7xiqIi/PQIMKv8WSK9kv+eshe0IlHG5E9QIQBlzPSCT9NPaC+buQ2PagTVSbfP98LyKeohtnKb3Zl6k9WcBzdoFldopcfo1CCdPyOuXnKICoTlqeXPy3Xi0RfwDIF9FxRlRarwAAAABJRU5ErkJggg=="},"ESLint 中文网")]),A("dt",null,[A("a",{href:"https://zhuanlan.zhihu.com/p/466178241",add_date:"1713783113",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACcklEQVQ4jT3TTahVdRQF8N8+59yr5numZlmgQVQUUaOkkUKDoonRQIIgCMLyQYOa5CxMigZBDjIsIiyKjIqmFk0alDxq0CTpSUEiWYZUfqX3Xt85578bnFeDDXuyF2utvVbYn2PFS8aeFtbXI/oKFWqhQb0ylTQSav8Ib/vZC42pV8x5XidV9DNcI2MkhMxOBJCIaGVJ8+btdbsS9uZljTUqqVbvukd+cZLJFGNpLMT/jGikBqtQudzora1CKRPVw9vkwYfY+TFlAxrxyyW2rmf1eJDQhzhzlb+uSo35RqcgdPLRu0RTc/Qxevx6niMn2L+ds1OudOLODeL14/LFYzTrqKITZcrNm8WOW9j1AUtnefUbHjzEyXP8ORXbj7DjXb48JTeOhVZET5UdCssz7jvAsUVKEWcuMPmDi5PBwOmM9gpXe5kpdWQnGzkATGbs3sEN93PbJrmwTdy7SV7AtGWULKesioiCjuhFE5XMZbFlHXdfT92wcQ2/XVDmGtEWSiF6tEKuPLRFJ6tMjFg6ze435MKHXJzx/nfiucPy6BJrR3TLw0FfBsY6sheVHFBHYzTsfYDzE3loF089IrqW1Q2lZUSsbVYYdERHI2SEaK9w041i306eeE+c+pvPFsSTH8lrV/H9s6Ivcut6XlscjM2WJggtW67j02f4/DiffEu28uBXnPidO17m8OPywNf8cIZZhTFdq4rYkxezmNu8gVs3icWfpEpEkP2wq1doVysRbhRjhFmVvbesVp09JxZ/lOoh+2nwRAymRUU16E9FpVbrvNm4ZJ9KrbGnasyV8l/vkEMjFZFl0KwWKudNvOO0ff8CDNsVBiQlEB0AAAAASUVORK5CYII="},"全网最热门的30个UI设计网站合集 - 知乎")]),A("dt",null,[A("a",{href:"https://typescript.p6p.net/",add_date:"1713946147",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACjElEQVQ4jXWSMUhrWRRF17vvitoIYgoRi1hISGOphRbTZIr4SK+FYCNJqyBYiCJi+YO1hYIomEoQCRgQRCKmsQkELMTCELVJCv0q+e/d/ZtvRoeZDae759x91tmepL+BvwAHGOccxhgAstksQRAwNjZGf38/vb29hGFIT08PHUn6of/R2tqaEomEjo+PdXNz859vLPATCCWFgAUIwxBjDAsLC5RKJe7v78nn80xMTBCPx8lms0gCwDrnfGOMlYQkK4muri4APM8DoFKpMDg4iDGGRCLBV1ljDJJwzmGtBaBer3N3d0ehUKDRaDAyMsLk5CS5XA4A51xnuP2EZq21lUqFs7MzXl5eeHp6otVq8fDwQK1WI51O0263Mcbw+SmA+dx5ZWUlXF5eJplMUq1WGR0dJZPJEIvF2NzcZHZ2Fmstxhg8z+uUNcb01Ot1BgYGOD8/x/d9jDE0Gg12dnZ4fX2lWCySSqXwfR9JHfsAnqQtSStfga2urhJFEW9vb8TjcWZmZuju7iaVSv3T+G8G7XY7NMbYcrnM9PQ0w8PDzM/P8/z8jCSCIOBryDpOJG055xSG4a/PcGxvbyuZTGpqakqPj4/fghNFkaIoknNOzjl1HPi+75fLZQqFAre3t+zv73N5eUmtVmNvb4+LiwvGx8dZX1/nT2bwPA8ThiEApVIpSqfTNJtNDg8PaTab7O7uUq1WyeVyxGIxTk9P8TwP51znjLy/v29J0sHBwa+jo6OO1Uwmo3w+r1arpSAINDQ0pKurKznn1G63O2vw5wqS5CTp4+NDzjnNzc2pWCxqaWlJgDY2NiTpW/M3Bs65yDlnP+O8uLhIX18f19fXnJycEAQBYRji+z6e53UY/AbxBdyhDICvlwAAAABJRU5ErkJggg=="},"TypeScript 阮一峰 | 阮一峰 TypeScript 教程")]),A("dt",null,[A("a",{href:"https://less.bootcss.com/#%E6%A6%82%E8%A7%88",add_date:"1713799742",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABO0lEQVQ4jdWSvUoDQRSFv9mZbFyDP2DiNgmoECWaKqaK2ARRggp2BvIACpZ2voEPIOgbWFrbCmltrCzUShBsBMnGzcyORXQwIEis9HTfPdwD9wf+vcTK+q4F8H0fT6rhA6y1FmBh4wg/GBs6QM3U9nlon2LiCC0VOo76hh8AoOMIazRCKjypSIx2rPwAJaQEIHnr0NUx91cnAMyuHgA4Bpir7XHXPnNcrB+irO4C0I1eeLw+JyzvAPB0c0F+uQVAWN4hlc4gPTXA6cwEyvT6ASbuuEa3IAGFasvV8pUm+UrTcaHaQvU+ZjbxKwDZYh2EQiqf1MgoWmtypQbWGJ5vLwkXtwaYsLxtPzU1v2a/arrUGODvfBEubVpMDwArPIRNSJIEz/N+ZGQKlZsMgGDo+7s/CLPjx7/u/hN6B0G3qQJQmNOyAAAAAElFTkSuQmCC"},"Less 快速入门 | Less.js 中文文档 - Less 中文网")]),A("dt",null,[A("a",{href:"https://www.sass.hk/",add_date:"1713803379",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACz0lEQVQ4jUWRTWtdZRSFn/2ec+4996tporb0lmirxtyEVhsMXKutgqgTKw4ExUnBmeCfyMCZEycFB4KYoYIoFCylIH4gEo1KqJYYS6O1NU3b2MQkvR/n3cvBTev6AXvt9Tz2ylMz01mmDxTjpHCEmRmSFMwMAASAC5lhMjMsJL/StzdCksRZxCEnyiUTbiALIeByokckYUEGmMstKgpxyJNiNnUvJiKKQEAQgqlzu0ev16dSLZOWEooi0tnokuclhSSAE/rqRiNMpO5yTAGBBWN7s8Powb124rVjPDi+n3qjQqfT48+LKzZ76oxu/bNBlibIFQBPJQ9IWDA62z0Ojjd5+9SbGhquGzB434yxiVFOf/SNXb+2RpomuIThlrpcAAlGt9vjpVePMTRct36vz3vvfMLc179w757dHGk/wtZmR2bg7kgOBFJJd5uQqNTKxOhYCOSVMmvXN1j+/W9+/G6R4ZGGpVmCuw/smJOM7z86IwkC9Lp9ynmJ488dIYTA40dbPPviNA88tI+1G+vcXF0nzZJB2Y7eZKzZnsFALkrljIuLV7ixeovm/fcxNFynsatK6/ABjj8/xcL8EitXb5JmKZIjIHm4+cQMEiAE9Lt9Fr5f4quzP7HwwxK1Rs6efSPUGhWyUsoXn89TzrO7M4IkuSQJFf1C7WcOq/XoAf5d3+LMp9/y/rufIYkYnbxSFmgnSEipcDMJC4Hb211efv1pptot/Ty3aP1ewdjkKOW8BMCXZ+dNCLnYuUBKxN1kSGBw6bcrTLVbNv3kJHdyeXmFjz88x7nTc6rVcopYAIaB7IXH3hKBaCLcobu3OcLuexqWJAnbWx3++mOV9bVN1RuVgQEDM1xYkiIuBMJEVBElBczs8vI1XVq6CgymlcoZ9V0V3N12aHsgTeR+IciLk+46jzCEJFeplKlaL1Ot5+TVEmYQC/8ft2NROi8vTv4HmQSdvH7nvQsAAAAASUVORK5CYII="},"Sass世界上最成熟、稳定和强大的CSS扩展语言 | Sass中文网")]),A("dt",null,[A("a",{href:"https://eslint.vuejs.org/",add_date:"1714902032",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACiElEQVQ4ja2Ru2tUURCHfzPn3Ht3k0gSXLMWgqsWFhY+EETCxiWrIGrjH+ALwd7SRfEqMcHaQGxE0UKwSxcCkQ0oxAeSRkQQTCBFNioRY5J795w7Y7EG10fpVw6/+WaYocqTuGth8YXzm4pkLzTUV9YJc8DcXD1BG6VSJYcSYOt59feLZFcaum3roYAqU7ceM/FBNerTqSVKpz9ltrcjcEky/Gbi0QMAOHD8zPkgl6v55TUXHdliomqfUkZWVF7TwNTQgCFTJyjJmsfavY9AAijr4vLnxm4A6C0U35PQVuSAjos7wB0WCtJMs4qZf/h0fsfZwc1s7CGx0oQhar5ddrYz3x0FUZrLdx42YXjKf1trhtU+4u15ZzgwKtnodPXqXQZA3xM3LFm2xI5suLdHTanT+tV1AaMGRs2vrospddpwb4+yIytZtvQ9ccMAiBGDXp2MF1VkxORDVguJBgoEEYJqANUAIhSVC6QWYvIhq8jIq5PxIuKWQKCgBdcY8003y2qs3dmV2T3dJKmopKJ2TzfZXV0Zq7G+6WYXXGMMCkIM4Y03fThxJwXkChkGvCAqF5QCJgqYonJB4QVkGIBcaWVbtAQERQyuH702Id6NmyC0XMz5cH8Pwv094GLOmyC04t14/ei1CcRgEPSXAACutwoJ0pqKriJVDvsLGvYXFKmyiq4mSGvt2d8FP7eYOTb0TlRGbT4yCChDQJnNR0ZURmeODb1rn/67AMDGQb8kK7el6eaZ2TCzkaab/5Ks3N44XHuLwZ8QeOnSs/Xt5wa/cmBPk2ESlcsvj9+cAYFR/zW9Ff8XccxAjEr/0CwA1J9f3QfEQBzLP/N/oS1xefJGtTx5o9pe++/8APiZMIA97lsPAAAAAElFTkSuQmCC"},"Introduction | eslint-plugin-vue")]),A("dt",null,[A("a",{href:"https://www.nextjs.cn/",add_date:"1715217902",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABkklEQVQ4jY2TvW7iUBCFP18jL1CCkBCIAongCB6BCGpTUPIOVJSReAoIdNtt4y2TCskVhWtSuqFBdpFmk115qRCabS4IG0h2pGnOPedo7vxAMr4BDvATiIC9zkhjjuZcjTLwBHwAciM/NKd8Tfz8iTCdz+cmlnb9X/Exn7QWB/h1fDBNU4rFomQymRM5l8tJu91OYFrjALjnztVqVTzPk8FgcMJarZYsl0splUrpKlwFPJw3w7IsbNtmMplQq9UAME2TfD6PUirduweV7qhhGERRxHa7ZTweYxjGrakBlC8sAfb7PdPpFNu26ff7HA6Hmw4KeEuDhmEQhiHz+ZzRaEQ2myUIgmvVvCnAT4sBlFJ4nsd6vWY4HDKbzdjtdmkDXwE/gPcjEscxq9XqRF4sFjQaDer1OnEcn4vftfZykZRSiXF1Oh1xXVcqlcrFIpnAAXgF7oB7ABFJ1BmGIYVCgV6vh+/7iMgL8Aj8MTXnL7DSl2YD2fRngyCg2+3+tizr+2azebzWfL4652az6YhI4pz/AasJuXfBmkIhAAAAAElFTkSuQmCC"},"Next.js - React 应用开发框架 | Next.js中文网")]),A("dt",null,[A("a",{href:"https://nextjs.org/",add_date:"1715222283",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABcUlEQVQ4jZWTsYrCYBCEv6zBA0FQELlSBBtJY5+UFnYWtpa2voDYiqUo+AIid60vYCU+gliJhcWJXIJNQM6w10TJ5eLhLUyz/87wMzsLP+sFaADvwAH4CnEIe41wJrFegTHgAfoAXjjzmkRe/EGMYxEVSYeqz5JvGIdcGsDn7SGXy6lt21osFu/DIqKWZWmhUIgKfIZc3qLKjuOo67o6m800m80qoOl0WqfTqdbr9fgv3gSwo2aYpsnpdKJardLtdhERVJX9fs/5fI57Z0vcURHheDzS7/dptVo0m02CIGC73eK67i/zzaR9igjr9ZrRaESv12O327HZbJIEEODjUTDm8znL5ZLBYIDv+3ieFx/5EGAV7QRBwOVyAeB6vTIcDvF9n06nQyqVigusiK8xn89rrVZT0zTvblcqFZ1MJuo4TuIanwqSZVnabrejWbgHiWeibBiGlstlLZVKahjG4tE9/HlMIuJlMpnEY7rVv8/5GxU00np4/hILAAAAAElFTkSuQmCC"},"Next.js by Vercel - The React Framework")]),A("dt",null,[A("a",{href:"https://www.programmercarl.com/",add_date:"1715327341",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABvklEQVQ4jYVSsW4aQRCd2b1DPmLrDBWmQSfHFEZUfACCwoqsywcgUTi9C39SytCfadIi0TlKZGQkghAJh1BOdgoKfOF2JsWcLucUyVQ7s+/tvPe0yMzMDC8LEYmIiLTW+SEAqDyOmRFR2Eopy7KIKH+bEuQk6Jvr6/PTU8/zOp3OcDjUWuc5KQERRRgift//+qbg9dnZp7s73/fH47FSioj+yGZmkWuMIaKbj7dvPrxn5sn9vda63+8z836/FwAz5zwgIuIru3hUONo9P583GrVabTabMbPWWvb/bRoA7MNDp1RyDg5Mkmy32+PSMQAQkaABwHohDuDn5y9fpw+DH9FtEERR5PtvJWKlVLpEPBhjkiRh5suLi2zb1bur3W5njBF7UiipCU0p9TCdRlFkae26brPZzPJRKs0TZST9PypTbomy+Xy+2Ww8zwvD0HGc1Wp1Uj15enzSWler1cVi0W63C4UCEVny/GAwEMJoNGo0GkEQdLvd5XLpum69Xg+CoNVqlctlAEBjDCJOJpMwDOM4tm07juMwDCuVSpIkjuNYlrVer3u9XrFYBIDUdBbz/z1kff4Lis4MlKWCiL8BcKwthz//Ld0AAAAASUVORK5CYII="},"代码随想录")]),A("dt",null,[A("a",{href:"https://www.hello-algo.com/",add_date:"1715348368",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACGklEQVQ4jdWSv2tTURTHv+fc+95L0/xoWkOgrba0YtoKQkEdBCkiWHBLtRmcHHToUnRR3J5/gE7FyUE3bSfF0Q6CIjpWKC0uhpqWKI0mtCZ57957HFqLg6MgfqazfPh+OXyB/x76dQwu3u/6jKYd3gUnu+FWy2H0R0NAuBsSAMbEhGgAKD5ZmGPhmaILtjmtc87FIYC3+wFyIIchF5/m78QT+rnvqam1UnlBY3ZWCfG4OFcFUcLGptJ2vHaQ9zuZTADgjG/tacANji09yGhcPO4R0SiY8wRKsKc2dqptu6cLgQgIQ8LUFI9srg8yu7SIjMFhlz09qcc4fYQIZ8VKRIxuFrdan59v7n2IZL+6AHD+o3snhIMVsHq/xn23calsaXR5saTbrWlV335nUulJm8vno0TXM9tpveEgyBlBU/vIsMGMjjp5vf11g6KoZZ19wUCK43T21I/CwMet89PL7aGj1TiTVkRSsuz1s/avaLiCcnyVjDMm25sUqD7ZjSpa+WUHfYzJyTjBHc5sbl0jE18ma2sQIc/z6jBRvxPXkNgdik38mDqdb8rECUkGO+LcSOx1XmsiqlVOXrgBAEMrr3KI4xZZp5RHc2K5x9Nd150x3X4y+VB876UCsqT1LRFZr3y4WSOI7I1paYmRSuneXgT1OjqFAaVrVWsKA0rXdCPu+eIlv58rNRCGangom/pUaTQRhu5vrvof8RNlH+ZM8zIHfAAAAABJRU5ErkJggg=="},"Hello 算法")]),A("dt",null,[A("a",{href:"https://www.langchain.com.cn/",add_date:"1715403749",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABSElEQVQ4jbXSTUsCQRjA8Wdnxt3N3dRMljWjLYrCXkD0IB3sXuc+QZ+j7xUFQYcIqlMgJYVgZqkZIqu5s7szOx2SotLCQ3N6DvNj/g+MpKgqjHPQWLcBABNC3qdEhBQ2ow4Nuj3+CyAfbn9nZbrAN/Ly9u51vcL+SDJMo2bOZtLkyX3c2kNodOkgyXfdYqlVVO3wYnMt40VN7eaMB8PSBoBzTh3KsJTOxVPJyGq2s5QLnR8I/iPtc2kAEBhNLoRQX9UFJBNgptDFyfdXvgDmBGFLce3mDO3OMZox3OeGdHuPRgLBwW4wfUGvdRCUvVgL0lpwWMQel4YDAPC6ARNEMfQ7gcsVulyFeA8u+3gkwAipqtJ+oNrUxOkxO3pF+axG1qPVkjMEyLI8b1nIlzUp5lRDXj+wX/yrjjAspV33fVcAgPTvn29s8Aa/cHz1kudeRwAAAABJRU5ErkJggg=="},"LangChain中文网:500页中文文档教程,助力大模型LLM应用开发从入门到精通")]),A("dt",null,[A("a",{href:"https://easyai.tech/",add_date:"1715403839",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAB+UlEQVQ4jY1Tv4saURD+Zt47fyRqkUDSCEeCoElz10iyKQ5BUFbYFQv/gFT5B1LkH0ibKm3+AgtlFQk2sTMkKUyzgStM8CRCCou9HKsx+16aXVjvIjgw8N7MNx/zZr4H/N/4wBhoDwHa7XbC87wMAORyud+dTufPQQSmaSaFEGUAkog0AAghSCm1TSaTn68T7RDYtn0fwBOl1LfBYDADEITdCN/3HzLzo/V6/XE0Gv2KakR0sCzrFhGdAfjQ7/eXAHT4bnJdV52fn6+KxeJPIcRZNpu9WC6X22gwBADMfKqUmjqOcxnrTodOAOA4zmUQBNN8Pn8aYRiANk0zycxHg8FgAYCq1erdWLEGoOv1+h0ANBwOL4go0W63EwC0BACl1O0gCDQA1Ww230gpX1iW9Vwp9QUAiKicTqfftVqtt91u91UQBNr3/QyAlbyxFiJNRCq6M7NmZqG1Jq21ijBRXoagK2ZmANzr9V4ahvF6MpmsYrzfDcN4H8Y4tKudVTabzWeNRuP42orjDgBoNBrHrVbL2BkiACwWi6mU8sS27WyY1LFiHeokK6U8mc/nXyPMjpBqtdq9VCr1FIDred6P8Xj8FwAqlYrMZDIPiOjxZrOZxIV0Q8qFQiFZKpXKzHyktVbhwIRSajubzT65rrtfynE79DPts4O/8z/KzsqN9Cor2wAAAABJRU5ErkJggg=="},"产品经理的人工智能学习库 - easyAI")]),A("dt",null,[A("a",{href:"https://mui.com/material-ui/",add_date:"1715520049",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAB2UlEQVQ4jYXTz4tPYRQG8M977x2ZQbIislGUvVhMTcqvjY0yKUnZoFgoG9JwZ4YUI9lQisWwMwsTK8ICWfAnsLAiqZkx5htm7j0Wc2f8mhnP7u2c8zznPOc9QHfkysjMh7/j3ZET6d/EMrI/AguRNkjKKNROqzxzIb1oCgvUylQ376NqE/rSHd2R2+gw3upLjzMt7ejV5qkyBpyJtco0pUy1M9GlNx5a5oZkN9horzWuCzuh0CGpfZJZqcNJ4/bpicuSDQrHtOG7Shhvuq6NeIO70wQtYbFFapPGvJTbarlrEsa8V/mgwxZJgI/uu5nuzXiQqdUo1Ebkdqhs1vLEuFsmbJL0yxtluJkmHY/Vylg13cFSSQj8QKE/vcb2WZt7Yikq0dD0RKclHmi5jIuZr0JoF1b8sboyMr1xSOaiXIGiiXVa1uQiM5Am1PZjENPul7FK7ZE2t+XWGTMsc0mZarkvKlMzIxXgfBrC0Kx6Zb1Ftqn8MOWUvnS16Wq7cASFpOMXQRmZUigbpxk15Zt2i0064Gx8kXRpc1CBUc9NGp7+iXMiMj22yJ3TblczLC3v0O+cQVIsQPAbemOPzAmVV8YNuJI+NyJphmRuzHdI3ZH/V3TOgr8vtcFPIFy3mkBsQH4AAAAASUVORK5CYII="},"Material UI: React components that implement Material Design")]),A("dt",null,[A("a",{href:"https://v2.vuepress.vuejs.org/zh/",add_date:"1715521439",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACfElEQVQ4jaWQP2idZRSHn3PeP8mtlyY0KSkOUbpVOzkUW5FcIYVYyKIIDgpunQsVQmnDZ6ZLnbWQSWg2UccW7JAgpRCo7RJxTIRIL5piSbg39/ve9z0ObSQt1sVnOJzhx/ODn3R+qMYfH9lrTjDFo89/pAdMHBuTX1a/3eMQb3Q+au88fmJTwIkvz/OIHsf67SCdW0s3xbkzeGv2V/+Q+qc/i2vHkJrU/fn2zW8A3pr79DMf/ELeq5v47qSOdo4bSYLlvC4zt794z6F3pBU0/1UzWPkN2ysY+fdmIG8ChJZtCO5VaSutT6Zx4xEbNCVTZt3Wyurmax/PHHcxnCmRRhTqX59k14rjorlosHdcCBfyoG5GOpPo60eyE+fyfv3V2oXqawUYmu/mYd3TIi6cHsNPt3zu10XELovY5dyvi59u+XB6DC3i8rDuDc13AZQKvTd/dduydcWpEsXiuQlQUzOiGRE1jecmIIqJU7Vs3XvzV7epUKWiALKby3Larx8ozvuT7eJPHTWri1ldzJ86av5kuyjOp/36wW4uy4BQURSACrk/X/VV7JqVUkAYOTtpMupERp2MnJ00EKyUomLX7s9XfSoEeHoAMATBZm4tfedH4weFnIZ3eg5gZHYqP2v/fu39xQ8PsgCeF0gSF7VO50V5Jbw9YQCSTEtJu0ni4ot5/ecTjAq9O7ewUXK6oTGoBCkSpGgMWnK6cXduYYMKPWh/XvB0iwIwiPF6Ggw3xTknzrk0GG4OYrx+OPPvgmdbrM9e2TFsSVRFVMWwpfXZKzvYoc1eKhAMQ9a2tldKkx6WJj1c29peOTzcfwsOuLjcYFzCuMTF5ealuf/L32G8J47iwhLRAAAAAElFTkSuQmCC"},"首页 | VuePress")])]),A("p")]),A("dt",null,[A("h3",{add_date:"1714641449",last_modified:"1714643654"},"工具"),A("dl",null,[A("p"),A("dt",null,[A("a",{href:"https://img.logosc.cn/vectorize",add_date:"1714641429",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACiUlEQVQ4jZWTT2gdZRTFz73fzDcz7yVtWsUYDFjSQEVsqAQKgkiKgm4jFIkatRXcpJBUSiohyjMWujJCcKO00JVBI66zEJVGhHYrbox/SqFgMIbm33vzvpm5x0VokJiCnt2Fe3/ncOAC/1EEZL9Z7nuxH2RgtI6ugw/h+sptwUIFALpnRQCgNrP9ZHapOQEAX+C0AwA+df44Dnd/D4l/wlDvIp8d794HsCN1csGpvn/o4trB01iwb4caETxmEcUnAETwHc+hlLE9AAogTC6v90PwvGTJgaIznhSAACJAjoClQVjCKoA4sG8CZ/F58dkDzINB3JlHp/7sOfVdI6+IaZgQIUlQhXVQPvsHYMfdNzafUJE3GdpGK03SrGe1Xn8VAKLrs5+j57cRe2HpVtgMT8vSRzf/lSDy7i3xaUKaAaIsAkUw1T+33AtQ2uNfPYzhm4/op3OdBIQNqN5zxwcbx1T0DRbBIBAREFZVmmRdtzf7RjkqNd7tGNd2lthqvSEA0QB3E9TVTYpPOmmlCeFEYwdQlBVD0Nd+H3n8rE/D0XLdqsjzmfBNPCgAFRCmjbxPxb3IEEzUqQi2acXP0FgRmoTisWsrr8wpc5RU0wwpGI3vdhB5extp0kUrgySJGvVKZTgjGkkFB+8Km//rJf6R99JL25XbYgIZbn/tB9TPbB0H5HW2A0VdwnaeF06utqZrPyC0FpnUNHM5l5tHZX71ZVEXtDCHKJEOqo6pd+4cIl+nVQGxB41fhneSHwFKaTKDUDQNETIXqg/vTNitZr+l2mI7hyn1bMSq+pjIPymqwDina24Vv+7UKmy9xxv+3bWTUS1NRUu703pQbmwM4oj7RSrG1Pj/vOJ99DfqiCfPFzwUEgAAAABJRU5ErkJggg=="},"在线位图转矢量图;JPG转矢量;PNG转SVG,EPS,AI矢量格式 - AI改图神器")]),A("dt",null,[A("a",{href:"https://mc.js.cool/#",add_date:"1714643654",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABt0lEQVQ4jUWRPW4UQRhE39fVCEQAHICMbHfWy0/ITTgAxyDlDFzAEgchQ8ieWVuIlBNAYGnsqS4CWyKtoN5TVX35/tH2fDFvbLymqx/rCFzkwptrLgxAhzMQ3bZtRFGYBN8Z0BMBpQLGGISWRuiX84wYexPqF7nxfL5I2n/e6alyyLDneQEmTZI6qxGSCF5hpd1uCG4AWusZVRSBG2j045w8Vr2dHP34sbL6zeFX2VefltH68/fvecR4WfHd8unESpeVTbVCYIVbHj9TCW5dVdqgISvbYDUrPXqdzXy9it2vUdP0Yeodj0sYx1d/QPNpsl3TQtyBCrVSpgeBTRWSgDY2iFyYagLqy8d3iWCXKCnbP3+egN1ukmjtqmp4BET2oJ5QRWtKlAzABkqSxLZRFaAK6uHAN1U5HASe57mKs8Ne0vEIVctyZqeKxHAF7gBgG3zflN6RqAEkAIkA6YFwuW2cn4dq+93U1H8/f0HB8qfFIwsEziRN0yRVrzKwbXWvmAcaNsmD930CguoJrel4nBJaux72328FYtqXWjgM+3S6BmCS1O9HkJRQ5cTjLjCSQBVK/jOh/gFMGgxFE588rAAAAABJRU5ErkJggg=="},"MC.JS")])]),A("p")]),A("dt",null,[A("h3",{add_date:"1714994683",last_modified:"1715217902"},"前端面试"),A("dl",null,[A("p"),A("dt",null,[A("a",{href:"https://vue3js.cn/interview/",add_date:"1709709831"},"web前端面试 - 面试官系列")])]),A("p")]),A("dt",null,[A("a",{href:"http://n.ddnddn.com/edgj",add_date:"1582167138"},"京东")]),A("dt",null,[A("a",{href:"http://n.ddnddn.com/edgh",add_date:"1582167138"},"天猫")]),A("dt",null,[A("a",{href:"http://n.ddnddn.com/eelg",add_date:"1582167138"},"淘宝")]),A("dt",null,[A("a",{href:"http://n.ddnddn.com/edgf",add_date:"1582167138"},"百度")]),A("dt",null,[A("a",{href:"http://r.ddnddn.com/egjf",add_date:"1576319723"},"网址导航")])]),A("p")]),A("dt",null,[A("h3",{add_date:"1618661901",last_modified:"0"},"Lenovo"),A("dl",null,[A("p"),A("dt",null,[A("a",{href:"http://www.lenovo.com/",add_date:"1581555207",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAA2klEQVQ4jb2Pz06DYBDEBwo3LvZmvNj4LrxFTz6HSZ+iPo83L0006UlMNKYCLagV5c9HPnY8YEigtJWLe9vd+WVmjPh6jiFjQethAPNsIJC1APt8Yth26T381cFx3dHJOJpd9aqZJDuRhCC7R1LWgTw/yVtsIesAAqA5UikJXsVfUan+SKDUDkw+JQzk4x3koQ6UihR9t2BRHClNpbjdoigg3KcGYDFNmedMv1GnJEzHOb25rd/60Ysupy1AAv+3KACgXN7LV9Ks1SbsOBgvF2f73HvHHKT+F+AH/1SNawmOMokAAAAASUVORK5CYII="},"Lenovo")]),A("dt",null,[A("a",{href:"https://portal.dxy.cn/",add_date:"1648969258",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAACQElEQVQ4jX2ST0jUcRDF3/z2Z4qrxmZGprQSRkq1kSZqwgr9IdOEsiIKS7JDp7BTUYdO0h/oGNWhIIgOHapLQRuImKyQYlikWJZlaeqmtraru+7ud16Hjexiw7vMwIcZ3jxpOfYASxQFAhAEBaICAcQmuSRAEla6M6XMu27Dxtyu9o/9vaP/AYSEpyLvaHN5RmYawQ7foFJt1aUWaElVwckzVdFIQqmWSGA8pErLKAkaqipVVdUoVdW4VmY0nq7o9Y/0942JyM/p+cBEiEpLqXv2e67ePtzQVGqn2qpIGBjFwaaSvu7RH5Oh0soCAK+7R1TVEJYxjEQXslxpO+uKzrXWiG2pckWOM3tVZo9/eG/DZkAScfU9GTAGxqilio4XQ9OBMCD5blfdoS2qptxb+Ph+76mWaoKfBidbzz+dCvxSVVU4POvrEzHT7f+cuTwtIyu12JPb7nvvsGRTSd7g24l7t/zPHr2bnYmQSEoaa+8ARPJFkOwcZyyu4WAkZZkjHksAAgixaL2tmmwIIN2Z0nC8dPxrMBxe2LbdPTsT7Wz7MPDm+79mW4aqBqrqsK1L1/d5dxTOzS90tg2tXpPl3V148UptdU2RIf7KooGhqmJXfXG+25VIaNfL4chc/PKF55PjIREcOVEKVRrSgEaTJ5GQsko3wLs3uoJTEYBj34Jnmx9uLVvrykkXsYwxgACwSZIUSGg2du2Sr8f/RWgRAmEsal51DotIMogiACAHqm8SsGARxJ8kUyCKJCWLQwGJ30jpVtRsvaJdAAAAAElFTkSuQmCC"},"丁香园_医疗领域的连接者_丁香园生物医药科技网")]),A("dt",null,[A("a",{href:"https://cg.163.com/#/mobile",add_date:"1650647490",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAADMUlEQVQ4jV3TS0xcZRwF8HO+7373zgyl47QDA6MM1JgpEQJKmxh5lRhjF9REE2Ory0bSncRV07hxVXWpG+OmCyXpxnRcdGNMKiqJK2OoidIH4SFQGKDTAe4M9/X9XVQT69mdxdmdH/E4BCAAMDhxbhRiLgIyJoJuACCxAvBnMLo2P3tz7r8b/lNQLJ7KtJW7PwXkEhWNWAsLERAgSEUNsTYC5Mvtu39d3tj4tQEAGgALA6+1FJ7J39COedfGCQWSiBU4hwlVIIBAhJJAoB3jvpTOHTmNo6WKv7UYKwDSkWu5qh3vbBwGgWiCodUUsN6XxfZIHs3OFBmLBoE4CgLtuGc7ci1XAQh7xyeHPXqzgGWiofShZZh3sfTesxABM8s+xFAKt6pIbQawLoWJWEJLIMGEY5SZImmsSKwblkLB4lsFeNWAPTMrYmohDjs87hc9MWsJXKsYuwQJY8RMORSMWVhRTavqfUex/nYXEgh7Pl4Qsxdxc7ITuyN5aU1lGD5siHP9DtofiEpSVigY48CZNwJGiWl2pnHvgzLaf6ji2C+7EEWuXehCozuD7soDlLa1jJ5/Bb8/HeK3y98g7yvEDiMlCtCBxe5IHi1LPkozKwgKKSx81AcI0H/lNtILdSzXd/D9hzOYTr2Inlf7cdg8hFKEUlSrUETiaXH2IgBAsyPFjpsb0vvJn3g0kMMfV8ridWa57j9CUN3DeNdJ8W0MrZ1VZWHnYBxmb9dsffAp1IZyaJ+tStutKu9Nl2XtnZIUvl0j7+zKC0N96C8/h835Jeu5LiF2jr0vTw57njfLMOb660W1M3acmY0AQZsLtxqw9PUyjtcpx7raMD58GqIoX12vWK0cSUI7QQAYPPPmZ9qY9+1BI/BPHDF+KUNvJ0B2YR9xFKN4ooiDfR9xYuXA9yPPTXs2iT+f/7Ey/fjK2dJP4jmndDp10lSb0nr/wHo7Ia2rSM9B7eGeRGFkwyhiOpUxSRJ9t1lrXPK3FmMNAFtbi5EOMzfSudYsDIck4xpxSIgAAjjGUdpxlKaKrbVfbN9dndq8P9f4l+QTnJ+fODdqxFwEOCZi/8cZ1+ZnK09w/hv52YeGXAjI+wAAAABJRU5ErkJggg=="},"网易云游戏平台")]),A("dt",null,[A("a",{href:"http://gede.5read.com/o/a.h?a=GEDE:gede.5read.com%2Fg%2F52447095.h&pageId=95763&wfwfid=119932&websiteId=68282&uid=203686657",add_date:"1652509071",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC60lEQVQ4jW2STWhcZRSGn/Pd796Z60yaSRg7TZr6QwliF6IgKIZSUlNLtYsuomg3hUJrIVYFXboVN+rCUsGK4N8iCqIbW4ux1UZR2koXqRgxUto0BJKmSZq5mXtn7vcdFxmqEc/ibA4v78v7HJkh3hIRnWgiuxX1ggBr69+jKJCqyB2IMSb0cnpZk8MyT9fJIuxZwbcUTFus6+UeU+kmHHiY9Ouv8OAr2LAlpVMyS8W5NWfzX1dEQALwKUrOxskJVl5/A1+vY+/Z6hvvHDPWoyr/E5kgQF0D1RSJK0TbBzHVLjo/+sCYZNUvDD8rzmVq1ydWEAMKzi0Q9vUTDe2ksGsn8d6nkFJRVcDdWqZ5dhxDhMxQcaAKCGJRTVEyyq+8SvjAg4gJcHNz0GpROnwAd2MBPz9PfmlCl158CfuPc4jXOqZWpfrpJ+RXrqO3VnDLNxEb4m8ukU9dYWnkKNmFi0Rb70dsRzuvWLzWCe7azMbzP9P87Xe0mSKRAA6AaOARVkc/I7vwE4GUyf+aRJspZo1cC+ksUT0zJq2Ll3DXpykMDUo69h3FXU/g6wmmr1fqb7+pgekGdYgUEQwWMajW6Xp3lKCnxtLBQ1o9c9osHnxeyyNHyMZ/oTi4g+zbs6ri2rT97dqM12WigUHi/c+Y5K1jvvD4EBIEmk/8QTTwGG76KlLtFhMX27DWEzfgtfTCEQQ0/XFcCk/uNvnM7NrVWmM6K7TO/6rxc8NiwrKqW4XA3v5wYyu9Gu3YLi5J0OkFNZt7UWtFl1bQeuLj/U+TnHgfKXfQ9fGHoqTq3I327ygmemibkZ5NhlJJaDaRKNSwdqeYbffSGP2c8L5+U375KIsHDmlx316pXb4s8b5h2BCrQSR4bdPdjxbnF/uT78/l2bkfxM/OiZv8U/3UVW188SWatSB3ZCfHdPX4e2pqNWxPn3Y0MuuuXftGpmBLGY7nsAc6RKm34xVEsChJu7oNAk31pBjQAvGpJoz8DfGUUDVjrMaYAAAAAElFTkSuQmCC"},"在线阅读")]),A("dt",null,[A("a",{href:"https://matlabacademy.mathworks.com/?s_tid=getstart_mlacad",add_date:"1652694084",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACS0lEQVQ4jZVS30sUURg99975tTOzk7uu626r67ZqOpTZQxFFCPkDQzMCMciKHiSqF4PI/oSkeqnAHnwqISMhLAl7MCkwDMuKCo20Wio1Yq0MDXednbk9GVu0oOfx+845nO/wASvEweqG5t41cuG/c7oidcuVpl3rvl7f0pjdCYCtzmCIe4o+jpwtj3yGf5tWOeAr2L06g0c3L5XG3xV55RSkEIWRR4+kr4VMOveZcyWO29NmxF4fNqdfQqU+gDqQDVT1rM8L7Z+Yms6YgHUOHlUU9XH+p4mW0uF+hFw2hKQIJCh8HurNUWj1fxM09XD2ZbS9OXnjQoc+Nyv+YjLy4x/gDWhgFgFPEOhZFJJA9gG4BgCUc07qbj0vqLgzfnKGvRrSNmztWpsbEK35BWg/4gjTBAxDAnMIuEVAdECUeOW9zZEIANCavrenk4r2hqj6RcrYdsYd0Kp6pMxNiH6PwasrMDQBBICdApibwKcKhos5DQBAbaAvYaV67cUFyKkEmCRhemISwdFBhFWKLF2BR2egFBAJgZbN4M9ioAwHAIAs31/RPXzMRa3j8feTcuhqu1k+F0NujgeFARfMqIyIKWNJdaacRadj5kEqdza+dCJpo5akl1h0uV/+1lqnnAoIXfl+z96gX0FBUERxVITtJjEusT1y69g4ADzbET605GDjXwbLGKjOKwsXyk+CfkHRVQrLwU9bIrVq2/hIOu/pznA00x/BuWve5qNl3Oo2F+fPlzZmJGYCf1Fcz8dKHibumzWrFv8xSSs5E34Dce+z66FJdCUAAAAASUVORK5CYII="},"Self-Paced Online Courses - MATLAB & Simulink")]),A("dt",null,[A("a",{href:"https://www.imooc.com/",add_date:"1657378337",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACIElEQVQ4jX2TP2hTURTGv3PvffljE5omDWoFI2J1EBWNbra+aAOpFB1KROlSBB2cRBdxsTrp4CoICk6CZBDEqQ5V0UkXRSoShApiaiPakDTJ+3ePy0tN22e+7V6+87vnu+deIEAMEAAsjU6ef77v6ECQpyMRuGuaEgCUgTMHU8lbPjTQGwxIp/k9skbFaib+uPbU493ZQQI0/M56AhgQVCp5g8fSE1FJh6RAXzGT2dMdrSeAAP17bGK7NMT9FhyDhG7+dOorQcUbAAwIBsQy23ctdtNSgi32Kg8WyosEsB8jGMCAIEBXCvnTcUNOWuxZhgCFFcIzCwvVb7nciUahsL/j3QAgQLNpKghct8khJbV0hK1DindWTuZnVUQ+cSSu/DtvLYAA4Gmztjcs6XBTex9I8HJISHjQnoY+3mYv1YJ77nU2u5XWA2Z8wHAiNiql5h1xdTUZlY2wYkFCOxZckNAuCy80tLnvlF8jVgE3fFpMIdbSzqc3v5xXbc++ORA1dKZ/UySqaJEIbkSRToZksrtG+f0zANTd9ttUPDGSe/bCBfCoOZUvKwNDSZJfGraeE9CRr7X6x+57WDNbvmTGqjV153O1dnt09t337qwrZ3NlT3MizkvbqDRvB42R6N7LBrWshwf6o8MEMGdhsGmqOUCFw3KXoeQ1Ks3b/3tUq2pMH9nCRciO8cf02AXrYr7npwoUA8TFkTRfHh/vrNd7/gLmRdGEfuN8KwAAAABJRU5ErkJggg=="},"慕课网-程序员的梦工厂")]),A("dt",null,[A("a",{href:"https://dxs.moe.gov.cn/zx/a/hd_sxjm_sxjmlw_2020qgdxssxjmjslwzs/210618/1699930.shtml",add_date:"1657595539",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAChklEQVQ4jY1T3UtTcRh+fr/fzj7aV8ttZ86lZjidYTaKPjAmZoUUGSIKBkYXRVe7sBvpovB/CIKFNyFBSUIQJBTURSFCUl2FBSVlpLVqmzs7+zg75+0mxclI36uXl/fhed6H5wW2UQQwAgQBbPN8O/iqNQ7waky8GkvxWLg13eYbSh5vDW/cyY70d20l2wQAatjzgOpNlD645zIAYJ4k5UR0JN8RmllnS/Z1Oi0Q3ZaC+tly/fUCuqEDjArdh1q0D+/mhBASP9w1rEosbf76bYxSP07DU9PLCGAMoJWRfr9t9vknlDXD5Pa80hhfZjbbEv/5fdCkKPvydscykyxZW7EQFoIrJX/tkH3u44xpTYFdllEulbhZzTtEQT1jYYDBOVSdkGfMMKm5gJA0u+FyP1QiB254p58uEMBNDCAAyDZHlZ2ex090kenM62UOIg2G7gczuIUMLpzuBfvJ85dY9O5bXGUaAYIB+mbrWOMiWXMXB6JqR939VJ1USvpRTsugtMzK6ZAtWWitmU6f7WlaMxoVzTxJWrThWr5+x5Iig4oB0GrIbKQaHV9SDfaVXICRLoOUZvf71eFz3grwn7Exd6E9+KgUFJT3gzIhc1lp8U5osfbYXDzuWhod3ZWLeG9nAlzTaxllO4I314Uvvli0Km3ys6IflJNBhb2uN1osEgOoIlSZgVPNmd3WrCqjmGtyvhycIoFEIiGp7XWTFGCUDwoqRXwTv+Nx14Zk8n+hYkrvkf1qvZUMPygVsqhKX49sujB154qe+nW0ZLbOMl/tPXPTpwRuMX2qistFrVSGEJMFm41Zna6c1WI2QG0+RwKQxseJb4jwFl9GbPN5a4aK/wMrzuIA8BfvzxaDZm2r8gAAAABJRU5ErkJggg=="},"2020全国大学生数学建模竞赛论文展示(C170) - 2020全国大学生数学建模竞赛论文展示 - 中国大学生在线")]),A("dt",null,[A("a",{href:"https://dxs.moe.gov.cn/zx/hd/sxjm/sxjmlw/2021qgdxssxjmjslwzs/2021gjsbqgdxssxjmjslwzs.shtml",add_date:"1657595309"},"2021高教社杯全国大学生数学建模竞赛论文展示 — 中国大学生在线")]),A("dt",null,[A("a",{href:"https://www.fotor.com/photo-editor-app/editor/basic",add_date:"1663836172",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACyElEQVQ4jSXQy4pcVRgF4LX+vc+pi5W+2CZtLnQSQVQ0gs6cOHbs0ImvYGb6AgEjBBwJPoPgTHSgMxEEEZvQ2NqIxqS6K3Yq1V116tz2/peDDL4X+AgAB4ev3Q6hux38yuUXN95RUkMiAuoQ598BagEPYEnV/9YMqzTrwc8vvv/4s3j/91c+Gg5xr14XaH3qVX/CUbyE7IkQ4BLoACOUqoR0lpXNrk5Gujv/+hJtaPikXkvJkUlysT6AINBMz1AwSgS6WQ9QlJCrNaToH9vRmT1fEBoEGFCiy3Os2n9oNgDggAAEol8k+jqDJBhlKKR2Wl6w76dB3z4yzltiUkiRJc+aQ6XcEooURWUxnSYpgjaUfBVQ/3yBzS8biuMCfLgip2vzV7ecb+8EjcOa580hNgdvAAak0wQg0RRU74+YHgzkScaBGElpXEgAeLAA/l4639op8fr2XyiGVwEa8rJF93CE9nBENSaWTosSQMU6R0oijQpBWGfihymxv2jw7s37eHM/4vzHbeTlECwyzAR1ES6RJLn75Yc9wQiDIIIUY9Gjqjcw/HUX9746xp5VYJHgICAKEmQgpBRbFIIoZMHKBPUR6Y+b6I+v4aWDJY7K/1A02xjlBhtsYXJkAiBEUZEOhqInSE/HF9kf3VCXxry8WGKrWaEqM+YpYysPsELUpjXYYKdMmURGDjr1TzfV/nmdvtgSY7LSGl05qdmbITowKzMmtYkCTzX2JQtuW6MxsmK1/7Kl2QsijKFo2RdBew8aFq2Uo4EAEoFZmXmtNbllNjBMNdZzSLT2eHeuKNI6TzRMqozd00YpEnBBAKKAeZRWwUEBgHsQUClWZoP+U4SCjmBOae9RDWZAEgmnKDpFEHhc+rM80EpEi+DdoN+++Snceq/OZXlr52k7un7SeooURA+Q36jPPUhOmJoAHzgwkT3poDsfPPnizv8L4bTNFHXMrQAAAABJRU5ErkJggg=="},"Fotor Editor |Fotor - 照片在线编辑工具")]),A("dt",null,[A("a",{href:"https://learn.microsoft.com/zh-cn/sql/ssms/download-sql-server-management-studio-ssms?view=sql-server-ver16",add_date:"1664954767",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAQklEQVQ4jWP87if3nwEH4Nz0iLF+FwNO+UY3BkYmXJLEglEDqGAAxYCRYcl7nPHMECPI+H8n7nTA6D6aDgaHARQDAKgRDRsLiHU6AAAAAElFTkSuQmCC"},"下载 SQL Server Management Studio (SSMS) - SQL Server Management Studio (SSMS) | Microsoft Learn")]),A("dt",null,[A("a",{href:"https://bbs.huaweicloud.com/blogs/348672",add_date:"1667054363",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACxklEQVQ4jV2SwWvcZRCGn5lv89tkm6ZNMdKakjUhRYoNiDFHMUQPgqCQUkuFWiyevEvBUxXUQ9WD+BfosaC34qFKpaBIqFj0IJF0EyEJSdbN7ia72ez+vu/10ESqAwMzvC/zzuEx/lfrDI7wxNANf/r4rJ8YmO+bm8P7+7/2vuIddVrvHr368dajfj8cBAWARPEiebpMFsoM9s+E4DPeP1i2rHiZmF4H0Oxs4T8HKoycXGX4/TWGxwxNqCenG1GezuX17alUrxJrW552mxNrF6bHNrLND7amy6fgIDUQ3wB7T+iYYYu0orS1Z340O621VYulbTATeW8l9HrXVOu801vcqwKfFQS+THwOnIBdMfQJ3bSUltuTGMMyMxsogvtSqu8Oa6P1ph60YCdOC7xwE+xZ7J7BRYfBBPOGLdDoTaY/dgLN3DgSILCg6v551eMgu3kE7gEWrtL3BVApUdg0eMqx04JFR5lyatS7SX/3cm10qzTyF9hPLeArwYNdSm/bTfz+CNnkCfpuH6GwnsHzhg0Z3DYUwE0oN3gJaAJ3hY0KvSi0GF5BWURzGT7l2JPCfglQAdaAW0K/G/4Y0AA1hL0MeibBvtCn4TW430N/gRULsFfAhkCbCb4pU/9uiM7SDqUU0TmgDFQj/JbQDXH8S38LOsBPVfJmhf20Skd1UrsNFYPcII/klS6p3SbRJKpBr1En/TjOcsd5+NuKkb7vkpo18lu7xA+naCwd0jZGYwnCRx3it23iTof0Q0Zz5VC3A5TtOmQ62B/R/sX9OvjnULwAgYdtNnHq8fMe+VVBZwyTJfsz9zQaUCvh4yRrBNiRpXI01j1p1LDxhC/gOlZwmMc1g6yWpH1Z3HbsrLARS5TNdTdJrwJDJlVw1gUnydMKpksutCzjjinVzKwvyC8p2QBQMmNdpAFBEPaz4XuWwgZQctdZA/0DZelix8BzL+IAAAAASUVORK5CYII="},"ENSP安装教程【手把手教学】-云社区-华为云")]),A("dt",null,[A("a",{href:"https://support.huawei.com/enterprise/zh/doc/EDOC1100082528/725fb7d0",add_date:"1667113528",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACxklEQVQ4jV2SwWvcZRCGn5lv89tkm6ZNMdKakjUhRYoNiDFHMUQPgqCQUkuFWiyevEvBUxXUQ9WD+BfosaC34qFKpaBIqFj0IJF0EyEJSdbN7ia72ez+vu/10ESqAwMzvC/zzuEx/lfrDI7wxNANf/r4rJ8YmO+bm8P7+7/2vuIddVrvHr368dajfj8cBAWARPEiebpMFsoM9s+E4DPeP1i2rHiZmF4H0Oxs4T8HKoycXGX4/TWGxwxNqCenG1GezuX17alUrxJrW552mxNrF6bHNrLND7amy6fgIDUQ3wB7T+iYYYu0orS1Z340O621VYulbTATeW8l9HrXVOu801vcqwKfFQS+THwOnIBdMfQJ3bSUltuTGMMyMxsogvtSqu8Oa6P1ph60YCdOC7xwE+xZ7J7BRYfBBPOGLdDoTaY/dgLN3DgSILCg6v551eMgu3kE7gEWrtL3BVApUdg0eMqx04JFR5lyatS7SX/3cm10qzTyF9hPLeArwYNdSm/bTfz+CNnkCfpuH6GwnsHzhg0Z3DYUwE0oN3gJaAJ3hY0KvSi0GF5BWURzGT7l2JPCfglQAdaAW0K/G/4Y0AA1hL0MeibBvtCn4TW430N/gRULsFfAhkCbCb4pU/9uiM7SDqUU0TmgDFQj/JbQDXH8S38LOsBPVfJmhf20Skd1UrsNFYPcII/klS6p3SbRJKpBr1En/TjOcsd5+NuKkb7vkpo18lu7xA+naCwd0jZGYwnCRx3it23iTof0Q0Zz5VC3A5TtOmQ62B/R/sX9OvjnULwAgYdtNnHq8fMe+VVBZwyTJfsz9zQaUCvh4yRrBNiRpXI01j1p1LDxhC/gOlZwmMc1g6yWpH1Z3HbsrLARS5TNdTdJrwJDJlVw1gUnydMKpksutCzjjinVzKwvyC8p2QBQMmNdpAFBEPaz4XuWwgZQctdZA/0DZelix8BzL+IAAAAASUVORK5CYII="},"基础配置命令 - FIT AP V200R019C00 命令参考 - 华为")]),A("dt",null,[A("a",{href:"https://baijiahao.baidu.com/s?id=1736587573028546971&wfr=spider&for=pc",add_date:"1667135284",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAADCklEQVQ4jXWTTWhUdxTFz/3/33w2CdNMQkMaqfmAGqziIiRtESutkqKgmEUJootSKF0I4kK3ChoUa6ltLWiRbrqsUlsrWlMVP6K2BCtBtDZ+JdFM6ryYmHHezHsv754uEo2bXjhwuPdyFgd+INlNMkcympFyRqTqnOfsZu6POZLdIOnOniKSOjEZ6ZZted25x6WqMjcW8tNNYzxwcEJnMl+EkKSLWRP5gep0RN23/4lW1d1hdt4d9l3x+MXX40xnB1nffI/nL3o6Pa0ahnMhBoCSkHhMxBrI4N1AMhnLRELwKBfCdSNkaywI4fBIKNaKOA6EhABQo6pGhPLTLwUc/fkZutZWUEQlU2XR0ZZCR3sKnqdoabayZHGCh76fRP81HyIUVTUgqRf7PK1ruqfVDXf15OkCb94q88FQ8KK6a9dLfJwPuXOPSyczqG8vH9G8GypJNQDk+kBZrIFUVYqcu1BC64IEnhUVm7fmsb3HRUtTDLU1FuculNDc6EhuLJTb/wQCQBySaG9LIpEAxp8o3luawtBwgM41oyiVCWuBP/t9HP+xHu1tSXz5zQSWvpNC64I4lAqJoojGCK78UYYfEIsWxrG6axRNjTEYI+i7WkLgEx91VaBnRxanz5SwZHEc89+IQRUQVaUqYa3B8EiI9R+PYWg4xA+HX8NfAz6+/W4Kla8YeCXFyg/S2L+3Fo4VRJHCGIEhhdYa/n074IrVo1z1YRqXfm/AorcSKEwRh76qRUWFYN/uGjwYCrFq3SgLBaUxhoDQqKoAlAMHJyWdhmz6LMNlnQ9x7HgRmVcNkklB3lWEIfB6XYyXLnvSe7YoIpQoUjHGGAUArww+nSLv3w9l765arnw/Bb9MKIHPe7J4tyNJpYq1loUCCQggojIdkdZAfz1ZlI2f/IuGBgdda9JiLXCq10MsZtC5IgXPI44cK9IY4MTRejTOj5GEEZIugCyg6D1b5onfiggDoKXZkdY34/B9YuCGj4ePIlZXW2zorsTCVkcAAwDjeI7zDI0vo/t/o8/JzZHs/g8akjdG32H2lQAAAABJRU5ErkJggg=="},"网盾带你读好书之——“圣经”《TCP/IP详解卷一》")]),A("dt",null,[A("a",{href:"https://jwxt.hnucm.edu.cn/jsxsd/framework/xsMain.htmlx",add_date:"1672745246"},"湖南中医药大学强智教务管理系统")]),A("dt",null,[A("a",{href:"https://www.spsspro.com/",add_date:"1681831708",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABsUlEQVQ4jX2STWsTYRSFn/vO5LNJg7SJsRW1Cu2oKFVQXHSjhW5cibh35S9w4dJ/oNBf4Lq4c9GVXYhQEOxCaRXUflAxalNj004zyeS+LmZIOqF4l/few7nn3CNjTyxHylpE+E/HHZgZAxB2AURwDZrEmMS20GrjB+RSDGVwDY1D9FiGaNtvM1Xh8Ry3Jihm2P7D4irPXhN2Y+Y+gwgdZaTAi4fcv0YpS/2AS2PMerTD4xgM+G3uXmbyJLW/3H7OryYXq4jQVVIOPWf6GgwEIUAxy71pTuR5t8GH7+TS6BEjneLMU8CC67BR5+o4XpVZjwfXOTPC5580/JhBEgCLEUJlcZWGT7nA+TI3z3FnklcfOQhwJEbEgEiWY9gPePOFhRXWatw4y8Qom3XefmMoE8swPVvVIjB9mkqRVsjLFTZ3UWU4R7d3UN8loRkw57HwiK+/2drlVAmvCrC8Tj6F6kA0BCN0lPdbXBnnQhlVPtWYX2J5PTYqOlui8EWiDzukXSoFSnmaLXb22WtRyCSy5PY+rZZcCrX82GO7gRFch+Fs4gkMphVESDvggsWC2sF4/wP70qUQoyMGOgAAAABJRU5ErkJggg=="},"SPSSPRO-免费专业的在线数据分析平台")]),A("dt",null,[A("a",{href:"http://support.lenovo.com/",add_date:"1581555207"},"Lenovo Support")])]),A("p")]),A("dt",null,[A("h3",{add_date:"1667734777",last_modified:"1667749262"},"ensp"),A("dl",null,[A("p"),A("dt",null,[A("a",{href:"https://support.huawei.com/enterprise/zh/doc/EDOC1000069579/e3dbf6c3",add_date:"1667734751",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACxklEQVQ4jV2SwWvcZRCGn5lv89tkm6ZNMdKakjUhRYoNiDFHMUQPgqCQUkuFWiyevEvBUxXUQ9WD+BfosaC34qFKpaBIqFj0IJF0EyEJSdbN7ia72ez+vu/10ESqAwMzvC/zzuEx/lfrDI7wxNANf/r4rJ8YmO+bm8P7+7/2vuIddVrvHr368dajfj8cBAWARPEiebpMFsoM9s+E4DPeP1i2rHiZmF4H0Oxs4T8HKoycXGX4/TWGxwxNqCenG1GezuX17alUrxJrW552mxNrF6bHNrLND7amy6fgIDUQ3wB7T+iYYYu0orS1Z340O621VYulbTATeW8l9HrXVOu801vcqwKfFQS+THwOnIBdMfQJ3bSUltuTGMMyMxsogvtSqu8Oa6P1ph60YCdOC7xwE+xZ7J7BRYfBBPOGLdDoTaY/dgLN3DgSILCg6v551eMgu3kE7gEWrtL3BVApUdg0eMqx04JFR5lyatS7SX/3cm10qzTyF9hPLeArwYNdSm/bTfz+CNnkCfpuH6GwnsHzhg0Z3DYUwE0oN3gJaAJ3hY0KvSi0GF5BWURzGT7l2JPCfglQAdaAW0K/G/4Y0AA1hL0MeibBvtCn4TW430N/gRULsFfAhkCbCb4pU/9uiM7SDqUU0TmgDFQj/JbQDXH8S38LOsBPVfJmhf20Skd1UrsNFYPcII/klS6p3SbRJKpBr1En/TjOcsd5+NuKkb7vkpo18lu7xA+naCwd0jZGYwnCRx3it23iTof0Q0Zz5VC3A5TtOmQ62B/R/sX9OvjnULwAgYdtNnHq8fMe+VVBZwyTJfsz9zQaUCvh4yRrBNiRpXI01j1p1LDxhC/gOlZwmMc1g6yWpH1Z3HbsrLARS5TNdTdJrwJDJlVw1gUnydMKpksutCzjjinVzKwvyC8p2QBQMmNdpAFBEPaz4XuWwgZQctdZA/0DZelix8BzL+IAAAAASUVORK5CYII="},"二层交换机与路由器对接上网配置示例 - Sx300系列交换机 典型配置案例 - 华为")]),A("dt",null,[A("a",{href:"https://support.huawei.com/enterprise/zh/doc/EDOC1000069579/426cffd9",add_date:"1667745754",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACxklEQVQ4jV2SwWvcZRCGn5lv89tkm6ZNMdKakjUhRYoNiDFHMUQPgqCQUkuFWiyevEvBUxXUQ9WD+BfosaC34qFKpaBIqFj0IJF0EyEJSdbN7ia72ez+vu/10ESqAwMzvC/zzuEx/lfrDI7wxNANf/r4rJ8YmO+bm8P7+7/2vuIddVrvHr368dajfj8cBAWARPEiebpMFsoM9s+E4DPeP1i2rHiZmF4H0Oxs4T8HKoycXGX4/TWGxwxNqCenG1GezuX17alUrxJrW552mxNrF6bHNrLND7amy6fgIDUQ3wB7T+iYYYu0orS1Z340O621VYulbTATeW8l9HrXVOu801vcqwKfFQS+THwOnIBdMfQJ3bSUltuTGMMyMxsogvtSqu8Oa6P1ph60YCdOC7xwE+xZ7J7BRYfBBPOGLdDoTaY/dgLN3DgSILCg6v551eMgu3kE7gEWrtL3BVApUdg0eMqx04JFR5lyatS7SX/3cm10qzTyF9hPLeArwYNdSm/bTfz+CNnkCfpuH6GwnsHzhg0Z3DYUwE0oN3gJaAJ3hY0KvSi0GF5BWURzGT7l2JPCfglQAdaAW0K/G/4Y0AA1hL0MeibBvtCn4TW430N/gRULsFfAhkCbCb4pU/9uiM7SDqUU0TmgDFQj/JbQDXH8S38LOsBPVfJmhf20Skd1UrsNFYPcII/klS6p3SbRJKpBr1En/TjOcsd5+NuKkb7vkpo18lu7xA+naCwd0jZGYwnCRx3it23iTof0Q0Zz5VC3A5TtOmQ62B/R/sX9OvjnULwAgYdtNnHq8fMe+VVBZwyTJfsz9zQaUCvh4yRrBNiRpXI01j1p1LDxhC/gOlZwmMc1g6yWpH1Z3HbsrLARS5TNdTdJrwJDJlVw1gUnydMKpksutCzjjinVzKwvyC8p2QBQMmNdpAFBEPaz4XuWwgZQctdZA/0DZelix8BzL+IAAAAASUVORK5CYII="},"前言 - Sx300系列交换机 典型配置案例 - 华为")]),A("dt",null,[A("a",{href:"https://blog.csdn.net/m0_57515995/article/details/124215939",add_date:"1667749262",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAB/klEQVQ4jU2SPWtUYRCFz5n3bvbubhISoyJiJxEsEkglghZJZUTUJkhAUoja2WihgohFQPAXaGVhk8rGRkQrURDEFOL3B2idkDXJ7t3Nve8ci7ubZOozZ2aeMyzmptAvSSBpBgkxgkQIkOQOkgCAZLeaJEi1NkBDWgeEzXUkFVZTuIPcaSjVkqPb5bEZTp/H4aPMc//0Hs+X9Psb0hrlILm9kuRw5+XbNjsPuX//yNEx239Inbbfu6IvH5jWISU9fzNlmV26FWbn/d0rf7SI5oosaPosF25gZC9iLH1ZzE0JRLeN8YnwYEl/fsSb8+xkTOvyiK0O9h3ERhMSAZAGgKTy3E6eppmePcHmOhtDgmjG2iDXVtjnAcAAyCNrDYxPSNLXZVZTxYIlZTmSBKSkciUrb0BSwfAe5DnW11DmUE7u6bhN3/opRHTbSALSGgSaARDAEOCuYmu72QAwJGq39OszLXDqhP6tKjrc6VEbTdUaHDugIufODRAt6OVTxcIWrtuZi6imCokGUkwet7sPbfExBqpy72Etc0C7xVMX7OodVCroZGquojHIoRECvvzG71+DnLRdSQPIWhiftJlzODLJ4VF1Mvz9qeXXevsCRcEQ+hN2Ps/UaaPIUWswJJKjm6EoWB8syZLsPV8PgiLTGlhHjIoFSaR10OQRpSPwH79sFlWOAVADAAAAAElFTkSuQmCC"},"(49条消息) 第三讲:交换机原理及配置_yu.deqiang的博客-CSDN博客")])]),A("p")]),A("dt",null,[A("h3",{add_date:"1670834333",last_modified:"1713086379"},"前端"),A("dl",null,[A("p"),A("dt",null,[A("a",{href:"https://cn.vuejs.org/",add_date:"1671189407",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACfklEQVQ4jaWSTUiVURCG3znnfD/Xn1RuurBokwsVJEoIMn9woSSi1CaoRbRy565FgV7OhRDdla2DXAS1iwJdmiKJgYgUtdDARbZRSqV7v3u/75wzLa6aWbjpXc7MMzPMvMBRaS2aWfutPOThmFp5yGtm7UNrcTRO3dPZSeGrBia20dx2rfmwE8qKQBSlGVt59vQFALTeuXvbc8F9mys61VJdSHWd3iIm6WKzrpjonQj8YSZG2FKNnys74O0EKuUe1+uh1wBAq/EjjuJaeEDYUg0Z+AATbGJvyY3nsx/P3exolFI2czklMA7xlz2jvPBUeY6p8jt3qqK8ZqIoDtrTUE2VRjhBSb7w8m1fJqsAwEnK2GIySITQv1SD+NOeZ7YiBssHBMDkLIu60PMv1jDFrCwnBScpAwACGmK+d3TNODMpQl9QmXRBWxoMBmJHHDsCGGFbGlQmnQh9YZyZnO8dXYOGENBwYFBOReMmX9gQTgq/qcqp8xWAYWbDLBsq4DVVOeGkMPnCRk5F42AQNFzpJVlNyz0Tu85yBooECAja02AJIkUUXE0DBECRcJYzyz0Tu8hqAkrhQw8AQPdlmpOpoN2RNflXmxIElA2esQJS2ai4MPueu/brXekGvzsAWruE6R5ba5CAUp11nOqsYxgQW2sSpnslUB9S4gjvoLVY6M8s2cRMycCTVC4dlUknA0/axEwt9GeWoHXpbgdOPOZYAoArbx7WpwKsEqgGABj8IyriwuLAyLf9Ov57g4OEBi0OjGxa68bIl4J8Kax1Y4sDI5vQoKPwvzY4aEMNM8P+WVH7GQz6yluN631PYtCf8EkiAGifyd7omNbXTxz2v/oFIW4et9u/Qy8AAAAASUVORK5CYII="},"Vue.js - 渐进式 JavaScript 框架 | Vue.js")]),A("dt",null,[A("a",{href:"https://www.bootcdn.cn/",add_date:"1672323668",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC00lEQVQ4jVWTTWhcdRTFf/f/3ps3mUw6idbg+EHRII0t2toSKYofsYitiBU34iLUCqWKC92JghtFV+KiBYUgBYMrcVEjWEtsUapgC2mb0gS/mmBDoWkrOtrM5M28//+4eFMxd3FX917Ouecck+QBkySvYLFFhsGvfy3pg+nDeInX7nuCDTfeYgB58IrMycwMkEkKQcHMnAyskbX4ZO44E3Pfc6X5DwA3lKs8P7yNFzc+zNpKH4CCgjlzsi4CBzA5f1oHzkxx7uoilTilFEUAdIJnuZNxV3+dl+4d5bn12xSZOSCYpPDLn5fsvZNf6tiFWXPmqMQlAkISGBiGM6OVt+l4z0h9SG/d/7RtGVyHE/DuiS+YnJ+mLyn/txwkMMNhAPgQ6IlK1NIKxy/+xDs/HsJLikPwXOtkrEl6AFjxHYJENUnxCmQ+B8DMaPucnjilLynTDp62z3FmRmwOIVZ8TjVJOTA6xq6hrVxablAr9bB/dIzx7XsYrKyh5TNEtwwcsu51T71a49OdLzP7x0UOzn7HY7dvYGLHPs5eWeSrhRkmduxj/UCdzHcKYlJB0YpGCKKVt4mcsfeeR3n7gWd5/9RhPjp7lLWVPpqdNnkIGCBAGM66UBIXc7n5N7u/Hqde6eeR24bZO3WQQ79Ns2toC8MDdV44Ms584zJpVCqeDMQAQQEzSKOEldDhzR8+J7Li+7dWB/jm91kmz58idhHl7kwePEGB2MyolSo0sha9vSmpiym5CBXA8AoA9CZpQRVoZC1qaYU0Togl8cbIUzgXcWRhBt+VkOte6EpoGMt5BoJnhrby+siTJC5ilZWPLc5p/+kpTi6dJ3Ux5TgBCv2becamm9bx6ubH2XnHpkKE61YOkhnIzCzzOZ/9fIKPz33LQuMqIG7u7WfPxocYu/tBqqVyYXJpVZj+H2cwbKnZ0IczR7nWznhl83burA0aErnCqjj/CxQyZYlhcOJvAAAAAElFTkSuQmCC"},"BootCDN - Bootstrap 中文网开源项目免费 CDN 加速服务")]),A("dt",null,[A("a",{href:"https://www.npmjs.com/",add_date:"1672746238",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABJElEQVQ4jYWSPW4CQQyFP3uGREqEFIlQcgY6Wg6wl4BTcBLOQ4nE3oA6VehAiPCzdopZZtkkEHd+Y/vZb56U4CBACDyIqkplERBwsKp6UK9paGoweOr3X0cjNwNEpK5ydwBEdb9anTYbBUrVJayLwt3N3bwVGVkXxRJK1ZgGutWVACKZR8DMEHGzhEZq2lbY4fAxm513u7fxuDeZNA/uMc+rVzcTVT+fP+fzL3c5Ht+nU0Cu5Ppz+LU/9noxhNDt3u6Z5LorPFWFWSPaPw13oj4664h7TjJO/hJ3pc3Y3PI3KjH1iqqIcNVeOh0XaXAQ1UQk5W9rqPrptF0s7HJ5HgxehkPM9mWZrCElkMzXJtdsypuUZL7k23Brb3fMHEQkqEJj7285nLmISMvnnQAAAABJRU5ErkJggg=="},"新人掌")]),A("dt",null,[A("a",{href:"https://www.runoob.com/",add_date:"1673524645",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC9klEQVQ4jV2TX2jWZRTHP9/n+b3/9qbbnGukc206nWOEUGhjhDcuw4tmF9WFUUh30U0bFlaXUYvChIgugigqCjLCJUWmFv2hZEJdFjbXHG/mRUhue/f++f2e53SxzaRzdTh8z+Gcw+cr1sIQwl767kBnqVh4UFGjKPYLgnAzkXguhvqJiZHT19a0AFptF2DHvh97PFf0z8hpQBIxGAicExYNM37LGuGFiXumPlwborXktR/vn2ztKBwNIZI1YpDAJAHIzMwgV3DeENWF8Nz48MlJjBXB69MPHM4Vc+9ULpLVqkgOJ4HdtCJAjIRSGW3eZr7RyA6N333yI714dqxrfbubrl733e++gtWX5Z03LMqcTPVM7Ohs2Pq+Ns1fbmWo+Wc2dNT7dYXs0mIt3ZO0lP0jzltPKSmHI4cPOEiAaCunCcmRKJPPe0vTRAlL/m++MFzW35LLPZQ4x30xRmtd18LB/XvxylsucTIgyzJqjSblUgsSCiHaclhk6o8zlrmaSRpNgG6LEBR1fXFJ5eIt/PzrHAvVZXb2buHc+V/o6minq6Odgd4eVevLZhgxGIbrdxgyDAPKLSU+++Yn3ps6w2BvD7OVq9y6oZ3BbT289cnnfPnDBcqlIma29lfvcKo4L2RYmgXbfceA7RrYysX5Cr9frjC4tYeZ+SvcNbSdOwe3WzNNAeG8MLOZxKK+NnSvECEENnVu0KNjozZbuarF6jLtbWV2+E0M79ppDq+FxjWTpBhMzvmzzmEfpPVwRYUo73wMmafZMPXdttn27dmNJ8/Gto0WUilkDq9cTAqONA2zTdc8IYDj02NPlMulN1tDX5pQckZwIEmyVZ9gREQSU1viHzfna9XaY+PDp96/gfLx8wdfzZftSKOeElILErCKMqsoJ3nnc/mE5pK9PDFy6tkbKK/FGxcefhIXn3Zet2NGjCt15wQYMepS1giTT418+vZ/ZvqfnY99u39Lrlg+FM32Ad0Iw5iT46uleu3j5/ee/utmO/8LnwFof3hW+HsAAAAASUVORK5CYII="},"菜鸟教程 - 学的不仅是技术,更是梦想!")]),A("dt",null,[A("a",{href:"https://echarts.apache.org/zh/option.html#title",add_date:"1673874208",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAADAElEQVQ4jVVTS2icVRT+vnPvP490Jo+GMmMoKLYSFyakVAikmqrYLgSLbXErFN8U3ZRiF4pxIXQRF6IuFMGNLlwYFxGKrTRWC2IQxSe1Vlva1DQqJqaZxz/33nNcTAz17A7nfIvvRXSHAAwAZkYfvE8FjxHYHc3qJMyB1wjMIeHtA9/PnrkRw/UF724f761WatNGHKJzPpjBYAYYoMYCBWoWxPjW+cWLzx1d+q4BADSA720fr1aqtdmi+MnriCk2W5ZauQAkhfCVTSZeVJNJf1aQZgyn1hbz/b8tnWx5AjZTqU2XnJ9cjSHvrDWzwZHbZOv9Eyj092L5xwu4cvIzxkbbuXLJ/gl53pcV91jdjk8t4Rm+P/LAZOb96URYZ7Uhw48e5I4jj0Myv0Fv+dyvdvbZl9BYWIIrlQym6ulMU7hbHN1TTpzrrDVQ3zXGnceepniP1AnQlExDtIHbt3H85SNd2dZ1c0QGyhNCwe4INQ1RbnloLwBYioFSyEAR0glV1bbcOcrNI8OIrTYgToKqkbxHIlBPqnCZZ3GgDwAgzq0bYF1vzQgSxf4qUjs3ETCZMpoOCWBGITREtP9aRvffNqwmCYrAYjKYobxlEDEPoIiRNHGQRTECztnCqc+71qp1g0XAkoIkLn98hivnL+GuN16kKxUNeQeZuKsC2KcOYKG6Sa/OfYmLs5+YyzxMlZbUoGoaE8R7k8xDOx1MvHJMi+UyY4hz7uDgtj+cl0e0y52/z33BQl8Vg3cMQ5wjnSNFUL15CBoD5l941SxG5H+vxOuXFw4TAGbG9r3e4/zhVe3kliyLzRY3jw6jPrEDpcEBNK/9icWzX2Hl50vmy6VQVRZz6vSBH04c5RQgt9b2lis3FT/s8dmelRhURFJoNp22OwQMIM2Vi8mXSq5XMmlrmi2s5g/PXzgRNtL25tDOnlpt63GFPenIQoDB2D0LicwI1dQm+do3X//y/BR+6vxXyf/V+YOxfbtAO0TDvdFsCKRllCuEnTbVd/Z/+9H8jZh/AeK8dx+kMPWPAAAAAElFTkSuQmCC"},"Documentation - Apache ECharts")]),A("dt",null,[A("a",{href:"https://element.eleme.cn/#/zh-CN",add_date:"1675427261",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAClklEQVQ4jX2TTYiVZRiGr+c7Zzwz4xn7o5/RJKFoEQRNP7RwRiSyRQylCM7CFKlNixbBUC3ciBEjQ9gpCkyYoDLczGJMnWlKxKFpE/2M0SJE0HTUStPKms6Z833v1eKYmWEXPJsXnpsH3vuCyxhsMRMyAGpu7Nvj931jHqfmRgAhY4sZGPyL1mLrcasPL3vfiW2Hdd7WDB3WO3Y5zpAPXQqKVtCVvOjijhHffPqQzVO5mnJrMxa1GQvNnc31mSnnF474Gs+5+PLVANUR1z426ez0edXCgydt9o6aFu1oTe+o6dNTNrVw8id9dMLZrhHXAAQrra4a5Ls9/Sz5+XfmX/qMtokTsCCDatnIAi7m4Q9/wGAPvNrbiDkqrN7HiU82c0+ZEpXuim0d4MBElGfOGd0LwyRRGPzWhFIQL9yPgz3GXF5JnSXjtgrtdEclo51UEOZGANzQjkmCwEYh994kH/bj8HLi8x9xYBwaRkhIGTM6CSQiIELyorXcLIhqW7C3P1i2iFj3ET6xl7jzeqlkUEhwHZS5BkmotsFb38DwV3BrJ0ythRVLUpYs/f3pZMwhgQrJoFxCJCplPFvHoS/l+R78dj1x38247YtSagglkF8ho06WYZRJlAIu/ElkWSskJA6sDjY/SOw+gne9S3xwBDoiERjkREZB40w98ouRMfq4+cDdkX6pw4UGdlcRpH8frp+ER5aGB9aYz0XGmXrUOUcdgK53fHLlfo9+fFY1OX3a5opR0407TV07TD27TdOnW0WaOq+rrijSPzzrLQvednjDQevH51VzX5+xeONSlU82ddMh5ys73f6fKl8l0wO3v+f+l7/WRmrJ9MqMLt3lOFv/T6ardd7uur4xjy0f8xg1n7qWzn8B+yieXRI8OTwAAAAASUVORK5CYII="},"Element - 网站快速成型工具")]),A("dt",null,[A("a",{href:"https://api.aa1.cn/",add_date:"1677635024",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACYklEQVQ4jX2TzWtVVxTFf2ufc29i34MkpFRi0IhtIyVoC5FSHAgtnejAkZOIioNi6R/RkRP/Be2k0EGgwwoKtjPBQUSwBUfiJ2hTP4IQfb533zlnd3BvakVwDc5k77NYe+21xRsEoADi/XDAgMxW86fxjx9ROAxN0xYd2ds8XnyrvUBd4/ny7fTt2QhnKlc6EYiLRS2nEPjbBJLjnQC5yEqzcOZc7OqbRcPsjItAbsj9XeEyHMfd3QSbtHJXHGFyBcBKcUvDYoAJmSQTMoQ1w2RpXExSEG6w4gar6pTjpShMmKY/6+OpKKekZjhWbpJKduYPzqo3VyunAhKwKoNlnCIFMUoNi8fnOfnn15ra26Oaisx9NUNvxzaa1Gjp5E5m90+RSkZBgmUitEN7cYIC84dmeXT1GbuPbOfv6xsc/vkAj9Y2uH9xnRf3BoRKnSWtSS2BwXiU+HDfFDu/+YhX/wzZc3SOl+uveXztObd+ecDS8d2UUWawMaTgqB0c23ozhZm9fR5eecLl09fZfDhgeqHP9GKfpVML/PXTXQZPR7x+2iCEd2sSnK8+qRfW5HxRNCpljNydMY0+/+5jti/PcOmHNbYx6XW/ooyzq9SGuHm7efBlhBvgu7x1VR4qIUkhm9/5dZ37vz2hH3tYBXmYHeGSumjeIMKKw6gARW3OcS8oQBokxi/BKuEZZHJw7377mxyIvrw2iFHEICqT1xbChMVqwuS1icpEDBAjXptLPVhVhAvZ/djvUpqUM3SKISGc/8e5k4ZQkdKkebgCF9J/F7OD8x/U1OW9h9yhobHHfD8A+Bfvvg6T4LeF0QAAAABJRU5ErkJggg=="},"免费API - 提供免费接口调用平台")]),A("dt",null,[A("a",{href:"https://nodejs.cn/api/fs.html#fspromisesrmdirpath-options",add_date:"1680160224",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC1UlEQVQ4jW2TT4iVVRjGn+c957vfnWlmuNZgKCkEhS7cmSS2aTe00M0wQn9IsRSCIMyFMbgqdKVtok2Bi1EQvAspMHATbVwWLQpiQKjUHJs7c6/3u/f77vfvPC6u84forA6H933Oy/vjB2w7CzcX3Mb9/A/vv3Xh9plfz9489cvpq+/M/V8NAHD8CNc+jhoAPr4zv3+SzUVfR++ZYpZ5QL+fBzNcQ8Clr08sLW8EtY+3a24kvXv99ZnZl3afi0L86WQ8OVX1glR5hYoYpgVdw5G1EiOv9P7ufHn1/PfJ5gSnb8/NT+2ILkZuYl8YGFzdKK00jxChLoCiCMiyvPLeR1PTTSDojxpavDz/7S1789zR2ezfaMk89+XDsgxBAuApEgGEQICE0RdlqUE/K513+//pdJbwwczztucVb3XSHKaPGczDAQQkwiAYRKMAiQLrOkCC6z4ZhIdrnfTgnlfNWvFIzThivtY0lQAgApSe/S6BBEmjyrpmFYIe9boWAtiabcl6+SyjmLQ6QtH3NAcoiOQ2TAAUSEDoDRKm+QjeO/Y6PRqeG8AZETUMf/71BEUeYEZIgKQt3hSMRFYV8GaILQKmpmGjaiQfU0kxxOp6X49XUjhPSALJbSGERIBQpRrNhlerSGXNVpMZ07Cy3g2NhuejlUSDtJD3JkHjJRICJFCiyKwqQgjSzhdepE3s7YYHq+szRajNOauKssKDhz2ajTEqiAgaoxxvo3beLCmy6coVwb557ae1pFueNPKei13U8I4rnaTqDTK5yCRKGk9QEaSfiCIG3Cvq6mT7wztdBwD3f7z/+643dl8nANIOoWGNPCuxq7VDdQXUQXSxd6Msz7O8uDJYzU7c/fzuz1uUFuDQHst05OKRA867xVCFtw+8vBc7p1sYpgVGo/JGkiaXrn1067f/9myZuYBNVQ9/cfjY0a/mlj/77szyJzdOHdvUeVyzKeFT9k18v9LwrSUAAAAASUVORK5CYII="},"fs 文件系统 | Node.js API 文档")]),A("dt",null,[A("a",{href:"https://www.bootcss.com/",add_date:"1681372918",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAB8UlEQVQ4jW2Tv2tUQRDHP7Nv73K5iDkjIooigoeFnVbBWFpYiFhqI2gKxX/ATkQRixQHYiNpYqNlbCNYxSJai97JGbBIIJeYw+TC/Xhvx2LvrXucA+/Nm9n9znznx5PaEZ3D6CJQdahxgENx6rXm9qh2TmmAm5faUdcAqiMXVUdAY0E02HUbgzMURzrULsoqeDGoCjoMquh5i/9AgYwBlx5mXH0yRS5pV9n5lfLhRYevy4JhYgj2j40NpxnOpAAs3dmg+wdKFeHyfIVbr6dZ+Nbid90gFEJSG9FB1QcBaK526e2WUBybjS0efTrDqYtC63uGERsw1kVgH8qLimAooMC5uQLZQGmudUGKw6ZGJbjIkYd4sHwSlwp2Upg5bVm6v8FO01AQCdldKEH/OXJZe9emvy9MHUuoXilxu3aCTmub9VVForJNDtZoGgBf3u7z+c2Ajwt9Xt1osbXe5/rjwzh1I1MwsRGXgIDRAglFhCK9jmNyOgmLlTO349n9+8K1Mv09AZSzs4eozpZZebkbtjJnbuN1VYG0ZzhoO24+PR760d5Mef98m5XaHkYqI1OQZzMDzfc8Y0BfD0g1CxfyM88wwcgEqA1+q+gPharnkGCljPn/Hzis20SMtW5E5a6iP0MTNQEMMK6VBD9EUGgK3PsLVyhZsU/0LHMAAAAASUVORK5CYII="},"Bootstrap中文网")]),A("dt",null,[A("a",{href:"https://pixijs.com/",add_date:"1683431794",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACgklEQVQ4jW2TT0hUURTGf+e+OzqO/0ZL0orKEAQnCClwEQSl26BFtRCDIKpFLQpbtRPcta2F0CYhSYuiIGoTlURQZItCSdP+SJmp5FjmjPPeu6fFe5qE3/bw3fOd3zlX+CcBFEi95PC5OsqPbybZCKrfKIxMs9Dbwr0rgM9/plVzN83bT7Krv5aKFnBAoFHZCgjf+TV4lVft3YxNxUYVorZyAEr76BisY0MzLPkKIoiN6hoIAqTsFLMvtnCjVWFZQD0FI6C3OHS2kdoTkPcVtUKRVyDAAR7WRIn8oJzSHa1UT+5gYkjBM1EDTH2q5gioU1AhIfP8ftPL+wMDjLdlWRoGaxQU1DWltx5FMYAzAjrTNZCq8lL1UBBBrE/orvOh8xTPn3bw5HE/Exd9HNFIgVQnS3cqw1ZALUBNZY1FjI1hSkjov2NqcoV0mmTxWvJORUdHYrwALAeKRrPkCdQnlP3UtzWwaXQfm7buZmNXAkFxgZBI/MwvjmWaMoESUyZvJZRQCxSwJFwRnm2nsSdESeLFwQIniADyduFzH4IDjAXIVmYRLUgFCQRjFFUPNAECoVMIBTwoSUzy40Ebj29Ga8ZFe06VaZkUqRAAKtHObXwrzhM8DxxfmLt7iYcno02hqwxkyYpV4+VZJoc6UMnhJgySK64oCXJ+7v1o7sftgzy6A9C3BqgFSE/Omk+yGHxkGh9jHM7v4e2x+0y94/Ie4fRQiODi2PHxRjIA2W01LrSh10BaM1RphippJK1AwJkhXwUZAC+OrWsCRA+kz5f/kUBnqimWEqwEaH6ewtzKPxEIj0HIOjIKIrLXf/bry4UZ/K95cdnXzHZeY/xrbNb1jCv6CzXkE+6uURFuAAAAAElFTkSuQmCC"},"PixiJS")]),A("dt",null,[A("a",{href:"https://learnlayout.com/toc.html",add_date:"1684036103",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAADR0lEQVQ4jV2R309bdRjGn/d7zvectT1nUFiFrgi0JDjaGBwlzLiNC7MYJTGRDRMX0WS7WCLE/Q292v1inMkuwBjJLkQ0uGjijMS6GSFkLiZWR7YubMAKlbZQStvz6/Wi0xA/d+/7Pp/35iE8I8UpkaKUBwCDb7/+QrQsv22ryyg8Rk6pP1ppVUd+/2L+/v+z1LBTAqmU9/7c9d7n+no+zP66PHZ6/nH7hUqH5+7XcB0rYvlSMhdN9s9u3X/00WdnL6386xCYBYi8icWvzutm4GM14A8+TS/Dnb7lPU9+guth1d5hOfGmCJ9KwtnbL9b3KpPXTozeALMgAJhYnD2v+Y2Zje9ukwK26k0h9XTuNinKJqCH4BbX8XP3e6yVnjoeCe3oG6fYqpTfvXZi7Ia4eHMqJk3z6tade8j/sOSY8Zi0HY9C5iFE/CpFAhqFAoDtenQ4HpX5W4vO1p3fIE3z6sWbUzGhBY3LQlFDVnHHMWIRxUz0AJ4L22VyGOwws+2B4LgwEj0wohHFKpQcoaghLWhcFoKUEcey2Og+qtQ2C3DKFYCIiMAMJm4MTILIKe+huvk3jO4OxbEsFqSMCBB1VnPb2JhbIDMeA0kVxGCAiJhAzACYALCQEmaiB+tzP1I1tw0QdQqw56g+HYc622EXdwHmZ+VyQwUAEAMAewy7sAtfVztUnw6w5wiCyOhBE839vZ6VL4IdF40XREKAhSAGM4FBcF3U80UE+3s9PWiCIDKCbWtG1TQqZ594stmEbDYBbuC6TK7LxCBmZpbNJvRgE3YfPvFUTSO2rRmx9mB1ytqv3T1yPCEr2XUr900apKrQNAG/j9jvE6xJBkmJ3PxPqGTXrCMDCWnt1+6uPVidIgC4kP483tQS+r66thmpb5fsctlG4s+04tgFgjwM1drmP156xzUNgt4alL6OtvWdQv616eHxjACDpofHM6X8xhk9HFpoGUrItr4Omc7URHLoZU4e7+P0w6BoOxaWLUMvSj0cWijlN85MD49nwCAVBAYzfUr0F4BXP0jPvhXoCp8zBuOvVKvVGAAYyWNZf1f4l8rjrS8/GR77Go2SCNRoB/8tDjB6ZbJ1YPDk0sDgyaXRK5OtB28Hs/8ATYKIx0Ata9EAAAAASUVORK5CYII="},"学习 CSS 布局")]),A("dt",null,[A("a",{href:"https://megasu.gitee.io/flexboxfroggy/",add_date:"1684036230",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAB7UlEQVQ4jc2SQUsbYRCG35lvd6NVCSm1FmppIbnmUGwpBC+iUDC5xmN/gBfx1NsePNm7+AP6G4LQQxBBPNVSEC/SQ5oqbqDtgklMst9+Mz1YY9Ecein0gYGBl5d3Zhjgv6J6XC2Ecbi1dra+iirMjQCzdra+GsbhVvW4WvjTQ8PuI/yNBxsfzIFZCJ5m8O3Z6dtep7sLAOOTEwtPGrPvkq8DuJLbDb+Hr/ECFgD42l8MipMegvz5YQQbWcBh0/ODPc8P9uCwaSOL88MIHoJ8MShOXvu862ZualEbSVMerz9CnMRI+gkx8TgAJGmC+HmM6VfTaFw2ZW5qUY9whOEE87VybtBpZUjVRf0IF7YNKCAiEBFAgQvbRtRvgVTdoNPKzNfKOQCgcr1c4jHz3jn+OftwpnAvO5EDA9BbFyZAU0Xv4jI+/dH6YkjuS9+98WjMX2Gf84Y0T4YRZDNgw7gDAS5x6LV7Od/QSxgPCl7xxEqTmaCpDlNVb8f/Rq9K7ZUuVpo8SNOacy6GodGmURiCcy4epGmN60s7J3C6DYYMp2W6W0Q3X2MgcLpdX9o58Sr7lWUwZSUVYSa2nQTENPKIYgXsMcSKkE/Zyn5l2bNqZgKlkoj73O52oSmggpEQA51BFyoCZlNKlD79/d7/il9FnuP8vO1JiAAAAABJRU5ErkJggg=="},"Flexbox Froggy - 一个用来学CSS flexbox的游戏")]),A("dt",null,[A("a",{href:"https://neumorphism.io/#e0e0e0",add_date:"1684036434",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABOklEQVQ4jZWSvUoDURCFv3sJBGK5VsEHCKQSbXyCgJDWwsaARd5ECPoAPkFaSSOk8g3strDMA0gQAsqanWNx9+5u/uPc7s6cM+fMjDsf6srDg4M2B0Iwl2P0/uxe4p+7GOrNQVtGy0wnICSFcitgqGAQIs+Wvz936TiZArjLoT5kauWZnQWwBYCEEMigIIw5SSan63ScTD2AGaudt4KrHMgrW0w6/XTgo7tQYHvAazlo4nn0wdo/wYpKOG1UI14tUCl3ExwfQLBg0d9x4MJCjaA+/V1g6sSlBRrliRzwXFdVbqRSsGcTbANbqcAL5iha2NJZq5bq8gOBYyQs2zggjhgm4AC6t189T/4qye843UpBXGP+DbJAANC9+ezJFhOgWe+wLjuoWBYq3awkAOj00wGeJyDhcMzMcf8HOPx6iWMUuCsAAAAASUVORK5CYII="},"Neumorphism/Soft UI CSS shadow generator")]),A("dt",null,[A("a",{href:"https://www.javascript.fun/",add_date:"1684036632",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABm0lEQVQ4jV3TzW+MYRQF8N+800aqPkIq1oSFlY1EKlGsJBYWYmvJv2FnoXvfohIJsbBpN4iFEImwUBJKJDJptVNtNdWGjjGtRc/Ua57kzft8nXPPPfe5rI2Kf+MYhvAJc1jGaPYGSvfKGLARVzCDVcziHZp4nb0mbmBTG1RFEfAITqOWvTG0AvqW+QbsxVE8QLMa5os4mfkvTId8MevdCTKDSRxGH4aFbQKP8CHRvoTsYwh+4mWIp7AURYeqOB+27ZG7K9HfhGgntibv6ZjanXUXvI/LNczjIQ6iJz71YTCKange8sUEsISv+IxrYd6DHR2luheSMYzjbTzRwHfUcaKztnG+gn78SZor8atexdnk1ML+HPbgAE6FeBYLOIfNIZxPYLcDnovE5Rys5mEVpe9VAvxIOS8VuFrKtZEKdIW0CKCS/2TmE/HqboEXuJ4yNgJcCHhLyYsisn9jX3rjWbspevEksscjsYU7HYZeyJ3HUfBfQ/XicqKs5OLNnG3DcTzFrdIbWWeoBNBu5zM4kpRGk0od9/PQ1jF/ASh2hSDw7KH5AAAAAElFTkSuQmCC"},"Front End | JavaScript Fun | 前端工坊")]),A("dt",null,[A("a",{href:"https://www.flaticon.com/free-icons/share",add_date:"1684036673",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACuklEQVQ4jX2STWhVVxSF1z7nvHvvezdN/AmCQqBQQURIIgRLdRJQkwdOOurEQbEdqo1GBDUoN04KbVFMUqoiZi7iRNCkQimlkEHR16SgYIaBVqFEX3Lfuz/nnL0dmOIPqd94rY/NZtHQXz/2GKIvWbhGgAiD8A4CAbgShxWX2rsz/Ud/P/DnxO4wNJ9nBV03SviO6ewYEO8BIojnd+vM0GEFdrVd+sDfAAADuhR0dw3y3y+GDUADZbPlAAgBZOLIQL0+QkRApEAVA3Z86kH/yOJQY+KwicPB/J/lUgifUn1+8oauhV9zVrCAPAg/kXAGgQGRh1Jg59KVtv8h7FrRke9ukNGfKK3IZcVN4z0uUG4PkVZbdFDRLrOP7/d+cw3rUJ+fOms6ou2cF8wlP2em8wQA9YXJk6ajesm1ChHhFgkmhcQGnTVdruR//Fsu3Ouq9W4LmRoQ6dJxpFyajc70Hr+sIKBmaq/aNH+iQyMEiisb4rPhxs4LRGpMiaw+HLhuKyUSHQUbyWjYNH/STO1VCEhhPKG5vaMZsx8DoCDi3ctWxoV15Yt04n7f8V/2L0x9pgJ12LUKT1opZj82t3c0w3hCa+8GgSDDjSs/VzqqB11WCgEpG7tjdueJZ/WFyXs6jobhhVyWP5jtHxn6r6MAAONr41H6DDtfAiIUmKrYYB+IRAi/Eili5wqQPvN257UgASNJ1GzfsUds7bSpRUpYjCL5tnf++3hJL1927WJJnL8123fsEZJEIQG/EQBAkggA8o4uuna+DPZs4mj7VoQjj3clpVj/VWkxDoDWsgDw3u4TKCTgemPitOmsfufahYOg2c5p9297ji6ttw31noAhoGa3m/JpvqijwISbPtpcDfkcBAS5pT8sWGOuZzRzjCNwftGutp4S6DYIAnzB6+X/l4+nk2hwOok+lHkFwGBfVNlrN1AAAAAASUVORK5CYII="},"Share Icons & Symbols")]),A("dt",null,[A("a",{href:"https://uigradients.com/#LoveCouple",add_date:"1684036747",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABXUlEQVQ4jYWS0Y0UMRBE6/XM6jghkuCLMMiJy4IjChIgG1LgH7SzaLl592F7vEiL8M+0Pe5yVXXx9eO3z+c6f7pydtkvrG6pbKxesnjJ4pYl7bt6ccklq2fXRX49vP+y/qzr02/MyUgSCdlzd5lAoiG5brzL96fawDIS2COIIYm2LlorSYh2EKyS62ZVEpE9OxWUHUyE1myj1eAgRgMqgZSdWwWNjMvE9KKz6CAEogm9r6lOVzEuddJzMU5Mdns3oW4vqmHQHkAeBh7gt7B1UO3/tEnFafs0McOJ0A2qiXqjPX2mx9McDCYjY/OAzqCLYcj7i+ksmYfQTDysHhP5Vw6mOcOzpCqYlp3Gum3v5oCMAWJiilgDc4/Nuv/koEjLy9g3LthSGDLo3MlBzUlLFE09uCNBIsZWTfNGeUyVaMpy58/pkWKt50dxuIIoHKljYh14+JKX0xt/vP3w/AotqOD0tvFzlgAAAABJRU5ErkJggg=="},"uiGradients - Beautiful colored gradients")]),A("dt",null,[A("a",{href:"https://goldfirestudios.com/howler-js-modern-web-audio-javascript-library",add_date:"1684669324",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABlUlEQVQ4jZWQPWsUYRRGz/POzMLGIAYshECyRaosgqQIyjbxR/gD/B32ahMsRKwMprQTFIloIfgRTCw04NoYM4tZ8iGbQl2TcWfeazEZs4kbmdzy4T7nHq7ejNaMEmPoeqO9dm1xtDZncLXIXZkygBObOYi3h/KS/R9Zxvv9RgwkJwII3oVJ8IFcYQ/ITgL4bej+xZ3P3wHMbBgISwMMFtxu8LAvGAcqZQFtsJt/r4McXOhf+A/AUsFso91aLJKX5ybOIk0fyGHHA0yPtBve64+iMK0bTIClGE1gLzymvW5BcKNQP4htCnTK0AMn6xmqFwb+0G242/j6ZflfKdWBFWf+tklTkP8gEywA2/t7S0GkuaPlj5OTFTwjgluGDLOxAhAAz5HNg6WG7kzH8eZRQNrpRDhb9WnwBKfToEoBMEMdefcK1IwiezboKzvVagZ63Nha3TbpF9DLAcZTeb/snY8EnW536OcgwEwcJ8l67XWuQwvYAnBJb+jKpY3WJ9B5D62Zb83uIIDALvMiBTgzUt1ArAD8AW6so1O6MKouAAAAAElFTkSuQmCC"},"吼叫者.js - 现代网络音频Javascript库 - 金火工作室")]),A("dt",null,[A("a",{href:"https://www.iconfont.cn/",add_date:"1684675284",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAADLUlEQVQ4jYWTS2xUdRjFz/e/j7nzaMs00zKkVqYPrWmcRiGgEsDEBoyQUCUZBEwAJXRRNSYu1A21K4OJIPGxABVTjc+mQBMLtE3VyFQaQg1txWKmgAUGGKZ3Wph2Xvfe/+eCGhQ0nvU5OWfxO4R/FyFQ53NvW/cJ561U7v29LwKQAPg//P+QAAD3lu0fiolRVoaj7H5m8+sAgEhE+b+wAgC+1es2ar1dDE4yOMn6F58Wih56YtnfC+6e3NysgZmMkroqY+8eEzzpIB+3YV21kYuzsbP1twBQBOZbXoBuh29Ld7W2/ohMnGEnLOQuS2QvSXDSpmtn2Wh55eO7mwGEItuCqYrSHbphrMpWBFdkVj8i+b4QoWABJAACtJEx1o6cEFzIH3SlZ6Kl2dkD5/fvv0GuJyO1+VD5MfWNl2o8RgXcI5MOD/4kzDX3wHm4gUAS+qF++MdUdlYu40zYL7Lnh4Hd+6J+4awV1eHa1n3Pbaqp7Diaw9CE40WVMr8sQv6ucwQuMGLjXB61uTy0gXz2ApJ9p+yVg8P53c9vXS7dRTvUNYsXPdpUvVC+tXmjbtT1UOaxreyT61E2VYtJ8yrpl0wEbjQgMz6J3Oi3MHoPKNAVtJwYdL4eGKgVV8zUTY+/lO+vr2ezvwfevrepyttN2XyMoBrg4mLYfBr1ehfQuQvp00NoWLqU05YlzOmbNtT1m147NHKGOZdzPmtvlxfP/i4vnIvJkg/eYdgJhhnj8J5dnJsy5cnjUe7s6HCYmd88eJjxeOMqRWZnho4lUjXTRA0rlixxRs2UeHlkmCaaGhkujeDx8PX5ZTjZ/zOFKyp5XuW9cmfnYfHed0da0N31zV8MqHih+Rc8+1QYwYCFmkoVLhfBcW6xr2uAOQXEEwWMX3bhq+7P0fnlFrS1CTGHph2cnd2ux5Np5cEHNHiLCYqwoakSmipB5FAgSGp1yGVciP+62Ey/CkCgrY2VuYeJmTOjcW8s+T1dub4IVmEB5pUICCHIcoS4Zgq197itf9TRh3fbN1wcP5WYW850B5UMQPU1Nj3t1C1czv5iD2wHZKamlbE/js4M9Pxwhxd/AscabhDcvSOcAAAAAElFTkSuQmCC"},"iconfont-阿里巴巴矢量图标库")]),A("dt",null,[A("a",{href:"https://www.freenom.com/zh/index.html?lang=zh",add_date:"1684898203",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAB60lEQVQ4ja2RTUhUYRSGn3Pv/WauM6NDzZ9DY43ihGsjiMhFUNKiIAJbaLmwsNoUEeSmRYssahu0q1bVUto4QouIiAJDEYqoiGgiijJrQOfOnZ/va1ETaSL2867Oec/hOZxz4B8ljWD/4IUR3/fPGiMPu3KZ05dGDz1eDcABGBi6eHRq+sWo55Vtrc32VKJlBOhbNeDZ80JvqeTZLS2RaiaTvJ1Irnm02hUcAN+rzAOEwk1P82Pn+vJjvzcaY2wgICLer77s3nfmwdt3nzYWi/PxaDSykI1FZj727vkw370lHtW+M9jmXrn2urx1tqL3aggnXGuyf7178kRn8AmAZLsOGhGN4zjUanVUaYHy0BEKm3rA90gHbd6X64iA0QYQOsP23LFceMepXGDaau9oG0inEy99v0JzNFLoyGb6S5kNE1QrYKBY1exMuVd3pdwD7WE1KQKvvPraqS+VcQDr7vj5m6lkzKvVNAHlfJ64d/nWbGzdDLqGAN1Rdf1OT+RwflvkxvHO0HCzsjAG3nj1VgALjKV1Tf04lAOArrgghBxhc1zdbxysVamvTbaAMWC+e9ZKL7IFUgFxG3ksjHJkcc+KgKXyl/H+CLCc/gvAeOXqnFIKx7F/FgIiBC1ZPMGHgCUoy8JClrL+Tt8AtZeios74VV0AAAAASUVORK5CYII="},"Freenom - 人人都熟悉的名字")]),A("dt",null,[A("a",{href:"https://cloud.tencent.com/act/pro/seckill_season?fromSource=gwzcw.7236233.7236233.7236233&utm_medium=cpc&utm_id=gwzcw.7236233.7236233.7236233",add_date:"1684929688",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACJ0lEQVQ4jZWST0iTcRzGP7+977t/zilJBUJCxLophFBQHcWD0SFjRcFoTREzRcpTReihIiSkQ6c0QlKLFUhEHbp0aoamQmSHpkUXS6ZsYW5ur3u/HTbZ/EN/nt/p++V5Hh5+3we2g1+0f9r9FUFxEhTnnyhq4ygKlNApJ4HLCIcB0Ihgcpd76ulmA9sWcYf04uYZgheLmwg3yOKllDAdcrvALUbLewMRxUU5wRUROrN3tmS9JH1cF6FdziKiqBUjb1bkdkHe0CaTBWMxaFknAu3SRofUF6fOiSMzB3B6r/KdRty2OdzJXg75BjYkGJYmHPjJsBfFBzLc4pyaVkzMHkF3vmbNNNHMESytmvJdR0nEw9gTrdTUxHki/XhoJs4EFh/RaUTHwKReMRadwu6oIP7zIHXVCwC8+9KFx3WN5ZXjzO5rooLzLNFFQPUB0C+78TDOGkvwaVEYi3YDEI066JHcZZ5PVTIio7wQ4aG0AtAjNnryvRiUbkZFdNIpE0uqAPD50gA8kFKWuY+bY8QIEFJDOQNlAav5X6kijamzmhykrLyZyNxb9iw85mu1h28M46SOBEFCaoiwuPiBBYADDQen8RDiFwOKyMwOjJJHeL0NLCaSzFfacTt0VoA0KcCVuxigEBSKEiDJK1IECh2YnmtgvqyGWEUJmpXBsmlo2JH8KyCLwThn1Mv1xaZa/g/WiwQQDmvs9Ofmz5MKarfX7EeIIZxSWYDfa9nVNFgL8gwAAAAASUVORK5CYII="},"上云精选_云服务器秒杀_开发者上云推荐-腾讯云")]),A("dt",null,[A("a",{href:"https://console.cloud.tencent.com/domain/all-domain",add_date:"1684930699",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACJ0lEQVQ4jZWST0iTcRzGP7+977t/zilJBUJCxLophFBQHcWD0SFjRcFoTREzRcpTReihIiSkQ6c0QlKLFUhEHbp0aoamQmSHpkUXS6ZsYW5ur3u/HTbZ/EN/nt/p++V5Hh5+3we2g1+0f9r9FUFxEhTnnyhq4ygKlNApJ4HLCIcB0Ihgcpd76ulmA9sWcYf04uYZgheLmwg3yOKllDAdcrvALUbLewMRxUU5wRUROrN3tmS9JH1cF6FdziKiqBUjb1bkdkHe0CaTBWMxaFknAu3SRofUF6fOiSMzB3B6r/KdRty2OdzJXg75BjYkGJYmHPjJsBfFBzLc4pyaVkzMHkF3vmbNNNHMESytmvJdR0nEw9gTrdTUxHki/XhoJs4EFh/RaUTHwKReMRadwu6oIP7zIHXVCwC8+9KFx3WN5ZXjzO5rooLzLNFFQPUB0C+78TDOGkvwaVEYi3YDEI066JHcZZ5PVTIio7wQ4aG0AtAjNnryvRiUbkZFdNIpE0uqAPD50gA8kFKWuY+bY8QIEFJDOQNlAav5X6kijamzmhykrLyZyNxb9iw85mu1h28M46SOBEFCaoiwuPiBBYADDQen8RDiFwOKyMwOjJJHeL0NLCaSzFfacTt0VoA0KcCVuxigEBSKEiDJK1IECh2YnmtgvqyGWEUJmpXBsmlo2JH8KyCLwThn1Mv1xaZa/g/WiwQQDmvs9Ofmz5MKarfX7EeIIZxSWYDfa9nVNFgL8gwAAAAASUVORK5CYII="},"我的域名 - 域名注册 - 控制台")]),A("dt",null,[A("a",{href:"https://www.bt.cn/new/index.html",add_date:"1684941903",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACW0lEQVQ4jW2TTWiUVxSGn/ec75svMTPp+IcGB420oNJBCoVC6aJScGGhq6IbXdSNFMS1LroIlC5cBcF9Vy6kRV24KaVUCoWK0JVIIy4SOqWFJiMm1Uycufd0MZlgft67O+e9z+Hccy5s1QzGDAVAc3a62Zx9r7keL5jBttq1DTDSWfzgBye/KvZW0ak/+oZzpJ1sGpn3HT22i73gqfyYHB+BjmGUVauOTP2AP+T69eUr/QKLLDbmXnGO5IB4QjQ+nbrkya+Yq56z/lWKn1T4fmuULzB9J4txwYmaxxdlOb77xVznEQ/AAfZcP9karPWfuSyTmUC8HeJDxJw3ayvmdoagEVnPMX5fex0/vLX/0MTKnb+WDcBfp/ZYUdzGox2wlIx75eGJS1g0FKp3ny5+ST/dxVmSaNdK3S4jtzc94p4r70zWpsY/t0b1NC2v3lTN/yTis7HDDQa9fN89WqvdtcvVZHX8Zcn33QsPlzdP4RTFgdPtRRVWj0GSCrNIkapWHZ8oPfcGOVAo8n8Lz8b2MfNgALwx11PTBZG7RHgQ5H5OEAZhuZdS5MBKOag7fWS4J5sBS/MBWBABCgkTQhIQhhQREYDNL8yzHVC0bNiREIhY35MYtrkRE7Qmp7QN0AIIVYgcjKwEEEIaxpQVVJ3df8dWgDqznVUZt6xmrmGxQTA6DCThu9yDuMXF+d5oACNAAPzTf3wt99INudwqK7QuG/dChTyv9G8s1H67+uadHT/Tga/f/USuy4jT1aE6qvmPkQY3F84//Hkn/1ZtQKeut09Mf/v+8Y1MbC/4PyUD4+pzwjbVAAAAAElFTkSuQmCC"},"宝塔面板 - 简单好用的Linux/Windows服务器运维管理面板")]),A("dt",null,[A("a",{href:"https://console.dnspod.cn/dns/xiaoyugege.club/record",add_date:"1684982713",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACZUlEQVQ4jYWTT4iVVRjGf+97znfnhoEMFBFCjGNpOa1cZRQVuWnjTle6ECJcBaKbXH0EqWA4CG7cuCxwF7hoYSBupRLBRf4ZLzhKM4oGMnPn3u8779Pim3vNVe/mcN7Fc54/54HJXFTF/80BJS4rgWyysu5i2nlOP+QZFsoagxCPSCybszwe8XCwygqLNnwFTDLMlDsQRMuv6nHS++AGBESLZhLDXdtYZVFLKtwz4+Z6yy8PzR6DbJNKx+K9s7qUMoc1osXIEmaQSICD5ynnR23LoXvH7JoDUG+ugyvmJCBjpNQjWQ8sgTvECJV1RhZsy4ULc7X6+aUokyd9BJjATAzb4OsEz4pRWeFtd77HeKsMCXO2ayvbO4DaAuRmfBkNWEUC/rh73H7+r2+7zmmvO0dCIHgxfMKzPNG/4yy7FXxI0PhrVGXEVWo5t8ks0PKYZMFuBZAwd/5aPWUrzmUcIAWf+ww9CaIh2sJv1BYsENRofp73BXvU0ngPReEGgHOQAPCKL1TAEhkxWHvKn9Tyzn9TSuz3HpUgVLAwrgNkML1zWrPR8ikivI+XlisrP9rapvTx/BltdedIjAmcSg1/t2tTAJgxPvbMm2poKLgHww8WtW8MVSq8YcZRM3bEmI30On2N+WlQ2z/U8i6FxGdWUaIl1FA8c1ziRAVmCY+AGLPhffpqeNBWnJmkl/lGVcp8BSRPJCY10eYRdN9xC/0IlpuGA/e/tSeT9PL8u8wJ1hlxS94ZOi2aIaDgrJeW322D8/e/s8GkSAB5aY4lrvIJzzFmEc+nHGAWcQdxjQDrwGs5ZtOH/gXaOhrhzPW4wQAAAABJRU5ErkJggg=="},"批量操作 - DNSPod-免费智能DNS解析服务商-电信_网通_教育网,智能DNS")]),A("dt",null,[A("a",{href:"https://picwish.cn/upload?action=process&resourceId=f0a1381d-1f3e-40f9-92cb-07ead98bcfa6&url=https%3A%2F%2Fpicwishsz.oss-cn-shenzhen.aliyuncs.com%2Fefa%2Fefa87988-d6e0-4b2a-91f3-5b85bd6cd3ca.jpg%3FExpires%3D1685993374%26OSSAccessKeyId%3DLTAI5tGjJnh66c1txANiRBQN%26Signature%3Dq%2Ft5bBuiqlIPSDe8eEXEcKRsXbc%253D&filename=2625833729.png",add_date:"1685986239",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACXklEQVQ4jW2Sy2tVVxjFf+s7OUmuJl4UXx1YW2Io0lF1UhxZ6EQEQY1WKSLtVA3qpOKgDT5mDkRKCx22k6L4nDiog/ThXyBSB4aklQ606W00L3PPuXs5uPdEja7Jhs1ea6+1vk+8gp1/Ta3Mi/7vkcJyUkKWAjSJGb05wGUkgwUyQLwqMDtTLyzmgI2yloHqAcsDD0ge3j2RLu656ncq8hsCox9qRn/HSSffl1gh8YxEYTPrHo/xSF/YvrZ9xL1tF0sEAG58oqmnm2IYacJmPaLlmhfiobYxSV++Rh/XB1v7QWbE8ZrArl3+YOigj63ZRuv/KY47PE4fWTzQlpjWgLopgRS1bAcA3+COQPvMc35Y1c8lb+DE6EeaKrM4zj0Go9Sg7YSJ1EKU3gCA5K7O5waw+Wl6Bso57mCy2m7ORa7N5UG3Ur9Dc0oEIKbbLEtLO6iw97C/7ZnnSPGclnuJ1oEk+ijyPPLmZPr6xr7sLF7SwQgOjIYO+VLPHEcKU6Sag3nIfo5mthB5UXq6+Th+bEfojKKTQlja+6UvdM9zokwUQEbCzhz5gpRW87z5KZ9fP6Br1TJpkYw8tMfne0pON1P7VgbVIImCjN/L/zhz/Rf92i5dCaBrsUJB6zPenV/uP93ksaVuZe7VH/pOY9y9ekUP2jkdjChVvlW1ieTtHn+v3th4QYka+Cmo3lj95OhvWj/efoOqnytENU+wRvX+hCd0igai4bU00Mp76/pY3JfXyS8FqsS2bm3Vw4XQMecxqy7WxjL8NuJbBDpO7Lg9qLFU6CvQnX9z/nnp8k28AE/2FCacUAhTAAAAAElFTkSuQmCC"},"picwish.cn/upload?action=process&resourceId=f0a1381d-1f3e-40f9-92cb-07ead98bcfa6&url=https%3A%2F%2Fpicwishsz.oss-cn-shenzhen.aliyuncs.com%2Fefa%2Fefa87988-d6e0-4b2a-91f3-5b85bd6cd3ca.jpg%3FExpires%3D1685993374%26OSSAccessKeyId%3DLTAI5tGjJnh66c1txANiRBQN%26Signature%3Dq%2Ft5bBuiqlIPSDe8eEXEcKRsXbc%253D&filename=2625833729.png")]),A("dt",null,[A("a",{href:"https://www.wondercv.com/cvs/rrEuiEA/editor",add_date:"1687446460",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAACDElEQVQ4jUWSP4teRRyFnzMz9755s2uKFDaSKIrGrIUEKyGFio0LVhErP4CFfgYLi4AiWImFVayMpYVF0ggiiG5lpSBotdiELGzy3pk78zsWu2JzmvMHDjzyR7fAgEkIAIPOVGBFIBmfmYUWNpLUKzGQ/ovCGEyTy0SEpLOdQjUSbelPPVfffNcRtm0PnNuyf+8bPfjH2z1iCPms4Jx0uownn1leuZkfPXbKEdGWZdrb3129tvnqdj7+m+2+HYJEs2pQUe2lrnnZ5eVRXh6nupTTh9PTz9YPP46rL3G60HBzogbNNCtQyeScyqSc8zzVESfHxw/n7YO332NkWri6UCFDDTopJSUBSZpyBtbeoy5OGWctHaVCM9lU1EFWyp7nZDPG1FrZXogReZrZhQckF9YgRAt6yEx37ujkhGkah4f94GD+9JP+1mE8cYk6sJHS2QGaNUgSB9fT73/o8uVy99v53v3851+88HyplYFWs7qwgmG1rQR+7XX//Kt++IlL++X2Z/2Lz9O80QgaDgOFHgCrNSxbtt+5xXff++ar+uXIL15LDqVED8LIhWbCtNAgRVAbV674g/fB3Hg57RZNk0awBiMsFXqYpBaEUs6+uIVzCAFtM8CFDcsgSXZhRb0zb/XjEV9+rc1MxDl8NjabmaPf2DXNGzvk62+cr/XObgcC/c85QJAyexeNhf4Fgqg25uiNhTsAAAAASUVORK5CYII="},"超级简历WonderCV - HR推荐简历模板,智能简历制作工具,专业中英文简历模板免费下载")]),A("dt",null,[A("a",{href:"https://www.wondercv.com/cvs",add_date:"1687447241",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAB+UlEQVQ4jVWSv2tWdxTGP+d8773Jy5tKcXAKMTrYvCXYxaWgEApi1a1mc3URJH9CoVsprf+AToqTOAodzCQilJYunUREuoRCtWnR3Hu/v54ON5X0TOcczuc5PPCYvt5mKjPMkKYBhBmIKo5UQ9ThdUmUjDkIDIyaaVqallr/D5gRh7J6evhiG0lIVUUKcVjZfWhv/mA2p5YjgBsHYzmxHs9dsIMe81JrHIZuvtKvbyzd/Tbs/c5sjirgRBHFiI0lxBTG3sfexwOLffP+n3bt1HDrm3JywbuBCFHOOAHVChaCheAheGiatutz3t/b+7tdfnv1OiUwVqIaoghiFAV3N3OZuXsTAlJKqcZRIaCGmHCbPECUFcwwd9o2SNY0FmOYzVRr03aMlSx8Mh0qUeRq0N2/x/4+XZcvX7HFYumH79OlL3XsI8ZCFYaTRYYkyxhoY+EvXtrHx9uHj7onu+HVa9vcbFImiyyS/EMH5lC3trTxqT19bn/+1X53u+zsOHgVSSSR1ZBEFUlWcAB07St7/GM9/7n99LM+OWNg7iQhYTRkIZEqFQP6nrXVevMG7vrsrA8jy0tWIWvy8N+HLJuAriOEKX0A1h4mbSy4gRqyyIV2xrNf/M4DuilqdkhIdB2//kafWOqQTGcvHmqkxMEwyRzNM4IQmM+m9b/7eRtAWn7ZBwAAAABJRU5ErkJggg=="},"超级简历WonderCV - HR推荐简历模板,智能简历制作工具,专业中英文简历模板免费下载")]),A("dt",null,[A("a",{href:"https://layui.dev/docs/2.8/index.html",add_date:"1688356326",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACZ0lEQVQ4jXWTvYudRRjFf+eZuZ/7xpvsJks2ET+iLhoIq1FEoiApgmBhYWcK/xDLRLC10sImbRBBazsLK80fIIjECFoE495sdu/HO3Ms3rvXbXxgeIqHOfObc2bU/PDt5VGkW8xm72E2sAEQyF3HNkjdAGwbNRs/1rb9Kg/RZ8r5A7e1IihAlrCNTnbWJYFZLt+JZft2MJu/X6ePWyQBbKTEohStT151dTDHEsF8UbAJmYydktB+u+DT3T19uPOMHy3mJOnkuU5IsRKSlAQEwgJJ4VKtn6ePfHt3T6d7fZbdrTqKWrXfLj2v1QF2t4hjdbsqRfjhfKYLow1uXnye6XJBUmCb4sqNrfO60B/qsG0VtrAVArlWrSxQElSbjy5eYpwzANO25ebOc3z35nW+eeNdmpxpO19WBEBnswkFAq6cOs3LzYSj0hKC1hWAsobvKv8Xk8FrxxmnxCvNhHv7f7PVH3D3r9/55cljfjs84LBWBhH4JMFx1q8+dQarA3p9sgkrpxe1sttM2B6OqCua1UxGcrEZpuSrky3kzvudwZgcohfBshY+fvoSt3b3OCgtsYo4dyrooLR+cdTorTPnvKxFEcG0XXA0O+J+WzjbG/DC+BRN7vlyM9Ef8yMPUyZjQ4RdrU9euuJBSuvXc33rPF+8dg0hnh01bA9HALq2ue07D37VOPWsc99/7VpK7aekG2d3OrRqIRhEokkZA22t7JclofBP/zzUg9kT9xXO7uV7Keery8Wi3P3zfrj7ABJQcRcbIEQKGYtRSu5HwjlFbuHzXsSXkkabvT5ebf7/EqUW1wgp0p1/Adl9JnCitu7BAAAAAElFTkSuQmCC"},"开始使用 - Layui 文档")]),A("dt",null,[A("a",{href:"https://stackoverflow.co/",add_date:"1689255184",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABXklEQVQ4jbWQsUsCYRjGn/fuSu/Sk3ALmlzNtoagKRqSaHMKGkKhEOV0KWispSXPQaglAnNobOgfaCyIcgicmxO9zFPv/N5WwTs5gt7x+5739/2eDwgw/bK67HcnBQG4Ag3L0LJ/BoBFDuDzTiGUCAywDC3bNbRtANCrwxaBziRZanAGcjADwR8AX1uGesEZyFGzXwO43VsKn07GaJa5lY/GMefUAYooEvaELDnCEW9M2I1V7GdPg04hlLAM7dYqqut67ftLNwdpMB5dgRfXdVMgHIFpx9egfbwYk0eDA2LKAWJMkK6cUOhOGdkpZmoQiy29OmwFq1AKb5CgQyakAXqQJKpELn/eJzPK1JKhPhHjk4EmMzUVmU/coVLkeXff672pk155YXUsxikCJQFeYVCSgCiAV920N311b+r37FslH413S+qaV86rggfIBbG38RRAN+2ZHzsTMKvGv80vvziHGAusG84AAAAASUVORK5CYII="},"Empowering the world to develop technology through collective knowledge - Stack Overflow")]),A("dt",null,[A("a",{href:"https://stackoverflow.org.cn/",add_date:"1689255192",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABXklEQVQ4jbWQsUsCYRjGn/fuSu/Sk3ALmlzNtoagKRqSaHMKGkKhEOV0KWispSXPQaglAnNobOgfaCyIcgicmxO9zFPv/N5WwTs5gt7x+5739/2eDwgw/bK67HcnBQG4Ag3L0LJ/BoBFDuDzTiGUCAywDC3bNbRtANCrwxaBziRZanAGcjADwR8AX1uGesEZyFGzXwO43VsKn07GaJa5lY/GMefUAYooEvaELDnCEW9M2I1V7GdPg04hlLAM7dYqqut67ftLNwdpMB5dgRfXdVMgHIFpx9egfbwYk0eDA2LKAWJMkK6cUOhOGdkpZmoQiy29OmwFq1AKb5CgQyakAXqQJKpELn/eJzPK1JKhPhHjk4EmMzUVmU/coVLkeXff672pk155YXUsxikCJQFeYVCSgCiAV920N311b+r37FslH413S+qaV86rggfIBbG38RRAN+2ZHzsTMKvGv80vvziHGAusG84AAAAASUVORK5CYII="},"Stack Overflow中文网")]),A("dt",null,[A("a",{href:"http://c.biancheng.net/css3/list-style.html",add_date:"1670258082",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACgUlEQVQ4jV2TzYtcVRDFf3XvfR89Pd1oZoYkaBKURCcaJaCLQCALEdGF/0EQNAiBQATJQnQliIuAC3HjOpu4NhDE5A8IiCFDskt0jF8ziuPHTE9/vPfuPS5et0xbq3urOFXUOads7fkjT/i8/DTJXjUUmIWZMR9Cmj6scaavYjV+J1hWfr7o3Ss7TUpzQOcgJTRLe9/mJRnyi969vp2Vhd194emEkRAOMHNOSpE42DXXKbGiBAmNRrLgwQdD0gwTwGjBMnNOsarM8oKlN87RP/MS+aHDECPj7+7bxicf02xuyLLMpgMJIDDMMMW6Mt/vc/jyZ/ROnQagGezgfKB88ihbV69Q/fSj+TwXyJAUMDMEyTDVNQcvfUDv1GkmD9f55fJHjNZuY97TeeYE1a8/47IMJJvxZXdfXBVAmowpjq1y7IsvAfjhwjn+uXmd8OgSpESqJri8wEIACaZchxnjaVLROf4sLssYr3/PcO02YWkFm9Z9nreqSHMKu6nooIRf6CKJNBoiCbTXBXs//L+BMO9p/tzCzCgOHSFbXiENB60fANUVirGdvqdZW00JK0uGd76l/vsvfK/HgXffI+xbJg2HpGpCWNmP7/dRjP/tD9AaCcycVzPYtqWzb/H4+x8CUG1uMLx3B9fpsvDcSR5efJvBN7fw3a7QTEYMDEnJQndRW1evWNrdZfnsm5RPrfLIy68BMHpwn/qP33FZmIKnBpqzsrUbxt0B1utbfvAxfHeBZmeH5rdNUdfg/byVBTf6s2OSkJm5Xh/Fhsn6A0gC57AQzLynlQf1vPPbTfraqR6fH8R0TVicXRspyczhihLX6eCKAnNOUzDC4iCma6rH5/8F5tA3xipPqRgAAAAASUVORK5CYII="},"CSS list-style(列表样式)")]),A("dt",null,[A("a",{href:"https://www.w3school.com.cn/",add_date:"1666452519",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC2UlEQVQ4jZVTTWhUZxQ99/u+95JJJtFM6iiDUJDUn5nJvIAbF2bhKFZSFZE2gnanoBvdqejCjLWtBEEFERdtF1UQmkUhWMTWjJHuuhEyTYI/gyUixaIhZuL8vPe+d68LHVqjm97NPZcL5x4u5wCLagTQTTz2Se+qYiY3eifr7f7QHgCoCQqAKgACQG6kUm3xrmWHiehkh9GdvgjqUTRiAx769OGf95tEg0BEWFTjvX27CHQ26Thr/wn8v6xIgYAdH7nu53PWWgh/XW/ULgyUy5V3FNxanfHcFuerFqKdgYgvLN/kpybOFAC3AATjWW+7Ag13O0561tpyg6MT3VPOqAaAsXTvPtcxvy3VZs2CjWaqQX3jtvvTPxez3qH0itTonkSyunm6dH1AElclJq8AbOty3C/nEramACAI6jdE+MB8FD5uN/rjNid2eSyTu7fEmCtGqeVaIw4AeD4d5CdLZy3zyVYi1ko6FAAMlMuVTZOlH4Kg0V+Noktxo/NElHgZ2svz1jYsaGJk5crYIBAIoAQIBUIsEqn/PnDrgwd/b56cOFIN7YbKK+oD+MkSY1o18GsqsezR7WzfZwSwIhiASCkFAwBfAPpg1juvIM9+nywN56dLfwBAMZPz6swzJLjKggMa8j2AlBKEzaMKALa86YcYNDgEyDhgAJDycXzm6UIuPzVxqsb2WofWK2729LgCBAQIM0P9y0SzmrBAgGwCrAAUQ62SSsX7b2dzhU7tHJtne2ugXPYZqoMAUURaAcB6rIcA1bg2/cWs99PNnkyaAG6YWKZV0S9tygz5wj9y4O+/m80ddTR9WxdWIlRpGonG030eKznTZZzt85G1zHJaapXv0N6+PALmNMxaDZxLuo73IgzLDeYT3VOl0fesXMzkdmilhpOOu+5Z6D9ki1OiZGfSbdk7G4YMktONevXie1YWgN4O74Sp05hOXxj1KBoJPxCmxQL+d5xfA1C1UZr6ORxyAAAAAElFTkSuQmCC"},"w3school 在线教程")]),A("dt",null,[A("a",{href:"http://alloyteam.github.io/AlloyImage/docs.html#mutiThread",add_date:"1689397111",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAADB0lEQVQ4jXWSz2tcVRTHv+e+N5lJrEmYUMgkDXHRack0ksVEi8UwRINmaqhaJdDQ2lpE0GbnQrqovCylC3fFfyJoFnUhhDYk2CoxYEXqxkbElISOb2xm3q9777nHRRJpQb/Lwzkfvt9zDvC0FAC8Mzt7fm3tzsajR2F7e7vRWl6+/f3MzJvvPtnzXyIAuH79ixsiIs45NtZJlrEwO8cs8unVa5//H8QDgIsXP7giIhLHmY6TjJPESBRr+ftxxM1m24iInDnz1vknZyAitF/Ira9v3BcRieKU09RIkhiJokxa7VTCZssmKcvNm9/+8FTmhYUFAoByuXy0XD52LNMMpTwle3AQ7fEdi2o2QxwZOnJiYGBgCACCIFDqIM/o6NgL3d3PKLaWAQFEoDwFay2SOEGmU8rSlPv6+rpOnZo4fWDg32VM16fr+5FEKQXlKUTtCHEcQ8QBAlhmUcrD2NjYSQCo1WrwgyCwAAojI5XnAcDPeYoANP5qAgJ4vgdrLIwxYGaKohiHD/cfBUCTk5NWAcCFC+9/MvHySyfiOGUC1MOH22BmkCJoraGNgTYG1rIXhiFPvjI18Xp95goAqCAICnNzc+8xA84JbW7+AQGgFMEYDWYLaw2MMcgyDa0Nhc0Q9ek3LgGAGh4eHiqVBgY9D3iw+Ts5ATxP7Vt2sJbBlmGMRaYN2Dnq7OxCb29xeHFxseRHUaS3tv7cvXP3uwfV8RdHDx16FlpbECmIuD0IOzAzRBycc25t9dYvvp/rJnjabzQaW/Pz86+WSqWur5e+WVWkOlmcEIGsdY7ZEQASiPh+jnZ2dlqfXbs6F4YhB0HQPDjj/cuXP7w0ODjUmaSp3v8hVywWVU9PD1nLzrFIEkW6v3+w9+2zs+cA/Fqr1ZRfqVQEAHbbrXw+34FisejlC3nyPY9u31r+jYi86vjJ53ZbLcnlcp4IkMRxHgBWVlZAIkJEJACKU1OvfVytjlc7Ojq8e/d++nlp6asbhULBr9dPf3T8+EglyRK7sf7j3dXVlS8B7IoI/QP5zrTVsKidRQAAAABJRU5ErkJggg=="},"AlloyImageWEB图像处理")]),A("dt",null,[A("a",{href:"https://www.axios-http.cn/",add_date:"1689516031",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA3klEQVQ4jWP4//8/0////2v/////4j/x4AVUDxMDlEEuqGX8////CwYGBnEGPODnt/8M7FyM2KReMhHSvKLvE8ONsz9xSYuz4JJ5dv8Pw8VDPxjevfyLz3wGJmyCFw79YKiPeM3w/MEfvJpxGiAswczgn8ZLUDNOA2TVWBlEpZnJN4AUQJQB10/9Yji3/wcDw38yDTi58zuDqAwzAwOWpIAzGmFA04SNIbaSn4FXALtdeA2QVGBhcAzlxmsBzqT88/t/BnZOrMkXGbxkYmBgmIpNhgjNDAwMDFMZKM3OAHQ+6ajC+7BRAAAAAElFTkSuQmCC"},"Axios 中文文档 | Axios 中文网 | Axios 是一个基于 promise 的网络请求库,可以用于浏览器和 node.js")]),A("dt",null,[A("a",{href:"https://1024code.com/~",add_date:"1690335590",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACyklEQVQ4jY2TS2hTURCG/zn3xtwkN03UqkErpEVdVEV8LAQRWrH2YXxAMQtBrIp0pRuxIFQI+MAKtlgQq9aK0J0ulGKDLaLoSrGgom4UxMRiNNoY87hJ7s0ZF0ka8AEOwxnOwBlmvv8M8D/GoH/l1Mq998KdDpfu7Szk8tEfieTgQCg4A+bSQyJui13fKTR1BxdlJD2TGnpCR+MASlV6+8e2aZrrPjND05xIpX/cE4mnu980NvKtYLAYSI/2CW1Oj5UypkjQChDFJJmbwu6DcQEAgpWAEIJzubTxM/nd4qLVYmjrFt8KBosdP0f7FIfWk4slusbn7t+QisRXgtnHBroBQKB8MgAioRJBYQCNk85MW/bmcbvb3ZP9+HnfRF33zSZ+qD5efSwqi8p7hqO+WqAChZnLcDixSlelab1OT8eCkw1HRtvfDdofUbO1LXrtkK2mdq1QPbdnITJLAjFARGAAgIz5v7kmPIfCALCer9jC1J1viVzvctb5h/NJ60x4XkcYAJUYCFHhCVDZc6YEh0T7u0H7FHWbLZGrXc7FdTcKGetE2Nvai1BIAOBZGX83DQAoJMNAfmvk2gHnkqUjVkYMSK59AAAIhXiWgZQSKPcOLnnGZVcAoDU6vFev84+YaT4pv7j7NbvrWWDm/XkADC6PQCQYTAAzV0ZwZi0T3KSqDu9gIWldGve0nw4v3/jJiBuHbW7f8e0zye0gcFUFAoOIShEMAwBWELOuS8M2XeruoXp/6ZphK4kpmMqeqoyyhJBZWswoAlCS3qIAXTUVcp1yLGo42zr96gCo2dryIrpEsTmWEehDtYDCY1JK0jTdUeOZr5KiTr64+/ITmMVY7eYzlkHnnAvrR3YlMs91/4K3DHwrOOXl2V2oLJNDn9tp5XJRI2tePHcikACYyl+UA7H4DrK7d0FyJG8mhyZ8vq//UvBPq2zlX3K/AOyAQxZ4+ZcFAAAAAElFTkSuQmCC"},"工作台 - 1024Code")]),A("dt",null,[A("a",{href:"https://1024code.com/codecubes/7m4wiwi",add_date:"1690623404",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACyklEQVQ4jY2TS2hTURCG/zn3xtwkN03UqkErpEVdVEV8LAQRWrH2YXxAMQtBrIp0pRuxIFQI+MAKtlgQq9aK0J0ulGKDLaLoSrGgom4UxMRiNNoY87hJ7s0ZF0ka8AEOwxnOwBlmvv8M8D/GoH/l1Mq998KdDpfu7Szk8tEfieTgQCg4A+bSQyJui13fKTR1BxdlJD2TGnpCR+MASlV6+8e2aZrrPjND05xIpX/cE4mnu980NvKtYLAYSI/2CW1Oj5UypkjQChDFJJmbwu6DcQEAgpWAEIJzubTxM/nd4qLVYmjrFt8KBosdP0f7FIfWk4slusbn7t+QisRXgtnHBroBQKB8MgAioRJBYQCNk85MW/bmcbvb3ZP9+HnfRF33zSZ+qD5efSwqi8p7hqO+WqAChZnLcDixSlelab1OT8eCkw1HRtvfDdofUbO1LXrtkK2mdq1QPbdnITJLAjFARGAAgIz5v7kmPIfCALCer9jC1J1viVzvctb5h/NJ60x4XkcYAJUYCFHhCVDZc6YEh0T7u0H7FHWbLZGrXc7FdTcKGetE2Nvai1BIAOBZGX83DQAoJMNAfmvk2gHnkqUjVkYMSK59AAAIhXiWgZQSKPcOLnnGZVcAoDU6vFev84+YaT4pv7j7NbvrWWDm/XkADC6PQCQYTAAzV0ZwZi0T3KSqDu9gIWldGve0nw4v3/jJiBuHbW7f8e0zye0gcFUFAoOIShEMAwBWELOuS8M2XeruoXp/6ZphK4kpmMqeqoyyhJBZWswoAlCS3qIAXTUVcp1yLGo42zr96gCo2dryIrpEsTmWEehDtYDCY1JK0jTdUeOZr5KiTr64+/ITmMVY7eYzlkHnnAvrR3YlMs91/4K3DHwrOOXl2V2oLJNDn9tp5XJRI2tePHcikACYyl+UA7H4DrK7d0FyJG8mhyZ8vq//UvBPq2zlX3K/AOyAQxZ4+ZcFAAAAAElFTkSuQmCC"},"接口管理平台 - 1024Code")]),A("dt",null,[A("a",{href:"https://docschina.org/",add_date:"1691408327",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACQ0lEQVQ4jaWTu2tUYRDFf/M99u69axIjqJCoRVpBhFRWRrGwsRGChY2l2gj+AT7KiFZaKAgWYqOFoCCIrzSCCKKIpS8kgUhQMZJs9u6dGSuzwU48zcwwcOZwhgP/CfnT+NTURuowQssEaym6rIi0yblE1cg50xcnu6D6XWZn5wASgIOQ2nfJYQrTBZKMIsMFZg0iiSjgtkQhioRhxD74tm27ZW6umwZawjhm1zF9RojHMb+B+CGMCfBlxJ/j8oRGtyLMMDZWsY4gg0Swh7iM0+hN6voNZXscuI81ixTFDGpCr3+VMnf/3A3rvGjjrqS0Qk5jFHkn2DwxHAFGqHvXMP9AlU4BbWL0AcHkZMQtEOIuRCoMpfYv5PZhoMCki8oCaA+z1zhLrKa4ZiKqLVwzMZ8jBnC7RdWaJsguXIwcl0EWaOUtqCZMhylVBgpidJCE+yf6zWWa1YvE+J66/oyEtwi36a9cotF5TLfjspnULwYEVVUhYYSmfwWvH5AqZaWZAblDv/8FZILc2SmPHpxAfQFB6HXWedA0AbzB5SceDGuGyDKO6Sop7sXsK6rvfP/Bs5TlAcxX6IQ4ICiKEvMuOZ0hpCM8/fgK8ZOkeAxYorEX5OUlXB9T9+4hBMx03RtbbWIYopV3gB9l38QFYjpAUW5H7Ss5nCeOzkPcivCDnDs0TWstC75nT0m54SjCJqCDyxC44RIIoUIYxekSZBiXjOt3fv04LS9ffvv3+P2FQRqnpyOLi2szs5sd7qyt/6oiYAC/ATiP/oiaLa7fAAAAAElFTkSuQmCC"},"印记中文 - 深入挖掘国外前端新领域,为中国 Web 前端开发人员提供优质文档")]),A("dt",null,[A("a",{href:"https://swiper.com.cn/",add_date:"1691416083",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAB+klEQVQ4ja2TP2hTURjFf/fevPfytEbQRQcJdhCtFUNtQRE0FCKIOIgEHXQKIVJdSh0cxEXBzVLB2iDiYOkSQehgoYgNguCS0i6FisRNkYDWNuZfc991aBNfHu0g+E33zzmH8917PkGg0rFyzCiV8YyJC+gGMFCUQuSF1tlnC10LfrxoLZI9xo6Eq6PADUAGhTfLAyZWa+5wbkk02gLJHmPvCldnBAy2kGcvK/oTCtuB5YLH7GST8srGnYF3azX3fG5JNCRAJFwd9ZMvDYW4etvi2xfDcsFj4Jzi3pRDZE/b9uCmW0Q6Vo55UhZatpUFj+fCvBpbZy6nAXBc6E8oPkzrjnak552QRqmMv2fHBccV/CyZNrJeJUgGkEapjPSMiftPK6vwaV5zZdgiekQESZ0vakxctr7KXxN3GpS+Gu6+dLj1yN5WSEC3SPVV6oC9FeD4GcnFtEX0sODNiyavx5tBSEMaKG5ncfG9x4PrdXJj61xIWfSe6oyHgaKUQuSDRLercz87qVkpGQ72dgpIIfJSaJ1lI2EAHDsteTgdZl/0b9979wt27oYf342f7wmtswIg1Vd5AgwBhGwYeWpz4JDk44xGN2Egofj9y3D/Wp1GrS0w/nx+x80toxyyIJ5UHD2pUCH4vOjxdqpJZa3dezvK/2eY/PWv4/wHygjIdI8wyCcAAAAASUVORK5CYII="},"Swiper中文网-轮播图幻灯片js插件,H5页面前端开发")]),A("dt",null,[A("a",{href:"https://roadmap.sh/",add_date:"1691594658",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA1ElEQVQ4ja2SsYqEMBiEv9yeJ1qLPkV8gX23beRqn0JrO8lrWIl9EExju2myjeTYQwN6N/BDMj8zDJMAPIAn4E7OE3iI7fDFNVixuV3GR2gZRdF5AyklbduyLAvWWqZpIsuyoIkvJkkSt66r+w0pZajMn0tRFF7U970ry9KlaRp8jc+jWOM4MgzD+Q6OIIS4blBVFdZauq7b3e92UNe157XWns/z/K2DtwTGGJRSaK2Z59nzTdPgnEMphTHmOEFo4jje5f/lK9s/6O1tM7kDt7Ni4PsFCwyqb3RdVmgAAAAASUVORK5CYII="},"Developer Roadmaps - roadmap.sh")]),A("dt",null,[A("a",{href:"https://www.denojs.cn/",add_date:"1691597543",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABiUlEQVQ4jX2TsariUBCGPzcaQiCNgqSJIEYQrIUFwdbewn0BW32K9NeFfYWtrF2rVNoKNm5jF1DETq6N6DmzxSZBE70/THEOMx/MzD+QVxv4ALbAZxzb+K/9Ij+VCfwCFCBvQsU55qvi8IvCbIQJxIgBP4EfCa1Wq+F5Hs1mk0qlgm3bXC4XtNZJSh0oA3+SnhUg5XJZhsOhnM9nedT9fpcgCMR13Ww7beLhiGVZMh6PnwqPx6MMBgNZLpciIrJYLKRarT5CPognLI1GQ7JarVYCSBAE6V+/338EbInXJJ1OJwcQEdnv96K1Tt/r9Vocx0kAnynA9/2XgKy01lKv11PANyACOBwOjEajr3wCQKFQwHXd5BkZQBP4frvd2O12zGYzlFJYloVSCsdxcpDT6UQYhgC/n9aYhGEY4nme+L4vm80m10YURWKaZrpG+G/Pl66bTCY5wHQ6lbgm1Vsrd7vdXHGxWEytnIXkjqnVasn1epX5fC69Xu/tMT3q6ZxLpdLFtu2/vDnnf8Jaa61zEGOVAAAAAElFTkSuQmCC"},"Deno - 一个 安全的 JavaScript 和 TypeScript 运行时环境 | Deno 中文文档 | Deno 中文网")]),A("dt",null,[A("a",{href:"https://www.typingstudy.com/lesson/1",add_date:"1692523389",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACZElEQVQ4jX2TvWtlVRTFf2vfc/O+kjgimVHRqIwwIDI2CqIiiGJjoWghVmJp6T8g+IWjKezNCBZ2goXFFPbKJFgMDOKAigojBCIPQp7vJfe9u5fFzTMKMgf2OXBgr7POXmtp8/L4UkR5y9kWsASyjST+bxkMsqJaZC4+0X3b41YRYacDZOBWAEsQKeTMDImw0wLNWjzPrtm+RTPITktEdBdWCfnjpwZ64YEVjhZJHR0T24SgCKruVAkt2boE6ODYzLPVyw+ucHOS7B0kG2swrAMJJo2ZLoxOWWitFv0ilaPWvHh+hUfPFeYJT95d+OiZEX/Oki9/amhaeHaz8PhdNa27EZYQV35tuLa/oCxa89idhVcv9BjW4uJG4cLthWv7c7640bC5Fnz2/CqLhMOm+06vEjfGLbt7C8poRXy4O2Pr+xlXXzvD5z8c8/7OlHOjoE04OwgCeOnrQ779Y85qLQwMiljvBQUEJGnRjd6kDTYhM5mbXhHvPjFkb5L0itifJtvXj/j9MIml5nGie0iUEEL0KvHLQcul3RmV4N714ExPvPFwjzcv9pnOkyKJzAQJSUwX5uA4GRTRL6JN887VGe/tzACYNuabV9bZGEb3GIAk0uavuXn9oR6PbFTcnCQf7Mw4f1uw/dygMxemaeHpe2q++vmYtAmDQ9Ck/PZ3U34ct5wdBP3qRHDEoIhRLYZF3NEXV36b+9PrRwzqyrr/8thLe85aTlwNIegXSMNscWplSbatusK9kIpNLsM0qpCr0xClO6DV+j9xEJIhlJkZFlsoGpDbf4Ts6t/7shKclq1oLLb+BtWnNXEMw9kuAAAAAElFTkSuQmCC"},"Touch Typing Practice Online")]),A("dt",null,[A("a",{href:"https://vee-validate.logaretm.com/v3/",add_date:"1692714289",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACJElEQVQ4jcWSO0jVYRjGf+/3Hf//o8eOTY1hLUFDFASSSxhRUJuSSykiuERLkTQ0dMKGoAuCuRRCF7WLYGsRdJEISgwME9KwIZo8mqL+z+3/fW+DeSmkhoae7YXned7bA/8IQTOGyWhHUBIDsRYLgFqFIogV5vJCwltcdoq6gUVenKwkirbhKTFye8JwMYN19jzJ4IPGZcMJY0cT4scTasZs7N7ZdDBiSNwjTAcAJlfslnTFKNBOBm/I4J2WLmvRRRqYClXm1etn9XzDaUqUUFx8hdqeWZ61HSa0x1nIL5ArXQMwZDDsvDqm+bhLUgHi40+Ohb1OC3twflijwhs3nb3Po2PlUvIXNLRWi66Txr5xFDFkUEDiCned7OIXSYW1Nk7WU9M943zc7gznaBxwbEo3S2Wwj9lokkSpc+WCAoAiCGqHz7RKZdjjv0dTvpCroe5mFoCnTVvEBW9JJqp1LtdC/d07gABqVpxQxFV87dWZpddmc/l2o/b06q9ie1aqktU6n3/J1o/96LKYny6sn4JXpw7YpH2ihTjyS243+ZyVVPBenA99lDtEQ//QKhcw6xOBIuy/8Vyj/ENJh1XG+A7KTIekgrRfKjygoX8I1sQAiY3S5Z3vMNmlo6AnxBh0enEWz6WNuOaXSlAyGA7emvBx3IUBRFF1nTT2fiaDWdl9TbIxhMctVWIYw/tYQ7eLI30Lv4v/hGXjwaY2Bptb/9LsP+MH6sMCje6IGeYAAAAASUVORK5CYII="},"VeeValidate")]),A("dt",null,[A("a",{href:"https://cn.sli.dev/",add_date:"1693112604",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC10lEQVQ4jWWTz4uWdRTFz/0+z7zvO82MOVoEVougYHJ2LVLUxopoIUX/QGAgtqhFuqu0eCNqFRmFVBtTqLBNtCjaFbYSmWaQQCOlSDJncKaa9/fzfO+5p8VoDXVWd/O5n3sXx7AxksFMAHDol8XN465tdUT8qdHVEzN7ugCAdjuh3Y6biOE/efXy/COp2To47A8eomvaQ2LY9UB5Zlj58RM7dsxvFNlG82tXzr9J4aWQYdgbIdcZJCAkWKOFuva6rvKRU3t2vXWTMQgGg175efGNxuTEy2vLK04i3FUwYAzBsxQhZ6AsJqaKYWfwwunHdr8LyQwAjvx0bi41mmcG3UEmlSJgOYdFQJlhpCkoOCOIBKDwfuUPfPXE3osJACIVz7uS6BIJcxcYUHaBhOhCdsEdKVc5mBqthOIgAJTPzs/fmivfRTrcVZBCpowueAgkwBDoAoUgU/JhJc+xFw+3y7I5nu50cnOu3JwyUiIBp4xx43QHIgJgWBDwLMuubbNP79xUihGeA+4CKTHWX8gUgmGZBlKofUzd3JBFRpmoxNpuv+XXVC6vdn/bOtFaoRUT7hkeANdNyoQFoaN3fYBx69rC2iwWOvfEpfo+uzqavnB+/+MrBgAHzp79KJWNZwZ/deoIK90FV1i4NPIxHLv7ddw//kNClIAb/sAdscSpi9vT5aMJAKq6fq+qcqZSyozwCDBDctpa3cS51RlgkKPqNqQRYkvvirZPXZgNcF8CZB/PzS1Ulb+YxifL7CicyE5FJsIia7F3b6BfW6PqyQZ9RyuKuO4n05e/P5cAEyT77NHdbw86vcM21uxbc7IZNlbWKooSXvzImXKl2mI26AhNb6A//CR9s3rA2vC0Xqn1JV/sm3tn2BvtrEf1MQrfe60l82rpWjX93fJw8hI2VUX065N4v7/f2gj9r4ztdvp3/rZ88NTnW5/88NPbAECn8ZS+Tsf/af4N+G+LtRX037SqBQAAAABJRU5ErkJggg=="},"Slidev")]),A("dt",null,[A("a",{href:"https://astro.build/",add_date:"1693112930",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACGklEQVQ4jX1Sz0tUURT+vnPfe46N1Oj4dHCQilahgzghiNgPiFZCCyWmZf0V0f8Q1F8QRbSqXUIgtBGqRVG5rNUEGuWojL5xUmfeOS2cJ88hPXDhnnO++53vnHOJ4yYANJ8v3nSBu6+mCgBCuritS5u1teddeLA7AABhYXTZibtqpocgEqpxbf336iUAUXfFY2R9/cPjBKdV47aqtQ6PHpASDg6OzHWw7kSC3sCriNCHGUj4IvQNEJImTiodrP6PQAH4oCyomlGcg9rrOLbHQnqqaiBv9Q4MjAKwbgIBgHy+eI3gZTONAVCpL5qNv4/MTM3MSMn2uZ75dBtpBXAeK0IaSapqfb8ZfWg2N34BtkIRAcwortJpN04TKICzAOfUrE0RAe1dFEUbh2lbJEkza1FYzoXFiUS5JFLCQnHeeW6EhE+AiO1NoixuxYumCpCBiNfjC+8lg2dHjoSF0adCnjOzpfbB/retrfUvAPaBOwK8soGhoWlBcFGcXDe189C9hVqt1kiKeNkwLCTO2NjUFIBMej6l0mx/cs/lcjlgOHu0+y7LlCZnvge+n2212++F8sNgVwAr/9xcu1CvVneQ+gde6mFCtkfyo5pNep67EQSZ2+IcGtHO23q1GiH1B7rXaElyezd6+PXT8ngcx08A2G60/XK1+uduZ3XHCE610uTMg4ny7OfTMN4JcQGgjfrOs+BMsNLxjxSm7R8/ONq4/2CIPgAAAABJRU5ErkJggg=="},"Astro")]),A("dt",null,[A("a",{href:"https://bun.sh/",add_date:"1693113187",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAADEklEQVQ4jX1TTWxUZRQ993vTDraFNzBt2lo1dKgh/pCIERegq8aoSUkjsSRVosbIyMpuMJEF5sVoBE10YzS4wEQNCfUngWhiLcS0/IwtYFtbbITOtOW1A0Nl+qadDvPmvfcdF51ZYIh3d+895yQ39xzgzhIAiqRR7tcBAElV3gn+p1QZCAA7mpubf+jevSv94KbWfgAdq0K9BgB1N7LQshSAZgCfx/e+Howl+ngzNcQ/z53g/p69NOvqegFsuTuZFACvPPfsM/P9P3/P5esTOj054M1NntWZqYTvLkwGI8On2LWrwwFw8I5TyuQe6+A7ZDFDnUuW0ld/DzLJYebS4zprj3JxblxzxQ4YLPDLzw7TMIzj8Xi8qiLUvu/NNzS5Qt9J+VyZI5kl87b2nGnt52Y0SzdIL0M3mwrIHD859C4BHCUp0tjUdPLSuV87Wlrqg9Jt1xgZuyyjYxPs7topNTW1gBADZy7Qtufkpd2dVIZCKBTSjz7ZLpf/uvIaul584Za/bGvmbT2fuqhffnWPrjVN/njsCMkl/v3Hb9zZ2UmprtK/nPxG69vzpP5Hv/1WXANIqColVULSCzxpEBO1NwrYvvkhbHviceSXsrh34wNoXFON5x/ehi2RmHhFFxADYcMQAI+FEkMXxhwntyMSjWjqAEfe/1C5dZrWgQPiz17H8j0hWB9/xJZwoyAc0CWB5SW5ODruASip6Vn766++PS6qJqJKJlCMGTRipjS2beTVqSlGGqKsiUXFixksNCmGzXUyeHrA6x88r0QwAsuylGmu/e5E71GSDpm3tbuY0n5uhjNXhnUhm9T+0mz5GxmOD/7kt95/nwuAhmF0VuzQIMCp/T1xXptMBMzbJG+tCgYLZG6GTvKS/8Vhq9CwYb0HgAA+qIRHyoMwgEPm2tp97U9vX/PI5jbUR9e7i04unJy+htNnh5DO3AQARyn1ntb6UwBSsaSsmpJKRLYC6AbwFIAN5UT6ACaUUmeqq/WxYhHTFc5/46kIEOxVQJdsbW2tq2+LUqmo39fXVxARVnAANAD8C2h5su6t06jwAAAAAElFTkSuQmCC"},"Bun — A fast all-in-one JavaScript runtime")]),A("dt",null,[A("a",{href:"https://codepip.com/",add_date:"1693130879",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC10lEQVQ4jVWT32vWdRTHX+fzfTbmiIaNiQxB2OZ+UEqM1sQfeyIvykj7cbMg+wNieFEXMaoLsx9X3Up1E8UmNILqogSDNLZAMnIwW6mthKQ1UWsLcs/z/ZwfXTzzod5wOAcOr8PhDW9hQ/dOTvVJKsbd7GWxaA9VMG+Ub3S12+H+VjKbWZx+cQlAAIYmp8dTKk4I0un1GqFGWctgTooGXAHEA6EgcnkrVCd++vilGdk5ebLHwi9ISEeUJa5G8uDQSC97h7ZxdflP5i8vc37xNzwbEo64EDmvqcpw0TV6eCJJy8Go1fCsFB68/uwYRw+PMLCtkwTc37uVjvZWLl7+HS8NMSVRtInmm0XXg4+fjpwrd+A3nqvy1N5BAMpsXFtZpVSlt/seNre3cfHSNTwb5AyqexLqbVFmkhnHj1R5cs/gHV9pbSlorSRSQFlX9g/3cOSxB0hqRFZQb0tkpX67zqGRHTy9b6gJ31z9h+Pvneb9T86x9vc6CSjrmepIH6M7t5PX65AziayIGg/t2s5/9c7MN7z70Synzixw6uwCRRKIIDzY1d+NZCOyUYlskBWJ+N8B3KHUxmzehPEAC8gKSaiQlSiVs+d/5pHdA03++Wf2QzaWV/7iYPU+NCtEIAQLC1cbHqRE0TV44FgRwZWlP+juupuh3q0AtG9qpTraz5bNd1Ekwd1pbakwO7fIF59/R3JHzEmUWhN1vFRefftTPvtyvvlFmZWyVNwa8NzcIlMfnsFLRdRBtVZ09YyJUDwsmrEy8/Xcj3Rv6WCov5uiSKxcX0WA2dkfmPrgK6zMJHPEhFB7TXYcONZTWL6Qgo7QjGUjRfDEo8Ps2z3AL7+uMP/9Et+eu4Rv7MQC17zmZsONMI1NjqNxQpxOz2UjTOt18CBFgBqVJIg7YglXvRWhE1eun5yRZpyHX+jzFOOR7RUsNmHWjHOYIRaE2XqKeDNpzCzemF4C+BdMSLswECgafAAAAABJRU5ErkJggg=="},"Codepip | Learn to code by playing games")]),A("dt",null,[A("a",{href:"https://www.yuque.com/baiyueguang-rfnbu/tr4d0i",add_date:"1693230691",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC+0lEQVQ4jV3TT2sdZRzF8XOeZ2ZyZ27S2KQmtU0qrUpXZqMgJIiYhe+gi267KW5U3GXXneCqFSQI0o0LQd2ICi3U+Kc2qFgQN7ooiub2kjS2yc3c3Dszzzy/46LVhS/gszrfQwCQRJICgJHCikEXWtlqiM0CAKQ+6yV0Gw68WjC99X8DANjSVl4pXC5V10O12m1L7YSB7YR9242lhmpVqq4rhctb2sr/g5JcX/2itOqaJN0Lg7jXHoYyVvGgraxsKxvGJj4Iw7Dd7EdJKq261le/kOQIAINYXTniJl7bDoO66zvpp/e+5e3yN0z6LjLnMZNOY3l6Cac68xpbE05mRydKG78z7YvXOdDoBZq72SgwWnQpU27u/4K3//gAnp4kISd10wJvLpzH0uTTGqOxKZeLzr/ozHjRuyQ1iwDAkdVY7MzjeDrLjIkK19EUCrZti3d7H+Pm/s+kAdExDRYuOgCrldUC6BpFpC7Bbr3H+82+YGKIgUGtFMWcHcylR1FZ40ZxLCNeTlqFEzIgSgSJUQz4sHdDVRNYJB50Bois2ehUNo/Hkkk01pItAa+TDiAAwWDImOGT/mf4O/yOhTxDxgAzIJrBy+GvageVNSCBKINJcAmTvnMOuc/19YMbuFPdxmIxxSKJmk1bHPEBEaZUnr1qG3dGPaT0onfw3t91hrgx4yf548F3tnnwJXJfIEAyIyOpbmJKYTRI0SLuVrsQYFma0aSvXF533/9p9H34Zvg5Oj4XIAACHQSRAOgp6VG0gsnRwYIFmnvPFQU3vzj8aH16diqRIYAQQUACKAlQaw+xg9fxbCbMTBxLAK0vZtM/OFyCY3u4Vh/G691jxYQMgBBByhNozSGaV9kO22eKBaw88fzEQTu4fj9t1i79mzIAnNs8l589c/atqtKrtcWsHB4itI22R8QY5LNzZ3D+xCvNU91T61u9X9eWF5fHwMMNAYEgBABv/Lm2Elq70NTV6l7pF4RJLM2d7r30+HMbp7vzV5/k/K1HLyRI/QN7F9CfB8suEwAAAABJRU5ErkJggg=="},"前端知识点汇总")]),A("dt",null,[A("a",{href:"https://www.lingtiku.com/",add_date:"1693232832",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACvUlEQVQ4jXWTXUhTYRjH/+85O9PtNM+cS+dXOnR+NC3JNBUi8MYKpCsvvDASKrCLLCK8qAsJQi9D6KaLbgwKzaIUKawgyyxqllqiS+amR9vyY3NzX7pz3i50ahOfu5f3+f358/yfh8V+1VXHAmbmSHNjuaKivN+XW9gPi2UVANmXAQC0tjI0pqmss6Op8O6di0JTU2KsQKwaAUABEN3VlkqBV+SAEG14JTyy4vHNJ2drmdn29pmtXgoAilhYfelK2YmSgg6dni8TlzxsRJKRUqSJzC0sf5r85bgeBaPF7ob5C5eLqyrNb4rNRhPDUTkxQSUZkjQyfyCO0acmGjklV7+oz3ktj35zRt3vdgBTobEty5Sqy9cIwaPGImWWoCWEEIjeVYwtiSGGMAnOFU+7CNTsHV7d+fRTD+753opWiUZLppTK8vZzyGmTzPfb1nCuMTOKMduDVCkOHU42xFenm8gPUZQ+2WwSJZApIfJXu12yOByRqhQjY05JU0K5lrGVFNkRWPOHeU5JAZDWl7249qSLbP2RW89fkJvdzwgA8AolQSgUic5OAUAGALjE38MztuX5oNdw+/TZjUWfjyObDtFSUyMLyriN+fAa+9lu98DuniabadCdFObmwp78gpWx1b+1ebkZnMmgJ95wUHaHA1SdEM9MRnxcc28Pxn/ab9C+px9jU6AA4BscevQuRLO/WG1JeoGvV/Px2nUqw+31uxc8q4/j3bKnYND1cGKfRQJKS7lkjWo8sBhaFw7qzqQb9IJMKfkTYdxk1v9KoVHFTdRwLCb2X2WgtlZdWl31vepkSV4Zr5NULCe997mUAx8sk1NDo8fQ3R3c3f6/AwoC0hvwVxwfGBoet24YMys5BZswbJvtCwbC07HwXgGyeUjOBfe0Z0ocsTqcQcKSpJDL36MV+DQ0NPDo7PTvRv4BYzku2nCZ5AQAAAAASUVORK5CYII="},"灵题库-前端题库")]),A("dt",null,[A("a",{href:"http://www.webqdkf.com/",add_date:"1693638193",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACiklEQVQ4jX2TzUtUYRTGn3PvHWecMcSuxYzmZxIRYhHRphaSQUFfi4ISCULcBC0iomUEBtE/0KZFO4OgRWuDalH0RaFEjWWlpqOiXp07jPfO3Pu+Twtn4rqwZ/McOM85vPD+jqAikoaIaJIpAGcBnADQCkAA5AA8B/BMRBySIiJEZFgqfprkGLfWD5ID0RmQNCp+MxIMSQYVr9ZBpD8cnUUxDE8pkkrrsKxUoLTWSlMHSutQa10OFUOtFUlSbZgXhleqL6ijV5whSVYyW+nupywfT/4JNzasF0imLRTyF0dW/RbHXVA9jfXG099zuNS5C45fknmvzMVyGV6g5FjG5pOZRRRDZUwUiuEeu6Guvzl+2UIYnP+5XuadD195bW8bHmZn+G2taPRlbN4en5STGRtZx8WXpVVJ18Z1OhETSwSDr8d55kJvn4FEqvFcUyO0VxLTNORq924ZnV7g4cwOsVO18DQwtK8TQ91dXPTL4oTEsh8YDTUxWGKmLNQk/H21Ivd72nFkexKWZaHpQAdafRf3muuxTAHXHEAC3GqxMbXmMm7V4tH+DsSpaMGUBXdlhXtnv9NZStCKWTyaTBpvXo0zKSK97W1wCwV6zpwc6uzUauKzdHV1adNLiG5v9g0AL5ZWViQ7OyfTuRzml5Zl7NcUV/N5mV/LMzs7x9TOtISGScfzxWzYjtG372kkU2Ka5jsh2VgsFj8WCoWWeDxOpZQhIvB9HyIiAJDJZOi6LpRSKJVKOhaLiYjkbds+WGVhoPLVW4GgI3U1c2MTykqp4X9prQO9AWGota7iXP63QakHm1COHNMgyan/wJgjeR0RSfQiRYQk0wD6ARwHsK3S9gC8BDAiItPRc/4LEylRlkPMMYMAAAAASUVORK5CYII="},"【web前端开发】公号平台官网平台 | 一个专注于web前端开发领域技术学习与研究的平台")]),A("dt",null,[A("a",{href:"https://i1ktozbsp99.feishu.cn/drive/home/",add_date:"1693907736",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACjElEQVQ4jW2TT2jcdRDFP29+v+xumthk0wQhQmlEkLpBi+LFiiDixVMPeqpQPMQePAT0VhEvFTyooIcKgl5EULx58mRFUW8JRFHxL5aQFjWbtGn+bHe/83rYGHJw4J3mzQxvZh62z9q+antguxRnlswcZPowbOc+yj53zfazsv0vcMx2CgKJ/wuDDzK2kQLoyrYNKdB2Fr7a7tJUKBCJsU0rgtOjbSyMhA56ErVtS1JJq6nw8u51vfz3LwaJ0JCWA96c7fjFqTn1SzpAEQHYykxLIo3tVBXBB91VFtZWQIEjcClMqObHk495NpoHSjIT2c7dWzDasGy5n0WNquLS+l+8sPY9VYxgiWwMeEVzPLUx4V6kOnPHPN0eRbZ9ecUs/QEvnRnup1egVQfnV3/gvY0/qZpNGp92aV7eInaS7o09Xlt8hAsLDxO2qSvx9mfw6kew0xOtWvRL8sbsSR6cnMbvXmP8kw2qXdPP5N65Ns8/PY9tQhIhM3FEfPw1nHsLvvsZRioY3NjhxKVNGl/cxFMj1M2KrZs9zj8zz3R7lEFJahuMsM3kmPlpVSy8Y544JVaWl/jm218ZHws82GNju88DnRM8d6ZDyaSKoJZMSTktAR5roUzx+ZIZqU4z/+h97G1eYb27yfG72rx/sePJo01lDs9f26I9hkYq3N1CR1qijqGkkhW3ygz9O2a4/x54/Rw8dDcqaYcQGJXijICl39CHX+Ll39H1bZxGjdrcOQlPnjJnH5enj1olY794+NOHXtkB0N2Sr/yDen3THhfHZ6DVGNJL4ioO24OQ7XVgKpNEKPSfZ7TPgUGBKmQdzMUSYbMewCJwNQKHsC2n5UzIFLaoKx02qSUMXJNYvA0YiItHxhP5CQAAAABJRU5ErkJggg=="},"主页 - 飞书云文档")]),A("dt",null,[A("a",{href:"https://stackoverflow.com/",add_date:"1693919606",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABXklEQVQ4jbWQsUsCYRjGn/fuSu/Sk3ALmlzNtoagKRqSaHMKGkKhEOV0KWispSXPQaglAnNobOgfaCyIcgicmxO9zFPv/N5WwTs5gt7x+5739/2eDwgw/bK67HcnBQG4Ag3L0LJ/BoBFDuDzTiGUCAywDC3bNbRtANCrwxaBziRZanAGcjADwR8AX1uGesEZyFGzXwO43VsKn07GaJa5lY/GMefUAYooEvaELDnCEW9M2I1V7GdPg04hlLAM7dYqqut67ftLNwdpMB5dgRfXdVMgHIFpx9egfbwYk0eDA2LKAWJMkK6cUOhOGdkpZmoQiy29OmwFq1AKb5CgQyakAXqQJKpELn/eJzPK1JKhPhHjk4EmMzUVmU/coVLkeXff672pk155YXUsxikCJQFeYVCSgCiAV920N311b+r37FslH413S+qaV86rggfIBbG38RRAN+2ZHzsTMKvGv80vvziHGAusG84AAAAASUVORK5CYII="},"Stack Overflow - Where Developers Learn, Share, & Build Careers")]),A("dt",null,[A("a",{href:"https://gitee.com/river-ice/notes/tree/master/%E5%89%8D%E7%AB%AF/nodejs/%E5%B0%9A%E7%A1%85%E8%B0%B7",add_date:"1693927889",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAB0UlEQVQ4jY2TMYtTQRDHf7NZIrkkoHk51EDgCLnr7xsclqcifgQLk0qwsbXRDyB2CXZ21nplsLC3fsmRC0RCRC65JHqPU9+Oxduc+l4E/zDF7szO/Gf+s8I77qF0ABDan26XP5yTf65wC3SHxDES6Ammu8uXj/wB4S1T4DoAyufJ3VpjxcUSyJGCgFP01Razx3WIAEw6KGaigGKEtKkRg5hWRHA0hgKARWjjfAuG9o3ByRO1OWvrtaRmCvr1G8fXGgfnWnkBszYh5eqE2tYYCj8GJ0/Vw8XxRosXCw2lqn2CeMD2vhXyz1ZcPIRA1eYswPTBI5av34C6DAMUUEXBgGtJSBACexhh9/sURBjkb0Icg0i2C5/Ao28TqXyUiA9wIELzbIiUitkZXG34JLpjsxw3QwCMgVIxOXgSFmQE7GV71aSSJ2fKJZrzYTrtyAj0/roSMKViov26fzGU7t8BEdxydVldoGcF0wXX0vVSGUNzPkTTtUziTtRRBFzyFgipdEKparxY/lP/n/Mznb3sav9KTUMCDal0LmczhkJEcKQiBxuWz8/kt3wC7wucHtYhMgB1iAqcHqKuK04dTsmYpw3aXT9m07IP2N7XZCb/9Z1/AXAS+qNSf43uAAAAAElFTkSuQmCC"},"node尚硅谷2023学习")]),A("dt",null,[A("a",{href:"https://express.nodejs.cn/",add_date:"1694097473",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAB8ElEQVQ4jZVSS2sTURi9350IFuza/IEKlizrVoX6wJrHyhR1YdT+gCRVaB5rx7aLUO2mhqQw2dhNG5cxTcjSLgLZ2wRirZMHQ6BDCMnMvcfF1aDiJt/i8h2+c78H5xAANkvwmdiMMQ8AIgIghFC5pmlE5Lou55xzrlYQQihIAKSUnP81Sv2cJlMCAA6Acy6EyOXz0Wgsl8tLKYnIMIyvp6dEpHp/PDio1+tExAGYpnn33v3j8vHlubnP5fJDv9+2bY/nUiwaG48nmqbVarVMJnPV65VSMgDRWDyVTuN3pNLpre1tABuJhK6/tSzr5q3bjUYDgOu6rN/vLy/fabfblmWZpmlZ1peTk/DqquM4Fxf2y7W1Bysr+f19AI7jSCk9tm3/MM1AIKh5NMaIMTYej68tLEwmk/n5KzeWljaSqU/FohCCiIiIdTodfyDQ6/Usy+p2u+qtVKpCiGaz+SgcXn/1OplKTScwAM8ikWw2O70hkUi+0XUAwWDoqFgEEAyFDg+P1A0kpWy1Wo+fPF1cvO71es++nQ2HQ6Ng6LouhNja3HRd9/z8e+T5i3c7Oz6fj0kpAQwGg/e7u/H4ulEoKLj3YW80GkkpXdcFUKlWS6USgP8r/Y/kfypNUkpV++UlzjXOFVSmUl5SNE3TaFZ7/wS6Om5XlLakwAAAAABJRU5ErkJggg=="},"Express 中文网")]),A("dt",null,[A("a",{href:"https://www.yuque.com/hulaoshi-yderw/lukf8b",add_date:"1694154855",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC+0lEQVQ4jV3TT2sdZRzF8XOeZ2ZyZ27S2KQmtU0qrUpXZqMgJIiYhe+gi267KW5U3GXXneCqFSQI0o0LQd2ICi3U+Kc2qFgQN7ooiub2kjS2yc3c3Dszzzy/46LVhS/gszrfQwCQRJICgJHCikEXWtlqiM0CAKQ+6yV0Gw68WjC99X8DANjSVl4pXC5V10O12m1L7YSB7YR9242lhmpVqq4rhctb2sr/g5JcX/2itOqaJN0Lg7jXHoYyVvGgraxsKxvGJj4Iw7Dd7EdJKq261le/kOQIAINYXTniJl7bDoO66zvpp/e+5e3yN0z6LjLnMZNOY3l6Cac68xpbE05mRydKG78z7YvXOdDoBZq72SgwWnQpU27u/4K3//gAnp4kISd10wJvLpzH0uTTGqOxKZeLzr/ozHjRuyQ1iwDAkdVY7MzjeDrLjIkK19EUCrZti3d7H+Pm/s+kAdExDRYuOgCrldUC6BpFpC7Bbr3H+82+YGKIgUGtFMWcHcylR1FZ40ZxLCNeTlqFEzIgSgSJUQz4sHdDVRNYJB50Bois2ehUNo/Hkkk01pItAa+TDiAAwWDImOGT/mf4O/yOhTxDxgAzIJrBy+GvageVNSCBKINJcAmTvnMOuc/19YMbuFPdxmIxxSKJmk1bHPEBEaZUnr1qG3dGPaT0onfw3t91hrgx4yf548F3tnnwJXJfIEAyIyOpbmJKYTRI0SLuVrsQYFma0aSvXF533/9p9H34Zvg5Oj4XIAACHQSRAOgp6VG0gsnRwYIFmnvPFQU3vzj8aH16diqRIYAQQUACKAlQaw+xg9fxbCbMTBxLAK0vZtM/OFyCY3u4Vh/G691jxYQMgBBByhNozSGaV9kO22eKBaw88fzEQTu4fj9t1i79mzIAnNs8l589c/atqtKrtcWsHB4itI22R8QY5LNzZ3D+xCvNU91T61u9X9eWF5fHwMMNAYEgBABv/Lm2Elq70NTV6l7pF4RJLM2d7r30+HMbp7vzV5/k/K1HLyRI/QN7F9CfB8suEwAAAABJRU5ErkJggg=="},"21信管-JavaEE")]),A("dt",null,[A("a",{href:"https://ejs.bootcss.com/",add_date:"1694176801"},"EJS -- 嵌入式 JavaScript 模板引擎 | EJS 中文文档")]),A("dt",null,[A("a",{href:"https://mongoosejs.com/docs/models.html",add_date:"1694312974",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABrklEQVQ4jZVQPUtcQRS9d974Hsi6TYqAcUEWBLWyE7aws7HWys5KEPwF9kELEcGE/AHBwkoXFrZbWAwJCIohgRBBk7i8BcH9wn0f954U8xazBCGeZmbOnHPnzOF+ux2enxcWFrqNxtejo+7d3avZ2ZmVFT+XI6Lvp6e/63XjecWlpYlSSUW8jeXlk9VVz9ra1lY3DDWOvx0fX5fLxcXF6ubml8ND6/utm5uPOztj4+Ov5+YovLjYzeXeF4u3tRoAAI/39x+mpw+mpspra1Gn48jP+/vvJiejdpuaV1dvmT/t7QFI4ziJIgCV9fXtIOi3WgDSfl9FOo3GQaHws163mqZBPv9mfh4ixMzMUDUjIxOlUpDPq4ixlpjZ89japNczpGqDwI6OkjHsYAxErO8DICIQMTMRAWBmA2YwZ2rKwMxOPUQSEZFxXkfh6TazuSXbMxORycY4duB8Uv8DM3R6RvQ3LFQljqHqvuWiS5pKkjjGpQcgcayq9Pjw8KNScZWriIoACC8vf52dAVBVVQWQRNF1tdprNrM2/h/GDc4qH0SCqooMMQPZy194kZqI/gCGQhQB92Wt5wAAAABJRU5ErkJggg=="},"Mongoose v7.5.0: Models")]),A("dt",null,[A("a",{href:"https://yk2012.github.io/sgg_webpack5/",add_date:"1694594834"},"首页 | 尚硅谷 Web 前端之 Webpack5 教程")]),A("dt",null,[A("a",{href:"https://webpack.docschina.org/loaders/",add_date:"1694597327",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACkklEQVQ4jYWTMW9bZRiFn/fzdRw7je3EbhNqoEWQCqWhJVBUBAIhIUCCAcQACwMDEhNi79gZWED8ALYOMLCgqgxkQC2IpRSZtAQIAeISx7Hd3MS51/f7DkODFEIq3vmcZzjnPXDASYokPZMFXfReFyQ9LMkO0u43mqSZ1Ou9633f+qbtdaXtw8pm+NlL5yQ19ntsj3kqwBurW3r7h1544MatYI2SI87EIBOzVRdOVt3VWsE+Aj4zsx6ASSoBL6/v6N1mP5z5sa9cPBSH8sZ9xcBOgJUdR5KJWsF4aMKlM2VbKOftA+BLk/T0Vsan3677+rVuwJlxOB9Yu7nBpastxosRz54+ymilzFoKow4eq+c4NeGaeceLETDyx1YodBIxX8sxiAd8svALXy91SbIAwHfLPV46NcWrjx8jzUX8GgcaJRudLloUAWSC32Jh8vz1+waTpYij1QLLnQE5ZxyvlXASy6tdwmSdzaHQbnTRP0maQerFpcUOrV7CE/dXmb+nzEjOMRh6LjbXSTEemaxjewqN9ldiwHJnwJ+9HV5/dJrVfsJXNzYIArP/vkIEIEC7gkLkECILcHMzoR2nCDBEMbLdyvcBDDg8akyPGa+cvZfiSI6F6x2k2+L6WJ7n5o7w1NxdFEpGa/BvwPDuMRt2U8e1biCLDvH8kyc4M9Ml3R6QZGLqSIWoPM5SAhWJ+ZpjIm8p4E3SGPBaO9E7zV44vdiX2xyKUmTMVYw4g6VYDL2YLBhzVTd8sOIuj+d5H/hi7ys3ArzZ2tZb33fD8Z9uBRql24DtTJysOs1WXbNesI+BC2bWudOYZlOvDxf7vn2l7XV5zWslDitDr/OSjv3vIndBeUkvZEELIehzSWcluYO0fwO02GAhQEQDaQAAAABJRU5ErkJggg=="},"Loaders | webpack 中文文档")]),A("dt",null,[A("a",{href:"https://gitee.com/",add_date:"1694698892",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABsElEQVQ4jaWTT2sTURTFf/eRSQK2bkKaQqEb6VAiKPgFRJFCBBEUxKWLbMSdGxeuunDrqsWNH8AvIEIEwUL9A12I0EBSs4kFi+3CxHZmmk7fdfEycRLHgHhX78+555777nnCROwwf16J66AriiwCCNoFaQi550vsbafxkiyUar7N/lPB3lcwk8RDsFXMM5/yQ6E5GBG45O+vQK9mJWZQvfGZqyUktCivtShpi5J+rd3R4MOWnkaRZkXc66vDltcAxPV88lnBnLl+jYWXL6bW1jhmx5tHwArehZwS15OeS6uPAAg23nHw+An2R+8PAnsUDN8MA3Fd2pS2FaoAS4NviOexu3Kb4PVbzNkZpFgcVxBF2P5h8qjNnBuVugPPc1V+OsC5/TaSz48rOD7mS3FhqEIWM8c1LUyhML53JsmOTtmnU1mmU1lm794DpyAMR/eCdnMgDdBqOrFw6SL28Oh3ldkZZu/eAiDa+pRCSmNsjL4eTJWv1rJ75SbhxvvRGCFlpLjXzzTPaRhqsPlRu5dvaGK4kZH4Tysbx9Ic+MzVwKwL2L+mgQWznv4HMgn61+/8C0A56wPnYTeTAAAAAElFTkSuQmCC"},"工作台 - Gitee.com")]),A("dt",null,[A("a",{href:"https://www.emojiall.com/zh-hans",add_date:"1695556833",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAADTElEQVQ4jWVTXWyTZRR+ztuvX9v9hM3BsjV6oSYaftZmSDe7tVOUAMafrqWLcfFvKu00jHhBCCYG6xX+hBDCDeNCE0KQGy+EBJkOaV032FAcTbfgqmxituzHDRi6td/3fu/xYjT+8CTn5jnnec7FeQ7hf3h6+0v+m/OLHVJaQd2h3y+IkF8uTGl220DlfZWfne850Xd3lAAwFYWxWMyeGf3jACvu8njW6g0+L9y1awAQpmfmcPlyBsNXR00hxNENazfsO3YssVTUirZEQn+8KXzm+dYYn/06aTGz5HthfdubVuG2d9gfiCRjsUQJAAEAaGwOf/pcaCfncuMFZmYpJVuWpYolTVNZpsHMzOO/ThTC0bc58ERbNwDgqW0vb/T5XzDP9aTu2aqUYlaKFTMrZjbu8qnUJdMf2GG1Rjuf1G4t3IzX19dpW7cGlVKKT35xGlJKevWVCAgEFgSMZUCzk9CImCuqEGxpsHm965DJjOzSTCmDjT4vE0gs5/M4fuJL5PMFRHc8g9LSEjAzqOcUOHkaCkSWxw99vY8bfB6MjFzboum6XlNbW00A2OVy4sjhBElpobS0ZOVWRKD23eBQB2xOHTbHCu+urSYQlWv/zoBSCo8+8vB/csHMUFU1QFUNsmMT+HNxmv2b1qz0FEMzDGNmemauAgAx/yO0LAtCiBUDaUGzazj40RFUrCon/6Y6npyaYd2hmULT7OmhoQwBsIQgnpub56WlZdhsNhARhBDQ7Bqy2Z+RHc1xNPosA6DBwWEucTkvitUVld3DV7Oy93w/hBC0d98Beu2NPdw/8ANu317EwsItfHWmFzs73+OWgI8CzT7qSw/Jsdy4qK+v6yYAaAxGDlVXrX738KH3DSKyf/zJUfppeIRdLgeUYhQKBiLh7di7J043bkwau3Z/qJeVu86d/KCrFQDE64mEs6kl+k0o0snJ7wctZubZ2Xl1IXmRk6lLfOfOX4qZ+bsLAzIUiXMoEvvlypVr7uJHAQASic+dqXTvwXx+Oe71rLM1Nz2Ghx58gEGE69d/Q1//j5TLjcPtru55q+PFzs2b/RMAqGhAABgA2tu7tvw+NfsmmLdZyloFAC6XwygrK0tv9K4/vn+/8xRR3Cxq/gZYvbxpRSw4agAAAABJRU5ErkJggg=="},"Emoji大全 | Emoji表情符号词典 📓 | EmojiAll中文官方网站")]),A("dt",null,[A("a",{href:"https://v2.cn.vuejs.org/",add_date:"1695715109",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACnElEQVQ4ja2RzWuVVxDGn5lzzvvm5sPcJpFqQUSzUCkxCy2l2sartlZElwoiCAb/AN20UaoevBBKt22gKwvSVbuTqAiKRSsFQbBqBN0YRRuKYD6a5N77nnNmXFyikrpw4bMahoffPDNDn/12tFRMzkUAyC5P2umPl5o0X6cHP5z5D29ozTeDHaa1RWfGnqcPv/yg6e9ba6ly8fQvZPgTMELt+vNyuDWZmbbMpVyqt86e+REANh4YPMKRj6e5osg2dIWWL3qmSOBSTDdp4FJ1mzV8hZxBetHA7Nlx0JxA28zszGq7iutO2yfmH9OstGmbQfvBlTBdOTQkxCJsN49/vfpoxf4ty611G7UFQQkUHs5E67KSa0hXNh++sg3zaawVIa8sJdvbGhnGhHr4+dquUz9ZABSiqZpGsZeYy1lfJ+LYtA1Pa8pwhwFCmK2pWVGybn2nUiSTpHiRrFQBEMOD/trz3bOkMsyZY85Z8s3dIAPSkERDFDJE+ec94IyFM8cpyfCNHf4f+CZAANCTCR2J8/UxJmtcb4fYtUtUCyEthOy6Jep62xOTNbHWuPfkXx0BQPAQBgB4T+OHfD0yDakqQQn55m5QzkS5oXxTFyAgVSFhfDt+yNehzfcuAAQe/OfXJ0al0Rg1zhqzrJRcf1mz/rKaZaVknDOpEc9d23HiAjwY1ERYLFLgNEQhbifVPN/UowBAhXKiWBPIscV+flV5CLznGzurYymmES7lTI6FHAmXck4hjFzf5e/DY+FuAABaTARAfaND5W7beoeN+QgAJMmzqTytv73VTwML2y9O8DoJ3d39/aSInCLLTNZwSunk7a1+Cv7/A9+WAFAl/L6PKx19f0NV/7ip/a+O/U7SJrhy0VcGzvuBN3vvXS8Bjg8qIaejH/EAAAAASUVORK5CYII="},"Vue.js")]),A("dt",null,[A("a",{href:"https://reactnative.cn/",add_date:"1696419547",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACeUlEQVQ4jZ2TT0iTARjGf98f3aebm186cTaWfY4wDUEESxETDcGQSE8llIQR0VWogx48BnmwjoGHqEOHwrJb6LIOiUadCtNilnM6x5bNuf9/vg42U/Bi7+l9ed/nhed9nleoqqrq0nV9HLBxuFgXBGFAUlX13V5wcV0jJU0dJIN+MtFtAAxWG+WdvUj5BuIb3txoEdAu7wUr5XbK2rrxTj7G3tNP+PuXnUlnLb7Xzzl64Qpx/xpx32oOYhM0TdNzVUljG4IkEZidBuDknVEQBBbuDgJQ2tSBnskQnJ/Z5SHuJZWOhJFMZgDU+mayyQTZRBy1vhkA2WQhHQnvO4ScS/LMKpa6RspauzjS0EJBRSXBORcAx68NUt7Zi1Grxu96RdTjJrW1CYCkqupIafM5Krr72Jh+iVJWQSr0m+XxewiSRCq0ydrkE4yVJ0hvb+GbmsBx+SaCKBL1uHcoWM+eZ2lsmMjyIr8+vEVSFESDQs3QA2qG7iMaFGSTmeCci8jyIktjw1hbu/7dIBuPUehwAmBy1iIXFZMI+Ih63EQ9bhIBH3KRBZOzFoBCh5NsIgGAoGmaLhoKcFy6gVJqQ6lwEJx1oWdSrE48AsDe0w+ihLWlk9jqD+KBdVaePiSbiLFPxkKHE/vFq0R+fqPkTDvof1u6TnB+BpNWjefZOFGPe1cFSVXVkV1rOWuI+71sTL0g8H6KktNtZLbDfB29TejzRwRRRFIKiK2vHOyDbDKJbNzxQb5aip5OI8gy+ap1R3OjmWwyebAPAEILnyjv7MVyqoFUOMTi2DAAx/pukVdkAVFk483kvgWCpmlrHP4Tc+ETgeuA7z/AXmDgD9AW23M4cke8AAAAAElFTkSuQmCC"},"React Native 中文网 · 使用React来编写原生应用的框架")]),A("dt",null,[A("a",{href:"https://docs.qq.com/sheet/DWnFvZE1mV2F4R2lm?groupUin=HX5JmxDJd6KzmfTmTLoBSQ%253D%253D&tab=BB08J2",add_date:"1696597070",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACl0lEQVQ4jU2TvYucZRTFf+c+7zsfGZgRl1ULWcFCV5vFDxAEsREUEfwDhIBYhSCCnYWypWBhoyksBLFR8C8QRVHcgGQ1dktM4gfBjSaBncls5p334zkWM4leONziXg7nfhyxju0PTm+kwaYZw3gGjPlfjJk3Sw+HjvT7tVtn33x/cbsigNjbfacomjdUtxUpkhQgiQjjkIrSUDissjkRV+vR4mW23r4EUADkonqmHhR302RIgjAEKwiIvELOMBpskMttzGXYVQFA1y2obHJuqBWE1wqAsFdEWYBZNiKWFcJ4lwTAa0+/gvQQTc6gAkJEJ7BQrLOFpERycf5gJz+5+SBfP/BNrFZhk1vILYCxocsidyLn/2BDm5Xr6rFwnB5PN8erEXIWGNmobSEFtuWkHLkLUmS6CCvjfovaJmPP1C+9JjBIuGpkNRASZWGiVG4akyQUphxa2cY5BDGZTLitwDh4ZLDFZjlEDv5mwWF7xM7ofknYSvq5uca8rRxZysDRP9dXV1C2XNe8vvMCL933MIH8+cVz8cWvP/iTZ1+l8QK5x8m9jzkbMwkhofFktlLgujVJvPvTZ3zYFeQIrmvJQp2f+/Y9dYiUzSEVGpXucoeQZ9PxegSAuuX57Sd4dGOLFKG9vw7845UDnXrqRedlR0r2mfNfcak6IhAI3XXPnSVmg31w40qeT+dKIf68ccj8Vs0vf1ykq1q3ZRPz45s59ZIRNsrT6XStwNWQoqfvfzvXQ93qfXt9GAz59PKXoABl6I9SKgZ0MgmG+WYvCoBoF9/1F+lx5dGSvsOSsoNciaIYgsAp3LWWqLvEiUFRe3/jeHKsO3Z+6+RG6pdeuXfMlBkAE8bAjOm6L+7teXDYpQsX9qd8tN/8C0roUt/FUOeHAAAAAElFTkSuQmCC"},"蓝桥杯历年真题和模拟题练习登记")]),A("dt",null,[A("a",{href:"https://www.ruanyifeng.com/blog/2017/05/websocket.html",add_date:"1696667609",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAB8ElEQVQ4ja1TPWgiURic0yuSJqAQA9ay2IiKKQRZWCTYWaXYQouA2IukEDtLU1gabBQ7SbGVC4qNpUQWKwtRVEzhD0gMgoiyzlUGwq53zU314M0b5ptvHvA/wAt4e3uj2+2mqqocDAamnF9nATNhXddxf38PWZbx+fkJn8+Hm5sbhMNhXF9fAwB+/82Z1WqF3+9Hv99Hs9mEz+dDMBiEKIrfAhdHmM/nVFWVLpeLsViMhUKBx+PRwDM4aLfb0DQNy+US0+kUi8UCiqLAarViu93CZrP94FvOB03TEIlEoCgKVqsV2u02ZFmGruvI5XKoVquGx984Ho9MpVJUFIUkWS6XWavVmM/nabFYKEkSh8Oh6RbOaVPXdZLk4XBgpVKhIAj0eDz0er1sNpscj8emGRhCbDQaDIVCbLVafHh44N3dHSVJYjKZ5Gaz+bcASc5mM0qSRKfTyZeXF263W1P7hiLpuo5ut4t0Oo1EIoHdbod4PI5ut4tOpwOHw4GnpydcXV0Zq7zf7ymKIgVB4OvrK0kyGo3y+fmZ7+/vLJVKBMD5fG4+Qq/X4+PjI7++vkiSlUqFgUCA6/WaxWKRkUiEiUTicgaHw+HHRSaTYTab5Wg0IgDe3t6aZnHxM00mE5xOJ9TrdXx8fCCbzcJutxs69Afyih5+YaqNBAAAAABJRU5ErkJggg=="},"WebSocket 教程 - 阮一峰的网络日志")]),A("dt",null,[A("a",{href:"https://react.dev/",add_date:"1696676589",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC9ElEQVQ4jW2TTWhddRDFf+f/v3nXxCRFkfqJQqFUCEhdiFajaURBqgvT+ITGvKQI1kVsiBi6MS8+Xl5Waf2qmCqpYD6qEnUhogG1RYmk4EIEF4Wi4FdopSqN2nrfvfc/Lp7WKM5qhjkHZubMEf+JqHxkG5ZvyH489wkvP3oOgD0vtUQbW+5A/mw20beyHq/1ReHJ2X1y7mawU8B1ufECgJceA/sGdAVmx5NaaepvjrvAHlvYLKfu5Kezg8lEaSjxjHinGe80k6Q2kkyUhpLzYQBHN2MLmy9MTKXiAOIs22Lyn/Pi0G+Nmh6cvYmkOKIngSn2D/xu5dkvYrItSaXyVWOFYtGzuJhH4692e3OjwPtm3Ia4G+kowpzZ9mAclWkZ2Y5cYX9WHTxGsegFEI/N3wf2ONKVBm8JHkiytIfUf0dyWVbYcGaTnN4x0xGJBwm2CuGZpDb4rm8qLwzj6LWgqol2Ea5GOpRPDn7cdOf9D/vm8zemtYEl37nzDGibmZ0I8s9KGnZdvRudJ/TV19Ld6WT/cRmrkttar/a/0TQ+/4hvaTvkm1unLyrP7alPll6TuEFOP2QTfSv1X9PdntDXUEH5P2pgjdwsRh7kMSj81fSYtJ7jtb03jgp+VF07vwduBZ3yt/e0prXSYXfLvaml9Y/qtdLThfG5hxzWGlDsunrWojiaCtLbAojG5u7x0ihwjREWhXYFQjGt//E1FoWm+OLrnfLXjTAn2IXp29zbgaxSWnIUiz6rlZZysxpwUtKqwYrDHysUWmYKzYUZR/aBYZ8qcBrjZO5tMquUligWfURHh9HR4XymViN8Wa8OTAPTcXl+RNImDJA9Va+WngMolGev9YG2rPGAFlGpBICkMncizjXE3vl2DvavJVm6FEfRhwBJlt0FwL7DbUJbk5C9QrXB+5eZ4rH5J5B1Ik6DuyoP2fONS7thxCrG5UjLSbX/wP+6EaCpPH+Tifbsl0uXObgjAWDve3F0yc+dMtbSif7P1uP/BNwnP3Xw88OYAAAAAElFTkSuQmCC"},"React")]),A("dt",null,[A("a",{href:"https://www.doubao.com/chat/",add_date:"1697290375",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAADbklEQVQ4jU3TXWxTdRzG8ef3P6ft+t5uMCDrpjAUzWI72ZZMRsZiDDdKILoEIQQ0OJ3TmeiNGL3ojVES40U3syG+zES9xIuFKIJLpzAqsA1ncCQTHFs3267t+rL17Zzz/3khS3juP3fPlwAAzAQiBoBry1qnYlJ7BGG/oRleEooAIyOAn8rrpeH2RuvlBw3h/kZvsK2+QZ4mRfSV1gri/NeDuD15hS1WO/YePEL+PU/D6bHDMGRoYV68d6CVCgBAwSCLp3phrSWcc3iwP76Yk5+fekXOTU0Ib3UNZbI5ZNYLXFfXIA/1vSuePdEtUgn+WQU9/8MwigQAUwkO2e3oz2UhL33zCX4cGRS+h7ZzfCUJ0jS43G7EEwmUdcmnvhzV2rr8lmTcGGzZpvaLyRi3Q/Lr2SxkYTWGybHzZHd5OZlMof3RHTT06Wka+WIIxw93o7ieo7t/XjPlcpAM9E7HeY8gRq9QSFUU0D8zEcokV7BeKmO7207vvPEa7wwEYDeb6eRLx+nowef45kSYdA2kqooqgR7BhH1aGUwAzd+aRrGiwaxr1NL0ON9ZjFIpnkA5n+PJiSvc8aSfPBaBYqHChg4mxj6ha6gzmMEG80o8BtZ1bKrdwp/9EqHIX3/DYjUjlV6lby+E6fZSAl2dXWAhyNABXYdPEAACgRlgaUDTdbTWb8WJjjY0NzUBVVaQEEgux9hXV4+OBg/s5TUYBIABVTUhakg8DBBV2R0MoUAQ0du9LwNON/LRKLw2G3//4QcUS69yocoLqZrZpIAE8ZJgwrjZBBIKWBoGuLiGsdk5vnprlovlMpfm78LIpmhmcYG/+m0ayUc6URY2tphBYBpXBXDWkHyMmcTuZw5Bq2hUZIXH7uWFS0xjc+0Wvh7L4ePQEHz+dhywKFSoSGg66Uw0vHGkAYcDb66mAdUEOKuB/EKKxYUzMIpFSrZ1s2dXgKwCKJVQ8dbAvJbDwO6t9JYSDLKobsG4WUOry41GqQOXL4b1zfd+p+bmAHm3+SAy//K5i2E9EY/RY/5daqWESwlGz05H0NhoCWdGl23Xl/KhY6/2V2w2B/e9+AKnZiKycucPGfnuLG9yu9jhcFbe/yg0cCPN7g13v0Ym4P+cAext2NF4kll0+Wo8Pn/gCZ66ORNdTKTDTodlZG529tcHLP8HmYOtlz2pLo4AAAAASUVORK5CYII="},"豆包 - 你的 AI 朋友")]),A("dt",null,[A("a",{href:"https://react.docschina.org/",add_date:"1697361577",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC9ElEQVQ4jW2TTWhddRDFf+f/v3nXxCRFkfqJQqFUCEhdiFajaURBqgvT+ITGvKQI1kVsiBi6MS8+Xl5Waf2qmCqpYD6qEnUhogG1RYmk4EIEF4Wi4FdopSqN2nrfvfc/Lp7WKM5qhjkHZubMEf+JqHxkG5ZvyH489wkvP3oOgD0vtUQbW+5A/mw20beyHq/1ReHJ2X1y7mawU8B1ufECgJceA/sGdAVmx5NaaepvjrvAHlvYLKfu5Kezg8lEaSjxjHinGe80k6Q2kkyUhpLzYQBHN2MLmy9MTKXiAOIs22Lyn/Pi0G+Nmh6cvYmkOKIngSn2D/xu5dkvYrItSaXyVWOFYtGzuJhH4692e3OjwPtm3Ia4G+kowpzZ9mAclWkZ2Y5cYX9WHTxGsegFEI/N3wf2ONKVBm8JHkiytIfUf0dyWVbYcGaTnN4x0xGJBwm2CuGZpDb4rm8qLwzj6LWgqol2Ea5GOpRPDn7cdOf9D/vm8zemtYEl37nzDGibmZ0I8s9KGnZdvRudJ/TV19Ld6WT/cRmrkttar/a/0TQ+/4hvaTvkm1unLyrP7alPll6TuEFOP2QTfSv1X9PdntDXUEH5P2pgjdwsRh7kMSj81fSYtJ7jtb03jgp+VF07vwduBZ3yt/e0prXSYXfLvaml9Y/qtdLThfG5hxzWGlDsunrWojiaCtLbAojG5u7x0ihwjREWhXYFQjGt//E1FoWm+OLrnfLXjTAn2IXp29zbgaxSWnIUiz6rlZZysxpwUtKqwYrDHysUWmYKzYUZR/aBYZ8qcBrjZO5tMquUligWfURHh9HR4XymViN8Wa8OTAPTcXl+RNImDJA9Va+WngMolGev9YG2rPGAFlGpBICkMncizjXE3vl2DvavJVm6FEfRhwBJlt0FwL7DbUJbk5C9QrXB+5eZ4rH5J5B1Ik6DuyoP2fONS7thxCrG5UjLSbX/wP+6EaCpPH+Tifbsl0uXObgjAWDve3F0yc+dMtbSif7P1uP/BNwnP3Xw88OYAAAAAElFTkSuQmCC"},"React 官方中文文档")]),A("dt",null,[A("a",{href:"https://e.huawei.com/cn/talent/portal/#/",add_date:"1697600341",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC60lEQVQ4jZ2STWtcVRiAn/ecc+/M3GS+mklTdBGrJRXTqmCL1JWFulAsooLaLiz42bpS6w8Qij9AXVaRurGoCKJBuysS2tJFioJgSkMkqU36Mc3MJHe+7j3ndZHUH+Czf57VI2yhYAC9BvHYtu2fRk82Xlqv6/76U8/hauXLPir8sDz72/u7Pv9lCIhAgE0JAIEgoGVG3ykp7xarpfHIjhzIRwoH3Fhje6FeP37/num3BfSe/F/gTxi9RfX0IhQ95uF84L3vDdWJfcS0m9PcXlFW/wlZ2tm9+DTFtcN7vvirQRnAAVSovDGOfQuqdxS9SM+/l62kGhfNfW5lRfJ2CzHGkKaXqjse+7h2Y/Bm1q79Aa3P3FblmRYhi5EPPeFEX3W+Or8x1c99HbEUR0q0fZh33fVCdHPwwfqVVhZlHAI2AxlhpoJ5PgUEexz0Z9fOT/rfO8a3vSSVSFTyn4a3sxM0s7jWDayhMwDmPPZMi2ypSzgV0GEVs18R10IXjVcxCxvm7pW1xTC37mrLg33S9cMUPRWj19eofS3fwcJDFB4cxX1VwVwt4w730SmBswUkDiAZ2hfkSAHm+4SZHNldQo71CNfkNLw+AWf2kpBj0wTOlrBRRmgOCT8CFHEvxNAIkAXktW1I0sTTR48JwJfwygOYZxsUJ3bgnEcXB2Tf7KR/HuBvRg9WkFdj3M4umiXorSb+10k2vnWAJDB7k/Byk67tYF2ZOLHIwr1ZUvIFixsdkrshhA4hWSWdBcQA9OFugIsWKnfwl1J6J/fSW94aTabpL3XQj1LC5QG+3iO/MAHNzYH/PwZAJhv1o4O8d6Fkkn1B8zRSs+qhnEnWslFxl/q8Q86aK7hJyYeriJ0yYqYH6s+paMMZMUeKheQQQW+grpUTukF41BJVBZ5AzPcSy1FVHfESXUVoW0Oj1W/P1eL6J7aWJI8H1XNIyIyKYM2LYJZFpExgRWFghDEfdM5YgoYwJ2IPFky8JML4v2YmVY+h5RqmAAAAAElFTkSuQmCC"},"华为人才在线")]),A("dt",null,[A("a",{href:"https://www.npmjs.com/package/ws",add_date:"1697983378",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABJElEQVQ4jYWSPW4CQQyFP3uGREqEFIlQcgY6Wg6wl4BTcBLOQ4nE3oA6VehAiPCzdopZZtkkEHd+Y/vZb56U4CBACDyIqkplERBwsKp6UK9paGoweOr3X0cjNwNEpK5ydwBEdb9anTYbBUrVJayLwt3N3bwVGVkXxRJK1ZgGutWVACKZR8DMEHGzhEZq2lbY4fAxm513u7fxuDeZNA/uMc+rVzcTVT+fP+fzL3c5Ht+nU0Cu5Ppz+LU/9noxhNDt3u6Z5LorPFWFWSPaPw13oj4664h7TjJO/hJ3pc3Y3PI3KjH1iqqIcNVeOh0XaXAQ1UQk5W9rqPrptF0s7HJ5HgxehkPM9mWZrCElkMzXJtdsypuUZL7k23Brb3fMHEQkqEJj7285nLmISMvnnQAAAABJRU5ErkJggg=="},"ws - npm")]),A("dt",null,[A("a",{href:"http://www.npmdoc.org/wszhongwenwendangws-jszhongwenjiaochengjiexi.html",add_date:"1697983382"},"ws中文文档|ws js中文教程|解析 | npm中文文档")]),A("dt",null,[A("a",{href:"https://open.weixin.qq.com/",add_date:"1698496063",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACVklEQVQ4jYWTsYsdZRTFf+fO7LxdV4Vskfd2RfCPiMbCRpJKEKJZi9Q2sRMllQjTmUaxkVjZBjSiIAQ2REI6NdvaBAKmSPY9XWNYNc95M3OPxZuNm8pTfXzfveccvsOBQ9QER3GDcrwzXh/vjNe5QfnE25FZAWCEMMDWd1tnnH6NzifAx4exXym1q9DV+6/f//bojqgJanJ8eXyyeEqfEnqZAHpYUg4yBZBA+of+kd+dnZv9SE0I4Lmvxqc8im8Qz7g3MQo8N04ngEKhNZFNokJg/lSTb9x7a/a9ti5vPO+q+omSCdCpY9f4OsQ7hDcASD2AvCR02iUngJKOqRaLl4KquhCrMaGlUakSxcW9s7MPEdeiCkUVQlxb3sVFlSppaWI1JlTVBW1+PblDwQv0pFZUuPUt5OtynHf4GIBSf1j5OdZprehFt9lTKOj5RVtXNueWR49jqSSNhP82Tnv4A2lduDFe2P9FqCaQHwypWkheuPdBtk5bSEJy2j7I1gv3QgJMgO2HYemmKsl2GhsIi1JIxjZLIosSCGPbTlWSxM0g209o+EeFwjgHBQ0WdUgEHJ57lRItTdJ+HHvb+7uZfi+qCGnpBJOH6sMymMRuI6KM1Sgy/f707O+3gpqYbk8v5aPuvAq1iFCpkJbqQkusKPS0Vij4rfurf3v65vQzIIIaA5qXix0KRogDp29jGtsMLubu/HPO+ah/mCdn27MvhkJlSY2oyTXWXvHCV9pF98H+s/t3J81k0+mJQqnUbG9j7x6v0j1uY00+0VCuMuL/8CUFHho84F84lT/uunoxgAAAAABJRU5ErkJggg=="},"微信开放平台")]),A("dt",null,[A("a",{href:"http://mock.pe666.cn/getting-started.html",add_date:"1698588059",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACuUlEQVQ4jT2STWudVRSFn7X3uV8JWjVEWyFVcVANaawThY4dRakGEQf9ATpSRyJFEPQPOAj0BwhOBEEn0ehUUBF1IMGiwSDFj9QKxtzbvOec92wHuXW+12Kt9WwBHJzf2hjb6K2OshZhkw6jYnSYCkbBqeGR5WQ8TOO9w768c+mHzff01+rWs4Ph4H3HFmatzo9MJYw8FxeMLKeSomDRGFnFy1Grl1O4ve3YwrSVUpqnrg9ViDJMlJByGEVO6T2OSyhHqI1bRaNBD29aKM5NW41CSpNXLrJ8dSP0yLKOb/UqlqiW6Irg7oke37oYp58+S3eMz+ijw8+lkA1zk4osTj12hsUnlrQ8GPH3SzuUJqo5XelZe3WN00/dr7S8ED9/dpPcpCofWgmnzHv3XVGZNe54cpl7XnyU42ljdtS495kHWdl8gOgbfW3KZso4GceK5ivjETJkRsvBystr+MoptLTA6uvrQCAzQqKGxwkdw7owZYyMKRAAN3auM7xzwMOvnWf1jXUm943Z/+QPOLGZ4z1JnW5zzhgtgjSG33eu04c4+9wKAHsf/8b+5wc8tHGGFpxgxSnh2P+cw2khFFBD7F79kXxYyYeVb7b2KE1EBC1EJp38iIxUwiMj9UoRgVoLYjzkxrUp25e/pIb489qMuy4sEQ1aQMWjhqmQImXN+zTJRinMpeZG8cTNX2ZknJoSuYC5GC6myEgZo0pKFS9VKXU52P/oV2q0+Pcgq7rDMNE3hwXx0xeHfPvBQeQKOVJkOYGqPl3/8DvZ5MJR60uXScVd1VJUOV1IGadXiuMqbmUUo2EUs2o2GdS++966qitdaCqNB/1oQO8pikxdSAXntlFNLhZHUcwxmwygTaPpil3a3dz+p+UXMvZ1DrtVSJTwKKQocnIYhRQ5PHJzGppBfJVrfv7d3fXt/wCuFpf9LOi6VwAAAABJRU5ErkJggg=="},"开始 & 安装 | Mockjs中文文档-自建文档非官方!")]),A("dt",null,[A("a",{href:"https://www.lanqiao.cn/courses/18421",add_date:"1698915845",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACxUlEQVQ4ja2TTWhVZxCG35nvOyf3J/cmJt7YxigSUcFoRAUXulCktEWLdpNS3FgKiigIIqILf44miOhCAuLCjSIixVu6sIgtFFEKgmirMbnqQqQl4k/iT3KTc82553wzXWiMoSvBZzPDMAwD7/sSPiRQRgkEFAF0AEUIOooMdABtUMwH4R4UAQkmo4SPYmLfvi2kANDeGa+WOFwN9maouhdUkZOUT83TKPoShqaAzGOALvUFdH38AAHA7GCkKUPmFJGut9kMkjACNPldlQts7RJiCxeNAKBRMr5PpL+8vHv7x8fF5WPcHGgmA1wwfnq9Oofq6JtjEoUrQNxExi6RKAxdHN6XatLWe++3xnisshREpnHR4tMAQAv2l3ex9Y9CnRNFV1+QDdqDSjf56e0Sv4nA/I3E1elszMzeA9nO8dfbuyrdkiQ9zIa3sVcDBf7tK13qnLuzPFWR/MAWgMq13n2pP0r3c+eQVFtbdvQ3IFAGlJ4NjhymJPmCVeSpSnSHPPsTit85zqGB2Paqi3pEkp8BJRTJOYc/syZX91ZC0oHupkFV9NlwLPk6XR42xrREAPAgyD1s3f16rT/63I4VUiFQrwBQ8YfO/+PPqk5ISTJU6T/O6RTvoVztGZceOlHYOlCLVeB0yh71Gj4/m0fhEAAUtg7UZuP6Lbjx0EOHGgBo3f1kZr42t5El0cuAAREaP5vqbcJVSojwqxBI1c1r2/t6XaHRfgtFDy7PiVAkh6DkZ1P5DU5wiwCgbd/w98z4SlRIVe5KIn8Za1cCmAEVj4iHFXrBJXbQmmSuslkGplulIH/xvSUX7g3XKMerSKRFQTUgvFLgGVQ9YmoGJEPCZWUuq+jFUlf9FUBpUgaaNz/J1BWy7aCogciOVPqn3ORpj2oyXtNi4TgtVR54cKTu73HrfxL+n8JAeaJ/F9v3s4NAEEyK8n9U3jd9D2HuUQAAAABJRU5ErkJggg=="},"第十四届蓝桥杯国赛出题 - 蓝桥云课")]),A("dt",null,[A("a",{href:"https://www.lanqiao.cn/contests/web-2024-dx-1/challenges/",add_date:"1699356526",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACxUlEQVQ4ja2TTWhVZxCG35nvOyf3J/cmJt7YxigSUcFoRAUXulCktEWLdpNS3FgKiigIIqILf44miOhCAuLCjSIixVu6sIgtFFEKgmirMbnqQqQl4k/iT3KTc82553wzXWiMoSvBZzPDMAwD7/sSPiRQRgkEFAF0AEUIOooMdABtUMwH4R4UAQkmo4SPYmLfvi2kANDeGa+WOFwN9maouhdUkZOUT83TKPoShqaAzGOALvUFdH38AAHA7GCkKUPmFJGut9kMkjACNPldlQts7RJiCxeNAKBRMr5PpL+8vHv7x8fF5WPcHGgmA1wwfnq9Oofq6JtjEoUrQNxExi6RKAxdHN6XatLWe++3xnisshREpnHR4tMAQAv2l3ex9Y9CnRNFV1+QDdqDSjf56e0Sv4nA/I3E1elszMzeA9nO8dfbuyrdkiQ9zIa3sVcDBf7tK13qnLuzPFWR/MAWgMq13n2pP0r3c+eQVFtbdvQ3IFAGlJ4NjhymJPmCVeSpSnSHPPsTit85zqGB2Paqi3pEkp8BJRTJOYc/syZX91ZC0oHupkFV9NlwLPk6XR42xrREAPAgyD1s3f16rT/63I4VUiFQrwBQ8YfO/+PPqk5ISTJU6T/O6RTvoVztGZceOlHYOlCLVeB0yh71Gj4/m0fhEAAUtg7UZuP6Lbjx0EOHGgBo3f1kZr42t5El0cuAAREaP5vqbcJVSojwqxBI1c1r2/t6XaHRfgtFDy7PiVAkh6DkZ1P5DU5wiwCgbd/w98z4SlRIVe5KIn8Za1cCmAEVj4iHFXrBJXbQmmSuslkGplulIH/xvSUX7g3XKMerSKRFQTUgvFLgGVQ9YmoGJEPCZWUuq+jFUlf9FUBpUgaaNz/J1BWy7aCogciOVPqn3ORpj2oyXtNi4TgtVR54cKTu73HrfxL+n8JAeaJ/F9v3s4NAEEyK8n9U3jd9D2HuUQAAAABJRU5ErkJggg=="},"第十五届蓝桥杯(Web 应用开发)模拟赛 1 期-大学组 - 蓝桥云课")]),A("dt",null,[A("a",{href:"https://yiyan.baidu.com/",add_date:"1699605065",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC50lEQVQ4jV2TPWiddRjFf8//fe/N2yRtY9PbxIjEVJQiqDWDrQoOKeKggxbRioJ0EtQhSx3URR1EugjSRbBUcBBFa8XJaiGgUBeV1kj8aGtNalKbpLdJb/p+/Z/jcNNBz/QcOM+BM/yQtE/SvKQoKVbRXXKXXGevRP216pJc7vI6ul/Prf/sM0mLwKALl2RJwGpHR6ZrO3TayZqByZ2Bp241MIgyBUxmBGDJJEnCzRQATvxZ6+DJ3KYuOD2NAElCTAMPjaa8PJ5q1zYZgMs8GMHcFc0U/liKenvqKp/NlNauTOM3NW3yvg10auP9n2vOdUy9WWJP3p5q8i5jpNdNCm7url8WKj363oJduOq0Bnq0f1e/vXDvBkY2BSBw7PeKA1MFpQWt1MG2b0n4+OFEYxux1MyYWy5t7p9ct41kdvjZLbZ7tAeAuTV497sOn8yULBcwMtCwTZk4syydbQfbvikQJNEMUOeVDfcau0ebRBcffD7Ngy9+zcETbS6vVDx9R4NPH8sYSAR5ZU0TEqRmhrugrCnzijpCHSOvvDPF3zPzTBwY49W925gYS6giFHnEK0cSZkYKYC4oK7yMIEFIGLh7nHZrlUPP3MyO4QYxOp1SqK7xMiIXIFKpe1DUBDlpGijymmRwmKwxhARVFAGxOTOsjqisMXUnBDOjyGsoas3Or/HN6Sv0ZSmWF5SrHWJ0GomRJAlHT61xqV1Q51F55d0JkhgbyjS6tWHnz6zwyJu/6rmJlq11Kqidzb2B3xYKXv9yiWOn1nTNZTtv6bMdrVSSzNw9mlmYXSz01kezHD6+YEVH6m312Mb+Jk/cP8gXP60ye7FSa6jHnt/T0uSeGxjsM5PMTZLc5SFYAPh2ekVvfHjejv/QptmfUpZAlrL3ga289viNumc0MxDu5iEQkLTYLfG4Tpsk15Gv5jW2/3vd+dKPOnpySddVR3d3xXW7yP9x7ga6RXOXruni5UKSFKOv4+z/wflfDMoai+Z0/t0AAAAASUVORK5CYII="},"文心一言")]),A("dt",null,[A("a",{href:"https://lanqiao-courses.feishu.cn/docx/L4nGdvpEkooFsExz7VwcCSWcnQh",add_date:"1699611955",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAB0klEQVQ4jX2SPWtUURCGn5lz7t7NSmziRxtQEUSJP0HLVBaCnSBWgghW1gF/giBWIoKFhQr+AdOJWFgEVkTEj0YjZCPEhHV3z3kt7t29l11xYDjDzJl33vkwajl9XcvpEMZ/ZLLH6MsjG7Z9BnD+ti4E5ymJIMCsBaTZI4wBzovhkLv9+/YbIAKQOBVKjk0ymFW6IAKMldjljhIn2NAVNpDX4XHOZMREmcqaUxMikSb7JHcur+2wBqbYquAGGidsNPnnLNQpsMKROTI4A7yLAG6YOxyM4ewqXL0I0gKAPX6F+t9QrwQ3Os0MqBJigO8DePmmHu8UpLa3d7HomIRlVdEZgNUg3QJWjzcM1AyQz9uN3+tNxRZFsqAs4ejhpngMUITK7nWrP+0txTpRqj9vD+D565qVwc9f8GMXgsPyEnQi5DwH4IaZoVFC51bRjXUsJegUsLkFTzZhqYRJAmXwBQaQc6763/oKNx9QH2TVSgxVcp4mq2I9AxiJ9yFj9azU3sG0mCqvBJIxa8JB1r/H25x4VvSIFgjmmIdGbaqOY0R3HChqBiaAP2NdswM+GVySODIr3r5JIQPSmETg43wYgJO3VK5AubNwiI2EffThoe0B/AU8oMSk2dj9JwAAAABJRU5ErkJggg=="},"Docs")]),A("dt",null,[A("a",{href:"https://element-plus.org/zh-CN/#/zh-CN",add_date:"1699613107",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACnklEQVQ4jW2TS2hVZxSFv/2fk5vEV4gvpNY2KhUUjUJQwZp702JbcZ6KDoQQiwh2oFTiA/EIKkFRSlsVa9JbxQz01omgkw5MjNqiBopihXbixFAUFZuHeO85/3Jwc66Kbv7B/tks9tpr7wXvC8mI5Cq5ZGlpQaRM9pSi9B++A25VgFkCaNFx1d81ewZAJLd8JtOqQ444Y03uF2X6JrDHVYDnFQBQsGTpMc3KdSs/dSJPc906t+IHzSUyH3uGSLiF51ESc4NWvL3ZvCFSTUMD31nAbsU8AU5JbHRVzPAJB5895tCd7TbSfELL+jfbTQBLuzcPsy4M6cSYooQDQy85OrDJRhsPa3z9dDqcsUPGA19k39V26yGSIzIPQHO3vv3iNymXV77pe32UMmo6qao0X3pcc1p+1eBXF6VslzpSvcIxGh8Xn3Onr93aKkIW8AObrASw8metqqphv08YX/yfFzhml5lTXpVEgiiVVyejgAfTpz/qg8/OqKe2nt8F43xMc2mU2yaqAfgacwDOMHNQnsnUcoUAIKxli8uwvjRMW+8Ga1TI5MwElghGykzfuAOVn4Gptw8PEBuXi4McKxo+l9eloJo15hBGkuIqdyDPKJhoVZCqe73drmWmsL5uGoMuoCl5yZelYf40MRGAv19rYEEVCxtPaDoFS4gUEsllu9RRU8chH3Pgygab4Yf4K8gwv7L+BWMaxJ4eC3k4dRL3cmf0DZHFROYx7o8+ZnHvDfa15NUZ1vHIAp4njtNjIr42yYdbVfvJEnZi7BT8mxTZ3r/RLuW61GoZTjqjPvZ0lp5w+I9t9jTFWcVxZgJY9pPm1Y5jL2KtOf5zGWb6IhfiEXZd22L/pMaqXOE7ThyLbJeyn59VYWVeq9+u6y3/vAI0qSSwvH9RxwAAAABJRU5ErkJggg=="},"一个 Vue 3 UI 框架 | Element Plus")]),A("dt",null,[A("a",{href:"https://theme.typoraio.cn/",add_date:"1700305801",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACWUlEQVQ4jaVTPU8bQRSc9+5jD6MYUUZOGjeQBhAFyq+gtAu3UX4Af4NI1FSICIkoRRoqSuMKUSCoKKxwEYpJZJ9jG13uvLe7L43vJIpIkTLSaldvNLuzb/SA/wSVh8PDw6DT6bSUUk1mfsY75yqBMebr2dnZ51arpaviyclJXWvdk3+EMaZ7d3f3otR74/H404LTi/U3VHyapu8AwO/3+2+VUm0A1jnnFUXBp6enMh6PEUURACDPc6yurqLT6fi+7xfM7Jj5JQDw0tLSa8/zRESYmWk2m8nR0RE1Gg3q9Xp0cXFBjUaDjo+PaTKZgJkJAC12+FrriTFGwjAk55wwM+/v72NnZwdxHMM5h3a7jWazCWYmay08z6OysZym6SOAOQAqioKWl5extbUFay2yLEOe57DWYnNzE/V6XYwxz2Lk6XT6KCI5EYGIxDmHLMtQRikiYGZkWQZrbSWsHFxeXj6JSCIiEBFxzkFEQESIoghRFIGI4JyDtbayXl2wt7eXW2t/lEURkSAIMBwOcXNzg9vbW4xGI4RhiEWUACDVFwCI1vonEcnCAQVBgIeHB6yvr2NtbU3iOBallCxeFQBVL3wASNN0AIA8z3NEJMYYbGxsYHt7mwCQMcZqrUUp5YgIADhJkrh0gCRJurPZDPP5PHTOgZnJGEN5nsMYA9/3vVqt5kdRFCqlwvv7++75+fkXAFQOk3d1dfVhZWXlPTOHAIyI5AB+ichQREYAHpl5OJ1Ov+3u7n4cDAa/n00jAHS73Te1Wu1VURSjfr///fr6+ung4EADsPgL/gCc7K9iTt5DIwAAAABJRU5ErkJggg=="},"Themes Gallery — Typora")]),A("dt",null,[A("a",{href:"https://mvnrepository.com/",add_date:"1700400061",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAiUlEQVQ4jWNgoBAwMjAwMEiUXPtPlGJGRhT+825NRhZsEiS74PzNx0S5AB0YqsuSbzMKkCy9/h8bHxeNzGbCpYlYy5jwSTIwMDAs27ALQwxZHRO6JDooPiqLVx5uwPNuTUZkmoGBgaHX+jEKjc7G6iR0PjFsigDFCYmFgYGBwWvOF7JdQDAWaA4AfshA0En8tb4AAAAASUVORK5CYII="},"Maven Repository: Search/Browse/Explore")]),A("dt",null,[A("a",{href:"https://www.jbsou.cn/",add_date:"1700809017",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACBklEQVQ4jaWTPWtUQRSGn5k7d3fNxrBigvgBYpVAiGCnFgHBUhvBQsFCSamQTsuFNKKFoIKIla2CpRb+gYAgfmAnipIiSoiwye7m3plzjsXejSaslW8zzLwzzzlzZg78p9yIuXH37GX2Nc6zWSSk9EiCFKEUR0qKpIzC1ugVS2EXwACYnLjB5N6T1DsgJZR9KIEtoC+DsaZA4/VuQKWsRzcqvVigKaMQRxQoFaIZYp6oRsRGA7xN0Oku83VlkbFGjUIMIiiO6BVlGp89IRijAWoB0W/cfvdmpL8wvUqLhOBHA0wMp4E2HvCAAvAJxyzGmhtHzRHCjis42tWrqDnUjDZKG2hXAPDMMihobkByHqCKNDygqCgqrvJsOwCVH2IfzKHDIrZRLh07wPGjJ+htbpBSHVGtPFdBjOszczTzI2yV46gIJm4AuHPmGlOtJRyHKJuR4DK6/ffbGVycGmfm8D1UrpBSHSQi6lCvgfbpefZPPCb4QL8QqsCkOPylxvTBWzT9AutbQpGEMgUwJc99oFk7RbDAZreHSo6KEMhJ8ftf5Z2nVyaSRNQyRADnQX54fnVe0dn4SUPH8JJTkwZrvY9sFA8Z9kqRnmEW8OzBa416lhHCfR59Xh5suDk3R6t5FbUpVN7yYfUpz1fWGTYXwOLMBdTOkaKS8ZIHX178SXC0/rW+Q78BBsQDI4mnMmUAAAAASUVORK5CYII="},"煎饼搜音乐下载网 - 全网音乐免费下载 搜你妹音乐在线下载高清无损超清音乐网")]),A("dt",null,[A("a",{href:"https://pinia.vuejs.org/zh/",add_date:"1700878341",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACuUlEQVQ4jW2STWhcVRiGn+/cM/fOb5tUbYpQCyI0qK0W3GhRi4Kr+oMSXShiKUbcuRUKvVBx4UYXKgjFbgTBWTWCK9EgrUT8WSRqdWElCDFlGjNO5s6duefc87mYaa2J3/Lj5YH3B/7n5nQuIsWkax/U37jy/sUz6++dBEg1Ndu1Ox6kmLa0S1LC1rDzOtPmga1B/8SxL1ObShq2yy0AiiDok1+/dMhWzIf+Efdpo9b8KPPZy8WfRQiuvPe+Y1PJInhSLCn+GkAmAIMQnv7ihSdsUjkvTnFO16O9zT02MnFFJC8ud98ajkZHo1uqN/nYvHr+/nPfkGLsBKMAerXsFDjNB2U5NVPdNy0jcqdseBKbu9OV2EJkCIHGfy0wBtjNzs+DeHr1uTtaB47fmvjdNRv5Msj3g9KcUy02XIhsr7j8W7+4MMkryPZQLq3ML8ze1nqcv7rghhBVoNZire/9qV960RUX5j975uOzpJjrAE0xkhJ09eSjVMznRAYdeiXricY1TDUO1GPz61qxPHv32Xt21piOLeBGpwa+wdIPTR98Bak3MTZmM7udvNvQgw0/q6uv3AmgOi7ATKrQjZUT+ykHD11dP6ALX81Ef2eJDLMengbvfiLy7UrLszeJ/TB/ajI3A2BUx5A9Uf8wVmRGtvTNFxON/e/606Ut8s0/mH8s49DNVejnasTfNQa0w7iF9pxAG3yxmwiS+pJSYppxIkcONjFGaVV/FIKx9AXjZReATKq3zLXH/oPrUYgU2UgQoZKoGhOhJfjcEUrvk6QV4UP3WgYiqBEhAGRF5zucy6wtRd3QE0rUO8F7oXQhuEKxwRD8xbGD6/n9S9OlB1+jHr9NCJS5J7IVVQ2IIExVoesW2d85zr7lgUzGt2NIeuHos9TM87jwMCK7CBKIzDKiC1jzjhxZ7N6o/weK3U0eae0m3QAAAABJRU5ErkJggg=="},"Pinia | The intuitive store for Vue.js")]),A("dt",null,[A("a",{href:"https://fontawesome.com.cn/",add_date:"1700998696",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAADoklEQVQ4jV3NW0xbdQDH8d///z+n95vIhAKFrgU6Shg6MoFsGm/LQNzDLtAQFhWnZsZ4SXzTZDHuzcTEl2VGo5lLmJnVhEwzJ3sYujkCynBjbmEs3G8tpaf309P2f/4+zZh93z/5EjxU8WzguZGVRLbnw8i4eO2FvYLQU6TIWzhEeGTT+EZ31KPCp+gkHOYAQB5A8arXBG3HucKu6IGzyaWSY8Nyvk/b0QVZr+BqSRtP542XVpffOjm1+sX/h5LoBQv3ApmLNQdsZnowtyiXVDCjbUF9JfeICguk0q+RLTqfl/Wu7fv6P9rdZtYNxqWfI67hvnAfJwJgBOAitO89WAyfb9WtaN8tRCXbXwU95POTW8kEnWM+9ARehEO2UUgUYDJQyP+Ie9kB5r0CY9d+77F1VTlSFTN5rE0qubmsUnkF1G0QZEYrFwdb+omZMprRsjydTZfSKQWMsGa9zPib9LKn/swYQ+jafArK1A0eEtW0vtxBJnOrYmJR47tbe6Ak4uxqZElfSmzRIghxuFywmKyqRbKmJXJXC+2y0sKa10xmvJoUvj4vGlw1yNslTM/nZb8vj2//HtVnY3FS7qgT8XyW2+S8HPDUDZ348+Q4KfzkuSMDTWqlxP9YFuTmaBKm2ayeUgiTUmRoI9qGy0pyoMG9l7f6nqYOs12fHJ8kpMjWDDI5yk4ctU5TkC6eKln9QQNqG010ggvxZTRHfynz3P5dDSJVZDtXc7qIpSLU5XSQ6vIKxCMxl5BIBwGA9z/1f7UZSb4e0/KlnI2wu2s6cgUnctN2AhEE81bqjo5W8Etz8M/HSUf94yVTVAglohjYm98E2+NF9tkmsTGF2klad5KKljphc5rE5qyFm/wvCV5IE/tTAbgGnsXi6DjcqkHaHgiyuseqz1PW5LAnd1aaF/wVwtReWWzsdnNfgIjYVJzQsj3M0vk8wbZawe+vA1TouWMdYqnROppIbRzXu1oGCd7pNqJTGoJZOgyhA1sZGC5mdJ7tgL39MFhVjZ4Y/lqU76mmtC1QWk9tyTDyd3Go/xQAyjBxn3/ccu+Hq2UN04QWx6TbT1okvbvW7G/j1O6Eev2GxKQMcwSrSZoWoUEwphTOHDH3z9xpBiF4KOcHY8cl/7bThdk5jS9HjUJg3d6MEVfDoyG1vlJa/mfqAhgZwOBgHgCk/+T3vQzKHE1alXNlmapn+Hq8DwbTLcHo29FPDl1zDo+ddrjdVnQ+cQWAeMD+BZvionoPojrsAAAAAElFTkSuQmCC"},"首页 - FontAwesome 字体图标中文Icon")]),A("dt",null,[A("a",{href:"https://typescript.bootcss.com/",add_date:"1701069086",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABfklEQVQ4jc2STyiDcRjHn+f3/n7vbDYmNmOxKIU4u3BDLTm5u4gjF+VEKTVOykFJOXDkJqFE4mCHHchKSS0ae/2ZZnu3d3t/7/tzMvYWtZvv7fs838/z1NODMH0K5YiUlf6fAC1xwmIQ8EcdSwFK0OeSKcFiV+Omki5UMFLrYKYQySzXuEmL4+pd8s5oZ5Pbxk2BAEzC8H167eJpsrex3etAgK3I89xh7HtDJm9sRhR/tTzR0/Ce4+thJaUZ84OBZrdt5fxRgMjpJkH8AhBSGl89i3ucbKS7LpbUQkf3Xqc81ed/zfLtq5dbRZUoMYTlSgSBIAACIEr4ktX3b5IdXsfeWNdMf8Btp2AFSqUbYvYgNr17pxsiFGxZCLbIlPwFUAkL3Fw+eRjeuI4q6kBbja+K0d/SQoDXyZaGWqMJlSB6Ktndm5bJG1bAECKeyifSBQDI6iYCjPc0AMLlk7p4/JBUOVreWyLor5J1UyQ+CgLAzkhdJSOIb6qeyXNA/AQYcJ2NnjtVaQAAAABJRU5ErkJggg=="},"TypeScript 中文手册 - TypeScript 中文手册")]),A("dt",null,[A("a",{href:"https://gitcode.com/mirrors/vuejs/vue-devtools/tags",add_date:"1701490963",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACuklEQVQ4jW2TTWhdZRCGn5n5zs3tDbFFLIJtBdMUMbWJkIUWN9IWBcU0NglFunHTlQuhUFq6FgRx6cpFUBcixjQFwR8oSBEJRi5qW4tCUNS0VLOINvbm3nPON+PmBgL23c1ifnjnfYS+AkQg1iYffWgoNV9OJkciYtiDZMJv4Sx1q7sfDn2y8iPbJNuL7vShVwtL51TYRwTu4AEpKRA4/FOW1Zkdi9fnthZqgAJSzzzxzkCjeDvc9+Tau5WHq0BKgud8u669p7CzkezsVjOACnh1Yuy8Nex0rrJbUrXCmhpUGW6WOb/V2aiOqcrfFIZ7fCEQ0b8+bRwfGVXT87h7Dj6Nsv4GKHt1+fngzY2V1L7VAehMjU4a/vAbC1cvAjCLMk9OzaJ1WkXuIwhTuZPmr73OPdS6dGMZWP4IDEDmyQAJkWM5e2ihAlHGLMYvKG2ygG++dPCIYZNSyLC4POgRqS503Sv/oLHww1zCeSTABSzXfjktkmNiQoW2b86MHW0W6TKiIAEIFgE5sMKOlifGBxOIioR47VTOnwC02w7QDF/L2Zcj511i8rtn/5WIUNUpU3lAlHMJiVVFRkIgqae+uwEgC9evAk/9cXhvc9/S6uaWH/XMeAuVU+TYpRH+laiImmCqhwWCCSxAYxYLYO/SarefF7ovHBgWeKYf3e+kc3z0yYGiuIJQgK736vrF1uK1pXt9opw6NJEKfVeQxwHKqjwpAL3psTcbA+ls7nlW5W4On8vOlxaxVoe0jNgvps+K8LzADi2MXPl76ePvX5EAvTFKOvDY+PtFw05SOZgQHkRQI2GqKgTU2UkNo67rK3f+7Uze/9nKxnaYpJoeu6Cqr6mwewtRoo+cCBHRcWJu/Vb3wu6vf974H40Afz23f2Tn0OCsiT4dsEcJc+Q22b8tvbw4eOmndn+2CMR/fvhPllGffOYAAAAASUVORK5CYII="},"Tags - vue-devtools - GitCode")]),A("dt",null,[A("a",{href:"https://devtools.vuejs.org/guide/installation.html#chrome",add_date:"1701501121",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACdklEQVQ4jUWTP28dRRTFf2dm17vxS4QtJySWwCYI0RCRVMFCFChSgiJqKELDd6AIHS0SH4IKCfgA/EkKKxIuoEvAdQhBih07AVvkZd/uzhyKffsy08ydmXPmzj3nCuDS+x9dinX9JeYdiZcYhxAAxuOWzRGBX5umvbm7/d1dXfzgxuWS+EMoy7XUdwCWpAE38oxgG1AsSlLfHaSu/TC+8sbFr2O5dCH1XQeEOdgD0AMTGuL5mXPuY1GeQt4ISFsppQwUC7DmEw3va1wssitSThl0uZA4BRn32Y5hALcGGwoNuSSDBKWEMSmjgoC0Uti2Z1nV1XPy05b+/n9U750Gie7evwCUb69ANrOdQ+LmRPFcRfPjPqpEcJOpr52lfPOk08MpPu7RpKDaWqO+vk59fZ1qaw2dLPBxT3o4pXht4vraWdxk9O5XnzmuV0y//RuajAVhObL8yQZxtQIg/TNj+s1feJoGOarA8sevkvaeE14INfw3lIF83DO7c4Axtmm2D8jHPSrDC23nuHimO/9FPFOrvvKy0/1n8rRHVSQ9eo5WK9JBQ7tzSDhR4D6jlZLJjQ2nB1M1P+250IlA8/M+dYDi/DKz32aECCxF2u3HGNBSHIzUmXJzQv/nM5pb+6gOFEJSLc9u74sYrCrINgrCbR5cGISzCVWg+/3IpKxQDZIXZJ44hlVKWRDmLhxI5j4c1wZLyCFmSyjnpyHLOzHEgJ1Gr2N7KKnnxhyqqYHYmBRDDHb+JbTT7vPUd3uxKEvGC5KQZDTAx3jRTEWZ+m6va9ub8fGD3cO1zbduh8DrmNMS9UIkMbalF11pjky+08zaT//Y/n73f1jmP+IqKYgaAAAAAElFTkSuQmCC"},"Installation | Vue Devtools")]),A("dt",null,[A("a",{href:"https://mp.weixin.qq.com/wxamp/devprofile/get_profile?token=884929132&lang=zh_CN",add_date:"1701504886",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACYUlEQVQ4jYVTPWhTURT+zr33vbykaUyfxaGLOnQQB0EXRYVWhDooTllEymsIrZvFSXDp1KFg96YJjRTbIYMFwa2QRXRwUFARRYo/i6lt0vjyXt7Lyz0OsVqrbb/xHO53vvPd8wEHgmm/ujqYgNjJbZyVUg4TI8UQ73S7+WRhgdYBpj3Yuy/PjLM6pTdnpDIn44kkAEBrIAia73UYZotF++k+8oizE7U7cSt9Pwi2NlijCERfiMTVmNU3Evj1j1HYvrDHCsSZzBuTmEajKOxE7ShXKvavAIDjrBUY4lEs1ntFs3tN7JgqupNZAEA8fqiXiPvbodtUZD0DWAwNrVml0vEWa/1KSgkmfUTtkKy3mQCgbQlSDEgSnSgKTCCpKxW0RrPrw0LKG0HgASxfqN/7Zr+dE4Z1SzO3dFi7HbqJDiWZGKBQggGm7HhjVkljMhaLw/M2H6x9sFcVQJzN1UaVYc4lehLWj0bttWkeE0Cdf8khErqTybw1gIHzIHzyvXqpCnumUqFIOBPVQZI0y4yY29ia9nT7cj5PXpOUAQAEaCF6RLl8Mmy5jZFOsHG6kO+bepwnDwCU0HIoHk8dbvn15cK8fQ8AMhmWEF87QFIDIDPyCQCWlo7W/hje9Ux0ZTI0WG7/R7lMnRRSthAyDcBjVl63MyV2GQ5ynOqgMs3nUpnpqO1PRyxXDAlbg+/2JNKXfLf2sDBv39w2+5+LAYCxidqYIay5mGUZQcuHlCaUIeE36y8Dn68vLtqf9yUAACdXvSikOUbQJwiiSRCrbsOdX14e+P7/a/0LOyO7O757xbmLn+HpD81UFilbAAAAAElFTkSuQmCC"},"小程序")]),A("dt",null,[A("a",{href:"https://github.com/TencentCloudBase/Good-practice-tutorial-recommended",add_date:"1701537584",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACpklEQVQ4jW2SwWucVRTFf/e975uZtCElk9RIwabGaZO0FMFNK9SFexHElQu7EN0oCt0ILQquxIUiWK1Y/QcERXFbUaqrCorFWDuJTBJLGqKNHYua5Jv37nGRCR2CZ3Uf3HcP53evsS0DBDDZmnk4xvLZAI8K7gWZYauOfeWePlxauHaFAdlAXU7NHH/D4HkLoYGEJAGYmWGGXBuSznfaP50FfGdAgFY5NdP4pCjKx9wdtv8NzL/7DjGQUu/TzvW5p4AcAU1NH3irVqudTil9mZVfMWw8mO13+aqga2al0JWc8lmkPbVa/fGR0fH67fXfL9mh1uzJWBSXY1HG1Ks+6LTnXgCKI0cenKiqv9YBcr3evNFu3wKqqenjbxdl+WLOva3Uy6eKEONzFkJNnjFIQATy/PzVlQE+N/t5oqHk7tEs7AnBnwlmPIIkd21s9tJ7QO43DwLeqXOidwHPfyPJzE4FYQcMM9AfK53ri/1GHyC3Q1EAy+32b4KbZmDY/QEwIQmGm83W0C7n3bKJiYmGYEQyk1EEM5aRCGbN4bHyRN+p+J8IBaD6vrGTweweSTJYCmT/lhAA+6csio8mDx+eZRvm7gjpYOvosTLG8wAWzCR9Y5Ot2YeKovjepXeC2X0hxifc8+ee/bXF+Z+vAhxsHT1WlsXLkp4MZnuBJMlzzifC8q+//CD5+0WML1VV9brn9CZi+N+tdOeufbVpcDqY7ZXYCDEWwt9dWrj2YwBCJ2+dkfvleqP+scu/k/u5teX2zka4kdKKS2sAMcahnNIXnbR1DggDoA41HpgeuRiK8LRnZ33zzmh3aakL0Gy1RkaLoa5ZyO75Yqc9dwao6F9dn3I33V5f+2zf+P6vTbq1mqtLdLsJYOPPJqNjZT0nvbq4MHdh4Nj4D+UDSXCdrYd5AAAAAElFTkSuQmCC"},"GitHub - TencentCloudBase/Good-practice-tutorial-recommended: 优秀实践教程推荐")]),A("dt",null,[A("a",{href:"https://www.sohu.com/a/73038639_393515",add_date:"1701736824",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACGklEQVQ4jZ2TTUiUURSGn/t9d8ZvzD+UYiZ0YkilECLw52smBNGKiFnmop+NEoYtIoRaVESLtBZBQW2CWtRGqYFaBUa2iaIhkTYJamMNBhk0JJM5On+nhc3gZxHVgRcu7zn35dz35SqAVAThP8pzEKVSEcRd/++XlfsQK5PDGOsbF+92EAjXYu7EgbKgh/DpzuKcpIdXD6kIknuzipBti8/rFUD6+7qL/L1rrQIUUeBTEcQhUBg4d+ZokSvg0e3dEmxr+0XA4UH7cZv38Tif5ufp7+3i5smxP/qQfge/NfHy/QOcH3rs4OoDVUw9XPg7gfU18bGL1vAYWmvampt5fitaFHCkUHD8wp0Gh0DLtq/MPG0km83yMhp19BwCIdvG5/UyeGOGwas1RX584gMNe6bRWhOybed6a1PITSHRsUbxVldIGLcjOsuyJLyv1ZFMKoLogtCl6zAyuhGPpagsK2fO7WJAKfY2fWFXC1S0L2Nsf00+6VxAAxwZKGfkWZ4NKomoBMo0yeTzHCvJsX/chM8V8GSJbMcK0gm6GiQB1tsQXDmhxGN5xQIxQVw/V96EISG0zFIqgksyWJKhVNJUyjJVksIQOdydVju2aonPaXxb6hDDhVaKhXyGlulZhjCpowSLJdSaD5shj6fJL4y+8FOqLenr7ZmMxWL+tW9LJBK1r06dfSCbA7IIsogh3zDkO6ZIMCiSTNYA/AA5nxDoqlFp/AAAAABJRU5ErkJggg=="},"魏则西整个事件过程")]),A("dt",null,[A("a",{href:"https://svgwave.in/",add_date:"1701864526",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAB1ElEQVQ4jbWSPWgUURSFz31vNu6a1QUlKBiioCLY+BPB2AhBBMHa3speYu+zFiuriIJdBDtB8LeyiIEoRlEUBQXd3ZndrLPJ7MzOZt671yKZIDGrbOGpHo/7HS73HOB/yYhRIkaJCA0E9gOMMWqz/02HF+TxcBLSUdKuQpqqJ8vnFjYw0tfgYfj8dAE8pQT7SIGYxXV7/ObFPb59/XJpDpi0v8/r1b1BuAZMNx8dt6CbiaiRCJKEKel64EaXYzrlCnwhqzaOlNT5d632g1ZuoNb2EACIpHjRd95w3enId57+Gtg9tY6UqzFHPKa9XSdwRqXR1fHx6a25gZc/Lv18WvEzdVBEegC060mx2+USQA4OSmcEfcBLxbYPdcL2YQDzgFHrBiGA2BIBCiABW8GKBSACEYa2hHaWURa3IJJjRtajub/j7JLP9MUX2hI4xY2hQtpU1G1kTjetcACCP/e9uJIsfkr3xh/y1L218AlEEkjhLrNMiHBZa92RkW0/pL48yoKdeL/YSZ58fMZYMrVXM0nfGHd/m5+07KYc1Njqednpnn07dGf2Vu3GlVkA7p9FwueX2+GpY7BUAbkq9k+8zpPaWKQ/1a/7far8FyOjIKIGBwfULxvc7ij4LUVBAAAAAElFTkSuQmCC"},"Svg Wave - A free & beautiful gradient SVG wave Generator.")]),A("dt",null,[A("a",{href:"https://juejin.cn/post/6982363593241002014",add_date:"1701867266",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACD0lEQVQ4jZWTPWhTcRTFf/f/XhNTG8wkmPdeLS0dFZwsilg/EERwExQKWigu2kFqQU1T//GLGkXBthREoYObk+DiII3g7i4i2JiCS0HaNK3Je9chSQ22lnqme8+95wz3A/4Fq2bT+H+QzmjgWfW36tnofE4dAG9cT7gxCo7wwbN6vLW2tcFrCUEF4aC00S1tdKP0gUq9tilUAFJWU/4dPdpkfasDflYHWvL+lNVUq6YlUPGtTnXlVTut5jmt8XX/YY0Ht/VRV17VtzrVqnFBtJ6I1kKdkRV6nSSjQR/7y/t0KNEOBl46SU6Fy7yrhczUNSogKt64jjgxztSWGVmYkE9c0h1BF/ecdkZqK3wREKednrDM4+IiY0zKWjqrB5wYT4h4a8SQBI65HcwFWT3PrKwWrVyvLjFoDHsxdFaXGCzmZJRJWQvG9YIboyBCv4Z0CEB6TE+6cWalDS9a5X6xgKUgtT1ZPSIGXcjJR6y6gZIzCW5plVJY4WLpgbwXrBqsRLvHtCfu8sJJ0h8u8aa8wtXFvHwH8Kz6BqadJGejZebWKgz9mJCvWDX1aTZM/GuaMLt4KDsZjip8psqARoiJ88ok6I3KPJv/yQ2eSqWpkfVVNQgAL6uXnRjTGlFFERzc6BdXSnfl+d+9Gw+q8Tjpm3o4yOl8kNNv6YweWhf+OaAt0DTJaJDOaNDKbR/bfOffVnrk9zizKkQAAAAASUVORK5CYII="},"好玩的 CSS - 40 个有趣的 CSS 网站 - 掘金")]),A("dt",null,[A("a",{href:"https://web.lumispectrum.com/controlPanel?active=1",add_date:"1702636526",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC80lEQVQ4jU2TT2hcZRTFf/d+L29mkjQZGpJxkk0XaddigxJUGCm0poI7F+KyGxciCIpily4qLmxRhBapexfqpjYQi4bGELuoG1dN8E+xZszYiYE4M+/N+969LpKUnu29Z3HvOT/hQAI4wMzS0rMqckFEWlKWTQBXbTusmvv1zvLy+uMe4UiLi7Xm1NSHYvY6IinuoHowcz/aGrrI1Xa3+x4bGwOAACinT9ea09PfaAivEaNKkghmmcDPwB+YjaE6ihmaJIvj1erCfxMTX9NulwpYs9G4pCGc86KAEAory8vDsnwuxvjm9r17Z7LB4El3v4Jq8OEw0yQ512w0LgEmU2fPPlMZGbnt7omI/FKIvPLPjRtbAE8sLb37d6fzKXfv9gFmz5//SFTf8RgLVMljfD5Mnjr1gYgsCBjuXwkMx0+efPrY/HzL3L/rra09ODxV9nu9H8cbjVcROS4iiYoEFdWWuzvuEMIbIYQvk0rlC1W9gEjv8NsGONvbfRf5VkJQdy9F5AUVsznMAJQYCzejzPPb2WDwYuf+/T8P43VarQqAm22JiIu7iftsgj+K0lBNgN+LLHsL1YnpubnpbGbmQS2EWUIY78AdIMNd3F1ExNVV/0IV3I0QxM0+iWa73Vu3NntZtlOtVBY6/f5Wryx/ZXFxUkQiIqCqLrKtDqsiIi4SiPHfgdkPVKsA5ejoaNPNHrKxMeh1uzkhOGY13F1AHVa1iPEqkEsIIPJbGeOgUM0ANMbKUHUP4Fi9nowVRYrqCRcxIBu6X9Puysodd/8spKkispeKhJF+P2N+vjJMkv5onkeAEaiO1Go1dX9KkyQ4XNtdXv5JAWn3+xdjnq8BTWL0vaIox2dmJkOe556mApCHUCTV6glJ0zOWZSvtnZ33OaTFWV3Nir29l9zsSpkkNWKU6thYIiKex2gAVZHjqerbbnZ9e3Pz5cN2WnKE2cP19X3g83qrVWd/P+vX62OVENKY57sHJWGqjPHjnZs3v38c5/8Bc8h8kCnWDZAAAAAASUVORK5CYII="},"光谱计划")]),A("dt",null,[A("a",{href:"https://jwxt.hnucm.edu.cn/",add_date:"1702873813"},"登录")]),A("dt",null,[A("a",{href:"https://www.yuque.com/dashboard",add_date:"1703078917",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC+0lEQVQ4jV3TT2sdZRzF8XOeZ2ZyZ27S2KQmtU0qrUpXZqMgJIiYhe+gi267KW5U3GXXneCqFSQI0o0LQd2ICi3U+Kc2qFgQN7ooiub2kjS2yc3c3Dszzzy/46LVhS/gszrfQwCQRJICgJHCikEXWtlqiM0CAKQ+6yV0Gw68WjC99X8DANjSVl4pXC5V10O12m1L7YSB7YR9242lhmpVqq4rhctb2sr/g5JcX/2itOqaJN0Lg7jXHoYyVvGgraxsKxvGJj4Iw7Dd7EdJKq261le/kOQIAINYXTniJl7bDoO66zvpp/e+5e3yN0z6LjLnMZNOY3l6Cac68xpbE05mRydKG78z7YvXOdDoBZq72SgwWnQpU27u/4K3//gAnp4kISd10wJvLpzH0uTTGqOxKZeLzr/ozHjRuyQ1iwDAkdVY7MzjeDrLjIkK19EUCrZti3d7H+Pm/s+kAdExDRYuOgCrldUC6BpFpC7Bbr3H+82+YGKIgUGtFMWcHcylR1FZ40ZxLCNeTlqFEzIgSgSJUQz4sHdDVRNYJB50Bois2ehUNo/Hkkk01pItAa+TDiAAwWDImOGT/mf4O/yOhTxDxgAzIJrBy+GvageVNSCBKINJcAmTvnMOuc/19YMbuFPdxmIxxSKJmk1bHPEBEaZUnr1qG3dGPaT0onfw3t91hrgx4yf548F3tnnwJXJfIEAyIyOpbmJKYTRI0SLuVrsQYFma0aSvXF533/9p9H34Zvg5Oj4XIAACHQSRAOgp6VG0gsnRwYIFmnvPFQU3vzj8aH16diqRIYAQQUACKAlQaw+xg9fxbCbMTBxLAK0vZtM/OFyCY3u4Vh/G691jxYQMgBBByhNozSGaV9kO22eKBaw88fzEQTu4fj9t1i79mzIAnNs8l589c/atqtKrtcWsHB4itI22R8QY5LNzZ3D+xCvNU91T61u9X9eWF5fHwMMNAYEgBABv/Lm2Elq70NTV6l7pF4RJLM2d7r30+HMbp7vzV5/k/K1HLyRI/QN7F9CfB8suEwAAAABJRU5ErkJggg=="},"工作台 · 语雀")]),A("dt",null,[A("a",{href:"https://chrome.zzzmh.cn/index",add_date:"1703946570",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAADF0lEQVQ4jXWSTWicVRiFn/feO18mM5OfCZ2EmKFUIkYxCGW6S0FEFxUpuKpdSDZSCBZXiQtBBEVwI8GAoi5cuNCFiS40FMFihTTRUtuIBY3RxJ9oYpNMZzJJZibzffe+LlLBCh44cDgcDmdxhH9BVUVENJ/Pd42Pjw9Za721VpMkSc3Ozv6ysLCwCQig/B8mJycH1tbWrlSr1f1qtVq7zd319fXvZ2Zm7vtv3pVKpVQURa5YLNrp6el4ZGTkKWPMiampqVNRFPlMJiObm5vx2NjYdKlUeg54ZmJiws3Pz4disehlbm7u7cHBwceBgxAC2Ww2n8lkcpVKZUVE1BiD956urq5j3vukUqlsWWtFRFLLy8vfuEKh0N/f31+8Y1cI9PX13X+HpwoiZLPZzn+scrm84VS1paoB8KgajLFiDAGQOA4AmkqJERE9LPccSuO9bzkFIyJGQwhijPW1Ha19fkEOrl/VZGtT0IDrOUL6+AnNPXoKd6TXagheRIyIGCeqBFWCMe7g8pe6/eak+PU/wDkR6wBorf5M/evL7Mx8QP7p85p77LRFFULAee8xIjQufOL/eu1lY6I0Nt+DikG9B8AaAyha32f71RfxO5WQPztqgiqmqz3HaqvC4ruTZKIMpNvQJMFXbtEW12kPDUK9SmjGQAqT66b8xhT+xyVsZyeuPQgv/PYZG8NpPrrSYs972nxM99kn+X3oIYKxDKW/wlY/JUkckrKEusf4D9FwBvft3p96vXqDGw8W9OJGRU8u3cSff5bX+0bli2uHpz0+WOKlk3dpd/ktQbKgHZD5CS2vqFnYWqZ6sKvWRrwynCUzdJRLR8/w8cWg6STR9pDopate31s8jbYd01BvqjatEu8rzR9wlVbNHKSCpONgrvVa8/7DdxMqaVIp4PYfchlYuWWRqIDIqmAiEMUk+8b1RvlW1ru4FSTOamTe6WnJ8z1bdLgsoooVMAoPHGly0Ngi9mkVNWpaKdfQjlhGz5279zt7s7DXrHsXnOzS4ImBYZZ+7WQ3AQckwCP31KCxSEwKiLEmLdv1gdrfPOuIfFB4OL0AAAAASUVORK5CYII="},"极简插件_Chrome扩展插件商店_优质crx应用下载")]),A("dt",null,[A("a",{href:"https://blog.csdn.net/u013737132/article/details/130191394",add_date:"1704215018",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAB/klEQVQ4jU2SPWtUYRCFz5n3bvbubhISoyJiJxEsEkglghZJZUTUJkhAUoja2WihgohFQPAXaGVhk8rGRkQrURDEFOL3B2idkDXJ7t3Nve8ci7ubZOozZ2aeMyzmptAvSSBpBgkxgkQIkOQOkgCAZLeaJEi1NkBDWgeEzXUkFVZTuIPcaSjVkqPb5bEZTp/H4aPMc//0Hs+X9Psb0hrlILm9kuRw5+XbNjsPuX//yNEx239Inbbfu6IvH5jWISU9fzNlmV26FWbn/d0rf7SI5oosaPosF25gZC9iLH1ZzE0JRLeN8YnwYEl/fsSb8+xkTOvyiK0O9h3ERhMSAZAGgKTy3E6eppmePcHmOhtDgmjG2iDXVtjnAcAAyCNrDYxPSNLXZVZTxYIlZTmSBKSkciUrb0BSwfAe5DnW11DmUE7u6bhN3/opRHTbSALSGgSaARDAEOCuYmu72QAwJGq39OszLXDqhP6tKjrc6VEbTdUaHDugIufODRAt6OVTxcIWrtuZi6imCokGUkwet7sPbfExBqpy72Etc0C7xVMX7OodVCroZGquojHIoRECvvzG71+DnLRdSQPIWhiftJlzODLJ4VF1Mvz9qeXXevsCRcEQ+hN2Ps/UaaPIUWswJJKjm6EoWB8syZLsPV8PgiLTGlhHjIoFSaR10OQRpSPwH79sFlWOAVADAAAAAElFTkSuQmCC"},"Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源)_vue3 element plus admin-CSDN博客")]),A("dt",null,[A("a",{href:"https://cn.vitejs.dev/",add_date:"1704216718",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACzElEQVQ4jYWSW2gcZRTHf+ebyczsbhNDpcTgpdAaRYRAL5RSCGlQCILPeVNBBBGJNwSJLWSqtvrmBUMfFKQIClaUokRT1BSCimK11GKjqMWGlqTJJtvd7Vx2v/mOD4s2PtT+Xs//nPM/F2E9sZq9w5gTI7hYYfKvB/rw7XbIBtnQNWzX+ObgkQ9emkSQGAcgoz9of5Jg54aoIuIA9GlKjA9NI24bznYTGUNYwNL3Y3I3R2dnY//kp5O9LsWX+77WVwLhSa/O6ZJ1v+WXza/39k43Hh3Y9xpGoe0cqBbtrtbhmY+nLlzY3L+xi9ujgkGX8qYfWKaClIdLHruCwuyKylDL70DTohCxxqkR4zVkYWlvRHvzswM35uRrIXrFrUliDptjw7IQprwQpmCqLpNLtCtJ1dHOPNcqRNo55B590Rl57J4Bu7X3vdzUIWzlh8bflXMGVKoLvGVWOVNRE/opfndRF9EapmhC26K5oxScJ6v3yPlfxgKT8HtPszoFKoYY+ewJyf2UiTBVCVIo2wbNej/V1S1gE9HcCTbh1I+PY1crUnbp/rGjt6YxiHTupwbETbzsPil7cn+U1mzRjLyhrW+w5659QtGtzcZtxecffetfqZXmHjrGMIAgata/QZjLhN/QzDV6PUkiubnynWjmQVGXsyfHJVsu0WNWnxNED4AAeJ3UAxqjJj4hS6Pb4003CLvDPLeNpS0S0GKDf7H4efaIbxPz/th0+XVFZQRRgH8dxIgDFXuZg1rTxSgPzOK53RJoTf/46RlTW/TtLXppf8f6Vf4zQgzy/DuyHCX6YjkTc1O4ZoOi6VZOP2UCy6tDX/b9GXf2pdco0HFRsfNvpyuc3ejXvOX5B83yxa6VTaVTh0Clo/kfOh3gwxEdnRkpdG6P6vEd+sj62HX5Rziz001/saOYj5n1FZXrJl6lI/5qMLvz+LbWToBrFfgbUaFXw6AWkg0AAAAASUVORK5CYII="},"Vite | 下一代的前端工具链")]),A("dt",null,[A("a",{href:"https://www.three3d.cn/",add_date:"1704298468",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACBUlEQVQ4ja2SvWtTYRTGf+d9b3Jv9DYkRDe7FFddqlNFkeLH4KAFEUQpIoh/gboIEbs6SCV0cdMlgyJKxU5KtYpWHVyKQetQUCymbT5vcj+OQ+iHLqXibzqcc3jOeeCBzSirpVy2m+79X1YvvqlkmZm/xvOv53sDla0Jzc7dZm5ZmfmpPFsYBaCoZuOKs1YpgqBUJl1S/YOo/4HFEII2hHGEcW7xYPE1I/K594kowLqaoKhaTH6CtPuSZmMSaZdoteex1uC4BRxKzGqK4o01K+sC8w9ziMQI++gEip89jDEXCePLiBg6QYTnD/Pt1xWKxWTVirD4qI+MNw7+MMvdMbreSfydx/nR6iB9aZrhMeruEF7uOitJTOhGNKMjnMu+QlUMvhkk5YySRLvw+yYQClRrM7gplziBREpY6SdWQxJZxGQwTol7mkVEDWH4iW74ERElThKgRRBdIoy+I4niuLshOUvQvgrSJA5j3MxeouhmzwLA0tQhHG+KOha3YKnWT1PbHpDJPmYpjIgzQof9NNwhvMI41SCim1JCO2TQoiF/9AWt7hj+Nkur3QGnxuCeJ9Qbd8nlHTRuoqqc2nGHRu0pOc8Blkhoyh8ZWJg+QVdWGDgwDQiVSpqqM0JHv3Bw4C0A95fzON4ZWuY9F9LvtpbMTVHtWdpIuWwpbuypUFb7d6T/md/iOdueRDcnrwAAAABJRU5ErkJggg=="},"老陈打码 | 麒跃科技")]),A("dt",null,[A("a",{href:"http://www.webgl3d.cn/pages/c0b143/",add_date:"1704298503",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAADB0lEQVQ4jU1TTWhcdRyc+X+897LZjSJLNRGCVQnSgoe0pKXQikFbEDz1It48iCCIlQq95PDqScSTnrx4ESUggvZS0X5HItL4AT3ED3RBq4gJpdns2/fe/+vnYVtxYG4zcxhmiDsopVQlywQAlXx44J0vHly+OujsG9baQvOGT+HS9VNL1yfiUqGcaPl/89rm6sL8XPHG6vf3PXt+s98BAUWBgEjB1QDPiwsr1147sHk3hCJCkrI+OPfEXF+vbm7NPHD20xlMZSkYBQgmBKlN0aXEuOVc9fyVlxcvAKUiAGwMPnrMTPfWejb031vb4775/R7TzROTAAIBQBAUQAK1yaj07da1xy689PgNBQAxU29qk/rDeuQClS0yRWsUMquRWY3caGRWMbPaWiZXFPm903n2FlAqfv3H+4cE+qoPYqczh2u/PsSLvzyCXlEjJUUBQCoBABGBghKhCLWW5MNTZuybp/Pc5j55P2yT2Tf7m/y0Pctbox6LzMukgURQiQhAkAJJNi9MVO1xE9A8qiSICy1BwqjIEwuXufHnomyP9jCKRUhWjGqpFVICCBGYzEiQOG8aP9JJW7TRgQTaSNG64pG9n2PUdtGGAtvVPAa3FiGiQRVBENoaRAk0TRwNkrdskwcJiAgBigRQq22ZyhL3Fj+zV9yUza2TJAmKwGYanuovNY7jS1W7G9o4Vk0cSxtruFTBp0oqX7MOTnZaI918A7O9H2B1V7KMtIyS5+ai3rm/c3P/QueYLvBw0zQhiVcxekS5w+iY4OFTDbCAk4OxO50bSbL+49//nCUAvPvdyYO5za+EkDopiCdhRCbboQACDXIEF4+GRl41NnOuCbK8sjSzriDgK4ufbOyORy8k1dTMXdaGBi7W0YUmutRGF6vYhtvJdo7aqY7x0e2+uLI0sw4RKhBSllCnD537eDgcLzeuviy2Tcy9Qe4NMmeYj43JnwPS4a+krZbPHJ77oCxLBVL4351LqLJEAoC3v33yhHN4Rin0Fa1IPL6j5fSXrx/BZwDl7gEB4F830rXNunKB+wAAAABJRU5ErkJggg=="},"5. 第一个3D案例—透视投影相机 | Three.js中文网")]),A("dt",null,[A("a",{href:"http://www.webgl3d.cn/",add_date:"1704298515",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAADB0lEQVQ4jU1TTWhcdRyc+X+897LZjSJLNRGCVQnSgoe0pKXQikFbEDz1It48iCCIlQq95PDqScSTnrx4ESUggvZS0X5HItL4AT3ED3RBq4gJpdns2/fe/+vnYVtxYG4zcxhmiDsopVQlywQAlXx44J0vHly+OujsG9baQvOGT+HS9VNL1yfiUqGcaPl/89rm6sL8XPHG6vf3PXt+s98BAUWBgEjB1QDPiwsr1147sHk3hCJCkrI+OPfEXF+vbm7NPHD20xlMZSkYBQgmBKlN0aXEuOVc9fyVlxcvAKUiAGwMPnrMTPfWejb031vb4775/R7TzROTAAIBQBAUQAK1yaj07da1xy689PgNBQAxU29qk/rDeuQClS0yRWsUMquRWY3caGRWMbPaWiZXFPm903n2FlAqfv3H+4cE+qoPYqczh2u/PsSLvzyCXlEjJUUBQCoBABGBghKhCLWW5MNTZuybp/Pc5j55P2yT2Tf7m/y0Pctbox6LzMukgURQiQhAkAJJNi9MVO1xE9A8qiSICy1BwqjIEwuXufHnomyP9jCKRUhWjGqpFVICCBGYzEiQOG8aP9JJW7TRgQTaSNG64pG9n2PUdtGGAtvVPAa3FiGiQRVBENoaRAk0TRwNkrdskwcJiAgBigRQq22ZyhL3Fj+zV9yUza2TJAmKwGYanuovNY7jS1W7G9o4Vk0cSxtruFTBp0oqX7MOTnZaI918A7O9H2B1V7KMtIyS5+ai3rm/c3P/QueYLvBw0zQhiVcxekS5w+iY4OFTDbCAk4OxO50bSbL+49//nCUAvPvdyYO5za+EkDopiCdhRCbboQACDXIEF4+GRl41NnOuCbK8sjSzriDgK4ufbOyORy8k1dTMXdaGBi7W0YUmutRGF6vYhtvJdo7aqY7x0e2+uLI0sw4RKhBSllCnD537eDgcLzeuviy2Tcy9Qe4NMmeYj43JnwPS4a+krZbPHJ77oCxLBVL4351LqLJEAoC3v33yhHN4Rin0Fa1IPL6j5fSXrx/BZwDl7gEB4F830rXNunKB+wAAAABJRU5ErkJggg=="},"Three.js中文网")]),A("dt",null,[A("a",{href:"https://interview.poetries.top/",add_date:"1706875024",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACaUlEQVQ4jX2Rz2+UVRiFn3O/b6ZMBxG1EpExTZoKJAoJQpSNmNjIhvAXGDcujCsTt26clUtJXLB1J6R/AKTGhsQIUdCFSUlMNTi2MCkUaWf6Y/rNzL3HxbRMUxKf1c1733vec94L27hOeHq+f7Lm5lvvujlxxr9THdY/+MgPzr3GLgRgIwn7ztQEh8tfsq96wfnYflOO0pNF9RqX6ekK3dJZ3PuVfPS4ajM3ADR8fP5NXsqvMxZqrAunMs5fJOyfhOomPLp1hXb7UyrPf0iMMxqfvTd0cG1yhNrkTQ5lp+n1CkTJjsIRlCVX3+uHF3ojPJz9jLXsKgcr71DEedVm5we5axPvsy+cZqvXRypjSSpBqAAhsP5jzlY1OR//hK2VA0QaHIkLADkAMb3NSEgEJUxmQBjJgxZ3gtvzOD94VKMrmV6Zm9tZ4sBBUplokQCMgmyQowUJlKP+CqKAyqED9mB3Q4Fy+IM+ouNAAFKSwMpCQgIFE7pW7991Ntb+kbCEhwLF4+t04iNiyEhEJDBgCxtFIs8RyDrX9PpfyzvTAYLrBJ357TG5PyfPREuBRBooAHZiNCtR+G+6fMEeguok1wl6Y+Y7iuJjlBVsIsK2i1Imgpboxos68v0iwI79pxFUJ9lkOvnDt/T7X6Nc66vduNYqokuIov+NDs/ctbd/bRe7C3adEPuVX/RkVRvewFliZLmjrPzy3HbutFdAewvNe7Pjo+2ff4rp9quUEiqfaCSmzo0dm3qwt/cZpqenM4CFpi+1Wxtutza9cN9f7b77X2zLoMafjVPNZmu52VxdWlpcPGGQ7WfcAvwHmjEoLjgfjHUAAAAASUVORK5CYII="},"前端进阶之旅")]),A("dt",null,[A("a",{href:"https://www.runoob.com/http/http-tutorial.html",add_date:"1707036070",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC9klEQVQ4jV2TX2jWZRTHP9/n+b3/9qbbnGukc206nWOEUGhjhDcuw4tmF9WFUUh30U0bFlaXUYvChIgugigqCjLCJUWmFv2hZEJdFjbXHG/mRUhue/f++f2e53SxzaRzdTh8z+Gcw+cr1sIQwl767kBnqVh4UFGjKPYLgnAzkXguhvqJiZHT19a0AFptF2DHvh97PFf0z8hpQBIxGAicExYNM37LGuGFiXumPlwborXktR/vn2ztKBwNIZI1YpDAJAHIzMwgV3DeENWF8Nz48MlJjBXB69MPHM4Vc+9ULpLVqkgOJ4HdtCJAjIRSGW3eZr7RyA6N333yI714dqxrfbubrl733e++gtWX5Z03LMqcTPVM7Ohs2Pq+Ns1fbmWo+Wc2dNT7dYXs0mIt3ZO0lP0jzltPKSmHI4cPOEiAaCunCcmRKJPPe0vTRAlL/m++MFzW35LLPZQ4x30xRmtd18LB/XvxylsucTIgyzJqjSblUgsSCiHaclhk6o8zlrmaSRpNgG6LEBR1fXFJ5eIt/PzrHAvVZXb2buHc+V/o6minq6Odgd4eVevLZhgxGIbrdxgyDAPKLSU+++Yn3ps6w2BvD7OVq9y6oZ3BbT289cnnfPnDBcqlIma29lfvcKo4L2RYmgXbfceA7RrYysX5Cr9frjC4tYeZ+SvcNbSdOwe3WzNNAeG8MLOZxKK+NnSvECEENnVu0KNjozZbuarF6jLtbWV2+E0M79ppDq+FxjWTpBhMzvmzzmEfpPVwRYUo73wMmafZMPXdttn27dmNJ8/Gto0WUilkDq9cTAqONA2zTdc8IYDj02NPlMulN1tDX5pQckZwIEmyVZ9gREQSU1viHzfna9XaY+PDp96/gfLx8wdfzZftSKOeElILErCKMqsoJ3nnc/mE5pK9PDFy6tkbKK/FGxcefhIXn3Zet2NGjCt15wQYMepS1giTT418+vZ/ZvqfnY99u39Lrlg+FM32Ad0Iw5iT46uleu3j5/ee/utmO/8LnwFof3hW+HsAAAAASUVORK5CYII="},"HTTP 教程 | 菜鸟教程")]),A("dt",null,[A("a",{href:"https://www.lanqiao.cn/contests/",add_date:"1707136372",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACxUlEQVQ4ja2TTWhVZxCG35nvOyf3J/cmJt7YxigSUcFoRAUXulCktEWLdpNS3FgKiigIIqILf44miOhCAuLCjSIixVu6sIgtFFEKgmirMbnqQqQl4k/iT3KTc82553wzXWiMoSvBZzPDMAwD7/sSPiRQRgkEFAF0AEUIOooMdABtUMwH4R4UAQkmo4SPYmLfvi2kANDeGa+WOFwN9maouhdUkZOUT83TKPoShqaAzGOALvUFdH38AAHA7GCkKUPmFJGut9kMkjACNPldlQts7RJiCxeNAKBRMr5PpL+8vHv7x8fF5WPcHGgmA1wwfnq9Oofq6JtjEoUrQNxExi6RKAxdHN6XatLWe++3xnisshREpnHR4tMAQAv2l3ex9Y9CnRNFV1+QDdqDSjf56e0Sv4nA/I3E1elszMzeA9nO8dfbuyrdkiQ9zIa3sVcDBf7tK13qnLuzPFWR/MAWgMq13n2pP0r3c+eQVFtbdvQ3IFAGlJ4NjhymJPmCVeSpSnSHPPsTit85zqGB2Paqi3pEkp8BJRTJOYc/syZX91ZC0oHupkFV9NlwLPk6XR42xrREAPAgyD1s3f16rT/63I4VUiFQrwBQ8YfO/+PPqk5ISTJU6T/O6RTvoVztGZceOlHYOlCLVeB0yh71Gj4/m0fhEAAUtg7UZuP6Lbjx0EOHGgBo3f1kZr42t5El0cuAAREaP5vqbcJVSojwqxBI1c1r2/t6XaHRfgtFDy7PiVAkh6DkZ1P5DU5wiwCgbd/w98z4SlRIVe5KIn8Za1cCmAEVj4iHFXrBJXbQmmSuslkGplulIH/xvSUX7g3XKMerSKRFQTUgvFLgGVQ9YmoGJEPCZWUuq+jFUlf9FUBpUgaaNz/J1BWy7aCogciOVPqn3ORpj2oyXtNi4TgtVR54cKTu73HrfxL+n8JAeaJ/F9v3s4NAEEyK8n9U3jd9D2HuUQAAAABJRU5ErkJggg=="},"比赛 - 蓝桥云课")]),A("dt",null,[A("a",{href:"https://d3js.org/",add_date:"1707446158",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACy0lEQVQ4jYWTTWhdVRSF195nn3vfe7kvf7VFInTwwElNSaVVqT9g/auDKJU2gmAHIUihguDA2kwkMzsrFIpYitZhfaCgEFQyqD8DHcQ2IjgQah+l1A5s7EtI7r3n7LMdNO2gEt2DPVmbzWJ9LGCTsakpt5kGAAYQsLH6bx6aHGhlz1Qab/iMLsqJ8wsEpO8PvnLcXBoDbN05c1mOWDT5lz9/u/XZvgsXyrsPbr116PTgSHE0rq9BnZkT/Bw1vPvDr+nmUNt9O9iUdpUCvBAaOWCUFknDwc7p+R4DQO5QARHGCcmUNOnuLHdfP/dYvG+5HyeUdFUEYElYqStr5bybGvyhAUwAUL9zYFdNfidT3Cqepo3SuCKpZFQprey4fKW9nb11jLXlhWedszERRNH6Cbo3nOW5A8MF2/kEe95nRNHS2Wz2yzfu6FePvXQ2czQDl8pM9DW6m+jUFGPkMtOZxVAef7bDResSOSsSpd7qtb8mhjqjL1y7To+mRDPibTQTVN7FPf9yYAARYPX7k995oadKDesIaw/lIwOniGWy1wthuOW9kp7b8t78tGzGmdgMbCBLMACJE1yh2LItgCoB2cYdAJiB0AVjGUxHEOzkk50Q25eYrVCyXtb/e0K3De7XlB5hZzMhYjSt5ZVF3iMAQAQDoADUPn562Dx9gLW6cGVGgC3Q3E99AF0AXT2zb7TZwkxs1pZKfvA2xk/2Pgxx41S7rc7xtCWMq5GySrXyx+qO4oHmdoN2EllLPGYBjJlYFFc/vuFADstA/jY0QqOByEFEoFq/3LhfWDKaj8ZFzoYyRGsMO7JaF7pf/LjEAKDBcjgHGMNEDN4tVlXab2jczIv2ElrNQgSANzSGHJnqIrl05NUuVDbYfYV+qGLADTToIr/+zUITSOGjF2ctpnPlupXsiJ2PUcQt0e9XP6e5K+V/tRX26f/U2W4T/AfQDUhOs+hHSwAAAABJRU5ErkJggg=="},"D3 的 Observable |用于定制数据可视化的 JavaScript 库")]),A("dt",null,[A("a",{href:"https://labuladong.gitee.io/algo/",add_date:"1708135932",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC60lEQVQ4jVWTP2idZRjFz3me973f/Z+ktU3NtcGg2IpCF/EPVmjBDA4VcXDQRYz0ZnJy6HjHDHZwENKWODoLdWrBBBWEQiuIY1FDejEqliZNc+/9vvd9HofUYs92hnOGc/gRByIAB4BLH312muQSybOVp56KQsGhma+7+1r/y09/+H+GDw0G5843jx97fsWA5RBChBCWzUmCQkYJMHglztXbP966MLh5ef+/FhmcG9R7s52vW4324ihPTBnMQAlBCCeymavS3E0aoZAH+w+ul8PaO/2r/bEAsLnZzkqr2V4c5XIStIZaU3TumRaVAc1Wgd6zLYpQNUSMcjmZ6k4v1ufzCgDjlQ8vvspCv5egUA0iogSAE693ce27dc5Md3By/gXf/GmEZquBKiWnm9GBspy8IQhcLmItqAaIBooQygK/3Nji/IsLHqbr/vONTU51DiHnDIJ0EiEWIcS4HECcMcApIgICIFwSa9WMc1t4b/83bP2z6SllPn30hJdpDFIlpcoJPSNO9EBCKAQJlYCx73mOTvlrE4e3/8DC3DFu7d72cTWCSgTM6QCc3gsg4e4Pz3SQjpFNMONAd/dbFPe20a5Oonn8TexVOzhSexLZEkDA3SFR4zCGCMNBiwMwZAYJrrEFUUO30/PsRgIwM4BwVUUtFkNx9w0VpQDm7oARDdb9z2qH96fecnvqff+7e4ql3ffpxhNIluBmphSa+4bkqlxNuUpwgEI3JHTjYRTa8Dt5j7+zxZ3qrh8t5iDUg41JmOVk5XhVr966dufdV94+0m60XhtXkxKkuBvbsYt2rY06AmaKWYgLDNk956rdaNfKqvzig8/7V3SAgRSn2huq+eVOo/PcpJw4CMswCpTiAckqN0vmDnYanTgej67v7lUfv3Tzm/wIpkvnB81DUwsrKedlVY1VThARN8sgyagR5lYJZPXuzq8X+pcHj2B6DOevPlk7LVGWQJzNnntwh0pt6JbWlVh77+LSYzj/C/YTZj7JXvxiAAAAAElFTkSuQmCC"},"labuladong 的算法笔记 | labuladong 的算法笔记")]),A("dt",null,[A("a",{href:"https://mockjs.fenxianglu.cn/",add_date:"1708323288",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACQUlEQVQ4jY1TTUiVURScufe76kszAklsEQRRkEGmywraRETQ7guCFtKP5kJyU+tvEUG0aVGYPNKCKFBoYUi7aCFYUfYUnuU+iJKojT7f++6900JJDY1mdw5nDmc4M8RGEGlq0L5A4ERElkX8NzKYTfujqf0XjeuLQ3fPtyYNjV0E2iR9LpnSO/R+yAEAAkFo8wVDXa5TnbdgzVUKTcZZBB8ioHkQxekvP+8jG6shg0GGDbK4f6inpVHxJWEciD0gdzAqirJMLE1dglDJ3/ilxe7ZgSfzGE0t5tqFg2Xi3Fhkx+CVbldwI75SvUeaU3Rmn3yMBIxEAQq2wbkY4te8Vr0w2zfyasMFHQ8uXbOu7gYUdysIgLQqTSQhCBC8SaxTjIshYoDUdwAtOTWREGiNNV+2Bdcm7wPJBCs8ShJAQnLyETBsdPVJMdQ8koIDFpffG9D02frkZFjOBSIBAEkQJIIk4E19IilO+ZgfD9V8joYxLOcBYLMh8ZZEIBhXqVp5D6mVOjKxjNCLmd7hSVHPjbOGliAwbnxAUYAlAQiRq58VJAie1taFpeovVMIzpKmt5fFxWK5VAdqQxKf228T0p9bTh5uTpsJRBRnFKJKCYGy9swKifLxY6h+eRPucWbjz8ceuM10lBj9V6n04/seJRwYv9xlnrwPcSwIxqGoMJn2lerPU/+j1Oieu3LqZlQ/cPrt9287WYwhoiRbTMz3F8rq5NRtnmUG5TIyNhTV2ukVosmzzkG0BIk0tRlO7ZTr/wm9SQRk4p6E2VgAAAABJRU5ErkJggg=="},"Mock.js文档备用站")]),A("dt",null,[A("a",{href:"https://leetcode.cn/problem-list/2ckc81c/",add_date:"1708864887",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACRUlEQVQ4jY1TzWsTcRB9+5H96u7m17SmraZxI4GlFaVSKj0KelCIxwYPFRQxN0H/BPWieLGXIoskqS1Vk4oILSpVKihIRIt4EDxJKT20UPqRppu22R0PKqYhqZ3T/GbeezO84Qf8JxizWJOuz+imOVxV5v4m4l7kHstixc2ll4Lk9XtcYGljfTd5zzjVYzFdUz8CAQKUB/siVU9uNdUCIJBzmdFm2hqiz73avsgWY6wzpBQAie5daF4tZyLrNBWlUjr6nXJ97bV4vvrBGGPL2+Xp+SKdjIXFZ1fP6r1ym9LtLmJMC1PXVmnxnfvIjtWdHImYIVVVCooik6apE7uaRFxp5HiW3h6l0siJW3UFTEN7oSgyqar6hIjqOk0PcZpuQK2ucQDQ3t7St7ZW+sRx3Ozg4MV+x3F2RkdHO1oYS3jwNRFEFXD+qsBWDHJVydtmPs9/TSQSb0QA8H2K/tF77TjODgCEgsEzBw91OJWdCnwiiDyPWIBHeUuCommYm5tbyOVyR0QAEAT+5++d/cTAwMDtfD7vLi0vT/M8f6lCnsr7HjxOrKyIbNXwNjTZW2S+IHxLJpPb/y5g6BOyLFHQ0J+nUqlAAw/O0x0E65oYj8dN09A/BGSZDjQ3Tdb23aw9RjNd5GaP3awrAAC2bRvhZu09IBE4bXJh2LbpcY/lpjuf0mSUyuno7OZYPNJQAACunYubgDpzN2lSORMhGu8kmjpM5Uz0C433ttbiG/ysbml+aON6WxAJIgiA8ErypfvclR/FWuQvcBnWk4r21EgAAAAASUVORK5CYII="},"👨‍💻 LeetCode 精选 TOP 面试题 - 力扣(LeetCode)")]),A("dt",null,[A("a",{href:"https://www.vueusejs.com/",add_date:"1709211510",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACaklEQVQ4jZ2SS2tUWRDHf1X3nNtJJ22CwQTstC8iiDAK0wsXkxkaRhHc6MbHRnDnB/ADeF3Mbj6BG7c6MgxuBHFhLzJIBhyycjEEQcdsGpW86Mc9955y0UlsHysLCop/nar61/+UAJBlurB/2U9VGpEX0Ftakpezs5F2uwCg1XInOx0dX1w0mrAx+F9XP5wJZFl0AK0zekXHfrltsdjorqxtTdSbB5uOVy/gEkBzvPGX1hvH1OladaG+b0qPTs33yqwNDxxAjDZXmayeyDe3CoT36tI5k3ySVksARDilLj0EzIDM+Mmq6/c35gCUoRVxkEeMLkYvxjJi9Gi3jXbbPsOMbhzkESgAHICKCIgiqDB0RPQTAxnBURAVERll8N2mANHMzGwPHI2/hZkZuwW6t4LqpxciZmaRzgGl01Ezi4gM8wao2u4KbohZKWYYmInYTjzBy4c5AIebE2KGCWaKiRlg5V4DIu/KfhBx4rWWVEMoBa/15vnrv5ugolKP/VJczU+oahr7uUSTzl6DohKXJYS+qkv96WnNV9a3NbfJZCy9BVD2cxiXzfTHaSHii7zoDYj/DDXIMv373J03iDxKvNdktlKrXpnvypTfKEO+XYawLdN+ffxyY5DMpLUk9Yrw5/KF7C1ZpoIhCPbT09uHvPmlJPWNWIRgIQ5ss1hHTKTmp8VJJUkrrujlqwPfaz3/9be14ZHu6A7Yz4+zk4lP77sx/wOyK/lOOhrlIPxr3XC1fTFbZaRw53OHTI7cuzF2pH78GiJnCWEBUROn/wFPnr16/ZCbd8PuwK/PKss+v8w/LifYF5iNDAU+AjjYF8aLHDoQAAAAAElFTkSuQmCC"},"VueUse中文文档 | VueUse中文文档")]),A("dt",null,[A("a",{href:"https://element-plus.org/zh-CN/",add_date:"1709264985",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACnklEQVQ4jW2TS2hVZxSFv/2fk5vEV4gvpNY2KhUUjUJQwZp702JbcZ6KDoQQiwh2oFTiA/EIKkFRSlsVa9JbxQz01omgkw5MjNqiBopihXbixFAUFZuHeO85/3Jwc66Kbv7B/tks9tpr7wXvC8mI5Cq5ZGlpQaRM9pSi9B++A25VgFkCaNFx1d81ewZAJLd8JtOqQ444Y03uF2X6JrDHVYDnFQBQsGTpMc3KdSs/dSJPc906t+IHzSUyH3uGSLiF51ESc4NWvL3ZvCFSTUMD31nAbsU8AU5JbHRVzPAJB5895tCd7TbSfELL+jfbTQBLuzcPsy4M6cSYooQDQy85OrDJRhsPa3z9dDqcsUPGA19k39V26yGSIzIPQHO3vv3iNymXV77pe32UMmo6qao0X3pcc1p+1eBXF6VslzpSvcIxGh8Xn3Onr93aKkIW8AObrASw8metqqphv08YX/yfFzhml5lTXpVEgiiVVyejgAfTpz/qg8/OqKe2nt8F43xMc2mU2yaqAfgacwDOMHNQnsnUcoUAIKxli8uwvjRMW+8Ga1TI5MwElghGykzfuAOVn4Gptw8PEBuXi4McKxo+l9eloJo15hBGkuIqdyDPKJhoVZCqe73drmWmsL5uGoMuoCl5yZelYf40MRGAv19rYEEVCxtPaDoFS4gUEsllu9RRU8chH3Pgygab4Yf4K8gwv7L+BWMaxJ4eC3k4dRL3cmf0DZHFROYx7o8+ZnHvDfa15NUZ1vHIAp4njtNjIr42yYdbVfvJEnZi7BT8mxTZ3r/RLuW61GoZTjqjPvZ0lp5w+I9t9jTFWcVxZgJY9pPm1Y5jL2KtOf5zGWb6IhfiEXZd22L/pMaqXOE7ThyLbJeyn59VYWVeq9+u6y3/vAI0qSSwvH9RxwAAAABJRU5ErkJggg=="},"一个 Vue 3 UI 框架 | Element Plus")]),A("dt",null,[A("a",{href:"https://alloyteam.github.io/CodeGuide/",add_date:"1710570344"},"Code Guide by @AlloyTeam")]),A("dt",null,[A("a",{href:"https://www.zhihu.com/question/340226283/answers/updated",add_date:"1710579268",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACcklEQVQ4jT3TTahVdRQF8N8+59yr5numZlmgQVQUUaOkkUKDoonRQIIgCMLyQYOa5CxMigZBDjIsIiyKjIqmFk0alDxq0CTpSUEiWYZUfqX3Xt85578bnFeDDXuyF2utvVbYn2PFS8aeFtbXI/oKFWqhQb0ylTQSav8Ib/vZC42pV8x5XidV9DNcI2MkhMxOBJCIaGVJ8+btdbsS9uZljTUqqVbvukd+cZLJFGNpLMT/jGikBqtQudzora1CKRPVw9vkwYfY+TFlAxrxyyW2rmf1eJDQhzhzlb+uSo35RqcgdPLRu0RTc/Qxevx6niMn2L+ds1OudOLODeL14/LFYzTrqKITZcrNm8WOW9j1AUtnefUbHjzEyXP8ORXbj7DjXb48JTeOhVZET5UdCssz7jvAsUVKEWcuMPmDi5PBwOmM9gpXe5kpdWQnGzkATGbs3sEN93PbJrmwTdy7SV7AtGWULKesioiCjuhFE5XMZbFlHXdfT92wcQ2/XVDmGtEWSiF6tEKuPLRFJ6tMjFg6ze435MKHXJzx/nfiucPy6BJrR3TLw0FfBsY6sheVHFBHYzTsfYDzE3loF089IrqW1Q2lZUSsbVYYdERHI2SEaK9w041i306eeE+c+pvPFsSTH8lrV/H9s6Ivcut6XlscjM2WJggtW67j02f4/DiffEu28uBXnPidO17m8OPywNf8cIZZhTFdq4rYkxezmNu8gVs3icWfpEpEkP2wq1doVysRbhRjhFmVvbesVp09JxZ/lOoh+2nwRAymRUU16E9FpVbrvNm4ZJ9KrbGnasyV8l/vkEMjFZFl0KwWKudNvOO0ff8CDNsVBiQlEB0AAAAASUVORK5CYII="},"前端面试自我介绍怎么介绍? - 知乎")]),A("dt",null,[A("a",{href:"https://www.jianshu.com/p/dbe8bfe541ff",add_date:"1710586333",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACz0lEQVQ4jV2ST2hcZRTFf+f7vvfezOuk0cTWaYtVUEGyEhV0oaBQ0uJCpV1lUwuCBSMoLQiiIoJ/KNiCi1hdxC4UBP/gUtCNiOBG3XRRin9qaJPUUs1Mp0ln3nvfd11MI8WzvNx77uGco5VDe/bnLj9Rp3RbAoiNzHlJYhOWEmDIeQPMSWTS+SpVR4JZWADrRkjCpHZH1COsqsbX3qMsB+ewaxsSRjQsK/LbjbDgJLpDw9Kg7/KZ+7Tj5JcW7rgbTWwldHdhdc3Ui28wffQtlOfmpm+V39Z1w2Qm6Gr50L5ksZGfmLTpl9/FYiM/vZ1meYnqz1+xqwO27N1Pfe4s2Z330Px2xuqV81z5/CNZXZsjRchypl46RnNpVZdeOUz1x1mGp39i8NUntB+ZZe3k2/x9/DVsY52r33+j3uJxLCXkHbpwcNbULnFlh9a9D9J+6FHWv/uaiX0HMAlCIP1zGdcuIcuRHP1PP2D4y4+4dkmQc9jGOnE0pL5wjrL1OMpy+p8t4soOFhsUAmk0RHJMvfA6bnIKixGTCCaRRtfo7HmSyYPPs/rcASbnDtOZfYo46CM53JYO1e9n6J16b5yOJSSQGUESMgMYxxUjYftOLEXW3n8HUsPN86+S3TWDihZyjv8qIhEwAwkzGxfGedJwg/yWGW565ghgZDt30/y1AhJcf7aJMB6CpOsLCdcqaZaX6H14DARTR99EeQEpYjdymBEwM4QAI0YhEQd9wq7dbJ17FuUFKtr0P14gXemPlbtgmAnJwpjIkA+4PEd5QW/xBK7dRnkLgDTo4bftoPPEHC5kpMEaOD9WIJCct2btstZ/+BaqkVHXSk0FqTf2p6kpH7uf8oGHrf/FKQ1P/ywVLZOZtPz03tUi+O6wiYm6ElkmyfF/WNOAgKoyipa1suBGTbropDiPseQlVLRMyOxGpzcjDgE5byo75r0HY0lq5v8FS/1LVvsjW/AAAAAASUVORK5CYII="},"前端面试--自我介绍 - 简书")]),A("dt",null,[A("a",{href:"https://yeee.wang/posts/3469.html",add_date:"1710586349"},"如何看待技术匠心? - Yee's Blog 个人生活网站分享 | 王大白")]),A("dt",null,[A("a",{href:"https://juejin.cn/post/7153661619787005989",add_date:"1710586353",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACD0lEQVQ4jZWTPWhTcRTFf/f/XhNTG8wkmPdeLS0dFZwsilg/EERwExQKWigu2kFqQU1T//GLGkXBthREoYObk+DiII3g7i4i2JiCS0HaNK3Je9chSQ22lnqme8+95wz3A/4Fq2bT+H+QzmjgWfW36tnofE4dAG9cT7gxCo7wwbN6vLW2tcFrCUEF4aC00S1tdKP0gUq9tilUAFJWU/4dPdpkfasDflYHWvL+lNVUq6YlUPGtTnXlVTut5jmt8XX/YY0Ht/VRV17VtzrVqnFBtJ6I1kKdkRV6nSSjQR/7y/t0KNEOBl46SU6Fy7yrhczUNSogKt64jjgxztSWGVmYkE9c0h1BF/ecdkZqK3wREKednrDM4+IiY0zKWjqrB5wYT4h4a8SQBI65HcwFWT3PrKwWrVyvLjFoDHsxdFaXGCzmZJRJWQvG9YIboyBCv4Z0CEB6TE+6cWalDS9a5X6xgKUgtT1ZPSIGXcjJR6y6gZIzCW5plVJY4WLpgbwXrBqsRLvHtCfu8sJJ0h8u8aa8wtXFvHwH8Kz6BqadJGejZebWKgz9mJCvWDX1aTZM/GuaMLt4KDsZjip8psqARoiJ88ok6I3KPJv/yQ2eSqWpkfVVNQgAL6uXnRjTGlFFERzc6BdXSnfl+d+9Gw+q8Tjpm3o4yOl8kNNv6YweWhf+OaAt0DTJaJDOaNDKbR/bfOffVnrk9zizKkQAAAAASUVORK5CYII="},"🤔为什么需要前端工程师? - 掘金")]),A("dt",null,[A("a",{href:"https://www.zhihu.com/question/394938577",add_date:"1710750360",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACcklEQVQ4jT3TTahVdRQF8N8+59yr5numZlmgQVQUUaOkkUKDoonRQIIgCMLyQYOa5CxMigZBDjIsIiyKjIqmFk0alDxq0CTpSUEiWYZUfqX3Xt85578bnFeDDXuyF2utvVbYn2PFS8aeFtbXI/oKFWqhQb0ylTQSav8Ib/vZC42pV8x5XidV9DNcI2MkhMxOBJCIaGVJ8+btdbsS9uZljTUqqVbvukd+cZLJFGNpLMT/jGikBqtQudzora1CKRPVw9vkwYfY+TFlAxrxyyW2rmf1eJDQhzhzlb+uSo35RqcgdPLRu0RTc/Qxevx6niMn2L+ds1OudOLODeL14/LFYzTrqKITZcrNm8WOW9j1AUtnefUbHjzEyXP8ORXbj7DjXb48JTeOhVZET5UdCssz7jvAsUVKEWcuMPmDi5PBwOmM9gpXe5kpdWQnGzkATGbs3sEN93PbJrmwTdy7SV7AtGWULKesioiCjuhFE5XMZbFlHXdfT92wcQ2/XVDmGtEWSiF6tEKuPLRFJ6tMjFg6ze435MKHXJzx/nfiucPy6BJrR3TLw0FfBsY6sheVHFBHYzTsfYDzE3loF089IrqW1Q2lZUSsbVYYdERHI2SEaK9w041i306eeE+c+pvPFsSTH8lrV/H9s6Ivcut6XlscjM2WJggtW67j02f4/DiffEu28uBXnPidO17m8OPywNf8cIZZhTFdq4rYkxezmNu8gVs3icWfpEpEkP2wq1doVysRbhRjhFmVvbesVp09JxZ/lOoh+2nwRAymRUU16E9FpVbrvNm4ZJ9KrbGnasyV8l/vkEMjFZFl0KwWKudNvOO0ff8CDNsVBiQlEB0AAAAASUVORK5CYII="},"web前端简历个人技能该怎么写? - 知乎")]),A("dt",null,[A("a",{href:"https://zhuanlan.zhihu.com/p/337726263",add_date:"1710915919",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACcklEQVQ4jT3TTahVdRQF8N8+59yr5numZlmgQVQUUaOkkUKDoonRQIIgCMLyQYOa5CxMigZBDjIsIiyKjIqmFk0alDxq0CTpSUEiWYZUfqX3Xt85578bnFeDDXuyF2utvVbYn2PFS8aeFtbXI/oKFWqhQb0ylTQSav8Ib/vZC42pV8x5XidV9DNcI2MkhMxOBJCIaGVJ8+btdbsS9uZljTUqqVbvukd+cZLJFGNpLMT/jGikBqtQudzora1CKRPVw9vkwYfY+TFlAxrxyyW2rmf1eJDQhzhzlb+uSo35RqcgdPLRu0RTc/Qxevx6niMn2L+ds1OudOLODeL14/LFYzTrqKITZcrNm8WOW9j1AUtnefUbHjzEyXP8ORXbj7DjXb48JTeOhVZET5UdCssz7jvAsUVKEWcuMPmDi5PBwOmM9gpXe5kpdWQnGzkATGbs3sEN93PbJrmwTdy7SV7AtGWULKesioiCjuhFE5XMZbFlHXdfT92wcQ2/XVDmGtEWSiF6tEKuPLRFJ6tMjFg6ze435MKHXJzx/nfiucPy6BJrR3TLw0FfBsY6sheVHFBHYzTsfYDzE3loF089IrqW1Q2lZUSsbVYYdERHI2SEaK9w041i306eeE+c+pvPFsSTH8lrV/H9s6Ivcut6XlscjM2WJggtW67j02f4/DiffEu28uBXnPidO17m8OPywNf8cIZZhTFdq4rYkxezmNu8gVs3icWfpEpEkP2wq1doVysRbhRjhFmVvbesVp09JxZ/lOoh+2nwRAymRUU16E9FpVbrvNm4ZJ9KrbGnasyV8l/vkEMjFZFl0KwWKudNvOO0ff8CDNsVBiQlEB0AAAAASUVORK5CYII="},"前端合成图片插件html2canvas.js - 知乎")]),A("dt",null,[A("a",{href:"https://cloud.tencent.com/developer/article/1794300",add_date:"1710934018",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACJ0lEQVQ4jZWST0iTcRzGP7+977t/zilJBUJCxLophFBQHcWD0SFjRcFoTREzRcpTReihIiSkQ6c0QlKLFUhEHbp0aoamQmSHpkUXS6ZsYW5ur3u/HTbZ/EN/nt/p++V5Hh5+3we2g1+0f9r9FUFxEhTnnyhq4ygKlNApJ4HLCIcB0Ihgcpd76ulmA9sWcYf04uYZgheLmwg3yOKllDAdcrvALUbLewMRxUU5wRUROrN3tmS9JH1cF6FdziKiqBUjb1bkdkHe0CaTBWMxaFknAu3SRofUF6fOiSMzB3B6r/KdRty2OdzJXg75BjYkGJYmHPjJsBfFBzLc4pyaVkzMHkF3vmbNNNHMESytmvJdR0nEw9gTrdTUxHki/XhoJs4EFh/RaUTHwKReMRadwu6oIP7zIHXVCwC8+9KFx3WN5ZXjzO5rooLzLNFFQPUB0C+78TDOGkvwaVEYi3YDEI066JHcZZ5PVTIio7wQ4aG0AtAjNnryvRiUbkZFdNIpE0uqAPD50gA8kFKWuY+bY8QIEFJDOQNlAav5X6kijamzmhykrLyZyNxb9iw85mu1h28M46SOBEFCaoiwuPiBBYADDQen8RDiFwOKyMwOjJJHeL0NLCaSzFfacTt0VoA0KcCVuxigEBSKEiDJK1IECh2YnmtgvqyGWEUJmpXBsmlo2JH8KyCLwThn1Mv1xaZa/g/WiwQQDmvs9Ofmz5MKarfX7EeIIZxSWYDfa9nVNFgL8gwAAAAASUVORK5CYII="},"面试官:Vue要做权限管理该怎么做?控制到按钮级别的权限怎么做?-腾讯云开发者社区-腾讯云")]),A("dt",null,[A("a",{href:"https://blog.csdn.net/zzx2015/article/details/114651591",add_date:"1711037154",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAB/klEQVQ4jU2SPWtUYRCFz5n3bvbubhISoyJiJxEsEkglghZJZUTUJkhAUoja2WihgohFQPAXaGVhk8rGRkQrURDEFOL3B2idkDXJ7t3Nve8ci7ubZOozZ2aeMyzmptAvSSBpBgkxgkQIkOQOkgCAZLeaJEi1NkBDWgeEzXUkFVZTuIPcaSjVkqPb5bEZTp/H4aPMc//0Hs+X9Psb0hrlILm9kuRw5+XbNjsPuX//yNEx239Inbbfu6IvH5jWISU9fzNlmV26FWbn/d0rf7SI5oosaPosF25gZC9iLH1ZzE0JRLeN8YnwYEl/fsSb8+xkTOvyiK0O9h3ERhMSAZAGgKTy3E6eppmePcHmOhtDgmjG2iDXVtjnAcAAyCNrDYxPSNLXZVZTxYIlZTmSBKSkciUrb0BSwfAe5DnW11DmUE7u6bhN3/opRHTbSALSGgSaARDAEOCuYmu72QAwJGq39OszLXDqhP6tKjrc6VEbTdUaHDugIufODRAt6OVTxcIWrtuZi6imCokGUkwet7sPbfExBqpy72Etc0C7xVMX7OodVCroZGquojHIoRECvvzG71+DnLRdSQPIWhiftJlzODLJ4VF1Mvz9qeXXevsCRcEQ+hN2Ps/UaaPIUWswJJKjm6EoWB8syZLsPV8PgiLTGlhHjIoFSaR10OQRpSPwH79sFlWOAVADAAAAAElFTkSuQmCC"},"2021 腾讯校招 + 后台开发面经(已 offer)_腾讯校招 后台开发-CSDN博客")]),A("dt",null,[A("a",{href:"https://zcmima.cn/#/resume/edit?id=MDAwMDAwMDAwMIW5pd6yqrui&tzUrl=tem0021&language=cn",add_date:"1711115295",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAB90lEQVQ4jVVSv2tTURg957sveXlpmmpraqSIWPFHcRd1EXRwcdDVP8HV0cFJJxF16eBQQRAEHQTBwUWtSPEXgmAVh1QUiWJ9aWKSJnn3+xzeS0jPcLnn4/vOvffcQ1VFBgUk3dXiF+++L/Z9c3765NE9F9MiDUbQzBRGy4aMePb18sf6vZni/pwrNrprUW772cNLk4XdgJGE917H8Kp2/drzXT8ar1Pa7f19+OH87ZVj/aRrXlUV491937m1vPDp1yPdguTm8oHVelaU4e1BstOPez6eLS0YMQJNSvnq7/Zq2kNVRdKyQUw6zU81u2+mikcEE2MzbG+ulPKzJnsBUFVRf4DaDYSVeH7xyZ/qIOkY/MgWAKGbTHRg1ju3rxgAoO9bsoFCsd7pXnj5M9Be6EiaGb0aAME6HAoip+YKAUmbOdF2VY98B9MHy+0AuZ5nz5ujToRODIPENweI8qQgMDMU5i6tVR5/+3d8R//pmZ0kr76N73xpHdqWu3+6GgW8+7l55X0jDB2AIH3XRqex3mq3y1E574yIcmJm4lCJAsKiQBQmBgCS/nEgEjoEMgzJMC6biQLwChrMDFDJ7KPSoNTMHDOMIaUkzTjURKqZUZLjAyklHUkhSYNB1G1pGgmPVtUESF0inIFKBw4zq2IgXBYHUkTSE/4DdDYk/Z+Qv00AAAAASUVORK5CYII="},"职场密码-AI智能简历制作与优化个人简历模板自荐信一键生成")]),A("dt",null,[A("a",{href:"https://getcssscan.com/css-box-shadow-examples?ref=producthunt",add_date:"1711196621",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAChUlEQVQ4jVWTTWucZRSGr/s875vM4FRLS0syZhEhoRuNkHHlxzYi5C8oZOMikFXI2i6kP8Jf4FYRKYJiybSgLlzIuHERoiCYdjJG6ny8M+9zu3gnBc/q8Hzc577hOtrZ2dlIKX0WEXu2BUgStiUEgDGSbBvAy/6h7Y9Tt9v9vCiKD3KdIZCaW0kCAYIXglpqZkhFumd7W71eL19LKzsMVplwnf/vIIU9rxHIocyywjYIkS3fKB0fbclv3Zbn+YUDzzPu3VZ8uCW/VJps0agrcBPas1q694ooAn54isq4HtL0Pz6FMtDWjeZtk4uQBDZCNuDLGYwXsPwfEUSK5mw4wxIg42Z+AZYlXGdFp4BFxoJALBYLJpMJK6srrCgRCN1ps8hZFshW6nbX73uW0Zu3iHfX4Ks/YJbJzhweHtLr9bj654rLyxHPfx/iN25S5gQXEyhEAUKF4M8JHs5grU16VvF88i9VVXF0dMTBwQGDXwecfvuInwY/89vFBUoBiNRdX7+vFPjZFHVK2H4Z/zLChbj6+4r9/X06nQ4br27w9nvvMJ/MePTNdxSrK2ATuCFMRdgJmBtPa1qrq5ydnTEYDKjrGoAnjx/z6YMHlK0Vs6QylpjiQBpW6LUOev0mkcVkOuH09JSUEv1+nzt37/L+3h7VrGpIBVK3u/6JDSoChlOY1nIR8NeETGYxXzCfzzk5OWE0GnF8fOx+v89wOKQoCrS7u5uBBmUpPK2NgNWkpU2qqqIsS1dVxebmpkajUR6Pxw0jwNcppQCwbbWT1EqSm2iSaLVaRITa7bbOz889Ho8pyzKAL9La2tr3trclbS83BxDLreQ6q5uiKApHhHPOX06n08P/AKpzTdMufXz5AAAAAElFTkSuQmCC"},"93 Beautiful CSS box-shadow examples - CSS Scan")]),A("dt",null,[A("a",{href:"https://prazdevs.github.io/pinia-plugin-persistedstate/",add_date:"1711540990",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACT0lEQVQ4jX2RT0hUURTGv3vn6huHec4M6sxoZYSL/hAqQYQiKLVoFQY1GEQlCYJG0LZMUIqgVlYguIgCoYIiIg2kAmkzLZJAIQpNIrE/TtJoM895896997QQpdEZz+47HH585/uALaZv5k7ptfm7PUTECt3wrQArIj1kycz1nm+3WgrdiP9FbKIzoOzsKHP1WKgsPJZy0m2McTDwowDG8wFyrMXenN6nJP9IrobwG6niMp/JGYe2ZXxlLrnASkU9SsTNZ033h/I6sDM6IWwFcglVpUVmxONgThISyWwjXAI5BHh0pmAGI8ceLYY0JvtrgxjYZeBysBiDIY7uCgNCE+DqNDH2omAGNHPR0F7FOAMgs8DyL7BAFEfCBgSZGJi2ep8ff7BUEABhdXHFa8GLAaUAww9oBRChudyD5rA5ubHP3BqVe37h73b03wsC3AA8RVi0duDheA2gJOA4HRtbWAfQbCwA6e4POH6cORzB/I8EpqZ/w8f/IMK2QVleQMmGjYB1RzR1Yg9x/YkxtrpmABiD1gw/E4RIZTk8Qqyc614OD78atja/oLMpSAeZ5STs1BIgHcB18DpeiVNdCqnwDfTejvrIqH7f19InNgFY/eh3ptwvHASS7ipAOrBtBjMYxsjTOBT5AF6yN1FR4c0fonQvGYIcbxGHcmxAOSCtcLVpJxq/fkCHmcbBqC+3uJxADo2/pHhDHePirIdQB1eZaUtU7W5vqwlFAwCAkscTwNvZ/AAAYI3vPgO4sqY7YyerD7Sy1jXtN43k4JML6TX9DwBS7xe4x8KsAAAAAElFTkSuQmCC"},"Home | pinia-plugin-persistedstate")]),A("dt",null,[A("a",{href:"https://zhuanlan.zhihu.com/p/541312930#:~:text=%E5%9C%A8%E6%8B%9B%E8%81%98%E6%95%B0%E6%8D%AE%E5%88%86%E6%9E%90%E4%B8%AD%EF%BC%8C%E6%8B%9B%E8%81%98%E5%AE%8C%E6%88%90%E5%BA%A6%E3%80%81%E5%B7%B2%E5%85%A5%E8%81%8C%E4%BA%BA%E6%95%B0%E7%AD%89%E5%8D%95%E4%B8%80%E7%BB%93%E6%9E%9C%E6%95%B0%E6%8D%AE%E9%80%9A%E5%B8%B8%E7%94%A8%E6%8C%87%E6%A0%87%E5%9B%BE%E8%A1%A8%E7%A4%BA%E3%80%82,%E8%80%8C%E6%B8%A0%E9%81%93%E6%95%88%E6%9E%9C%E3%80%81%E5%90%84%E5%B2%97%E4%BD%8D%E5%80%99%E9%80%89%E4%BA%BA%E6%95%B0%E9%87%8F%E3%80%81%E5%80%99%E9%80%89%E4%BA%BA%E5%B7%A5%E4%BD%9C%E7%BB%8F%E9%AA%8C%E5%88%86%E5%B8%83%E7%AD%89%E5%A4%9A%E4%B8%AA%E7%BB%B4%E5%BA%A6%E7%9A%84%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%9C%E5%B1%95%E7%A4%BA%EF%BC%8C%E9%80%9A%E5%B8%B8%E9%87%87%E7%94%A8%E6%9F%B1%E7%8A%B6%E5%9B%BE%E3%80%81%E9%A5%BC%E7%8A%B6%E5%9B%BE%E3%80%81%E6%9D%A1%E5%BD%A2%E5%9B%BE%E7%AD%89%E6%96%B9%E5%BC%8F%E8%A1%A8%E7%A4%BA%EF%BC%8C%E5%BE%80%E5%BE%80%E5%8F%88%E5%90%AB%E6%9C%89%E5%AF%B9%E6%AF%94%E5%88%86%E6%9E%90%E7%9A%84%E6%84%8F%E5%91%B3%E3%80%82",add_date:"1711766438",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACcklEQVQ4jT3TTahVdRQF8N8+59yr5numZlmgQVQUUaOkkUKDoonRQIIgCMLyQYOa5CxMigZBDjIsIiyKjIqmFk0alDxq0CTpSUEiWYZUfqX3Xt85578bnFeDDXuyF2utvVbYn2PFS8aeFtbXI/oKFWqhQb0ylTQSav8Ib/vZC42pV8x5XidV9DNcI2MkhMxOBJCIaGVJ8+btdbsS9uZljTUqqVbvukd+cZLJFGNpLMT/jGikBqtQudzora1CKRPVw9vkwYfY+TFlAxrxyyW2rmf1eJDQhzhzlb+uSo35RqcgdPLRu0RTc/Qxevx6niMn2L+ds1OudOLODeL14/LFYzTrqKITZcrNm8WOW9j1AUtnefUbHjzEyXP8ORXbj7DjXb48JTeOhVZET5UdCssz7jvAsUVKEWcuMPmDi5PBwOmM9gpXe5kpdWQnGzkATGbs3sEN93PbJrmwTdy7SV7AtGWULKesioiCjuhFE5XMZbFlHXdfT92wcQ2/XVDmGtEWSiF6tEKuPLRFJ6tMjFg6ze435MKHXJzx/nfiucPy6BJrR3TLw0FfBsY6sheVHFBHYzTsfYDzE3loF089IrqW1Q2lZUSsbVYYdERHI2SEaK9w041i306eeE+c+pvPFsSTH8lrV/H9s6Ivcut6XlscjM2WJggtW67j02f4/DiffEu28uBXnPidO17m8OPywNf8cIZZhTFdq4rYkxezmNu8gVs3icWfpEpEkP2wq1doVysRbhRjhFmVvbesVp09JxZ/lOoh+2nwRAymRUU16E9FpVbrvNm4ZJ9KrbGnasyV8l/vkEMjFZFl0KwWKudNvOO0ff8CDNsVBiQlEB0AAAAASUVORK5CYII="},"如何配置一份一目了然的招聘数据可视化看板? - 知乎")]),A("dt",null,[A("a",{href:"https://acm.hnucm.edu.cn/JudgeOnline/loginpage.php",add_date:"1711859156"},"Login")]),A("dt",null,[A("a",{href:"https://exam.nowcoder.com/cts/17321084/finish?id=E809FE86E69125CA5D2B1AAD67C4DCC3",add_date:"1711892253",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACgElEQVQ4jY2Ty2vUVxzFP987NzOTeQSJCZj4DKhDCqLRxKALF/URalEoiOAi4KKLKthFC0YooqFS6l+ggq58QCmiVkJiFK0YEomPVkEkkdJSXERjM2ptks7M7x4XmaShhNIDZ3G5n+/le+EcJC2V1CMpSIokhRCCQgia1qzzDFOeWWqSrgHbJEVmFpOEJOaSmWFmTLNAr2mKDoArlUp47+ccntYsJgDOhxDknLMbN38km0mrdX2LDQ0/o1AoYGYASCKRSLByxXJ+evRYY2N5a9u62UII8s45kOzkqdNas3qVpVJpDn51hMbcCkKY+kos5rj/4CHfHuukr3/A7vT1q23Lh+ack5eCmaCuIWdVtYsYGXlBa/M6jh4+hKSZLXrvDFI9v5p4tprFyz8wIUwyjzkw+GZXI9mxAW5MJEmk0nRfu86Vq10kKjN88flnbCt1Q2IruQUPGF+zAcwhwNsfv0DPETLvXhI2HSA8T0H0O9lshnXNLTS3tlLTtQ+lK2Hjl9AwSuruSRi6Am2dOE28gd8GiJLzsIaN4DyGyOdfk/BG0/Bx0lVVjO++QNFnYNkGQqoG/doPE3mcLVoLHU+w2hx28VNsfJSJQpEdH39Ee67A36/ylPac58SpM9zuvoT9sB9lF2IdT2HJejwhgE/C9q+hOI5u9VGZTE4Fp6WdRMteAP56m+fPQi3sPof81D0hwuOckIyoJCpSFquoZHDwHmcvfEcxChAi4vEKHv78mLVNTeCTWKkoYt5wMZmkABBCMOecXrwcte8vXmZycvKfICHiFXF2fbKT+vo6TbMAs6NsZf8fqWzngF7AlR+RJKIomtPlks10B+jlv+r8b89V5/fK65HJ363k1QAAAABJRU5ErkJggg=="},"2024实习生招聘-前端开发、UI开发_牛客")]),A("dt",null,[A("a",{href:"https://www.alloyteam.com/nav/",add_date:"1711988987",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAADB0lEQVQ4jXWSz2tcVRTHv+e+N5lJrEmYUMgkDXHRack0ksVEi8UwRINmaqhaJdDQ2lpE0GbnQrqovCylC3fFfyJoFnUhhDYk2CoxYEXqxkbElISOb2xm3q9777nHRRJpQb/Lwzkfvt9zDvC0FAC8Mzt7fm3tzsajR2F7e7vRWl6+/f3MzJvvPtnzXyIAuH79ixsiIs45NtZJlrEwO8cs8unVa5//H8QDgIsXP7giIhLHmY6TjJPESBRr+ftxxM1m24iInDnz1vknZyAitF/Ira9v3BcRieKU09RIkhiJokxa7VTCZssmKcvNm9/+8FTmhYUFAoByuXy0XD52LNMMpTwle3AQ7fEdi2o2QxwZOnJiYGBgCACCIFDqIM/o6NgL3d3PKLaWAQFEoDwFay2SOEGmU8rSlPv6+rpOnZo4fWDg32VM16fr+5FEKQXlKUTtCHEcQ8QBAlhmUcrD2NjYSQCo1WrwgyCwAAojI5XnAcDPeYoANP5qAgJ4vgdrLIwxYGaKohiHD/cfBUCTk5NWAcCFC+9/MvHySyfiOGUC1MOH22BmkCJoraGNgTYG1rIXhiFPvjI18Xp95goAqCAICnNzc+8xA84JbW7+AQGgFMEYDWYLaw2MMcgyDa0Nhc0Q9ek3LgGAGh4eHiqVBgY9D3iw+Ts5ATxP7Vt2sJbBlmGMRaYN2Dnq7OxCb29xeHFxseRHUaS3tv7cvXP3uwfV8RdHDx16FlpbECmIuD0IOzAzRBycc25t9dYvvp/rJnjabzQaW/Pz86+WSqWur5e+WVWkOlmcEIGsdY7ZEQASiPh+jnZ2dlqfXbs6F4YhB0HQPDjj/cuXP7w0ODjUmaSp3v8hVywWVU9PD1nLzrFIEkW6v3+w9+2zs+cA/Fqr1ZRfqVQEAHbbrXw+34FisejlC3nyPY9u31r+jYi86vjJ53ZbLcnlcp4IkMRxHgBWVlZAIkJEJACKU1OvfVytjlc7Ojq8e/d++nlp6asbhULBr9dPf3T8+EglyRK7sf7j3dXVlS8B7IoI/QP5zrTVsKidRQAAAABJRU5ErkJggg=="},"Web前端导航")]),A("dt",null,[A("a",{href:"https://www.zhihu.com/question/21520021#:~:text=%E4%B8%80%E4%BB%BD%E4%BC%98%E7%A7%80%E7%9A%84%E3%80%8C%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91%E5%B7%A5%E7%A8%8B%E5%B8%88%E3%80%8D%E7%AE%80%E5%8E%86%E6%98%AF%E6%80%8E%E4%B9%88%E6%A0%B7%E7%9A%84%EF%BC%9F%201%20%E6%9C%89%E6%89%8E%E5%AE%9E%E7%9A%84%E5%89%8D%E7%AB%AF%E5%9F%BA%E7%A1%80%EF%BC%8C%E4%BA%86%E8%A7%A3%E4%B8%9A%E7%95%8C%E5%85%88%E8%BF%9B%E7%9A%84%E6%8A%80%E6%9C%AF%E6%A6%82%E5%BF%B5%E5%92%8C%E5%BC%80%E5%8F%91%E6%96%B9%E5%BC%8F%EF%BC%8C%E6%9C%89%E5%AE%9E%E8%B7%B5%E7%BB%8F%E9%AA%8C%EF%BC%9B%202%20%E5%85%B7%E5%A4%87%E8%B7%A8%E7%BB%88%E7%AB%AF%E7%9A%84%E5%89%8D%E7%AB%AF%E5%BC%80%E5%8F%91%E8%83%BD%E5%8A%9B%EF%BC%8C%E5%9C%A8Web%EF%BC%88PC%20%2B%20Mobile%EF%BC%89%2F%20Node.js,6%20%E5%AF%B9%20%E5%89%8D%E7%AB%AF%E6%8A%80%E6%9C%AF%20%E6%9C%89%E6%8C%81%E7%BB%AD%E7%9A%84%E7%83%AD%E6%83%85%EF%BC%8C%E4%B8%AA%E6%80%A7%E4%B9%90%E8%A7%82%E5%BC%80%E6%9C%97%EF%BC%8C%E9%80%BB%E8%BE%91%E6%80%A7%E5%BC%BA%EF%BC%8C%E5%96%84%E4%BA%8E%E5%92%8C%E5%90%84%E7%A7%8D%E8%83%8C%E6%99%AF%E7%9A%84%E4%BA%BA%E5%90%88%E4%BD%9C%E3%80%82%207%20%E5%85%B7%E6%9C%89%20AB%20%E5%AE%9E%E9%AA%8C%E7%9A%84%E7%90%86%E8%AE%BA%E7%9F%A5%E8%AF%86%E5%92%8C%E5%AE%9E%E8%B7%B5%E7%BB%8F%E9%AA%8C%E7%9A%84%E4%BC%98%E5%85%88%E3%80%82",add_date:"1711993755",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACcklEQVQ4jT3TTahVdRQF8N8+59yr5numZlmgQVQUUaOkkUKDoonRQIIgCMLyQYOa5CxMigZBDjIsIiyKjIqmFk0alDxq0CTpSUEiWYZUfqX3Xt85578bnFeDDXuyF2utvVbYn2PFS8aeFtbXI/oKFWqhQb0ylTQSav8Ib/vZC42pV8x5XidV9DNcI2MkhMxOBJCIaGVJ8+btdbsS9uZljTUqqVbvukd+cZLJFGNpLMT/jGikBqtQudzora1CKRPVw9vkwYfY+TFlAxrxyyW2rmf1eJDQhzhzlb+uSo35RqcgdPLRu0RTc/Qxevx6niMn2L+ds1OudOLODeL14/LFYzTrqKITZcrNm8WOW9j1AUtnefUbHjzEyXP8ORXbj7DjXb48JTeOhVZET5UdCssz7jvAsUVKEWcuMPmDi5PBwOmM9gpXe5kpdWQnGzkATGbs3sEN93PbJrmwTdy7SV7AtGWULKesioiCjuhFE5XMZbFlHXdfT92wcQ2/XVDmGtEWSiF6tEKuPLRFJ6tMjFg6ze435MKHXJzx/nfiucPy6BJrR3TLw0FfBsY6sheVHFBHYzTsfYDzE3loF089IrqW1Q2lZUSsbVYYdERHI2SEaK9w041i306eeE+c+pvPFsSTH8lrV/H9s6Ivcut6XlscjM2WJggtW67j02f4/DiffEu28uBXnPidO17m8OPywNf8cIZZhTFdq4rYkxezmNu8gVs3icWfpEpEkP2wq1doVysRbhRjhFmVvbesVp09JxZ/lOoh+2nwRAymRUU16E9FpVbrvNm4ZJ9KrbGnasyV8l/vkEMjFZFl0KwWKudNvOO0ff8CDNsVBiQlEB0AAAAASUVORK5CYII="},"(1 封私信) 一个优秀的前端工程师简历应该是怎样的? - 知乎")]),A("dt",null,[A("a",{href:"https://http3check.net/",add_date:"1712075288",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAADV0lEQVQ4jW2TXWhbZRjH/8/7npPPJWmSpswI65y4tZu4iaObSlEYqR+dYnW9mnOITOxYb0YLA4c9Tq1U2g0RLCJjc1fDMmHuoo5OKVqqOOeFxrFgO+00XW3SfDRN0pNzzvt4UVqK+Lt74PfneeDhT1hPZ6fEyIgDAPuPDh6AEL1EXLVtHhgd7hld5ygADAC0kjQEYCgAaHvjvQd16TnF4I5abRlEAi5dQ82hzy0L74yf7U2uz9Dq8j0HjWC0bkMvmHsU4PG4XNmt9++ssFOTydvTXq+sRmzLMi2lfbhUzQ5+d+79zNoFT3X1t+q69xNmbnZsa+mBzc3Z5m0PN4QjDb7ckkLM/CHfuHzF7P/+noCppF8jmnFss2t0+MQotR3q8WvBhptCujaFg+H0Q9v3eOLxzdGlpcVMpuI+lQhcOtBSP/3EleJBJDOeXOq3b5ezxVycHWtuoVjdJqQ7FlaOqodSxUd37wtFI7FoLjd/wWWVW94MdMZb9C9bswX79WSx8aN4JBjZuzsRFMCiUioYCgVCmq2gBFjpUq9KqW00zepY94ubDvPVl87jvq2H7ZnSu7HEwKfAAIYvp5tIiIQm9LkamwFbVZUAACKCYiYQQQJFwzCEkoFfUDahwWoEAMMwBIFKECsuCQLggwgEbQYzAICZoYRgoA9i37kzvFAeQb3vkDP2SrfR18fEjiAmrEcs13RmAIKIAYZynIBhkCIidtg9A78HApUmEDELqmMoCCJmxQAq0BxbJ5KKbGX5FKuy1+N9uv+LytDRwMkpTdw+jum0cRFXPxu8kDqrSc9jNcss2Y7lIwK5dZ00UyuWfOwrKcbGH69fSzfteNLcVX/3eCjoxeR8x9cT9suZbHr8qxIWtugF32JhLqMUISak/LsyVynIP6+P17Y80jal6bK1WCnfuzCbdBLRidxE+Vn7jrd9exD59pliyt77WsJfKhc2TP18y6/p2u+Oso9cO38yJQFg+sZYKrZj50Wv5teYnccn74aDP/1RqHhQys3OpxfTs3fqdJfH/c+tv8qFdGaoBOfVbz4+cfN/y5Q48sEuKektKbmjZlkAAE26YFWrl/L5/Ns3Lp/59b+ZVQiGIVaH9q6h55/rPj2x/9jpyWeODb6wZq04a7/8F5bMma1aZnAbAAAAAElFTkSuQmCC"},"HTTP/3 Check")]),A("dt",null,[A("a",{href:"https://vue3js.cn/interview/linux/thread_process.html#%E4%BA%8C%E3%80%81%E7%BA%BF%E7%A8%8B",add_date:"1712243809"},"面试官:说说什么是进程?什么是线程?区别? | web前端面试 - 面试官系列")]),A("dt",null,[A("a",{href:"https://vue3js.cn/interview/webpack/webpack.html#%E4%B8%80%E3%80%81%E8%83%8C%E6%99%AF",add_date:"1712491571"},"面试官:说说你对webpack的理解?解决了什么问题? | web前端面试 - 面试官系列")]),A("dt",null,[A("a",{href:"https://www.tencent.com/zh-cn/business/tencent-cloud.html",add_date:"1712666528",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAB3klEQVQ4jX2Sy2pTURSGv7X2PrlWHYkidCJCi460XkAQOgoo1NpKfBHBiYgRBMFH8BXqfVDQQXWmYLSIikhAJ4LBgkiNbdOcvZeDk9qmOe2abNgsPv7LgqExB8ClL1e5/PX2ln8d3oWcTwnZY1Mke64z23rOxQ8nQWIeSKi9r+IqjiQYKx2lMhIRN4qGN4iWcGUldLvAXX78vMOrs6vQUGgYYML0p3P4wmPMFDMQBCgAZQwDC4gqvqqk3bfY+jUejS9sqBEAZj4/oLR/lu5vUAcWIAbrwwxUsADJCMQexN491no3mT/WzgDTraMkukCMRSyCUAIpYlEQAayDSczIIqjfi9kiMdyQ/2nUnlVxh7MsnN7HFWvELmDf6axNUCqubhbSCXCoQtoWYfucbxUphW+IO4grQ/j7hIdjM3kVbquxX4/vHUf0ABYC6gF7B8DkC787oE6mJklO4KsKGGlHiL4JwMvJmAfYpM7dskyITYAaoo6YdlhfXuyfTC5gi4VGzA4kniKsCOIVkY/Mn27v5J+hU75wZRQ4QkxTfCUCzYF8dgb0Fwp6Bl8tg3ksKpZm8utzw20NADYWlDHU/0Jkid7yErHwOsunnusfYJA81ayQ7suCTYLxdPzPbv4B/gGOYbP/COCPlQAAAABJRU5ErkJggg=="},"业务 - Tencent 腾讯")]),A("dt",null,[A("a",{href:"https://aotu.io/",add_date:"1713023755",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAB3UlEQVQ4ja2TP2hTURTGf/e+l+alVWPbECo0/QMiIjooiCDVoS6CoqBQOomLVHFpI24puAgVp1KcFByUKkhdRBBxKIhuDhVxEQxtY8U/DcY0yUvee/c4vCZtiovEb7lw77kf33e+c1TKyRpagA2oVgh0K5/rChoQARNsaLKs8AyCegFoC5T6C4EIdHZpTp3roLff5u28y5v5CiIwNOxwbDjGSs7n+VyJ/KppkFhxe/wGAlFHMXmri5Nn2gEYvbiNL8sBqQGbm9PdBAaOn4ixZ18br19V8P1QiQYwAn2DNkeGHCYn8lwa+cHLZ2XOjnRw4FCUudkSY6PfyYyvcvholL5BG5EtFmpVIQhgV69FIqlJJC3KJeHudAHPC/uQGrAJfHBdaU5Ba1he9HnyYI10ZiePX/Swe2+Eh/eKFH8bxAgXxraTznQye79IbtFHr+enUk5W6k2MRBT7D7aR7LH4+L7GUtZnR1xz5Vqc0+fbmZkq8PTRWlNSKuVkxZiwB1evx3EchTEh2YeFKt0Ji8vpOJ8/eSy8q1IpC3duF1jKhips1sm8mvA1F+DEFCKhrfxPw7eVgJmpXwhgWQq3Ing1aYxvkwWzZSvqWYs032u98WZvLq5P3r/gv+yC2wrBH+ojsjIDU6YeAAAAAElFTkSuQmCC"},"Aotu.io「凹凸实验室」")])]),A("p")]),A("dt",null,[A("h3",{add_date:"1689396661",last_modified:"1699445205"},"学习资源"),A("dl",null,[A("p"),A("dt",null,[A("a",{href:"http://www.chaoxing.com/",add_date:"1680313834",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC30lEQVQ4jV2TS2icVRTHf9+d15fUyXOamVhTJg3OosRKTAWtpcQgiMZHcVGLuLALmVJKRcS1m+5KUBGExGqxFFFcFcw21hetYE2tCCKpnRQSkjY2mXHme9zv3nNdJCmpBw7nLM7/LP7n/Dz+F1eHB/fhpCqBHrdr0R7T0BixNY2bTZCpCZJr2+e9rebbctnvKeYmPd8/rnJZ5ZxgWyHmdh29UCdKDCFWIuzZBvLWMYgAUlvizr7sTLbQeyRXKnnZQpF0vhOVy0IGPGfYOfEi/17/wyOVGrXOHnwBvroIJgVwqpR/P1vofjVX6scv7SbX9xDpfBeeUjirUWmonL9A7yPD+JUhVn78qWygcBFmvCvZB/Zmhrp/94cGVNvuIdoGKuSKg0jUQryETKlI/sBTqK5uEMN3w49x+8Y8LZxEMKKMcW/iUNuNUTva6Zh4no5nJ8j076L1y2VcGLD06Xl2vX703piFalpExqWlkTDENus4BTueGSNZXIS0B2Ixd1dZ+vBj/nrvNAEWh0MAgTFlcGWzGpL8U8eGIYXX3qB19QrpUh/x/J/opQX84cdZOH0Gi9tMEMBAWSUIOkzQtTX6Dh8lWrxJpjPP2jdfkz84RnLnDuuXfkAnMQmOBMFsiNGA0kgtxpLJF+h66RXWvviStkdHCX69juroILq5QvrBEjFCjKBx6E1xDDUVIbMRQteRlyGlCBZWwHMEfy+DWIL6Ot1PHyJ/6AAxlnhTHG7USypGPgmx0r5/BNEJzWYDRJBigcbcHAOnTvDbO+8ycuEzBk9UMdksAdAC0TDlAXwOH+3cP3qyraeH9cs/0/vkE0TLy+hGg/bKwzRu3SIKQ9orFdZXV5mfu0ZLqelJkaoHcA58AzMKxtU2QASw2wwLgSYQeN73zX733PQSwT2YzoHfhEnguMfGY20t0GyQE4DESp0NRN6ehuA+GrfiDOwzULUwbmDP5slqCcxamPoA7sP5P5rBWlX2FcYgAAAAAElFTkSuQmCC"},"超星")]),A("dt",null,[A("a",{href:"https://www.nowcoder.com/",add_date:"1681227402",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACgElEQVQ4jY2Ty2vUVxzFP987NzOTeQSJCZj4DKhDCqLRxKALF/URalEoiOAi4KKLKthFC0YooqFS6l+ggq58QCmiVkJiFK0YEomPVkEkkdJSXERjM2ptks7M7x4XmaShhNIDZ3G5n+/le+EcJC2V1CMpSIokhRCCQgia1qzzDFOeWWqSrgHbJEVmFpOEJOaSmWFmTLNAr2mKDoArlUp47+ccntYsJgDOhxDknLMbN38km0mrdX2LDQ0/o1AoYGYASCKRSLByxXJ+evRYY2N5a9u62UII8s45kOzkqdNas3qVpVJpDn51hMbcCkKY+kos5rj/4CHfHuukr3/A7vT1q23Lh+ack5eCmaCuIWdVtYsYGXlBa/M6jh4+hKSZLXrvDFI9v5p4tprFyz8wIUwyjzkw+GZXI9mxAW5MJEmk0nRfu86Vq10kKjN88flnbCt1Q2IruQUPGF+zAcwhwNsfv0DPETLvXhI2HSA8T0H0O9lshnXNLTS3tlLTtQ+lK2Hjl9AwSuruSRi6Am2dOE28gd8GiJLzsIaN4DyGyOdfk/BG0/Bx0lVVjO++QNFnYNkGQqoG/doPE3mcLVoLHU+w2hx28VNsfJSJQpEdH39Ee67A36/ylPac58SpM9zuvoT9sB9lF2IdT2HJejwhgE/C9q+hOI5u9VGZTE4Fp6WdRMteAP56m+fPQi3sPof81D0hwuOckIyoJCpSFquoZHDwHmcvfEcxChAi4vEKHv78mLVNTeCTWKkoYt5wMZmkABBCMOecXrwcte8vXmZycvKfICHiFXF2fbKT+vo6TbMAs6NsZf8fqWzngF7AlR+RJKIomtPlks10B+jlv+r8b89V5/fK65HJ363k1QAAAABJRU5ErkJggg=="},"牛客网 - 找工作神器|笔试题库|面试经验|实习招聘内推,求职就业一站解决_牛客网")]),A("dt",null,[A("a",{href:"https://zhuanlan.zhihu.com/p/348509956",add_date:"1682147332",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACcklEQVQ4jT3TTahVdRQF8N8+59yr5numZlmgQVQUUaOkkUKDoonRQIIgCMLyQYOa5CxMigZBDjIsIiyKjIqmFk0alDxq0CTpSUEiWYZUfqX3Xt85578bnFeDDXuyF2utvVbYn2PFS8aeFtbXI/oKFWqhQb0ylTQSav8Ib/vZC42pV8x5XidV9DNcI2MkhMxOBJCIaGVJ8+btdbsS9uZljTUqqVbvukd+cZLJFGNpLMT/jGikBqtQudzora1CKRPVw9vkwYfY+TFlAxrxyyW2rmf1eJDQhzhzlb+uSo35RqcgdPLRu0RTc/Qxevx6niMn2L+ds1OudOLODeL14/LFYzTrqKITZcrNm8WOW9j1AUtnefUbHjzEyXP8ORXbj7DjXb48JTeOhVZET5UdCssz7jvAsUVKEWcuMPmDi5PBwOmM9gpXe5kpdWQnGzkATGbs3sEN93PbJrmwTdy7SV7AtGWULKesioiCjuhFE5XMZbFlHXdfT92wcQ2/XVDmGtEWSiF6tEKuPLRFJ6tMjFg6ze435MKHXJzx/nfiucPy6BJrR3TLw0FfBsY6sheVHFBHYzTsfYDzE3loF089IrqW1Q2lZUSsbVYYdERHI2SEaK9w041i306eeE+c+pvPFsSTH8lrV/H9s6Ivcut6XlscjM2WJggtW67j02f4/DiffEu28uBXnPidO17m8OPywNf8cIZZhTFdq4rYkxezmNu8gVs3icWfpEpEkP2wq1doVysRbhRjhFmVvbesVp09JxZ/lOoh+2nwRAymRUU16E9FpVbrvNm4ZJ9KrbGnasyV8l/vkEMjFZFl0KwWKudNvOO0ff8CDNsVBiQlEB0AAAAASUVORK5CYII="},"论文必备|34个国内外社会调查数据介绍及分享 - 知乎")]),A("dt",null,[A("a",{href:"https://www.educoder.net/users/p7qzmtnb9/classrooms",add_date:"1676990637",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAADDklEQVQ4jYWTTWhUVxzFz//e92Ze38zEODMWO5VMlNm0iJsQAnVj6MKQkEBSkULWLS4UQcSFKzdupAv3pW6sbgpiBBfFLgraogkicVECipkYMh8t85U3mczMu/eeLuyHO8/6fGzOTwAIAL7N5wvpdPo7C6QPlcsL+LAEAAUAqocPj6tk8mFa688b1v7qnHsoIqGIOOfcO7tS0GR0QGRtZGvr9/9aCOhKsfhzyve/bFo7CMhkWik48p8JgYgAJAhgSJIi95uJxDefbWw0ZGdsbF5Zu9JttawOAi1hSBpjJAgEzoHGACJgHENEQEA+Jr12HN//odX6St7m83dTpdLXennZ7j95ol0UIZyZQev6dagwxOiVK6KzWepsFqZahet0OFhft96bN35cry944vtTg+EQqURCJY8f5/D1a0mcOCGwljQGrtkkfF9ULkd4HoKTJxFMTUkyDFlZWlr2jLUFNToKSaXE1OsQreHabdJaoTEIpqfBbpdub0/8Uom9lRXsP3+ucufOwdZqUwpaJ8z2NvtPn4L9PnWxKLbdFhfHAID+6irs7q6I53H39m3pPX4sXqEgjZs3YcPwU6nMzg4OXLrks9eD7XSQmptDXC6LqVYxfPEC8Dz4x45BHTyIwfo6vEzGuTiWxvnz9EslKimXd6IbN9C4epUqlUL86hWiW7fYu3eP0Z07gDFwnQ7E8ygipLWSOXsWyclJYRQNlY2iNT57hvTiogsmJlA7cwYIAhm5cEHM1hbD06fpF4ukiKhUCt7Roxi8fMncxYvQu7s7ylYqP4bz8xKcOsX68jJdt4v9Bw9oNjeZnJiQ3qNH4tptsNUiSAzW1tC4fNn5zkEdObIq1wD17fj4T5lud+lPzzMqDIk4htvbg2QyYL//7onGEFp7Agg9z35kjLbJ5IIAwPbISDaRz38v5KJPihIBtAathSgFkBAR7DmHfXKQUyrZJX/5bXNzRt7Hq1EsftEjJ61IBv9DJEKKJXtaqdlDWk9H1v7hBoO5T2q18vtoflB/jY2tNMfH727k84V/c38DU82UnPMwV1UAAAAASUVORK5CYII="},"头歌实践教学平台")]),A("dt",null,[A("a",{href:"https://acm.hnucm.edu.cn/JudgeOnline/ranklist.php",add_date:"1677136861"},"HNUCM-OJ")]),A("dt",null,[A("a",{href:"https://dasai.lanqiao.cn/",add_date:"1680075050",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACxUlEQVQ4ja2TTWhVZxCG35nvOyf3J/cmJt7YxigSUcFoRAUXulCktEWLdpNS3FgKiigIIqILf44miOhCAuLCjSIixVu6sIgtFFEKgmirMbnqQqQl4k/iT3KTc82553wzXWiMoSvBZzPDMAwD7/sSPiRQRgkEFAF0AEUIOooMdABtUMwH4R4UAQkmo4SPYmLfvi2kANDeGa+WOFwN9maouhdUkZOUT83TKPoShqaAzGOALvUFdH38AAHA7GCkKUPmFJGut9kMkjACNPldlQts7RJiCxeNAKBRMr5PpL+8vHv7x8fF5WPcHGgmA1wwfnq9Oofq6JtjEoUrQNxExi6RKAxdHN6XatLWe++3xnisshREpnHR4tMAQAv2l3ex9Y9CnRNFV1+QDdqDSjf56e0Sv4nA/I3E1elszMzeA9nO8dfbuyrdkiQ9zIa3sVcDBf7tK13qnLuzPFWR/MAWgMq13n2pP0r3c+eQVFtbdvQ3IFAGlJ4NjhymJPmCVeSpSnSHPPsTit85zqGB2Paqi3pEkp8BJRTJOYc/syZX91ZC0oHupkFV9NlwLPk6XR42xrREAPAgyD1s3f16rT/63I4VUiFQrwBQ8YfO/+PPqk5ISTJU6T/O6RTvoVztGZceOlHYOlCLVeB0yh71Gj4/m0fhEAAUtg7UZuP6Lbjx0EOHGgBo3f1kZr42t5El0cuAAREaP5vqbcJVSojwqxBI1c1r2/t6XaHRfgtFDy7PiVAkh6DkZ1P5DU5wiwCgbd/w98z4SlRIVe5KIn8Za1cCmAEVj4iHFXrBJXbQmmSuslkGplulIH/xvSUX7g3XKMerSKRFQTUgvFLgGVQ9YmoGJEPCZWUuq+jFUlf9FUBpUgaaNz/J1BWy7aCogciOVPqn3ORpj2oyXtNi4TgtVR54cKTu73HrfxL+n8JAeaJ/F9v3s4NAEEyK8n9U3jd9D2HuUQAAAABJRU5ErkJggg=="},"首页 — 全国大学生TMT行业赛事")]),A("dt",null,[A("a",{href:"https://chat.wuguokai.cn/#/chat/1685978572365",add_date:"1685978603",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABnElEQVQ4jYXTTYhNYRgH8N+di4by0cxYSSFSaFaMLBQ2EnbIapDJwmI2FkqK5ZCVbMiCjFBSImIzKWTGAuMrX9Eg6iqRjSIW7/+6JxaeOr3ve97zPM//4zm0opZ1Jk7jI75iBCv8J+pZp+IFrmBp3u/HL1zGWez4O7nZuR1nktyM7XiCa9ia5Du4mWbNXL34kE5LMB+3MYp1+aarUngYh5uHlfiOftzDXOzGsdwvwiBeB8ksTM95chv24VAqjscEfMNTHAiKi5iDd7iLvfiBiTAW2HU8Sod+PMPGUDiHviBaEAoNTIKX+RBeYVXE6q1w3owHOKG4AzdwRODcD/TFOBktNlUKbMFOLA8NWB2q2vEpRRbmsk/xfC024Dl2YYYyJ3X0oDEOHfiMoTyXUuBL9gOKIz/TrJb9mghsCt6jE7MVq97gVASD49gTgUdDaayih0FcrXDuyLpecWYE87BMse+hIvaf6FJsu4VtEWwIj7Xso4z5ebTlXKvcmYaDQXJdmc6jodYdlI0ISesH/LdSohsX8FYRdFgZOBUEfgPfSGSpC3830gAAAABJRU5ErkJggg=="},"免费学习测试")]),A("dt",null,[A("a",{href:"http://fwwb.org.cn/",add_date:"1681471102",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAACNElEQVQ4jWWSX0iTURjGn/d833SWfsstDf/UZk6hlAgi0lLBIpMQIghJROhSuvIy8CLoOqTbLszAkLwQCZY3QRIFRsIkaYYycaRzk03cdO7Pt+98bxebbtlzdc7F75zf+/IQM+MozDz7O/piIRrY57pyGm1zDLRWERGKIoovMyuRgbmI+0xJQmI1Dm84mTXSxS8WAGY+yOgj87sWIcf7LnTWqFcdNOtPjS++TWVjxUwe2NhL3ppcD6Upw8qn9ZguzUuVlgZNsVoXJ7xD8WTwH0A3ZP+HrZWYAFgyPZ6LOG2lCsyxbvknthBJrU3/GpFmtgB4/HtLuyxhMgAQIB41Vby8W/szPMYkAWwfLvuj33JiAsCXzSSDCRCApsjhlrKeRtvy9oQ/Pg/k7Dmw9yP3gwogZQgGEXODRktPmgUhdKDXaJc1S92+HsptNWMmCkqahQkmCN3nTx1k5P3pwJAn6LJ3DV55nZ+UYFVsBeC2s0yQALAR1zunAl93DG80uxZNZmUGBGYGk7uyA6A80HPRfrOaCDwf1AMJaBae7D3XZDc9q88JDMCp3XA5rufcBABVEe8fOK+dVRlUbeXP/fUd9btvvIPhlI9Broq2/tZXQih5u9yymNkXSbS/2/Q8tOvZqe/BCQNpgBwlrqftHxWyHBdKzXNELVXlM32nfeHhnbTvaFTlnntUIbW4furxiYjuuGsJXfuhrYw81EprehufNVd3nWgrnSgjAEPqkg1VlChCxX/5Cxsv/VY9EsK7AAAAAElFTkSuQmCC"},"中国大学生服务外包——创新创业大赛")]),A("dt",null,[A("a",{href:"https://leetcode.cn/problemset/all/",add_date:"1681898582",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACRUlEQVQ4jY1TzWsTcRB9+5H96u7m17SmraZxI4GlFaVSKj0KelCIxwYPFRQxN0H/BPWieLGXIoskqS1Vk4oILSpVKihIRIt4EDxJKT20UPqRppu22R0PKqYhqZ3T/GbeezO84Qf8JxizWJOuz+imOVxV5v4m4l7kHstixc2ll4Lk9XtcYGljfTd5zzjVYzFdUz8CAQKUB/siVU9uNdUCIJBzmdFm2hqiz73avsgWY6wzpBQAie5daF4tZyLrNBWlUjr6nXJ97bV4vvrBGGPL2+Xp+SKdjIXFZ1fP6r1ym9LtLmJMC1PXVmnxnfvIjtWdHImYIVVVCooik6apE7uaRFxp5HiW3h6l0siJW3UFTEN7oSgyqar6hIjqOk0PcZpuQK2ucQDQ3t7St7ZW+sRx3Ozg4MV+x3F2RkdHO1oYS3jwNRFEFXD+qsBWDHJVydtmPs9/TSQSb0QA8H2K/tF77TjODgCEgsEzBw91OJWdCnwiiDyPWIBHeUuCommYm5tbyOVyR0QAEAT+5++d/cTAwMDtfD7vLi0vT/M8f6lCnsr7HjxOrKyIbNXwNjTZW2S+IHxLJpPb/y5g6BOyLFHQ0J+nUqlAAw/O0x0E65oYj8dN09A/BGSZDjQ3Tdb23aw9RjNd5GaP3awrAAC2bRvhZu09IBE4bXJh2LbpcY/lpjuf0mSUyuno7OZYPNJQAACunYubgDpzN2lSORMhGu8kmjpM5Uz0C433ttbiG/ysbml+aON6WxAJIgiA8ErypfvclR/FWuQvcBnWk4r21EgAAAAASUVORK5CYII="},"题库 - 力扣 (LeetCode) 全球极客挚爱的技术成长平台")]),A("dt",null,[A("a",{href:"http://www.dangdang.com/",add_date:"1679643961",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABvUlEQVQ4jaWTS0hUYRTHf/fOzNWroS5qZy/c3GZKbcCF+KBAjBbjslUUQykjBrpolRvdKC58QzdCsSQKRFxYodsgsJeCwaD5CmtlMNqyZrTb4uj9us51Ef7h43zn+5/z/875HhqAE7WmgRj/h5fawnKD7ibn5EL/Q0VXVsPcoliA4jMwYINpHkTEnKg1rbs79w5D7VUYfCR0VQ0YBpw8JX5HF9RcUfy+iO5O7zXCp/fQmhA/FBJrGGLv3oT5D9B029OH5kQtx/VCIchkYPIVnC9RUUtJ6O6ElWXh/4Hu8ZpaYH5JkjfWof0+fP8GFyIwPiEtHIKqoP46dPfBzx1IxGH1i4qKlII9Cvkn4EYM1td8BIJB2N3N2sHF2XMwNQP2EIzYPgKV1XLyh7H3Bza/wtQEzL6BvDyorXDpoDsbsKUKP8y+FoEfWxC55KFURlszlJb7VLAH2ynQA1B8Gna2jxCYeyvjKIQvQmER9Pd4ltU11l2Dj0l491nKPHhApglll2HsBaTT8OyJR0AdohWW8aADAgH4/QtSKXnKhiHJiTgsLmQJZP/EW3cg3ggFhdL/2GN4Pp7dlsNTjWN+57+1iIcZRTioCQAAAABJRU5ErkJggg=="},"当当网")]),A("dt",null,[A("a",{href:"https://juejin.cn/",add_date:"1670238546",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACD0lEQVQ4jZWTPWhTcRTFf/f/XhNTG8wkmPdeLS0dFZwsilg/EERwExQKWigu2kFqQU1T//GLGkXBthREoYObk+DiII3g7i4i2JiCS0HaNK3Je9chSQ22lnqme8+95wz3A/4Fq2bT+H+QzmjgWfW36tnofE4dAG9cT7gxCo7wwbN6vLW2tcFrCUEF4aC00S1tdKP0gUq9tilUAFJWU/4dPdpkfasDflYHWvL+lNVUq6YlUPGtTnXlVTut5jmt8XX/YY0Ht/VRV17VtzrVqnFBtJ6I1kKdkRV6nSSjQR/7y/t0KNEOBl46SU6Fy7yrhczUNSogKt64jjgxztSWGVmYkE9c0h1BF/ecdkZqK3wREKednrDM4+IiY0zKWjqrB5wYT4h4a8SQBI65HcwFWT3PrKwWrVyvLjFoDHsxdFaXGCzmZJRJWQvG9YIboyBCv4Z0CEB6TE+6cWalDS9a5X6xgKUgtT1ZPSIGXcjJR6y6gZIzCW5plVJY4WLpgbwXrBqsRLvHtCfu8sJJ0h8u8aa8wtXFvHwH8Kz6BqadJGejZebWKgz9mJCvWDX1aTZM/GuaMLt4KDsZjip8psqARoiJ88ok6I3KPJv/yQ2eSqWpkfVVNQgAL6uXnRjTGlFFERzc6BdXSnfl+d+9Gw+q8Tjpm3o4yOl8kNNv6YweWhf+OaAt0DTJaJDOaNDKbR/bfOffVnrk9zizKkQAAAAASUVORK5CYII="},"稀土掘金")]),A("dt",null,[A("a",{href:"https://www.lanqiao.cn/contests/cup-s1/challenges/",add_date:"1669910413",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACxUlEQVQ4ja2TTWhVZxCG35nvOyf3J/cmJt7YxigSUcFoRAUXulCktEWLdpNS3FgKiigIIqILf44miOhCAuLCjSIixVu6sIgtFFEKgmirMbnqQqQl4k/iT3KTc82553wzXWiMoSvBZzPDMAwD7/sSPiRQRgkEFAF0AEUIOooMdABtUMwH4R4UAQkmo4SPYmLfvi2kANDeGa+WOFwN9maouhdUkZOUT83TKPoShqaAzGOALvUFdH38AAHA7GCkKUPmFJGut9kMkjACNPldlQts7RJiCxeNAKBRMr5PpL+8vHv7x8fF5WPcHGgmA1wwfnq9Oofq6JtjEoUrQNxExi6RKAxdHN6XatLWe++3xnisshREpnHR4tMAQAv2l3ex9Y9CnRNFV1+QDdqDSjf56e0Sv4nA/I3E1elszMzeA9nO8dfbuyrdkiQ9zIa3sVcDBf7tK13qnLuzPFWR/MAWgMq13n2pP0r3c+eQVFtbdvQ3IFAGlJ4NjhymJPmCVeSpSnSHPPsTit85zqGB2Paqi3pEkp8BJRTJOYc/syZX91ZC0oHupkFV9NlwLPk6XR42xrREAPAgyD1s3f16rT/63I4VUiFQrwBQ8YfO/+PPqk5ISTJU6T/O6RTvoVztGZceOlHYOlCLVeB0yh71Gj4/m0fhEAAUtg7UZuP6Lbjx0EOHGgBo3f1kZr42t5El0cuAAREaP5vqbcJVSojwqxBI1c1r2/t6XaHRfgtFDy7PiVAkh6DkZ1P5DU5wiwCgbd/w98z4SlRIVe5KIn8Za1cCmAEVj4iHFXrBJXbQmmSuslkGplulIH/xvSUX7g3XKMerSKRFQTUgvFLgGVQ9YmoGJEPCZWUuq+jFUlf9FUBpUgaaNz/J1BWy7aCogciOVPqn3ORpj2oyXtNi4TgtVR54cKTu73HrfxL+n8JAeaJ/F9v3s4NAEEyK8n9U3jd9D2HuUQAAAABJRU5ErkJggg=="},"第十三届蓝桥杯(Web 应用开发)线上模拟赛 - 蓝桥云课")]),A("dt",null,[A("a",{href:"https://www.centos.org/download/",add_date:"1692857850",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAADUklEQVQ4jV3TXWyTdRQG8Oec92tdcdBtbmxrVxiVZSYoMpyVTKV+cCOELWQXGgxmZoomTk1mAt7QC82ISkC5ATEYEuVqsqGLBGdokYGLsdz0YmyZbnYri3ysdOvq2vd9/8eLSeJ87s5JnqtzfoTlEAABgLPtx1pFoRMkEVdcPwAwcZqELznsnN7X3zP83w79OyDaHC1tClYcdqH2a8xGwS7CKdgCALqpk2XocEXZrLQTN6YzB6KJaB4AKAqwd0ePp87r7zdhvJBZyChRUMpxOXJwN4GAWO93onnKFIjZV2px0c0PLbjZttnvo0taHJB9mzqOWGS+ZHtUwf94SFtd4+NNHU9Q7eYgVlX74Kv3UnH6MldUzyO3qIoW+TaSaGXdYxd/oK93HQmzoV1ZzObQ8OzD/EzPLsrOzKG8oYpivQMABJGD7cj/0i0yeRzD196RmT82qNISG8rBUywk+zXWdCUCJ29TJnUH517/ElPXxlC+vkrKAnVip88jP3kFCL6FYq5IIgoaazoYb+ggjhRtW8RRHAiHUBlaSzs+6sBfybQ0v/okuS7DHR2Siuc/JKp6ERvuXJWbRy9x0fIKE2/Xlahap2Bj+we7qeaRIOKHz4tvfTW2vNJCudhegAhW+AwlziVlcXYAza89h4i1ioaPXYBeYtQxaPn+KyKAiABEABGYbOiGDRADopb395/hm7ajk0okuJhZkKff30lNO7dQamQc6esp2drZSgDw21fDUvtYkILhhzA6eF1+/mRQvL4HiAgpFlFx0zCIdFapkQnMTd2SoUPfSs3mACX7EpLsS0jNowH66VCfzE3ektTIBEhnZRoGQSTGUDjpKtdhYugeQ9YEKrHnVBfWbWvEfPouZdN3ad22Ruw51YU19ZXQPYYwMVzlOrbCSQKAs22ffW6S/rbtUYUHm+oMd8khf0sD6sMhAEBqZAIzv/4OrcSQ26Np21xi629VPL534L1uLQqw6Q3H9dVGi+noG2fHpyU7PedOXR2jytBaWpi9h8sfD8r8zYy6l7pNXi41bCn+mHOzXVvH4+4KTI315b3C8qZG/8NkGWQZ5jIm4RM3/kweiCa+yN8nuYLzmfZPW3Wld4Ik4ojrBwQ66TMQihHj9Mv9767g/A+Zs4wS4N5+ygAAAABJRU5ErkJggg=="},"Download")]),A("dt",null,[A("a",{href:"https://www.cnki.net/",add_date:"1693795063",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAAB10lEQVQ4jZVSP8hpcRj+nZs6oiPkpFBkoCxnQCeT0rdQyiCZDAwYTOQsBsOR/aRshlMymsSK/JkMysDgz3KOZDmO8t/vDu79fO51b33P9r71vO/7PO+DwKIbfAuw6Ia/IQhCPB6nKEoURfgWRbfkK/l0OrXb7dlsZrPZIpHI2wU/vha322273fp8Pq/X+6+LXgij0Wi32+VyueVymc1m1+v1/zRsNhuCIFQqVSwWUyqVarV6Op1+Hs9xnCiKsOh+Emia/pyi0+mazSaE8HK59Pv9TCbjcDjy+fyL6FAohGEYwzD3+73RaJjN5mq1utlsptOpxWK5Xq/dbhd8vNq63+8ZhjEajbVabbFYGAyGRCJxPB5Pp1MwGOx0Os8Nk8mE4zipVDoYDFarVblcdjqdBEFgGIaiaKVSQRDE5XKBHpAAAHie93g85/PZbrfP53OKotLptEajsVqtrVZLJpPRNO33+w+HAwaABAAAISRJEsfxbDZbr9f1er1Go0EQJJVKDYfDXq9XKpU8Ho9cLn/aKgjCw9lAIFAoFMLh8Gw2e3T2+/2baCgUCp7no9EoSZLRaNRkMkEIWZbFcfyPv/0ijMfjh6HJZFKr1bIsi6KoRCIBfwH5brx/AseDLUJKQoGcAAAAAElFTkSuQmCC"},"中国知网")]),A("dt",null,[A("a",{href:"https://zhzz.hnedu.cn/web/stsyappSy",add_date:"1697100783",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAADaElEQVQ4jU2TT2yTZRzHv7/n/de13Wi7FqV0bAgDxnQz4hLZUBFjIhhijAlw8aIOPBg9iUavaDyZkHmQjJHoSY8monEH8UATowm4ZX9YGNpuXevouq59u/V93z7P8/MwY/a9fQ+f0ycfwvYIAAPAg3z+RDgafVOurp1y/3qQZgCRw70lKx7/xdusTRzoPnB7J0P/HczeuhWN9/V9CqVGvam5tunvb2Bz5j6EBif29VLv6NsIHe1rQojxaqXySX9/fwMAiJlpYWEh2tHR8W1HNHImn83y8tXrei40R9VjMUr/oRC7u8HtQwN88J1LInP8Gaq7jZumaV5IpVKbgog40tZ2JZ5Mnin+ORX4hRVu1CtGVyEkhu54eKweoDMTEqg+NDaXlrk0PRPEk8lX/GbzChGxyOVyx4VhXHRdV239nTPtRCeBFT8SbXGoyEQtUHtYsdCa7XiCtnI50200lDCMS4uLi8NCCHHRcZyQ0hr+wzI56TTFBgfIaVUIDgDBWP/HJaf/SXL2PEp+uUxKaziOE7Jte1QIIU5JKVnYNsG04M7P86EPL8M7+jIMQ8CXNsSxl9B7+QO48/MM04SwLJJSsmEYLwgAaSkl6SCgzmdHqPTDTfJLRRz64ku0Dg4j8ep5DF3/Ct7KClZ/+pk6R0ZI+z5JKYmZ94ptowS1tYVITzfSZ8/i3mefQ1bKMEmibXcSlWwWMx99jL2vv4ZwVwaq6YGEAAOgQqGwZNp2Rpkms5RkOA7VZmcR7ulBbnwCsaefgh2LQzVcdD7/HMuNGsiy2FSKWkGwTMvF4jdtSr/RvJ1VlMkIBD5EOEK6UoEOOcBGDcI0QbtTUOU1GOk9rCsVHR4cNJqsvzZ1KnXNn5o+J67dMPWJYebFRaFTSUZ5DbR/P6n1dXBhhenFk8DcPejH+9n47Xd478d8NfjEOAHA0vT0WGK1/O4GIUDTM9m2BEkJam9nXXeBzS1Czz5Qva4RiciY59vVZGKsa2DgPWKAFpij0Vrtu1g4fLrecJml0hBEpJlAAEgwt1pMliU6olGqe96P9V27LhwmaggC+AiRW5ucPFctlcbQ9LyQEIYRtAQHAbEfkAgCEbIsg4KguVEuX61O3j1/hMilHTX+n3M+nx8xDOMtIjrJzOlty1Rk5l+VUhPd3d3Zncy/6H6nGM0KIwkAAAAASUVORK5CYII="},"首页 - 湖南省智慧资助服务平台")]),A("dt",null,[A("a",{href:"https://wordforest.cn/",add_date:"1697120147",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACoklEQVQ4jU2Tz29VVRDHP9855973Cra0ebQoVJDWAApGZSeJPzYuTOPGlcSFuGJFYuIf4NqNa1auMDEpweBCo6saF5gYTWixorY1YCkW02JSeOm7954zLt6DdpKzmsxnvjPnO2IQM+99dCGYzrl7hIwcZQTAWd9g0it38C/j/rBge692ussfz87Opgjw1vsXpq3Jn8iKIc8ZKYI7QkhOzGI4iwownCKnU2l0+irwUwTI23mfF17klLL7oNLBEX0pmR9rmDT3oEQ2t9Tr7QOI/QEsOe4Ck5QlF0hIBOD7JrJke/xArvCMooGj9BhQFOCDXbi7er1KTZNB4A73PLAnBq2Xwxgiuiup3zsC9HpBrRZyh7ppOPHsM0yMj+EDagzG1oMuC78tk7ODhIVKANZXUCFEVdW8M/MGr585TVkU7O+MMt4ZpSxLTj43xQdn3wYT7k7DLgWUJVW35vDkUzx98ABXvp5DiD+Wb+PuTB05yPwizLx5hpdPHePazzcYKZ7YAZQUbKUeE+NjrNy6w+FDT/Li89NcXLtHzpnXXjlNXVUs3FxhojNKzpkY+r0NoKImhsA/65ucPHaUX67f5NOLX1BVNU1KXLr8DZ9f+Y4XTkxx999NQjCa1Owaoaooyxara+vc+P0vPjz/Ln+vrRMsAJByZmR4L1sPHjL/6xJD7Rbb7ALUdYm1nFZZ8tW3PzC/uERnbISU+99gEt3tbZZWVpH6ZbHZBTBL7phLMNQu/fbqXVZu3ZEGt+A4ZubtVunuLnd3N/MdgDC5hMg5O61W6W3pkbcGBqOfBJcw1N9fBFBZPCSTzULMOQF6bKIdAkgaHJhIIXYBAsCf169tHH/p1TpEO+TSf5g2Fey+zDYfPaQNC2HDQrifTZ8NHWlfWpybS/8DyY8zdrt/J8oAAAAASUVORK5CYII="},"单词森林")]),A("dt",null,[A("a",{href:"https://www.kugou.com/songlist/gcid_3z110a8iezjz0a4/?src_cid=3z10x5alxz4cz05a&kgsscty1=qq_client&chl=qq_client",add_date:"1699445205",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACE0lEQVQ4jX2TT0iUURTFf+fNN0W0yLJAlEncNGJSUe3atGmRC2sTmX+gRcushbQoNYK0XbtqG1lK0D8KKqFatxcrR4iwyZBZREJR0HzvtOhzGl34Vvfd+96595x7rwBax8u9tkaAdkCsfwzMSR5bGC5MqTC2eEp4as2jEuYxgTlwJIZO5ONAcTWSehPBcJ1vGThvxSUR+my6QEiekRjCbjC6ATQACF9KwCuoywocJeUchB5Wc9lrMwB6oECXIy+BLUAxARIAWUMx5azkHuAHcgmrDdgGVJDLoEOOAAwCE0A+ZBk+gj9J7s24vfo8XDgIvAU+SNV9AZ2RyQMnrFgRzAMEAIlniP6VeoO8lJnf8tXkMGm+KZrXhh0AIYbTtp/WAECzFp01da0lgJzyF34l6UYHvwEaa3GxS6Gugsxb00xmsePKuw32n7YYwm8gXd1lRAyuATjGdvBsLRzi15+5xpYIN1saF79HewCIdf/fm1j8X4HUjTVZi6dhybm0GdhfqTSNfhktTBuu1VGcQOpmpYVAkeBmRe5b9Dj4DpFNmcAXd46XuzC5rPiH4K1kU5kAVSDB3CLHESICTtbxTbAOZPajYK5HPJ3dqwl4HtQBbHbkBfKg8W3F0C+xxyDQjJXeDdb2iJ7zbwoBldQ6Vu4zurdG5RLoiYizGefdyMeyRHWtcL+gts6XWbNt65yS8NWFkcLkX+C52WtXXh88AAAAAElFTkSuQmCC"},"听风听雨 · 来自旷野的舒适纯音_精选集_乐库频道_酷狗网")]),A("dt",null,[A("a",{href:"https://www.yuque.com/cessstudy/kak11d",add_date:"1673450446",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAC+0lEQVQ4jV3TT2sdZRzF8XOeZ2ZyZ27S2KQmtU0qrUpXZqMgJIiYhe+gi267KW5U3GXXneCqFSQI0o0LQd2ICi3U+Kc2qFgQN7ooiub2kjS2yc3c3Dszzzy/46LVhS/gszrfQwCQRJICgJHCikEXWtlqiM0CAKQ+6yV0Gw68WjC99X8DANjSVl4pXC5V10O12m1L7YSB7YR9242lhmpVqq4rhctb2sr/g5JcX/2itOqaJN0Lg7jXHoYyVvGgraxsKxvGJj4Iw7Dd7EdJKq261le/kOQIAINYXTniJl7bDoO66zvpp/e+5e3yN0z6LjLnMZNOY3l6Cac68xpbE05mRydKG78z7YvXOdDoBZq72SgwWnQpU27u/4K3//gAnp4kISd10wJvLpzH0uTTGqOxKZeLzr/ozHjRuyQ1iwDAkdVY7MzjeDrLjIkK19EUCrZti3d7H+Pm/s+kAdExDRYuOgCrldUC6BpFpC7Bbr3H+82+YGKIgUGtFMWcHcylR1FZ40ZxLCNeTlqFEzIgSgSJUQz4sHdDVRNYJB50Bois2ehUNo/Hkkk01pItAa+TDiAAwWDImOGT/mf4O/yOhTxDxgAzIJrBy+GvageVNSCBKINJcAmTvnMOuc/19YMbuFPdxmIxxSKJmk1bHPEBEaZUnr1qG3dGPaT0onfw3t91hrgx4yf548F3tnnwJXJfIEAyIyOpbmJKYTRI0SLuVrsQYFma0aSvXF533/9p9H34Zvg5Oj4XIAACHQSRAOgp6VG0gsnRwYIFmnvPFQU3vzj8aH16diqRIYAQQUACKAlQaw+xg9fxbCbMTBxLAK0vZtM/OFyCY3u4Vh/G691jxYQMgBBByhNozSGaV9kO22eKBaw88fzEQTu4fj9t1i79mzIAnNs8l589c/atqtKrtcWsHB4itI22R8QY5LNzZ3D+xCvNU91T61u9X9eWF5fHwMMNAYEgBABv/Lm2Elq70NTV6l7pF4RJLM2d7r30+HMbp7vzV5/k/K1HLyRI/QN7F9CfB8suEwAAAABJRU5ErkJggg=="},"前端")])]),A("p")]),A("dt",null,[A("h3",{add_date:"1700999502",last_modified:"1707036070"},"开源项目"),A("dl",null,[A("p"),A("dt",null,[A("a",{href:"https://panjiachen.gitee.io/vue-element-admin-site/zh/",add_date:"1698407492",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACaklEQVQ4jY1TO2hUQRQ9986b9/atIV9c1IAW2ogRBQs/IBg7wSRarFhZprCIIDY28iy0iqJdsNJKWBthbYKoMZqQQizEIhASIhqxidlsks37zMy12HzMGsVTXWbOOXdgzgEaIRFDQPUZdOZN5CGK+A/etlgXAkB/v268LpaKqvFsUyBCIJLekXvduim4blLbSYQ5k5oPFOjh8omBiUbupoGAQJCe0cELOsiVRJyWzIF8BVIMG6eGPW/ckAyVjw88/W250Ppw9tX9zuYcfxTrClBI4cRzmXPE5DhQHimPCQQn8s7apL986sYkAGJEEQFAs08DKtAFkKS1zz91Mlcj0kqJQFfHflA8WzEmiVNmOq3gl8+N3t0JQBhR5I6Vo7xAiqaWCnyl0m8rWHzxBRQwmfkY1eGvALMiZi+rrsZeqA9oCq8CAAPA7vaWI2DaZzMDyRznj3Ygm49hF1JJpqvkFULoPXm4xBErVs5Yw8yHNwzYuv0q8ImYLIwTf1devBaf4qkKJdNVCQ+2ghSBBCJOHGvPs8ZMbBiISJ5AYCKBA6CIwq42qb7+LqaSIHeoHZJYBxKrW3cE2Uo8uuSWhiCg+gsULTprydX/l8SIhF3t5JYzUU3a+YXQEFjp5ry2SVaKyV0c6Y6WQRAPADKSCZ2ZVWYEIpJJ6lh35Gxbz172WkPlNYXK1bIpl5k7z09ee7I1B2sh6ht/cMvP526bJAMAMDPAgIibtLF9NF9Zffz+/M2FNX49fluiDKB37OFlpVUfrMvb1M4w4e3M7NzLT1cGVwAApaLCpWd2uxr9E9uV6O/EqF7nYqmo/kf4CylQKrlivdcjAAAAAElFTkSuQmCC"},"vue-element-admin")]),A("dt",null,[A("a",{href:"https://gitee.com/baiwumm/Vue2-Admin",add_date:"1700999576",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAB0UlEQVQ4jY2TMYtTQRDHf7NZIrkkoHk51EDgCLnr7xsclqcifgQLk0qwsbXRDyB2CXZ21nplsLC3fsmRC0RCRC65JHqPU9+Oxduc+l4E/zDF7szO/Gf+s8I77qF0ABDan26XP5yTf65wC3SHxDES6Ammu8uXj/wB4S1T4DoAyufJ3VpjxcUSyJGCgFP01Razx3WIAEw6KGaigGKEtKkRg5hWRHA0hgKARWjjfAuG9o3ByRO1OWvrtaRmCvr1G8fXGgfnWnkBszYh5eqE2tYYCj8GJ0/Vw8XxRosXCw2lqn2CeMD2vhXyz1ZcPIRA1eYswPTBI5av34C6DAMUUEXBgGtJSBACexhh9/sURBjkb0Icg0i2C5/Ao28TqXyUiA9wIELzbIiUitkZXG34JLpjsxw3QwCMgVIxOXgSFmQE7GV71aSSJ2fKJZrzYTrtyAj0/roSMKViov26fzGU7t8BEdxydVldoGcF0wXX0vVSGUNzPkTTtUziTtRRBFzyFgipdEKparxY/lP/n/Mznb3sav9KTUMCDal0LmczhkJEcKQiBxuWz8/kt3wC7wucHtYhMgB1iAqcHqKuK04dTsmYpw3aXT9m07IP2N7XZCb/9Z1/AXAS+qNSf43uAAAAAElFTkSuQmCC"},"Vue2-Admin: Vue2-Admin 是一个后台管理系统解决方案,采用前后端分离技术开发。它使用了最新的技术栈,提供了丰富的功能组件,希望本项目可以帮助到您。")]),A("dt",null,[A("a",{href:"https://webgradients.com/",add_date:"1701008729",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACdklEQVQ4jW2RPYtdZRSFn7X3e865uUlmmImjGR00BmEMTJlCrGwsTO9H8AekUOzs5xdYpbARG0kEwUIrC5GABgsbwcIUFqIQmGSSyzif59zzLou5YISsbhfrY68lsEB+43NPXtqqH2XqXbteEbQ8AZtemb9V1y8PvombX22rB0sA7/1wdCnPT243U14be6jDyNMQTZIN9Ifjj8cP+utfX5v+rbe+2F2aXlr+vlvNqyezcZBIg56qYBs0divZnDwa7/Yv55slnl36UGfy6tGj2ttupAADGGyBIGRsMEERR7vjkM7XuTPcKOtnub5/Qu2PXSKF9AQRAcZz6/SU6+4oPSbqSdYc9H7ZWNLmUKt2ZR4eWP0oMkALUwwEuDfeqdK+UESQRqHNEnLTpdlYgrWpvLNnPdiHeV0IBdQD4/smxoAmjG3XMSS10YRcBCF52qDLz4iti3ChM+Mc6vGCXBMSqFWn3wgbRyNoJNoQZdHd0kRsvSCeP2eGnYVzGFcj/X+gKEJNmILVZrgLSExKXFmHtbNiXOy6sPZpN0ZCUcTQZrgJXGT9l8a0Rby6CZnG1mnsWoWQI23TR1r3Jl24CLeB28RNiCZAI6ytiPXnYBh8Oo1koGbKoHvhkdvTjuhS80bQhmgTd0VuA9qEjYtypIyRwJgahcD1Vuz8Obt5vDf+srIabcI8sBux6AQXyyvnrVKMrREzdueymR+Nd+dZPo3taxf2Hj8c3j6c8fPycjbTSUYRajNchFJo2oVDSBlZzmTTH4w/JfnOtzd0mGB991kzixe3b71ymX8Cr2JWQqSQ2yLvzuCPv3QcGb/WWj/5/X5+cOdjzcD6F6bgLZVtEDCBAAAAAElFTkSuQmCC"},"Fresh Background Gradients | WebGradients.com 💎")]),A("dt",null,[A("a",{href:"https://juejin.cn/post/7228990409909108793",add_date:"1706938054",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACD0lEQVQ4jZWTPWhTcRTFf/f/XhNTG8wkmPdeLS0dFZwsilg/EERwExQKWigu2kFqQU1T//GLGkXBthREoYObk+DiII3g7i4i2JiCS0HaNK3Je9chSQ22lnqme8+95wz3A/4Fq2bT+H+QzmjgWfW36tnofE4dAG9cT7gxCo7wwbN6vLW2tcFrCUEF4aC00S1tdKP0gUq9tilUAFJWU/4dPdpkfasDflYHWvL+lNVUq6YlUPGtTnXlVTut5jmt8XX/YY0Ht/VRV17VtzrVqnFBtJ6I1kKdkRV6nSSjQR/7y/t0KNEOBl46SU6Fy7yrhczUNSogKt64jjgxztSWGVmYkE9c0h1BF/ecdkZqK3wREKednrDM4+IiY0zKWjqrB5wYT4h4a8SQBI65HcwFWT3PrKwWrVyvLjFoDHsxdFaXGCzmZJRJWQvG9YIboyBCv4Z0CEB6TE+6cWalDS9a5X6xgKUgtT1ZPSIGXcjJR6y6gZIzCW5plVJY4WLpgbwXrBqsRLvHtCfu8sJJ0h8u8aa8wtXFvHwH8Kz6BqadJGejZebWKgz9mJCvWDX1aTZM/GuaMLt4KDsZjip8psqARoiJ88ok6I3KPJv/yQ2eSqWpkfVVNQgAL6uXnRjTGlFFERzc6BdXSnfl+d+9Gw+q8Tjpm3o4yOl8kNNv6YweWhf+OaAt0DTJaJDOaNDKbR/bfOffVnrk9zizKkQAAAAASUVORK5CYII="},"Vue3.3 + Vite+ Element-Plus + TypeScript 从0到1搭建企业级后台管理系统(前后端开源) - 掘金")])]),A("p")]),A("dt",null,[A("h3",{add_date:"1701066400",last_modified:"1705385890"},"鸿蒙开发"),A("dl",null,[A("p"),A("dt",null,[A("a",{href:"https://www.harmonyos.com/",add_date:"1701066377",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACYUlEQVQ4jaWTP0hcaRTFf/e9GUEfb14S0YUNwUItduqIYCN2MjYiiMiAJKwgNtu8KsMshKSxs5RpxCqFU1iOxQqCiii4u/6BUcFKGG0iIoKFed/Z5vk2gaTKBxcunHvPPd/9A+ABFeAz4IAE0A8sSWM+pzkewDtAuVzOdXZ2Ot/3FQSBoigSIM/zMt/M1NHR4YIgcCnhO8zsFnDFYjGp1+sCXKlU0tLSkgCFYajd3V2VSiUBrtFoaH5+PgGc7/u3OUlRyuYVi0VNT0/b8PAwhUKBp9fT00O1WqW/v99GR0dZW1vz0pwolzr2+Pio9vZ2Gx8fp6+vj8PDQwDCMGRjY4N8Pk8cxzQaDfL5vACTJM/MDCAIAru+vmZqaorFxUWiKALAzAjDkDiOmZiY4Pz8nCiKLMXMB94DJEnCzc0Nx8fHOOe4vLzk7OwM5xwPDw/s7e3RarUwM05OTmi1WtkXvxlVKu+79gOMbFwLCws6ODjQzs6ORkZGsqCuri7VajVtbW1pbm5OZvY/ge/7AjQ7O6ujoyMNDg4qjmM1m011d3cL0MzMjC4uLlQul1Uul79V8CRreXlZ1Wo1A/b39zUwMJApWFlZ0fb2tiYnJ7OlApRLkkSAra+v6+PHD/bvP38zNDSE5Gg2mwD09vayubnJ6ekptVqNer0uSQYo55wzQKurq9b1y6+q/PnBrq6vePP2d+7v7wG4u7tjbGyMtrY2KpWKAHvaHwNugQIgfvvD4+Vr8eWLcfYJrv76KhbM7KmyS4G77JgA5xVeOZ71yZ73y9pfZP34uutm5lICpbk/d87/AbuJTsGhjfkLAAAAAElFTkSuQmCC"},"华为HarmonyOS智能终端操作系统官网 | 应用设备分布式开发者生态")]),A("dt",null,[A("a",{href:"https://developer.harmonyos.com/",add_date:"1701066591",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACYUlEQVQ4jaWTP0hcaRTFf/e9GUEfb14S0YUNwUItduqIYCN2MjYiiMiAJKwgNtu8KsMshKSxs5RpxCqFU1iOxQqCiii4u/6BUcFKGG0iIoKFed/Z5vk2gaTKBxcunHvPPd/9A+ABFeAz4IAE0A8sSWM+pzkewDtAuVzOdXZ2Ot/3FQSBoigSIM/zMt/M1NHR4YIgcCnhO8zsFnDFYjGp1+sCXKlU0tLSkgCFYajd3V2VSiUBrtFoaH5+PgGc7/u3OUlRyuYVi0VNT0/b8PAwhUKBp9fT00O1WqW/v99GR0dZW1vz0pwolzr2+Pio9vZ2Gx8fp6+vj8PDQwDCMGRjY4N8Pk8cxzQaDfL5vACTJM/MDCAIAru+vmZqaorFxUWiKALAzAjDkDiOmZiY4Pz8nCiKLMXMB94DJEnCzc0Nx8fHOOe4vLzk7OwM5xwPDw/s7e3RarUwM05OTmi1WtkXvxlVKu+79gOMbFwLCws6ODjQzs6ORkZGsqCuri7VajVtbW1pbm5OZvY/ge/7AjQ7O6ujoyMNDg4qjmM1m011d3cL0MzMjC4uLlQul1Uul79V8CRreXlZ1Wo1A/b39zUwMJApWFlZ0fb2tiYnJ7OlApRLkkSAra+v6+PHD/bvP38zNDSE5Gg2mwD09vayubnJ6ekptVqNer0uSQYo55wzQKurq9b1y6+q/PnBrq6vePP2d+7v7wG4u7tjbGyMtrY2KpWKAHvaHwNugQIgfvvD4+Vr8eWLcfYJrv76KhbM7KmyS4G77JgA5xVeOZ71yZ73y9pfZP34uutm5lICpbk/d87/AbuJTsGhjfkLAAAAAElFTkSuQmCC"},"HarmonyOS应用开发官网 - 华为HarmonyOS打造全场景新服务")]),A("dt",null,[A("a",{href:"https://developer.harmonyos.com/cn/docs/documentation/doc-guides-V3/arkts-create-custom-components-0000001473537046-V3",add_date:"1705308503",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACYUlEQVQ4jaWTP0hcaRTFf/e9GUEfb14S0YUNwUItduqIYCN2MjYiiMiAJKwgNtu8KsMshKSxs5RpxCqFU1iOxQqCiii4u/6BUcFKGG0iIoKFed/Z5vk2gaTKBxcunHvPPd/9A+ABFeAz4IAE0A8sSWM+pzkewDtAuVzOdXZ2Ot/3FQSBoigSIM/zMt/M1NHR4YIgcCnhO8zsFnDFYjGp1+sCXKlU0tLSkgCFYajd3V2VSiUBrtFoaH5+PgGc7/u3OUlRyuYVi0VNT0/b8PAwhUKBp9fT00O1WqW/v99GR0dZW1vz0pwolzr2+Pio9vZ2Gx8fp6+vj8PDQwDCMGRjY4N8Pk8cxzQaDfL5vACTJM/MDCAIAru+vmZqaorFxUWiKALAzAjDkDiOmZiY4Pz8nCiKLMXMB94DJEnCzc0Nx8fHOOe4vLzk7OwM5xwPDw/s7e3RarUwM05OTmi1WtkXvxlVKu+79gOMbFwLCws6ODjQzs6ORkZGsqCuri7VajVtbW1pbm5OZvY/ge/7AjQ7O6ujoyMNDg4qjmM1m011d3cL0MzMjC4uLlQul1Uul79V8CRreXlZ1Wo1A/b39zUwMJApWFlZ0fb2tiYnJ7OlApRLkkSAra+v6+PHD/bvP38zNDSE5Gg2mwD09vayubnJ6ekptVqNer0uSQYo55wzQKurq9b1y6+q/PnBrq6vePP2d+7v7wG4u7tjbGyMtrY2KpWKAHvaHwNugQIgfvvD4+Vr8eWLcfYJrv76KhbM7KmyS4G77JgA5xVeOZ71yZ73y9pfZP34uutm5lICpbk/d87/AbuJTsGhjfkLAAAAAElFTkSuQmCC"},"创建自定义组件-自定义组件-基本语法-学习ArkTS语言-入门-HarmonyOS应用开发")]),A("dt",null,[A("a",{href:"https://docs.openharmony.cn/pages/v4.0/zh-cn/application-dev/application-dev-guide.md/",add_date:"1705385890",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAClElEQVQ4jY1TW0iTYRh+vv/7/38HD81ZebjrrkYnCiW7iAjzsDK6aFQXSUGwctBVFMmUZdGFmAXiBoJ0gAJ/6SAeKloaBQVBpBdRhCPIA4m6tX9t7vTv7cKJ05b0XH58z/u933MAMkEkIAtsio1nOwcAlkFmYIwA4EhfX5Ek/SoVEVR7rBd8y3fAwEB/D0iT6/pHq2Rjoplr8+WyGAIXVKaTw5MiUz3uCmfrPzYgBjCqHvjRlJOHFopODosUuMuZ6pN5yCxL4RqdPuLgCI4lI/PH3Pu7fektGAASAUYHHs2d0uXqWmKhCcfg4b3uVY8MnPFecZuLpEbSWBkYxpfIAABLJ+VWPvXPVvdPe5aEtCkKBxE7+uSW6fRI2+alSQ0jtuLaoVodAObwlheCIIjrN8arAL5uITDbuqjFVfTCpYHANLFNFDk8595dL/4aS24zmqZ7t5sMP5+5cHxDiemL81PlNYEJqZ0ENvWmfsf3RRdcqbQ61F93cQ7x2AnStEuvZz8TkgvnQckOuJBioJMgeLP6nulQkkuMiPjuQIEgyFIimaIUAFBSozjiEFJEYwJD6b77Y5tAxACXkPYctuftZlEQHnBJvPHR3pVIJfhDUeCNABg475GYdFCYnzG8IEqE9PkFl8EYQbEwm6JwgAA1roWjcYenwmmxe+0VXJZ2xSKJDgAU+K1u/Tb+qpMBQOXjmXqj2XhPi0w0DFotntV/OfvSuSWvMPmW4v4Pt/d0WTNtXA7S0I+mnBy0IDI5zBC8I0sBHxciZh1XawyGqINp/veRYKCuu6bbn6ERrYjyoYHRar0+3szJXybzxSjLUnhKElS3u915E73Q/qtMVkUpzs9dKCFE1R6rfc0yrYSiZK3tWnX+A8NxJRTcUHS0AAAAAElFTkSuQmCC"},"应用开发导读")])]),A("p")]),A("dt",null,[A("h3",{add_date:"1705939974",last_modified:"1714641429"},"厂子"),A("dl",null,[A("p"),A("dt",null,[A("a",{href:"https://talent.baidu.com/jobs/list",add_date:"1695028131",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAADRUlEQVQ4jV1TS2icZRQ9937/vCfjn5B5JLF5TJpqKZaWtgtTEUtpArZuFKoVa3yDLgU16KIuBDcKKhhBLVRbglbX1Wyq0IRo7aIKoUlmkpioeU46k0wmk/n/7/uui9QHuavL5ZwDh3MusGPa3Rk325Qf7szMzbclcwe3r1dC2czMuWxq8tGdeP5vHQsCAELmaUWJo0TBJmacA4BspmvA4Ya3iYNft6bGj27jhXYI7PMAAIS9ENJWaoaIWlobcvsA6tN2xSOKsSKn7w6Z/xVIp3+NdTQv9QAAWUyDlMMcURD8aYWZoBQgioiZyNwESIBv/rEgHKXooEOhoY5M/jtrvUvarI6KeMva0vvh4sSkQP+suEEZW/xhXcvF1nQpC5w2AEAdqVwPsxoSWKPYVb4pnfl9qfOyix8TJRwrAUASy/FEEx5Y1yvDdSo4yOyeMKb07cxS7gUWUAfIASBbItaA0AqQdRqbTDYz83J7ZrZvBQObUwup76MIP8gUf8TatTBz5Klsavcp9jSuiPgLjsrErFSUv8VDyeRYJuEErzPFBhyOXOhMn70GjAUJJi2AAWQTYrUFN/L87d1/GGsfE+gPjPZP/VXM/paMRD81Nn5v1VurKV73WNV3dzWH3nHrIsOAUYqTUZA4wpik/5dCBNRSP3V+vsTPDnzk+htl47z+1hLu2RXWlUo4sHx77Y22FmcMku6t6dXrc8vZS9vJQ/jEwblmN5S/2n24IP39i/7MtGe/OF+U4w9NS9fdU6b70Kw+2bMirpN7dUcZhV46dCOQrpu4emR/SQoFz/vq4prNT9bkwmclmx/3bFdbXt57d9WMjlT9CC/K3l25j4HLChBigOSXSrq7XA4ce/yM0bfGffXE2UmMXKtKPEFU3tA0v6ClUPB5Y0OzlZK1EnylJXngPoCEAcCr+YlohDD60yYd2B/Gi89ncPj+sGifdCym9Gv99XK8Ny6hEJPAAhAJgDN3/APNdRONsTq+VVwLNT7ZF8DDJ+8CoDD4ZRWeL3jmuQg87WN0pGo//6TCbsJWiqXAnsJm6zxtPwZJeyrXGwxEP1xYLKc8gS8ic/E439wqC1Vhj4SY9kQjzI1urFrzq2/OLncNAKC/AStrgmhLItCXAAAAAElFTkSuQmCC"},"百度校园招聘")]),A("dt",null,[A("a",{href:"https://www.bytedance.com/zh/",add_date:"1693921801",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACA0lEQVQ4jY2SP2tUURDFfzPvZmP+rAmJ2oqFCEkhxEpBBLXRDxC18BPYKIi2a28jKPgJLEKwEIR0QQsxQmLSGAza2GiQTczuuuHte3fGInd3jQTMVAMzc+acMwO9cOGQUfPF0M3D2VsL02sbLzdYluKwALE19eRRvjXZ/rW1qCq2fG7q5urM7bd3/zvpiaXFk6EyeKMyNPpcsTJoNjCFF9dS20FShLm5DNAE1MmbjbLTaOQBxM1yw72V1hyE4czOxn2AEESFICCCKOpZrzznGZ9wamIALK1PMjx0iaJQZk7PmyDq4O4EF9zdcccTuDNLf9vKynE6xRKjk6doNl4B8xgqGYDIHgORnvaLz/yBd2yiRBfe3+MNth7w8hjttmHWTI3exde+yJRHzg+O68PM/dWVO0zAZoFIiaqgme6fAu1Rd++i/i7bFB6J8QjCyLD3nsyi/LMXFREc8KTDBXUloEK1CWhbcBfMIkgEMLO0z10dXNyjuMeuEeKA480qUA4aWYgcHQsI1dQjouoCBCBoZUSs0xrbo4m5IAKVnRNUmL7wnZXPl6nXryK6m76gdKc0cwKiPyx2PprLi3TFqgYhwtfdSA44M2fWgLW+8zI8VB0fiHlOqO9sT397fX27WzSTp2Y83qzw4ct9yZPBkpxzRIxs4F2r/nMVk8X+a9Zqfx3n8PEHOVbw9KG9a0IAAAAASUVORK5CYII="},"字节跳动")]),A("dt",null,[A("a",{href:"https://zhaopin.meituan.com/web/home",add_date:"1705940926",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACnklEQVQ4jS3TTYiVZRjG8d/9nveM86Vjmhq6MMEcPzJdtKpti2yV1cbJgSDc9GHNIsogw7CFmxZRGC0KQkIqyIhIWrRzWcxojBhWpqCJkYjHOfNxznO3eOdZPYvnvp7/dXNdAXne0yW8q2e8GjCkUHoSoTlZ1UJFWdRVu1SlY/GIM5HT9qudMmzYAleuyrERcd969JfHW/x3k7tzcvNmYQBz5vQcjJxxQe3h2ct6nTmt56bEo7vksSn6XZFoDcojJ7j4l/j6Azk8qL/jIbVFv0XOWOguaO97ictXBbIUMTLIzdtUwbrVdOZpVY2t7VvkDx+zorYUZVrJEBcuyVu3adcik7rFofcYHebDN5lfoKpYWpIb1rFrq4iUdSbVEN/+LD47w9go/dL8/Pd1WstCpd/c73TEaxPs3k25Rx1BLvLME3LvdtGuKclAm9dPMDLEiSnmussEPbZtlrkgIqhLEa0hvjorPv+OsZX0+w3BlevN0Kvv009aFXc6HJ4QO3fR74go01LFtRvcut14z6SqmXyLlcN8coyl+Uas1+eBtWzcgELkeTnXZfJtee0fUVWUwugIv85S1+wZ526nEegXtmySXxwXg4PUyHYtJvaJOx0GBxkd5cvvG1xYu4rDB5nvNrtYvUrUdZPQWqFd8+xE87h7g5On+fEcR15saD46zY6tvHyA9VuWA/4vEmVGt1xQjr+iPLZXrlmlbFovT74j86LMWfnpUblxnRyolT3b5BsvKAu/KDljvo7w5+KinbN/6G1/UGtqUjz5uBy9X5S7DdWhSQ48Jc+eE9/8JNeM6Q+MqbPj96ZMlVNGDYvlAnXpLzVLo7HRamNoGb/CPXOK5wNy2v6sHS2LxoUVVTQhyWzqHCEzKUUGC9F2KZbr/D/g3x2ZfKElrQAAAABJRU5ErkJggg=="},"首页 | 美团招聘")]),A("dt",null,[A("a",{href:"https://jobs.bytedance.com/campus/position/7257084116544899384/detail?recomId=69a45ee6-d565-11ee-83bb-043f72d0bb4e&sourceJobId=7232207651055454522",add_date:"1709034396",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACA0lEQVQ4jY2SP2tUURDFfzPvZmP+rAmJ2oqFCEkhxEpBBLXRDxC18BPYKIi2a28jKPgJLEKwEIR0QQsxQmLSGAza2GiQTczuuuHte3fGInd3jQTMVAMzc+acMwO9cOGQUfPF0M3D2VsL02sbLzdYluKwALE19eRRvjXZ/rW1qCq2fG7q5urM7bd3/zvpiaXFk6EyeKMyNPpcsTJoNjCFF9dS20FShLm5DNAE1MmbjbLTaOQBxM1yw72V1hyE4czOxn2AEESFICCCKOpZrzznGZ9wamIALK1PMjx0iaJQZk7PmyDq4O4EF9zdcccTuDNLf9vKynE6xRKjk6doNl4B8xgqGYDIHgORnvaLz/yBd2yiRBfe3+MNth7w8hjttmHWTI3exde+yJRHzg+O68PM/dWVO0zAZoFIiaqgme6fAu1Rd++i/i7bFB6J8QjCyLD3nsyi/LMXFREc8KTDBXUloEK1CWhbcBfMIkgEMLO0z10dXNyjuMeuEeKA480qUA4aWYgcHQsI1dQjouoCBCBoZUSs0xrbo4m5IAKVnRNUmL7wnZXPl6nXryK6m76gdKc0cwKiPyx2PprLi3TFqgYhwtfdSA44M2fWgLW+8zI8VB0fiHlOqO9sT397fX27WzSTp2Y83qzw4ct9yZPBkpxzRIxs4F2r/nMVk8X+a9Zqfx3n8PEHOVbw9KG9a0IAAAAASUVORK5CYII="},"前端开发实习生-飞书办公套件 - 字节跳动")]),A("dt",null,[A("a",{href:"https://jobs.bytedance.com/campus/position/7304542369528645897/detail?recomId=e7c51438-d565-11ee-914c-043f72b554ec&sourceJobId=7287477214408608059",add_date:"1709034459",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACA0lEQVQ4jY2SP2tUURDFfzPvZmP+rAmJ2oqFCEkhxEpBBLXRDxC18BPYKIi2a28jKPgJLEKwEIR0QQsxQmLSGAza2GiQTczuuuHte3fGInd3jQTMVAMzc+acMwO9cOGQUfPF0M3D2VsL02sbLzdYluKwALE19eRRvjXZ/rW1qCq2fG7q5urM7bd3/zvpiaXFk6EyeKMyNPpcsTJoNjCFF9dS20FShLm5DNAE1MmbjbLTaOQBxM1yw72V1hyE4czOxn2AEESFICCCKOpZrzznGZ9wamIALK1PMjx0iaJQZk7PmyDq4O4EF9zdcccTuDNLf9vKynE6xRKjk6doNl4B8xgqGYDIHgORnvaLz/yBd2yiRBfe3+MNth7w8hjttmHWTI3exde+yJRHzg+O68PM/dWVO0zAZoFIiaqgme6fAu1Rd++i/i7bFB6J8QjCyLD3nsyi/LMXFREc8KTDBXUloEK1CWhbcBfMIkgEMLO0z10dXNyjuMeuEeKA480qUA4aWYgcHQsI1dQjouoCBCBoZUSs0xrbo4m5IAKVnRNUmL7wnZXPl6nXryK6m76gdKc0cwKiPyx2PprLi3TFqgYhwtfdSA44M2fWgLW+8zI8VB0fiHlOqO9sT397fX27WzSTp2Y83qzw4ct9yZPBkpxzRIxs4F2r/nMVk8X+a9Zqfx3n8PEHOVbw9KG9a0IAAAAASUVORK5CYII="},"前端开发实习生-飞书 - 字节跳动")]),A("dt",null,[A("a",{href:"https://jobs.bytedance.com/campus/position/7326844881812228361/detail",add_date:"1709034593",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACA0lEQVQ4jY2SP2tUURDFfzPvZmP+rAmJ2oqFCEkhxEpBBLXRDxC18BPYKIi2a28jKPgJLEKwEIR0QQsxQmLSGAza2GiQTczuuuHte3fGInd3jQTMVAMzc+acMwO9cOGQUfPF0M3D2VsL02sbLzdYluKwALE19eRRvjXZ/rW1qCq2fG7q5urM7bd3/zvpiaXFk6EyeKMyNPpcsTJoNjCFF9dS20FShLm5DNAE1MmbjbLTaOQBxM1yw72V1hyE4czOxn2AEESFICCCKOpZrzznGZ9wamIALK1PMjx0iaJQZk7PmyDq4O4EF9zdcccTuDNLf9vKynE6xRKjk6doNl4B8xgqGYDIHgORnvaLz/yBd2yiRBfe3+MNth7w8hjttmHWTI3exde+yJRHzg+O68PM/dWVO0zAZoFIiaqgme6fAu1Rd++i/i7bFB6J8QjCyLD3nsyi/LMXFREc8KTDBXUloEK1CWhbcBfMIkgEMLO0z10dXNyjuMeuEeKA480qUA4aWYgcHQsI1dQjouoCBCBoZUSs0xrbo4m5IAKVnRNUmL7wnZXPl6nXryK6m76gdKc0cwKiPyx2PprLi3TFqgYhwtfdSA44M2fWgLW+8zI8VB0fiHlOqO9sT397fX27WzSTp2Y83qzw4ct9yZPBkpxzRIxs4F2r/nMVk8X+a9Zqfx3n8PEHOVbw9KG9a0IAAAAASUVORK5CYII="},"前端开发实习生-商业产品与技术 - 字节跳动")]),A("dt",null,[A("a",{href:"https://app.mokahr.com/apply/zhihu/78336#/",add_date:"1711040659",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAACBklEQVQ4jW3SMWyVVRgG4Oec/9z2tr1taYsFqdaENK1RBh1VBmPYTRwcWJBBHYxOTix2YWMlwck4OBijiRMmTTASBgYSoTSS2JBAIEgFKZTe29ve/57DYiNEvrzLl7zTmyf0+uWrX319yT81iUhFRfzvDZVXWk686eM5YXGpLJ5l5Nn2/4Pkx/dUf84vbvbESimgEDxzhSAluaddpMdtpa90VU0lC0EoStYvFDIVWV0TPexKIVM7sNedeyQCFQ1GBBpRYU/T5LAbj+Va6nQs7PP9cT+tuHbfgXGtpj/uubHpozcI5ve6tWF00GcX1D1RbX7Sb6ummq6veftll2/78JCV29Y7vlu2fNc3y5ZuerBmKIgpuvm37R0TQ16btvKXs9fc33SwpZV8sGBy0LHXzQx7acx2T6yirS1DyWCl2yXL2+qemQl31q23HXrB1TXdHdMtOztSI1m95UHH9JjWgLkpn75l/6hzV5z7XXPKzJjT50Hl8Kvixpb3D5sY9uUPOl0Xr9s/4tSSmDSGnTwi9yUGmkKUa2H08xKKjW36u8NnBhiiMj6indVFaCjZO3NSDB61qYQEglAIcg2PNkg0qIUsZHF2kr5GEoIQBP/SiFnsq6iK2JeKsuXguPDz5XL0jPbmrrCnCdnlmMhmZ/3yiVBKOb/q2wvWu8Lz2AlK8OIeX7xrYdoTcprFIEMKV9wAAAAASUVORK5CYII="},"知乎招聘 - 智者四海(北京)技术有限公司")]),A("dt",null,[A("a",{href:"https://campus.xiaohongshu.com/?referer_code=R6XGAKFL23NP",add_date:"1711103525",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACOElEQVQ4jX3Tz2vcVRQF8M998yOmkw5qDFERLNTUoqsi1CJqAoKCKLry18KdLlpwJ91EN/4TrtwFwY26E7qSbApVKrGIuFCkVqtoSTozaTuT+V4XL4k1gg8e77777j33wDsncmn5XaX1nswekAEi6rm3MrMGkbvvI830g8il5VsiujKbA00hkIi4DWAXLKLIHLeFzn+a9+KkgmTN7YHUuBHRaRN1VkQoJTVNLSylMoBSaJrUalVOTRO7OW3Uy3SatgahP5daJYxuVOqBnSmddrh5K6XQm0070zDT1RaRhiP6h8Nbb6SPPwt/bbFykm6XX69y7wKXf+OJx4JI59bD4nz66Rdt40k4eoTFe3jthXDxEn9e4+xp1s9z91O8/BxnVnn1RY48EJ4+yYWNqACDIW+/zmjEdz/wyvOV8nDIIw/z4RoL85VBBJtbnDrBR58wnii6He7q8+NlWm02rzPc5qsNfr7C1T8Yjxlt0zvEld9Z+5z336FVFJ0O579hdpajD7J+obI5/hDXNjnzJs88WVmcW+fipbq73SqVPLbSGI5YPhXuW0xrn4ZHl3jpWTa+5/ETtApff1vZlUj3L4bN6+mLL0UeW0kRjLbr19zZT+NxGIyY6XLjZtXCHTO0WkmG8SSVVjh8SBspk7leiEjTaeh00sJ8yIb+XJVj0+zrUi/qOZ1qk6TUNPtKR9jZ2a/ngJTr0BQRRZrsGuOgWarxIv/PTJOCVVEGovxj1dsMyb9dTaQoRBlg9W/aqf2ItboGxQAAAABJRU5ErkJggg=="},"小红书校园招聘")]),A("dt",null,[A("a",{href:"https://zhaopin.meituan.com/web/campus",add_date:"1711346914",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACnklEQVQ4jS3TTYiVZRjG8d/9nveM86Vjmhq6MMEcPzJdtKpti2yV1cbJgSDc9GHNIsogw7CFmxZRGC0KQkIqyIhIWrRzWcxojBhWpqCJkYjHOfNxznO3eOdZPYvnvp7/dXNdAXne0yW8q2e8GjCkUHoSoTlZ1UJFWdRVu1SlY/GIM5HT9qudMmzYAleuyrERcd969JfHW/x3k7tzcvNmYQBz5vQcjJxxQe3h2ct6nTmt56bEo7vksSn6XZFoDcojJ7j4l/j6Azk8qL/jIbVFv0XOWOguaO97ictXBbIUMTLIzdtUwbrVdOZpVY2t7VvkDx+zorYUZVrJEBcuyVu3adcik7rFofcYHebDN5lfoKpYWpIb1rFrq4iUdSbVEN/+LD47w9go/dL8/Pd1WstCpd/c73TEaxPs3k25Rx1BLvLME3LvdtGuKclAm9dPMDLEiSnmussEPbZtlrkgIqhLEa0hvjorPv+OsZX0+w3BlevN0Kvv009aFXc6HJ4QO3fR74go01LFtRvcut14z6SqmXyLlcN8coyl+Uas1+eBtWzcgELkeTnXZfJtee0fUVWUwugIv85S1+wZ526nEegXtmySXxwXg4PUyHYtJvaJOx0GBxkd5cvvG1xYu4rDB5nvNrtYvUrUdZPQWqFd8+xE87h7g5On+fEcR15saD46zY6tvHyA9VuWA/4vEmVGt1xQjr+iPLZXrlmlbFovT74j86LMWfnpUblxnRyolT3b5BsvKAu/KDljvo7w5+KinbN/6G1/UGtqUjz5uBy9X5S7DdWhSQ48Jc+eE9/8JNeM6Q+MqbPj96ZMlVNGDYvlAnXpLzVLo7HRamNoGb/CPXOK5wNy2v6sHS2LxoUVVTQhyWzqHCEzKUUGC9F2KZbr/D/g3x2ZfKElrQAAAABJRU5ErkJggg=="},"校园招聘 | 美团招聘")]),A("dt",null,[A("a",{href:"https://zhaopin.meituan.com/web/position/detail?jobUnionId=2309762897&highlightType=campus",add_date:"1711347628",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACnklEQVQ4jS3TTYiVZRjG8d/9nveM86Vjmhq6MMEcPzJdtKpti2yV1cbJgSDc9GHNIsogw7CFmxZRGC0KQkIqyIhIWrRzWcxojBhWpqCJkYjHOfNxznO3eOdZPYvnvp7/dXNdAXne0yW8q2e8GjCkUHoSoTlZ1UJFWdRVu1SlY/GIM5HT9qudMmzYAleuyrERcd969JfHW/x3k7tzcvNmYQBz5vQcjJxxQe3h2ct6nTmt56bEo7vksSn6XZFoDcojJ7j4l/j6Azk8qL/jIbVFv0XOWOguaO97ictXBbIUMTLIzdtUwbrVdOZpVY2t7VvkDx+zorYUZVrJEBcuyVu3adcik7rFofcYHebDN5lfoKpYWpIb1rFrq4iUdSbVEN/+LD47w9go/dL8/Pd1WstCpd/c73TEaxPs3k25Rx1BLvLME3LvdtGuKclAm9dPMDLEiSnmussEPbZtlrkgIqhLEa0hvjorPv+OsZX0+w3BlevN0Kvv009aFXc6HJ4QO3fR74go01LFtRvcut14z6SqmXyLlcN8coyl+Uas1+eBtWzcgELkeTnXZfJtee0fUVWUwugIv85S1+wZ526nEegXtmySXxwXg4PUyHYtJvaJOx0GBxkd5cvvG1xYu4rDB5nvNrtYvUrUdZPQWqFd8+xE87h7g5On+fEcR15saD46zY6tvHyA9VuWA/4vEmVGt1xQjr+iPLZXrlmlbFovT74j86LMWfnpUblxnRyolT3b5BsvKAu/KDljvo7w5+KinbN/6G1/UGtqUjz5uBy9X5S7DdWhSQ48Jc+eE9/8JNeM6Q+MqbPj96ZMlVNGDYvlAnXpLzVLo7HRamNoGb/CPXOK5wNy2v6sHS2LxoUVVTQhyWzqHCEzKUUGC9F2KZbr/D/g3x2ZfKElrQAAAABJRU5ErkJggg=="},"职位详情 | 美团招聘")]),A("dt",null,[A("a",{href:"https://jobs.bytedance.com/campus/position/7347949518421788954/detail",add_date:"1711350391",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACA0lEQVQ4jY2SP2tUURDFfzPvZmP+rAmJ2oqFCEkhxEpBBLXRDxC18BPYKIi2a28jKPgJLEKwEIR0QQsxQmLSGAza2GiQTczuuuHte3fGInd3jQTMVAMzc+acMwO9cOGQUfPF0M3D2VsL02sbLzdYluKwALE19eRRvjXZ/rW1qCq2fG7q5urM7bd3/zvpiaXFk6EyeKMyNPpcsTJoNjCFF9dS20FShLm5DNAE1MmbjbLTaOQBxM1yw72V1hyE4czOxn2AEESFICCCKOpZrzznGZ9wamIALK1PMjx0iaJQZk7PmyDq4O4EF9zdcccTuDNLf9vKynE6xRKjk6doNl4B8xgqGYDIHgORnvaLz/yBd2yiRBfe3+MNth7w8hjttmHWTI3exde+yJRHzg+O68PM/dWVO0zAZoFIiaqgme6fAu1Rd++i/i7bFB6J8QjCyLD3nsyi/LMXFREc8KTDBXUloEK1CWhbcBfMIkgEMLO0z10dXNyjuMeuEeKA480qUA4aWYgcHQsI1dQjouoCBCBoZUSs0xrbo4m5IAKVnRNUmL7wnZXPl6nXryK6m76gdKc0cwKiPyx2PprLi3TFqgYhwtfdSA44M2fWgLW+8zI8VB0fiHlOqO9sT397fX27WzSTp2Y83qzw4ct9yZPBkpxzRIxs4F2r/nMVk8X+a9Zqfx3n8PEHOVbw9KG9a0IAAAAASUVORK5CYII="},"前端开发实习生-抖音电商 - 字节跳动")]),A("dt",null,[A("a",{href:"https://talent-holding.alibaba.com/campus/position-list?campusType=internship&lang=zh",add_date:"1711520181",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABd0lEQVQ4jaXTP0iVURzG8c95VRJpjTQxMiIlEQMpCAKjJqE/ULgFkhYNIhTXQRrCaEioltyiIFpECmpoEocKCoLon1BhkDncm0MYoqBg8Ta8b11f6RJdf8s558fv+5zzPHBCnNOJ26jzf/UVvSHOKZQB/xGJ1gFDXeU/R3YfY89ZtndQVZP05j4zepzpt6KS4JFhrsWcfEhTJyHiUT8Dgcn79L2hihDnxBlwSz3nZogq+DTOs+t8GOfnmguGl7ixQ9bCrkP0TCT7gVDaVmMrldUU8qssNLYW4YmhZN2AwSlq1gj0veNeN35bCLiaOvn+hfwrQmB5ntFTWfj8S34sM7IfUgtdN4sDhdfcOfH3px++Qn17xl4isPdMcroUWCjhe1sLBwa5nM2mGOLKUhaO0NBM4SMrOHor6e+7SG0bm5qZHEsz2LqTnqds3JwMzTxn+gmPL7CYCrZ3cXCIb1O8f8CLu6tCXEdF6a8qt2YjsdOYLQPOi/X+AikmZLlaTxk5AAAAAElFTkSuQmCC"},"阿里巴巴控股集团校园招聘")]),A("dt",null,[A("a",{href:"https://careers.tencent.com/home.html",add_date:"1712031224",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABv0lEQVQ4jYWSPU9UQRSGnzM7sxE/IhAbKhPXFUk0MZoYCxsrCxURwhZqY2Fj6x+41hb+Dt2opQ2F0UQSPxoLuAghMbFTBDe4cOXOHIs7613JXTjVmTNnnjPvOwOD4trnE1x9M1YsntUGtZkyVQE1XPjoIDHU69c5enKOm4tnoeX3glTHrfQxdzrKzOoPptJW3xDpb7P/ssnVcZwbxmc5QQT8JbKfAc1HsfWnTC9P8EIelSDRKCEpZNTyJuTziH6glr9H9DIhM6gGgg+4IwkzK8+5sXSsdxhA/iNOpU9wh+7jf4PKwdghKBlIF+OGwc8T5B4vG18AiSZG4rnxh+RyHJUHiO2xFeQ2+CY7m2MYe5c/9nvP+dIDgKQt0FpjenkUNyRkHcVol63uW16dX9vnGQEWtJATzqAoxoHKV7Z+rQ96hV2AJERbTuO3hZoTkAVeX8lLqaWBFQBgMj0MnCLsKKauEJbi9MqPtOsnAsY0UB0BlLAtKGmx36463w9ox9xPYA4U5vrMg10s6rNhH8BslGma2KEcNKCssykrpf49AeJjchFjLfURC3xjrtGpvHsMW1H7RLaxgTgB3gGVk3vxF9CcoprSI8erAAAAAElFTkSuQmCC"},"首页 | 腾讯招聘")]),A("dt",null,[A("a",{href:"https://http3check.net/?host=https%3A%2F%2Fwww.hnucm.edu.cn%2F",add_date:"1712075270",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAADV0lEQVQ4jW2TXWhbZRjH/8/7npPPJWmSpswI65y4tZu4iaObSlEYqR+dYnW9mnOITOxYb0YLA4c9Tq1U2g0RLCJjc1fDMmHuoo5OKVqqOOeFxrFgO+00XW3SfDRN0pNzzvt4UVqK+Lt74PfneeDhT1hPZ6fEyIgDAPuPDh6AEL1EXLVtHhgd7hld5ygADAC0kjQEYCgAaHvjvQd16TnF4I5abRlEAi5dQ82hzy0L74yf7U2uz9Dq8j0HjWC0bkMvmHsU4PG4XNmt9++ssFOTydvTXq+sRmzLMi2lfbhUzQ5+d+79zNoFT3X1t+q69xNmbnZsa+mBzc3Z5m0PN4QjDb7ckkLM/CHfuHzF7P/+noCppF8jmnFss2t0+MQotR3q8WvBhptCujaFg+H0Q9v3eOLxzdGlpcVMpuI+lQhcOtBSP/3EleJBJDOeXOq3b5ezxVycHWtuoVjdJqQ7FlaOqodSxUd37wtFI7FoLjd/wWWVW94MdMZb9C9bswX79WSx8aN4JBjZuzsRFMCiUioYCgVCmq2gBFjpUq9KqW00zepY94ubDvPVl87jvq2H7ZnSu7HEwKfAAIYvp5tIiIQm9LkamwFbVZUAACKCYiYQQQJFwzCEkoFfUDahwWoEAMMwBIFKECsuCQLggwgEbQYzAICZoYRgoA9i37kzvFAeQb3vkDP2SrfR18fEjiAmrEcs13RmAIKIAYZynIBhkCIidtg9A78HApUmEDELqmMoCCJmxQAq0BxbJ5KKbGX5FKuy1+N9uv+LytDRwMkpTdw+jum0cRFXPxu8kDqrSc9jNcss2Y7lIwK5dZ00UyuWfOwrKcbGH69fSzfteNLcVX/3eCjoxeR8x9cT9suZbHr8qxIWtugF32JhLqMUISak/LsyVynIP6+P17Y80jal6bK1WCnfuzCbdBLRidxE+Vn7jrd9exD59pliyt77WsJfKhc2TP18y6/p2u+Oso9cO38yJQFg+sZYKrZj50Wv5teYnccn74aDP/1RqHhQys3OpxfTs3fqdJfH/c+tv8qFdGaoBOfVbz4+cfN/y5Q48sEuKektKbmjZlkAAE26YFWrl/L5/Ns3Lp/59b+ZVQiGIVaH9q6h55/rPj2x/9jpyWeODb6wZq04a7/8F5bMma1aZnAbAAAAAElFTkSuQmCC"},"HTTP/3 Check - www.hnucm.edu.cn")]),A("dt",null,[A("a",{href:"https://http3check.net/?host=",add_date:"1712075283",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAADV0lEQVQ4jW2TXWhbZRjH/8/7npPPJWmSpswI65y4tZu4iaObSlEYqR+dYnW9mnOITOxYb0YLA4c9Tq1U2g0RLCJjc1fDMmHuoo5OKVqqOOeFxrFgO+00XW3SfDRN0pNzzvt4UVqK+Lt74PfneeDhT1hPZ6fEyIgDAPuPDh6AEL1EXLVtHhgd7hld5ygADAC0kjQEYCgAaHvjvQd16TnF4I5abRlEAi5dQ82hzy0L74yf7U2uz9Dq8j0HjWC0bkMvmHsU4PG4XNmt9++ssFOTydvTXq+sRmzLMi2lfbhUzQ5+d+79zNoFT3X1t+q69xNmbnZsa+mBzc3Z5m0PN4QjDb7ckkLM/CHfuHzF7P/+noCppF8jmnFss2t0+MQotR3q8WvBhptCujaFg+H0Q9v3eOLxzdGlpcVMpuI+lQhcOtBSP/3EleJBJDOeXOq3b5ezxVycHWtuoVjdJqQ7FlaOqodSxUd37wtFI7FoLjd/wWWVW94MdMZb9C9bswX79WSx8aN4JBjZuzsRFMCiUioYCgVCmq2gBFjpUq9KqW00zepY94ubDvPVl87jvq2H7ZnSu7HEwKfAAIYvp5tIiIQm9LkamwFbVZUAACKCYiYQQQJFwzCEkoFfUDahwWoEAMMwBIFKECsuCQLggwgEbQYzAICZoYRgoA9i37kzvFAeQb3vkDP2SrfR18fEjiAmrEcs13RmAIKIAYZynIBhkCIidtg9A78HApUmEDELqmMoCCJmxQAq0BxbJ5KKbGX5FKuy1+N9uv+LytDRwMkpTdw+jum0cRFXPxu8kDqrSc9jNcss2Y7lIwK5dZ00UyuWfOwrKcbGH69fSzfteNLcVX/3eCjoxeR8x9cT9suZbHr8qxIWtugF32JhLqMUISak/LsyVynIP6+P17Y80jal6bK1WCnfuzCbdBLRidxE+Vn7jrd9exD59pliyt77WsJfKhc2TP18y6/p2u+Oso9cO38yJQFg+sZYKrZj50Wv5teYnccn74aDP/1RqHhQys3OpxfTs3fqdJfH/c+tv8qFdGaoBOfVbz4+cfN/y5Q48sEuKektKbmjZlkAAE26YFWrl/L5/Ns3Lp/59b+ZVQiGIVaH9q6h55/rPj2x/9jpyWeODb6wZq04a7/8F5bMma1aZnAbAAAAAElFTkSuQmCC"},"HTTP/3 Check")]),A("dt",null,[A("a",{href:"https://talent.amap.com/personal/campus-resume?batchId=5000000005&campusType=internship&cartIds=5000060514&lang=zh",add_date:"1712467726",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABd0lEQVQ4jaXTP0iVURzG8c95VRJpjTQxMiIlEQMpCAKjJqE/ULgFkhYNIhTXQRrCaEioltyiIFpECmpoEocKCoLon1BhkDncm0MYoqBg8Ta8b11f6RJdf8s558fv+5zzPHBCnNOJ26jzf/UVvSHOKZQB/xGJ1gFDXeU/R3YfY89ZtndQVZP05j4zepzpt6KS4JFhrsWcfEhTJyHiUT8Dgcn79L2hihDnxBlwSz3nZogq+DTOs+t8GOfnmguGl7ixQ9bCrkP0TCT7gVDaVmMrldUU8qssNLYW4YmhZN2AwSlq1gj0veNeN35bCLiaOvn+hfwrQmB5ntFTWfj8S34sM7IfUgtdN4sDhdfcOfH3px++Qn17xl4isPdMcroUWCjhe1sLBwa5nM2mGOLKUhaO0NBM4SMrOHor6e+7SG0bm5qZHEsz2LqTnqds3JwMzTxn+gmPL7CYCrZ3cXCIb1O8f8CLu6tCXEdF6a8qt2YjsdOYLQPOi/X+AikmZLlaTxk5AAAAAElFTkSuQmCC"},"高德地图招聘官网")]),A("dt",null,[A("a",{href:"https://talent.amap.com/personal/campus-application?lang=zh",add_date:"1712481629",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABd0lEQVQ4jaXTP0iVURzG8c95VRJpjTQxMiIlEQMpCAKjJqE/ULgFkhYNIhTXQRrCaEioltyiIFpECmpoEocKCoLon1BhkDncm0MYoqBg8Ta8b11f6RJdf8s558fv+5zzPHBCnNOJ26jzf/UVvSHOKZQB/xGJ1gFDXeU/R3YfY89ZtndQVZP05j4zepzpt6KS4JFhrsWcfEhTJyHiUT8Dgcn79L2hihDnxBlwSz3nZogq+DTOs+t8GOfnmguGl7ixQ9bCrkP0TCT7gVDaVmMrldUU8qssNLYW4YmhZN2AwSlq1gj0veNeN35bCLiaOvn+hfwrQmB5ntFTWfj8S34sM7IfUgtdN4sDhdfcOfH3px++Qn17xl4isPdMcroUWCjhe1sLBwa5nM2mGOLKUhaO0NBM4SMrOHor6e+7SG0bm5qZHEsz2LqTnqds3JwMzTxn+gmPL7CYCrZ3cXCIb1O8f8CLu6tCXEdF6a8qt2YjsdOYLQPOi/X+AikmZLlaTxk5AAAAAElFTkSuQmCC"},"高德地图招聘官网")]),A("dt",null,[A("a",{href:"https://job.xiaohongshu.com/record/campus?referer_code=R6XGAKFL23NP",add_date:"1713086379",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACOElEQVQ4jX3Tz2vcVRQF8M998yOmkw5qDFERLNTUoqsi1CJqAoKCKLry18KdLlpwJ91EN/4TrtwFwY26E7qSbApVKrGIuFCkVqtoSTozaTuT+V4XL4k1gg8e77777j33wDsncmn5XaX1nswekAEi6rm3MrMGkbvvI830g8il5VsiujKbA00hkIi4DWAXLKLIHLeFzn+a9+KkgmTN7YHUuBHRaRN1VkQoJTVNLSylMoBSaJrUalVOTRO7OW3Uy3SatgahP5daJYxuVOqBnSmddrh5K6XQm0070zDT1RaRhiP6h8Nbb6SPPwt/bbFykm6XX69y7wKXf+OJx4JI59bD4nz66Rdt40k4eoTFe3jthXDxEn9e4+xp1s9z91O8/BxnVnn1RY48EJ4+yYWNqACDIW+/zmjEdz/wyvOV8nDIIw/z4RoL85VBBJtbnDrBR58wnii6He7q8+NlWm02rzPc5qsNfr7C1T8Yjxlt0zvEld9Z+5z336FVFJ0O579hdpajD7J+obI5/hDXNjnzJs88WVmcW+fipbq73SqVPLbSGI5YPhXuW0xrn4ZHl3jpWTa+5/ETtApff1vZlUj3L4bN6+mLL0UeW0kRjLbr19zZT+NxGIyY6XLjZtXCHTO0WkmG8SSVVjh8SBspk7leiEjTaeh00sJ8yIb+XJVj0+zrUi/qOZ1qk6TUNPtKR9jZ2a/ngJTr0BQRRZrsGuOgWarxIv/PTJOCVVEGovxj1dsMyb9dTaQoRBlg9W/aqf2ItboGxQAAAABJRU5ErkJggg=="},"投递记录")]),A("dt",null,[A("a",{href:"https://jobs.bytedance.com/referral/campus/pc/position?keywords=%E5%89%8D%E7%AB%AF&category=6704215862603155720&location=&project=7194661126919358757&type=202&job_hot_flag=¤t=1&limit=10&functionCategory=&tag=&token=MzsxNzA5NjI3MTExNTk0OzY5OTQyNDkwMzgyMjg4ODI5NTg7MDsx",add_date:"1713172525",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACA0lEQVQ4jY2SP2tUURDFfzPvZmP+rAmJ2oqFCEkhxEpBBLXRDxC18BPYKIi2a28jKPgJLEKwEIR0QQsxQmLSGAza2GiQTczuuuHte3fGInd3jQTMVAMzc+acMwO9cOGQUfPF0M3D2VsL02sbLzdYluKwALE19eRRvjXZ/rW1qCq2fG7q5urM7bd3/zvpiaXFk6EyeKMyNPpcsTJoNjCFF9dS20FShLm5DNAE1MmbjbLTaOQBxM1yw72V1hyE4czOxn2AEESFICCCKOpZrzznGZ9wamIALK1PMjx0iaJQZk7PmyDq4O4EF9zdcccTuDNLf9vKynE6xRKjk6doNl4B8xgqGYDIHgORnvaLz/yBd2yiRBfe3+MNth7w8hjttmHWTI3exde+yJRHzg+O68PM/dWVO0zAZoFIiaqgme6fAu1Rd++i/i7bFB6J8QjCyLD3nsyi/LMXFREc8KTDBXUloEK1CWhbcBfMIkgEMLO0z10dXNyjuMeuEeKA480qUA4aWYgcHQsI1dQjouoCBCBoZUSs0xrbo4m5IAKVnRNUmL7wnZXPl6nXryK6m76gdKc0cwKiPyx2PprLi3TFqgYhwtfdSA44M2fWgLW+8zI8VB0fiHlOqO9sT397fX27WzSTp2Y83qzw4ct9yZPBkpxzRIxs4F2r/nMVk8X+a9Zqfx3n8PEHOVbw9KG9a0IAAAAASUVORK5CYII="},"字节跳动内推")]),A("dt",null,[A("a",{href:"https://app.mokahr.com/apply/didiglobal/6222#/job/77df628f-a435-4bfa-b8dd-39dfb17c6d6d",add_date:"1714359052",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABpElEQVQ4jX2SS2tTYRCGn5nvnKSx1ejiiCgEIVADBQUVxAu46g8Q/AmCuHHpH3CloEtFRRDcuSjoptC6ELtyI4ioUARR8G4lbQhNc86ZcfGdCNHQYZbvXN5nRtydmNuECCK4IyK+vfS/SAA2e6x9JoTJEnOyFmk9jkoAnt5n+R7NjLJA5N+CIufyAw50MBsVFDlJDQl4PsGMO6LjK6lS5kw3OX+dkEZz4DiV12cP+bLKkXnmLySRAmbUGrSPTbbx5AarL9g/O5oQweVbDAckadW4WgasRAI7dlGbAhR3GjNowsZP1n8gCl6Bj436XX59ApiawV0RoXOaeoN8wOItRAgJ7rgRAqIs3WXQJ61z6CQiipW05pg7Sz7k3QqPrtL7jSoa2Nzg8U1eLlIOmT1B+zhWikf2/S63L/L1PUnKzoyDh1Hl42u63ygKshaX7tDci5u4O2ao0ltj4RpvnrPVp8wBQkp9ms4pzl1hz74oEzdDpKoBPrzi7Qrr33HYndE5Q/sogJVowF3crKLhDmNHHV06CjSCHn3r34+IfEQR4vOMtRD5A0cEsaSJIRJgAAAAAElFTkSuQmCC"},"滴滴 -实习生招聘")]),A("dt",null,[A("a",{href:"https://app.mokahr.com/apply/didiglobal/6222#/job/803949ab-faed-4f4d-a158-720c1af206f2",add_date:"1714359082",icon:"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAIAAACQkWg2AAABpElEQVQ4jX2SS2tTYRCGn5nvnKSx1ejiiCgEIVADBQUVxAu46g8Q/AmCuHHpH3CloEtFRRDcuSjoptC6ELtyI4ioUARR8G4lbQhNc86ZcfGdCNHQYZbvXN5nRtydmNuECCK4IyK+vfS/SAA2e6x9JoTJEnOyFmk9jkoAnt5n+R7NjLJA5N+CIufyAw50MBsVFDlJDQl4PsGMO6LjK6lS5kw3OX+dkEZz4DiV12cP+bLKkXnmLySRAmbUGrSPTbbx5AarL9g/O5oQweVbDAckadW4WgasRAI7dlGbAhR3GjNowsZP1n8gCl6Bj436XX59ApiawV0RoXOaeoN8wOItRAgJ7rgRAqIs3WXQJ61z6CQiipW05pg7Sz7k3QqPrtL7jSoa2Nzg8U1eLlIOmT1B+zhWikf2/S63L/L1PUnKzoyDh1Hl42u63ygKshaX7tDci5u4O2ao0ltj4RpvnrPVp8wBQkp9ms4pzl1hz74oEzdDpKoBPrzi7Qrr33HYndE5Q/sogJVowF3crKLhDmNHHV06CjSCHn3r34+IfEQR4vOMtRD5A0cEsaSJIRJgAAAAAElFTkSuQmCC"},"滴滴 -实习生招聘")])]),A("p")])]),A("p")],-1),i=[g,E];function n(o,l){return t(),d("div",null,i)}const s=a(e,[["render",n],["__file","group.html.vue"]]),h=JSON.parse('{"path":"/intro/group.html","title":"我的网页收藏","lang":"zh-CN","frontmatter":{},"headers":[],"filePathRelative":"intro/group.md","git":{"createdTime":1715588813000,"updatedTime":1715588813000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":1}]},"readingTime":{"minutes":30.11,"words":9032}}');export{s as comp,h as data}; diff --git a/assets/index.html-3w8DbZRK.js b/assets/index.html-3w8DbZRK.js new file mode 100644 index 0000000..c0b2e7f --- /dev/null +++ b/assets/index.html-3w8DbZRK.js @@ -0,0 +1 @@ +import{_ as o,o as a,c,a as e,b as t}from"./app-B-BkP2m_.js";const n={},s=e("h1",{id:"介绍",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#介绍"},[e("span",null,"介绍")])],-1),i=e("p",null,[t("开始搭建 "),e("code",null,"React-Cli"),t(" 和 "),e("code",null,"Vue-cli"),t("。")],-1),r=[s,i];function l(d,m){return a(),c("div",null,r)}const u=o(n,[["render",l],["__file","index.html.vue"]]),h=JSON.parse('{"path":"/project/","title":"介绍","lang":"zh-CN","frontmatter":{},"headers":[],"filePathRelative":"project/README.md","git":{"createdTime":1715588813000,"updatedTime":1715780535000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":2}]},"readingTime":{"minutes":0.04,"words":11}}');export{u as comp,h as data}; diff --git a/assets/index.html-B2q_J4Ev.js b/assets/index.html-B2q_J4Ev.js new file mode 100644 index 0000000..6832963 --- /dev/null +++ b/assets/index.html-B2q_J4Ev.js @@ -0,0 +1 @@ +import{_ as o,r as l,o as a,c as r,a as e,b as t,d as s}from"./app-B-BkP2m_.js";const i={},c=e("h1",{id:"学习路线",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#学习路线"},[e("span",null,"学习路线")])],-1),d=e("p",null,[e("img",{src:"https://img2.imgtp.com/2024/05/15/O7QUOvJO.png",alt:"前端学习.png"})],-1),m={href:"https://roadmap.sh/frontend",target:"_blank",rel:"noopener noreferrer"},_=e("p",null,[e("strong",null,"学习方法")],-1),u=e("ul",null,[e("li",null,"多敲代码"),e("li",null,"打扎实基础"),e("li",null,"平时多看看技术博客"),e("li",null,"保持学习热情")],-1);function p(h,f){const n=l("ExternalLinkIcon");return a(),r("div",null,[c,d,e("p",null,[t("这里还有一些相关的学习路线,这个网站提供的挺好的! "),e("a",m,[t("前端学习路线"),s(n)])]),_,u])}const x=o(i,[["render",p],["__file","index.html.vue"]]),k=JSON.parse('{"path":"/intro/","title":"学习路线","lang":"zh-CN","frontmatter":{},"headers":[],"filePathRelative":"intro/README.md","git":{"createdTime":1715588813000,"updatedTime":1715941349000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":3}]},"readingTime":{"minutes":0.23,"words":70}}');export{x as comp,k as data}; diff --git a/assets/index.html-CwYlRy8M.js b/assets/index.html-CwYlRy8M.js new file mode 100644 index 0000000..165cd74 --- /dev/null +++ b/assets/index.html-CwYlRy8M.js @@ -0,0 +1 @@ +import{_ as a,o as t,c as o,a as e}from"./app-B-BkP2m_.js";const n={},c=e("h1",{id:"概要介绍",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#概要介绍"},[e("span",null,"概要介绍")])],-1),s=[c];function i(r,d){return t(),o("div",null,s)}const m=a(n,[["render",i],["__file","index.html.vue"]]),_=JSON.parse('{"path":"/advance/","title":"概要介绍","lang":"zh-CN","frontmatter":{},"headers":[],"filePathRelative":"advance/README.md","git":{"createdTime":1715941349000,"updatedTime":1715941349000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":1}]},"readingTime":{"minutes":0.01,"words":4}}');export{m as comp,_ as data}; diff --git a/assets/index.html-D8U3dmyr.js b/assets/index.html-D8U3dmyr.js new file mode 100644 index 0000000..73d39d0 --- /dev/null +++ b/assets/index.html-D8U3dmyr.js @@ -0,0 +1 @@ +import{_ as e,o as t,c as i}from"./app-B-BkP2m_.js";const a={};function o(s,r){return t(),i("div")}const c=e(a,[["render",o],["__file","index.html.vue"]]),l=JSON.parse('{"path":"/","title":"首页","lang":"zh-CN","frontmatter":{"home":true,"title":"首页","actions":[{"text":"内容介绍","link":"/intro/pre.html","type":"secondary"},{"text":"开始学习 →","link":"/base/","type":"primary"}],"features":[{"title":"💡 技术栈","details":"JS/HTML/CSS Vue React Webpack vite Axios Pinia Redux TS/JSX Express Next.js less/sass Node Java SpringBoot git ……"},{"title":"🛠️ 学习路线","details":"从基础到TodoList项目,基础框架项目,JS高级,再到前端所要了解的网络及安全知识、浏览器原理、技术相关底层原理、前端性能优化,最后企业实际项目"},{"title":"📦 项目/面试经验","details":"项目难点 / 面试经验 / 实习经历 / 学习思考 / 相关建议"}],"footer":"MIT Licensed | Copyright © 2024-present Rain"},"headers":[],"filePathRelative":"README.md","git":{"createdTime":1715588813000,"updatedTime":1715588813000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":1}]},"readingTime":{"minutes":0.51,"words":153}}');export{c as comp,l as data}; diff --git a/assets/index.html-DVAmJa9t.js b/assets/index.html-DVAmJa9t.js new file mode 100644 index 0000000..e077acf --- /dev/null +++ b/assets/index.html-DVAmJa9t.js @@ -0,0 +1 @@ +import{_ as e,o as l,c as t,e as i}from"./app-B-BkP2m_.js";const a={},o=i('

介绍

——小菜鸡一个说法

其实我感觉我在这上面没有太多的话语权,我自己开始课程学算法的时候就觉的难,根本听不进去,然后又有其他事情,就学不进一点算法。但是我觉的我做的好的一个点是我上课的时候还是去了课堂上听课,知道老师讲哪里来了,知道双指针、滑动窗口、并查集、动态规划、二分是啥。后面准备面试了,才发现算法在开发岗面试经常能碰到。后面开始准备还是对咱们这个课上听到的有点印象,虽然说重新学的过程很难,主要你能坚持下来,多刷题,相信会找到学算法的门路的!
面试的算法我挂了一题(美团一面,因为算法挂了),还有一题没完全写出来但讲好了思路(腾讯二面——字符串相乘),主要是刚开始,有些地方确实薄弱

面试中的算法baseline

  • 美团一面:最小深度二叉树
  • 腾讯二面:10000!、二叉树BFS
  • 腾讯云三面:字符串相乘
  • 腾讯金融一面:最大无重复子串
  • 小米二面:接雨水

笔试中的算法一般是出两道到三道:第一道非常的简单,但是还是有点难,能解决的样子。让我印象深的就是恒生电子考的全是金融相关的算法题,贪心、动规,你还得了解一下金融相关的知识,比如买卖股票;哦,还有一个印象深,阿里,三道算法一个不会,第一题我本来用JS内置方法是能解决的,但超时,这个题首先就是你要回溯出各个项,然后还有遍历判断,这遍历可有门道了,不会!

算法很重要

先来聊聊面试,这是大家从学校走向社会的重要一步。校招和社招的面试,一般来说有2-3轮技术面试和1轮HR面试。技术面试可能现场也可能电话,HR面试有些公司还不一定有,这种情况就是三轮技术面,当然可能有的公司面试跟上面说的不太一样,但正常来说是这样的。

对于技术面试来说,基本可以这样讲:技术面试=基础知识和业务逻辑面试+算法面试。所谓基础知识和业务逻辑面试,就是对你应聘岗位进行相关知识的考察,通俗地讲就是看你有没有干这份工作的专业能力。比如你要应聘前端岗位,那js、css、html和 jQuery的一些问题肯定会问。第一步如果你过了的话,那就来到了算法面试,通常会以代码的形式考察,很少会单讲算法。

从上面的:技术面试=基础知识和业务逻辑面试+算法面试 来看,对于业务逻辑知识层面的,那没的说,你想从事这个岗位的工作,那这一部分知识是必备的。但我们可以看出算法的普遍性,这也正是算法重要的原因之一:它是一种通用的考察点,不管你应聘哪个岗位都可以进行考察;

另外考察算法的另一个非常重要的原因是:它包含了太多的逻辑思维,可以考察你思考问题的逻辑和解决问题的能力;这一点也是面试官比较看重的,因为它可以反映出你的潜力,我曾经听阿里一位资深面试官这样讲过:当一个人逻辑思维和能力不错的情况下,你还会担心专业的业务知识方面他不行或者学不会吗?”管中窥豹,算法的重要性我想大家都应该明白了。

其实想说的算法重要的原因是:它是你扎实基本功的反映之一,这些东西很大程度上会决定你未来在IT这条路上到底能走多远。 现实点说,由于现在互联网行业薪酬较高的实际情况,很多人会报班或者半路出家去学IT,其实这变相拉低了广义上程序员的门槛,似乎大家都可以通过这条路来寻求高薪。那作为想或者已经从事这个行业的我们,如果你是科班的,那再好不过了,请珍惜这个机会;如果你不是,但也想干这行,在竞争越来越激烈的今天,必须要有点硬功夫,而上面说的算法就是其中之一,当然还包括类似于数据结构、汇编、组原、计网、数学等等,如果这些学好的话,它们是和别人竞争的一项无形的资本,也就是我们说的会让你有区分度。

计算机相关专业出来的,大学四年数据结构与算法都学不好,有什么能拿出来的呢,我是这样的想法。

开始系统学算法+日常刷题

leetcode.png

我的学习路径:

  • 首先就是了解基础的数据结构,用自己擅长的语言手写数据结构
  • 刷算法题不要从动态规划和简单题开始刷,避免很难或者漫无目的的刷题
  • 从二叉树、链表题开始、到二分双指针矩阵、再到动态规划贪心回溯等。慢慢来,可以跟着代码随想录网站来的!
  • 找到适合自己的算法学习方法,你的效率会变高的。比如我就是刷力扣Hot100,不会的就看题解或者视频。

成果:连续的两个月刷题,自己确实在这上面学到了很多,我自己更有想法好好去学去刷算法题。面试算法基本能过了现在!

',18),p=[o];function s(n,c){return l(),t("div",null,p)}const h=e(a,[["render",s],["__file","index.html.vue"]]),d=JSON.parse('{"path":"/algorithm/","title":"介绍","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"算法很重要","slug":"算法很重要","link":"#算法很重要","children":[]},{"level":2,"title":"开始系统学算法+日常刷题","slug":"开始系统学算法-日常刷题","link":"#开始系统学算法-日常刷题","children":[]}],"filePathRelative":"algorithm/README.md","git":{"createdTime":1715588813000,"updatedTime":1715941349000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":3}]},"readingTime":{"minutes":5.07,"words":1522}}');export{h as comp,d as data}; diff --git a/assets/index.html-D_jBZyL5.js b/assets/index.html-D_jBZyL5.js new file mode 100644 index 0000000..dd0201b --- /dev/null +++ b/assets/index.html-D_jBZyL5.js @@ -0,0 +1 @@ +import{_ as i,o as l,c as e,e as a}from"./app-B-BkP2m_.js";const o={},t=a('

[TOC]

面试经历及问题

我记录的问题可能有缺少……因为后面只想记录自己面试没有回答好的问题。

4-8——美团金融

  1. 前端模块化是什么?
  2. 项目编码规范
  3. Vue2和Vue3的特点区别
  4. Proxy的优缺点?
  5. HTTP和HTTPS的区别
  6. 闭包,this指向?
  7. call、apply、bind的区别?
  8. HTTP状态码和业务状态码?
  9. 手写instanceOf
  10. 算法最小树深度
  11. 还有很多……忘记了,没有录音

4-12——腾讯云一面

  1. 自我介绍
  2. 编码规范
  3. 代码风格上有没有用工具吗?
  4. 会做代码review吗?
  5. 谈了一下我的比赛经历
  6. 你学前端多久了啊?
  7. 你了解事件委托吗?
  8. 事件委托为什么能作用在其父节点上,利用了什么机制?
  9. 使用未声明的变量会干嘛?
  10. 第一个函数题(受教了),判断输出的。
  11. 第二个函数题,考作用域和变量提升的 FOO
  12. 什么是作用域?那作用域链呢?作用域的顶端是什么?
  13. 我们浏览器的全局对象是?
  14. 一个闭包输出题,setTimeout var 循环输出
  15. 了解过闭包吗?
  16. 想要这个题目正常打印01234该怎么解决?
  17. 为什么setTimeout输出的时间不准确?
  18. 有了解过JS的事件循环吗?
  19. 一个异步同步输出打印的题目
  20. new Promise中这个大的for循环会影响执行顺序吗?
  21. 用过new关键字去创建对象吗,说说这个执行流程
  22. 怎么改变this指向?
  23. call、apply、bind的区别?
  24. 箭头函数和普通函数的区别?
  25. 手写实现call函数
  26. 讲一下原型链
  27. 你有写过继承吗?
  28. 了解那些http的状态吗,仔细讲讲
  29. promise有什么特点?
  30. promise的几种状态有了解吗?
  31. 一个Promise的题目,封装请求的
  32. async await 和Promise有什么区别?
  33. 你用过生成器吗?
  34. 你知道什么是同源策略吗?
  35. 跨域有几种方式?
  36. XSS 和XSS防御
  37. CSRF 和 CSRF防御
  38. Vue的选项数据绑定原理
  39. 出了一个Vue相关,实现计算器的改错题。
  40. 聊一下我的项目,里面遇到的难点,怎么解决的,有什么收获
  41. 你平时怎么学前端的?
  42. 你搞过可视化吗?
  43. 平时学习中有写过博客吗?
  44. 面试官介绍业务和实习一般做什么内容。
  45. 继承了解吗?

整场下来一个半小时多一点,问题基本上是答出来了,面试管对我感觉也很好,在面试后二十分钟内就给我调整成复试状态了。

4-17——腾讯云二面

腾讯二面面试官给我的压力太大了,虽然录了音,我不敢再去听了QAQ,答得也还行,也有一些没有回答太好的,我记记。

  1. ETAG怎么生成的?
  2. 浏览器缓存的这些策略的应用场景
  3. ES6之前没有Promise怎么来进行的异步任务?
  4. git merge 和 git rebase 的区别?
  5. 谈谈你对Vue的设计目标和思想的理解?
  6. TLS这个过程你有了解吗?
  7. webpack中loader和plugin的使用场景和项目中的用法。
  8. 热跟新的实现机制,大概了解,webpack中的一些问题

4-19——领健

这家面试有意思,我是说题目。

  1. 一个页面同时渲染500个头像图片

假设一个界面,你屏幕窗口这么大的界面,上面显示了500个头像,img图像一次显示了500个,有没有优化方案。这个500个加载还是很慢的。

  1. 数组取第一个
  2. 箭头函数和普通函数(项目中那些地方必须只能用普通函数或者箭头函数)
  3. 你项目中深拷贝的一个方式
  4. 一个JSON相关的问题
  5. TS的枚举你知道吗,enum值转为ES5是什么样子的?
  6. 终极大题

还是你跟我聊天这个屏幕这么大,现在有这么一组数据,现在长度是500, [{x,y,r},{x,y,r},{x,y,r}……]x,y是表示位置。r是每个半径。要把这500个圆画在这个屏幕上

4-19——小米一面

  1. TS实现一个函数(检查TS)
  2. setTimeout的一个问题:用setTimeout模拟setInterval,进行定时打印!!!
  3. 4:3的一个问题,padding-bottom,center .实现一个长宽4:3的矩形,用padding-bottom:75%;width:100%;
  4. 一个跟定时任务相关的,纯前端实现

一个alter一天开始登录的时候只执行一次,后续就不会在去执行了。 我的解题思路:本地存储+时间判断对比(大概对了)

4-25——腾讯云三面

这个面试过程还可以,但是我有大大的疑惑。面试过程有两点没回答好,其他都还行。 基本二面三面是围绕项目来讲的!!!

  1. 在这个Vue模板上用到的那个事件绑定,是怎么做的?是和原生绑定事件一样吗?
  2. 你这里用到的Pinia进行数据的持久化,为什么会有这个情况,你有深入了解吗?
  3. SPA首屏渲染做的优化方案,讲你在项目中怎么解决这个问题。

4-26——小米二面

  1. 发布订阅、观察者模式的区别?
  2. 手写JS发布订阅
  3. treeshcking是怎么做到,是在框架层面还是ES6上面
  4. cookie相关的知识,获取cookie,JS操作cookie
  5. 使用 JavaScript 读取 Cookie
  6. 你能讲一下浏览器的存储吗,他们之间的一些区别?
  7. HTTP3之余HTTP2的改变,请讲一下。
  8. 了解Pinia底层原理吗,它是怎么进行状态管理的?

5-7——腾讯金融

5-10——海康威视

',27),n=[t];function s(r,c){return l(),e("div",null,n)}const p=i(o,[["render",s],["__file","index.html.vue"]]),d=JSON.parse('{"path":"/interview/","title":"面试经历及问题","lang":"zh-CN","frontmatter":{},"headers":[],"filePathRelative":"interview/README.md","git":{"createdTime":1715780535000,"updatedTime":1716560799000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":2}]},"readingTime":{"minutes":5.1,"words":1530}}');export{p as comp,d as data}; diff --git a/assets/index.html-DpEQMVKn.js b/assets/index.html-DpEQMVKn.js new file mode 100644 index 0000000..781bd96 --- /dev/null +++ b/assets/index.html-DpEQMVKn.js @@ -0,0 +1 @@ +import{_ as t,o as a,c as o,a as e}from"./app-B-BkP2m_.js";const n={},i=e("h1",{id:"介绍",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#介绍"},[e("span",null,"介绍")])],-1),s=e("p",null,"大学里课上也学了这么多开发上面的课程,怎么说也得总结总结嘛!其实面试也考,考的还好,主要是数据结构与算法会考得难一些",-1),c=e("p",null,"我想了想大学里在喜欢听的课程:",-1),l=e("blockquote",null,[e("p",null,"高等数学、数据结构与算法、Java、JavaEE、Web应用技术、设计模式、计算机网络、信息安全、Linux")],-1),r=e("p",null,"其中Java、JavaEE、Web应用技术、Linux、计算机网络都是提前学习过的,所以上课非常轻松。但是我好像好久没去搞Java、Linux这些,差不多忘了差不多了。Java的那些特性,数据结构方法的使用,Linux的开关防火墙、一些常用命令……不过我精通Vim!菜鸡的我^_^",-1),_=[i,s,c,l,r];function u(d,m){return a(),o("div",null,_)}const p=t(n,[["render",u],["__file","index.html.vue"]]),x=JSON.parse('{"path":"/computer/","title":"介绍","lang":"zh-CN","frontmatter":{},"headers":[],"filePathRelative":"computer/README.md","git":{"createdTime":1715780535000,"updatedTime":1715780535000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":1}]},"readingTime":{"minutes":0.65,"words":195}}');export{p as comp,x as data}; diff --git a/assets/index.html-SEv6fOr9.js b/assets/index.html-SEv6fOr9.js new file mode 100644 index 0000000..0fbf079 --- /dev/null +++ b/assets/index.html-SEv6fOr9.js @@ -0,0 +1 @@ +import{_ as i,o as e,c as l,e as t}from"./app-B-BkP2m_.js";const a={},s=t('

前言

前端基础技术栈


  • HTML5 / CSS3 / JS / TS
  • less / sass / postcss
  • axios
  • Vue / React
  • Vuex / Pinia / Redux
  • MUI / ElementPlus / Ant Design
  • Webpack / Vite
  • Git
  • Java / Node
  • canvas / SVG / D3 / three.js / Echarts
  • Springboot / Express / Koa
  • ……

深入了解


  • 浏览器原理、缓存机制
  • 前端SPA应用性能优化、首屏优化
  • 网络原理、安全对策
  • 框架、插件、第三方库底层原理
  • 小程序、混合、原生、桌面应用开发
  • 项目自动化部署CI/CD
  • ……

学习还得继续进行下去……

',8),o=[s];function n(c,r){return e(),l("div",null,o)}const d=i(a,[["render",n],["__file","index.html.vue"]]),m=JSON.parse('{"path":"/base/","title":"前言","lang":"zh-CN","frontmatter":{},"headers":[],"filePathRelative":"base/README.md","git":{"createdTime":1715588813000,"updatedTime":1715941349000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":3}]},"readingTime":{"minutes":0.46,"words":138}}');export{d as comp,m as data}; diff --git a/assets/learn.html-vWb14hW0.js b/assets/learn.html-vWb14hW0.js new file mode 100644 index 0000000..ff9763c --- /dev/null +++ b/assets/learn.html-vWb14hW0.js @@ -0,0 +1 @@ +import{_ as t,o as l,c as n,a as e}from"./app-B-BkP2m_.js";const o={},a=e("h1",{id:"我能学到什么",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#我能学到什么"},[e("span",null,"我能学到什么")])],-1),i=e("ul",null,[e("li",null,"青铜:是什么,有什么用,如何使用(这是最重要的)。"),e("li",null,"黄金:"),e("li",null,"钻石:"),e("li",null,"王者:")],-1),r=e("blockquote",null,[e("p",null,"我还是一个菜鸡~~~~~~~~~~~~~~~~~~")],-1),s=[a,i,r];function c(u,_){return l(),n("div",null,s)}const m=t(o,[["render",c],["__file","learn.html.vue"]]),h=JSON.parse('{"path":"/intro/learn.html","title":"我能学到什么","lang":"zh-CN","frontmatter":{},"headers":[],"filePathRelative":"intro/learn.md","git":{"createdTime":1715588813000,"updatedTime":1715588813000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":1}]},"readingTime":{"minutes":0.13,"words":38}}');export{m as comp,h as data}; diff --git a/assets/other.html-CPOBY2mK.js b/assets/other.html-CPOBY2mK.js new file mode 100644 index 0000000..1f788c1 --- /dev/null +++ b/assets/other.html-CPOBY2mK.js @@ -0,0 +1,3 @@ +import{_ as l,r as s,o as p,c as a,a as t,b as r,d as n,e as o}from"./app-B-BkP2m_.js";const i={},g=o(`

其他的一些小问题

proxy的优缺点?

Object.defineProperty的缺陷:

  1. 无法检测到对象属性的新增或删除

    由于js的动态性,可以为对象追加新的属性或者删除其中某个属性, 这点对经过Object.defineProperty方法建立的响应式对象来说,只能追踪对象已有数据是否被修改,无法追踪新增属性和删除属性,这就需要另外处理。

  2. 不能监听数组的变化(对数组基于下标的修改、对于 .length 修改的监测)

    vue在实现数组的响应式时,它使用了一些hack,把无法监听数组的情况通过重写数组的部分方法来实现响式,这也只限制在数组的push/pop/shift/unshift/splice/sort/reverse七个方法,其他数组方法及数组的使用则无法检测到, 解决方法主要是使用proxy属性,这个proxy属性是ES6中新增的一个属性,proxy属性也是一个构造函数,他也可以通过new的方式创建这个函数,表示修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种元编程proxy可以理解为在目标对象之前架设一层拦截,外界对该对象的访问,都必须经过这层拦截,因此提出了一种机制,可以对外界的网文进行过滤和改写,proxy这个词是代理,用来表示由他代理某些操作,可以译为代理器

proxy代理的特点:

  • proxy直接代理的是整个对象而非对象属性
  • proxy的代理针对的是整个对象而不是像object.defineProperty针对某个属性
  • 只需要做一层代理就可以监听同级结构下的所有属性变化,包括新增的属性和删除的属性
  • proxy代理身上定义的方法共有13种,其中我们最常用的就是set和get,但是他本身还有其他的13种方法

proxy的劣势:

兼容性问题,虽然proxy相对越object.defineProperty有很有优势,但是并不是说proxy,就是完全的没有劣势,主要表现在以下的两个方面:

  1. proxy有兼容性问题,无完全的polyfill: proxy为ES6新出的API,浏览器对其的支持情况可在w3c规范中查到,通过查找我们可以知道, 虽然大部分浏览器支持proxy特性,但是一些浏览器或者低版本不支持proxy, 因此proxy有兼容性问题,那能否像ES6其他特性有polyfill解决方案呢?, 这时我们通过查询babel文档,发现在使用babel对代码进行降级处理的时候,并没有合适的polyfill

  2. 第二个问题就是性能问题,proxy的性能其实比promise还差, 这就需要在性能和简单实用上进行权衡,例如vue3使用proxy后, 其对对象及数组的拦截很容易实现数据的响应式,尤其是数组

     虽然proxy有性能和兼容性处理,但是proxy作为新标准将受到浏览器厂商重点持续的性能优化,
    + 性能这块会逐步得到改善
    +
`,9),d={href:"https://juejin.cn/post/6844903601416978439",target:"_blank",rel:"noopener noreferrer"},c=t("h2",{id:"vue的双向绑定原理-腾讯",tabindex:"-1"},[t("a",{class:"header-anchor",href:"#vue的双向绑定原理-腾讯"},[t("span",null,"Vue的双向绑定原理(腾讯)")])],-1),h={href:"https://juejin.cn/post/7080562890628923423#heading-29",target:"_blank",rel:"noopener noreferrer"},u={href:"https://zhuanlan.zhihu.com/p/138710460",target:"_blank",rel:"noopener noreferrer"},x=o('

HTTP请求方法:幂等和非幂等?

幂等性和安全性是http请求方法的特性, 比如 get请求方法是具有安全的

安全性(此次请求不会修改后台):

** 仅指该方法的多次调用不会产生副作用,不涉及传统意义上的“安全”,这里的副作用是指资源状态。** 即,安全的方法不会修改资源状态,尽管多次调用的返回值可能不一样(被其他非安全方法修改过)。


幂等性(多次请求一个url,返回值不变):

** 是指该方法多次调用返回的效果(形式)一致,客户端可以重复调用并且期望同样的结果。一次调用和多次调用产生的效果是一致的,都是对一个变量进行赋值。**

————————————————————————————————方法名 安全性 幂等性 请求方法的作用get √ √ 请求指定的页面信息,并返回实体主体head √ √ 只请求页面的首部options √ √ 允许客户端查看服务器的性能delete × √ 请求服务器删除指定的数据put × √ 从客户端向服务器传送的数据取代指定的文档的内容post × × 请求服务器接受所指定的文档作为对所标识的URI的新的从属实体————————————————————————————————

方法名安全性幂等性请求方法的作用
get请求指定的页面信息,并返回实体主体
head只请求页面的首部
options允许客户端查看服务器的性能
delete×请求服务器删除指定的数据
put×从客户端向服务器传送的数据取代指定的文档的内容
post××请求服务器接受所指定的文档作为对所标识的URI的新的从属实体

method.pngPOST和GET谁更安全?

get更安全

get比post安全? -->get对于服务器是安全的–> get是幂等的,post是非幂等的

post更安全

① GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。

② 浏览器有跨域访问的限制,如果是get的话,jsonp很容易突破跨域的限制。但是post跨域比较不容易。

为什么put和delete是幂等,而patch则是非幂等的? 重点来了,put 和 patch 都是用于更新数据资源的。 区别 在于

put 做更新操作时候是提交一整个更新后的实体(即全部),而不是需要修改的实体中的部分属性。当 URI 指向一个存在的资源,服务器要做的事就是查找并替换。

patch 做更新操作的时候是请求中的实体是一组将要应用到实体的更改(即部分),而不是像 PUT 请求那样是要替换旧资源的实体。可以理解为:PATCH 请求中的实体保存的是修改资源的指令,该指令指导服务器来对资源做出修改。

怎么理解呢?要明白并理解 RESTful 核心就是 面向资源编程,如下:

** PUT /flowers/1 #修改 序号为1的花(flowers) 的全部信息**

put【幂等】:用于更新资源,没有的话则执行创建操作。每次执行请求时都会先判断一下序号为1的花信息是否存在,不存在则创建,否则视为更新。很显然,请求携带的数据每次都是一样的,所以不论请求多少次,最终的结果都是后台存在这么一个资源(创建或更新)。

** PATCH /flowers/1/variety/lily/num/331 #假设url采用pathinfo模式,修改 序号为1的花(flowers) 的品种信息为百合,数量修改位431朵**

patch【非幂等】:用于更新资源,即数据实体的一部分属性,该数据必然存在,否则失去更新意义。每次执行请求时都会先判断一下序号为1的花信息是否存在,存在则更新数据信息,这里有两个属性要改,做的处理可能是这样的:品种(variety)直接改为百合(lily),而数量(num)假设原本存在100朵,我们要修改到 431 朵,所以增加 331 朵。很显然,多次请求时,会重复增加 331 ,属性数量就无法保持 431 。而 PUT 请求不论执行多少次,属性数量永远都是 431 , PATCH 则会改变,处于不可控的地位,所以说 PUT 方法是幂等的,而 PATCH 方法不是幂等的。

** DELETE /flowers/1 #删除 序号为1的花(flowers) 的全部信息**

delete【幂等】: 用于删除资源,会将资源从后台删除。每次执行请求时都会先判断一下序号为1的花信息是否存在,存在则删除,否则不做任何操作。很显然,无论执行多少次资源的状态总是被删除的,不会有其它副作用的影响。

内存泄漏问题?

',26),y={href:"https://zhuanlan.zhihu.com/p/411103328",target:"_blank",rel:"noopener noreferrer"},b={href:"https://juejin.cn/post/7065705130963763231",target:"_blank",rel:"noopener noreferrer"},f={href:"https://juejin.cn/post/7232127712642547770",target:"_blank",rel:"noopener noreferrer"},_=o('

前端开发中,使用base64图片的弊端是什么?

  1. 造成网页阻塞 弊端主要不在于 base64 编码后比原图要大,而是因为如果把大图片编码到 html / css 中,会造成后者体积明显增加,明显影响网页的打开速度。如果用外链图片的话,图片可以在页面渲染完成后继续加载,不会造成阻塞。如果 base64 是被编码到 css/js 中,是可以缓存的,因为 css/js 文件可以缓存。 假设base64编码后的字符串长度为256kb,用户的网速为每个连接32kb/s,而除去这个字符串外html大小仅为32kb,其中图片前后各16kb 那么不考虑其他资源加载的情况下,用户会先在半秒后看到这个图片上面的内容,然后花费8秒加载图片,再在半秒后看到完整的网页
  2. 有兼容性问题 使用 base64 的另外一个弊端是 IE 的兼容性问题。IE 8 以下不支持 data url,IE 8 开始支持 data url,却有大小限制,32k(未测试)。
  3. 用法上面的问题 还有一个问题是,如果构建工具比较落后(或者没有构建工具),手动插入 base64 是很蛋疼的,编辑器会卡到哭。

什么是Gzip?

gzip是一种数据的压缩格式,或者说是一种文件格式。

Gzip原本用户UNIX系统的文件压缩,后来逐渐成为Internet最主流的数据压缩格式。当用户访问我们的web站点时,服务器就将我们的网页文件进行压缩,将压缩后的文件传输到客户端,对于纯文本文件我们可以至少压缩到原大小的40%,这样大大提高了传输效率,页面便可更快的加载出来。

gzip是一种数据的压缩格式,也可以说是文件格式。linux系统该文件后缀为.gz 。使用gzip需要web容器,浏览器的支持。

  • 配置 js、text、json、css 这种纯文本进行压缩,效率极高
  • 压缩需要消化CPU,对于大文件(音乐/视频/图片)的压缩,会增加服务器压力。
',7);function k(m,v){const e=s("ExternalLinkIcon");return p(),a("div",null,[g,t("p",null,[t("a",d,[r("面试官: 实现双向绑定Proxy比defineproperty优劣如何? - 掘金"),n(e)])]),c,t("p",null,[t("a",h,[r("vue的双向绑定原理与实现 - 掘金"),n(e)])]),t("p",null,[t("a",u,[r("安全验证 - 知乎"),n(e)])]),x,t("p",null,[t("a",y,[r("一文帮你解决前端开发中的内存泄露问题"),n(e)])]),t("p",null,[t("a",b,[r("前端常见内存泄漏及解决方案 - 掘金"),n(e)])]),t("p",null,[t("a",f,[r("如何查找和解决前端内存泄漏问题? - 排查和分析技巧详解 - 掘金"),n(e)])]),_])}const P=l(i,[["render",k],["__file","other.html.vue"]]),j=JSON.parse('{"path":"/interview/other.html","title":"其他的一些小问题","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"proxy的优缺点?","slug":"proxy的优缺点","link":"#proxy的优缺点","children":[]},{"level":2,"title":"Vue的双向绑定原理(腾讯)","slug":"vue的双向绑定原理-腾讯","link":"#vue的双向绑定原理-腾讯","children":[]},{"level":2,"title":"HTTP请求方法:幂等和非幂等?","slug":"http请求方法-幂等和非幂等","link":"#http请求方法-幂等和非幂等","children":[]},{"level":2,"title":"内存泄漏问题?","slug":"内存泄漏问题","link":"#内存泄漏问题","children":[]},{"level":2,"title":"前端开发中,使用base64图片的弊端是什么?","slug":"前端开发中-使用base64图片的弊端是什么","link":"#前端开发中-使用base64图片的弊端是什么","children":[]},{"level":2,"title":"什么是Gzip?","slug":"什么是gzip","link":"#什么是gzip","children":[]}],"filePathRelative":"interview/other.md","git":{"createdTime":1715780535000,"updatedTime":1715941349000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":2}]},"readingTime":{"minutes":9.28,"words":2783}}');export{P as comp,j as data}; diff --git a/assets/pre.html-Oos9G24k.js b/assets/pre.html-Oos9G24k.js new file mode 100644 index 0000000..bf1a0da --- /dev/null +++ b/assets/pre.html-Oos9G24k.js @@ -0,0 +1 @@ +import{_ as e,o as t,c as o,e as i}from"./app-B-BkP2m_.js";const a={},c=i('

前序

  • 入门前端真的特别容易,但是想要深入发展,你必须得沉下心来虚心学习。前端的内容日新月异,有的时候你会感觉自己跟不上技术发展的步伐,但是你又想着啥都学才能赶上。其实不是这样,你只要沉下心来好好专研一个方面的了解底层原理打扎实基础。你的学习过程会变得非常简单!

  • 学习是自己的事情,有时候鞭策自己好好学习,一段时间后你会发现超越了许多同学……

  • 选好自己的方向很重要,越早越好。当我准备自己的实习才发现,我的信息有多么的闭塞,你刚找实习了,别人就早已经有了三段实习经历。我想我要是大二就能有段实习那该多好,我能有更多的认识。

  • 这里记录了我学习的一些记录,一般是我不是很熟悉、或者经常碰到没记着、我觉得重要的…… 关于基础的知识我也提供了一些链接,也会在该站点展示。

  • 后续将会增加一些插件,提供更好的体验(也包括提交评论)……

',2),r=[c];function l(n,s){return t(),o("div",null,r)}const d=e(a,[["render",l],["__file","pre.html.vue"]]),m=JSON.parse('{"path":"/intro/pre.html","title":"前序","lang":"zh-CN","frontmatter":{},"headers":[],"filePathRelative":"intro/pre.md","git":{"createdTime":1715588813000,"updatedTime":1715588813000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":1}]},"readingTime":{"minutes":1.07,"words":321}}');export{d as comp,m as data}; diff --git a/assets/react-cli.html-UqdOc3bu.js b/assets/react-cli.html-UqdOc3bu.js new file mode 100644 index 0000000..c71c102 --- /dev/null +++ b/assets/react-cli.html-UqdOc3bu.js @@ -0,0 +1,842 @@ +import{_ as n,o as s,c as a,e as p}from"./app-B-BkP2m_.js";const t={},e=p(`

React 脚手架

开发模式配置

// webpack.dev.js
+const path = require("path");
+const ESLintWebpackPlugin = require("eslint-webpack-plugin");
+const HtmlWebpackPlugin = require("html-webpack-plugin");
+const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
+const CopyPlugin = require("copy-webpack-plugin");
+
+const getStyleLoaders = (preProcessor) => {
+  return [
+    "style-loader",
+    "css-loader",
+    {
+      loader: "postcss-loader",
+      options: {
+        postcssOptions: {
+          plugins: [
+            "postcss-preset-env", // 能解决大多数样式兼容性问题
+          ],
+        },
+      },
+    },
+    preProcessor,
+  ].filter(Boolean);
+};
+
+module.exports = {
+  entry: "./src/main.js",
+  output: {
+    path: undefined,
+    filename: "static/js/[name].js",
+    chunkFilename: "static/js/[name].chunk.js",
+    assetModuleFilename: "static/js/[hash:10][ext][query]",
+  },
+  module: {
+    rules: [
+      {
+        oneOf: [
+          {
+            // 用来匹配 .css 结尾的文件
+            test: /\\.css$/,
+            // use 数组里面 Loader 执行顺序是从右到左
+            use: getStyleLoaders(),
+          },
+          {
+            test: /\\.less$/,
+            use: getStyleLoaders("less-loader"),
+          },
+          {
+            test: /\\.s[ac]ss$/,
+            use: getStyleLoaders("sass-loader"),
+          },
+          {
+            test: /\\.styl$/,
+            use: getStyleLoaders("stylus-loader"),
+          },
+          {
+            test: /\\.(png|jpe?g|gif|svg)$/,
+            type: "asset",
+            parser: {
+              dataUrlCondition: {
+                maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
+              },
+            },
+          },
+          {
+            test: /\\.(ttf|woff2?)$/,
+            type: "asset/resource",
+          },
+          {
+            test: /\\.(jsx|js)$/,
+            include: path.resolve(__dirname, "../src"),
+            loader: "babel-loader",
+            options: {
+              cacheDirectory: true,
+              cacheCompression: false,
+              plugins: [
+                // "@babel/plugin-transform-runtime", // presets中包含了
+                "react-refresh/babel", // 开启js的HMR功能
+              ],
+            },
+          },
+        ],
+      },
+    ],
+  },
+  plugins: [
+    new ESLintWebpackPlugin({
+      context: path.resolve(__dirname, "../src"),
+      exclude: "node_modules",
+      cache: true,
+      cacheLocation: path.resolve(
+        __dirname,
+        "../node_modules/.cache/.eslintcache"
+      ),
+    }),
+    new HtmlWebpackPlugin({
+      template: path.resolve(__dirname, "../public/index.html"),
+    }),
+    new ReactRefreshWebpackPlugin(), // 解决js的HMR功能运行时全局变量的问题
+    // 将public下面的资源复制到dist目录去(除了index.html)
+    new CopyPlugin({
+      patterns: [
+        {
+          from: path.resolve(__dirname, "../public"),
+          to: path.resolve(__dirname, "../dist"),
+          toType: "dir",
+          noErrorOnMissing: true, // 不生成错误
+          globOptions: {
+            // 忽略文件
+            ignore: ["**/index.html"],
+          },
+          info: {
+            // 跳过terser压缩js
+            minimized: true,
+          },
+        },
+      ],
+    }),
+  ],
+  optimization: {
+    splitChunks: {
+      chunks: "all",
+    },
+    runtimeChunk: {
+      name: (entrypoint) => \`runtime~\${entrypoint.name}\`,
+    },
+  },
+  resolve: {
+    extensions: [".jsx", ".js", ".json"], // 自动补全文件扩展名,让jsx可以使用
+  },
+  devServer: {
+    open: true,
+    host: "localhost",
+    port: 3000,
+    hot: true,
+    compress: true,
+    historyApiFallback: true, // 解决react-router刷新404问题
+  },
+  mode: "development",
+  devtool: "cheap-module-source-map",
+};
+

生产模式配置

// webpack.prod.js
+const path = require("path");
+const ESLintWebpackPlugin = require("eslint-webpack-plugin");
+const HtmlWebpackPlugin = require("html-webpack-plugin");
+const MiniCssExtractPlugin = require("mini-css-extract-plugin");
+const TerserWebpackPlugin = require("terser-webpack-plugin");
+const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
+const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
+const CopyPlugin = require("copy-webpack-plugin");
+
+const getStyleLoaders = (preProcessor) => {
+  return [
+    MiniCssExtractPlugin.loader,
+    "css-loader",
+    {
+      loader: "postcss-loader",
+      options: {
+        postcssOptions: {
+          plugins: [
+            "postcss-preset-env", // 能解决大多数样式兼容性问题
+          ],
+        },
+      },
+    },
+    preProcessor,
+  ].filter(Boolean);
+};
+
+module.exports = {
+  entry: "./src/main.js",
+  output: {
+    path: path.resolve(__dirname, "../dist"),
+    filename: "static/js/[name].[contenthash:10].js",
+    chunkFilename: "static/js/[name].[contenthash:10].chunk.js",
+    assetModuleFilename: "static/js/[hash:10][ext][query]",
+    clean: true,
+  },
+  module: {
+    rules: [
+      {
+        oneOf: [
+          {
+            // 用来匹配 .css 结尾的文件
+            test: /\\.css$/,
+            // use 数组里面 Loader 执行顺序是从右到左
+            use: getStyleLoaders(),
+          },
+          {
+            test: /\\.less$/,
+            use: getStyleLoaders("less-loader"),
+          },
+          {
+            test: /\\.s[ac]ss$/,
+            use: getStyleLoaders("sass-loader"),
+          },
+          {
+            test: /\\.styl$/,
+            use: getStyleLoaders("stylus-loader"),
+          },
+          {
+            test: /\\.(png|jpe?g|gif|svg)$/,
+            type: "asset",
+            parser: {
+              dataUrlCondition: {
+                maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
+              },
+            },
+          },
+          {
+            test: /\\.(ttf|woff2?)$/,
+            type: "asset/resource",
+          },
+          {
+            test: /\\.(jsx|js)$/,
+            include: path.resolve(__dirname, "../src"),
+            loader: "babel-loader",
+            options: {
+              cacheDirectory: true,
+              cacheCompression: false,
+              plugins: [
+                // "@babel/plugin-transform-runtime" // presets中包含了
+              ],
+            },
+          },
+        ],
+      },
+    ],
+  },
+  plugins: [
+    new ESLintWebpackPlugin({
+      context: path.resolve(__dirname, "../src"),
+      exclude: "node_modules",
+      cache: true,
+      cacheLocation: path.resolve(
+        __dirname,
+        "../node_modules/.cache/.eslintcache"
+      ),
+    }),
+    new HtmlWebpackPlugin({
+      template: path.resolve(__dirname, "../public/index.html"),
+    }),
+    new MiniCssExtractPlugin({
+      filename: "static/css/[name].[contenthash:10].css",
+      chunkFilename: "static/css/[name].[contenthash:10].chunk.css",
+    }),
+    // 将public下面的资源复制到dist目录去(除了index.html)
+    new CopyPlugin({
+      patterns: [
+        {
+          from: path.resolve(__dirname, "../public"),
+          to: path.resolve(__dirname, "../dist"),
+          toType: "dir",
+          noErrorOnMissing: true, // 不生成错误
+          globOptions: {
+            // 忽略文件
+            ignore: ["**/index.html"],
+          },
+          info: {
+            // 跳过terser压缩js
+            minimized: true,
+          },
+        },
+      ],
+    }),
+  ],
+  optimization: {
+    // 压缩的操作
+    minimizer: [
+      new CssMinimizerPlugin(),
+      new TerserWebpackPlugin(),
+      new ImageMinimizerPlugin({
+        minimizer: {
+          implementation: ImageMinimizerPlugin.imageminGenerate,
+          options: {
+            plugins: [
+              ["gifsicle", { interlaced: true }],
+              ["jpegtran", { progressive: true }],
+              ["optipng", { optimizationLevel: 5 }],
+              [
+                "svgo",
+                {
+                  plugins: [
+                    "preset-default",
+                    "prefixIds",
+                    {
+                      name: "sortAttrs",
+                      params: {
+                        xmlnsOrder: "alphabetical",
+                      },
+                    },
+                  ],
+                },
+              ],
+            ],
+          },
+        },
+      }),
+    ],
+    splitChunks: {
+      chunks: "all",
+    },
+    runtimeChunk: {
+      name: (entrypoint) => \`runtime~\${entrypoint.name}\`,
+    },
+  },
+  resolve: {
+    extensions: [".jsx", ".js", ".json"],
+  },
+  mode: "production",
+  devtool: "source-map",
+};
+

其他配置

  • package.json
{
+  "name": "react-cli",
+  "version": "1.0.0",
+  "description": "",
+  "main": "index.js",
+  "scripts": {
+    "start": "npm run dev",
+    "dev": "cross-env NODE_ENV=development webpack serve --config ./config/webpack.dev.js",
+    "build": "cross-env NODE_ENV=production webpack --config ./config/webpack.prod.js"
+  },
+  "keywords": [],
+  "author": "",
+  "license": "ISC",
+  "devDependencies": {
+    "@babel/core": "^7.17.10",
+    "@pmmmwh/react-refresh-webpack-plugin": "^0.5.5",
+    "babel-loader": "^8.2.5",
+    "babel-preset-react-app": "^10.0.1",
+    "copy-webpack-plugin": "^10.2.4",
+    "cross-env": "^7.0.3",
+    "css-loader": "^6.7.1",
+    "css-minimizer-webpack-plugin": "^3.4.1",
+    "eslint-config-react-app": "^7.0.1",
+    "eslint-webpack-plugin": "^3.1.1",
+    "html-webpack-plugin": "^5.5.0",
+    "image-minimizer-webpack-plugin": "^3.2.3",
+    "imagemin": "^8.0.1",
+    "imagemin-gifsicle": "^7.0.0",
+    "imagemin-jpegtran": "^7.0.0",
+    "imagemin-optipng": "^8.0.0",
+    "imagemin-svgo": "^10.0.1",
+    "less-loader": "^10.2.0",
+    "mini-css-extract-plugin": "^2.6.0",
+    "postcss-loader": "^6.2.1",
+    "postcss-preset-env": "^7.5.0",
+    "react-refresh": "^0.13.0",
+    "sass-loader": "^12.6.0",
+    "style-loader": "^3.3.1",
+    "stylus-loader": "^6.2.0",
+    "webpack": "^5.72.0",
+    "webpack-cli": "^4.9.2",
+    "webpack-dev-server": "^4.9.0"
+  },
+  "dependencies": {
+    "antd": "^4.20.2",
+    "react": "^18.1.0",
+    "react-dom": "^18.1.0",
+    "react-router-dom": "^6.3.0"
+  },
+  "browserslist": ["last 2 version", "> 1%", "not dead"]
+}
+
  • .eslintrc.js
module.exports = {
+  extends: ["react-app"], // 继承 react 官方规则
+  parserOptions: {
+    babelOptions: {
+      presets: [
+        // 解决页面报错问题
+        ["babel-preset-react-app", false],
+        "babel-preset-react-app/prod",
+      ],
+    },
+  },
+};
+
  • babel.config.js
module.exports = {
+  // 使用react官方规则
+  presets: ["react-app"],
+};
+

合并开发和生产配置

  • webpack.config.js
const path = require("path");
+const ESLintWebpackPlugin = require("eslint-webpack-plugin");
+const HtmlWebpackPlugin = require("html-webpack-plugin");
+const MiniCssExtractPlugin = require("mini-css-extract-plugin");
+const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
+const TerserWebpackPlugin = require("terser-webpack-plugin");
+const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
+const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
+
+// 需要通过 cross-env 定义环境变量
+const isProduction = process.env.NODE_ENV === "production";
+
+const getStyleLoaders = (preProcessor) => {
+  return [
+    isProduction ? MiniCssExtractPlugin.loader : "style-loader",
+    "css-loader",
+    {
+      loader: "postcss-loader",
+      options: {
+        postcssOptions: {
+          plugins: [
+            "postcss-preset-env", // 能解决大多数样式兼容性问题
+          ],
+        },
+      },
+    },
+    preProcessor,
+  ].filter(Boolean);
+};
+
+module.exports = {
+  entry: "./src/main.js",
+  output: {
+    path: isProduction ? path.resolve(__dirname, "../dist") : undefined,
+    filename: isProduction
+      ? "static/js/[name].[contenthash:10].js"
+      : "static/js/[name].js",
+    chunkFilename: isProduction
+      ? "static/js/[name].[contenthash:10].chunk.js"
+      : "static/js/[name].chunk.js",
+    assetModuleFilename: "static/js/[hash:10][ext][query]",
+    clean: true,
+  },
+  module: {
+    rules: [
+      {
+        oneOf: [
+          {
+            // 用来匹配 .css 结尾的文件
+            test: /\\.css$/,
+            // use 数组里面 Loader 执行顺序是从右到左
+            use: getStyleLoaders(),
+          },
+          {
+            test: /\\.less$/,
+            use: getStyleLoaders("less-loader"),
+          },
+          {
+            test: /\\.s[ac]ss$/,
+            use: getStyleLoaders("sass-loader"),
+          },
+          {
+            test: /\\.styl$/,
+            use: getStyleLoaders("stylus-loader"),
+          },
+          {
+            test: /\\.(png|jpe?g|gif|svg)$/,
+            type: "asset",
+            parser: {
+              dataUrlCondition: {
+                maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
+              },
+            },
+          },
+          {
+            test: /\\.(ttf|woff2?)$/,
+            type: "asset/resource",
+          },
+          {
+            test: /\\.(jsx|js)$/,
+            include: path.resolve(__dirname, "../src"),
+            loader: "babel-loader",
+            options: {
+              cacheDirectory: true, // 开启babel编译缓存
+              cacheCompression: false, // 缓存文件不要压缩
+              plugins: [
+                // "@babel/plugin-transform-runtime",  // presets中包含了
+                !isProduction && "react-refresh/babel",
+              ].filter(Boolean),
+            },
+          },
+        ],
+      },
+    ],
+  },
+  plugins: [
+    new ESLintWebpackPlugin({
+      extensions: [".js", ".jsx"],
+      context: path.resolve(__dirname, "../src"),
+      exclude: "node_modules",
+      cache: true,
+      cacheLocation: path.resolve(
+        __dirname,
+        "../node_modules/.cache/.eslintcache"
+      ),
+    }),
+    new HtmlWebpackPlugin({
+      template: path.resolve(__dirname, "../public/index.html"),
+    }),
+    isProduction &&
+      new MiniCssExtractPlugin({
+        filename: "static/css/[name].[contenthash:10].css",
+        chunkFilename: "static/css/[name].[contenthash:10].chunk.css",
+      }),
+    !isProduction && new ReactRefreshWebpackPlugin(),
+  ].filter(Boolean),
+  optimization: {
+    minimize: isProduction,
+    // 压缩的操作
+    minimizer: [
+      // 压缩css
+      new CssMinimizerPlugin(),
+      // 压缩js
+      new TerserWebpackPlugin(),
+      // 压缩图片
+      new ImageMinimizerPlugin({
+        minimizer: {
+          implementation: ImageMinimizerPlugin.imageminGenerate,
+          options: {
+            plugins: [
+              ["gifsicle", { interlaced: true }],
+              ["jpegtran", { progressive: true }],
+              ["optipng", { optimizationLevel: 5 }],
+              [
+                "svgo",
+                {
+                  plugins: [
+                    "preset-default",
+                    "prefixIds",
+                    {
+                      name: "sortAttrs",
+                      params: {
+                        xmlnsOrder: "alphabetical",
+                      },
+                    },
+                  ],
+                },
+              ],
+            ],
+          },
+        },
+      }),
+    ],
+    // 代码分割配置
+    splitChunks: {
+      chunks: "all",
+      // 其他都用默认值
+    },
+    runtimeChunk: {
+      name: (entrypoint) => \`runtime~\${entrypoint.name}\`,
+    },
+  },
+  resolve: {
+    extensions: [".jsx", ".js", ".json"],
+  },
+  devServer: {
+    open: true,
+    host: "localhost",
+    port: 3000,
+    hot: true,
+    compress: true,
+    historyApiFallback: true,
+  },
+  mode: isProduction ? "production" : "development",
+  devtool: isProduction ? "source-map" : "cheap-module-source-map",
+};
+
  • 修改运行指令 package.json
{
+  "name": "react-cli",
+  "version": "1.0.0",
+  "description": "",
+  "main": "index.js",
+  "scripts": {
+    "start": "npm run dev",
+    "dev": "cross-env NODE_ENV=development webpack serve --config ./config/webpack.config.js",
+    "build": "cross-env NODE_ENV=production webpack --config ./config/webpack.config.js"
+  },
+  "keywords": [],
+  "author": "",
+  "license": "ISC",
+  "devDependencies": {
+    "@babel/core": "^7.17.10",
+    "@pmmmwh/react-refresh-webpack-plugin": "^0.5.5",
+    "babel-loader": "^8.2.5",
+    "babel-preset-react-app": "^10.0.1",
+    "cross-env": "^7.0.3",
+    "css-loader": "^6.7.1",
+    "css-minimizer-webpack-plugin": "^3.4.1",
+    "eslint-config-react-app": "^7.0.1",
+    "eslint-webpack-plugin": "^3.1.1",
+    "html-webpack-plugin": "^5.5.0",
+    "image-minimizer-webpack-plugin": "^3.2.3",
+    "imagemin": "^8.0.1",
+    "imagemin-gifsicle": "^7.0.0",
+    "imagemin-jpegtran": "^7.0.0",
+    "imagemin-optipng": "^8.0.0",
+    "imagemin-svgo": "^10.0.1",
+    "less-loader": "^10.2.0",
+    "mini-css-extract-plugin": "^2.6.0",
+    "react-refresh": "^0.13.0",
+    "sass-loader": "^12.6.0",
+    "style-loader": "^3.3.1",
+    "stylus-loader": "^6.2.0",
+    "webpack": "^5.72.0",
+    "webpack-cli": "^4.9.2",
+    "webpack-dev-server": "^4.9.0"
+  },
+  "dependencies": {
+    "react": "^18.1.0",
+    "react-dom": "^18.1.0",
+    "react-router-dom": "^6.3.0"
+  },
+  "browserslist": ["last 2 version", "> 1%", "not dead"]
+}
+







 
 






































优化配置

const path = require("path");
+const ESLintWebpackPlugin = require("eslint-webpack-plugin");
+const HtmlWebpackPlugin = require("html-webpack-plugin");
+const MiniCssExtractPlugin = require("mini-css-extract-plugin");
+const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
+const TerserWebpackPlugin = require("terser-webpack-plugin");
+const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
+const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
+const CopyPlugin = require("copy-webpack-plugin");
+
+const isProduction = process.env.NODE_ENV === "production";
+
+const getStyleLoaders = (preProcessor) => {
+  return [
+    isProduction ? MiniCssExtractPlugin.loader : "style-loader",
+    "css-loader",
+    {
+      loader: "postcss-loader",
+      options: {
+        postcssOptions: {
+          plugins: [
+            "postcss-preset-env",
+          ],
+        },
+      },
+    },
+    preProcessor && {
+      loader: preProcessor,
+      options:
+        preProcessor === "less-loader"
+          ? {
+              // antd的自定义主题
+              lessOptions: {
+                modifyVars: {
+                  // 其他主题色:https://ant.design/docs/react/customize-theme-cn
+                  "@primary-color": "#1DA57A", // 全局主色
+                },
+                javascriptEnabled: true,
+              },
+            }
+          : {},
+    },
+  ].filter(Boolean);
+};
+
+module.exports = {
+  entry: "./src/main.js",
+  output: {
+    path: isProduction ? path.resolve(__dirname, "../dist") : undefined,
+    filename: isProduction
+      ? "static/js/[name].[contenthash:10].js"
+      : "static/js/[name].js",
+    chunkFilename: isProduction
+      ? "static/js/[name].[contenthash:10].chunk.js"
+      : "static/js/[name].chunk.js",
+    assetModuleFilename: "static/js/[hash:10][ext][query]",
+    clean: true,
+  },
+  module: {
+    rules: [
+      {
+        oneOf: [
+          {
+            test: /\\.css$/,
+            use: getStyleLoaders(),
+          },
+          {
+            test: /\\.less$/,
+            use: getStyleLoaders("less-loader"),
+          },
+          {
+            test: /\\.s[ac]ss$/,
+            use: getStyleLoaders("sass-loader"),
+          },
+          {
+            test: /\\.styl$/,
+            use: getStyleLoaders("stylus-loader"),
+          },
+          {
+            test: /\\.(png|jpe?g|gif|svg)$/,
+            type: "asset",
+            parser: {
+              dataUrlCondition: {
+                maxSize: 10 * 1024,
+              },
+            },
+          },
+          {
+            test: /\\.(ttf|woff2?)$/,
+            type: "asset/resource",
+          },
+          {
+            test: /\\.(jsx|js)$/,
+            include: path.resolve(__dirname, "../src"),
+            loader: "babel-loader",
+            options: {
+              cacheDirectory: true,
+              cacheCompression: false,
+              plugins: [
+                // "@babel/plugin-transform-runtime",  // presets中包含了
+                !isProduction && "react-refresh/babel",
+              ].filter(Boolean),
+            },
+          },
+        ],
+      },
+    ],
+  },
+  plugins: [
+    new ESLintWebpackPlugin({
+      extensions: [".js", ".jsx"],
+      context: path.resolve(__dirname, "../src"),
+      exclude: "node_modules",
+      cache: true,
+      cacheLocation: path.resolve(
+        __dirname,
+        "../node_modules/.cache/.eslintcache"
+      ),
+    }),
+    new HtmlWebpackPlugin({
+      template: path.resolve(__dirname, "../public/index.html"),
+    }),
+    isProduction &&
+      new MiniCssExtractPlugin({
+        filename: "static/css/[name].[contenthash:10].css",
+        chunkFilename: "static/css/[name].[contenthash:10].chunk.css",
+      }),
+    !isProduction && new ReactRefreshWebpackPlugin(),
+    // 将public下面的资源复制到dist目录去(除了index.html)
+    new CopyPlugin({
+      patterns: [
+        {
+          from: path.resolve(__dirname, "../public"),
+          to: path.resolve(__dirname, "../dist"),
+          toType: "dir",
+          noErrorOnMissing: true, // 不生成错误
+          globOptions: {
+            // 忽略文件
+            ignore: ["**/index.html"],
+          },
+          info: {
+            // 跳过terser压缩js
+            minimized: true,
+          },
+        },
+      ],
+    }),
+  ].filter(Boolean),
+  optimization: {
+    minimize: isProduction,
+    // 压缩的操作
+    minimizer: [
+      // 压缩css
+      new CssMinimizerPlugin(),
+      // 压缩js
+      new TerserWebpackPlugin(),
+      // 压缩图片
+      new ImageMinimizerPlugin({
+        minimizer: {
+          implementation: ImageMinimizerPlugin.imageminGenerate,
+          options: {
+            plugins: [
+              ["gifsicle", { interlaced: true }],
+              ["jpegtran", { progressive: true }],
+              ["optipng", { optimizationLevel: 5 }],
+              [
+                "svgo",
+                {
+                  plugins: [
+                    "preset-default",
+                    "prefixIds",
+                    {
+                      name: "sortAttrs",
+                      params: {
+                        xmlnsOrder: "alphabetical",
+                      },
+                    },
+                  ],
+                },
+              ],
+            ],
+          },
+        },
+      }),
+    ],
+    // 代码分割配置
+    splitChunks: {
+      chunks: "all",
+      cacheGroups: {
+        // layouts通常是admin项目的主体布局组件,所有路由组件都要使用的
+        // 可以单独打包,从而复用
+        // 如果项目中没有,请删除
+        layouts: {
+          name: "layouts",
+          test: path.resolve(__dirname, "../src/layouts"),
+          priority: 40,
+        },
+        // 如果项目中使用antd,此时将所有node_modules打包在一起,那么打包输出文件会比较大。
+        // 所以我们将node_modules中比较大的模块单独打包,从而并行加载速度更好
+        // 如果项目中没有,请删除
+        antd: {
+          name: "chunk-antd",
+          test: /[\\\\/]node_modules[\\\\/]antd(.*)/,
+          priority: 30,
+        },
+        // 将react相关的库单独打包,减少node_modules的chunk体积。
+        react: {
+          name: "react",
+          test: /[\\\\/]node_modules[\\\\/]react(.*)?[\\\\/]/,
+          chunks: "initial",
+          priority: 20,
+        },
+        libs: {
+          name: "chunk-libs",
+          test: /[\\\\/]node_modules[\\\\/]/,
+          priority: 10, // 权重最低,优先考虑前面内容
+          chunks: "initial",
+        },
+      },
+    },
+    runtimeChunk: {
+      name: (entrypoint) => \`runtime~\${entrypoint.name}\`,
+    },
+  },
+  resolve: {
+    extensions: [".jsx", ".js", ".json"],
+  },
+  devServer: {
+    open: true,
+    host: "localhost",
+    port: 3000,
+    hot: true,
+    compress: true,
+    historyApiFallback: true,
+  },
+  mode: isProduction ? "production" : "development",
+  devtool: isProduction ? "source-map" : "cheap-module-source-map",
+  performance: false, // 关闭性能分析,提示速度
+};
+


























 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 


















































































































































 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 


















 

`,19),o=[e];function l(c,i){return s(),a("div",null,o)}const u=n(t,[["render",l],["__file","react-cli.html.vue"]]),k=JSON.parse('{"path":"/project/react-cli.html","title":"React 脚手架","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"开发模式配置","slug":"开发模式配置","link":"#开发模式配置","children":[]},{"level":2,"title":"生产模式配置","slug":"生产模式配置","link":"#生产模式配置","children":[]},{"level":2,"title":"其他配置","slug":"其他配置","link":"#其他配置","children":[]},{"level":2,"title":"合并开发和生产配置","slug":"合并开发和生产配置","link":"#合并开发和生产配置","children":[]},{"level":2,"title":"优化配置","slug":"优化配置","link":"#优化配置","children":[]}],"filePathRelative":"project/react-cli.md","git":{"createdTime":1715588813000,"updatedTime":1715588813000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":1}]},"readingTime":{"minutes":7.6,"words":2279}}');export{u as comp,k as data}; diff --git a/assets/statusCode.html-Cc4EJ3fp.js b/assets/statusCode.html-Cc4EJ3fp.js new file mode 100644 index 0000000..0d9f3da --- /dev/null +++ b/assets/statusCode.html-Cc4EJ3fp.js @@ -0,0 +1,3 @@ +import{_ as s,r,o as n,c as i,a as e,b as t,d as o,e as l}from"./app-B-BkP2m_.js";const c={},d=e("h1",{id:"项目中状态码的设置-设置在http状态码还是返回业务状态码",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#项目中状态码的设置-设置在http状态码还是返回业务状态码"},[e("span",null,"项目中状态码的设置?设置在HTTP状态码还是返回业务状态码?")])],-1),u={href:"https://www.zhihu.com/search?q=HTTP%E7%8A%B6%E6%80%81%E7%A0%81&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A3205282542%7D",target:"_blank",rel:"noopener noreferrer"},h=e("h2",{id:"http-状态码",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#http-状态码"},[e("span",null,"HTTP 状态码")])],-1),T={href:"https://www.zhihu.com/search?q=%E5%93%8D%E5%BA%94%E7%B1%BB%E5%88%AB&search_source=Entity&hybrid_search_source=Entity&hybrid_search_extra=%7B%22sourceType%22%3A%22answer%22%2C%22sourceId%22%3A3205282542%7D",target:"_blank",rel:"noopener noreferrer"},p=l(`
  • 1xx:表示请求已被接收,需要继续处理。
  • 2xx:表示请求已成功被服务器接收、理解、并接受。
  • 3xx:重定向,需要客户端采取进一步的操作才能完成请求。
  • 4xx:客户端错误,表示请求包含语法错误或者无法完成请求。
  • 5xx:服务器错误,服务器在处理请求的过程中发生了错误。

HTTP 状态码是一种标准的约定,用于表示请求的处理情况。客户端在接收到这些状态码后,可以根据不同的状态码采取相应的处理措施。如果需要表达更具体的状态信息,通常的做法是在 HTTP 响应 body 中返回业务状态码,而不是自定义 HTTP 状态码。业务状态码是由应用或服务自己定义的,可以根据实际的业务需求进行定义,比如表示用户不存在、商品库存不足、支付失败等状态。

业务状态码

业务状态码是在 HTTP 状态码之上,由应用程序自身定义的,以反映特定业务逻辑的状态。这些状态码可以针对不同的操作不同的条件提供更详细更具体的信息,以便客户端能够更好地理解和处理业务流程,根据不同的状态码采取相应的处理措施。 业务状态码通常定义在响应的数据(Response Body)中,与其他响应数据一起返回给客户端。拿登录接口举个例子,登录成功后,使用 HTTP 状态码200,业务状态码1(也可以约定其他的值)来表示,响应数据格式如下:

如果账号或者密码不正确,使用 HTTP 状态码200,业务状态码1001(业务状态码可以根据自己或团队整体情况而定)来表示,响应数据格式如下:

业务状态码是需要根据具体应用程序的需求和上下文定义的,可以根据业务逻辑和操作类型自定义状态码的值。另外,针对同一个应用来说,业务状态码类型要保持一致,统一使用整型或统一使用字符串,建议统一使用整型。

{"code":1, "data":null,"msg":""}
+
{"code":1001, "data":null,"msg":"账号或密码错误"}
+
`,8);function _(m,x){const a=r("ExternalLinkIcon");return n(),i("div",null,[d,e("blockquote",null,[e("p",null,[t("HTTP 状态码用于表示 Web 服务器对请求的处理情况,是 HTTP 协议规定的一种标准表示方式。而业务状态码是为了满足应用程序特定的业务逻辑需求,提供更具体和细粒度的响应状态。在设计接口时,我们应根据情况综合考虑使用"),e("a",u,[t("HTTP状态码"),o(a)]),t("和业务状态码,以提供清晰、一致和易理解的接口响应。")])]),h,e("p",null,[t("HTTP 状态码是由 HTTP 协议定义的,用于表示 Web 服务器对请求的响应状态,每一个状态码都有特定的含义。虽然开发者可以自定义 HTTP 状态码,但并不推荐这样做,因为这可能会引起混淆或者与将来的 HTTP 规范相冲突。HTTP 状态码的值是三位数字,其中第一位数字表示"),e("a",T,[t("响应类别"),o(a)]),t(",目前有以下五个类别:")]),p])}const v=s(c,[["render",_],["__file","statusCode.html.vue"]]),P=JSON.parse('{"path":"/interview/statusCode.html","title":"项目中状态码的设置?设置在HTTP状态码还是返回业务状态码?","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"HTTP 状态码","slug":"http-状态码","link":"#http-状态码","children":[]},{"level":2,"title":"业务状态码","slug":"业务状态码","link":"#业务状态码","children":[]}],"filePathRelative":"interview/statusCode.md","git":{"createdTime":1715780535000,"updatedTime":1715780535000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":1}]},"readingTime":{"minutes":3.1,"words":931}}');export{v as comp,P as data}; diff --git a/assets/style-BA5ghLdg.css b/assets/style-BA5ghLdg.css new file mode 100644 index 0000000..5e05055 --- /dev/null +++ b/assets/style-BA5ghLdg.css @@ -0,0 +1 @@ +.vp-back-to-top-button{position:fixed!important;bottom:4rem;inset-inline-end:1rem;z-index:100;width:48px;height:48px;padding:8px;border-width:0;border-radius:50%;background:var(--back-to-top-bg-color);color:var(--back-to-top-color);box-shadow:2px 2px 10px 4px var(--back-to-top-shadow);cursor:pointer}@media (max-width: 959px){.vp-back-to-top-button{transform:scale(.8);transform-origin:100% 100%}}@media print{.vp-back-to-top-button{display:none}}.vp-back-to-top-button:hover{color:var(--back-to-top-color-hover)}.vp-back-to-top-button .back-to-top-icon{overflow:hidden;width:100%;height:100%;background:currentcolor;border-radius:50%;-webkit-mask-image:var(--back-to-top-icon);mask-image:var(--back-to-top-icon);-webkit-mask-position:50%;mask-position:50%;-webkit-mask-size:cover;mask-size:cover}.vp-scroll-progress{position:absolute;right:-2px;bottom:-2px;width:52px;height:52px}.vp-scroll-progress svg{width:100%;height:100%}.vp-scroll-progress circle{opacity:.9;transform:rotate(-90deg);transform-origin:50% 50%}.back-to-top-enter-active,.back-to-top-leave-active{transition:opacity .3s}.back-to-top-enter-from,.back-to-top-leave-to{opacity:0}:root{--back-to-top-z-index: 5;--back-to-top-icon: url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%201024%201024'%3e%3cpath%20d='M512%20843.2c-36.2%200-66.4-13.6-85.8-21.8-10.8-4.6-22.6%203.6-21.8%2015.2l7%20102c.4%206.2%207.6%209.4%2012.6%205.6l29-22c3.6-2.8%209-1.8%2011.4%202l41%2064.2c3%204.8%2010.2%204.8%2013.2%200l41-64.2c2.4-3.8%207.8-4.8%2011.4-2l29%2022c5%203.8%2012.2.6%2012.6-5.6l7-102c.8-11.6-11-20-21.8-15.2-19.6%208.2-49.6%2021.8-85.8%2021.8'/%3e%3cpath%20d='m795.4%20586.2-96-98.2C699.4%20172%20513%2032%20513%2032S324.8%20172%20324.8%20488l-96%2098.2c-3.6%203.6-5.2%209-4.4%2014.2L261.2%20824c1.8%2011.4%2014.2%2017%2023.6%2010.8L419%20744s41.4%2040%2094.2%2040%2092.2-40%2092.2-40l134.2%2090.8c9.2%206.2%2021.6.6%2023.6-10.8l37-223.8c.4-5.2-1.2-10.4-4.8-14M513%20384c-34%200-61.4-28.6-61.4-64s27.6-64%2061.4-64c34%200%2061.4%2028.6%2061.4%2064S547%20384%20513%20384'/%3e%3c/svg%3e");--back-to-top-bg-color: #fff;--back-to-top-color: #3eaf7c;--back-to-top-color-hover: #71cda3;--back-to-top-shadow: rgb(0 0 0 / 20%)}:root{--external-link-icon-color: #aaa}.external-link-icon{position:relative;display:inline-block;color:var(--external-link-icon-color);vertical-align:middle;top:-1px}@media print{.external-link-icon{display:none}}.external-link-icon-sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0;-webkit-user-select:none;-moz-user-select:none;user-select:none}:root{--medium-zoom-z-index: 100;--medium-zoom-bg-color: #ffffff;--medium-zoom-opacity: 1}.medium-zoom-overlay{background-color:var(--medium-zoom-bg-color)!important;z-index:var(--medium-zoom-z-index)}.medium-zoom-overlay~img{z-index:calc(var(--medium-zoom-z-index) + 1)}.medium-zoom--opened .medium-zoom-overlay{opacity:var(--medium-zoom-opacity)}:root{--nprogress-color: #29d;--nprogress-z-index: 1031}#nprogress{pointer-events:none}#nprogress .bar{background:var(--nprogress-color);position:fixed;z-index:var(--nprogress-z-index);top:0;left:0;width:100%;height:2px}.vp-page-meta{max-width:var(--content-width);margin:0 auto;padding:.75rem 2.5rem;display:flex;flex-wrap:wrap;justify-content:space-between;overflow:auto}@media (max-width: 959px){.vp-page-meta{padding:2rem}}@media (max-width: 419px){.vp-page-meta{padding:1.5rem}}@media print{.vp-page-meta{margin:0!important;padding-inline:0!important}}@media (max-width: 719px){.vp-page-meta{display:block}}.vp-page-meta .vp-meta-item{flex-grow:1}.vp-page-meta .vp-meta-item .vp-meta-label{font-weight:500}.vp-page-meta .vp-meta-item .vp-meta-label:not(a){color:var(--c-text-lighter)}.vp-page-meta .vp-meta-item .vp-meta-info{color:var(--c-text-quote);font-weight:400}.vp-page-meta .git-info{text-align:end}.vp-page-meta .edit-link{margin-top:.25rem;margin-bottom:.25rem;margin-inline-end:.5rem;font-size:14px}@media print{.vp-page-meta .edit-link{display:none}}.vp-page-meta .edit-link .icon{position:relative;bottom:-.125em;width:1em;height:1em;margin-inline-end:.25em}.vp-page-meta .last-updated,.vp-page-meta .contributors{margin-top:.25rem;margin-bottom:.25rem;font-size:14px}@media (max-width: 719px){.vp-page-meta .last-updated,.vp-page-meta .contributors{font-size:13px;text-align:start}}.vp-page-nav{display:flex;flex-wrap:wrap;max-width:var(--content-width, 740px);min-height:2rem;margin-inline:auto;margin-top:0;padding-block:.5rem;padding-inline:2rem;border-top:1px solid var(--c-border);transition:border-top var(--t-color);padding-top:1rem;padding-bottom:0}@media (max-width: 959px){.vp-page-nav{padding-inline:1rem}}@media print{.vp-page-nav{display:none}}.vp-page-nav .route-link{display:inline-block;flex-grow:1;margin:.25rem;padding:.25rem .5rem;border:1px solid var(--c-border);border-radius:.25rem}.vp-page-nav .route-link:hover{background:var(--c-bg-light)}.vp-page-nav .route-link .hint{color:var(--c-text-quote);font-size:.875rem;line-height:2}.vp-page-nav .prev{text-align:start}.vp-page-nav .next{text-align:end}:root{--c-brand: #3eaf7c;--c-brand-light: #4abf8a;--c-bg: #ffffff;--c-bg-light: #f3f4f5;--c-bg-lighter: #eeeeee;--c-bg-dark: #ebebec;--c-bg-darker: #e6e6e6;--c-bg-navbar: var(--c-bg);--c-bg-sidebar: var(--c-bg);--c-bg-arrow: #cccccc;--c-text: #2c3e50;--c-text-accent: var(--c-brand);--c-text-light: #3a5169;--c-text-lighter: #4e6e8e;--c-text-lightest: #6a8bad;--c-text-quote: #999999;--c-border: #eaecef;--c-border-dark: #dfe2e5;--c-tip: #42b983;--c-tip-bg: var(--c-bg-light);--c-tip-title: var(--c-text);--c-tip-text: var(--c-text);--c-tip-text-accent: var(--c-text-accent);--c-warning: #ffc310;--c-warning-bg: #fffae3;--c-warning-bg-light: #fff3ba;--c-warning-bg-lighter: #fff0b0;--c-warning-border-dark: #f7dc91;--c-warning-details-bg: #fff5ca;--c-warning-title: #f1b300;--c-warning-text: #746000;--c-warning-text-accent: #edb100;--c-warning-text-light: #c1971c;--c-warning-text-quote: #ccab49;--c-danger: #f11e37;--c-danger-bg: #ffe0e0;--c-danger-bg-light: #ffcfde;--c-danger-bg-lighter: #ffc9c9;--c-danger-border-dark: #f1abab;--c-danger-details-bg: #ffd4d4;--c-danger-title: #ed1e2c;--c-danger-text: #660000;--c-danger-text-accent: #bd1a1a;--c-danger-text-light: #b5474d;--c-danger-text-quote: #c15b5b;--c-details-bg: #eeeeee;--c-badge-tip: var(--c-tip);--c-badge-warning: #ecc808;--c-badge-warning-text: var(--c-bg);--c-badge-danger: #dc2626;--c-badge-danger-text: var(--c-bg);--c-code-group-tab-title: rgba(255, 255, 255, .9);--c-code-group-tab-bg: var(--code-bg-color);--c-code-group-tab-outline: var(var(--c-code-group-tab-title));--c-code-group-tab-active-border: var(--c-brand);--t-color: .3s ease;--t-transform: .3s ease;--code-bg-color: #282c34;--code-hl-bg-color: rgba(0, 0, 0, .66);--code-ln-color: #9e9e9e;--code-ln-wrapper-width: 3.5rem;--font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;--font-family-code: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;--navbar-height: 3.6rem;--navbar-padding-v: .7rem;--navbar-padding-h: 1.5rem;--sidebar-width: 20rem;--sidebar-width-mobile: calc(var(--sidebar-width) * .82);--content-width: 740px;--homepage-width: 960px}.vp-back-to-top-button{--back-to-top-color: var(--c-brand);--back-to-top-color-hover: var(--c-brand-light);--back-to-top-bg-color: var(--c-bg)}.vp-catalog-wrapper{--catalog-bg-color: var(--c-bg);--catalog-bg-secondary-color: var(--c-bg-dark);--catalog-border-color: var(--c-border);--catalog-active-color: var(--c-brand);--catalog-hover-color: var(--c-brand-light)}.waline-wrapper{--waline-bg-color: var(--c-bg);--waline-bg-color-light: var(--c-bg-light);--waline-text-color: var(--c-color);--waline-border: 1px solid var(--c-border);--waline-border-color: var(--c-border);--waline-theme-color: var(--c-brand);--waline-active-color: var(--c-brand-light)}.DocSearch{--docsearch-primary-color: var(--c-brand);--docsearch-text-color: var(--c-text);--docsearch-highlight-color: var(--c-brand);--docsearch-muted-color: var(--c-text-quote);--docsearch-container-background: rgba(9, 10, 17, .8);--docsearch-modal-background: var(--c-bg-light);--docsearch-searchbox-background: var(--c-bg-lighter);--docsearch-searchbox-focus-background: var(--c-bg);--docsearch-searchbox-shadow: inset 0 0 0 2px var(--c-brand);--docsearch-hit-color: var(--c-text-light);--docsearch-hit-active-color: var(--c-bg);--docsearch-hit-background: var(--c-bg);--docsearch-hit-shadow: 0 1px 3px 0 var(--c-border-dark);--docsearch-footer-background: var(--c-bg)}.external-link-icon{--external-link-icon-color: var(--c-text-quote)}.medium-zoom-overlay{--medium-zoom-bg-color: var(--c-bg)}.vp-notice-wrapper{--notice-color: var(--c-text);--notice-bg-color: var(--c-bg);--notice-primary-color: var(--c-brand);--notice-primary-text-color: var(--c-bg);--notice-primary-hover-color: var(--c-brand-light);--notice-button-color: var(--c-bg-light);--notice-button-hover-color: var(--c-bg-lighter)}#nprogress{--nprogress-color: var(--c-brand)}body{--photo-swipe-bullet: var(--c-bg);--photo-swipe-bullet-active: var(--c-brand)}body{--pwa-text-color: var(--c-text);--pwa-bg-color: var(--c-bg);--pwa-border-color: var(--c-brand);--pwa-btn-text-color: var(--c-bg);--pwa-btn-bg-color: var(--c-brand);--pwa-btn-hover-bg-color: var(--c-brand-light)}.redirect-modal-mask{--redirect-bg-color: var(--c-bg);--redirect-bg-color-light: var(--c-bg-light);--redirect-bg-color-lighter: var(--c-bg-lighter);--redirect-text-color: var(--c-text);--redirect-primary-color: var(--c-brand);--redirect-primary-hover-color: var(--c-brand-light);--redirect-primary-text-color: var(--c-bg)}.search-box{--search-bg-color: var(--c-bg);--search-accent-color: var(--c-brand);--search-text-color: var(--c-text);--search-border-color: var(--c-border);--search-item-text-color: var(--c-text-lighter);--search-item-focus-bg-color: var(--c-bg-light)}html.dark{--c-brand: #3aa675;--c-brand-light: #349469;--c-bg: #22272e;--c-bg-light: #2b313a;--c-bg-lighter: #262c34;--c-bg-dark: #343b44;--c-bg-darker: #37404c;--c-text: #adbac7;--c-text-light: #96a7b7;--c-text-lighter: #8b9eb0;--c-text-lightest: #8094a8;--c-border: #3e4c5a;--c-border-dark: #34404c;--c-tip: #318a62;--c-warning: #e0ad15;--c-warning-bg: #2d2f2d;--c-warning-bg-light: #423e2a;--c-warning-bg-lighter: #44442f;--c-warning-border-dark: #957c35;--c-warning-details-bg: #39392d;--c-warning-title: #fdca31;--c-warning-text: #d8d96d;--c-warning-text-accent: #ffbf00;--c-warning-text-light: #ddb84b;--c-warning-text-quote: #ccab49;--c-danger: #fc1e38;--c-danger-bg: #39232c;--c-danger-bg-light: #4b2b35;--c-danger-bg-lighter: #553040;--c-danger-border-dark: #a25151;--c-danger-details-bg: #482936;--c-danger-title: #fc2d3b;--c-danger-text: #ea9ca0;--c-danger-text-accent: #fd3636;--c-danger-text-light: #d9777c;--c-danger-text-quote: #d56b6b;--c-details-bg: #323843;--c-badge-warning: var(--c-warning);--c-badge-warning-text: #3c2e05;--c-badge-danger: var(--c-danger);--c-badge-danger-text: #401416;--code-hl-bg-color: #363b46}html.dark .DocSearch{--docsearch-logo-color: var(--c-text);--docsearch-modal-shadow: inset 1px 1px 0 0 #2c2e40, 0 3px 8px 0 #000309;--docsearch-key-shadow: inset 0 -2px 0 0 #282d55, inset 0 0 1px 1px #51577d, 0 2px 2px 0 rgba(3, 4, 9, .3);--docsearch-key-gradient: linear-gradient(-225deg, #444950, #1c1e21);--docsearch-footer-shadow: inset 0 1px 0 0 rgba(73, 76, 106, .5), 0 -4px 8px 0 rgba(0, 0, 0, .2)}html.dark body{--pwa-shadow-color: rgb(0 0 0 / 30%);--pwa-content-color: #ccc;--pwa-content-light-color: #999}html,body{padding:0;margin:0;background-color:var(--c-bg);transition:background-color var(--t-color)}html.dark{color-scheme:dark}html{font-size:16px}body{font-family:var(--font-family);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-size:1rem;color:var(--c-text)}a{font-weight:500;color:var(--c-text-accent);text-decoration:none;overflow-wrap:break-word}p a code{font-weight:400;color:var(--c-text-accent)}kbd{font-family:var(--font-family-code);color:var(--c-text);background:var(--c-bg-lighter);border:solid .15rem var(--c-border-dark);border-bottom:solid .25rem var(--c-border-dark);border-radius:.15rem;padding:0 .15em}code{font-family:var(--font-family-code);color:var(--c-text-lighter);padding:.25rem .5rem;margin:0;font-size:.85em;background-color:var(--c-bg-light);border-radius:3px;overflow-wrap:break-word;transition:background-color var(--t-color)}blockquote{font-size:1rem;color:var(--c-text-quote);border-left:.2rem solid var(--c-border-dark);margin:1rem 0;padding:.25rem 0 .25rem 1rem;overflow-wrap:break-word}blockquote>p{margin:0}ul,ol{padding-left:1.2em}strong{font-weight:600}h1,h2,h3,h4,h5,h6{font-weight:600;line-height:1.25;overflow-wrap:break-word}h1:focus-visible,h2:focus-visible,h3:focus-visible,h4:focus-visible,h5:focus-visible,h6:focus-visible{outline:none}h1 .header-anchor,h2 .header-anchor,h3 .header-anchor,h4 .header-anchor,h5 .header-anchor,h6 .header-anchor{color:inherit;text-decoration:none;position:relative}h1 .header-anchor:hover:before,h2 .header-anchor:hover:before,h3 .header-anchor:hover:before,h4 .header-anchor:hover:before,h5 .header-anchor:hover:before,h6 .header-anchor:hover:before{font-size:.8em;content:"¶";position:absolute;left:-.75em;color:var(--c-brand)}h1 .header-anchor:focus-visible,h2 .header-anchor:focus-visible,h3 .header-anchor:focus-visible,h4 .header-anchor:focus-visible,h5 .header-anchor:focus-visible,h6 .header-anchor:focus-visible{outline:none}h1 .header-anchor:focus-visible:before,h2 .header-anchor:focus-visible:before,h3 .header-anchor:focus-visible:before,h4 .header-anchor:focus-visible:before,h5 .header-anchor:focus-visible:before,h6 .header-anchor:focus-visible:before{content:"¶";position:absolute;left:-.75em;color:var(--c-brand);outline:auto}h1{font-size:2.2rem}h2{font-size:1.65rem;padding-bottom:.3rem;border-bottom:1px solid var(--c-border);transition:border-color var(--t-color)}h3{font-size:1.35rem}h4{font-size:1.15rem}h5{font-size:1.05rem}h6{font-size:1rem}@media print{a[href^="http://"]:after,a[href^="https://"]:after{content:" (" attr(href) ") "}}p,ul,ol{line-height:1.7;overflow-wrap:break-word}hr{border:0;border-top:1px solid var(--c-border)}table{border-collapse:collapse;margin:1rem 0;display:block;overflow-x:auto;transition:border-color var(--t-color)}tr{border-top:1px solid var(--c-border-dark);transition:border-color var(--t-color)}tr:nth-child(2n){background-color:var(--c-bg-light);transition:background-color var(--t-color)}tr:nth-child(2n) code{background-color:var(--c-bg-dark)}th,td{padding:.6em 1em;border:1px solid var(--c-border-dark);transition:border-color var(--t-color)}.arrow{display:inline-block;vertical-align:middle;width:1em;height:1em;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(0,0,0,0.5)' d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z'/%3E%3C/svg%3E");background-position:center;background-repeat:no-repeat;line-height:normal;transition:all .3s}html.dark .arrow{background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(255,255,255,0.5)' d='M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z'/%3E%3C/svg%3E")}.arrow.down{transform:rotate(180deg)}.arrow.right{transform:rotate(90deg)}.arrow.left{transform:rotate(-90deg)}.badge{display:inline-block;font-size:14px;font-weight:600;height:18px;line-height:18px;border-radius:3px;padding:0 6px;color:var(--c-bg);vertical-align:top;transition:color var(--t-color),background-color var(--t-color)}.badge.tip{background-color:var(--c-badge-tip)}.badge.warning{background-color:var(--c-badge-warning);color:var(--c-badge-warning-text)}.badge.danger{background-color:var(--c-badge-danger);color:var(--c-badge-danger-text)}.badge+.badge{margin-left:5px}code[class*=language-],pre[class*=language-]{color:#ccc;background:none;font-family:var(--font-family-code);font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;hyphens:none}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*=language-],pre[class*=language-]{background:#2d2d2d}:not(pre)>code[class*=language-]{padding:.1em;border-radius:.3em;white-space:normal}.token.comment,.token.block-comment,.token.prolog,.token.doctype,.token.cdata{color:#999}.token.punctuation{color:#ccc}.token.tag,.token.attr-name,.token.namespace,.token.deleted{color:#ec5975}.token.function-name{color:#6196cc}.token.boolean,.token.number,.token.function{color:#f08d49}.token.property,.token.class-name,.token.constant,.token.symbol{color:#f8c555}.token.selector,.token.important,.token.atrule,.token.keyword,.token.builtin{color:#cc99cd}.token.string,.token.char,.token.attr-value,.token.regex,.token.variable{color:#7ec699}.token.operator,.token.entity,.token.url{color:#67cdcc}.token.important,.token.bold{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.inserted{color:#3eaf7c}.theme-default-content pre,.theme-default-content pre[class*=language-]{line-height:1.375;padding:1.3rem 1.5rem;margin:.85rem 0;border-radius:6px;overflow:auto}.theme-default-content pre code,.theme-default-content pre[class*=language-] code{color:#fff;padding:0;background-color:transparent!important;border-radius:0;overflow-wrap:unset;-webkit-font-smoothing:auto;-moz-osx-font-smoothing:auto}.theme-default-content .line-number{font-family:var(--font-family-code)}div[class*=language-]{position:relative;background-color:var(--code-bg-color);border-radius:6px}div[class*=language-]:before{content:attr(data-title);position:absolute;z-index:3;top:.8em;right:1em;font-size:.75rem;color:var(--code-ln-color)}div[class*=language-] pre,div[class*=language-] pre[class*=language-]{background:transparent!important;position:relative;z-index:1}div[class*=language-] .highlight-lines{-webkit-user-select:none;-moz-user-select:none;user-select:none;padding-top:1.3rem;position:absolute;top:0;left:0;width:100%;line-height:1.375}div[class*=language-] .highlight-lines .highlight-line{background-color:var(--code-hl-bg-color)}div[class*=language-]:not(.line-numbers-mode) .line-numbers{display:none}div[class*=language-].line-numbers-mode .highlight-lines .highlight-line{position:relative}div[class*=language-].line-numbers-mode .highlight-lines .highlight-line:before{content:" ";position:absolute;z-index:2;left:0;top:0;display:block;width:var(--code-ln-wrapper-width);height:100%}div[class*=language-].line-numbers-mode pre{margin-left:var(--code-ln-wrapper-width);padding-left:1rem;vertical-align:middle}div[class*=language-].line-numbers-mode .line-numbers{position:absolute;top:0;width:var(--code-ln-wrapper-width);text-align:center;color:var(--code-ln-color);padding-top:1.25rem;line-height:1.375;counter-reset:line-number}div[class*=language-].line-numbers-mode .line-numbers .line-number{position:relative;z-index:3;-webkit-user-select:none;-moz-user-select:none;user-select:none;height:1.375em}div[class*=language-].line-numbers-mode .line-numbers .line-number:before{counter-increment:line-number;content:counter(line-number);font-size:.85em}div[class*=language-].line-numbers-mode:after{content:"";position:absolute;top:0;left:0;width:var(--code-ln-wrapper-width);height:100%;border-radius:6px 0 0 6px;border-right:1px solid var(--code-hl-bg-color)}@media (max-width: 419px){.theme-default-content div[class*=language-]{margin:.85rem -1.5rem;border-radius:0}}.code-group__nav{margin-top:.85rem;margin-bottom:calc(-1.7rem - 6px);padding-bottom:calc(1.7rem - 6px);padding-left:10px;padding-top:10px;border-top-left-radius:6px;border-top-right-radius:6px;background-color:var(--c-code-group-tab-bg)}.code-group__nav-tab{border:0;padding:5px;cursor:pointer;background-color:transparent;font-size:.85em;line-height:1.4;color:var(--c-code-group-tab-title);font-weight:600}.code-group__nav-tab:focus{outline:none}.code-group__nav-tab:focus-visible{outline:1px solid var(--c-code-group-tab-outline)}.code-group__nav-tab-active{border-bottom:var(--c-code-group-tab-active-border) 1px solid}@media (max-width: 419px){.code-group__nav{margin-left:-1.5rem;margin-right:-1.5rem;border-radius:0}}.code-group-item{display:none}.code-group-item__active{display:block}.code-group-item>pre{background-color:orange}.custom-container{transition:color var(--t-color),border-color var(--t-color),background-color var(--t-color)}.custom-container .custom-container-title{font-weight:600}.custom-container .custom-container-title:not(:only-child){margin-bottom:-.4rem}.custom-container.tip,.custom-container.warning,.custom-container.danger{padding:.1rem 1.5rem;border-left-width:.5rem;border-left-style:solid;margin:1rem 0}.custom-container.tip{border-color:var(--c-tip);background-color:var(--c-tip-bg);color:var(--c-tip-text)}.custom-container.tip .custom-container-title{color:var(--c-tip-title)}.custom-container.tip a{color:var(--c-tip-text-accent)}.custom-container.tip code{background-color:var(--c-bg-dark)}.custom-container.warning{border-color:var(--c-warning);background-color:var(--c-warning-bg);color:var(--c-warning-text)}.custom-container.warning .custom-container-title{color:var(--c-warning-title)}.custom-container.warning a{color:var(--c-warning-text-accent)}.custom-container.warning blockquote{border-left-color:var(--c-warning-border-dark);color:var(--c-warning-text-quote)}.custom-container.warning code{color:var(--c-warning-text-light);background-color:var(--c-warning-bg-light)}.custom-container.warning details{background-color:var(--c-warning-details-bg)}.custom-container.warning details code{background-color:var(--c-warning-bg-lighter)}.custom-container.warning .external-link-icon{--external-link-icon-color: var(--c-warning-text-quote)}.custom-container.danger{border-color:var(--c-danger);background-color:var(--c-danger-bg);color:var(--c-danger-text)}.custom-container.danger .custom-container-title{color:var(--c-danger-title)}.custom-container.danger a{color:var(--c-danger-text-accent)}.custom-container.danger blockquote{border-left-color:var(--c-danger-border-dark);color:var(--c-danger-text-quote)}.custom-container.danger code{color:var(--c-danger-text-light);background-color:var(--c-danger-bg-light)}.custom-container.danger details{background-color:var(--c-danger-details-bg)}.custom-container.danger details code{background-color:var(--c-danger-bg-lighter)}.custom-container.danger .external-link-icon{--external-link-icon-color: var(--c-danger-text-quote)}.custom-container.details{display:block;position:relative;border-radius:2px;margin:1.6em 0;padding:1.6em;background-color:var(--c-details-bg)}.custom-container.details code{background-color:var(--c-bg-darker)}.custom-container.details h4{margin-top:0}.custom-container.details figure:last-child,.custom-container.details p:last-child{margin-bottom:0;padding-bottom:0}.custom-container.details summary{outline:none;cursor:pointer}.home{padding:var(--navbar-height) 2rem 0;max-width:var(--homepage-width);margin:0 auto;display:block}.home .hero{text-align:center}.home .hero img{max-width:100%;max-height:280px;display:block;margin:3rem auto 1.5rem}.home .hero h1{font-size:3rem}.home .hero h1,.home .hero .description,.home .hero .actions{margin:1.8rem auto}.home .hero .actions{display:flex;flex-wrap:wrap;gap:1rem;justify-content:center}.home .hero .description{max-width:35rem;font-size:1.6rem;line-height:1.3;color:var(--c-text-lightest)}.home .hero .action-button{display:inline-block;font-size:1.2rem;padding:.8rem 1.6rem;border-width:2px;border-style:solid;border-radius:4px;transition:background-color var(--t-color);box-sizing:border-box}.home .hero .action-button.primary{color:var(--c-bg);background-color:var(--c-brand);border-color:var(--c-brand)}.home .hero .action-button.primary:hover{background-color:var(--c-brand-light)}.home .hero .action-button.secondary{color:var(--c-brand);background-color:var(--c-bg);border-color:var(--c-brand)}.home .hero .action-button.secondary:hover{color:var(--c-bg);background-color:var(--c-brand-light)}.home .features{border-top:1px solid var(--c-border);transition:border-color var(--t-color);padding:1.2rem 0;margin-top:2.5rem;display:flex;flex-wrap:wrap;align-items:flex-start;align-content:stretch;justify-content:space-between}.home .feature{flex-grow:1;flex-basis:30%;max-width:30%}.home .feature h2{font-size:1.4rem;font-weight:500;border-bottom:none;padding-bottom:0;color:var(--c-text-light)}.home .feature p{color:var(--c-text-lighter)}.home .theme-default-content{padding:0;margin:0}.home .footer{padding:2.5rem;border-top:1px solid var(--c-border);text-align:center;color:var(--c-text-lighter);transition:border-color var(--t-color)}@media (max-width: 719px){.home .features{flex-direction:column}.home .feature{max-width:100%;padding:0 2.5rem}}@media (max-width: 419px){.home{padding-left:1.5rem;padding-right:1.5rem}.home .hero img{max-height:210px;margin:2rem auto 1.2rem}.home .hero h1{font-size:2rem}.home .hero h1,.home .hero .description,.home .hero .actions{margin:1.2rem auto}.home .hero .description{font-size:1.2rem}.home .hero .action-button{font-size:1rem;padding:.6rem 1.2rem}.home .feature h2{font-size:1.25rem}}.page{padding-top:var(--navbar-height);padding-left:var(--sidebar-width)}.navbar{position:fixed;z-index:20;top:0;left:0;right:0;height:var(--navbar-height);box-sizing:border-box;border-bottom:1px solid var(--c-border);background-color:var(--c-bg-navbar);transition:background-color var(--t-color),border-color var(--t-color)}.sidebar{font-size:16px;width:var(--sidebar-width);position:fixed;z-index:10;margin:0;top:var(--navbar-height);left:0;bottom:0;box-sizing:border-box;border-right:1px solid var(--c-border);overflow-y:auto;scrollbar-width:thin;scrollbar-color:var(--c-brand) var(--c-border);background-color:var(--c-bg-sidebar);transition:transform var(--t-transform),background-color var(--t-color),border-color var(--t-color)}.sidebar::-webkit-scrollbar{width:7px}.sidebar::-webkit-scrollbar-track{background-color:var(--c-border)}.sidebar::-webkit-scrollbar-thumb{background-color:var(--c-brand)}.sidebar-mask{position:fixed;z-index:9;top:0;left:0;width:100vw;height:100vh;display:none}.theme-container.sidebar-open .sidebar-mask{display:block}.theme-container.sidebar-open .navbar>.toggle-sidebar-button .icon span:nth-child(1){transform:rotate(45deg) translate3d(5.5px,5.5px,0)}.theme-container.sidebar-open .navbar>.toggle-sidebar-button .icon span:nth-child(2){transform:scale3d(0,1,1)}.theme-container.sidebar-open .navbar>.toggle-sidebar-button .icon span:nth-child(3){transform:rotate(-45deg) translate3d(6px,-6px,0)}.theme-container.sidebar-open .navbar>.toggle-sidebar-button .icon span:nth-child(1),.theme-container.sidebar-open .navbar>.toggle-sidebar-button .icon span:nth-child(3){transform-origin:center}.theme-container.no-navbar .theme-default-content h1,.theme-container.no-navbar .theme-default-content h2,.theme-container.no-navbar .theme-default-content h3,.theme-container.no-navbar .theme-default-content h4,.theme-container.no-navbar .theme-default-content h5,.theme-container.no-navbar .theme-default-content h6{margin-top:1.5rem;padding-top:0}.theme-container.no-navbar .page{padding-top:0}.theme-container.no-navbar .sidebar{top:0}.theme-container.no-sidebar .sidebar{display:none}@media (max-width: 719px){.theme-container.no-sidebar .sidebar{display:block}}.theme-container.no-sidebar .page{padding-left:0}.theme-default-content a:not(.header-anchor):hover{text-decoration:underline}.theme-default-content img{max-width:100%}.theme-default-content h1,.theme-default-content h2,.theme-default-content h3,.theme-default-content h4,.theme-default-content h5,.theme-default-content h6{margin-top:calc(.5rem - var(--navbar-height));padding-top:calc(1rem + var(--navbar-height));margin-bottom:0}.theme-default-content h1:first-child,.theme-default-content h2:first-child,.theme-default-content h3:first-child,.theme-default-content h4:first-child,.theme-default-content h5:first-child,.theme-default-content h6:first-child{margin-bottom:1rem}.theme-default-content h1:first-child+p,.theme-default-content h1:first-child+pre,.theme-default-content h1:first-child+.custom-container,.theme-default-content h2:first-child+p,.theme-default-content h2:first-child+pre,.theme-default-content h2:first-child+.custom-container,.theme-default-content h3:first-child+p,.theme-default-content h3:first-child+pre,.theme-default-content h3:first-child+.custom-container,.theme-default-content h4:first-child+p,.theme-default-content h4:first-child+pre,.theme-default-content h4:first-child+.custom-container,.theme-default-content h5:first-child+p,.theme-default-content h5:first-child+pre,.theme-default-content h5:first-child+.custom-container,.theme-default-content h6:first-child+p,.theme-default-content h6:first-child+pre,.theme-default-content h6:first-child+.custom-container{margin-top:2rem}@media (max-width: 959px){.sidebar{font-size:15px;width:var(--sidebar-width-mobile)}.page{padding-left:var(--sidebar-width-mobile)}}@media (max-width: 719px){.sidebar{top:0;padding-top:var(--navbar-height);transform:translate(-100%)}.page{padding-left:0}.theme-container.sidebar-open .sidebar{transform:translate(0)}.theme-container.no-navbar .sidebar{padding-top:0}}@media (max-width: 419px){h1{font-size:1.9rem}}#vp-comment{max-width:var(--content-width);margin:0 auto;padding:2rem 2.5rem}@media (max-width: 959px){#vp-comment{padding:2rem}}@media (max-width: 419px){#vp-comment{padding:1.5rem}}.navbar{--navbar-line-height: calc( var(--navbar-height) - 2 * var(--navbar-padding-v) );padding:var(--navbar-padding-v) var(--navbar-padding-h);line-height:var(--navbar-line-height)}.navbar .logo{height:var(--navbar-line-height);margin-right:var(--navbar-padding-v);vertical-align:top}.navbar .site-name{font-size:1.3rem;font-weight:600;color:var(--c-text);position:relative}.navbar .navbar-items-wrapper{display:flex;position:absolute;box-sizing:border-box;top:var(--navbar-padding-v);right:var(--navbar-padding-h);height:var(--navbar-line-height);padding-left:var(--navbar-padding-h);white-space:nowrap;font-size:.9rem}.navbar .navbar-items-wrapper .search-box{flex:0 0 auto;vertical-align:top}@media screen and (max-width: 719px){.navbar{padding-left:4rem}.navbar .site-name{display:block;width:calc(100vw - 11rem);overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.navbar .can-hide{display:none}}.navbar-items{display:inline-block}@media print{.navbar-items{display:none}}.navbar-items a{display:inline-block;line-height:1.4rem;color:inherit}.navbar-items a:hover,.navbar-items a.route-link-active{color:var(--c-text)}.navbar-items .navbar-item{position:relative;display:inline-block;margin-left:1.5rem;line-height:var(--navbar-line-height)}.navbar-items .navbar-item:first-child{margin-left:0}.navbar-items .navbar-item>a:hover,.navbar-items .navbar-item>a.route-link-active{margin-bottom:-2px;border-bottom:2px solid var(--c-text-accent)}@media (max-width: 719px){.navbar-items .navbar-item{margin-left:0}.navbar-items .navbar-item>a:hover,.navbar-items .navbar-item>a.route-link-active{margin-bottom:0;border-bottom:none}.navbar-items a:hover,.navbar-items a.route-link-active{color:var(--c-text-accent)}}.toggle-sidebar-button{position:absolute;top:.6rem;left:1rem;display:none;padding:.6rem;cursor:pointer}.toggle-sidebar-button .icon{display:flex;flex-direction:column;justify-content:center;align-items:center;width:1.25rem;height:1.25rem;cursor:inherit}.toggle-sidebar-button .icon span{display:inline-block;width:100%;height:2px;border-radius:2px;background-color:var(--c-text);transition:transform var(--t-transform)}.toggle-sidebar-button .icon span:nth-child(2){margin:6px 0}@media screen and (max-width: 719px){.toggle-sidebar-button{display:block}}.toggle-color-mode-button{display:flex;margin:auto;margin-left:1rem;border:0;background:none;color:var(--c-text);opacity:.8;cursor:pointer}@media print{.toggle-color-mode-button{display:none}}.toggle-color-mode-button:hover{opacity:1}.toggle-color-mode-button .icon{width:1.25rem;height:1.25rem}.DocSearch{transition:background-color var(--t-color)}.navbar-dropdown-wrapper{cursor:pointer}.navbar-dropdown-wrapper .navbar-dropdown-title,.navbar-dropdown-wrapper .navbar-dropdown-title-mobile{display:block;font-size:.9rem;font-family:inherit;cursor:inherit;padding:inherit;line-height:1.4rem;background:transparent;border:none;font-weight:500;color:var(--c-text)}.navbar-dropdown-wrapper .navbar-dropdown-title:hover,.navbar-dropdown-wrapper .navbar-dropdown-title-mobile:hover{border-color:transparent}.navbar-dropdown-wrapper .navbar-dropdown-title .arrow,.navbar-dropdown-wrapper .navbar-dropdown-title-mobile .arrow{vertical-align:middle;margin-top:-1px;margin-left:.4rem}.navbar-dropdown-wrapper .navbar-dropdown-title-mobile{display:none;font-weight:600;font-size:inherit}.navbar-dropdown-wrapper .navbar-dropdown-title-mobile:hover{color:var(--c-text-accent)}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item{color:inherit;line-height:1.7rem}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item .navbar-dropdown-subtitle{margin:.45rem 0 0;border-top:1px solid var(--c-border);padding:1rem 0 .45rem;font-size:.9rem}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item .navbar-dropdown-subtitle>span{padding:0 1.5rem 0 1.25rem}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item .navbar-dropdown-subtitle>a{font-weight:inherit}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item .navbar-dropdown-subtitle>a.route-link-active:after{display:none}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item .navbar-dropdown-subitem-wrapper{padding:0;list-style:none}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item .navbar-dropdown-subitem-wrapper .navbar-dropdown-subitem{font-size:.9em}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item a{display:block;line-height:1.7rem;position:relative;border-bottom:none;font-weight:400;margin-bottom:0;padding:0 1.5rem 0 1.25rem}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item a:hover,.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item a.route-link-active{color:var(--c-text-accent)}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item a.route-link-active:after{content:"";width:0;height:0;border-left:5px solid var(--c-text-accent);border-top:3px solid transparent;border-bottom:3px solid transparent;position:absolute;top:calc(50% - 2px);left:9px}.navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item:first-child .navbar-dropdown-subtitle{margin-top:0;padding-top:0;border-top:0}.navbar-dropdown-wrapper.mobile.open .navbar-dropdown-title,.navbar-dropdown-wrapper.mobile.open .navbar-dropdown-title-mobile{margin-bottom:.5rem}.navbar-dropdown-wrapper.mobile .navbar-dropdown-title,.navbar-dropdown-wrapper.mobile .navbar-dropdown-title-mobile{display:none}.navbar-dropdown-wrapper.mobile .navbar-dropdown-title-mobile{display:block}.navbar-dropdown-wrapper.mobile .navbar-dropdown{transition:height .1s ease-out;overflow:hidden}.navbar-dropdown-wrapper.mobile .navbar-dropdown .navbar-dropdown-item .navbar-dropdown-subtitle{border-top:0;margin-top:0;padding-top:0;padding-bottom:0}.navbar-dropdown-wrapper.mobile .navbar-dropdown .navbar-dropdown-item .navbar-dropdown-subtitle,.navbar-dropdown-wrapper.mobile .navbar-dropdown .navbar-dropdown-item>a{font-size:15px;line-height:2rem}.navbar-dropdown-wrapper.mobile .navbar-dropdown .navbar-dropdown-item .navbar-dropdown-subitem{font-size:14px;padding-left:1rem}.navbar-dropdown-wrapper:not(.mobile){height:1.8rem}.navbar-dropdown-wrapper:not(.mobile):hover .navbar-dropdown,.navbar-dropdown-wrapper:not(.mobile).open .navbar-dropdown{display:block!important}.navbar-dropdown-wrapper:not(.mobile).open:blur{display:none}.navbar-dropdown-wrapper:not(.mobile) .navbar-dropdown{display:none;height:auto!important;box-sizing:border-box;max-height:calc(100vh - 2.7rem);overflow-y:auto;position:absolute;top:100%;right:0;background-color:var(--c-bg-navbar);padding:.6rem 0;border:1px solid var(--c-border);border-bottom-color:var(--c-border-dark);text-align:left;border-radius:.25rem;white-space:nowrap;margin:0}.page{padding-bottom:2rem;display:block}.page .theme-default-content{max-width:var(--content-width);margin:0 auto;padding:2rem 2.5rem;padding-top:0}@media (max-width: 959px){.page .theme-default-content{padding:2rem}}@media (max-width: 419px){.page .theme-default-content{padding:1.5rem}}.sidebar ul{padding:0;margin:0;list-style-type:none}.sidebar a{display:inline-block}.sidebar .navbar-items{display:none;border-bottom:1px solid var(--c-border);transition:border-color var(--t-color);padding:.5rem 0 .75rem}.sidebar .navbar-items a{font-weight:600}.sidebar .navbar-items .navbar-item{display:block;line-height:1.25rem;font-size:1.1em;padding:.5rem 0 .5rem 1.5rem}.sidebar .sidebar-items{padding:1.5rem 0}@media (max-width: 719px){.sidebar .navbar-items{display:block}.sidebar .navbar-items .navbar-dropdown-wrapper .navbar-dropdown .navbar-dropdown-item a.route-link-active:after{top:calc(1rem - 2px)}.sidebar .sidebar-items{padding:1rem 0}}.sidebar-item{cursor:default;border-left:.25rem solid transparent;color:var(--c-text)}.sidebar-item:focus-visible{outline-width:1px;outline-offset:-1px}.sidebar-item.active:not(p.sidebar-heading){font-weight:600;color:var(--c-text-accent);border-left-color:var(--c-text-accent)}.sidebar-item.sidebar-heading{transition:color .15s ease;font-size:1.1em;font-weight:700;padding:.35rem 1.5rem .35rem 1.25rem;width:100%;box-sizing:border-box;margin:0}.sidebar-item.sidebar-heading+.sidebar-item-children{transition:height .1s ease-out;overflow:hidden;margin-bottom:.75rem}.sidebar-item.collapsible{cursor:pointer}.sidebar-item.collapsible .arrow{position:relative;top:-.12em;left:.5em}.sidebar-item:not(.sidebar-heading){font-size:1em;font-weight:400;display:inline-block;margin:0;padding:.35rem 1rem .35rem 2rem;line-height:1.4;width:100%;box-sizing:border-box}.sidebar-item:not(.sidebar-heading)+.sidebar-item-children{padding-left:1rem;font-size:.95em}.sidebar-item-children .sidebar-item-children .sidebar-item:not(.sidebar-heading){padding:.25rem 1rem .25rem 1.75rem}.sidebar-item-children .sidebar-item-children .sidebar-item:not(.sidebar-heading).active{font-weight:500;border-left-color:transparent}a.sidebar-heading+.sidebar-item-children .sidebar-item:not(.sidebar-heading).active{border-left-color:transparent}a.sidebar-item{cursor:pointer}a.sidebar-item:hover{color:var(--c-text-accent)}.table-of-contents .badge{vertical-align:middle}.dropdown-enter-from,.dropdown-leave-to{height:0!important}.fade-slide-y-enter-active{transition:all .2s ease}.fade-slide-y-leave-active{transition:all .2s cubic-bezier(1,.5,.8,1)}.fade-slide-y-enter-from,.fade-slide-y-leave-to{transform:translateY(10px);opacity:0}div[class*=language-]:hover:before{display:none}div[class*=language-]:hover .vp-copy-code-button{opacity:1}.vp-copy-code-button{position:absolute;top:.5em;right:.5em;z-index:5;width:2.5rem;height:2.5rem;padding:0;border-width:0;border-radius:.5rem;background:transparent;outline:none;opacity:0;cursor:pointer;transition:opacity .4s}@media print{.vp-copy-code-button{display:none}}.vp-copy-code-button:focus,.vp-copy-code-button.copied{opacity:1}.vp-copy-code-button:hover,.vp-copy-code-button.copied{background:var(--copy-code-hover)}.vp-copy-code-button.copied .vp-copy-icon{-webkit-mask-image:var(--code-copied-icon);mask-image:var(--code-copied-icon)}.vp-copy-code-button.copied:after{content:attr(data-copied);position:absolute;top:0;right:calc(100% + .25rem);display:block;height:1.25rem;padding:.625rem;border-radius:.5rem;background:var(--copy-code-hover);color:var(--copy-code-color);font-weight:500;line-height:1.25rem;white-space:nowrap}.vp-copy-icon{width:1.25rem;height:1.25rem;padding:.625rem;background:currentcolor;color:var(--copy-code-color);font-size:1.25rem;-webkit-mask-image:var(--code-copy-icon);mask-image:var(--code-copy-icon);-webkit-mask-position:50%;mask-position:50%;-webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;-webkit-mask-size:1em;mask-size:1em}:root{--code-copy-icon: url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20height='20'%20width='20'%20stroke='rgba(128,128,128,1)'%20stroke-width='2'%3e%3cpath%20stroke-linecap='round'%20stroke-linejoin='round'%20d='M9%205H7a2%202%200%200%200-2%202v12a2%202%200%200%200%202%202h10a2%202%200%200%200%202-2V7a2%202%200%200%200-2-2h-2M9%205a2%202%200%200%200%202%202h2a2%202%200%200%200%202-2M9%205a2%202%200%200%201%202-2h2a2%202%200%200%201%202%202'%20/%3e%3c/svg%3e");--code-copied-icon: url("data:image/svg+xml,%3csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%2024%2024'%20fill='none'%20height='20'%20width='20'%20stroke='rgba(128,128,128,1)'%20stroke-width='2'%3e%3cpath%20stroke-linecap='round'%20stroke-linejoin='round'%20d='M9%205H7a2%202%200%200%200-2%202v12a2%202%200%200%200%202%202h10a2%202%200%200%200%202-2V7a2%202%200%200%200-2-2h-2M9%205a2%202%200%200%200%202%202h2a2%202%200%200%200%202-2M9%205a2%202%200%200%201%202-2h2a2%202%200%200%201%202%202m-6%209%202%202%204-4'%20/%3e%3c/svg%3e");--copy-code-color: #9e9e9e;--copy-code-hover: rgb(0 0 0 / 50%)}:root{--search-bg-color: #ffffff;--search-accent-color: #3eaf7c;--search-text-color: #2c3e50;--search-border-color: #eaecef;--search-item-text-color: #5d81a5;--search-item-focus-bg-color: #f3f4f5;--search-input-width: 8rem;--search-result-width: 20rem}.search-box{display:inline-block;position:relative;margin-left:1rem}@media print{.search-box{display:none}}.search-box input{-webkit-appearance:none;-moz-appearance:none;appearance:none;cursor:text;width:var(--search-input-width);height:2rem;color:var(--search-text-color);display:inline-block;border:1px solid var(--search-border-color);border-radius:2rem;font-size:.9rem;line-height:2rem;padding:0 .5rem 0 2rem;outline:none;transition:all ease .3s;background:var(--search-bg-color) url("data:image/svg+xml,%3c?xml%20version='1.0'%20encoding='UTF-8'?%3e%3csvg%20xmlns='http://www.w3.org/2000/svg'%20width='12'%20height='13'%3e%3cg%20stroke-width='2'%20stroke='%23aaa'%20fill='none'%3e%3cpath%20d='M11.29%2011.71l-4-4'/%3e%3ccircle%20cx='5'%20cy='5'%20r='4'/%3e%3c/g%3e%3c/svg%3e") .6rem .5rem no-repeat;background-size:1rem}@media (max-width: 719px){.search-box input{cursor:pointer;width:0;border-color:transparent;position:relative}}.search-box input:focus{cursor:auto;border-color:var(--search-accent-color)}@media (max-width: 719px){.search-box input:focus{cursor:text;left:0;width:10rem}}@media (max-width: 419px){.search-box input:focus{width:8rem}}.search-box .suggestions{background:var(--search-bg-color);width:var(--search-result-width);position:absolute;top:2rem;right:0;border:1px solid var(--search-border-color);border-radius:6px;padding:.4rem;list-style-type:none}@media (max-width: 419px){.search-box .suggestions{width:calc(100vw - 4rem);right:-.5rem}}.search-box .suggestion{line-height:1.4;padding:.4rem .6rem;border-radius:4px;cursor:pointer}.search-box .suggestion a{white-space:normal;color:var(--search-item-text-color)}.search-box .suggestion.focus{background-color:var(--search-item-focus-bg-color)}.search-box .suggestion.focus a{color:var(--search-accent-color)}.search-box .suggestion .page-title{font-weight:600}.search-box .suggestion .page-header{font-size:.9em;margin-left:.25em} diff --git a/assets/summary.html-BY-_yyB5.js b/assets/summary.html-BY-_yyB5.js new file mode 100644 index 0000000..11c3f1d --- /dev/null +++ b/assets/summary.html-BY-_yyB5.js @@ -0,0 +1 @@ +import{_ as e,o as t,c as a,e as o}from"./app-B-BkP2m_.js";const c={},i=o('

总结

本章节我们学习到了:

  1. 如何搭建 React-CliVue-Cli

  2. 如何对脚手架进行优化。

  3. 未来随着项目越来越大,还可以在优化的方案。

',3),r=[i];function s(l,m){return t(),a("div",null,r)}const d=e(c,[["render",s],["__file","summary.html.vue"]]),p=JSON.parse('{"path":"/project/summary.html","title":"总结","lang":"zh-CN","frontmatter":{},"headers":[],"filePathRelative":"project/summary.md","git":{"createdTime":1715588813000,"updatedTime":1715588813000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":1}]},"readingTime":{"minutes":0.17,"words":52}}');export{d as comp,p as data}; diff --git a/assets/vue-cli.html-COG2D5XS.js b/assets/vue-cli.html-COG2D5XS.js new file mode 100644 index 0000000..68c7bf3 --- /dev/null +++ b/assets/vue-cli.html-COG2D5XS.js @@ -0,0 +1,847 @@ +import{_ as n,o as s,c as a,e as p}from"./app-B-BkP2m_.js";const t={},e=p(`

Vue 脚手架

开发模式配置

// webpack.dev.js
+const path = require("path");
+const ESLintWebpackPlugin = require("eslint-webpack-plugin");
+const HtmlWebpackPlugin = require("html-webpack-plugin");
+const { VueLoaderPlugin } = require("vue-loader");
+const { DefinePlugin } = require("webpack");
+const CopyPlugin = require("copy-webpack-plugin");
+
+const getStyleLoaders = (preProcessor) => {
+  return [
+    "vue-style-loader",
+    "css-loader",
+    {
+      loader: "postcss-loader",
+      options: {
+        postcssOptions: {
+          plugins: [
+            "postcss-preset-env", // 能解决大多数样式兼容性问题
+          ],
+        },
+      },
+    },
+    preProcessor,
+  ].filter(Boolean);
+};
+
+module.exports = {
+  entry: "./src/main.js",
+  output: {
+    path: undefined,
+    filename: "static/js/[name].js",
+    chunkFilename: "static/js/[name].chunk.js",
+    assetModuleFilename: "static/js/[hash:10][ext][query]",
+  },
+  module: {
+    rules: [
+      {
+        // 用来匹配 .css 结尾的文件
+        test: /\\.css$/,
+        // use 数组里面 Loader 执行顺序是从右到左
+        use: getStyleLoaders(),
+      },
+      {
+        test: /\\.less$/,
+        use: getStyleLoaders("less-loader"),
+      },
+      {
+        test: /\\.s[ac]ss$/,
+        use: getStyleLoaders("sass-loader"),
+      },
+      {
+        test: /\\.styl$/,
+        use: getStyleLoaders("stylus-loader"),
+      },
+      {
+        test: /\\.(png|jpe?g|gif|svg)$/,
+        type: "asset",
+        parser: {
+          dataUrlCondition: {
+            maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
+          },
+        },
+      },
+      {
+        test: /\\.(ttf|woff2?)$/,
+        type: "asset/resource",
+      },
+      {
+        test: /\\.(jsx|js)$/,
+        include: path.resolve(__dirname, "../src"),
+        loader: "babel-loader",
+        options: {
+          cacheDirectory: true,
+          cacheCompression: false,
+          plugins: [
+            // "@babel/plugin-transform-runtime" // presets中包含了
+          ],
+        },
+      },
+      // vue-loader不支持oneOf
+      {
+        test: /\\.vue$/,
+        loader: "vue-loader", // 内部会给vue文件注入HMR功能代码
+        options: {
+          // 开启缓存
+          cacheDirectory: path.resolve(
+            __dirname,
+            "node_modules/.cache/vue-loader"
+          ),
+        },
+      },
+    ],
+  },
+  plugins: [
+    new ESLintWebpackPlugin({
+      context: path.resolve(__dirname, "../src"),
+      exclude: "node_modules",
+      cache: true,
+      cacheLocation: path.resolve(
+        __dirname,
+        "../node_modules/.cache/.eslintcache"
+      ),
+    }),
+    new HtmlWebpackPlugin({
+      template: path.resolve(__dirname, "../public/index.html"),
+    }),
+    new CopyPlugin({
+      patterns: [
+        {
+          from: path.resolve(__dirname, "../public"),
+          to: path.resolve(__dirname, "../dist"),
+          toType: "dir",
+          noErrorOnMissing: true,
+          globOptions: {
+            ignore: ["**/index.html"],
+          },
+          info: {
+            minimized: true,
+          },
+        },
+      ],
+    }),
+    new VueLoaderPlugin(),
+    // 解决页面警告
+    new DefinePlugin({
+      __VUE_OPTIONS_API__: "true",
+      __VUE_PROD_DEVTOOLS__: "false",
+    }),
+  ],
+  optimization: {
+    splitChunks: {
+      chunks: "all",
+    },
+    runtimeChunk: {
+      name: (entrypoint) => \`runtime~\${entrypoint.name}\`,
+    },
+  },
+  resolve: {
+    extensions: [".vue", ".js", ".json"], // 自动补全文件扩展名,让vue可以使用
+  },
+  devServer: {
+    open: true,
+    host: "localhost",
+    port: 3000,
+    hot: true,
+    compress: true,
+    historyApiFallback: true, // 解决vue-router刷新404问题
+  },
+  mode: "development",
+  devtool: "cheap-module-source-map",
+};
+

生产模式配置

// webpack.prod.js
+const path = require("path");
+const ESLintWebpackPlugin = require("eslint-webpack-plugin");
+const HtmlWebpackPlugin = require("html-webpack-plugin");
+const MiniCssExtractPlugin = require("mini-css-extract-plugin");
+const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
+const TerserWebpackPlugin = require("terser-webpack-plugin");
+const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
+const { VueLoaderPlugin } = require("vue-loader");
+const { DefinePlugin } = require("webpack");
+
+const getStyleLoaders = (preProcessor) => {
+  return [
+    MiniCssExtractPlugin.loader,
+    "css-loader",
+    {
+      loader: "postcss-loader",
+      options: {
+        postcssOptions: {
+          plugins: [
+            "postcss-preset-env", // 能解决大多数样式兼容性问题
+          ],
+        },
+      },
+    },
+    preProcessor,
+  ].filter(Boolean);
+};
+
+module.exports = {
+  entry: "./src/main.js",
+  output: {
+    path: undefined,
+    filename: "static/js/[name].[contenthash:10].js",
+    chunkFilename: "static/js/[name].[contenthash:10].chunk.js",
+    assetModuleFilename: "static/js/[hash:10][ext][query]",
+    clean: true,
+  },
+  module: {
+    rules: [
+      {
+        // 用来匹配 .css 结尾的文件
+        test: /\\.css$/,
+        // use 数组里面 Loader 执行顺序是从右到左
+        use: getStyleLoaders(),
+      },
+      {
+        test: /\\.less$/,
+        use: getStyleLoaders("less-loader"),
+      },
+      {
+        test: /\\.s[ac]ss$/,
+        use: getStyleLoaders("sass-loader"),
+      },
+      {
+        test: /\\.styl$/,
+        use: getStyleLoaders("stylus-loader"),
+      },
+      {
+        test: /\\.(png|jpe?g|gif|svg)$/,
+        type: "asset",
+        parser: {
+          dataUrlCondition: {
+            maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
+          },
+        },
+      },
+      {
+        test: /\\.(ttf|woff2?)$/,
+        type: "asset/resource",
+      },
+      {
+        test: /\\.(jsx|js)$/,
+        include: path.resolve(__dirname, "../src"),
+        loader: "babel-loader",
+        options: {
+          cacheDirectory: true,
+          cacheCompression: false,
+          plugins: [
+            // "@babel/plugin-transform-runtime" // presets中包含了
+          ],
+        },
+      },
+      // vue-loader不支持oneOf
+      {
+        test: /\\.vue$/,
+        loader: "vue-loader", // 内部会给vue文件注入HMR功能代码
+        options: {
+          // 开启缓存
+          cacheDirectory: path.resolve(
+            __dirname,
+            "node_modules/.cache/vue-loader"
+          ),
+        },
+      },
+    ],
+  },
+  plugins: [
+    new ESLintWebpackPlugin({
+      context: path.resolve(__dirname, "../src"),
+      exclude: "node_modules",
+      cache: true,
+      cacheLocation: path.resolve(
+        __dirname,
+        "../node_modules/.cache/.eslintcache"
+      ),
+    }),
+    new HtmlWebpackPlugin({
+      template: path.resolve(__dirname, "../public/index.html"),
+    }),
+    new CopyPlugin({
+      patterns: [
+        {
+          from: path.resolve(__dirname, "../public"),
+          to: path.resolve(__dirname, "../dist"),
+          toType: "dir",
+          noErrorOnMissing: true,
+          globOptions: {
+            ignore: ["**/index.html"],
+          },
+          info: {
+            minimized: true,
+          },
+        },
+      ],
+    }),
+    new MiniCssExtractPlugin({
+      filename: "static/css/[name].[contenthash:10].css",
+      chunkFilename: "static/css/[name].[contenthash:10].chunk.css",
+    }),
+    new VueLoaderPlugin(),
+    new DefinePlugin({
+      __VUE_OPTIONS_API__: "true",
+      __VUE_PROD_DEVTOOLS__: "false",
+    }),
+  ],
+  optimization: {
+    // 压缩的操作
+    minimizer: [
+      new CssMinimizerPlugin(),
+      new TerserWebpackPlugin(),
+      new ImageMinimizerPlugin({
+        minimizer: {
+          implementation: ImageMinimizerPlugin.imageminGenerate,
+          options: {
+            plugins: [
+              ["gifsicle", { interlaced: true }],
+              ["jpegtran", { progressive: true }],
+              ["optipng", { optimizationLevel: 5 }],
+              [
+                "svgo",
+                {
+                  plugins: [
+                    "preset-default",
+                    "prefixIds",
+                    {
+                      name: "sortAttrs",
+                      params: {
+                        xmlnsOrder: "alphabetical",
+                      },
+                    },
+                  ],
+                },
+              ],
+            ],
+          },
+        },
+      }),
+    ],
+    splitChunks: {
+      chunks: "all",
+    },
+    runtimeChunk: {
+      name: (entrypoint) => \`runtime~\${entrypoint.name}\`,
+    },
+  },
+  resolve: {
+    extensions: [".vue", ".js", ".json"],
+  },
+  mode: "production",
+  devtool: "source-map",
+};
+

其他配置

  • package.json
{
+  "name": "vue-cli",
+  "version": "1.0.0",
+  "description": "",
+  "main": "main.js",
+  "scripts": {
+    "start": "npm run dev",
+    "dev": "cross-env NODE_ENV=development webpack serve --config ./config/webpack.dev.js",
+    "build": "cross-env NODE_ENV=production webpack --config ./config/webpack.prod.js"
+  },
+  "keywords": [],
+  "author": "",
+  "license": "ISC",
+  "devDependencies": {
+    "@babel/core": "^7.17.10",
+    "@babel/eslint-parser": "^7.17.0",
+    "@vue/cli-plugin-babel": "^5.0.4",
+    "babel-loader": "^8.2.5",
+    "copy-webpack-plugin": "^10.2.4",
+    "cross-env": "^7.0.3",
+    "css-loader": "^6.7.1",
+    "css-minimizer-webpack-plugin": "^3.4.1",
+    "eslint-plugin-vue": "^8.7.1",
+    "eslint-webpack-plugin": "^3.1.1",
+    "html-webpack-plugin": "^5.5.0",
+    "image-minimizer-webpack-plugin": "^3.2.3",
+    "imagemin": "^8.0.1",
+    "imagemin-gifsicle": "^7.0.0",
+    "imagemin-jpegtran": "^7.0.0",
+    "imagemin-optipng": "^8.0.0",
+    "imagemin-svgo": "^10.0.1",
+    "less-loader": "^10.2.0",
+    "mini-css-extract-plugin": "^2.6.0",
+    "postcss-preset-env": "^7.5.0",
+    "sass-loader": "^12.6.0",
+    "stylus-loader": "^6.2.0",
+    "vue-loader": "^17.0.0",
+    "vue-style-loader": "^4.1.3",
+    "vue-template-compiler": "^2.6.14",
+    "webpack": "^5.72.0",
+    "webpack-cli": "^4.9.2",
+    "webpack-dev-server": "^4.9.0"
+  },
+  "dependencies": {
+    "vue": "^3.2.33",
+    "vue-router": "^4.0.15"
+  },
+  "browserslist": ["last 2 version", "> 1%", "not dead"]
+}
+
  • .eslintrc.js
module.exports = {
+  root: true,
+  env: {
+    node: true,
+  },
+  extends: ["plugin:vue/vue3-essential", "eslint:recommended"],
+  parserOptions: {
+    parser: "@babel/eslint-parser",
+  },
+};
+
  • babel.config.js
module.exports = {
+  presets: ["@vue/cli-plugin-babel/preset"],
+};
+

合并开发和生产配置

// webpack.config.js
+const path = require("path");
+const ESLintWebpackPlugin = require("eslint-webpack-plugin");
+const HtmlWebpackPlugin = require("html-webpack-plugin");
+const MiniCssExtractPlugin = require("mini-css-extract-plugin");
+const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
+const TerserWebpackPlugin = require("terser-webpack-plugin");
+const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
+const { VueLoaderPlugin } = require("vue-loader");
+const { DefinePlugin } = require("webpack");
+const CopyPlugin = require("copy-webpack-plugin");
+
+// 需要通过 cross-env 定义环境变量
+const isProduction = process.env.NODE_ENV === "production";
+
+const getStyleLoaders = (preProcessor) => {
+  return [
+    isProduction ? MiniCssExtractPlugin.loader : "vue-style-loader",
+    "css-loader",
+    {
+      loader: "postcss-loader",
+      options: {
+        postcssOptions: {
+          plugins: ["postcss-preset-env"],
+        },
+      },
+    },
+    preProcessor,
+  ].filter(Boolean);
+};
+
+module.exports = {
+  entry: "./src/main.js",
+  output: {
+    path: isProduction ? path.resolve(__dirname, "../dist") : undefined,
+    filename: isProduction
+      ? "static/js/[name].[contenthash:10].js"
+      : "static/js/[name].js",
+    chunkFilename: isProduction
+      ? "static/js/[name].[contenthash:10].chunk.js"
+      : "static/js/[name].chunk.js",
+    assetModuleFilename: "static/js/[hash:10][ext][query]",
+    clean: true,
+  },
+  module: {
+    rules: [
+      {
+        // 用来匹配 .css 结尾的文件
+        test: /\\.css$/,
+        // use 数组里面 Loader 执行顺序是从右到左
+        use: getStyleLoaders(),
+      },
+      {
+        test: /\\.less$/,
+        use: getStyleLoaders("less-loader"),
+      },
+      {
+        test: /\\.s[ac]ss$/,
+        use: getStyleLoaders("sass-loader"),
+      },
+      {
+        test: /\\.styl$/,
+        use: getStyleLoaders("stylus-loader"),
+      },
+      {
+        test: /\\.(png|jpe?g|gif|svg)$/,
+        type: "asset",
+        parser: {
+          dataUrlCondition: {
+            maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
+          },
+        },
+      },
+      {
+        test: /\\.(ttf|woff2?)$/,
+        type: "asset/resource",
+      },
+      {
+        test: /\\.(jsx|js)$/,
+        include: path.resolve(__dirname, "../src"),
+        loader: "babel-loader",
+        options: {
+          cacheDirectory: true,
+          cacheCompression: false,
+          plugins: [
+            // "@babel/plugin-transform-runtime" // presets中包含了
+          ],
+        },
+      },
+      // vue-loader不支持oneOf
+      {
+        test: /\\.vue$/,
+        loader: "vue-loader", // 内部会给vue文件注入HMR功能代码
+        options: {
+          // 开启缓存
+          cacheDirectory: path.resolve(
+            __dirname,
+            "node_modules/.cache/vue-loader"
+          ),
+        },
+      },
+    ],
+  },
+  plugins: [
+    new ESLintWebpackPlugin({
+      context: path.resolve(__dirname, "../src"),
+      exclude: "node_modules",
+      cache: true,
+      cacheLocation: path.resolve(
+        __dirname,
+        "../node_modules/.cache/.eslintcache"
+      ),
+    }),
+    new HtmlWebpackPlugin({
+      template: path.resolve(__dirname, "../public/index.html"),
+    }),
+    new CopyPlugin({
+      patterns: [
+        {
+          from: path.resolve(__dirname, "../public"),
+          to: path.resolve(__dirname, "../dist"),
+          toType: "dir",
+          noErrorOnMissing: true,
+          globOptions: {
+            ignore: ["**/index.html"],
+          },
+          info: {
+            minimized: true,
+          },
+        },
+      ],
+    }),
+    isProduction &&
+      new MiniCssExtractPlugin({
+        filename: "static/css/[name].[contenthash:10].css",
+        chunkFilename: "static/css/[name].[contenthash:10].chunk.css",
+      }),
+    new VueLoaderPlugin(),
+    new DefinePlugin({
+      __VUE_OPTIONS_API__: "true",
+      __VUE_PROD_DEVTOOLS__: "false",
+    }),
+  ].filter(Boolean),
+  optimization: {
+    minimize: isProduction,
+    // 压缩的操作
+    minimizer: [
+      new CssMinimizerPlugin(),
+      new TerserWebpackPlugin(),
+      new ImageMinimizerPlugin({
+        minimizer: {
+          implementation: ImageMinimizerPlugin.imageminGenerate,
+          options: {
+            plugins: [
+              ["gifsicle", { interlaced: true }],
+              ["jpegtran", { progressive: true }],
+              ["optipng", { optimizationLevel: 5 }],
+              [
+                "svgo",
+                {
+                  plugins: [
+                    "preset-default",
+                    "prefixIds",
+                    {
+                      name: "sortAttrs",
+                      params: {
+                        xmlnsOrder: "alphabetical",
+                      },
+                    },
+                  ],
+                },
+              ],
+            ],
+          },
+        },
+      }),
+    ],
+    splitChunks: {
+      chunks: "all",
+    },
+    runtimeChunk: {
+      name: (entrypoint) => \`runtime~\${entrypoint.name}\`,
+    },
+  },
+  resolve: {
+    extensions: [".vue", ".js", ".json"],
+  },
+  devServer: {
+    open: true,
+    host: "localhost",
+    port: 3000,
+    hot: true,
+    compress: true,
+    historyApiFallback: true, // 解决vue-router刷新404问题
+  },
+  mode: isProduction ? "production" : "development",
+  devtool: isProduction ? "source-map" : "cheap-module-source-map",
+};
+

优化配置

const path = require("path");
+const ESLintWebpackPlugin = require("eslint-webpack-plugin");
+const HtmlWebpackPlugin = require("html-webpack-plugin");
+const MiniCssExtractPlugin = require("mini-css-extract-plugin");
+const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
+const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
+const TerserWebpackPlugin = require("terser-webpack-plugin");
+const CopyPlugin = require("copy-webpack-plugin");
+const { VueLoaderPlugin } = require("vue-loader");
+const { DefinePlugin } = require("webpack");
+const AutoImport = require("unplugin-auto-import/webpack");
+const Components = require("unplugin-vue-components/webpack");
+const { ElementPlusResolver } = require("unplugin-vue-components/resolvers");
+// 需要通过 cross-env 定义环境变量
+const isProduction = process.env.NODE_ENV === "production";
+
+const getStyleLoaders = (preProcessor) => {
+  return [
+    isProduction ? MiniCssExtractPlugin.loader : "vue-style-loader",
+    "css-loader",
+    {
+      loader: "postcss-loader",
+      options: {
+        postcssOptions: {
+          plugins: ["postcss-preset-env"],
+        },
+      },
+    },
+    preProcessor && {
+      loader: preProcessor,
+      options:
+        preProcessor === "sass-loader"
+          ? {
+              // 自定义主题:自动引入我们定义的scss文件
+              additionalData: \`@use "@/styles/element/index.scss" as *;\`,
+            }
+          : {},
+    },
+  ].filter(Boolean);
+};
+
+module.exports = {
+  entry: "./src/main.js",
+  output: {
+    path: isProduction ? path.resolve(__dirname, "../dist") : undefined,
+    filename: isProduction
+      ? "static/js/[name].[contenthash:10].js"
+      : "static/js/[name].js",
+    chunkFilename: isProduction
+      ? "static/js/[name].[contenthash:10].chunk.js"
+      : "static/js/[name].chunk.js",
+    assetModuleFilename: "static/js/[hash:10][ext][query]",
+    clean: true,
+  },
+  module: {
+    rules: [
+      {
+        test: /\\.css$/,
+        use: getStyleLoaders(),
+      },
+      {
+        test: /\\.less$/,
+        use: getStyleLoaders("less-loader"),
+      },
+      {
+        test: /\\.s[ac]ss$/,
+        use: getStyleLoaders("sass-loader"),
+      },
+      {
+        test: /\\.styl$/,
+        use: getStyleLoaders("stylus-loader"),
+      },
+      {
+        test: /\\.(png|jpe?g|gif|svg)$/,
+        type: "asset",
+        parser: {
+          dataUrlCondition: {
+            maxSize: 10 * 1024,
+          },
+        },
+      },
+      {
+        test: /\\.(ttf|woff2?)$/,
+        type: "asset/resource",
+      },
+      {
+        test: /\\.(jsx|js)$/,
+        include: path.resolve(__dirname, "../src"),
+        loader: "babel-loader",
+        options: {
+          cacheDirectory: true,
+          cacheCompression: false,
+          plugins: [
+            // "@babel/plugin-transform-runtime" // presets中包含了
+          ],
+        },
+      },
+      // vue-loader不支持oneOf
+      {
+        test: /\\.vue$/,
+        loader: "vue-loader", // 内部会给vue文件注入HMR功能代码
+        options: {
+          // 开启缓存
+          cacheDirectory: path.resolve(
+            __dirname,
+            "node_modules/.cache/vue-loader"
+          ),
+        },
+      },
+    ],
+  },
+  plugins: [
+    new ESLintWebpackPlugin({
+      context: path.resolve(__dirname, "../src"),
+      exclude: "node_modules",
+      cache: true,
+      cacheLocation: path.resolve(
+        __dirname,
+        "../node_modules/.cache/.eslintcache"
+      ),
+    }),
+    new HtmlWebpackPlugin({
+      template: path.resolve(__dirname, "../public/index.html"),
+    }),
+    new CopyPlugin({
+      patterns: [
+        {
+          from: path.resolve(__dirname, "../public"),
+          to: path.resolve(__dirname, "../dist"),
+          toType: "dir",
+          noErrorOnMissing: true,
+          globOptions: {
+            ignore: ["**/index.html"],
+          },
+          info: {
+            minimized: true,
+          },
+        },
+      ],
+    }),
+    isProduction &&
+      new MiniCssExtractPlugin({
+        filename: "static/css/[name].[contenthash:10].css",
+        chunkFilename: "static/css/[name].[contenthash:10].chunk.css",
+      }),
+    new VueLoaderPlugin(),
+    new DefinePlugin({
+      __VUE_OPTIONS_API__: "true",
+      __VUE_PROD_DEVTOOLS__: "false",
+    }),
+    // 按需加载element-plus组件样式
+    AutoImport({
+      resolvers: [ElementPlusResolver()],
+    }),
+    Components({
+      resolvers: [
+        ElementPlusResolver({
+          importStyle: "sass", // 自定义主题
+        }),
+      ],
+    }),
+  ].filter(Boolean),
+  optimization: {
+    minimize: isProduction,
+    // 压缩的操作
+    minimizer: [
+      new CssMinimizerPlugin(),
+      new TerserWebpackPlugin(),
+      new ImageMinimizerPlugin({
+        minimizer: {
+          implementation: ImageMinimizerPlugin.imageminGenerate,
+          options: {
+            plugins: [
+              ["gifsicle", { interlaced: true }],
+              ["jpegtran", { progressive: true }],
+              ["optipng", { optimizationLevel: 5 }],
+              [
+                "svgo",
+                {
+                  plugins: [
+                    "preset-default",
+                    "prefixIds",
+                    {
+                      name: "sortAttrs",
+                      params: {
+                        xmlnsOrder: "alphabetical",
+                      },
+                    },
+                  ],
+                },
+              ],
+            ],
+          },
+        },
+      }),
+    ],
+    splitChunks: {
+      chunks: "all",
+      cacheGroups: {
+        // layouts通常是admin项目的主体布局组件,所有路由组件都要使用的
+        // 可以单独打包,从而复用
+        // 如果项目中没有,请删除
+        layouts: {
+          name: "layouts",
+          test: path.resolve(__dirname, "../src/layouts"),
+          priority: 40,
+        },
+        // 如果项目中使用element-plus,此时将所有node_modules打包在一起,那么打包输出文件会比较大。
+        // 所以我们将node_modules中比较大的模块单独打包,从而并行加载速度更好
+        // 如果项目中没有,请删除
+        elementUI: {
+          name: "chunk-elementPlus",
+          test: /[\\\\/]node_modules[\\\\/]_?element-plus(.*)/,
+          priority: 30,
+        },
+        // 将vue相关的库单独打包,减少node_modules的chunk体积。
+        vue: {
+          name: "vue",
+          test: /[\\\\/]node_modules[\\\\/]vue(.*)[\\\\/]/,
+          chunks: "initial",
+          priority: 20,
+        },
+        libs: {
+          name: "chunk-libs",
+          test: /[\\\\/]node_modules[\\\\/]/,
+          priority: 10, // 权重最低,优先考虑前面内容
+          chunks: "initial",
+        },
+      },
+    },
+    runtimeChunk: {
+      name: (entrypoint) => \`runtime~\${entrypoint.name}\`,
+    },
+  },
+  resolve: {
+    extensions: [".vue", ".js", ".json"],
+    alias: {
+      // 路径别名
+      "@": path.resolve(__dirname, "../src"),
+    },
+  },
+  devServer: {
+    open: true,
+    host: "localhost",
+    port: 3000,
+    hot: true,
+    compress: true,
+    historyApiFallback: true, // 解决vue-router刷新404问题
+  },
+  mode: isProduction ? "production" : "development",
+  devtool: isProduction ? "source-map" : "cheap-module-source-map",
+  performance: false,
+};
+










 
 
 















 
 
 
 
 
 
 
 
 
 
















































































































 
 
 
 
 
 
 
 
 
 
 





































 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 







 
 
 
 











 

`,16),o=[e];function l(c,i){return s(),a("div",null,o)}const u=n(t,[["render",l],["__file","vue-cli.html.vue"]]),k=JSON.parse('{"path":"/project/vue-cli.html","title":"Vue 脚手架","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"开发模式配置","slug":"开发模式配置","link":"#开发模式配置","children":[]},{"level":2,"title":"生产模式配置","slug":"生产模式配置","link":"#生产模式配置","children":[]},{"level":2,"title":"其他配置","slug":"其他配置","link":"#其他配置","children":[]},{"level":2,"title":"合并开发和生产配置","slug":"合并开发和生产配置","link":"#合并开发和生产配置","children":[]},{"level":2,"title":"优化配置","slug":"优化配置","link":"#优化配置","children":[]}],"filePathRelative":"project/vue-cli.md","git":{"createdTime":1715588813000,"updatedTime":1715588813000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":1}]},"readingTime":{"minutes":7.23,"words":2168}}');export{u as comp,k as data}; diff --git "a/assets/\344\272\214\345\210\206\346\237\245\346\211\276\360\237\215\260.html-CGobr-E8.js" "b/assets/\344\272\214\345\210\206\346\237\245\346\211\276\360\237\215\260.html-CGobr-E8.js" new file mode 100644 index 0000000..6194bb3 --- /dev/null +++ "b/assets/\344\272\214\345\210\206\346\237\245\346\211\276\360\237\215\260.html-CGobr-E8.js" @@ -0,0 +1,106 @@ +import{_ as n,o as s,c as a,e as p}from"./app-B-BkP2m_.js";const e={},t=p(`

二分查找🍰

35. 搜索插入位置

/**
+ * @param {number[]} nums
+ * @param {number} target
+ * @return {number}
+ */
+var searchInsert = function(nums, target) {
+    let left=0,right=nums.length-1
+    while(left<=right){
+        const mid=Math.floor((left+right)/2)
+        if(nums[mid]==target){
+            return mid
+        }else if(nums[mid]>target){
+            right=mid-1
+        }else{
+            left=mid+1
+        }
+    }
+    return left//left就是安插的那个点!!!
+};
+

34. 在排序数组中查找元素的第一个和最后一个位置

/**
+ * @param {number[]} nums
+ * @param {number} target
+ * @return {number[]}
+ */
+var searchRange = function(nums, target) {
+    let index=search(nums,target)
+    if(index==-1)return [-1,-1]
+
+    let left=index,rigth=index
+    while(nums[left]==target || nums[rigth]==target){
+        if(nums[left]==target)left--
+        if(nums[rigth]==target)rigth++
+    }
+    return [left+1,rigth-1]
+};
+
+//又是开始进行二分查找!!!
+const search=(nums,target)=>{
+    let low=0,high=nums.length
+    while(low<=high){
+        const mid=Math.floor((low+high)/2)
+        if(nums[mid]==target){
+            return mid
+        }else if(nums[mid]>target){
+            high=mid-1
+        }else{
+            low=mid+1
+        }
+    }
+    return -1
+}
+

69. x 的平方根

/**
+ * @param {number} x
+ * @return {number}
+ */
+var mySqrt = function (x) {
+    //用二分法进行求解:左闭右开区间
+    let low = 0, high = Math.ceil(x / 2)//优化方案点
+    while (low < high) {
+        const mid = Math.ceil((high + low) / 2)
+        const res = mid * mid
+        if (res == x) {
+            return mid
+        } else if (res > x) {
+            high = mid - 1
+        } else {
+            low = mid 
+        }
+    }
+    return low
+};
+

367. 有效的完全平方数

/**
+ * @param {number} num
+ * @return {boolean}
+ */
+var isPerfectSquare = function(num) {
+    //4=1+3 9=1+3+5 16=1+3+5+7以此类推,模仿它可以使用一个while循环,
+    // 不断减去一个从1开始不断增大的奇数,若最终减成了0,说明是完全平方数,否则,不是。
+    let num1=1;
+    while(num>0){
+        num-=num1
+        num1+=2
+    }
+    return num==0
+};
+
/**
+ * @param {number} num
+ * @return {boolean}
+ */
+var isPerfectSquare = function (num) {
+    //用二分法来进行求解:和上面的那个题目差不多!!!
+    let low = 0, high = Math.ceil(num / 2)
+    while (low < high) {
+        const mid = Math.ceil((low + high) / 2)
+        const res = mid * mid
+        if(res==num){
+            return true
+        }else if(res<num){
+            low=mid
+        }else{
+            high=mid-1
+        }
+    }
+    return false
+};
+
`,10),o=[t];function c(l,i){return s(),a("div",null,o)}const r=n(e,[["render",c],["__file","二分查找🍰.html.vue"]]),k=JSON.parse('{"path":"/algorithm/%E4%BA%8C%E5%88%86%E6%9F%A5%E6%89%BE%F0%9F%8D%B0.html","title":"二分查找🍰","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"35. 搜索插入位置","slug":"_35-搜索插入位置","link":"#_35-搜索插入位置","children":[]},{"level":2,"title":"34. 在排序数组中查找元素的第一个和最后一个位置","slug":"_34-在排序数组中查找元素的第一个和最后一个位置","link":"#_34-在排序数组中查找元素的第一个和最后一个位置","children":[]},{"level":2,"title":"69. x 的平方根","slug":"_69-x-的平方根","link":"#_69-x-的平方根","children":[]},{"level":2,"title":"367. 有效的完全平方数","slug":"_367-有效的完全平方数","link":"#_367-有效的完全平方数","children":[]}],"filePathRelative":"algorithm/二分查找🍰.md","git":{"createdTime":1715588813000,"updatedTime":1715588813000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":1}]},"readingTime":{"minutes":1.48,"words":444}}');export{r as comp,k as data}; diff --git "a/assets/\344\272\214\345\217\211\346\240\221\360\237\215\210.html-mIpyovbq.js" "b/assets/\344\272\214\345\217\211\346\240\221\360\237\215\210.html-mIpyovbq.js" new file mode 100644 index 0000000..a595e0f --- /dev/null +++ "b/assets/\344\272\214\345\217\211\346\240\221\360\237\215\210.html-mIpyovbq.js" @@ -0,0 +1,533 @@ +import{_ as n,o as s,c as a,e as t}from"./app-B-BkP2m_.js";const e={},p=t(`

二叉树🍈

94. 二叉树的中序遍历

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {number[]}
+ */
+var inorderTraversal = function (root) {
+    const result = []
+    const traverse = (root) => {
+        if (root == null) return;
+        traverse(root.left)
+        result.push(root.val)
+        traverse(root.right)
+    }
+    traverse(root)
+    return result
+};
+

104. 二叉树的最大深度

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {number}
+ */
+var maxDepth = function(root) {
+    if(root==null)return 0
+    return Math.max(maxDepth(root.left),maxDepth(root.right))+1
+};
+

226. 翻转二叉树

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {TreeNode}
+ */
+var invertTree = function(root) {
+    //判断不存在直接返回
+    if(root==null)return root
+    //存在进行下面的处理
+    const tmp=root.left
+    root.left=root.right
+    root.right=tmp
+    //遍历
+    invertTree(root.left)
+    invertTree(root.right)
+
+    return root
+};
+

101. 对称二叉树

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {boolean}
+ */
+var isSymmetric = function(root) {
+    return dfs(root.left,root.right)
+};
+
+function dfs(left,right){
+    if(left==null && right==null)return true //都没有
+    if(left==null || right==null)return false //只有一个
+    if(left.val!=right.val)return false //两者都有
+    return dfs(left.left,right.right) && dfs(left.right,right.left)
+}   
+

543. 二叉树的直径

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {number}
+ */
+var diameterOfBinaryTree = function(root) {
+    let maxLen=0
+    // 二叉树最大深度的变种!
+    const maxline=(root)=>{
+        if(root==null)return 0
+        const left=maxline(root.left)
+        const right=maxline(root.right)
+        maxLen=Math.max(maxLen,right+left)
+        return Math.max(left,right)+1
+    }
+    maxline(root)
+    return maxLen
+};
+

102. 二叉树的层序遍历

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {number[][]}
+ */
+var levelOrder = function (root) {
+    const queue = [root], res = [] //是类似队列的操作
+    if(root==null)return res// 注意这里还有一个条件判断
+  
+    while (queue.length) {
+        const len = queue.length
+        const arr = []
+        for (let i = 0; i < len; i++) {
+            const node = queue.shift()
+            arr.push(node.val)
+            if (node.left) {
+                queue.push(node.left)
+            }
+            if (node.right) {
+                queue.push(node.right)
+            }
+        }
+        res.push(arr)
+    }
+    return res
+};
+

108. 将有序数组转换为二叉搜索树

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {number[]} nums
+ * @return {TreeNode}
+ */
+var sortedArrayToBST = function (nums) {
+    // 注意这个函数带上的两个参数
+    function buildTree(low, high) {
+        if (low > high) return null//注意这里的终止条件
+        // 下面进行树的生成
+        const mid = Math.floor((low + high) / 2)
+        const root = new TreeNode(nums[mid])
+        root.left = buildTree(low, mid - 1)
+        root.right = buildTree(mid + 1, high)
+        return root
+    }
+    return buildTree(0, nums.length - 1)
+};
+

98. 验证二叉搜索树

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {boolean}
+ */
+var isValidBST = function(root) {
+    //中序遍历求解!!!
+    // 二叉搜索树「中序遍历」得到的值构成的序列一定是升序的,
+    // 这启示我们在中序遍历的时候实时检查当前节点的值是否大于前一个中序遍历到的节点的值即可。
+    let stack = [];
+    let inorder = -Infinity;
+
+    while (stack.length || root !== null) {
+        // 入栈节点
+        while (root !== null) {
+            stack.push(root);
+            root = root.left;
+        }
+        root = stack.pop();
+        // 如果中序遍历得到的节点的值小于等于前一个 inorder,说明不是二叉搜索树
+        if (root.val <= inorder) {
+            return false;
+        }
+        inorder = root.val;
+        root = root.right;
+    }
+    return true;
+};
+

Error! 没有考虑子树的所有节点都必须大于或小于根节点

例如:[5,4,6,null,null,3,7],这样的做法只是考虑在两层间的对比!

var isValidBST = function (root) {
+    if (root == null) return true
+    if ((root.left != null && root.val <= root.left.val) 
+        || (root.right != null && root.right.val <= root.val)) {
+        return false
+    }
+    return isValidBST(root.left) && isValidBST(root.right)
+};
+

另一种解题思路:先遍历收集在进行对比

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {boolean}
+ */
+var isValidBST = function (root) {
+    //直接可以遍历出来再进行对比
+    const res = []
+    const travel = (root) => {
+        if (root == null) return
+        travel(root.left)
+        res.push(root.val)
+        travel(root.right)
+    }
+    travel(root)
+    let flag = true;
+    for (let i = 1; i < res.length; i++) {
+        if (res[i - 1] >= res[i]) flag = false
+    }
+    return flag
+};
+

230. 二叉搜索树中第K小的元素

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @param {number} k
+ * @return {number}
+ */
+var kthSmallest = function (root, k) {
+    //中序遍历找第k个元素
+    let i = 0, value
+    const travel = (root) => {
+        if (root == null) return
+        travel(root.left)
+        if (++i == k) {
+            value = root.val;
+            return
+        }
+        travel(root.right)
+    }
+    travel(root)
+    return value
+};
+

199. 二叉树的右视图

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {number[]}
+ */
+var rightSideView = function (root) {
+    return levelOrder(root)
+};
+
+
+function levelOrder(root) {
+    let res = [], stack = [root]
+    if (root == null) return []
+
+    while (stack.length) {
+        let arr = []
+        let len = stack.length
+        for (let i = 0; i < len; i++) {
+            const node = stack.shift()
+            arr.push(node.val)
+            if (node.left) {
+                stack.push(node.left)
+            }
+            if (node.right) {
+                stack.push(node.right)
+            }
+        }
+        //这里直接push最后一个就行了!!!
+        res.push(arr[arr.length-1])
+    }
+    return res
+}
+

103. 二叉树的锯齿形层序遍历

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {number[][]}
+ */
+var zigzagLevelOrder = function (root) {
+    if(root==null)return []//注意这里的一个条件判断!!!
+
+    const res = [], queue = [root]
+    let order = true
+    while (queue.length) {
+        let arr = []
+        let len = queue.length
+        for (let i = 0; i < len; i++) {
+            const node = queue.shift()
+            if (order) {
+                arr.push(node.val)
+            } else {
+                arr.unshift(node.val)
+            }
+            if (node.left) {
+                queue.push(node.left)
+            }
+            if (node.right) {
+                queue.push(node.right)
+            }
+        }
+        res.push(arr)
+        order = !order
+    }
+    return res
+};
+

114. 二叉树展开为链表

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {void} Do not return anything, modify root in-place instead.
+ */
+var flatten = function(root) {
+    let list=[]
+    travel(root,list)
+    for(let i=1;i<list.length;i++){
+        const prev=list[i-1],cur=list[i]
+        prev.left=null
+        prev.right=cur
+    }
+};
+
+function travel(root,list){
+    //先序遍历进行收集!!!
+    if(root==null)return 
+    list.push(root)
+    travel(root.left,list)
+    travel(root.right,list)
+}
+

105. 从前序与中序遍历序列构造二叉树

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {number[]} preorder
+ * @param {number[]} inorder
+ * @return {TreeNode}
+ */
+// 优化方案
+var buildTree = function (preorder, inorder) {
+    const helper = (p_start, p_end, i_start, i_end) => {
+        if (p_start > p_end) return null
+        const rootVal = preorder[p_start]//根节点的值
+        const root = new TreeNode(rootVal)//根节点
+        const mid = inorder.indexOf(rootVal)//根节点在
+        let leftNum = mid - i_start //左子树的节点数
+        root.left = helper(p_start + 1, p_start + leftNum, i_start, mid - 1)
+        root.right = helper(p_start + leftNum + 1, p_end, mid + 1, i_end)
+        return root
+    }
+    return helper(0, preorder.length - 1, 0, inorder.length - 1)
+};
+
+
+//第一种写法
+function buildTree(preorder, inorder) {
+    if (preorder.length == 0 || inorder.length == 0) return null
+    const root = new TreeNode(preorder[0])
+    const mid = inorder.indexOf(preorder[0])
+    root.left = buildTree(preorder.slice(1, mid + 1), inorder.slice(0, mid))
+    root.right = buildTree(preorder.slice(mid + 1), inorder.slice(mid + 1))
+    return root
+}
+

437. 路径总和 III

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @param {number} targetSum
+ * @return {number}
+ */
+var pathSum = function(root, targetSum) {
+    let ans=0
+    const map=new Map()
+    dfs(root,0)
+    return ans
+// 前缀和定义
+// 用它干什么
+// HashMap存的是什么
+// 恢复状态代码的意义:题目中可以拿 node 值为5的节点来说
+
+    function dfs(root,preSum){
+        if(root==null)return
+        let target=preSum+root.val
+        map.set(preSum,(map.get(preSum)||0)+1)
+        ans+=(map.get(target-targetSum)||0)
+
+        dfs(root.left,target)
+        dfs(root.right,target)
+
+        map.set(preSum,map.get(preSum)-1)
+    }
+};
+

236. 二叉树的最近公共祖先

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val) {
+ *     this.val = val;
+ *     this.left = this.right = null;
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @param {TreeNode} p
+ * @param {TreeNode} q
+ * @return {TreeNode}
+ */
+var lowestCommonAncestor = function(root, p, q) {
+    
+    const travel=(root,p,q)=>{
+        if(root==null ||root==p ||root==q)return root
+        let left=travel(root.left,p,q)
+        let right=travel(root.right,p,q)
+        
+        // 后续遍历中进行处理!需要进行往上返回!
+        if(left!=null &&right!=null)return root
+        if(left==null)return right
+        if(right==null)return left
+        
+    }
+    
+    return travel(root,p,q)
+};
+

124. 二叉树中的最大路径和

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} root
+ * @return {number}
+ */
+const maxPathSum = (root) => {
+    let maxSum = Number.MIN_SAFE_INTEGER; // 最大路径和
+
+    const dfs = (root) => {
+        if (root == null) { // 遍历到null节点,收益0
+           return 0;
+        }
+        const left = dfs(root.left);   // 左子树提供的最大路径和
+        const right = dfs(root.right); // 右子树提供的最大路径和
+
+        const innerMaxSum = left + root.val + right; // 当前子树内部的最大路径和
+        maxSum = Math.max(maxSum, innerMaxSum);      // 挑战最大纪录
+
+        const outputMaxSum = root.val + Math.max( left, right); // 当前子树对外提供的最大和
+
+        // 如果对外提供的路径和为负,直接返回0。否则正常返回
+        return outputMaxSum < 0 ? 0 : outputMaxSum;
+    };
+
+    dfs(root);  // 递归的入口
+
+    return maxSum; 
+};
+

100. 相同的树

/**
+ * Definition for a binary tree node.
+ * function TreeNode(val, left, right) {
+ *     this.val = (val===undefined ? 0 : val)
+ *     this.left = (left===undefined ? null : left)
+ *     this.right = (right===undefined ? null : right)
+ * }
+ */
+/**
+ * @param {TreeNode} p
+ * @param {TreeNode} q
+ * @return {boolean}
+ */
+//如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。
+var isSameTree = function (p, q) {
+    if (p == null && q == null) return true
+    if (p == null || q == null) return false
+    if (p.val != q.val) return false
+    return isSameTree(p.left, q.left) && isSameTree(p.right, q.right)
+};
+
`,40),o=[p];function c(l,i){return s(),a("div",null,o)}const r=n(e,[["render",c],["__file","二叉树🍈.html.vue"]]),k=JSON.parse('{"path":"/algorithm/%E4%BA%8C%E5%8F%89%E6%A0%91%F0%9F%8D%88.html","title":"二叉树🍈","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"94. 二叉树的中序遍历","slug":"_94-二叉树的中序遍历","link":"#_94-二叉树的中序遍历","children":[]},{"level":2,"title":"104. 二叉树的最大深度","slug":"_104-二叉树的最大深度","link":"#_104-二叉树的最大深度","children":[]},{"level":2,"title":"226. 翻转二叉树","slug":"_226-翻转二叉树","link":"#_226-翻转二叉树","children":[]},{"level":2,"title":"101. 对称二叉树","slug":"_101-对称二叉树","link":"#_101-对称二叉树","children":[]},{"level":2,"title":"543. 二叉树的直径","slug":"_543-二叉树的直径","link":"#_543-二叉树的直径","children":[]},{"level":2,"title":"102. 二叉树的层序遍历","slug":"_102-二叉树的层序遍历","link":"#_102-二叉树的层序遍历","children":[]},{"level":2,"title":"108. 将有序数组转换为二叉搜索树","slug":"_108-将有序数组转换为二叉搜索树","link":"#_108-将有序数组转换为二叉搜索树","children":[]},{"level":2,"title":"98. 验证二叉搜索树","slug":"_98-验证二叉搜索树","link":"#_98-验证二叉搜索树","children":[]},{"level":2,"title":"230. 二叉搜索树中第K小的元素","slug":"_230-二叉搜索树中第k小的元素","link":"#_230-二叉搜索树中第k小的元素","children":[]},{"level":2,"title":"199. 二叉树的右视图","slug":"_199-二叉树的右视图","link":"#_199-二叉树的右视图","children":[]},{"level":2,"title":"103. 二叉树的锯齿形层序遍历","slug":"_103-二叉树的锯齿形层序遍历","link":"#_103-二叉树的锯齿形层序遍历","children":[]},{"level":2,"title":"114. 二叉树展开为链表","slug":"_114-二叉树展开为链表","link":"#_114-二叉树展开为链表","children":[]},{"level":2,"title":"105. 从前序与中序遍历序列构造二叉树","slug":"_105-从前序与中序遍历序列构造二叉树","link":"#_105-从前序与中序遍历序列构造二叉树","children":[]},{"level":2,"title":"437. 路径总和 III","slug":"_437-路径总和-iii","link":"#_437-路径总和-iii","children":[]},{"level":2,"title":"236. 二叉树的最近公共祖先","slug":"_236-二叉树的最近公共祖先","link":"#_236-二叉树的最近公共祖先","children":[]},{"level":2,"title":"124. 二叉树中的最大路径和","slug":"_124-二叉树中的最大路径和","link":"#_124-二叉树中的最大路径和","children":[]},{"level":2,"title":"100. 相同的树","slug":"_100-相同的树","link":"#_100-相同的树","children":[]}],"filePathRelative":"algorithm/二叉树🍈.md","git":{"createdTime":1715588813000,"updatedTime":1716560799000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":2}]},"readingTime":{"minutes":7.25,"words":2176}}');export{r as comp,k as data}; diff --git "a/assets/\345\211\215\347\253\257\346\200\247\350\203\275\344\274\230\345\214\226.html-COZTg7ue.js" "b/assets/\345\211\215\347\253\257\346\200\247\350\203\275\344\274\230\345\214\226.html-COZTg7ue.js" new file mode 100644 index 0000000..359743f --- /dev/null +++ "b/assets/\345\211\215\347\253\257\346\200\247\350\203\275\344\274\230\345\214\226.html-COZTg7ue.js" @@ -0,0 +1,56 @@ +import{_ as n,o as a,c as s,e}from"./app-B-BkP2m_.js";const p={},t=e(`

前端性能优化

性能

1.重要性:

关注前端可以很好地提高性能。如果我们可以将后端响应时间缩短一半,整体响应时间只能减少 5%~10%。而如果关注前端性能,同样是将其响应时间减少一半,则整体响应时间可以减少 40%~45%。

改进前端通常只需要较少的时间和资源,减少后端延迟会带来很大的改动。

只有 10%~20%的最终用户响应时间花在了下载 HTML 文档上,其余的 80%~90%时间花在了下载页面中的所有组件上。

2.定位:

2.1 技术上的选择

在前端日常开发中,技术上的选择是非常重要的。为什么要讲这个呢?因为现象频发。

前端工程化严重的当下,轻量化的框架慢慢被遗忘掉了。并不是所有的业务场景都适合使用工程化框架,react/vue 并不轻量。

复杂的框架是为了解决复杂的业务

如果研发 h5、PC 展示等场景简单的业务时候,javascript 原生 配合一些轻量化插件更适合。

多页面应用也并不都是缺点。根据业务不同而选择不一样的技术是非常重要的,是每个前端都应该反思的事情。

这方面是导致卡顿的关键问题。

2.2 NetWork

我们的老朋友 NetWork 想必前端同学都很熟悉。我们先来看一下 network 面板 NetWork 从面板上我们可以看出一些信息:

  • 请求资源 size
  • 请求资源时长
  • 请求资源数量
  • 接口响应时长
  • 接口发起数量
  • 接口报文 size
  • 接口响应状态
  • 瀑布图

瀑布图是什么呢?

瀑布图就是上方图片后面的 waterfall 纵列

瀑布图是一个级联图, 展示了浏览器如何加载资源并渲染成网页. 图中的每一行都是一次单独的浏览器请求. 这个图越长, 说明加载网页过程中所发的请求越多. 每一行的宽度, 代表浏览器发出请求并下载该资源的过程中所耗费的时间。它的侧重点在于分析网路链路

瀑布图颜色说明:

  • DNS Lookup [深绿色] - 在浏览器和服务器进行通信之前, 必须经过 DNS 查询, 将域名转换成 IP 地址. 在这个阶段, 你可以处理的东西很少. 但幸运的是, 并非所有的请求都需要经过这一阶段.

  • Initial Connection [橙色] - 在浏览器发送请求之前, 必须建立 TCP 连接. 这个过程仅仅发生在瀑布图中的开头几行, 否则这就是个性能问题(后边细说).

  • SSL/TLS Negotiation [紫色] - 如果你的页面是通过 SSL/TLS 这类安全协议加载资源, 这段时间就是浏览器建立安全连接的过程. 目前 Google 将 HTTPS 作为其 搜索排名因素 之一, SSL/TLS 协商的使用变得越来越普遍了.

  • Time To First Byte (TTFB) [绿色] - TTFB 是浏览器请求发送到服务器的时间+服务器处理请求时间+响应报文的第一字节到达浏览器的时间. 我们用这个指标来判断你的 web 服务器是否性能不够, 或者说你是否需要使用 CDN.

  • Downloading (蓝色) - 这是浏览器用来下载资源所用的时间. 这段时间越长, 说明资源越大. 理想情况下, 你可以通过控制资源的大小来控制这段时间的长度.

那么除了瀑布图的长度外,我们如何才能判断一个瀑布图的状态是健康的呢?

  • 首先, 减少所有资源的加载时间. 亦即减小瀑布图的宽度. 瀑布图越窄, 网站的访问速度越快.

  • 其次, 减少请求数量 也就是降低瀑布图的高度. 瀑布图越矮越好.

  • 最后, 通过优化资源请求顺序来加快渲染时间. 从图上看, 就是将绿色的"开始渲染"线向左移. 这条线向左移动的越远越好.

这样,我们就可以从 network 的角度去排查“慢”的问题。

2.3 webpack-bundle-analyzer

项目构建后生成的 bundle 包是压缩后的。webpack-bundle-analyzer 是一款包分析工具。

我们先来看一下它能带来的效果。如下图: 打包分析

从上图来看,我们的 bundle 包被解析的一览无余。其中模块面积占的越大说明在 bundle 包中 size 越大。就值得注意了,重点优化一下。

它能够排查出来的信息有

显示包中所有打入的模块 显示模块 size 及 gzip 后的 size 排查包中的模块情形是非常有必要的,通过 webpack-bundle-analyzer 来排查出一些无用的模块,过大的模块。然后进行优化。以减少我们的 bundle 包 size,减少加载时长。

安装

# NPM
+npm install --save-dev webpack-bundle-analyzer
+# Yarn
+yarn add -D webpack-bundle-analyzer
+

使用(as a Webpack-Plugin)

const BundleAnalyzerPlugin =
+  require("webpack-bundle-analyzer").BundleAnalyzerPlugin;
+
+module.exports = {
+  plugins: [new BundleAnalyzerPlugin()],
+};
+

然后构建包完毕后会自动弹出一个窗口展示上图信息。

2.4 Performance

chrome 自带的 performance 模块。先附上一个官网文档传送门:Performance

可以检测很多方面的数据,多数情况的性能排查上用的比较多。如果想要深入了解的同学建议去看一下官方文档。

接下来我们来说一下在 performance 面板中如何排差“慢”的问题,它给我们提供了哪些信息呢。先附上一张 performance 的面板图片。 Performance 从上图中可以分析出一些指标

  • FCP/LCP 时间是否过长?
  • 请求并发情况 是否并发频繁?
  • 请求发起顺序 请求发起顺序是否不对?
  • javascript 执行情况 javascript 执行是否过慢?

这些指标就是我们需要重点关注的,当然 performance 的功能并不止于此。

先记住如何获取到这些指标,后面来一一进行解析优化。

2.5 PerformanceNavigationTiming

获取各个阶段的响应时间,我们所要用到的接口是 PerformanceNavigationTiming 接口。

PerformanceNavigationTiming 提供了用于存储和检索有关浏览器文档事件的指标的方法和属性。 例如,此接口可用于确定加载或卸载文档需要多少时间。

function showNavigationDetails() {
+  const [entry] = performance.getEntriesByType("navigation");
+  console.table(entry.toJSON());
+}
+

使用这个函数,我们就可以获取各个阶段的响应时间,如图: hh 参数说明

  • navigationStart 加载起始时间
  • redirectStart 重定向开始时间(如果发生了 HTTP 重定向,每次重定向都和当前文档同域的话,就返回开始重定向的 fetchStart 的值。其他情况,则返回 0)
  • redirectEnd 重定向结束时间(如果发生了 HTTP 重定向,每次重定向都和当前文档同域的话,就返回最后一次重定向接受完数据的时间。其他情况则返回 0)
  • fetchStart 浏览器发起资源请求时,如果有缓存,则返回读取缓存的开始时间
  • domainLookupStart 查询 DNS 的开始时间。如果请求没有发起 DNS 请求,如 keep-alive,缓存等,则返回 fetchStart
  • domainLookupEnd 查询 DNS 的结束时间。如果没有发起 DNS 请求,同上
  • connectStart 开始建立 TCP 请求的时间。如果请求是 keep-alive,缓存等,则返回 domainLookupEnd
  • (secureConnectionStart) 如果在进行 TLS 或 SSL,则返回握手时间
  • connectEnd 完成 TCP 链接的时间。如果是 keep-alive,缓存等,同 connectStart
  • requestStart 发起请求的时间
  • responseStart 服务器开始响应的时间
  • domLoading 从图中看是开始渲染 dom 的时间,具体未知
  • domInteractive 未知
  • domContentLoadedEventStart 开始触发 DomContentLoadedEvent 事件的时间
  • domContentLoadedEventEnd DomContentLoadedEvent 事件结束的时间
  • domComplete 从图中看是 dom 渲染完成时间,具体未知
  • loadEventStart 触发 load 的时间,如没有则返回 0
  • loadEventEnd load 事件执行完的时间,如没有则返回 0
  • unloadEventStart unload 事件触发的时间
  • unloadEventEnd unload 事件执行完的时间

关于我们的 Web 性能,我们会用到的时间参数:

  • DNS 解析时间: domainLookupEnd - domainLookupStart
  • TCP 建立连接时间: connectEnd - connectStart
  • 白屏时间: responseStart - navigationStart
  • dom 渲染完成时间: domContentLoadedEventEnd - navigationStart
  • 页面 onload 时间: loadEventEnd - navigationStart

根据这些时间参数,我们就可以判断哪一阶段对性能有影响。

2.6 抓包

有一些业务状况是没有上述的一些调试工具该怎么办呢?我们可以利用抓包工具进行对页面信息对抓取,上述我们通过 chrome 工具排查出来的指标,也可以通过抓包工具进行抓取。

这里我推荐一款抓包工具 charles。

2.7 性能测试工具

  • Pingdom
  • Load Impact
  • WebPage Test
  • Octa Gate Site Timer
  • Free Speed Test

3.优化:

前端的优化种类繁多,主要包含三个方面的优化:网络优化(对加载时所消耗的网络资源优化),代码优化(资源加载完后,脚本解释执行的速度),框架优化(选择性能较好的框架,比如 benchmark)。

3.1 tree shaking

中文(摇树),webpack 构建优化中重要一环。摇树用于清除我们项目中的一些无用代码,它依赖于 ES 中的模块语法。

比如日常使用 lodash 的时候

import _ from "lodash";
+

如果如上引用 lodash 库,在构建包的时候是会把整个 lodash 包打入到我们的 bundle 包中的。

import _isEmpty from "lodash/isEmpty";
+

如果如上引用 lodash 库,在构建包的时候只会把 isEmpty 这个方法抽离出来再打入到我们的 bundle 包中。

这样的化就会大大减少我们包的 size。所以在日常引用第三方库的时候,需要注意导入的方式。

如何开启摇树

在 webpack4.x 中默认对 tree-shaking 进行了支持。 在 webpack2.x 中使用 tree-shaking:传送门

3.2 split chunks

中文(分包)

在没配置任何东西的情况下,webpack 4 就智能的帮你做了代码分包。入口文件依赖的文件都被打包进了 main.js,那些大于 30kb 的第三方包,如:echarts、xlsx、dropzone 等都被单独打包成了一个个独立 bundle。

其它被我们设置了异步加载的页面或者组件变成了一个个 chunk,也就是被打包成独立的 bundle。

它内置的代码分割策略是这样的:

  • 新的 chunk 是否被共享或者是来自 node_modules 的模块
  • 新的 chunk 体积在压缩之前是否大于 30kb
  • 按需加载 chunk 的并发请求数量小于等于 5 个
  • 页面初始加载时的并发请求数量小于等于 3 个

大家可以根据自己的项目环境来更改配置。配置代码如下:

splitChunks({
+  cacheGroups: {
+    vendors: {
+      name: \`chunk-vendors\`,
+      test: /[\\\\/]node_modules[\\\\/]/,
+      priority: -10,
+      chunks: "initial",
+    },
+    dll: {
+      name: \`chunk-dll\`,
+      test: /[\\\\/]bizcharts|[\\\\/]\\@antv[\\\\/]data-set/,
+      priority: 15,
+      chunks: "all",
+      reuseExistingChunk: true,
+    },
+    common: {
+      name: \`chunk-common\`,
+      minChunks: 2,
+      priority: -20,
+      chunks: "all",
+      reuseExistingChunk: true,
+    },
+  },
+});
+

没有使用 webpack4.x 版本的项目,依然可以通过按需加载的形式进行分包,使得我们的包分散开,提升加载性能。

按需加载也是以前分包的重要手段之一

这里推荐一篇非常好的文章:webpack 如何使用按需加载

3.3 拆包

与 3.2 的分包不同。大家可能没发现,上面 2.3 的 bundle 包解析中有个有趣的现象,上面项目的技术栈是 react,但是 bundle 包中并没有 react、react-dom、react-router 等。

因为把这些插件“拆”开了。并没有一起打在 bundle 中。而是放在了 CDN 上。下面我举一个例子来解释一下。

假设:原本 bundle 包为 2M,一次请求拉取。拆分为 bundle(1M) + react 桶(CDN)(1M) 两次请求并发拉取。

从这个角度来看,1+1 的模式拉取资源更快。

换一个角度来说,全量部署项目的情况,每次部署 bundle 包都将重新拉取。比较浪费资源。react 桶的方式可以命中强缓存,这样的化,就算全量部署也只需要重新拉取左侧 1M 的 bundle 包即可,节省了服务器资源。优化了加载速度。

注意:在本地开发过程中,react 等资源建议不要引入 CDN,开发过程中刷新频繁,会增加 CDN 服务其压力,走本地就好。

3.4 gzip

服务端配置 gzip 压缩后可大大缩减资源大小。

Nginx 配置方式

http {
+  gzip on;
+  gzip_buffers 32 4K;
+  gzip_comp_level 6;
+  gzip_min_length 100;
+  gzip_types application/javascript text/css text/xml;
+  gzip_disable "MSIE [1-6]\\.";
+  gzip_vary on;
+}
+

配置完成后在 response header 中可以查看。

3.5 图片压缩

开发中比较重要的一个环节,我司自己的图床工具是自带压缩功能的,压缩后直接上传到 CDN 上。

如果公司没有图床工具,我们该如何压缩图片呢?我推荐几种我常用的方式

  • 智图压缩 (百度很难搜到官网了,免费、批量、好用)
  • tinypng(免费、批量、速度块)
  • fireworks 工具压缩像素点和尺寸 (自己动手,掌握尺度)
  • 找 UI 压缩后发给你

图片压缩是常用的手法,因为设备像素点的关系,UI 给予的图片一般都是 x2,x4 的,所以压缩就非常有必要。

3.6 图片分割

如果页面中有一张效果图,比如真机渲染图,UI 手拿着刀不让你压缩。这时候不妨考虑一下分割图片。

建议单张土图片的大小不要超过 100k,我们在分割完图片后,通过布局再拼接在一起。可以图片加载效率。

这里注意一点,分割后的每张图片一定要给 height,否则网速慢的情况下样式会塌陷。

3.7 sprite

南方叫精灵图,北方叫雪碧图。这个现象就很有趣。

在网站中有很多小图片的时候,一定要把这些小图片合并为一张大的图片,然后通过 background 分割到需要展示的图片。

这样的好处是什么呢?先来普及一个规则

浏览器请求资源的时候,同源域名请求资源的时候有最大并发限制,chrome 为 6 个,就比如你的页面上有 10 个相同 CDN 域名小图片,那么需要发起 10 次请求去拉取,分两次并发。第一次并发请求回来后,发起第二次并发。

如果你把 10 个小图片合并为一张大图片的画,那么只用一次请求即可拉取下来 10 个小图片的资源。减少服务器压力,减少并发,减少请求次数。

附上一个 sprite 的例子。 雪碧

3.8 CDN

中文(内容分发网络),服务器是中心化的,CDN 是“去中心化的”。

在项目中有很多东西都是放在 CDN 上的,比如:静态文件,音频,视频,js 资源,图片。那么为什么用 CDN 会让资源加载变快呢?

举个简单的例子:

以前买火车票大家都只能去火车站买,后来我们买火车票就可以在楼下的火车票代售点买了。

你细品。

所以静态资源度建议放在 CDN 上,可以加快资源加载的速度。

3.9 懒加载

懒加载也叫延迟加载,指的是在长网页中延迟加载图像,是一种非常好的优化网页性能的方式。

当可视区域没有滚到资源需要加载的地方时候,可视区域外的资源就不会加载。

可以减少服务器负载,常适用于图片很多,页面较长的业务场景中。

如何使用懒加载呢?

图片懒加载 layzr.js

3.10 iconfont

中文(字体图表),现在比较流行的一种用法。使用字体图表有几种好处

  • 矢量
  • 轻量
  • 易修改
  • 不占用图片资源请求。

就像上面说的雪碧图,如果都用字体图标来替换的画,一次请求都免了,可以直接打到 bundle 包中。

使用前提是 UI 给点力,设计趋向于字体图标,提前给好资源,建立好字体图标库。

3.11 逻辑后移

逻辑后移是一种比较常见的优化手段。用一个打开文章网站的操作来举个例子。

没有逻辑后移处理的请求顺序是这个样子的 first 页面的展示主体是文章展示,如果文章展示的请求靠后了,那么渲染文章出来的时间必然靠后,因为有可能因为请求阻塞等情况,影响请求响应情况,如果超过一次并发的情况的话,会更加的慢。如图的这种情况也是在我们项目中发生过的。

很明显我们应该把主体“请求文章”接口前移,把一些非主体的请求逻辑后移。这样的话可以尽快的把主体渲染出来,就会快很多。

优化后的顺序是这个样子的。 second 在平常的开发中建议时常注意逻辑后移的情况,突出主体逻辑。可以极大的提升用户体验。

3.12 算法复杂度

在数据量大的应用场景中,需要着重注意算法复杂度问题。

在这个方面可以参考 Javascript 算法之复杂度分析这篇文章。

如上面 Performance 解析出的 Javascript 执行指标上,可以推测出来你的 code 执行效率如何,如果执行时间过长就要考虑一下是否要优化一下复杂度了。

在时间换空间,空间换时间的选择上,要根据业务场景来进行取舍。

3.13 组件渲染

拿 react 举例,组件分割方面不要太深。需要控制组件的渲染,尤其是深层组件的 render。

老生常谈的话题,我们可以一些方式来优化组件渲染

  • 声明周期控制 - 比如 react 的 shouldComponentUpdate 来控制组件渲染。
  • 官网提供的 api- PureComponent
  • 控制注入组件的参数
  • 分配组件唯一 key
  • 没有必要的渲染是对性能的极大浪费。

3.14 node middleware

中文(node 中间件)

中间件主要是指封装所有 Http 请求细节处理的方法。一次 Http 请求通常包含很多工作,如记录日志、ip 过滤、查询字符串、请求体解析、Cookie 处理、权限验证、参数验证、异常处理等,但对于 Web 应用而言,并不希望接触到这么多细节性的处理,因此引入中间件来简化和隔离这些基础设施与业务逻辑之间的细节,让我们能够关注在业务的开发上,以达到提升开发效率的目的。

使用 node middleware 合并请求。减少请求次数。这种方式也是非常实用的。

3.15 web worker

Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。这样的好处是,一些计算密集型或高延迟的任务,被 Worker 线程负担了,主线程(通常负责 UI 交互)就会很流畅,不会被阻塞或拖慢。

合理实用 web worker 可以优化复杂计算任务。这里直接抛阮一峰的入门文章:传送门

3.16 缓存

缓存的原理就是更快读写的存储介质+减少 IO+减少 CPU 计算=性能优化。而性能优化的第一定律就是:优先考虑使用缓存。

缓存的主要手段有:浏览器缓存、CDN、反向代理、本地缓存、分布式缓存、数据库缓存。

3.17 GPU 渲染

每个网页或多或少都涉及到一些 CSS 动画,通常简单的动画对于性能的影响微乎其微,然而如果涉及到稍显复杂的动画,不当的处理方式会使性能问题变得十分突出。

像 Chrome, FireFox, Safari, IE9+和最新版本的 Opera 都支持 GPU 加速,当它们检测到页面中某个 DOM 元素应用了某些 CSS 规则时就会开启。

虽然我们可能不想对元素应用 3D 变换,可我们一样可以开启 3D 引擎。例如我们可以用 transform: translateZ(0) 来开启 GPU 加速 。

只对我们需要实现动画效果的元素应用以上方法,如果仅仅为了开启硬件加速而随便乱用,那是不合理的。

3.18 Ajax 可缓存

Ajax 在发送的数据成功后,为了提高页面的响应速度和用户体验,会把请求的 URL 和返回的响应结果保存在缓存内,当下一次调用 Ajax 发送相同的请求(URL 和参数完全相同)时,它就会直接从缓存中拿数据。

在进行 Ajax 请求的时候,可以选择尽量使用 get 方法,这样可以使用客户端的缓存,提高请求速度。

3.19 Resource Hints

Resource Hints(资源预加载)是非常好的一种性能优化方法,可以大大降低页面加载时间,给用户更加流畅的用户体验。

现代浏览器使用大量预测优化技术来预测用户行为和意图,这些技术有预连接、资源与获取、资源预渲染等。

Resource Hints 的思路有如下两个:

  • 当前将要获取资源的列表
  • 通过当前页面或应用的状态、用户历史行为或 session 预测用户行为及必需的资源

实现 Resource Hints 的方法有很多种,可分为基于 link 标签的 DNS-prefetch、subresource、preload、 prefetch、preconnect、prerender,和本地存储 localStorage。

3.20 SSR

渲染过程在服务器端完成,最终的渲染结果 HTML 页面通过 HTTP 协议发送给客户端,又被认为是‘同构'或‘通用',如果你的项目有大量的 detail 页面,相互特别频繁,建议选择服务端渲染。

服务端渲染(SSR)除了 SEO 还有很多时候用作首屏优化,加快首屏速度,提高用户体验。但是对服务器有要求,网络传输数据量大,占用部分服务器运算资源。

Vue 的 Nuxt.js 和 React 的 next.js 都是服务端渲染的方法。

3.21 UNPKG

UNPKG 是一个提供 npm 包进行 CDN 加速的站点,因此,可以将一些比较固定了依赖写入 html 模版中,从而提高网页的性能。首先,需要将这些依赖声明为 external,以便 webpack 打包时不从 node_modules 中加载这些资源,配置如下:

externals: { 'react': 'React' }
+

其次,你需要将所依赖的资源写在 html 模版中,这一步需要用到 html-webpack-plugin。下面是一段示例:

<% if (htmlWebpackPlugin.options.node_env === 'development') { %>
+  <script src="https://unpkg.com/react@16.7.0/umd/react.development.js"></script>
+<% } else { %>
+  <script src="https://unpkg.com/react@16.7.0/umd/react.production.min.js"></script>
+<% } %>
+

这段代码需要注入 node_env,以便在开发的时候能够获得更友好的错误提示。也可以选择一些比较自动的库,来帮助我们完成这个过程,比如 webpack-cdn-plugin,或者 dynamic-cdn-webpack-plugin。

4.总结:

还有一些比较常用的优化方法我没有列举出来,例如将样式表放在顶部,将脚本放在底部,减少重绘,按需加载,模块化等。方法很多,对症下药才是关键。

借鉴了很多大佬最后总结出来的文章,希望自己和同为菜鸟的小伙伴可以永远怀着一颗学徒的心。

`,177),l=[t];function i(o,r){return a(),s("div",null,l)}const d=n(p,[["render",i],["__file","前端性能优化.html.vue"]]),u=JSON.parse('{"path":"/advance/%E5%89%8D%E7%AB%AF%E6%80%A7%E8%83%BD%E4%BC%98%E5%8C%96.html","title":"前端性能优化","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"1.重要性:","slug":"_1-重要性","link":"#_1-重要性","children":[]},{"level":2,"title":"2.定位:","slug":"_2-定位","link":"#_2-定位","children":[{"level":3,"title":"2.1 技术上的选择","slug":"_2-1-技术上的选择","link":"#_2-1-技术上的选择","children":[]},{"level":3,"title":"2.2 NetWork","slug":"_2-2-network","link":"#_2-2-network","children":[]},{"level":3,"title":"2.3 webpack-bundle-analyzer","slug":"_2-3-webpack-bundle-analyzer","link":"#_2-3-webpack-bundle-analyzer","children":[]},{"level":3,"title":"2.4 Performance","slug":"_2-4-performance","link":"#_2-4-performance","children":[]},{"level":3,"title":"2.5 PerformanceNavigationTiming","slug":"_2-5-performancenavigationtiming","link":"#_2-5-performancenavigationtiming","children":[]},{"level":3,"title":"2.6 抓包","slug":"_2-6-抓包","link":"#_2-6-抓包","children":[]},{"level":3,"title":"2.7 性能测试工具","slug":"_2-7-性能测试工具","link":"#_2-7-性能测试工具","children":[]}]},{"level":2,"title":"3.优化:","slug":"_3-优化","link":"#_3-优化","children":[{"level":3,"title":"3.1 tree shaking","slug":"_3-1-tree-shaking","link":"#_3-1-tree-shaking","children":[]},{"level":3,"title":"3.2 split chunks","slug":"_3-2-split-chunks","link":"#_3-2-split-chunks","children":[]},{"level":3,"title":"3.3 拆包","slug":"_3-3-拆包","link":"#_3-3-拆包","children":[]},{"level":3,"title":"3.4 gzip","slug":"_3-4-gzip","link":"#_3-4-gzip","children":[]},{"level":3,"title":"3.5 图片压缩","slug":"_3-5-图片压缩","link":"#_3-5-图片压缩","children":[]},{"level":3,"title":"3.6 图片分割","slug":"_3-6-图片分割","link":"#_3-6-图片分割","children":[]},{"level":3,"title":"3.7 sprite","slug":"_3-7-sprite","link":"#_3-7-sprite","children":[]},{"level":3,"title":"3.8 CDN","slug":"_3-8-cdn","link":"#_3-8-cdn","children":[]},{"level":3,"title":"3.9 懒加载","slug":"_3-9-懒加载","link":"#_3-9-懒加载","children":[]},{"level":3,"title":"3.10 iconfont","slug":"_3-10-iconfont","link":"#_3-10-iconfont","children":[]},{"level":3,"title":"3.11 逻辑后移","slug":"_3-11-逻辑后移","link":"#_3-11-逻辑后移","children":[]},{"level":3,"title":"3.12 算法复杂度","slug":"_3-12-算法复杂度","link":"#_3-12-算法复杂度","children":[]},{"level":3,"title":"3.13 组件渲染","slug":"_3-13-组件渲染","link":"#_3-13-组件渲染","children":[]},{"level":3,"title":"3.14 node middleware","slug":"_3-14-node-middleware","link":"#_3-14-node-middleware","children":[]},{"level":3,"title":"3.15 web worker","slug":"_3-15-web-worker","link":"#_3-15-web-worker","children":[]},{"level":3,"title":"3.16 缓存","slug":"_3-16-缓存","link":"#_3-16-缓存","children":[]},{"level":3,"title":"3.17 GPU 渲染","slug":"_3-17-gpu-渲染","link":"#_3-17-gpu-渲染","children":[]},{"level":3,"title":"3.18 Ajax 可缓存","slug":"_3-18-ajax-可缓存","link":"#_3-18-ajax-可缓存","children":[]},{"level":3,"title":"3.19 Resource Hints","slug":"_3-19-resource-hints","link":"#_3-19-resource-hints","children":[]},{"level":3,"title":"3.20 SSR","slug":"_3-20-ssr","link":"#_3-20-ssr","children":[]},{"level":3,"title":"3.21 UNPKG","slug":"_3-21-unpkg","link":"#_3-21-unpkg","children":[]}]},{"level":2,"title":"4.总结:","slug":"_4-总结","link":"#_4-总结","children":[]}],"filePathRelative":"advance/前端性能优化.md","git":{"createdTime":1715955258000,"updatedTime":1715955258000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":1}]},"readingTime":{"minutes":20.19,"words":6058}}');export{d as comp,u as data}; diff --git "a/assets/\345\211\215\347\253\257\350\267\257\347\224\261\347\232\204\345\256\236\347\216\260\345\216\237\347\220\206.html-Cpum1TjA.js" "b/assets/\345\211\215\347\253\257\350\267\257\347\224\261\347\232\204\345\256\236\347\216\260\345\216\237\347\220\206.html-Cpum1TjA.js" new file mode 100644 index 0000000..68516cf --- /dev/null +++ "b/assets/\345\211\215\347\253\257\350\267\257\347\224\261\347\232\204\345\256\236\347\216\260\345\216\237\347\220\206.html-Cpum1TjA.js" @@ -0,0 +1,511 @@ +import{_ as n,o as s,c as a,e as t}from"./app-B-BkP2m_.js";const p={},o=t(`

前端路由的实现原理

基本的原理先看看

  • hash模式: hashchange + location.hash
  • history模式: history对象
<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>hash路由</title>
+  </head>
+  <body>
+    <!-- hash路由 -->
+
+    <a href="#/a">跳转 A 页面</a>
+    <a href="#/b">跳转 B 页面</a>
+    <div id="box" style="border: 10px solid #000; height: 200px"></div>
+
+    <button onclick="to('/a')">跳转到 A路由</button>
+    <button onclick="to('/b')">跳转到 B路由</button>
+    <button onclick="to('/c')">跳转到 C路由</button>
+    <script>
+      let box = document.getElementById("box");
+      window.addEventListener("hashchange", function (e) {
+        //hashchange
+        box.innerHTML = location.hash;
+        console.log(e);
+      });
+
+      function to(path) {
+        box.innerHTML = path;
+        history.pushState({}, null, path);
+      }
+    </script>
+  </body>
+</html>
+

在 HTML 文档中,history.pushState() 方法向浏览器的会话历史栈增加了一个条目。

该方法是异步的。为 popstate 事件增加监听器,以确定导航何时完成。state 参数将在其中可用。

语法

pushState(state, unused)
+pushState(state, unused, url)
+

参数

state
state 对象是一个 JavaScript 对象,其与通过 pushState() 创建的新历史条目相关联。每当用户导航到新的 state,都会触发 popstate 事件,并且该事件的 state 属性包含历史条目 state 对象的副本。
state 对象可以是任何可以序列化的对象。因为 Firefox 将 state 对象保存到用户的磁盘上,以便用户重启浏览器可以恢复,我们对 state 对象序列化的表示施加了 16 MiB 的限制。如果你传递的 state 对象的序列化表示超出了 pushState() 可接受的大小,该方法将抛出异常。如果你需要更多的空间,建议使用 sessionStorage 和/或 localStorage。

unused
由于历史原因,该参数存在且不能忽略;传递一个空字符串是安全的,以防将来对该方法进行更改。

url 可选
新历史条目的 URL。请注意,浏览器不会在调用 pushState() 之后尝试加载该 URL,但是它可能会在以后尝试加载该 URL,例如,在用户重启浏览器之后。新 URL 可以不是绝对路径;如果它是相对的,它将相对于当前的 URL 进行解析。新的 URL 必须与当前 URL 同源;否则,pushState() 将抛出异常。如果该参数没有指定,则将其设置为当前文档的 URL。


在正式开始看路由的实现之前,先来看看自定义元素和自定义事件

自定义事件

<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>自定义事件</title>
+  </head>
+  <body>
+    <h1>用一个构造函数 CustomEvent</h1>
+    <script>
+      // 创建自定义事件
+      const catFound = new CustomEvent("animalfound", {
+        detail: {
+          name: "猫",
+        },
+      });
+      const dogFound = new CustomEvent("animalfound", {
+        detail: {
+          name: "狗",
+        },
+      });
+
+      // 添加合适的事件监听器
+      window.addEventListener("animalfound", (e) => console.log(e.detail.name));
+
+      // 触发事件
+      window.dispatchEvent(catFound);
+      window.dispatchEvent(dogFound);
+
+      // 控制台中输出“猫”和“狗” 
+    </script>
+  </body>
+</html>
+

自定义元素

自定义内置元素

<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Document</title>
+  </head>
+  <body>
+    <!-- 注意这个 is  !!!! -->
+    <p is="word-count">xiaoyu</p>
+    <script>
+      // Create a class for the element
+      class WordCount extends HTMLParagraphElement {
+        constructor() {
+          // Always call super first in constructor
+          super();
+
+          // count words in element's parent element
+          var wcParent = this.parentNode;
+          console.log(wcParent);
+
+          function countWords(node) {
+            var text = node.innerText || node.textContent;
+            return text.length;
+          }
+
+          var count = "Words: " + countWords(wcParent);
+
+          // Create a shadow root
+          var shadow = this.attachShadow({ mode: "open" });
+
+          // Create text node and add word count to it
+          var text = document.createElement("span");
+          text.textContent = count;
+
+          // Append it to the shadow root
+          shadow.appendChild(text);
+
+          // Update count when element content changes
+          setInterval(function () {
+            var count = "Words: " + countWords(wcParent);
+            text.textContent = count;
+          }, 200);
+        }
+      }
+
+      // Define the new element
+      customElements.define("word-count", WordCount, { extends: "p" });
+
+      let ctor = customElements.get("word-count");
+      console.log(ctor);//获取构造函数
+      console.log(customElements.getName(WordCount) === "word-count");//比较
+    </script>
+  </body>
+</html>
+

自主定义元素

<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>自定义元素</title>
+    <style>
+        body{
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            min-height: 80vh;
+        }
+    </style>
+  </head>
+  <body>
+    <popup-info
+    img="../../../exer/photo/图片1.png"
+    text="Your card validation code (CVC) is an extra
+                                      security feature — it is the last 3 or 4
+                                      numbers on the back of your card."></popup-info>
+  
+    <script>
+      // Create a class for the element
+      // 自主定制元素的构造函数必须扩展HTMLElement。@@@@!!!!
+      class PopUpInfo extends HTMLElement {
+        constructor() {
+          // Always call super first in constructor
+          super();
+
+          // Create a shadow root
+        //   Element.attachShadow() 方法给指定的元素挂载一个 Shadow DOM,并且返回对 ShadowRoot 的引用。@@@@!!!
+          var shadow = this.attachShadow({ mode: "open" });
+        //   console.log(this.shadowRoot);
+        //   console.log(shadow);
+
+          // Create spans
+          var wrapper = document.createElement("span");
+          wrapper.setAttribute("class", "wrapper");
+          var icon = document.createElement("span");
+          icon.setAttribute("class", "icon");
+          icon.setAttribute("tabindex", 0);
+          var info = document.createElement("span");
+          info.setAttribute("class", "info");
+
+          // Take attribute content and put it inside the info span
+          var text = this.getAttribute("text");
+          info.textContent = text;
+
+          // Insert icon
+          var imgUrl;
+          if (this.hasAttribute("img")) {
+            imgUrl = this.getAttribute("img");
+          } else {
+            imgUrl = "img/default.png";
+          }
+          var img = document.createElement("img");
+          img.src = imgUrl;
+          icon.appendChild(img);
+
+          // Create some CSS to apply to the shadow dom
+          var style = document.createElement("style");
+
+          style.textContent =
+            ".wrapper {" +
+            "position: relative;" +
+            "}" +
+            ".info {" +
+            "font-size: 0.8rem;" +
+            "width: 200px;" +
+            "display: inline-block;" +
+            "border: 1px solid black;" +
+            "padding: 10px;" +
+            "background: white;" +
+            "border-radius: 10px;" +
+            "opacity: 0;" +
+            "transition: 0.6s all;" +
+            "position: absolute;" +
+            "bottom: 20px;" +
+            "left: 10px;" +
+            "z-index: 3;" +
+            "}" +
+            "img {" +
+            "width: 1.2rem" +
+            "}" +
+            ".icon:hover + .info, .icon:focus + .info {" +
+            "opacity: 1;" +
+            "}";
+
+          // attach the created elements to the shadow dom
+
+          shadow.appendChild(style);
+          shadow.appendChild(wrapper);
+          wrapper.appendChild(icon);
+          wrapper.appendChild(info);
+        }
+      }
+
+      // Define the new element
+      customElements.define("popup-info", PopUpInfo);
+    </script>
+  </body>
+</html>
+

Vue-router 的实现

路由界面文件

<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Document</title>
+    <style>
+      .c-link{
+        background-color: yellow;
+        width: 100px;
+        line-height: 40px;
+        margin: 20px;
+        cursor: pointer;
+        display: inline-block;
+        text-align: center;
+      }
+      .c-link:active{
+        transform: scale(0.8);
+      }
+    </style>
+  </head>
+  <body>
+    <div class="product-item">测试的产品</div>
+    <h3>原始路径在页面加载时写到了你的剪切板上!!!意味着你可以直接在URL地址栏进行粘贴</h1>
+    <div class="flex">
+      <ul class="menu-x">
+        <c-link to="/" class="c-link">首页</c-link>
+        <c-link to="/about" class="c-link">关于</c-link>
+      </ul>
+    </div>
+    <div>
+      <c-router>
+        <c-route path="/" component="home" default></c-route>
+        <c-route path="/detail/:id" component="detail"></c-route>
+        <c-route path="/about" component="about"></c-route>
+      </c-router>
+    </div>
+    <!-- 记录开始渲染的地址 -->
+    <script>
+      navigator.clipboard.writeText(location.href)
+    </script>
+
+    <script src="./router.js"></script>
+  </body>
+</html>
+

router.js

const oriPushState = history.pushState;
+/* 
+不借助第三方工具库实现路由,我们需要思考以下几个问题:
+如何实现自定义标签,如vue的<router-view>,React的<Router>
+如何实现业务组件
+如何动态切换路由
+*/
+
+/* 如果想监听 pushState 和 replaceState 行为,可以通过在方法里面主动去触发 popstate 事件,
+另一种是重写history.pushState,通过创建自己的eventedPushState自定义事件,并手动派发,实际使用过程中就可以监听了。 */
+// 重写pushState
+history.pushState = function (state, title, url) {
+    // 触发原事件
+    oriPushState.apply(history, [state, title, url]);
+    // 自定义事件
+    var event = new CustomEvent("c-popstate", {
+        detail: {
+            state,
+            title,
+            url
+        }
+    });
+    //触发这个事件
+    window.dispatchEvent(event);
+}
+
+// <c-link to="/" class="c-link">首页</c-link>
+class CustomLink extends HTMLElement {
+    connectedCallback() {
+        this.addEventListener("click", ev => {
+            ev.preventDefault();
+            const to = this.getAttribute("to");
+            // 更新浏览历史记录
+            history.pushState("", "", to);
+        })
+    }
+}
+window.customElements.define("c-link", CustomLink);
+
+// 优先于c-router注册
+// <c-toute path="/" component="home" default></c-toute>
+class CustomRoute extends HTMLElement {
+    #data = null;
+    getData() {
+        return {
+            default: this.hasAttribute("default"),
+            path: this.getAttribute("path"),
+            component: this.getAttribute("component")
+        }
+    }
+}
+window.customElements.define("c-route", CustomRoute);
+
+// 容器组件
+class CustomComponent extends HTMLElement {
+    async connectedCallback() {
+        // 获取组件的path,即html的路径
+        const strPath = this.getAttribute("path");
+        // 加载html
+        const cInfos = await loadComponent(strPath);
+        const shadow = this.attachShadow({ mode: "closed" });
+        // 添加html对应的内容
+        this.#addElement(shadow, cInfos);
+    }
+    #addElement(shadow, info) {
+        // 添加模板内容
+        if (info.template) {
+            shadow.appendChild(info.template.content.cloneNode(true));
+        }
+        // 添加脚本
+        if (info.script) {
+            // 防止全局污染,并获得根节点
+            var fun = new Function(\`\${info.script.textContent}\`);
+            // 绑定脚本的this为当前的影子根节点
+            fun.bind(shadow)();
+        }
+        // 添加样式
+        if (info.style) {
+            shadow.appendChild(info.style);
+        }
+    }
+}
+window.customElements.define("c-component", CustomComponent);
+
+// <c-router></c-router>
+class CustomRouter extends HTMLElement {
+    #routes
+    connectedCallback() {
+        const routeNodes = this.querySelectorAll("c-route");
+
+        // 获取子节点的路由信息
+        this.#routes = Array.from(routeNodes).map(node => node.getData());
+        // 查找默认的路由
+        const defaultRoute = this.#routes.find(r => r.default) || this.#routes[0];
+        // 渲染对应的路由
+        this.#onRenderRoute(defaultRoute);
+        // 监听路由变化
+        this.#listenerHistory();
+    }
+
+    // 渲染路由对应的内容
+    #onRenderRoute(route) {
+        var el = document.createElement("c-component");
+        el.setAttribute("path", \`/\${route.component}\`);
+        el.id = "_route_";
+        this.append(el);
+    }
+
+    // 卸载路由清理工作
+    #onUploadRoute(route) {
+        this.removeChild(this.querySelector("#_route_"));
+    }
+
+    // 监听路由变化
+    #listenerHistory() {
+        // 导航的路由切换
+        window.addEventListener("popstate", ev => {
+            console.log("onpopstate:", ev);
+            const url = location.pathname.endsWith(".html") ? "/" : location.pathname;
+            const route = this.#getRoute(this.#routes, url);
+            console.log(route);
+            this.#onUploadRoute();
+            this.#onRenderRoute(route);
+        });
+        // pushStat或replaceSate
+        window.addEventListener("c-popstate", ev => {
+            console.log("c-popstate:", ev);
+            const detail = ev.detail;
+            const route = this.#getRoute(this.#routes, detail.url);
+            this.#onUploadRoute();
+            this.#onRenderRoute(route);
+        })
+    }
+
+    // 路由查找
+    #getRoute(routes, url) {
+        console.log(routes,url);
+        return routes.find(function (r) {
+            const path = r.path;
+            const strPaths = path.split('/');
+            const strUrlPaths = url.split("/");
+            //注意这里有点关键!!!
+            let match = true;
+            for (let i = 0; i < strPaths.length; i++) {
+                if (strPaths[i].startsWith(":")) {
+                    continue;
+                }
+                match = strPaths[i] === strUrlPaths[i];
+                if (!match) {
+                    break;
+                }
+            }
+            return match;
+        })
+    }
+}
+window.customElements.define("c-router", CustomRouter);
+
+// 动态加载组件并解析
+async function loadComponent(path) {
+    const defaultPath="http://localhost:5000"
+    this.caches = this.caches || {};
+    // 缓存存在,直接返回
+    if (this.caches[path]) {
+        return this.caches[path];
+    }
+    console.log(path);
+    const res = await fetch(defaultPath+path).then(res => res.text());
+    console.log(res);
+    // 利用DOMParser校验
+    // DOMParser 可以将存储在字符串中的 XML 或 HTML 源代码解析为一个 DOM Document。
+    const parser = new DOMParser();
+    const doc = parser.parseFromString(res, "text/html");
+    // 解析模板,脚本,样式
+    const template = doc.querySelector("template");
+    const script = doc.querySelector("script");
+    const style = doc.querySelector("style");
+    // 缓存内容
+    this.caches[path] = {
+        template,
+        script,
+        style
+    }
+    return this.caches[path];
+}
+

pages文件夹中的页面文件,模仿远程的文件
about.html

<template>
+    About Me!
+</template>
+

detail.html

<template>
+    <div>商品详情</div>
+    <div id="detail">
+        商品ID:<span id="product-id" class="product-id"></span>
+    </div>
+</template>
+
+<script>
+    this.querySelector("#product-id").textContent=history.state.id;
+</script>
+
+<style>
+    .product-id{
+        color:red;
+    }
+</style>
+

home.html

<template>
+    <div>商品清单</div>
+    <div id="product-list">
+        <div>
+            <a data-id="10" class="product-item c-link">香蕉</a>
+        </div>
+        <div>
+            <a data-id="11" class="product-item c-link">苹果</a>
+        </div>
+        <div>
+            <a data-id="12" class="product-item c-link">葡萄</a>
+        </div>
+    </div>
+</template>
+
+<script>
+    let container = this.querySelector("#product-list");
+    // 触发历史更新
+    // 事件代理
+    container.addEventListener("click", function (ev) {
+        console.log("item clicked");
+        if (ev.target.classList.contains("product-item")) {
+            const id = +ev.target.dataset.id;
+            history.pushState({
+                    id
+            }, "", \`/detail/\${id}\`)
+        }
+    })
+</script>
+
+<style>
+    .product-item {
+        cursor: pointer;
+        color: blue;
+    }
+</style>
+
`,32),e=[o];function c(l,u){return s(),a("div",null,e)}const k=n(p,[["render",c],["__file","前端路由的实现原理.html.vue"]]),r=JSON.parse('{"path":"/advance/%E5%89%8D%E7%AB%AF%E8%B7%AF%E7%94%B1%E7%9A%84%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86.html","title":"前端路由的实现原理","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"基本的原理先看看","slug":"基本的原理先看看","link":"#基本的原理先看看","children":[]},{"level":2,"title":"自定义事件","slug":"自定义事件","link":"#自定义事件","children":[]},{"level":2,"title":"自定义元素","slug":"自定义元素","link":"#自定义元素","children":[{"level":3,"title":"自定义内置元素","slug":"自定义内置元素","link":"#自定义内置元素","children":[]},{"level":3,"title":"自主定义元素","slug":"自主定义元素","link":"#自主定义元素","children":[]}]},{"level":2,"title":"Vue-router 的实现","slug":"vue-router-的实现","link":"#vue-router-的实现","children":[]}],"filePathRelative":"advance/前端路由的实现原理.md","git":{"createdTime":1715941349000,"updatedTime":1716636128000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":2}]},"readingTime":{"minutes":7.77,"words":2331}}');export{k as comp,r as data}; diff --git "a/assets/\345\212\250\346\200\201\350\247\204\345\210\222\360\237\215\223.html-DaiZxN1P.js" "b/assets/\345\212\250\346\200\201\350\247\204\345\210\222\360\237\215\223.html-DaiZxN1P.js" new file mode 100644 index 0000000..a5890a6 --- /dev/null +++ "b/assets/\345\212\250\346\200\201\350\247\204\345\210\222\360\237\215\223.html-DaiZxN1P.js" @@ -0,0 +1,159 @@ +import{_ as n,o as s,c as a,e as p}from"./app-B-BkP2m_.js";const t={},o=p(`

动态规划🍓

70.爬楼梯

/**
+ * @param {number} n
+ * @return {number}
+ */
+var climbStairs = function (n) {
+    const dp=[1,2]
+    for(let i=2;i<n;i++){
+        dp[i]=dp[i-1]+dp[i-2]
+    }
+    return dp[n-1]
+};
+

118.杨辉三角

/**
+ * @param {number} numRows
+ * @return {number[][]}
+ */
+var generate = function (numRows) {
+    // 打印输出一个杨辉三角
+    const dp = Array.from({ length: numRows }, () => new Array(numRows).fill(1))
+    for (let i = 0; i < numRows; i++) {
+        for (let j = 0; j < numRows; j++) {
+            if (j > 0 && j < i) {
+                dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1]
+            }
+        }
+    }
+    //进行push进去
+    let res = []
+    for (let i = 0; i < numRows; i++) {
+        res.push(dp[i].slice(0, i + 1))
+    }
+    return res
+};
+

198.打家劫舍

/**
+ * @param {number[]} nums
+ * @return {number}
+ */
+var rob = function (nums) {
+    //如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
+    let len = nums.length
+    if (len <= 2) return Math.max.apply(null, nums)
+    let max = nums[0];
+    let M = nums[0]
+    let dp = [nums[0], nums[1]]
+    if (max < dp[1]) max = dp[1]
+    for (let i = 2; i < len; i++) {
+        //这个M表示间隔两位以上的最大数
+        if (dp[i - 2] > M) M = dp[i - 2]
+        dp[i] = M + nums[i]
+        if (max < dp[i]) max = dp[i]
+    }
+    return max
+};
+

279.完全平方数

/**
+ * @param {number} n
+ * @return {number}
+ */
+var numSquares = function (n) {
+    //用动态规划求解
+    let dp = new Array(n + 1).fill(0)//其实主要是初始化dp[0]
+    //每一个对应的位置最大可以是本身1+1+1……
+    for (let i = 1; i <= n; i++) {
+        dp[i] = i;//每次都将当前数字先更新为最大的结果,最坏的结果
+        //这里的j是平方数的底子
+        for (let j = 1; i - j * j >= 0; j++) {
+            dp[i] = Math.min(dp[i], dp[i - j * j] + 1)
+        }
+    }
+    return dp[n]
+};
+

322.零钱兑换

/**
+ * @param {number[]} coins
+ * @param {number} amount
+ * @return {number}
+ */
+var coinChange = function(coins, amount) {
+    // 定义dp数组
+    let dp=new Array(amount+1).fill(Infinity)
+    dp[0]=0
+    //注意两层for循环的遍历,分别遍历的是啥?
+    for(let i=0;i<coins.length;i++){
+        for(let j=coins[i];j<=amount;j++){
+            dp[j]=Math.min(dp[j],dp[j-coins[i]]+1)
+        }
+    }
+    if(dp[amount]==Infinity)return -1
+    return dp[amount]
+};
+

300.最长递增子序列

/**
+ * @param {number[]} nums
+ * @return {number}
+ */
+var lengthOfLIS = function(nums) {
+    let n=nums.length;
+    let dp=new Array(n).fill(1)
+    for(let i=1;i<n;i++){
+        for(let j=0;j<i;j++){
+            if(nums[i]>nums[j]){
+                dp[i]=Math.max(dp[i],dp[j]+1)
+            }
+        }
+    }
+    return Math.max.apply(null,dp)
+};
+

416.分割等和子集

/**
+ * @param {number[]} nums
+ * @return {boolean}
+ */
+var canPartition = function(nums) {
+    // 显然是0-1背包问题
+    let n=nums.length
+    let target=nums.reduce((p,v)=>p+v,0)/2
+    if(!Number.isInteger(target))return false
+    let dp=new Array(target+1).fill(0)
+    for(let i=0;i<n;i++){
+        for(let j=target;j>=nums[i];j--){
+            dp[j]=Math.max(dp[j],dp[j-nums[i]]+nums[i])
+        }
+    }
+    return dp[target]==target
+};
+

62.不同路径

/**
+ * @param {number} m
+ * @param {number} n
+ * @return {number}
+ */
+var uniquePaths = function(m, n) {
+    let dp=Array.from({length:m},()=>new Array(n).fill(1))
+    for(let i=1;i<m;i++){
+        for(let j=1;j<n;j++){
+            dp[i][j]=dp[i][j-1]+dp[i-1][j]
+        }
+    }
+    return dp[m-1][n-1]
+};
+

64.最小路径

/**
+ * @param {number[][]} grid
+ * @return {number}
+ */
+var minPathSum = function(grid) {
+    let m=grid.length
+    let n=grid[0].length
+    let dp=Array.from({length:m},()=>new Array(n))
+    dp[0][0]=grid[0][0]
+    for(let i=0;i<m;i++){
+        for(let j=0;j<n;j++){
+            if(i==0 && j>0){
+                dp[i][j]=dp[i][j-1]+grid[i][j]
+            }
+            if(j==0 && i>0){
+                dp[i][j]=dp[i-1][j]+grid[i][j]
+            }
+            if(j>0 && i>0){
+                dp[i][j]=Math.min(dp[i-1][j],dp[i][j-1])+grid[i][j]
+            }
+        }
+    }
+    return dp[m-1][n-1]
+};
+
`,19),e=[o];function c(l,u){return s(),a("div",null,e)}const k=n(t,[["render",c],["__file","动态规划🍓.html.vue"]]),r=JSON.parse('{"path":"/algorithm/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%F0%9F%8D%93.html","title":"动态规划🍓","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"70.爬楼梯","slug":"_70-爬楼梯","link":"#_70-爬楼梯","children":[]},{"level":2,"title":"118.杨辉三角","slug":"_118-杨辉三角","link":"#_118-杨辉三角","children":[]},{"level":2,"title":"198.打家劫舍","slug":"_198-打家劫舍","link":"#_198-打家劫舍","children":[]},{"level":2,"title":"279.完全平方数","slug":"_279-完全平方数","link":"#_279-完全平方数","children":[]},{"level":2,"title":"322.零钱兑换","slug":"_322-零钱兑换","link":"#_322-零钱兑换","children":[]},{"level":2,"title":"300.最长递增子序列","slug":"_300-最长递增子序列","link":"#_300-最长递增子序列","children":[]},{"level":2,"title":"416.分割等和子集","slug":"_416-分割等和子集","link":"#_416-分割等和子集","children":[]},{"level":2,"title":"62.不同路径","slug":"_62-不同路径","link":"#_62-不同路径","children":[]},{"level":2,"title":"64.最小路径","slug":"_64-最小路径","link":"#_64-最小路径","children":[]}],"filePathRelative":"algorithm/动态规划🍓.md","git":{"createdTime":1715588813000,"updatedTime":1715588813000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":1}]},"readingTime":{"minutes":2.65,"words":794}}');export{k as comp,r as data}; diff --git "a/assets/\345\217\214\346\214\207\351\222\210_\346\273\221\345\212\250\347\252\227\345\217\243\360\237\215\250.html-Bm2XjH9A.js" "b/assets/\345\217\214\346\214\207\351\222\210_\346\273\221\345\212\250\347\252\227\345\217\243\360\237\215\250.html-Bm2XjH9A.js" new file mode 100644 index 0000000..4530d62 --- /dev/null +++ "b/assets/\345\217\214\346\214\207\351\222\210_\346\273\221\345\212\250\347\252\227\345\217\243\360\237\215\250.html-Bm2XjH9A.js" @@ -0,0 +1,422 @@ +import{_ as n,o as s,c as a,e as p}from"./app-B-BkP2m_.js";const t={},e=p(`

双指针_滑动窗口🍨

27. 移除元素

/**
+ * @param {number[]} nums
+ * @param {number} val
+ * @return {number}
+ */
+var removeElement = function(nums, val) {
+    let slow=0
+    for(let fast=0;fast<nums.length;fast++){
+        if(nums[fast]!=val){
+            nums[slow]=nums[fast]
+            slow++
+        }
+    }
+    return slow
+};
+

26. 删除有序数组中的重复项

/**
+ * @param {number[]} nums
+ * @return {number}
+ */
+var removeDuplicates = function(nums) {
+    let slow=0
+    for(let fast=0;fast<nums.length;fast++){
+        if(nums[fast]!=nums[slow]){
+            slow++
+            nums[slow]=nums[fast]
+        }
+    }
+    return slow+1
+};
+

283. 移动零

/**
+ * @param {number[]} nums
+ * @return {void} Do not return anything, modify nums in-place instead.
+ */
+var moveZeroes = function(nums) {
+    let slow=0;
+    for(let fast=0;fast<nums.length;fast++){
+        if(nums[fast]!=0){
+            nums[slow]=nums[fast]
+            slow++
+        }
+    }
+    for(let i=slow;i<nums.length;i++){
+        nums[i]=0
+    }
+};
+

209. 长度最小的子数组

/**
+ * @param {number} target
+ * @param {number[]} nums
+ * @return {number}
+ */
+var minSubArrayLen = function (target, nums) {
+    //用滑动窗口进行求解
+    let start = 0, end = 0;
+    const n = nums.length;
+    let sum = 0, ans = n + 1;//这个尽量设一个较大的值
+    while (end < n) {
+        sum += nums[end]
+        end++
+        while (sum >= target) {
+            ans = Math.min(ans, end - start)
+            sum -= nums[start]
+            start++
+        }
+    }
+    return ans == n + 1 ? 0 : ans
+};
+

904. 水果成篮

/**
+ * @param {number[]} fruits
+ * @return {number}
+ */
+var totalFruit = function (fruits) {
+    if (fruits.length <= 2) return fruits.length
+    //用滑动窗口+哈希来求解
+    const map = new Map()
+    let right = 0, left = 0//left来进行标记
+    let max = -Infinity
+    while (right < fruits.length) {
+        const type = fruits[right]
+        right++
+        //注意这个位置求值后下面结尾也要注意一下!!!
+        const it = Array.from(map.values())
+        max = Math.max(max, it.reduce((p, v) => p + v, 0))
+
+        map.set(type, (map.get(type) || 0) + 1)
+
+
+        while (map.size > 2) {
+            const ty = fruits[left]
+            left++
+            map.set(ty, map.get(ty) - 1)
+
+            if (map.get(ty) == 0) {
+                map.delete(ty)
+            }
+        }
+
+    }
+    const it = Array.from(map.values())
+    max = Math.max(max, it.reduce((p, v) => p + v, 0))
+
+    return max == -Infinity ? fruits.length : max
+};
+

11. 盛最多水的容器

/**
+ * @param {number[]} height
+ * @return {number}
+ */
+var maxArea = function(height) {
+    let maxA=0
+    let left=0,right=height.length-1
+    while(left<right){
+        let area=Math.min(height[left],height[right])*(right-left)
+        maxA=Math.max(maxA,area)
+        if(height[left]<height[right]){
+            left++
+        }else{
+            right--
+        }
+    }
+    return maxA
+};
+

15. 三数之和

/**
+ * @param {number[]} nums
+ * @return {number[][]}
+ */
+var threeSum = function(nums) {
+    //这里有三个去重的点子
+    const len=nums.length;
+    const res=[];
+    nums.sort((a,b)=>a-b)
+    for(let i=0;i<len-2;i++){
+        //第一点优化:但凡开始记录,后面的三数和绝对大于0
+        if(nums[i]>0)break;
+        //第二点优化:跳过重复的点  
+        if(i>0 && nums[i]==nums[i-1])continue;
+        let L=i+1,R=len-1
+        while(L<R){
+            const target=nums[i]+nums[L]+nums[R];
+            if(target==0){
+                res.push([nums[i],nums[L],nums[R]])
+                //第三层优化:
+                while(L<R && nums[L]==nums[L+1])L++
+                while(L<R && nums[R]==nums[R+1])R--
+                L++
+                R--
+            }else if(target<0){
+                L++
+            }else{
+                R--
+            }
+        }
+    }
+    return res
+};
+

这个写得差点时间超限:

/**
+ * @param {number[]} nums
+ * @return {number[][]}
+ */
+var threeSum = function(nums) {
+    nums.sort((a,b)=>a-b)
+    const res=[]
+    for(let i=1;i<nums.length;i++){
+        const first=nums[i-1];
+        let left=i,right=nums.length-1
+        while(left<right){
+            const target=nums[left]+nums[right]+first;
+            if(target==0){
+                res.push([first,nums[left],nums[right]]+"")
+                left++
+                right--
+            }
+            else if(target<0){
+                left++
+            }else{
+                right--
+            }
+        }
+    }
+    let result=[...new Set(res)].map(str=>str.split(",").map(s=>Number(s)))
+    return result
+};
+

42. 接雨水

/**
+ * @param {number[]} height
+ * @return {number}
+ */
+var trap = function (height) {
+    //利用双指针方法进行求解!!!这个方法比较简单!!!
+    let ans = 0;
+    let left = 0, right = height.length - 1;
+    let leftMax = 0, rightMax = 0
+    while (left < right) {
+        leftMax = Math.max(leftMax, height[left])
+        rightMax = Math.max(rightMax, height[right])
+        if (height[left] < height[right]) {
+            ans += leftMax - height[left]
+            left++
+        } else {
+            ans += rightMax - height[right]
+            right--
+        }
+    }
+    return ans
+};
+

3. 无重复字符的最长子串

/**
+ * @param {string} s
+ * @return {number}
+ */
+var lengthOfLongestSubstring = function (s) {
+    let res = 0;
+    let left = 0, right = 0;
+    let window = {}
+    while (right < s.length) {
+        const c = s[right]
+        right++;
+        window[c] = (window[c] || 0) + 1
+
+        while (window[c] > 1) {
+            const d = s[left];
+            left++;
+            window[d]--
+        }
+        res = Math.max(res, right - left)
+    }
+    return res
+};
+

前面用了一个JS方法来求解的

/**
+ * @param {string} s
+ * @return {number}
+ */
+var lengthOfLongestSubstring = function (s) {
+    //滑动窗口问题
+    let max = 0;
+    let slow = 0, fast = 0;
+    while (fast < s.length) {
+        if (!s.slice(slow, fast).includes(s[fast])) {
+            fast++
+        } else {
+            slow++
+        }
+        max = Math.max(fast - slow, max)
+    }
+    return max
+};
+

76.最小覆盖子串

/**
+ * @param {string} s
+ * @param {string} t
+ * @return {string}
+ */
+var minWindow = function (s, t) {
+    // 哈希表 need 记录需要匹配的字符及对应的出现次数
+    // 哈希表 window 记录窗口中满足 need 条件的字符及其出现次数
+    let need = new Map();
+    let window = new Map();
+    //先将need填充好
+    for (let i = 0; i < t.length; i++) {
+        if (need.has(t[i])) {
+            need.set(t[i], need.get(t[i]) + 1)
+        } else {
+            need.set(t[i], 1)
+        }
+    }
+
+    let left = 0, right = 0;
+    let valid = 0;
+    // 记录最小覆盖子串的起始索引及长度
+    let start = 0, len = Infinity;
+    while (right < s.length) {
+        // c 是将移入窗口的字符
+        const c = s[right]
+        // 扩大窗口
+        right++
+        // 进行窗口内数据的一系列更新
+        if (need.has(c)) {
+            if (window.has(c)) {
+                window.set(c, window.get(c) + 1)
+            } else {
+                window.set(c, 1)
+            }
+            if (window.get(c) === need.get(c)) {
+                valid++
+            }
+        }
+        // 判断左侧窗口是否要收缩
+        while (valid === need.size) {
+            // 在这里更新最小覆盖子串
+            if (right - left < len) {
+                start = left
+                len = right - left
+            }
+            // d 是将移出窗口的字符
+            const d = s[left]
+            // 缩小窗口
+            left++
+            // 进行窗口内数据的一系列更新
+            if(need.has(d)){
+                if(window.get(d)==need.get(d)){
+                    valid--
+                }
+                window.set(d,window.get(d)-1)
+            }
+        }
+    }
+    // 返回最小覆盖子串
+    return len==Infinity ?'':s.substr(start,len)
+};
+

438. 找到字符串中所有字母异位词

/**
+ * @param {string} s
+ * @param {string} p
+ * @return {number[]}
+ */
+var findAnagrams = function (s, p) {
+    // 定义需求和窗口
+    const need = new Map()
+    const window = new Map()
+    // 收集需求
+    for (const c of p) {
+        need.set(c, (need.get(c) || 0) + 1);
+    }
+    let left = 0, right = 0, ans = [];//存储结果
+    let valid = 0;//统计是否达到需求数
+    while (right < s.length) {
+        const c = s[right]
+        right++;
+        // 对窗口内数据进行更新
+        if (need.has(c)) {
+            window.set(c, (window.get(c) || 0) + 1)
+            if (need.get(c) === window.get(c)) {
+                valid++
+            }
+        }
+        // 判断左侧窗口是否需要收缩
+        while (right - left == p.length) {
+            // 符合窗口条件,把索引值加入结果数组
+            if (valid === need.size) {
+                ans.push(left)
+            }
+            const d = s[left]
+            left++;
+            // 条件成立进行窗口收缩!
+            if (need.has(d)) {
+                if (window.get(d) === need.get(d)) {
+                    valid--
+                }
+                window.set(d, window.get(d) - 1)
+            }
+        }
+
+    }
+    return ans
+};
+

567.字符串的排列

/**
+ * @param {string} s1
+ * @param {string} s2
+ * @return {boolean}
+ */
+var checkInclusion = function(s1, s2) {
+    let need=new Map()
+    let window=new Map()
+    //先进行need的收集!!!
+    for(const c of s1){
+        need.set(c,(need.get(c)||0)+1)
+    }
+    let left=0,right=0
+    let valid=0
+    while(right<s2.length){
+        const c=s2[right]//目前探寻的是最右边的填充
+        right++
+        //进行窗口的一系列更新
+        if(need.has(c)){
+            window.set(c,(window.get(c)||0)+1)
+            if(window.get(c)==need.get(c)){
+                valid++
+            }
+        }
+        //判断窗口是否需要进行缩放
+        while(right-left>=s1.length){
+            //找到了合法的子串
+            if(valid==need.size){
+                return true
+            }
+            const d=s2[left]
+            left++
+            //进行窗口的一系列更新
+            if(need.has(d)){
+                if(window.get(d)==need.get(d)){
+                    valid--
+                }
+                window.set(d,window.get(d)-1)
+            }
+        }
+    }
+    return false
+
+};
+

239. 滑动窗口最大值

/**
+ * @param {number[]} nums
+ * @param {number} k
+ * @return {number[]}
+ */
+var maxSlidingWindow = function(nums, k) {
+    const n = nums.length;
+    const q = [];
+    for (let i = 0; i < k; i++) {
+        while (q.length && nums[i] >= nums[q[q.length - 1]]) {
+            q.pop();
+        }
+        q.push(i);
+    }
+
+    const ans = [nums[q[0]]];
+    for (let i = k; i < n; i++) {
+        while (q.length && nums[i] >= nums[q[q.length - 1]]) {
+            q.pop();
+        }
+        q.push(i);
+        while (q[0] <= i - k) {
+            q.shift();
+        }
+        ans.push(nums[q[0]]);
+    }
+    return ans;
+};
+
`,31),o=[e];function c(l,u){return s(),a("div",null,o)}const k=n(t,[["render",c],["__file","双指针_滑动窗口🍨.html.vue"]]),r=JSON.parse('{"path":"/algorithm/%E5%8F%8C%E6%8C%87%E9%92%88_%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%F0%9F%8D%A8.html","title":"双指针_滑动窗口🍨","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"27. 移除元素","slug":"_27-移除元素","link":"#_27-移除元素","children":[]},{"level":2,"title":"26. 删除有序数组中的重复项","slug":"_26-删除有序数组中的重复项","link":"#_26-删除有序数组中的重复项","children":[]},{"level":2,"title":"283. 移动零","slug":"_283-移动零","link":"#_283-移动零","children":[]},{"level":2,"title":"209. 长度最小的子数组","slug":"_209-长度最小的子数组","link":"#_209-长度最小的子数组","children":[]},{"level":2,"title":"904. 水果成篮","slug":"_904-水果成篮","link":"#_904-水果成篮","children":[]},{"level":2,"title":"11. 盛最多水的容器","slug":"_11-盛最多水的容器","link":"#_11-盛最多水的容器","children":[]},{"level":2,"title":"15. 三数之和","slug":"_15-三数之和","link":"#_15-三数之和","children":[]},{"level":2,"title":"42. 接雨水","slug":"_42-接雨水","link":"#_42-接雨水","children":[]},{"level":2,"title":"3. 无重复字符的最长子串","slug":"_3-无重复字符的最长子串","link":"#_3-无重复字符的最长子串","children":[]},{"level":2,"title":"76.最小覆盖子串","slug":"_76-最小覆盖子串","link":"#_76-最小覆盖子串","children":[]},{"level":2,"title":"438. 找到字符串中所有字母异位词","slug":"_438-找到字符串中所有字母异位词","link":"#_438-找到字符串中所有字母异位词","children":[]},{"level":2,"title":"567.字符串的排列","slug":"_567-字符串的排列","link":"#_567-字符串的排列","children":[]},{"level":2,"title":"239. 滑动窗口最大值","slug":"_239-滑动窗口最大值","link":"#_239-滑动窗口最大值","children":[]}],"filePathRelative":"algorithm/双指针_滑动窗口🍨.md","git":{"createdTime":1715588813000,"updatedTime":1716636128000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":3}]},"readingTime":{"minutes":5.39,"words":1616}}');export{k as comp,r as data}; diff --git "a/assets/\345\223\246\357\274\201\345\217\210\345\255\246\345\210\260\344\272\206\357\274\201.html-C9i-xBd2.js" "b/assets/\345\223\246\357\274\201\345\217\210\345\255\246\345\210\260\344\272\206\357\274\201.html-C9i-xBd2.js" new file mode 100644 index 0000000..d5074fe --- /dev/null +++ "b/assets/\345\223\246\357\274\201\345\217\210\345\255\246\345\210\260\344\272\206\357\274\201.html-C9i-xBd2.js" @@ -0,0 +1,564 @@ +import{_ as n,o as s,c as a,e as t}from"./app-B-BkP2m_.js";const p={},e=t(`

哦!又学到了!

append 和 appendchild 方法的区别

<!--
+ELement.append()方法在当前ELement的最后一个子节点之后插入一组Node对象或字符串对象。
+被插入的字符串对象等价为Text,节点其与Node.appendchild()的差异:
+
+Element.append()允许附加字符串对象,而 Node.appendchild()只接受Node对象。
+ELement.append()没有返回值,而 Node.appendChild()返回附加的Node对象。
+ELement.append()可以附加多个节点和字符串,而 Node.appendchiLd()只能附加一个节点。
+-->
+<div class="first"></div>
+<div class="second"></div>
+<script>
+  let first = document.queryselector(".first");
+  let second = document.querySelector(".second");
+  let br = document.createElement("br");
+  first.append("小雨鸽", br, "<h1>真开心!</h1>");
+  //  first.appendchiLd("KJKj")//parameter 1 is not of type 'Node' .
+  let div = document.createElement("div");
+  div.innerHTML = "<h1>哈哈哈哈哈</h1>";
+  second.appendchild(div);
+</script>
+

特殊的 Array.from

<div id="uu">
+  <ul>
+    <li>小雨</li>
+    <li>小雨</li>
+    <li>小雨</li>
+    <li>小雨</li>
+    <li>小雨</li>
+  </ul>
+</div>
+
+<script>
+  // Array.from()将类数组(类数组对象、arguments、Nodelist)转化成普通数组
+  let list=document.queryselectorAll("#uu li")
+  console.log(list);
+  let arr=Array.from(list)
+  console.log(arr);
+  let obj={
+    0: "hello",
+    1:"KKK",
+    2:"hhhhhh",
+    length: 3
+   }
+  console.log(Array.from(obj));
+  function HH(...args){
+      console.log(args);
+      console.1og(arguments);
+      console.log(Array.from(arguments));
+  }
+  HH(1,2,3,4,5)
+
+  //当让也可以用解构赋值
+  arr=[...arguments]
+</script>
+

快速生成一个二维数组

const arr = Array.from({ length: rows }, () => new Array(cols).fill(0));
+

H5 的拖拽事件

document.addEventListener("DOMContentLoaded", () => {
+  const gardenCanvas = document.getElementById("garden-canvas");
+  const draggables = document.querySelectorAl1(".draggable");
+
+  // 设置各植物图标的拖放事件
+  draggables.forEach((draggable) => {
+    draggable.addEventListener("dragstart", handleDragstart);
+  });
+
+  // 设置花园画布的拖放事件
+  gardenCanvas.addEventListener("dragover", handleDragover);
+  gardencanvas.addEventListener("drop", handleDrop);
+
+  function handleDragstart(e) {
+    e.dataTransfer.clearData();
+    e.dataTransfer.setData("text/plain", e.target.src); //掌握在拖、放两个对象之间传递数据的方法
+    e.dataTransfer.effectAllowed = "copy"; //只允许复制操作
+  }
+
+  function handleDragover(e) {
+    e.preventDefault(); //阻止默认行为以允许放置
+    e.dataTransfer.dropEffect = "copy"; //显示为复制操作
+  }
+  function handleDrop(e) {
+    e.preventDefault();
+    // TOD0:补全代码
+    let img = document.createElement("img");
+    img.setAttribute("src", e.dataTransfer.getData("text/plain"));
+    img.styLe.cssText = \`position: absolute;top:\${e.layerY - 50}px;left:\${
+      e.layerX - 50
+    }px;\`;
+    img.className = "draggable";
+    gardenCanvas.appendchi1d(img);
+  }
+});
+

历史记录 localstorage

<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>localstorage实例</title>
+  </head>
+  <body>
+    <input type="text" />
+    <button>点击收搜</button>
+    <h2>历史展示区</h2>
+    <section>
+      <ol class="history"></ol>
+    </section>
+
+    <script>
+      const input = document.querySelector("input[type='text']");
+      const button = document.querySelector("button");
+      const history = document.querySelector(".history");
+
+      if (localStorage.length > 0) {
+        for (let i = 0; i < localStorage.length; i++) {
+          let key = localStorage.key(i);
+          let li = document.createElement("li");
+          let litext = document.createTextNode(localStorage.getItem(key));
+          li.appendChild(litext);
+          history.appendChild(li);
+
+          li.addEventListener("click", function () {
+            localStorage.removeItem(key);
+            this.parentNode.removeChild(this);
+          });
+        }
+      }
+
+      button.addEventListener("click", function () {
+        if (input.value) {
+          let key = new Date().valueOf();
+          let value = input.value;
+          localStorage.setItem(key, value);
+          input.value = "";
+
+          let li = document.createElement("li");
+          let litext = document.createTextNode(localStorage.getItem(key));
+          li.appendChild(litext);
+          history.appendChild(li);
+
+          li.addEventListener("click", function () {
+            localStorage.removeItem(key);
+            this.parentNode.removeChild(this);
+          });
+        }
+      });
+    </script>
+  </body>
+</html>
+
<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>测试cookie</title>
+  </head>
+  <body>
+    <form>
+      <label>用户名</label>
+      <input type="text" />
+      <label>密码</label>
+      <input type="password" />
+      <input type="checkbox" id="remenberMe" />
+      <label for="remenberMe">记住我</label>
+      <input type="submit" value="登录" />
+    </form>
+    <script>
+      const username = document.querySelector("input[type='text']");
+      const checkbox = document.querySelector("input[type='checkbox']");
+      const submit = document.querySelector("input[type='submit']");
+      let arrays = document.cookie
+        .split("; ")
+        .map((cookie) => cookie.split("="));
+      let cookie = {};
+      for (let i = 0; i < arrays.length; i++) {
+        let key = arrays[i][0];
+        let value = arrays[i][1];
+        cookie[key] = decodeURIComponent(value);
+        console.log(value);
+      }
+      // console.log(cookie);
+      if (document.cookie) {
+        username.value = cookie.username;
+        checkbox.checked = true;
+      }
+      submit.addEventListener("click", (e) => {
+        if (checkbox.checked && username.value != "") {
+          let key = "username";
+          let value = encodeURIComponent(username.value);
+          console.log(username.value);
+          let twoDays = 2 * 24 * 60 * 60;
+
+          document.cookie = \`\${key}=\${value}; max-age=\${twoDays}\`;
+        }
+        e.preventDefault();
+      });
+    </script>
+  </body>
+</html>
+

cookie 一般这样的形式:注意有空格和多个等号!!!

'lang=zh-cn; ollina=1702090; tfstk=f2ksLjCh=asas..; yuque_ctoken=ht8P_P8b8PHMMnnrY1u-SqQz; current_theme=default'
+

获取 cookie 的值

function getCookie(cname) {
+  var name = cname + "=";
+  var ca = document.cookie.split(";");
+  for (var i = 0; i < ca.length; i++) {
+    var c = ca[i].trim();
+    if (c.indexOf(name) == 0) return c.substring(name.length, c.length); //字符串截取
+  }
+  return "";
+}
+

使用 JavaScript 创建 Cookie

JavaScript 可以使用 document.cookie 属性来创建 、读取、及删除 cookie。

JavaScript 中,创建 cookie 如下所示:
document.cookie="username=John Doe";
您还可以为 cookie 添加一个过期时间(以 UTC 或 GMT 时间)。默认情况下,cookie 在浏览器关闭时删除:
document.cookie="username=John Doe; expires=Thu, 18 Dec 2043 12:00:00 GMT";
您可以使用 path 参数告诉浏览器 cookie 的路径。默认情况下,cookie 属于当前页面。
document.cookie="username=John Doe; expires=Thu, 18 Dec 2043 12:00:00 GMT; path=/";

使用 JavaScript 读取 Cookie

在 JavaScript 中, 可以使用以下代码来读取 cookie:
var x = document.cookie;不能读取到设置了 HttpOnly 的值,还要注意创建和读取不是一回事!!!

document.cookie 将以字符串的方式返回所有的 cookie,类型格式: cookie1=value; cookie2=value; cookie3=value;

使用 JavaScript 修改 Cookie

在 JavaScript 中,修改 cookie 类似于创建 cookie,如下所示:
document.cookie="username=John Smith; expires=Thu, 18 Dec 2043 12:00:00 GMT; path=/";
旧的 cookie 将被覆盖。

使用 JavaScript 删除 Cookie

删除 cookie 非常简单。您只需要设置 expires 参数为以前的时间即可,如下所示,设置为 Thu, 01 Jan 1970 00:00:00 GMT:
document.cookie = "username=; expires=Thu, 01 Jan 1970 00:00:00 GMT";
注意,当您删除时不必指定 cookie 的值。

图片懒加载

一种简易的实现方式

<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>一种简易的实现方式</title>
+    <style>
+      img {
+        display: block;
+        height: 200px;
+        margin: 30px;
+      }
+      p {
+        padding: 30px;
+      }
+    </style>
+  </head>
+  <body>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Ratione quam,
+      ducimus deleniti beatae dicta eius neque sed quos fuga dolorum in
+      accusantium vitae ipsum placeat quia molestiae, vero natus repellat!
+    </p>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Ratione quam,
+      ducimus deleniti beatae dicta eius neque sed quos fuga dolorum in
+      accusantium vitae ipsum placeat quia molestiae, vero natus repellat!
+    </p>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Ratione quam,
+      ducimus deleniti beatae dicta eius neque sed quos fuga dolorum in
+      accusantium vitae ipsum placeat quia molestiae, vero natus repellat!
+    </p>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Ratione quam,
+      ducimus deleniti beatae dicta eius neque sed quos fuga dolorum in
+      accusantium vitae ipsum placeat quia molestiae, vero natus repellat!
+    </p>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Ratione quam,
+      ducimus deleniti beatae dicta eius neque sed quos fuga dolorum in
+      accusantium vitae ipsum placeat quia molestiae, vero natus repellat!
+    </p>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Ratione quam,
+      ducimus deleniti beatae dicta eius neque sed quos fuga dolorum in
+      accusantium vitae ipsum placeat quia molestiae, vero natus repellat!
+    </p>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Ratione quam,
+      ducimus deleniti beatae dicta eius neque sed quos fuga dolorum in
+      accusantium vitae ipsum placeat quia molestiae, vero natus repellat!
+    </p>
+
+    <img data-src="1.jpg" alt="" />
+    <img data-src="2.jpg" alt="" />
+    <img data-src="3.jpg" alt="" />
+
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam qui
+      veritatis dolor quos culpa magni cupiditate quas repellat obcaecati.
+      Molestias facere quibusdam culpa excepturi fugiat iste at esse error
+      inventore.
+    </p>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam qui
+      veritatis dolor quos culpa magni cupiditate quas repellat obcaecati.
+      Molestias facere quibusdam culpa excepturi fugiat iste at esse error
+      inventore.
+    </p>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam qui
+      veritatis dolor quos culpa magni cupiditate quas repellat obcaecati.
+      Molestias facere quibusdam culpa excepturi fugiat iste at esse error
+      inventore.
+    </p>
+
+    <script>
+      let imgs = document.querySelectorAll("img");
+
+      window.addEventListener("scroll", (e) => {
+        imgs.forEach((img) => {
+          const imgTop = img.getBoundingClientRect().top;
+          if (imgTop < window.innerHeight) {
+            const data_src = img.dataset.src;
+            img.setAttribute("src", data_src);
+          }
+          console.log("滚动事件触发了"); //这样其实很浪费资源的!!!
+        });
+      });
+    </script>
+  </body>
+</html>
+

intersectionObserver 实现方式

<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>intersectionObserver实现方式</title>
+    <style>
+      img {
+        /* display: block; */
+        height: 200px;
+        margin: 30px;
+      }
+      p {
+        padding: 30px;
+      }
+    </style>
+  </head>
+  <body>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Ratione quam,
+      ducimus deleniti beatae dicta eius neque sed quos fuga dolorum in
+      accusantium vitae ipsum placeat quia molestiae, vero natus repellat!
+    </p>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Ratione quam,
+      ducimus deleniti beatae dicta eius neque sed quos fuga dolorum in
+      accusantium vitae ipsum placeat quia molestiae, vero natus repellat!
+    </p>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Ratione quam,
+      ducimus deleniti beatae dicta eius neque sed quos fuga dolorum in
+      accusantium vitae ipsum placeat quia molestiae, vero natus repellat!
+    </p>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Ratione quam,
+      ducimus deleniti beatae dicta eius neque sed quos fuga dolorum in
+      accusantium vitae ipsum placeat quia molestiae, vero natus repellat!
+    </p>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Ratione quam,
+      ducimus deleniti beatae dicta eius neque sed quos fuga dolorum in
+      accusantium vitae ipsum placeat quia molestiae, vero natus repellat!
+    </p>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Ratione quam,
+      ducimus deleniti beatae dicta eius neque sed quos fuga dolorum in
+      accusantium vitae ipsum placeat quia molestiae, vero natus repellat!
+    </p>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Ratione quam,
+      ducimus deleniti beatae dicta eius neque sed quos fuga dolorum in
+      accusantium vitae ipsum placeat quia molestiae, vero natus repellat!
+    </p>
+
+    <img data-src="1.jpg" alt="" />
+    <img data-src="2.jpg" alt="" />
+    <img data-src="3.jpg" alt="" />
+
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam qui
+      veritatis dolor quos culpa magni cupiditate quas repellat obcaecati.
+      Molestias facere quibusdam culpa excepturi fugiat iste at esse error
+      inventore.
+    </p>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam qui
+      veritatis dolor quos culpa magni cupiditate quas repellat obcaecati.
+      Molestias facere quibusdam culpa excepturi fugiat iste at esse error
+      inventore.
+    </p>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam qui
+      veritatis dolor quos culpa magni cupiditate quas repellat obcaecati.
+      Molestias facere quibusdam culpa excepturi fugiat iste at esse error
+      inventore.
+    </p>
+
+    <script>
+      /* intersectionObserver 交叉观察
+        IntersectionObserver 接口(从属于 Intersection Observer API)提供了一种异步观察目标元素与其祖先元素或顶级文档视口(viewport)交叉状态的方法。其祖先元素或视口被称为根(root)。
+        当一个 IntersectionObserver 对象被创建时,其被配置为监听根中一段给定比例的可见区域。
+        一旦 IntersectionObserver 被创建,则无法更改其配置,所以一个给定的观察者对象只能用来监听可见区域的特定变化值;
+        然而,你可以在同一个观察者对象中配置监听多个目标元素。
+        */
+
+      const images = document.querySelectorAll("img");
+
+      const callback = (entries) => {
+        entries.forEach((entry) => {
+          // console.log(entry);
+          if (entry.isIntersecting) {
+            const image = entry.target;
+            let data_src = image.dataset.data;
+            image.setAttribute("src", data_src);
+            observer.unobserve(image); //取消观察!!!
+            console.log("触发");
+          }
+        });
+        // console.log("看见了触发,看不见了也触发");
+      };
+      const observer = new IntersectionObserver(callback);
+
+      images.forEach((img) => {
+        //进行观察
+        observer.observe(img);
+      });
+    </script>
+  </body>
+</html>
+

多行省略

<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>多行省略</title>
+    <style>
+      p.p1 {
+        line-height: 30px;
+        width: 50%;
+        border: 1px solid red;
+        font-size: 20px;
+        /* text-overflow 属性并不会强制“溢出”事件的发生,因此为了能让文本能够溢出容器,你需要在元素上添加几个额外的属性:overflow 和 white-space */
+        white-space: nowrap;
+        overflow: hidden;
+
+        text-overflow: ellipsis; /* text-overflow 属性只对那些在块级元素溢出的内容有效 */
+      }
+      /* 第一种方法 :有比较大的局限性 */
+      p.p2 {
+        height: 110px;
+        width: 50%;
+        border: 1px black solid;
+        font-size: 20px;
+
+        overflow: hidden;
+        display: -webkit-box;
+        -webkit-box-orient: vertical;
+        -webkit-line-clamp: 4;
+        /* 简洁明了 这是私有属性 */
+      }
+      /* 第二种方法 伪元素创建  如果不是纯色的就不好弄了*/
+      p.p3 {
+        height: 110px;
+        width: 70%;
+        border: 1px black solid;
+        font-size: 20px;
+
+        overflow: hidden;
+        position: relative;
+        padding-right: 1em;
+        text-align: justify;
+      }
+      p.p3::before {
+        content: "...";
+        position: absolute;
+        right: 0;
+        bottom: 0;
+      }
+      p.p3::after {
+        content: "";
+        width: 1em;
+        height: 10em;
+        background-color: lime;
+        position: absolute;
+        display: inline;
+        right: 0;
+        margin-top: 0.5em;
+      }
+      /* 第三种方法:渐变色 */
+      p.p4 {
+        height: 110px;
+        width: 70%;
+        border: 1px black solid;
+        font-size: 20px;
+
+        overflow: hidden;
+        position: relative;
+        text-align: justify;
+      }
+      p.p4::after {
+        content: "";
+        position: absolute;
+        height: 1.2em;
+        width: 20%;
+        background: linear-gradient(to right, rgba(255, 255, 255, 0), #fff 80%);
+        right: 0;
+        bottom: 0;
+        margin-bottom: 0.2em;
+      }
+    </style>
+  </head>
+  <body>
+    <!-- 多行省略一直是个头疼的问题,因为规范的CSS里 text-overflow:ellipsis只适用于单行文本 -->
+    <p class="p1">
+      Lorem ipsum dolor sit amet, consectetur adipisicing elit. Excepturi itaque
+      hic unde qui quae aut porro veritatis facere vero eius. Perferendis nulla
+      rem autem incidunt porro culpa quis veniam obcaecati.
+    </p>
+    <p class="p2">
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Officia nisi
+      dolorum recusandae ea mollitia autem atque delectus incidunt placeat
+      deleniti doloremque suscipit rerum, corrupti ab sed ut quasi soluta est.
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Ipsum explicabo
+      earum obcaecati corrupti iusto quasi blanditiis cumque dolor, consequuntur
+      adipisci molestiae. Dolor eum laborum ipsum sapiente qui voluptatum
+      nostrum ex.
+    </p>
+    <p class="p3">
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Similique quidem
+      nihil, quaerat necessitatibus voluptatibus reiciendis. Nemo recusandae
+      officiis sapiente esse quis commodi corrupti soluta laboriosam, ut,
+      deleniti modi illum! In.
+    </p>
+    <p class="p4">
+      Lorem ipsum, dolor sit amet consectetur adipisicing elit. Pariatur
+      accusantium corporis, quae quasi consectetur necessitatibus tempore
+      perferendis rem impedit deleniti, dignissimos saepe. Amet error aliquam
+      veritatis deserunt laborum beatae cupiditate.
+    </p>
+  </body>
+</html>
+

数组扁平化

1. reduce 实现

const arr = [1, [2, [3, 4, 5]]];
+// 扁平化数组,用reduce函数
+function flatten(arr) {
+  return arr.reduce((pre, cur) => {
+    return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);
+  }, []);
+}
+

2. 数组方法 flat 实现

const arr = [1, [2, [3, [4, 5]]]];
+
+arr.flat(Infinity);
+

3. split + toString 实现

function flatten(arr) {
+  return arr
+    .toString()
+    .split(",")
+    .map((i) => Number(i));
+}
+

4. 正则 + JSON 实现

function flatten(arr) {
+  let str = JSON.stringify(arr);
+  str = str.replace(/(\\[|\\])/g, "");
+  // 拼接最外层,变成JSON能解析的格式
+  str = "[" + str + "]";
+  return JSON.parse(str);
+}
+

5. 扩展运算符 实现

function flatten(arr) {
+  while (arr.some((i) => Array.isArray(i))) {
+    arr = [].concat(...arr);
+    //注意这个concat方法特性  数组(解构一层) 或 元素 --> 元素
+  }
+  return arr;
+}
+

6. 普通递归 实现

function flatten(arr) {
+  let result = [];
+  for (let i = 0; i < arr.length; i++) {
+    // 当前元素是一个数组,对其进行递归展平
+    if (Array.isArray(arr[i])) {
+      // 递归展平结果拼接到结果数组
+      result = result.concat(flatten(arr[i]));
+    } else {
+      // 否则直接加入结果数组
+      result.push(arr[i]);
+    }
+  }
+  return result;
+}
+
`,47),o=[e];function c(l,i){return s(),a("div",null,o)}const k=n(p,[["render",c],["__file","哦!又学到了!.html.vue"]]),r=JSON.parse('{"path":"/base/%E5%93%A6%EF%BC%81%E5%8F%88%E5%AD%A6%E5%88%B0%E4%BA%86%EF%BC%81.html","title":"哦!又学到了!","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"append 和 appendchild 方法的区别","slug":"append-和-appendchild-方法的区别","link":"#append-和-appendchild-方法的区别","children":[]},{"level":2,"title":"特殊的 Array.from","slug":"特殊的-array-from","link":"#特殊的-array-from","children":[]},{"level":2,"title":"H5 的拖拽事件","slug":"h5-的拖拽事件","link":"#h5-的拖拽事件","children":[]},{"level":2,"title":"历史记录 localstorage","slug":"历史记录-localstorage","link":"#历史记录-localstorage","children":[]},{"level":2,"title":"记住我 cookie 实现","slug":"记住我-cookie-实现","link":"#记住我-cookie-实现","children":[]},{"level":2,"title":"图片懒加载","slug":"图片懒加载","link":"#图片懒加载","children":[]},{"level":2,"title":"多行省略","slug":"多行省略","link":"#多行省略","children":[]},{"level":2,"title":"数组扁平化","slug":"数组扁平化","link":"#数组扁平化","children":[]}],"filePathRelative":"base/哦!又学到了!.md","git":{"createdTime":1715941349000,"updatedTime":1716436918000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":3}]},"readingTime":{"minutes":9.94,"words":2981}}');export{k as comp,r as data}; diff --git "a/assets/\346\211\213\345\206\231\351\242\230.html-DlxoGdIN.js" "b/assets/\346\211\213\345\206\231\351\242\230.html-DlxoGdIN.js" new file mode 100644 index 0000000..e7613c6 --- /dev/null +++ "b/assets/\346\211\213\345\206\231\351\242\230.html-DlxoGdIN.js" @@ -0,0 +1,2714 @@ +import{_ as n,o as s,c as a,e as t}from"./app-B-BkP2m_.js";const p={},e=t(`

手写题

前端面试常考

1.实现一个 call 函数

Function.prototype.myCall = function (context) {
+  var context = context || window;
+  //给context添加一个属性
+  //getValue.call(a,'yck',24) => a.fn=getValue
+  context.fn = this;
+  //将context后面的参数全部取出
+  var args = [...arguments].slice(1);
+  //进行实现,这里改变了this->context
+  //getValue.call(a,'yck',24) => a.fn('yck',24)
+  var reslut = context.fn(...args);
+  //删除 fn
+  delete context.fn;
+  return reslut;
+};
+
+/* 黑马:手写call
+  1、定义myCall方法
+  2、设置this并调用原函数
+  3、接收剩余参数并返回结果
+  4、使用Symbol调优
+*/
+Function.prototype.myCall = function (thisArg, ...args) {
+  //下面这里有个小小的隐患,如果thisArg传进来的对象已经存在了f这个属性,那么下面这行代码会覆盖之前的值
+  //解决:给thisArg加一个一定不重名的新属性
+  let key = Symbol("key");
+  thisArg[key] = this;
+  const res = thisArg[key](...args);
+  //这里要去除,要不然后面的对象会新出现这个属性
+  delete thisArg[key];
+  return res;
+};
+

2.实现一个 apply 函数

Function.prototype.myApply = function (context) {
+  var context = context || window;
+  context.fn = this;
+
+  var reslut;
+  //需要判断是否存储第二个参数,如果存在,就将第二个参数展开
+  if (arguments[1]) {
+    reslut = context.fn(...arguments[1]);
+  } else {
+    reslut = context.fn();
+  }
+
+  delete context.fn;
+  return reslut;
+};
+
+/* 手写apply函数  */
+Function.prototype.myApply = function (thisArg, args) {
+  let fn = Symbol("fn");
+  thisArg[fn] = this;
+  const result = thisArg[fn](args);
+  delete thisArg[fn];
+  return result;
+};
+

3.实现一个 bind 函数

/* 首先了解一下bind函数 */
+//bind() 最简单的用法是创建一个函数,无论如何调用,它都会使用特定的 this 值进行调用。
+// 顶级的“this”绑定到“globalThis”。
+this.x = 9;
+const module = {
+  x: 81,
+  getX() {
+    return this.x;
+  },
+};
+
+// “getX”的“this”参数绑定到“module”。
+console.log(module.getX()); // 81
+
+const retrieveX = module.getX;
+// “retrieveX”的“this”参数在非严格模式下绑定到“globalThis”。
+console.log(retrieveX()); // 9
+
+// 创建一个新函数“boundGetX”,并将“this”参数绑定到“module”。
+const boundGetX = retrieveX.bind(module);
+console.log(boundGetX()); // 81
+
+//bind() 的另一个简单用法是创建一个具有预设初始参数的函数。
+function list(...args) {
+  return args;
+}
+
+function addArguments(arg1, arg2) {
+  return arg1 + arg2;
+}
+
+console.log(list(1, 2, 3)); // [1, 2, 3]
+
+console.log(addArguments(1, 2)); // 3
+
+// 创建一个带有预设前导参数的函数
+const leadingThirtySevenList = list.bind(null, 37);
+
+// 创建一个带有预设第一个参数的函数。
+const addThirtySeven = addArguments.bind(null, 37);
+
+console.log(leadingThirtySevenList()); // [37]
+console.log(leadingThirtySevenList(1, 2, 3)); // [37, 1, 2, 3]
+console.log(addThirtySeven(5)); // 42
+console.log(addThirtySeven(5, 10)); // 42
+//(最后一个参数 10 被忽略)
+
+//实现一个bind函数
+Function.prototype.myBind = function (context) {
+  if (typeof this !== "function") {
+    throw new TypeError("Error");
+  }
+  var _this = this;
+  var args = [...arguments].slice(1); //这里的arguments是myBind这个函数的参数
+  //返回一个函数
+  return function F() {
+    //因为返回了一个函数,我们可以new F(),所以需要判断
+    if (this instanceof F) {
+      return new _this(...args, ...arguments); //这里的arguments是F这个函数的参数
+    }
+    return _this.apply(context, args.concat(...arguments));
+  };
+};
+
+/* 实现一个bind方法 
+  1、定义myBind方法
+  2、返回绑定this的新函数
+  3、合并绑定和新传入的参数 
+  */
+//1.定义myBind方法
+Function.prototype.myBind = function (thisArg, ...args) {
+  // 2.返回绑定this的新函数
+  return (...reArgs) => {
+    // this:原函数(原函数.myBind)
+    return this.call(thisArg, ...args, ...reArgs);
+  };
+};
+
+//实现一个bind函数
+Function.prototype.myBind = function (thisArg, ...args) {
+  let _this = this;
+  return function () {
+    return _this.call(thisArg, ...args, ...arguments);
+  };
+};
+

4.实现 instanceof

//instanceof 可以正确判断对象的基本类型,因为内部机制是通过判断对象的原型链中是不是能够找到类型的 prototype
+
+function instance(left, right) {
+  //对象 和 类型
+  //获得类型的原型
+  let prototype = right.prototype;
+  //   获得对象的原型
+  left = left.__proto__;
+  //   判断对象类型是否等于类型的原型
+  while (true) {
+    if (left === null) {
+      //已经往上找不到了,没有原型了
+      return false;
+    }
+    if (prototype === left) {
+      return true;
+    }
+    left = left.__proto__;
+  }
+}
+
+let arr = [];
+console.log(instance(arr, Array));
+

5.实现一个 new

/* 
+在调用new的过程中会发生如下四件事情
+1.新生成了一个对象
+2.链接到了原型
+3.绑定this
+4.返回新对象
+*/
+function _new(constructor, ...args) {
+  //创建一个新的对象
+  var obj = new Object();
+  //链接到原型
+  obj.__proto__ = constructor.prototype;
+  //绑定this,执行构造函数
+  var res = constructor.apply(obj, args);
+  //确保new出来的是一个对象
+  return typeof res === "object" ? res : obj;
+}
+
+function Fun(name) {
+  this.name = name;
+}
+console.log(new Fun("xiao"));
+console.log(_new(Fun, "xiaoyu"));
+

6.Generator-id 生成器

// **需求:**使用\`Generator\`实现一个id生成器id
+
+function* idGenerator() {
+  let id = 0;
+  while (true) {
+    yield id++;
+  }
+}
+
+const idMaker = idGenerator();
+
+const { value: id1 } = idMaker.next();
+const { value: id2 } = idMaker.next();
+const { value: id3 } = idMaker.next();
+
+console.log(id1, id2, id3);
+

7.函数柯里化一道面试题

/**
+ * 改写函数,实现如下效果
+ *
+ * function sum(a,b,c,d){
+ *  return a+b+c+d
+ * }
+ *
+ * //改写函数,参数传递5个即可累加实现
+ * sum(1)(2)(3)(4)(5)
+ * sum(1)(2,3)(4)(5)
+ * sum(1)(2,3,4)(5)
+ * sum(1,2)(3,4,5)
+ *  */
+
+let arr = []; //保存不定长数组
+function sum(...args) {
+  arr.push(...args);
+  if (arr.length >= 5) {
+    //进行累加
+    let res = arr.slice(0, 5).reduce((cur, pre) => pre + cur, 0);
+    arr = [];
+    return res;
+  } else {
+    return sum;
+  }
+}
+
+console.log(sum(1)(2)(3, 5, 4, 1));
+

8.实现一个管道函数

function fn1(x) {
+  return x + 1;
+}
+function fn2(x) {
+  return x * 2;
+}
+
+// function pine(...fns){
+//     return function(x){
+//         return fns.reduce((acc,fn)=>{
+//             return fn(acc)
+//         },x)
+//     }
+// }
+
+const pineline = pine([fn1, fn2]);
+const output = pineline(5);
+console.log(output);
+
+function pine(fns) {
+  return function (x) {
+    return fns.reduce((prevRes, fn) => fn(prevRes), x);
+  };
+}
+

9.手写 loadsh_get 方法

// input
+const obj = {
+  选择器: { to: { toutiao: "FE Coder" } },
+  target: [1, 2, { name: "byted" }],
+};
+get(obj, "选择器.to.toutiao", "target[0]", "target[2].name");
+
+// output
+["FE coder", 1, "byted"];
+
+function get(object, ...path) {
+  return path.map((item) => {
+    let res = object;
+    item
+      .replace(/\\[/g, ".")
+      .replace(/\\]/g, "")
+      .split(".")
+      .map((path) => (res = res && res[path]));
+    return res;
+  });
+}
+
在表达式 obj & & obj ['a']中,计算第一个对象。
+
+如果是 false 整个表达式就是 false 结果就是第一个操作数,
+
+如果对象是真实的,那么第二部分
+
+Obj['a']将被求值,其结果将是表达式的最终结果。
+
+let obj = { a: 42 };
+let result = obj && obj['a']; // result will be 42
+
+obj = null;
+result = obj && obj['a']; // result will be null
+








 
 
 
 
 

10.手写 nextTick 方法

export function myNextTick(fn) {
+  let app = document.getElementById("app");
+  var observerOptions = {
+    childList: true, //观察目标子节点的变化
+    attributes: true, //观察属性变动
+    subtree: true, //观察后代节点,默认为false
+  };
+
+  //让fn()在DOM更新完成后执行
+  //创建一个DOM监听器
+  let observer = new MutationObserver((el) => {
+    //当被监听的DOM更新完成时,该回调会触发
+    console.log(el);
+    fn();
+  });
+  observer.observe(app, observerOptions);
+}
+
+// 另一种
+function nextTick(fn){
+    Promise.resolve().then(fn)
+}
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 


 
 
 

11.allComplete

// 手写一个方法,使用Promise.all,实现所有都resolved/reject时才返回,并返回所有的结果
+let p1 = new Promise((resolve, reject) => {
+  setTimeout(() => {
+    reject(100);
+  }, 1000);
+});
+let p2 = new Promise((resolve, reject) => {
+  setTimeout(() => {
+    resolve(200);
+  }, 2000);
+});
+let p3 = new Promise((resolve, reject) => {
+  setTimeout(() => {
+    resolve(300);
+  }, 3000);
+});
+
+Promise.allSettled([p1, p2, p3]).then((val) => {
+  console.log(val);
+  //   [
+  //     { status: "fulfilled", value: 100 },
+  //     { status: "fulfilled", value: 200 },
+  //     { status: "fulfilled", value: 300 },
+  //   ];
+});
+
+//方法就是让他们全部进行兑现!
+function allComplete(arr) {
+  return Promise.all(
+    arr.map((promise) => {
+      return new Promise((resolve) => promise.then(resolve, resolve));
+    })
+  );
+}
+

12.防抖与节流


+// 防抖与节流          共同点               区别               应用场景
+// 防抖:debounce   在事件频繁触发时       只执行最后一次      input输入
+// 节流:throttle   减少事件执行的次数     有规律地执行        拖拽、scroll
+
+//防抖
+function debounce(fn, delay){
+    let timer=null
+    return function(...args){
+        if(timer){
+            clearTimeout(timer)
+        }
+        timer=setTimeout(()=>{
+            fn.apply(this, args)
+        }, delay)
+    }
+}
+
+//节流
+function throttle(fn, delay){
+    let timer=null
+    return function(...args){
+        if(!timer){
+            timer=setTimeout(()=>{
+                fn.apply(this, args)
+                timer=null
+            }, delay)
+        }
+    }
+}
+
+
+//节流的另一种写法
+function throttle1(fn,delay){
+    let pre=0;
+    return function(){
+        let now=new Date();
+        if(now-pre>delay){
+            fn.apply(this,arguments)
+            pre=now;
+        }
+    }
+}
+
+

13.深拷贝浅拷贝

let obj = {
+  id: "1",
+  name: "luoyu",
+  msg: {
+    age: 18,
+  },
+};
+//浅拷贝:只会完整拷贝浅层,深层拷贝的是地址
+let o = {};
+for (let k in obj) {
+  o[k] = obj[k];
+}
+o.msg.age = 20;
+o.id = "2";
+o.name = "luolin";
+console.log(o);
+console.log(obj); //深层中obj对象的msg.age受到影响
+
+//还有一种浅拷贝操作
+Object.assign(o, obj); //将obj浅拷贝给o,实际开发中用assign方法实现浅拷贝
+console.log(o);
+console.log(obj);
+
+console.log("--------------------------");
+//深拷贝:每一级数据都会被拷贝
+o = {};
+//封装函数
+function deepCopy(newobj, oldobj) {
+  for (let k in oldobj) {
+    //判断我们的属性值属于哪种数据类型
+    //1.获取属性值  oldobj[k]
+    var item = oldobj[k];
+    if (item instanceof Array) {
+      //数组放在上面,因为数组也属于对象
+      //2.判断这个值是否是数组
+      newobj[k] = [];
+      deepCopy(newobj[k], item);
+    } else if (item instanceof Object) {
+      //3.判断这个值是否是对象
+      newobj[k] = {};
+      deepCopy(newobj[k], item);
+    } else {
+      ///属于简单数据类型
+      newobj[k] = item;
+    }
+  }
+}
+deepCopy(o, obj);
+o.msg.age = 23; //对obj没有了影响
+console.log(o);
+console.log(obj);
+

手写 Promise

完整 Promise

<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Document</title>
+  </head>
+  <body>
+    <h1>手写Promise</h1>
+    <script>
+      const PENDING = "pending";
+      const FULFILLED = "fulfilled";
+      const REJECTED = "rejected";
+      function runAsynctask(callback) {
+        if (typeof queueMicrotask === "function") {
+          queueMicrotask(callback);
+        } else if (typeof MutationObserver === "function") {
+          let obs = new MutationObserver(callback);
+          let divNode = document.createElement("div");
+          obs.observe(divNode, { childList: true });
+          divNode.innerHTML = "RAIN";
+        } else {
+          setTimeout(callback, 0);
+        }
+      }
+      function resolvePromise(p2, x, resolve, reject) {
+        if (x === p2) reject(new TypeError("chining error in Promise"));
+        if (x instanceof RainPromise) {
+          x.then(
+            (res) => resolve(res),
+            (err) => reject(err)
+          );
+        } else {
+          resolve(x);
+        }
+      }
+      class RainPromise {
+        state = PENDING;
+        result = undefined;
+        #handler = [];
+        constructor(func) {
+          const resolve = (res) => {
+            if (this.state === PENDING) {
+              this.state = FULFILLED;
+              this.result = res;
+              this.#handler.forEach((onFulfilled) => {
+                onFulfilled();
+              });
+            }
+          };
+          const reject = (res) => {
+            if (this.state === PENDING) {
+              this.state = REJECTED;
+              this.result = res;
+              this.#handler.forEach((onRejected) => {
+                onRejected();
+              });
+            }
+          };
+
+          func(resolve, reject);
+        }
+        // 实例方法 then()
+        then(onFulfilled, onRejected) {
+          onFulfilled =
+            typeof onFulfilled === "function" ? onFulfilled : (x) => x;
+          onRejected =
+            typeof onRejected === "function"
+              ? onRejected
+              : (x) => {
+                  throw x;
+                };
+
+          const p2 = new RainPromise((resolve, reject) => {
+            if (this.state === FULFILLED) {
+              runAsynctask(() => {
+                try {
+                  let x = onFulfilled(this.result);
+                  if (x === p2)
+                    reject(new TypeError("chining error in Promise"));
+                  if (x instanceof RainPromise) {
+                    x.then(
+                      (res) => resolve(res),
+                      (err) => reject(err)
+                    );
+                  } else {
+                    resolve(x);
+                  }
+                } catch (err) {
+                  reject(err);
+                }
+              });
+            } else if (this.state === REJECTED) {
+              runAsynctask(() => {
+                try {
+                  let x = onRejected(this.result);
+                  resolvePromise(p2, x, resolve, reject);
+                } catch (error) {
+                  reject(error);
+                }
+              });
+            } else if (this.state === PENDING) {
+              this.#handler.push({
+                onFulfilled: () => {
+                  runAsynctask(() => {
+                    try {
+                      let x = onFulfilled(this.result);
+                      resolvePromise(p2, x, resolve, reject);
+                    } catch (error) {
+                      reject(error);
+                    }
+                  });
+                },
+                onRejected: () => {
+                  runAsynctask(() => {
+                    try {
+                      let x = onRejected(this.result);
+                      resolvePromise(p2, x, resolve, reject);
+                    } catch (error) {
+                      reject(error);
+                    }
+                  });
+                },
+              });
+            }
+          });
+          return p2;
+        }
+
+        /* 实例方法 catch */
+        catch(undefined, onRejected) {
+          return this.then(undefined, onRejected);
+        }
+        /* 实例方法 finally */
+        finally(onFinally, onFinally) {
+          return this.then(onFinally, onFinally);
+        }
+        /* 静态方法 resolve */
+        static resolve(value) {
+          if (value instanceof RainPromise) {
+            return value;
+          }
+          return new RainPromise((resolve) => {
+            resolve(value);
+          });
+        }
+        /* 静态方法 reject */
+        static reject(error) {
+          return new RainPromise((undefined, reject) => {
+            reject(error);
+          });
+        }
+        /* 静态方法 race */
+        static race(promises) {
+          return new RainPromise((resolve, reject) => {
+            if (!Array.isArray(promises)) {
+              return reject(new TypeError("Argument is not Array"));
+            }
+            //对空没有处理,那就是pending
+            promises.forEach((p) => {
+              RainPromise.resolve(p).then(
+                (res) => {
+                  resolve(res);
+                },
+                (err) => {
+                  reject(err);
+                }
+              );
+            });
+          });
+        }
+        /* 静态方法 all */
+        static all(promises) {
+          return new RainPromise((resolve, reject) => {
+            if (!Array.isArray(promises)) {
+              return reject(new TypeError("Argument is not Array"));
+            }
+            promises.length === 0 && resolve(promises);
+            const result = [];
+            let count = 0;
+            promises.forEach((p, index) => {
+              RainPromise.resolve(p).then(
+                (res) => {
+                  result[index] = res;
+                  count++;
+                  count === promises.length && resolve(result);
+                },
+                (err) => {
+                  reject(err);
+                }
+              );
+            });
+          });
+        }
+        /* 静态方法 allsettled */
+        static allsettled(promises) {
+          return new RainPromise((resolve, reject) => {
+            if (!Array.isArray(promises)) {
+              return reject(new TypeError("Argument is not Array"));
+            }
+            promises.length === 0 && resolve(promises);
+            const result = [];
+            let count = 0;
+            promises.forEach((p, index) => {
+              RainPromise.resolve(p).then(
+                (res) => {
+                  result[index] = { state: FULFILLED, value: res };
+                  count++;
+                  count === promises.length && resolve(result);
+                },
+                (err) => {
+                  result[index] = { state: REJECTED, value: err };
+                  count++;
+                  count === promises.length && resolve(result);
+                }
+              );
+            });
+          });
+        }
+        /* 静态方法 any */
+        static any(promises) {
+          return new RainPromise((resolve, reject) => {
+            if (!Array.isArray(promises)) {
+              return reject(new TypeError("Argument is not Array"));
+            }
+            promises.length === 0 &&
+              reject(new AggregateError(promises, "All promise were rejected"));
+            const errors = [];
+            let count = 0;
+            promises.forEach((p, index) => {
+              RainPromise.resolve(p).then(
+                (res) => {
+                  resolve(res);
+                },
+                (err) => {
+                  errors[index] = err;
+                  count++;
+                  count === promises.length &&
+                    reject(
+                      new AggregateError(errors, "All promise were rejected")
+                    );
+                }
+              );
+            });
+          });
+        }
+      }
+    </script>
+  </body>
+</html>
+

01-构造函数

/**
+ *  构造函数
+ *  1.定义类
+ *  2.添加构造函数
+ *  3.定义resolve/reject
+ *  4.执行回调函数
+ * */
+//1.定义类
+class HMPromise {
+  // 2.添加构造函数
+  constructor(func) {
+    //3.定义resolve/reject
+    const resolve = (result) => {};
+    const reject = (result) => {};
+
+    //4.执行回调函数
+    func(resolve, reject);
+  }
+}
+
+/* 测试代码 */
+let p = new HMPromise((resolve, reject) => {
+  console.log("LLL");
+  resolve("成功");
+  // reject("失败")
+});
+








 
 
 
 
 
 
 
 
 
 
 







02-状态及原因

/**
+ *  状态及原因
+ *  1.添加状态(pending/fulfilled/rejected)
+ *  2.添加原因
+ *  3.调整resolve/reject
+ *  4.状态不可逆
+ * */
+const PENDING = "pending";
+const FULFILLED = "fulfilled";
+const REJECTED = "rejected";
+
+class HMPromise {
+  // 添加状态(pending/fulfilled/rejected)
+  // 添加原因
+  state = PENDING;
+  result = undefined;
+
+  constructor(func) {
+    const resolve = (result) => {
+      //状态不可逆
+      if (this.state === PENDING) {
+        //调整resolve/reject
+        this.state = FULFILLED;
+        this.result = result;
+      }
+    };
+    const reject = (result) => {
+      if (this.state === PENDING) {
+        this.state = REJECTED;
+        this.result = result;
+      }
+    };
+
+    func(resolve, reject);
+  }
+}
+
+/* 测试代码 */
+let p = new HMPromise((resolve, reject) => {
+  resolve("成功");
+  reject("失败");
+});
+







 
 
 




 
 


 
 
 
 
 
 
 
 
 
 
 
 
 
 










03-then 的方法-成功和失败的回调

/**
+ *  成功和失败的回调
+ *  1.添加实例方法
+ *  2.参数判断(参考文档)
+ *      2.1.执行成功的回调
+ *      2.2.执行失败的回调
+ *
+ * */
+const PENDING = "pending";
+const FULFILLED = "fulfilled";
+const REJECTED = "rejected";
+
+class HMPromise {
+  // 添加状态(pending/fulfilled/rejected)
+  // 添加原因
+  state = PENDING;
+  result = undefined;
+
+  constructor(func) {
+    const resolve = (result) => {
+      //状态不可逆
+      if (this.state === PENDING) {
+        //调整resolve/reject
+        this.state = FULFILLED;
+        this.result = result;
+      }
+    };
+    const reject = (result) => {
+      if (this.state === PENDING) {
+        this.state = REJECTED;
+        this.result = result;
+      }
+    };
+
+    func(resolve, reject);
+  }
+
+  //1.添加实例方法
+  then(onFulfilled, onRejected) {
+    //2.参数判断(参考文档)
+    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (x) => x;
+    onRejected =
+      typeof onRejected === "function"
+        ? onRejected
+        : (x) => {
+            throw x;
+          };
+
+    //执行成功的回调
+    if (this.state === FULFILLED) {
+      onFulfilled(this.result);
+    } else if (this.state === REJECTED) {
+      onRejected(this.result);
+    }
+  }
+}
+
+/* 测试代码 */
+let p = new HMPromise((resolve, reject) => {
+  resolve("成功");
+  reject("失败");
+});
+
+p.then(
+  (val) => {
+    console.log(val);
+  },
+  (err) => {
+    console.log(err);
+  }
+);
+






































 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 















04-then 的方法

/**
+ *  异步及多次调用
+ *  1.定义实例属性
+ *  2.保存回调函数
+ *  3.调用成功的回调
+ *  4.调用失败的回调
+ *
+ * */
+const PENDING = "pending";
+const FULFILLED = "fulfilled";
+const REJECTED = "rejected";
+
+class HMPromise {
+  // 添加状态(pending/fulfilled/rejected)
+  // 添加原因
+  state = PENDING;
+  result = undefined;
+  // 定义实例属性,用来保存我们的回调函数
+  //# 私有的,外部访问不到
+  #handlers = []; //[{onFulfilled,onRejected}...]
+
+  constructor(func) {
+    const resolve = (result) => {
+      //状态不可逆
+      if (this.state === PENDING) {
+        //调整resolve/reject
+        this.state = FULFILLED;
+        this.result = result;
+        //遍历取出
+        this.#handlers.forEach(({ onFulfilled }) => {
+          onFulfilled(this.result);
+        });
+      }
+    };
+    const reject = (result) => {
+      if (this.state === PENDING) {
+        this.state = REJECTED;
+        this.result = result;
+        this.#handlers.forEach(({ onRejected }) => {
+          onRejected(this.result);
+        });
+      }
+    };
+
+    func(resolve, reject);
+  }
+
+  //1.添加实例方法
+  then(onFulfilled, onRejected) {
+    //2.参数判断(参考文档)
+    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (x) => x;
+    onRejected =
+      typeof onRejected === "function"
+        ? onRejected
+        : (x) => {
+            throw x;
+          };
+
+    //执行成功的回调
+    if (this.state === FULFILLED) {
+      onFulfilled(this.result);
+    } else if (this.state === REJECTED) {
+      onRejected(this.result);
+    } else if (this.state === PENDING) {
+      //是pending状态的时候还不需要去执行函数,可以先保存起来
+      this.#handlers.push({
+        onFulfilled,
+        onRejected,
+      });
+    }
+  }
+}
+
+/* 测试代码 */
+let p = new HMPromise((resolve, reject) => {
+  setTimeout(() => {
+    resolve("成功");
+    // reject("失败")
+  }, 2000);
+});
+
+p.then(
+  (val) => {
+    console.log("then1" + val);
+  },
+  (err) => {
+    console.log("then1" + err);
+  }
+);
+
+p.then(
+  (val) => {
+    console.log("then2" + val);
+  },
+  (err) => {
+    console.log("then2" + err);
+  }
+);
+



















 









 
 
 






 
 
 






















 
 
 
 
 
 
 




























05-异步任务 API

/**
+ *  异步及多次调用
+ *  1.定义实例属性
+ *  2.保存回调函数
+ *  3.调用成功的回调
+ *  4.调用失败的回调
+ *
+ * */
+const PENDING = "pending";
+const FULFILLED = "fulfilled";
+const REJECTED = "rejected";
+
+class HMPromise {
+  // 添加状态(pending/fulfilled/rejected)
+  // 添加原因
+  state = PENDING;
+  result = undefined;
+  // 定义实例属性,用来保存我们的回调函数
+  //# 私有的,外部访问不到
+  #handlers = []; //[{onFulfilled,onRejected}...]
+
+  constructor(func) {
+    const resolve = (result) => {
+      //状态不可逆
+      if (this.state === PENDING) {
+        //调整resolve/reject
+        this.state = FULFILLED;
+        this.result = result;
+        //遍历取出
+        this.#handlers.forEach(({ onFulfilled }) => {
+          onFulfilled(this.result);
+        });
+      }
+    };
+    const reject = (result) => {
+      if (this.state === PENDING) {
+        this.state = REJECTED;
+        this.result = result;
+        this.#handlers.forEach(({ onRejected }) => {
+          onRejected(this.result);
+        });
+      }
+    };
+
+    func(resolve, reject);
+  }
+
+  //1.添加实例方法
+  then(onFulfilled, onRejected) {
+    //2.参数判断(参考文档)
+    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (x) => x;
+    onRejected =
+      typeof onRejected === "function"
+        ? onRejected
+        : (x) => {
+            throw x;
+          };
+
+    //执行成功的回调
+    if (this.state === FULFILLED) {
+      onFulfilled(this.result);
+    } else if (this.state === REJECTED) {
+      onRejected(this.result);
+    } else if (this.state === PENDING) {
+      //是pending状态的时候还不需要去执行函数,可以先保存起来
+      this.#handlers.push({
+        onFulfilled,
+        onRejected,
+      });
+    }
+  }
+}
+
+/* 测试代码 */
+console.log("top");
+let p = new HMPromise((resolve, reject) => {
+  resolve("success");
+});
+p.then((val) => {
+  console.log(val);
+});
+console.log("bottom");
+
+/**
+ * 异步任务:
+ * Vue:Promise.then,MutationObserver,setImmediate,setTimeout
+ * 我们选用:queueMicrotask MutationObserver setTimeout
+ *    Promise.then:手写Promise,不考虑这个
+ *    queueMicrotask:node11,新式浏览(不包括IE11)
+ *    MutationObserver:node不支持,IE11支持
+ *    setImmediate:IE10,11,支持,edge12-18支持(不考虑)
+ *    setTimeout:node,浏览器
+ * */
+
+//    ---------------异步任务1  queueMicrotask  -----------------
+console.log(1);
+queueMicrotask(() => {
+  console.log("queueMicrotask");
+});
+console.log(2);
+
+//    ---------------异步任务2 MutationObserver -----------------
+console.log(1);
+// 创建观察者,并传入回调函数
+const obs = new MutationObserver(() => {
+  console.log("mutationObserver");
+});
+//创建元素,并添加监听
+const divNode = document.createElement("div");
+//参数1 观察DOM节点
+//参数2 观察的选项(childList 观察子节点的改变)
+obs.observe(divNode, { childList: true });
+// 3.修改元素内容
+divNode.innerHTML = "itheima 666";
+console.log(2);
+
+// ------------- 异步任务 setTimeout --------------------------------
+



















































































 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

06-异步任务-函数封装

/**
+ * 异步任务-函数封装
+ *  1. 定义函数
+ *  2. 调用核心API(queueMicrotask,MutationObserver,setTimeout)
+ *  3. 使用封装函数
+ */
+
+// 函数封装
+function runAsynctask(callback) {
+  if (typeof queueMicrotask === "function") {
+    queueMicrotask(callback);
+  } else if (typeof MutationObserver === "function") {
+    const obs = new MutationObserver(callback);
+    const divNode = document.createElement("div");
+    obs.observe(divNode, { childList: true });
+    divNode.innerHTML = "hhh";
+  } else {
+    setTimeout(callback, 0);
+  }
+}
+
+const PENDING = "pending";
+const FULFILLED = "fulfilled";
+const REJECTED = "rejected";
+
+class HMPromise {
+  // 添加状态(pending/fulfilled/rejected)
+  // 添加原因
+  state = PENDING;
+  result = undefined;
+  // 定义实例属性,用来保存我们的回调函数
+  //# 私有的,外部访问不到
+  #handlers = []; //[{onFulfilled,onRejected}...]
+
+  constructor(func) {
+    const resolve = (result) => {
+      //状态不可逆
+      if (this.state === PENDING) {
+        //调整resolve/reject
+        this.state = FULFILLED;
+        this.result = result;
+        //遍历取出
+        this.#handlers.forEach(({ onFulfilled }) => {
+          // onFulfilled(this.result)
+          onFulfilled();
+        });
+      }
+    };
+    const reject = (result) => {
+      if (this.state === PENDING) {
+        this.state = REJECTED;
+        this.result = result;
+        this.#handlers.forEach(({ onRejected }) => {
+          // onRejected(this.result)
+          onRejected();
+        });
+      }
+    };
+
+    func(resolve, reject);
+  }
+
+  //1.添加实例方法
+  then(onFulfilled, onRejected) {
+    //2.参数判断(参考文档)
+    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (x) => x;
+    onRejected =
+      typeof onRejected === "function"
+        ? onRejected
+        : (x) => {
+            throw x;
+          };
+
+    //执行成功的回调
+    if (this.state === FULFILLED) {
+      runAsynctask(() => {
+        onFulfilled(this.result);
+      });
+    } else if (this.state === REJECTED) {
+      runAsynctask(() => {
+        onRejected(this.result);
+      });
+    } else if (this.state === PENDING) {
+      //是pending状态的时候还不需要去执行函数,可以先保存起来
+      this.#handlers.push({
+        onFulfilled: () => {
+          runAsynctask(() => {
+            onFulfilled(this.result);
+          });
+        },
+        onRejected: () => {
+          runAsynctask(() => {
+            onRejected(this.result);
+          });
+        },
+      });
+    }
+  }
+}
+
+/* 测试代码 */
+console.log("top");
+let p = new HMPromise((resolve, reject) => {
+  resolve("success");
+});
+p.then((val) => {
+  console.log(val);
+});
+console.log("bottom");
+








 
 
 
 
 
 
 
 
 
 
 
 






















































 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 












7-链式编程-处理返回值异常

/**
+ * 链式编程-处理返回值和普通内容(fulfilled状态)
+ * 1.返回新Promise实例
+ * 2.获取返回值
+ *  2.1.处理返回值
+ *  2.2.处理异常
+ */
+
+// 函数封装
+function runAsynctask(callback) {
+  if (typeof queueMicrotask === "function") {
+    queueMicrotask(callback);
+  } else if (typeof MutationObserver === "function") {
+    const obs = new MutationObserver(callback);
+    const divNode = document.createElement("div");
+    obs.observe(divNode, { childList: true });
+    divNode.innerHTML = "hhh";
+  } else {
+    setTimeout(callback, 0);
+  }
+}
+
+const PENDING = "pending";
+const FULFILLED = "fulfilled";
+const REJECTED = "rejected";
+
+class HMPromise {
+  // 添加状态(pending/fulfilled/rejected)
+  // 添加原因
+  state = PENDING;
+  result = undefined;
+  // 定义实例属性,用来保存我们的回调函数
+  //# 私有的,外部访问不到
+  #handlers = []; //[{onFulfilled,onRejected}...]
+
+  constructor(func) {
+    const resolve = (result) => {
+      //状态不可逆
+      if (this.state === PENDING) {
+        //调整resolve/reject
+        this.state = FULFILLED;
+        this.result = result;
+        //遍历取出
+        this.#handlers.forEach(({ onFulfilled }) => {
+          // onFulfilled(this.result)
+          onFulfilled();
+        });
+      }
+    };
+    const reject = (result) => {
+      if (this.state === PENDING) {
+        this.state = REJECTED;
+        this.result = result;
+        this.#handlers.forEach(({ onRejected }) => {
+          // onRejected(this.result)
+          onRejected();
+        });
+      }
+    };
+
+    func(resolve, reject);
+  }
+
+  // then方法
+  // 1.返回新Promise实例
+  // 2.获取任意返回值
+  // 2.1处理返回值
+  // 2.2处理异常
+  //1.添加实例方法
+  then(onFulfilled, onRejected) {
+    //2.参数判断(参考文档)
+    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (x) => x;
+    onRejected =
+      typeof onRejected === "function"
+        ? onRejected
+        : (x) => {
+            throw x;
+          };
+
+    const p2 = new HMPromise((resolve, reject) => {
+      //执行成功的回调
+      if (this.state === FULFILLED) {
+        runAsynctask(() => {
+          try {
+            //获取返回值
+            const x = onFulfilled(this.result);
+            // console.log("x",x);
+            //处理返回值
+            resolve(x);
+          } catch (error) {
+            // console.log("捕获异常:"+error);
+            reject(error);
+          }
+        });
+      } else if (this.state === REJECTED) {
+        runAsynctask(() => {
+          onRejected(this.result);
+        });
+      } else if (this.state === PENDING) {
+        //是pending状态的时候还不需要去执行函数,可以先保存起来
+        this.#handlers.push({
+          onFulfilled: () => {
+            runAsynctask(() => {
+              onFulfilled(this.result);
+            });
+          },
+          onRejected: () => {
+            runAsynctask(() => {
+              onRejected(this.result);
+            });
+          },
+        });
+      }
+    });
+    return p2;
+  }
+}
+
+/* 测试代码 */
+const p = new HMPromise((resolve, reject) => {
+  resolve(1);
+});
+p.then((res) => {
+  console.log("p1", res);
+  throw "throw-err";
+  return 2;
+}).then(
+  (res) => {
+    console.log("p2", res);
+  },
+  (err) => {
+    console.log("p2", err);
+  }
+);
+

8-链式编程-处理返回 Promise

/**
+ * 链式编程-处理返回值Promise
+ * 1.处理返回值Promise
+ * 2.调用then方法
+ */
+
+// 函数封装
+function runAsynctask(callback) {
+  if (typeof queueMicrotask === "function") {
+    queueMicrotask(callback);
+  } else if (typeof MutationObserver === "function") {
+    const obs = new MutationObserver(callback);
+    const divNode = document.createElement("div");
+    obs.observe(divNode, { childList: true });
+    divNode.innerHTML = "hhh";
+  } else {
+    setTimeout(callback, 0);
+  }
+}
+
+const PENDING = "pending";
+const FULFILLED = "fulfilled";
+const REJECTED = "rejected";
+
+class HMPromise {
+  // 添加状态(pending/fulfilled/rejected)
+  // 添加原因
+  state = PENDING;
+  result = undefined;
+  // 定义实例属性,用来保存我们的回调函数
+  //# 私有的,外部访问不到
+  #handlers = []; //[{onFulfilled,onRejected}...]
+
+  constructor(func) {
+    const resolve = (result) => {
+      //状态不可逆
+      if (this.state === PENDING) {
+        //调整resolve/reject
+        this.state = FULFILLED;
+        this.result = result;
+        //遍历取出
+        this.#handlers.forEach(({ onFulfilled }) => {
+          // onFulfilled(this.result)
+          onFulfilled();
+        });
+      }
+    };
+    const reject = (result) => {
+      if (this.state === PENDING) {
+        this.state = REJECTED;
+        this.result = result;
+        this.#handlers.forEach(({ onRejected }) => {
+          // onRejected(this.result)
+          onRejected();
+        });
+      }
+    };
+
+    func(resolve, reject);
+  }
+
+  // then方法
+  // 1.返回新Promise实例
+  // 2.获取任意返回值
+  // 2.1处理返回值
+  // 2.2处理异常
+  //1.添加实例方法
+  then(onFulfilled, onRejected) {
+    //2.参数判断(参考文档)
+    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (x) => x;
+    onRejected =
+      typeof onRejected === "function"
+        ? onRejected
+        : (x) => {
+            throw x;
+          };
+
+    const p2 = new HMPromise((resolve, reject) => {
+      //执行成功的回调
+      if (this.state === FULFILLED) {
+        runAsynctask(() => {
+          try {
+            //获取返回值
+            const x = onFulfilled(this.result);
+            // console.log("x",x);
+            // 1.处理返回值Promise
+            if (x instanceof HMPromise) {
+              //2.调用then方法
+              x.then(
+                (res) => resolve(res),
+                (err) => reject(err)
+              );
+            } else {
+              //处理返回值
+              resolve(x);
+            }
+          } catch (error) {
+            // console.log("捕获异常:"+error);
+            reject(error);
+          }
+        });
+      } else if (this.state === REJECTED) {
+        runAsynctask(() => {
+          onRejected(this.result);
+        });
+      } else if (this.state === PENDING) {
+        //是pending状态的时候还不需要去执行函数,可以先保存起来
+        this.#handlers.push({
+          onFulfilled: () => {
+            runAsynctask(() => {
+              onFulfilled(this.result);
+            });
+          },
+          onRejected: () => {
+            runAsynctask(() => {
+              onRejected(this.result);
+            });
+          },
+        });
+      }
+    });
+    return p2;
+  }
+}
+
+/* 测试代码 */
+const p = new HMPromise((resolve, reject) => {
+  resolve(1);
+});
+p.then((res) => {
+  return new HMPromise((resolve, reject) => {
+    // resolve(1)
+    reject("err");
+  });
+}).then(
+  (res) => {
+    console.log("p2", res);
+  },
+  (err) => {
+    console.log("p2", err);
+  }
+);
+

9-链式编程-处理重复引用

/**
+ * 链式编程-处理重复引用
+ */
+
+// 函数封装
+function runAsynctask(callback) {
+  if (typeof queueMicrotask === "function") {
+    queueMicrotask(callback);
+  } else if (typeof MutationObserver === "function") {
+    const obs = new MutationObserver(callback);
+    const divNode = document.createElement("div");
+    obs.observe(divNode, { childList: true });
+    divNode.innerHTML = "hhh";
+  } else {
+    setTimeout(callback, 0);
+  }
+}
+
+const PENDING = "pending";
+const FULFILLED = "fulfilled";
+const REJECTED = "rejected";
+
+class HMPromise {
+  // 添加状态(pending/fulfilled/rejected)
+  // 添加原因
+  state = PENDING;
+  result = undefined;
+  // 定义实例属性,用来保存我们的回调函数
+  //# 私有的,外部访问不到
+  #handlers = []; //[{onFulfilled,onRejected}...]
+
+  constructor(func) {
+    const resolve = (result) => {
+      //状态不可逆
+      if (this.state === PENDING) {
+        //调整resolve/reject
+        this.state = FULFILLED;
+        this.result = result;
+        //遍历取出
+        this.#handlers.forEach(({ onFulfilled }) => {
+          // onFulfilled(this.result)
+          onFulfilled();
+        });
+      }
+    };
+    const reject = (result) => {
+      if (this.state === PENDING) {
+        this.state = REJECTED;
+        this.result = result;
+        this.#handlers.forEach(({ onRejected }) => {
+          // onRejected(this.result)
+          onRejected();
+        });
+      }
+    };
+
+    func(resolve, reject);
+  }
+
+  // then方法
+  // 1.返回新Promise实例
+  // 2.获取任意返回值
+  // 2.1处理返回值
+  // 2.2处理异常
+  //1.添加实例方法
+  then(onFulfilled, onRejected) {
+    //2.参数判断(参考文档)
+    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (x) => x;
+    onRejected =
+      typeof onRejected === "function"
+        ? onRejected
+        : (x) => {
+            throw x;
+          };
+
+    const p2 = new HMPromise((resolve, reject) => {
+      //执行成功的回调
+      if (this.state === FULFILLED) {
+        runAsynctask(() => {
+          try {
+            //获取返回值
+            const x = onFulfilled(this.result);
+            //处理重复引用
+            if (x === p2) {
+              throw new TypeError(
+                "Chaining cycle detected for promise #<Promise>"
+              );
+            }
+            // console.log("x",x);
+            // 1.处理返回值Promise
+            if (x instanceof HMPromise) {
+              //2.调用then方法
+              x.then(
+                (res) => resolve(res),
+                (err) => reject(err)
+              );
+            } else {
+              //处理返回值
+              resolve(x);
+            }
+          } catch (error) {
+            // console.log("捕获异常:"+error);
+            reject(error);
+          }
+        });
+      } else if (this.state === REJECTED) {
+        runAsynctask(() => {
+          onRejected(this.result);
+        });
+      } else if (this.state === PENDING) {
+        //是pending状态的时候还不需要去执行函数,可以先保存起来
+        this.#handlers.push({
+          onFulfilled: () => {
+            runAsynctask(() => {
+              onFulfilled(this.result);
+            });
+          },
+          onRejected: () => {
+            runAsynctask(() => {
+              onRejected(this.result);
+            });
+          },
+        });
+      }
+    });
+    return p2;
+  }
+}
+
+/* 原生Promise测试 */
+//    const p=new Promise((resolve,reject)=>{
+//     resolve(1)
+//    })
+//    const p2=p.then(res=>{
+//     return p2
+//    })
+/* 报错信息:Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise> */
+
+/* 测试  手写Promise */
+const p = new HMPromise((resolve, reject) => {
+  resolve(1);
+});
+
+const p2 = p.then((res) => {
+  return p2;
+});
+
+p2.then(
+  (res) => {},
+  (err) => {
+    console.log("err", err);
+  }
+);
+

10-链式编程-rejected 状态

// 函数封装
+function runAsynctask(callback) {
+  if (typeof queueMicrotask === "function") {
+    queueMicrotask(callback);
+  } else if (typeof MutationObserver === "function") {
+    const obs = new MutationObserver(callback);
+    const divNode = document.createElement("div");
+    obs.observe(divNode, { childList: true });
+    divNode.innerHTML = "hhh";
+  } else {
+    setTimeout(callback, 0);
+  }
+}
+
+const PENDING = "pending";
+const FULFILLED = "fulfilled";
+const REJECTED = "rejected";
+
+class HMPromise {
+  // 添加状态(pending/fulfilled/rejected)
+  // 添加原因
+  state = PENDING;
+  result = undefined;
+  // 定义实例属性,用来保存我们的回调函数
+  //# 私有的,外部访问不到
+  #handlers = []; //[{onFulfilled,onRejected}...]
+
+  constructor(func) {
+    const resolve = (result) => {
+      //状态不可逆
+      if (this.state === PENDING) {
+        //调整resolve/reject
+        this.state = FULFILLED;
+        this.result = result;
+        //遍历取出
+        this.#handlers.forEach(({ onFulfilled }) => {
+          // onFulfilled(this.result)
+          onFulfilled();
+        });
+      }
+    };
+    const reject = (result) => {
+      if (this.state === PENDING) {
+        this.state = REJECTED;
+        this.result = result;
+        this.#handlers.forEach(({ onRejected }) => {
+          // onRejected(this.result)
+          onRejected();
+        });
+      }
+    };
+
+    func(resolve, reject);
+  }
+
+  // then方法
+  //1.处理异常
+  // 2.获取返回值
+  // 3.抽取函数
+  // 4.调用函数
+  then(onFulfilled, onRejected) {
+    //2.参数判断(参考文档)
+    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (x) => x;
+    onRejected =
+      typeof onRejected === "function"
+        ? onRejected
+        : (x) => {
+            throw x;
+          };
+
+    const p2 = new HMPromise((resolve, reject) => {
+      //执行成功的回调
+      if (this.state === FULFILLED) {
+        runAsynctask(() => {
+          try {
+            //获取返回值
+            const x = onFulfilled(this.result);
+            //处理重复引用
+            if (x === p2) {
+              throw new TypeError(
+                "Chaining cycle detected for promise #<Promise>"
+              );
+            }
+            // console.log("x",x);
+            // 1.处理返回值Promise
+            if (x instanceof HMPromise) {
+              //2.调用then方法
+              x.then(
+                (res) => resolve(res),
+                (err) => reject(err)
+              );
+            } else {
+              //处理返回值
+              resolve(x);
+            }
+          } catch (error) {
+            // console.log("捕获异常:"+error);
+            reject(error);
+          }
+        });
+      } else if (this.state === REJECTED) {
+        runAsynctask(() => {
+          //1.处理异常
+          try {
+            //2.获取返回值
+            const x = onRejected(this.result);
+            //3.下面有一步进行函数抽取的
+            //4 调用函数
+            resolvePromise(p2, x, resolve, reject);
+          } catch (err) {
+            reject(err);
+          }
+        });
+      } else if (this.state === PENDING) {
+        //是pending状态的时候还不需要去执行函数,可以先保存起来
+        this.#handlers.push({
+          onFulfilled: () => {
+            runAsynctask(() => {
+              // 1.处理异常
+              try {
+                //2.获取返回值
+                const x = onFulfilled(this.result);
+                //3.调用函数
+                resolvePromise(p2, x, resolve, reject);
+              } catch (error) {
+                reject(error);
+              }
+            });
+          },
+          onRejected: () => {
+            runAsynctask(() => {
+              // 1.处理异常
+              try {
+                //2.获取返回值
+                constx = onRejected(this.result);
+                //3.调用函数
+                resolvePromise(p2, x, resolve, reject);
+              } catch (error) {
+                reject(error);
+              }
+            });
+          },
+        });
+      }
+    });
+    return p2;
+  }
+}
+
+//3.抽取函数
+function resolvePromise(p2, x, resolve, reject) {
+  if (x === p2) {
+    throw new TypeError("Chaining cycle detected for promise #<Promise>");
+  }
+  // console.log("x",x);
+  // 1.处理返回值Promise
+  if (x instanceof HMPromise) {
+    //2.调用then方法
+    x.then(
+      (res) => resolve(res),
+      (err) => reject(err)
+    );
+  } else {
+    //处理返回值
+    resolve(x);
+  }
+}
+
+/* 原生Promise测试 */
+//    const p=new Promise((resolve,reject)=>{
+//     resolve(1)
+//    })
+//    const p2=p.then(res=>{
+//     return p2
+//    })
+/* 报错信息:Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise> */
+
+/* 测试  手写Promise */
+const p = new HMPromise((resolve, reject) => {
+  setTimeout(() => {
+    resolve(1);
+  }, 2000);
+});
+
+const p2 = p.then((res) => {
+  throw "error";
+  //  return p2
+  // return 2
+  return new HMPromise((resolve, reject) => {
+    resolve("HMPromise-2");
+  });
+});
+
+p2.then(
+  (res) => {
+    console.log("res:", res);
+  },
+  (err) => {
+    console.log("err", err);
+  }
+);
+

11-实例方法-catch-finally

// 函数封装
+function runAsynctask(callback) {
+  if (typeof queueMicrotask === "function") {
+    queueMicrotask(callback);
+  } else if (typeof MutationObserver === "function") {
+    const obs = new MutationObserver(callback);
+    const divNode = document.createElement("div");
+    obs.observe(divNode, { childList: true });
+    divNode.innerHTML = "hhh";
+  } else {
+    setTimeout(callback, 0);
+  }
+}
+
+const PENDING = "pending";
+const FULFILLED = "fulfilled";
+const REJECTED = "rejected";
+
+class HMPromise {
+  // 添加状态(pending/fulfilled/rejected)
+  // 添加原因
+  state = PENDING;
+  result = undefined;
+  // 定义实例属性,用来保存我们的回调函数
+  //# 私有的,外部访问不到
+  #handlers = []; //[{onFulfilled,onRejected}...]
+
+  constructor(func) {
+    const resolve = (result) => {
+      //状态不可逆
+      if (this.state === PENDING) {
+        //调整resolve/reject
+        this.state = FULFILLED;
+        this.result = result;
+        //遍历取出
+        this.#handlers.forEach(({ onFulfilled }) => {
+          // onFulfilled(this.result)
+          onFulfilled();
+        });
+      }
+    };
+    const reject = (result) => {
+      if (this.state === PENDING) {
+        this.state = REJECTED;
+        this.result = result;
+        this.#handlers.forEach(({ onRejected }) => {
+          // onRejected(this.result)
+          onRejected();
+        });
+      }
+    };
+
+    //   处理异常
+    try {
+      func(resolve, reject);
+    } catch (error) {
+      reject(error);
+    }
+  }
+
+  // then方法
+  //1.处理异常
+  // 2.获取返回值
+  // 3.抽取函数
+  // 4.调用函数
+  then(onFulfilled, onRejected) {
+    //2.参数判断(参考文档)
+    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (x) => x;
+    onRejected =
+      typeof onRejected === "function"
+        ? onRejected
+        : (x) => {
+            throw x;
+          };
+
+    const p2 = new HMPromise((resolve, reject) => {
+      //执行成功的回调
+      if (this.state === FULFILLED) {
+        runAsynctask(() => {
+          try {
+            //获取返回值
+            const x = onFulfilled(this.result);
+            //处理重复引用
+            if (x === p2) {
+              throw new TypeError(
+                "Chaining cycle detected for promise #<Promise>"
+              );
+            }
+            // console.log("x",x);
+            // 1.处理返回值Promise
+            if (x instanceof HMPromise) {
+              //2.调用then方法
+              x.then(
+                (res) => resolve(res),
+                (err) => reject(err)
+              );
+            } else {
+              //处理返回值
+              resolve(x);
+            }
+          } catch (error) {
+            // console.log("捕获异常:"+error);
+            reject(error);
+          }
+        });
+      } else if (this.state === REJECTED) {
+        runAsynctask(() => {
+          //1.处理异常
+          try {
+            //2.获取返回值
+            const x = onRejected(this.result);
+            //3.下面有一步进行函数抽取的
+            //4 调用函数
+            resolvePromise(p2, x, resolve, reject);
+          } catch (err) {
+            reject(err);
+          }
+        });
+      } else if (this.state === PENDING) {
+        //是pending状态的时候还不需要去执行函数,可以先保存起来
+        this.#handlers.push({
+          onFulfilled: () => {
+            runAsynctask(() => {
+              // 1.处理异常
+              try {
+                //2.获取返回值
+                const x = onFulfilled(this.result);
+                //3.调用函数
+                resolvePromise(p2, x, resolve, reject);
+              } catch (error) {
+                reject(error);
+              }
+            });
+          },
+          onRejected: () => {
+            runAsynctask(() => {
+              // 1.处理异常
+              try {
+                //2.获取返回值
+                constx = onRejected(this.result);
+                //3.调用函数
+                resolvePromise(p2, x, resolve, reject);
+              } catch (error) {
+                reject(error);
+              }
+            });
+          },
+        });
+      }
+    });
+    return p2;
+  }
+
+  /**
+   * catch方法
+   * 1.内部调用then方法
+   * 2.处理异常
+   */
+  catch(onRejected) {
+    //1.内部调用then方法(MDN文档中说的如是)
+    return this.then(undefined, onRejected);
+  }
+
+  /**
+   * finally方法
+   * 1.内部调用then方法
+   */
+  finally(onFinally) {
+    return this.then(onFinally, onFinally);
+  }
+}
+
+//3.抽取函数
+function resolvePromise(p2, x, resolve, reject) {
+  if (x === p2) {
+    throw new TypeError("Chaining cycle detected for promise #<Promise>");
+  }
+  // console.log("x",x);
+  // 1.处理返回值Promise
+  if (x instanceof HMPromise) {
+    //2.调用then方法
+    x.then(
+      (res) => resolve(res),
+      (err) => reject(err)
+    );
+  } else {
+    //处理返回值
+    resolve(x);
+  }
+}
+
+/* 测试  手写Promise */
+const p = new HMPromise((resolve, reject) => {
+  // resolve("LLLL")
+  // reject("reject-err")
+  //需要处理实例化的异常
+  throw "throw err";
+});
+
+p.then((res) => {
+  console.log("res", res);
+})
+  .catch((err) => {
+    console.log("err", err);
+  })
+  .finally(() => {
+    console.log("finally");
+  });
+

12-静态方法

// 函数封装
+function runAsynctask(callback) {
+  if (typeof queueMicrotask === "function") {
+    queueMicrotask(callback);
+  } else if (typeof MutationObserver === "function") {
+    const obs = new MutationObserver(callback);
+    const divNode = document.createElement("div");
+    obs.observe(divNode, { childList: true });
+    divNode.innerHTML = "hhh";
+  } else {
+    setTimeout(callback, 0);
+  }
+}
+
+const PENDING = "pending";
+const FULFILLED = "fulfilled";
+const REJECTED = "rejected";
+
+class HMPromise {
+  // 添加状态(pending/fulfilled/rejected)
+  // 添加原因
+  state = PENDING;
+  result = undefined;
+  // 定义实例属性,用来保存我们的回调函数
+  //# 私有的,外部访问不到
+  #handlers = []; //[{onFulfilled,onRejected}...]
+
+  constructor(func) {
+    const resolve = (result) => {
+      //状态不可逆
+      if (this.state === PENDING) {
+        //调整resolve/reject
+        this.state = FULFILLED;
+        this.result = result;
+        //遍历取出
+        this.#handlers.forEach(({ onFulfilled }) => {
+          // onFulfilled(this.result)
+          onFulfilled();
+        });
+      }
+    };
+    const reject = (result) => {
+      if (this.state === PENDING) {
+        this.state = REJECTED;
+        this.result = result;
+        this.#handlers.forEach(({ onRejected }) => {
+          // onRejected(this.result)
+          onRejected();
+        });
+      }
+    };
+
+    //   处理异常
+    try {
+      func(resolve, reject);
+    } catch (error) {
+      reject(error);
+    }
+  }
+
+  // then方法
+  //1.处理异常
+  // 2.获取返回值
+  // 3.抽取函数
+  // 4.调用函数
+  then(onFulfilled, onRejected) {
+    //2.参数判断(参考文档)
+    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (x) => x;
+    onRejected =
+      typeof onRejected === "function"
+        ? onRejected
+        : (x) => {
+            throw x;
+          };
+
+    const p2 = new HMPromise((resolve, reject) => {
+      //执行成功的回调
+      if (this.state === FULFILLED) {
+        runAsynctask(() => {
+          try {
+            //获取返回值
+            const x = onFulfilled(this.result);
+            //处理重复引用
+            if (x === p2) {
+              throw new TypeError(
+                "Chaining cycle detected for promise #<Promise>"
+              );
+            }
+            // console.log("x",x);
+            // 1.处理返回值Promise
+            if (x instanceof HMPromise) {
+              //2.调用then方法
+              x.then(
+                (res) => resolve(res),
+                (err) => reject(err)
+              );
+            } else {
+              //处理返回值
+              resolve(x);
+            }
+          } catch (error) {
+            // console.log("捕获异常:"+error);
+            reject(error);
+          }
+        });
+      } else if (this.state === REJECTED) {
+        runAsynctask(() => {
+          //1.处理异常
+          try {
+            //2.获取返回值
+            const x = onRejected(this.result);
+            //3.下面有一步进行函数抽取的
+            //4 调用函数
+            resolvePromise(p2, x, resolve, reject);
+          } catch (err) {
+            reject(err);
+          }
+        });
+      } else if (this.state === PENDING) {
+        //是pending状态的时候还不需要去执行函数,可以先保存起来
+        this.#handlers.push({
+          onFulfilled: () => {
+            runAsynctask(() => {
+              // 1.处理异常
+              try {
+                //2.获取返回值
+                const x = onFulfilled(this.result);
+                //3.调用函数
+                resolvePromise(p2, x, resolve, reject);
+              } catch (error) {
+                reject(error);
+              }
+            });
+          },
+          onRejected: () => {
+            runAsynctask(() => {
+              // 1.处理异常
+              try {
+                //2.获取返回值
+                constx = onRejected(this.result);
+                //3.调用函数
+                resolvePromise(p2, x, resolve, reject);
+              } catch (error) {
+                reject(error);
+              }
+            });
+          },
+        });
+      }
+    });
+    return p2;
+  }
+
+  /**
+   * catch方法
+   * 1.内部调用then方法
+   * 2.处理异常
+   */
+  catch(onRejected) {
+    //1.内部调用then方法(MDN文档中说的如是)
+    return this.then(undefined, onRejected);
+  }
+
+  /**
+   * finally方法
+   * 1.内部调用then方法
+   */
+  finally(onFinally) {
+    return this.then(onFinally, onFinally);
+  }
+
+  /**
+   * 静态方法-resolve
+   * 1.判断传入值
+   * 2.1.Promise直接返回
+   * 2.2.转为Promise并返回(fulfilled状态)
+   */
+  static resolve(value) {
+    //1.判断传入值
+    if (value instanceof HMPromise) {
+      // 2.1.Promise直接返回
+      return value;
+    }
+    // 2.2.转为Promise并返回(fulfilled状态)
+    return new HMPromise((resolve) => {
+      resolve(value);
+    });
+  }
+
+  /**
+   *  静态方法-reject
+   * 1.返回rejected状态的Promise
+   *  */
+  static reject(value) {
+    // 1.返回rejected状态的Promise
+    return new HMPromise((undefined, reject) => {
+      reject(value);
+    });
+  }
+
+  /**
+   * 静态方法啊-race
+   * 1、返回Promise
+   * 2、判断是否为数组 错误信息(Argument is not iterable)
+   * 3、等待一个敲定
+   */
+  static race(promises) {
+    //1、返回一个Promise
+    return new HMPromise((resolve, reject) => {
+      //2、判断是否为数组
+      if (!Array.isArray(promises)) {
+        return reject(new TypeError("Argument is not iterable"));
+      }
+      // 3、等待一个敲定
+      promises.forEach((p) => {
+        HMPromise.resolve(p).then(
+          (res) => resolve(res),
+          (err) => {
+            reject(err);
+          }
+        );
+      });
+    });
+  }
+
+  /**
+   * 静态方法-all
+   * 1.返回Promise实例
+   * 2.判断是否为数组 错误信息 :Argument is not iterable
+   * 3.空数组直接兑现
+   * 4.处理全部兑现
+   *    4.1记录结果
+   *    4.2.判断全部兑现
+   * 5.处理第一个拒绝
+   */
+  static all(promises) {
+    //1.返回Promise实例
+    return new Promise((resolve, reject) => {
+      // 2.判断是否为数组
+      if (!Array.isArray(promises)) {
+        return reject(new TypeError("Argument is not iterable"));
+      }
+      // 3.空数组直接兑现
+      promises.length === 0 && resolve(promises);
+      // 4.1记录结果
+      const result = [];
+      let count = 0;
+      promises.forEach((p, index) => {
+        HMPromise.resolve(p).then(
+          (res) => {
+            result[index] = res; //用索引来填充数组,不要去用push
+            //4.2.判断全部兑现,用次数来判断(保证能获取到所有的结果!!!),不要用记录结果数组的长度判断
+            count++;
+            count === promises.length && resolve(result);
+          },
+          (err) => {
+            // 5.处理第一个拒绝
+            reject(err);
+          }
+        );
+      });
+    });
+  }
+
+  /**
+   * 静态方法- allsettled
+   * 1.返回Promise
+   * 2.数组判断 错误信息:Argument is not iterable
+   * 3.为空数组直接敲定
+   * 4.等待全部敲定
+   * 4.1记录结果
+   * 4.2处理兑现{status:'fulfilled',value:''}
+   * 4.3处理拒绝{status:'rejected',reason:''}
+   */
+  static allSettled(promises) {
+    //1.返回Promise
+    return new HMPromise((resolve, reject) => {
+      //2.数组判断
+      if (!Array.isArray(promises)) {
+        return reject(new TypeError("Argument is not iterable"));
+      }
+      //3.数组为空直接敲定
+      promises.length === 0 && resolve(promises);
+      //4.等待全部敲定
+      //4.1记录结果
+      const result = [];
+      let count = 0;
+      promises.forEach((p, index) => {
+        HMPromise.resolve(p).then(
+          (res) => {
+            // 4.2处理兑现{status:'fulfilled',value:''}
+            result[index] = { status: FULFILLED, value: res };
+            count++;
+            count === promises.length && resolve(result);
+          },
+          (err) => {
+            // 4.3处理拒绝{status:'rejected',reason:''}
+            result[index] = { status: REJECTED, reason: err };
+            count++;
+            count === promises.length && resolve(result);
+          }
+        );
+      });
+    });
+  }
+
+  /**
+   * 静态方法-any
+   * 1.返回Promise,数组判断 错误信息:Argument is not iterable
+   * 2.空数组直接拒绝 aggregateError: All promise were rejected
+   * AggregateError([错误原因1...],All Promise were rejected)
+   * 3.等待结果
+   *  3.1.第一个兑现
+   *  3.2.全部拒绝
+   */
+  static any(promises) {
+    // 返回Promise,数组判断
+    return new HMPromise((resolve, reject) => {
+      if (!Array.isArray(promises)) {
+        return reject(new TypeError("Argument is not iterable"));
+      }
+      // 2.空数组直接拒绝 aggregateError: All promise were rejected
+      promises.length === 0 &&
+        reject(new AggregateError(promises, "All promise were rejected"));
+      //3.等待结果
+      const errors = [];
+      let count = 0;
+      promises.forEach((p, index) => {
+        HMPromise.resolve(p).then(
+          (res) => {
+            //3.1 第一个兑现
+            resolve(res);
+          },
+          (err) => {
+            //3.2 全部拒绝
+            errors[index] = err;
+            count++;
+            count === promises.length &&
+              reject(new AggregateError(errors, "All promise were rejected"));
+          }
+        );
+      });
+    });
+  }
+}
+
+//3.抽取函数
+function resolvePromise(p2, x, resolve, reject) {
+  if (x === p2) {
+    throw new TypeError("Chaining cycle detected for promise #<Promise>");
+  }
+  // console.log("x",x);
+  // 1.处理返回值Promise
+  if (x instanceof HMPromise) {
+    //2.调用then方法
+    x.then(
+      (res) => resolve(res),
+      (err) => reject(err)
+    );
+  } else {
+    //处理返回值
+    resolve(x);
+  }
+}
+
+/* 静态方法 resolve*/
+HMPromise.resolve(
+  new HMPromise((resolve, reject) => {
+    // resolve("成功")
+    // reject("失败")
+    // throw "error"
+  })
+).then(
+  (res) => {
+    console.log("res", res);
+  },
+  (err) => {
+    console.log("err", err);
+  }
+);
+
+HMPromise.resolve("hello").then((res) => {
+  // console.log("res",res);
+});
+
+/* 静态方法 reject */
+HMPromise.reject("error").catch((res) => {
+  // console.log("res",res);
+});
+
+/* 测试代码 race */
+//   const p1=new HMPromise((resolve,reject)=>{
+//     setTimeout(()=>{
+//         resolve(1)
+//     },2000)
+//   })
+//   const p2=new HMPromise((resolve,reject)=>{
+//     setTimeout(()=>{
+//         reject(2)
+//     },1000)
+//   })
+
+//   HMPromise.race([p1,p2,"itheima"]).then(res=>{
+//     console.log("res",res);
+//   },err=>{
+//     console.log("err",err);
+//   })
+
+/* 测试代码 - all */
+// const p1=HMPromise.resolve(1)
+// const p2=new HMPromise((resolve,reject)=>{
+//   setTimeout(()=>{
+//       resolve(2)
+//       // reject("error")
+//   },1000)
+// })
+// const p3=3;
+// HMPromise.all([p1,p2,p3]).then(res=>{
+//   console.log("res",res);
+// },err=>{
+//   console.log("err",err);
+// })
+
+/**测试代码 原生Promise
+ *   */
+// const p1 =HMPromise.resolve(1)
+// const p2=2;
+// const p3=new HMPromise((resolve,reject)=>{
+//   setTimeout(()=>{
+//     reject(3)
+//   },1000)
+// })
+
+/* 静态方法 测试 -- allsettled */
+// HMPromise.allSettled([p1,p2,p3]).then(res=>{
+//   console.log("res",res);
+// },err=>{
+//   console.log("err",err);
+// })
+
+/**
+ * 原生静态方法--Promise.allsettled
+ * 1.传入Promise都变成已敲定,即可获取兑现的结果
+ * 2.结果数组[{status: 'fulfilled', value: 1},
+ * {status: 'fulfilled', value: 2}
+ * {status: 'rejected', reason: 3}]
+ * 3.结果数组的顺序和传入的Promise数组顺序一致
+ * 4.空数组直接兑现
+ * 5.不传入数组,直接报错
+ */
+// Promise.allSettled([p1,p2,p3]).then(res=>{
+//   console.log("res",res);
+// },err=>{
+//   console.log("err",err);
+// })
+
+/** 测试代码  原生 Promise */
+const p1 = new HMPromise((resolve, reject) => {
+  setTimeout(() => {
+    reject(1);
+  }, 2000);
+});
+
+const p2 = 2;
+
+const p3 = new HMPromise((resolve, reject) => {
+  setTimeout(() => {
+    resolve(3);
+    // reject(3)
+  }, 1000);
+});
+
+/**
+ * 测试静态方法-any
+ * 1.参数:Promise数组
+ * 2.结果:
+ * 2.1获得第一个成功的原因!
+ * 2.2获得所有的拒绝原因 aggregateError: All promise were rejected
+ * 2.3 传入空数组,直接拒绝 aggregateError: All promise were rejected
+ * 2.4 不传入数组,直接报错
+ */
+// Promise.any([p1,p2,p3]).then(res=>{
+//   console.log("res",res);
+// },err=>{
+//   console.log("err",err);
+// })
+
+/* 测试手写 Any */
+HMPromise.any([]).then(
+  (res) => {
+    console.log("res", res);
+  },
+  (err) => {
+    console.dir(err);
+  }
+);
+

设计模式

单例模式

<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>单例模式</title>
+</head>
+<body>
+    <h1>单例模式</h1>
+    <script>
+        class SingleTon{
+            static #instance
+            static getInstance(){
+                if(this.#instance!==undefined){
+                    this.#instance=new SingleTon()
+                }
+                return this.#instance
+            }
+        }
+        let s1=SingleTon.getInstance()
+        let s2=SingleTon.getInstance()
+        console.log(s1===s2);
+    </script>
+</body>
+</html>
+

观察者模式

// 被观察者
+class Subject {
+  constructor() {
+    this.observerList = [];
+  }
+
+  addObserver(observer) {
+    this.observerList.push(observer);
+  }
+
+  removeObserver(observer) {
+    const index = this.observerList.findIndex((o) => o.name === observer.name);
+    this.observerList.splice(index, 1);
+  }
+
+  notifyObservers(message) {
+    const observers = this.observerList;
+    observers.forEach((observer) => observer.notified(message));
+  }
+}
+// 观察者
+class Observer {
+  constructor(name, subject) {
+    this.name = name;
+    // 观察者主动申请加入被观察者的列表
+    if (subject) {
+      subject.addObserver(this);
+    }
+  }
+
+  notified(message) {
+    console.log(this.name, "got message", message);
+  }
+}
+
+//   使用
+const subject = new Subject();
+const observerA = new Observer("observerA", subject);
+const observerB = new Observer("observerB");
+subject.addObserver(observerB); //被观察者主动将观察者加入列表
+subject.notifyObservers("Hello from subject");
+subject.removeObserver(observerA);
+subject.notifyObservers("Hello again");
+

发布订阅

class PubSub {
+  constructor() {
+    this.messages = {};
+    this.listeners = {};
+  }
+
+  publish(type, content) {
+    const existContent = this.messages[type];
+    if (!existContent) {
+      this.messages[type] = [];
+    }
+    this.messages[type].push(content);
+  }
+
+  subscribe(type, cb) {
+    const existListener = this.listeners[type];
+    if (!existListener) {
+      this.listeners[type] = [];
+    }
+    this.listeners[type].push(cb);
+  }
+
+  notify(type) {
+    const messages = this.messages[type];
+    const subscribers = this.listeners[type] || [];
+    subscribers.forEach((cb) => cb(messages));
+  }
+}
+
+class Publisher {
+  constructor(name, context) {
+    this.name = name;
+    this.context = context;
+  }
+
+  publish(type, content) {
+    this.context.publish(type, content);
+  }
+}
+
+class Subscriber {
+  constructor(name, context) {
+    this.name = name;
+    this.context = context;
+  }
+
+  subscribe(type, cb) {
+    this.context.subscribe(type, cb);
+  }
+}
+
+function main() {
+  const TYPE_A = "music";
+  const TYPE_B = "movie";
+  const TYPE_C = "novel";
+
+  const pubsub = new PubSub();
+
+  const publisherA = new Publisher("publisherA", pubsub);
+  publisherA.publish(TYPE_A, "we are young");
+  publisherA.publish(TYPE_B, "the silicon valley");
+  const publisherB = new Publisher("publisherB", pubsub);
+  publisherB.publish(TYPE_A, "stronger");
+  const publisherC = new Publisher("publisherC", pubsub);
+  publisherC.publish(TYPE_B, "imitation game");
+
+  const subscriberA = new Subscriber("subscriberA", pubsub);
+  subscriberA.subscribe(TYPE_A, (res) => {
+    console.log("subscriberA received", res);
+  });
+  const subscriberB = new Subscriber("subscriberB", pubsub);
+  subscriberB.subscribe(TYPE_C, (res) => {
+    console.log("subscriberB received", res);
+  });
+  const subscriberC = new Subscriber("subscriberC", pubsub);
+  subscriberC.subscribe(TYPE_B, (res) => {
+    console.log("subscriberC received", res);
+  });
+
+  pubsub.notify(TYPE_A);
+  pubsub.notify(TYPE_B);
+  pubsub.notify(TYPE_C);
+}
+
+main();
+
+// subscriberA received [ 'we are young', 'stronger' ]
+// subscriberC received [ 'the silicon valley', 'imitation game' ]
+// subscriberB received undefined
+

发布订阅另一种

<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>发布订阅模式</title>
+</head>
+<body>
+    <h1>发布订阅模式</h1>
+    <button id="on">注册事件</button>
+    <button id="off">注销事件event1</button>
+    <button id="emit">触发事件</button>
+    <button id="onOnce">注册一次性事件</button>
+    <button id="emitOnce">触发一次性事件</button>
+    <script>
+        class PubSub{
+            //存储事件
+            #handlers={
+                //结构:事件名:[callback1,callback2]
+            }
+            //注册事件
+            $on(event,func){
+                if(this.#handlers[event]==undefined){
+                    this.#handlers[event]=[]
+                }
+                this.#handlers[event].push(func)
+            }
+            //触发事件
+            $emit(event,...args){
+                let funcs=this.#handlers[event] || []
+                funcs.forEach((callback)=>{
+                    callback(...args)
+                })
+            }
+            //注销事件
+            $off(event){
+                this.#handlers[event]=undefined
+            }
+            //一次性触发事件
+            $once(event,callback){
+                this.$on(event,(...args)=>{
+                    callback(...args)
+                    this.$off(event)
+                })
+            }
+        }
+
+        const bus=new PubSub()
+        //进行测试
+        on.addEventListener("click",function(){
+            bus.$on("event1",()=>console.log("event1"))
+            bus.$on("event2",(a,b)=>console.log(a,b))
+            bus.$on("event2",(a,b)=>console.log("event2",a,b))
+        })
+        emit.addEventListener('click',function(){
+            bus.$emit("event1")
+            bus.$emit("event2",1,2)
+        })
+        off.addEventListener('click',function(){
+            bus.$off('event1')
+        })
+        onOnce.addEventListener('click',function(){
+            bus.$once("event3",()=>{console.log("读书很难吗?");})
+        })
+        emitOnce.addEventListener('click',function(){
+            bus.$emit("event3")
+        })
+    </script>
+</body>
+</html>
+
`,65),o=[e];function c(l,i){return s(),a("div",null,o)}const k=n(p,[["render",c],["__file","手写题.html.vue"]]),r=JSON.parse('{"path":"/base/%E6%89%8B%E5%86%99%E9%A2%98.html","title":"手写题","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"前端面试常考","slug":"前端面试常考","link":"#前端面试常考","children":[{"level":3,"title":"1.实现一个 call 函数","slug":"_1-实现一个-call-函数","link":"#_1-实现一个-call-函数","children":[]},{"level":3,"title":"2.实现一个 apply 函数","slug":"_2-实现一个-apply-函数","link":"#_2-实现一个-apply-函数","children":[]},{"level":3,"title":"3.实现一个 bind 函数","slug":"_3-实现一个-bind-函数","link":"#_3-实现一个-bind-函数","children":[]},{"level":3,"title":"4.实现 instanceof","slug":"_4-实现-instanceof","link":"#_4-实现-instanceof","children":[]},{"level":3,"title":"5.实现一个 new","slug":"_5-实现一个-new","link":"#_5-实现一个-new","children":[]},{"level":3,"title":"6.Generator-id 生成器","slug":"_6-generator-id-生成器","link":"#_6-generator-id-生成器","children":[]},{"level":3,"title":"7.函数柯里化一道面试题","slug":"_7-函数柯里化一道面试题","link":"#_7-函数柯里化一道面试题","children":[]},{"level":3,"title":"8.实现一个管道函数","slug":"_8-实现一个管道函数","link":"#_8-实现一个管道函数","children":[]},{"level":3,"title":"9.手写 loadsh_get 方法","slug":"_9-手写-loadsh-get-方法","link":"#_9-手写-loadsh-get-方法","children":[]},{"level":3,"title":"10.手写 nextTick 方法","slug":"_10-手写-nexttick-方法","link":"#_10-手写-nexttick-方法","children":[]},{"level":3,"title":"11.allComplete","slug":"_11-allcomplete","link":"#_11-allcomplete","children":[]},{"level":3,"title":"12.防抖与节流","slug":"_12-防抖与节流","link":"#_12-防抖与节流","children":[]},{"level":3,"title":"13.深拷贝浅拷贝","slug":"_13-深拷贝浅拷贝","link":"#_13-深拷贝浅拷贝","children":[]}]},{"level":2,"title":"手写 Promise","slug":"手写-promise","link":"#手写-promise","children":[{"level":3,"title":"完整 Promise","slug":"完整-promise","link":"#完整-promise","children":[]},{"level":3,"title":"01-构造函数","slug":"_01-构造函数","link":"#_01-构造函数","children":[]},{"level":3,"title":"02-状态及原因","slug":"_02-状态及原因","link":"#_02-状态及原因","children":[]},{"level":3,"title":"03-then 的方法-成功和失败的回调","slug":"_03-then-的方法-成功和失败的回调","link":"#_03-then-的方法-成功和失败的回调","children":[]},{"level":3,"title":"04-then 的方法","slug":"_04-then-的方法","link":"#_04-then-的方法","children":[]},{"level":3,"title":"05-异步任务 API","slug":"_05-异步任务-api","link":"#_05-异步任务-api","children":[]},{"level":3,"title":"06-异步任务-函数封装","slug":"_06-异步任务-函数封装","link":"#_06-异步任务-函数封装","children":[]},{"level":3,"title":"7-链式编程-处理返回值异常","slug":"_7-链式编程-处理返回值异常","link":"#_7-链式编程-处理返回值异常","children":[]},{"level":3,"title":"8-链式编程-处理返回 Promise","slug":"_8-链式编程-处理返回-promise","link":"#_8-链式编程-处理返回-promise","children":[]},{"level":3,"title":"9-链式编程-处理重复引用","slug":"_9-链式编程-处理重复引用","link":"#_9-链式编程-处理重复引用","children":[]},{"level":3,"title":"10-链式编程-rejected 状态","slug":"_10-链式编程-rejected-状态","link":"#_10-链式编程-rejected-状态","children":[]},{"level":3,"title":"11-实例方法-catch-finally","slug":"_11-实例方法-catch-finally","link":"#_11-实例方法-catch-finally","children":[]},{"level":3,"title":"12-静态方法","slug":"_12-静态方法","link":"#_12-静态方法","children":[]}]},{"level":2,"title":"设计模式","slug":"设计模式","link":"#设计模式","children":[{"level":3,"title":"单例模式","slug":"单例模式","link":"#单例模式","children":[]},{"level":3,"title":"观察者模式","slug":"观察者模式","link":"#观察者模式","children":[]},{"level":3,"title":"发布订阅","slug":"发布订阅","link":"#发布订阅","children":[]},{"level":3,"title":"发布订阅另一种","slug":"发布订阅另一种","link":"#发布订阅另一种","children":[]}]}],"filePathRelative":"base/手写题.md","git":{"createdTime":1715941349000,"updatedTime":1715941349000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":1}]},"readingTime":{"minutes":30.05,"words":9016}}');export{k as comp,r as data}; diff --git "a/assets/\346\211\213\346\222\225\346\225\260\346\215\256\347\273\223\346\236\204.html-CZWs24TO.js" "b/assets/\346\211\213\346\222\225\346\225\260\346\215\256\347\273\223\346\236\204.html-CZWs24TO.js" new file mode 100644 index 0000000..7b7ea44 --- /dev/null +++ "b/assets/\346\211\213\346\222\225\346\225\260\346\215\256\347\273\223\346\236\204.html-CZWs24TO.js" @@ -0,0 +1,386 @@ +import{_ as n,o as s,c as a,e as p}from"./app-B-BkP2m_.js";const t={},e=p(`

手撕数据结构

class Stack {
+  constructor() {
+    this.stack = [];
+  }
+  pop() {
+    return this.stack.pop();
+  }
+  push(item) {
+    this.stack.push(item);
+  }
+  peek() {
+    return this.stack[this.getCount() - 1];
+  }
+  getCount() {
+    return this.stack.length;
+  }
+  isEmpty() {
+    return this.getCount() === 0;
+  }
+}
+//栈的几种操作:出栈、入栈、栈顶、栈是否为空、栈的大小
+

单链队列

export default class Queue{
+    constructor(){
+        this.queue=[]
+    }
+    enQueue(item){
+        this.queue.push(item)
+    }
+    deQueue(){
+        this.queue.shift()
+    }
+    getHeader(){
+        return this.queue[0]
+    }
+    getLength(){
+        return this.queue.length
+    }
+    isEmpty(){
+        return this.getLength()===0
+    }
+}
+

循环队列

class SqQueue {
+  constructor(length) {
+    this.queue = new Array(length + 1); //预留空位
+    //队头
+    this.first = 0;
+    //队尾
+    this.last = 0;
+    //当前队列的大小
+    this.size = 0;
+  }
+  enQueue(item) {
+    //判断队尾 + 1 是否为队头
+    //如果是就代表需要扩容数组(下面的一个判断条件是队列已满)
+    // % this.queue.length 是为了防止数组越界
+    if (this.isFull()) {
+      this.resize(this.getLength() * 2 + 1);
+    }
+    this.queue[this.last] = item;
+    this.size++;
+    this.last = (this.last + 1) % this.queue.length;
+  }
+  deQueue() {
+    let r = this.getHeader();
+    this.queue[this.first] = null;
+    this.first = (this.first + 1) % this.queue.length;
+    this.size--;
+    //判断当前队列大小是否过小
+    //为了保证不浪费空间,在队列空间等于总长度的四分之一时 且不为2时缩小总长度为当前的一半
+    if (this.size <= this.getLength() / 4 && this.getLength() % 2 === 0) {
+      this.resize(this.getLength() / 2 + 1);
+    }
+    return r;
+  }
+  getHeader() {
+    if (this.isEmpty()) {
+      throw Error("Queue is empty");
+    }
+    return this.queue[this.first];
+  }
+  getLength() {
+    return this.queue.length - 1;
+  }
+  isEmpty() {
+    return this.first === this.last;
+  }
+  isFull() {
+    return this.first === (this.last + 1) % this.queue.length;
+  }
+  resize(length) {
+    let q = new Array(length);
+    for (let i = 0; i < length; i++) {
+      q[i] = this.queue[(i + this.first) % this.queue.length];
+    }
+    this.queue = q;
+    this.first = 0;
+    this.last = this.size;
+  }
+}
+

单向链表

class Node{
+    constructor(value,next=null) {
+        this.value=value
+        this.next=next
+    }   
+}
+
+class LinkedList{
+    constructor(value) {
+        this.head=new Node(value)
+    }
+    //查找节点
+    findNode(value){
+        let currentNode=this.head
+        while(currentNode.value !==value  && currentNode!=null){
+            currentNode=currentNode.next
+        }
+        return currentNode;
+    }
+    //指定位置插入节点
+    insertAfter(value,newValue){
+        const newNode=new Node(newValue)
+        const currentNode=this.findNode(value)
+
+        newNode.next=currentNode.next
+        currentNode.next=newNode
+    }
+    //在尾部插入节点
+    append(value){
+        const newNode=new Node(value)
+        let currentNode=this.head;
+        while(currentNode.next){
+            currentNode=currentNode.next
+        }
+        currentNode.next=newNode
+    }
+    //在头部插入节点
+    prepend(value){
+        const newNode=new Node(value)
+        newNode.next=this.head
+        this.head=newNode
+    }
+    //删除指定节点
+    remove(value){
+        let currentNode=this.head;
+        let previousNode=null;
+
+        while(currentNode.value!=value){
+            previousNode=currentNode;
+            currentNode=currentNode.next
+        }
+        if(currentNode===this.head){
+            this.head=currentNode.next
+        }else{
+            previousNode.next=currentNode.next
+        }
+    }
+    //删除头部节点
+    removeHead(){
+        this.head=this.head.next
+    }
+    //删除尾部节点
+    removeTail(){
+        let currentNode=this.head;
+        let previousNode=null
+        while(currentNode.next){
+            previousNode=currentNode
+            currentNode=currentNode.next
+        }
+        previousNode.next=null
+    }
+    //遍历链表节点
+    traverse(){
+        let currentNode=this.head
+        while(currentNode){
+            console.log(currentNode.value);
+            currentNode=currentNode.next
+        }
+    }
+
+}
+
+
+//操作实例
+let list=new LinkedList(1)
+list.append(2)
+list.append(3)
+list.append(4)
+
+list.insertAfter(2,5)
+list.prepend(6)
+list.remove(3)
+list.removeHead()
+list.removeTail()
+

class MaxHeap {
+  constructor() {
+    this.heap = [];
+  }
+  size() {
+    return this.heap.length;
+  }
+  empty() {
+    return this.size() === 0;
+  }
+  add(item) {
+    this.heap.push(item);
+    this._shiftUp(this.size() - 1);
+  }
+  removeMax() {
+    this._shiftDown(0);
+  }
+  getParentIndex(k) {
+    return parseInt((k - 1) / 2);
+  }
+  getLeftIndex(k) {
+    return k * 2 + 1;
+  }
+  getRightIndex(k) {
+    return k * 2 + 2;
+  }
+  _shiftUp(k) {
+    //如果当前节点比父节点大,就交换
+    while (this.heap[k] > this.heap[this.getParentIndex(k)]) {
+      this._swap(k, this.getParentIndex(k));
+      //将索引变成父节点
+      k = this.getParentIndex(k);
+    }
+  }
+  _shiftDown(k) {
+    //交换首位并删除末尾
+    this._swap(k, this.size() - 1);
+    this.heap.splice(this.size() - 1, 1);
+    //判断节点是否有左孩子,因为二叉堆的特性,有右必有左
+    while (this.getLeftIndex(k) < this.size()) {
+      let j = this.getLeftIndex(k);
+      //判断是否有右孩子,并且右孩子是否大于左孩子
+      if (j + 1 < this.size() && this.heap[j + 1] > this.heap[j]) j++;
+      //判断父节点是否已经比子节点都大
+      if (this.heap[k] >= this.heap[j]) break;
+      this._swap(k, j);
+      k = j;
+    }
+  }
+  _swap(left, right) {
+    let rightValue = this.heap[right];
+    this.heap[right] = this.heap[left];
+    this.heap[left] = rightValue;
+  }
+}
+
+/* 
+堆的插入操作是单一节点的上浮,时间复杂度 O(logn)
+堆的删除操作是单一节点的下沉,时间复杂度 O(logn)
+注意建堆操作的时间复杂度是 O(n) // 不要误认为是O(nlogn),有两种建立堆的方式。。
+*/
+

二分搜索树

import Queue from "./3.实现一个单链队列";
+class Node{
+    constructor(value){
+        this.value=value;
+        this.left=null
+        this.right=null
+    }
+}
+
+class BST{
+    constructor(){
+        this.root=null
+        this.size=0;
+    }
+    getSize(){
+        return this.size
+    }
+    isEmpty(){
+        return this.size===0
+    }
+    addNode(v){
+        this.root=this._addChild(this.root,v)
+    }
+    //添加节点时,需要比较添加的节点值和当前节点值的大小
+    _addChild(node,v){
+        if(!node){
+            this.size++
+            return new Node(v)
+        }
+        if(node.value>v){
+            node.left=this._addChild(node.left,v)
+        }else if(node.value<v){
+            node.right=this._addChild(node.right,v)
+        }
+        return node 
+    }
+    //先序遍历:可以用于打印树的结构
+    preTraversal(){
+        this._pre(this.root)
+    }
+    _pre(node){
+        if(node){
+            console.log(node.value);
+            this._pre(node.left)
+            this._pre(node.right)
+        }
+    }
+    //中序遍历:可以用于排序,对于BST来说,中序遍历可以实现一次遍历就得到有序值
+    midTraversal(){
+        this._mid(this.root)
+    }
+    _mid(node){
+        if(node){
+            this._mid(node.left)
+            console.log(node.value);
+            this._mid(node.right)
+        }
+    }
+    //后续遍历:可以用于先操作子节点再操作父节点的场景
+    backTraversal(){
+        this._back(this.root)
+    }
+    _back(node){
+        if(node){
+            this._back(node.left);
+            this._back(node.right);
+            console.log(node.value);
+        }
+    }
+    //广度遍历
+    breadthTraversal(){
+        if(!this.root)return null
+        let q=new Queue()
+        //将根节点入队
+        q.enQueue(this.root)
+        //循环判断队列是否为空,为空代表树遍历完毕
+        while(!q.isEmpty()){
+            //将队首出队,判断是否有左右子树,有的话,就先左后右入队
+            let n=q.deQueue()
+            console.log(n.value);
+            if(n.left)q.enQueue(n.left)
+            if(n.right)q.enQueue(n.right)
+        }
+    }
+    getMin(){
+        return this._getMin(this.root).value
+    }
+    _getMin(node){
+        if(!node.left)return node
+        return this._getMin(node.left)
+    }
+    getMax(){
+        return this._getMax(this.root).value
+    }
+    _getMax(node){
+        if(!node.right)return node
+        return this._getMax(node.right)
+    }
+    //向下取整
+    floor(v){
+        let node =this._floor(this.root,v)
+        return node ?node.value:null
+    }
+    _floor(node,v){
+        if(!node)return null
+        if(node.value==v)return v
+        //如果当前节点值还比需要的值大,就继续递归
+        if(node.value>v){
+            return this._floor(node.left,v)
+        }
+        //如果节点还拥有右子树
+        let right=this._floor(node.right,v)
+        if(right)return right
+        return node
+    }
+    //向上取整的基本操作一样的
+    cell(v){
+        let node =this._floor(this.root,v)
+        return node?node.value:null
+    }
+    _cell(node,v){
+        if(!node)return null
+        if(node.value==v)return v
+        if(node.value<v){
+            return this._cell(node.right,v)
+        }
+        let left=this._floor(node.left,v)
+        if(left)return left
+        return node
+    }
+}
+
`,13),o=[e];function c(l,u){return s(),a("div",null,o)}const k=n(t,[["render",c],["__file","手撕数据结构.html.vue"]]),r=JSON.parse('{"path":"/algorithm/%E6%89%8B%E6%92%95%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84.html","title":"手撕数据结构","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"栈","slug":"栈","link":"#栈","children":[]},{"level":2,"title":"单链队列","slug":"单链队列","link":"#单链队列","children":[]},{"level":2,"title":"循环队列","slug":"循环队列","link":"#循环队列","children":[]},{"level":2,"title":"单向链表","slug":"单向链表","link":"#单向链表","children":[]},{"level":2,"title":"堆","slug":"堆","link":"#堆","children":[]},{"level":2,"title":"二分搜索树","slug":"二分搜索树","link":"#二分搜索树","children":[]}],"filePathRelative":"algorithm/手撕数据结构.md","git":{"createdTime":1715588813000,"updatedTime":1715588813000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":1}]},"readingTime":{"minutes":4.23,"words":1269}}');export{k as comp,r as data}; diff --git "a/assets/\346\212\200\345\267\247_\346\225\260\345\255\246\360\237\215\214.html-De2mi0NT.js" "b/assets/\346\212\200\345\267\247_\346\225\260\345\255\246\360\237\215\214.html-De2mi0NT.js" new file mode 100644 index 0000000..a63d1ed --- /dev/null +++ "b/assets/\346\212\200\345\267\247_\346\225\260\345\255\246\360\237\215\214.html-De2mi0NT.js" @@ -0,0 +1,140 @@ +import{_ as n,o as s,c as a,e as p}from"./app-B-BkP2m_.js";const t={},e=p(`

技巧_数学🍌

136.只出现一次的数字

/**
+ * @param {number[]} nums
+ * @return {number}
+ */
+var singleNumber = function(nums) {
+    //这里的空间复杂度挺高的!!!
+    // let obj={}
+    // nums.forEach((num,index)=>{
+    //     if(obj[num]){
+    //         obj[num]=obj[num]+1
+    //     }else{
+    //         obj[num]=1
+    //     }
+    // })
+    // for(let key in obj){
+    //     if(obj[key]==1){
+    //         return key
+    //     }
+    // }
+
+    //进行异或操作
+    // 2 ^ 2 ^ 1 = 0 ^ 1 = 1
+    let res=0
+    for(const num of nums){
+        res^=num
+    }
+    return res
+};
+

31.下一个排列

/**
+ * @param {number[]} nums
+ * @return {void} Do not return anything, modify nums in-place instead.
+ */
+var nextPermutation = function(nums) {
+    let len=nums.length
+    let i=len-2//注意这里为什么是这样!
+    //从后开始寻找非降序的元素
+    while(i>=0 && nums[i]>=nums[i+1]){
+        i--
+    }
+    if(i>=0){
+        let j=len-1
+        while(j>=0 && nums[i]>=nums[j]){//从后往前走找到大于之前的那个数,进行交换
+             j--
+        }
+        swap(nums,i,j)
+    }
+    reverse(nums,i+1)//翻转最开始找到数后面的的一些数字
+};
+
+function swap(nums,i,j){
+    let tmp=nums[i]
+    nums[i]=nums[j]
+    nums[j]=tmp
+}
+
+function reverse(nums,start){
+    let end=nums.length-1
+    while(start<end){
+        swap(nums,start,end)
+        start++
+        end--
+    }
+}
+

560. 和为 K 的子数组

/**
+ * @param {number[]} nums
+ * @param {number} k
+ * @return {number}
+ */
+const subarraySum = (nums, k) => {
+    /* 
+    遍历 nums 之前,我们让 -1 对应的前缀和为 0,这样通式在边界情况也成立。
+    即在遍历之前,map 初始放入 0:1 键值对(前缀和为0出现1次了)。
+     */
+    const map = { 0: 1 };//可以想象一下前几个的前缀和正好等于k
+    let prefixSum = 0;
+    let count = 0;
+
+    for (let i = 0; i < nums.length; i++) {
+        prefixSum += nums[i];
+        /* 前缀和之差等于k,只关心等于 k 的前缀和之差出现的次数c,就知道了有c个子数组求和等于k。 */
+        if (map[prefixSum - k]) {
+            count += map[prefixSum - k];
+        }
+
+        if (map[prefixSum]) {
+            map[prefixSum]++;
+        } else {
+            map[prefixSum] = 1;
+        }
+    }
+    return count;
+};
+

1. 两数之和

/**
+ * @param {number[]} nums
+ * @param {number} target
+ * @return {number[]}
+ */
+var twoSum = function (nums, target) {
+    const map = new Map()
+    for (let i = 0; i < nums.length; i++) {
+        if (map.has(target - nums[i])) {
+            return [map.get(target - nums[i]), i]
+        }
+        map.set(nums[i], i)
+    }
+};
+

49. 字母异位词分组

/**
+ * @param {string[]} strs
+ * @return {string[][]}
+ */
+var groupAnagrams = function(strs) {
+    //要点,就是将字符串排序后就是相同的东西了
+    //还有,就是map来进行存储,key 为排序后的字符串,value为一个数组
+    let map=new Map();
+    for(const str of strs){
+        //sort排序一下key
+        let key = str.split("").sort().join("");
+        if(map.has(key))map.get(key).push(str)
+        else map.set(key,[str])
+    }
+    let arr=Array.from(map.values())
+    return arr
+};
+

128. 最长连续序列

/**
+ * @param {number[]} nums
+ * @return {number}
+ */
+var longestConsecutive = function(nums) {
+    //用之前两数之和的那个思想
+    nums.sort((a,b)=>a-b)
+    let map=new Map()
+    let max=0//最长连续的个数!!!
+    for(let i=0;i<nums.length;i++){
+        if(map.has(nums[i]-1))map.set(nums[i],map.get(nums[i]-1)+1)
+        else map.set(nums[i],1)
+        max=Math.max(max,map.get(nums[i]))
+    }
+    return max
+};
+
`,13),o=[e];function c(l,i){return s(),a("div",null,o)}const k=n(t,[["render",c],["__file","技巧_数学🍌.html.vue"]]),r=JSON.parse('{"path":"/algorithm/%E6%8A%80%E5%B7%A7_%E6%95%B0%E5%AD%A6%F0%9F%8D%8C.html","title":"技巧_数学🍌","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"136.只出现一次的数字","slug":"_136-只出现一次的数字","link":"#_136-只出现一次的数字","children":[]},{"level":2,"title":"31.下一个排列","slug":"_31-下一个排列","link":"#_31-下一个排列","children":[]},{"level":2,"title":"560. 和为 K 的子数组","slug":"_560-和为-k-的子数组","link":"#_560-和为-k-的子数组","children":[]},{"level":2,"title":"1. 两数之和","slug":"_1-两数之和","link":"#_1-两数之和","children":[]},{"level":2,"title":"49. 字母异位词分组","slug":"_49-字母异位词分组","link":"#_49-字母异位词分组","children":[]},{"level":2,"title":"128. 最长连续序列","slug":"_128-最长连续序列","link":"#_128-最长连续序列","children":[]}],"filePathRelative":"algorithm/技巧_数学🍌.md","git":{"createdTime":1715588813000,"updatedTime":1716636128000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":2}]},"readingTime":{"minutes":2.26,"words":679}}');export{k as comp,r as data}; diff --git "a/assets/\346\223\215\344\275\234\347\263\273\347\273\237_\347\274\226\350\257\221\345\216\237\347\220\206.html-CRyC5gvZ.js" "b/assets/\346\223\215\344\275\234\347\263\273\347\273\237_\347\274\226\350\257\221\345\216\237\347\220\206.html-CRyC5gvZ.js" new file mode 100644 index 0000000..76a43c4 --- /dev/null +++ "b/assets/\346\223\215\344\275\234\347\263\273\347\273\237_\347\274\226\350\257\221\345\216\237\347\220\206.html-CRyC5gvZ.js" @@ -0,0 +1 @@ +import{_ as t,r as n,o,c as r,a as e,b as s,d as c,e as l}from"./app-B-BkP2m_.js";const i={},_=l('

操作系统与编译原理

参考资料

南京大学计算机系 谭添 编译原理导读PDF

',3),d={href:"https://blog.csdn.net/bay_Tong/article/details/114286621",target:"_blank",rel:"noopener noreferrer"};function h(m,p){const a=n("ExternalLinkIcon");return o(),r("div",null,[_,e("p",null,[e("a",d,[s("编译原理概述——基本知识要点汇总"),c(a)])])])}const u=t(i,[["render",h],["__file","操作系统_编译原理.html.vue"]]),E=JSON.parse('{"path":"/computer/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F_%E7%BC%96%E8%AF%91%E5%8E%9F%E7%90%86.html","title":"操作系统与编译原理","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"参考资料","slug":"参考资料","link":"#参考资料","children":[]}],"filePathRelative":"computer/操作系统_编译原理.md","git":{"createdTime":1715780535000,"updatedTime":1716636128000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":2}]},"readingTime":{"minutes":0.19,"words":57}}');export{u as comp,E as data}; diff --git "a/assets/\346\225\260\346\215\256\344\273\243\347\220\206Proxy.html-BncGCkYs.js" "b/assets/\346\225\260\346\215\256\344\273\243\347\220\206Proxy.html-BncGCkYs.js" new file mode 100644 index 0000000..7c77fa4 --- /dev/null +++ "b/assets/\346\225\260\346\215\256\344\273\243\347\220\206Proxy.html-BncGCkYs.js" @@ -0,0 +1,167 @@ +import{_ as n,o as s,c as a,e as t}from"./app-B-BkP2m_.js";const p={},e=t(`

数据代理 Proxy

简单的数据双向绑定

<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Document</title>
+</head>
+<body>
+    <div>
+        <h1>Proxy实现的双向数据绑定</h1>
+        <input type="text"  id="input">
+        <p id="show"></p>
+    </div>
+    <script>
+        let obj={}
+        const input=document.getElementById('input')
+        const show=document.getElementById('show')
+        // 设置代理
+        let newObj=new Proxy(obj,{
+            get(target,key){
+                return Reflect.get(target,key)
+            },
+            set(target,key,value){
+                if(key==='text'){
+                    input.value=value
+                    show.innerHTML=value//这不实现了双向绑定
+                }
+                return Reflect.set(target,key,value)
+            }
+        })
+
+        input.addEventListener('keyup',function(e){//'input'
+            newObj.text=e.target.value
+        })
+    </script>
+</body>
+</html>
+

Object.defineProperty

<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Object.defineProperty方法</title>
+</head>
+<body>
+    <script>
+        let number=18
+        let person={
+            name:'张三',
+            sex:'男',
+            age:number
+        }
+/*
+value和 get 是同一个作用,只能同时用一个。writable和set是同一个作用,用一个。
+所以,set和get 一个阵营 ,而value和writable一个阵营,不能两个阵营同时存在
+*/
+        Object.defineProperty(person,'age',{
+            //基本配置项
+            // value:18,
+            enumerable:true,//控制属性是否可以枚举,默认值为false
+            // writable:true,//控制属性是否可以被修改,默认值为false
+            configurable:true,//控制属性是否可以被删除,默认为false
+
+            //当有人读取person的age属性时,get函数(getter)就会被调用,且返回值就是age的值
+            get:function(){
+                console.log('有人读取了age属性');
+                return number
+            },
+            //当有人修改person的age属性时,set函数(setter)就会被调用,且返回的值是更改后的值
+            set(value){
+                console.log('有人修改了age属性,且值是'+value);
+                number=value
+            }
+        })
+        console.log(person);
+        console.log(Object.keys(person));
+        console.log(String.fromCharCode(97));//OHohohohoho
+    </script>
+</body>
+</html>
+

Proxy

<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Document</title>
+</head>
+<body>
+    <script>
+        let obj={
+            a:1
+        }
+        let newTarget=new Proxy(obj,{
+            set(target,key,value,receiver){
+                console.log('set',target,key,value,receiver);
+            },
+            get(target,key,receiver){
+                console.log('get',target,key,receiver);
+            }
+        })
+
+        newTarget.a
+        newTarget.a=10
+    </script>
+</body>
+</html>
+

Proxy 和 Reflect

<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Document</title>
+</head>
+<body>
+    Proxy和Reflect
+    <script>
+        let arr=[1,2,3,4]
+        console.log(arr[-1]);//undefined?为什么?
+
+        // 回到原来的问题
+        function createArray(arr){
+            let handle={
+                get(target,index,receiver){
+                    index=Number(index)
+                    if(index<0){
+                        index+=target.length
+                    }
+                    return Reflect.get(target,index,receiver)
+                }
+            }
+            return new Proxy(arr,handle)
+        }
+
+        arr=createArray(arr)
+        console.log(arr[-1]);
+console.log("-------------------------");
+        var star={
+            name:'zhoujielun',
+            age:18,
+            phone:'13287950909'
+        }
+        //代理陷阱
+        var proxy=new Proxy(star,{
+            get:function(target,key,receiver){
+                console.log(target,key,receiver);//代理对象、代理key值、Proxy代理对象
+                if(key==='phone'){
+                    return "经纪人电话:133333333333"
+                }else{
+                    // return target[key]
+                    return Reflect.get(target,key,receiver)//一样
+                }
+            }
+        })
+
+ 
+        // proxy.name
+        console.log(proxy.name);
+        console.log(proxy.age);
+        console.log(proxy.phone);//原来这里也很重要
+    </script>
+</body>
+</html>
+
`,9),o=[e];function c(l,u){return s(),a("div",null,o)}const k=n(p,[["render",c],["__file","数据代理Proxy.html.vue"]]),r=JSON.parse('{"path":"/advance/%E6%95%B0%E6%8D%AE%E4%BB%A3%E7%90%86Proxy.html","title":"数据代理 Proxy","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"简单的数据双向绑定","slug":"简单的数据双向绑定","link":"#简单的数据双向绑定","children":[]},{"level":2,"title":"Object.defineProperty","slug":"object-defineproperty","link":"#object-defineproperty","children":[]},{"level":2,"title":"Proxy","slug":"proxy","link":"#proxy","children":[]},{"level":2,"title":"Proxy 和 Reflect","slug":"proxy-和-reflect","link":"#proxy-和-reflect","children":[]}],"filePathRelative":"advance/数据代理Proxy.md","git":{"createdTime":1715941349000,"updatedTime":1715941349000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":1}]},"readingTime":{"minutes":2.23,"words":669}}');export{k as comp,r as data}; diff --git "a/assets/\346\225\260\346\215\256\345\272\223.html-BgkTBb__.js" "b/assets/\346\225\260\346\215\256\345\272\223.html-BgkTBb__.js" new file mode 100644 index 0000000..9dc7ba3 --- /dev/null +++ "b/assets/\346\225\260\346\215\256\345\272\223.html-BgkTBb__.js" @@ -0,0 +1 @@ +import{_ as t,o,c as a,a as e}from"./app-B-BkP2m_.js";const c={},s=e("h1",{id:"数据库",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#数据库"},[e("span",null,"数据库")])],-1),n=e("p",null,"正在更新……",-1),r=[s,n];function i(l,m){return o(),a("div",null,r)}const d=t(c,[["render",i],["__file","数据库.html.vue"]]),h=JSON.parse('{"path":"/computer/%E6%95%B0%E6%8D%AE%E5%BA%93.html","title":"数据库","lang":"zh-CN","frontmatter":{},"headers":[],"filePathRelative":"computer/数据库.md","git":{"createdTime":1715780535000,"updatedTime":1715780535000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":1}]},"readingTime":{"minutes":0.02,"words":7}}');export{d as comp,h as data}; diff --git "a/assets/\346\240\210_\345\240\206\360\237\215\212.html-BzrWtMIf.js" "b/assets/\346\240\210_\345\240\206\360\237\215\212.html-BzrWtMIf.js" new file mode 100644 index 0000000..9de431a --- /dev/null +++ "b/assets/\346\240\210_\345\240\206\360\237\215\212.html-BzrWtMIf.js" @@ -0,0 +1,386 @@ +import{_ as n,o as s,c as a,e as p}from"./app-B-BkP2m_.js";const t={},e=p(`

栈_堆🍊

20.有效的括号

/**
+ * @param {string} s
+ * @return {boolean}
+ */
+var isValid = function (s) {
+    const map = {
+        '(': -1,
+        ')': 1,
+        '{': -2,
+        '}': 2,
+        '[': -3,
+        ']': 3
+    }
+    const stack=[]
+    for(const c of s){
+        if(map[c]<0){
+            stack.push(map[c])
+        }else if(map[c]>0){
+            let top=stack.pop()
+            if(top+map[c]!=0)return false
+        }
+    }
+    if(stack.length!=0)return false
+    return true
+};
+

155.最小栈


+var MinStack = function() {
+    //搞了一个辅助栈!!!
+    this.stack=[]
+    this.min_stack=[Infinity]
+};
+
+/** 
+ * @param {number} val
+ * @return {void}
+ */
+MinStack.prototype.push = function(val) {
+    this.stack.push(val)
+    this.min_stack.push(Math.min(this.min_stack[this.min_stack.length-1],val))
+};
+
+/**
+ * @return {void}
+ */
+MinStack.prototype.pop = function() {
+    this.stack.pop()
+    this.min_stack.pop()
+};
+
+/**
+ * @return {number}
+ */
+MinStack.prototype.top = function() {
+    return this.stack[this.stack.length-1]
+};
+
+/**
+ * @return {number}
+ */
+MinStack.prototype.getMin = function() {
+    return this.min_stack[this.min_stack.length-1]
+};
+
+/**
+ * Your MinStack object will be instantiated and called as such:
+ * var obj = new MinStack()
+ * obj.push(val)
+ * obj.pop()
+ * var param_3 = obj.top()
+ * var param_4 = obj.getMin()
+ */
+

394.字符串解码

const decodeString = (s) => {
+    let numStack = [];        // 存倍数的栈
+    let strStack = [];        // 存 待拼接的str 的栈
+    let num = 0;              // 倍数的“搬运工”
+    let result = '';          // 字符串的“搬运工”
+    for (const char of s) {   // 逐字符扫描
+        if (!isNaN(char)) {   // 遇到数字
+            num = num * 10 + Number(char); // 算出倍数
+        } else if (char == '[') {  // 遇到 [
+            strStack.push(result); // result串入栈
+            result = '';           // 入栈后清零
+            numStack.push(num);    // 倍数num进入栈等待
+            num = 0;               // 入栈后清零
+        } else if (char == ']') {  // 遇到 ],两个栈的栈顶出栈
+            let repeatTimes = numStack.pop(); // 获取拷贝次数
+            result = strStack.pop() + result.repeat(repeatTimes); // 构建子串
+        } else {                   
+            result += char;        // 遇到字母,追加给result串
+        }
+    }
+    return result;
+};
+

739.每日温度

/**
+ * @param {number[]} temperatures
+ * @return {number[]}
+ */
+var dailyTemperatures = function (temperatures) {
+    // 单调递减栈
+    let stack = [];
+    let n = temperatures.length;
+    let res = new Array(n).fill(0);
+
+    // 遍历每日温度,维护一个单调栈,存储下标
+    for (let i = 0; i < n; i++) {
+        // 当日温度大于栈顶温度,说明栈顶温度的升温日找到了,栈顶出栈并计算天数;继续判断栈顶元素
+        while (stack.length && temperatures[i] > temperatures[stack[stack.length - 1]]) {
+            const top = stack.pop();
+            res[top] = i - top;
+        }
+        // 栈为空 或 每日温度小于等于栈顶温度 => 直接入栈
+        stack.push(i)
+    }
+
+    return res;
+};
+

84.柱状图中最大的矩形(不会)

/**
+ * @param {number[]} heights
+ * @return {number}
+ */
+var largestRectangleArea = function (heights) {
+    let maxArea = 0, stack = [];
+    let len = heights.length
+    //单调递减栈
+    for (let i = 0; i <= len; i++) {
+        while (stack.length > 0 && (heights[i] < heights[stack[stack.length - 1]] || i === len)) {
+            let height = heights[stack.pop()],
+                width = stack.length > 0 ? i - stack[stack.length - 1] - 1 : i;
+
+            maxArea = Math.max(maxArea, width * height);
+        }
+
+        stack.push(i);
+    }
+
+    return maxArea;
+};
+
+

844. 比较含退格的字符串

/**
+ * @param {string} s
+ * @param {string} t
+ * @return {boolean}
+ */
+var backspaceCompare = function(s, t) {
+    let sStack=[],tStack=[]
+    for(const c of s){
+        if(c=='##'){
+            sStack.pop()
+        }else{
+            sStack.push(c)
+        }
+    }
+    for(const c of t){
+        if(c=='##'){
+            tStack.pop()
+        }else{
+            tStack.push(c)
+        }
+    }
+    return sStack.join("")==tStack.join("")
+};
+

215. 数组中的第K个最大元素

/**
+ * @param {number[]} nums
+ * @param {number} k
+ * @return {number}
+ */
+var findKthLargest = function(nums, k) {
+    // 初始化小顶堆
+    // 请注意:我们将堆中所有元素取反,从而用大顶堆来模拟小顶堆
+    const maxHeap = new MaxHeap([]);
+    // 将数组的前 k 个元素入堆
+    for (let i = 0; i < k; i++) {
+        pushMinHeap(maxHeap, nums[i]);
+    }
+    // 从第 k+1 个元素开始,保持堆的长度为 k
+    for (let i = k; i < nums.length; i++) {
+        // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆
+        if (nums[i] > peekMinHeap(maxHeap)) {
+            popMinHeap(maxHeap);
+            pushMinHeap(maxHeap, nums[i]);
+        }
+    }
+    // 返回堆中元素
+    return getMinHeap(maxHeap)[0];
+};
+
+/* 元素入堆 */
+function pushMinHeap(maxHeap, val) {
+    // 元素取反
+    maxHeap.push(-val);
+}
+
+/* 元素出堆 */
+function popMinHeap(maxHeap) {
+    // 元素取反
+    return -maxHeap.pop();
+}
+
+/* 访问堆顶元素 */
+function peekMinHeap(maxHeap) {
+    // 元素取反
+    return -maxHeap.peek();
+}
+
+/* 取出堆中元素 */
+function getMinHeap(maxHeap) {
+    // 元素取反
+    return maxHeap.getMaxHeap().map((num) => -num);
+}
+
+class MaxHeap {
+  constructor(arr) {
+    this.heap = arr;
+  }
+  size() {
+    return this.heap.length;
+  }
+  isEmpty() {
+    return this.size() === 0;
+  }
+  peek(){
+    return this.heap[0]
+  }
+  getLeftChild(i) {
+    return i * 2 + 1;
+  }
+  getRightChild(i) {
+    return i * 2 + 2;
+  }
+  getParent(i) {
+    return parseInt((i - 1) / 2);
+  }
+  push(item) {
+    this.heap.push(item);
+    this.shiftUp(this.size() - 1);
+  }
+  pop() {
+    this.shiftDown(0);
+  }
+  shiftUp(i) {
+    while (this.heap[i] > this.heap[this.getParent(i)]) {
+      this.swap(i, this.getParent(i));
+      i = this.getParent(i);
+    }
+  }
+  shiftDown(i) {
+    //交换值并且删除最后一个值
+    this.swap(i, this.size() - 1);
+    this.heap.pop();
+
+    while (this.getLeftChild(i) < this.size()) {
+      let j = this.getLeftChild(i);
+      if (j + 1 < this.size() && this.heap[j] < this.heap[j + 1]) j++;
+      if (this.heap[i] >= this.heap[j]) break;
+      this.swap(i, j);
+      i = j;
+    }
+  }
+  swap(i, j) {
+    const tmp = this.heap[i];
+    this.heap[i] = this.heap[j];
+    this.heap[j] = tmp;
+  }
+  getMaxHeap(){
+    return this.heap
+  }
+}
+
+

347. 前 K 个高频元素

/**
+ * @param {number[]} nums
+ * @param {number} k
+ * @return {number[]}
+ */
+var topKFrequent = function (nums, k) {
+    const map = {}
+    for (const it of nums) {
+        map[it] = (map[it] || 0) + 1
+    }
+    let numArr = Object.values(map)
+    const maxHeap = new MaxHeap([]);
+    // 将数组的前 k 个元素入堆
+    for (let i = 0; i < k; i++) {
+        pushMinHeap(maxHeap, numArr[i]);
+    }
+    // 从第 k+1 个元素开始,保持堆的长度为 k
+    for (let i = k; i < numArr.length; i++) {
+        // 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆
+        if (numArr[i] > peekMinHeap(maxHeap)) {
+            popMinHeap(maxHeap);
+            pushMinHeap(maxHeap, numArr[i]);
+        }
+    }
+    // 返回堆中元素
+    let res = []
+    let narr = getMinHeap(maxHeap);
+    for (const key in map) {
+        if (narr.includes(map[key])) {
+            res.push(key)
+        }
+    }
+    return res
+};
+
+
+function pushMinHeap(maxHeap, val) {
+    // 元素取反
+    maxHeap.push(-val);
+}
+
+/* 元素出堆 */
+function popMinHeap(maxHeap) {
+    // 元素取反
+    return -maxHeap.pop();
+}
+
+/* 访问堆顶元素 */
+function peekMinHeap(maxHeap) {
+    // 元素取反
+    return -maxHeap.peek();
+}
+
+/* 取出堆中元素 */
+function getMinHeap(maxHeap) {
+    // 元素取反
+    return maxHeap.getMaxHeap().map((num) => -num);
+}
+
+class MaxHeap {
+    constructor(arr) {
+        this.heap = arr;
+    }
+    size() {
+        return this.heap.length;
+    }
+    isEmpty() {
+        return this.size() === 0;
+    }
+    peek() {
+        return this.heap[0]
+    }
+    getLeftChild(i) {
+        return i * 2 + 1;
+    }
+    getRightChild(i) {
+        return i * 2 + 2;
+    }
+    getParent(i) {
+        return parseInt((i - 1) / 2);
+    }
+    push(item) {
+        this.heap.push(item);
+        this.shiftUp(this.size() - 1);
+    }
+    pop() {
+        this.shiftDown(0);
+    }
+    shiftUp(i) {
+        while (this.heap[i] > this.heap[this.getParent(i)]) {
+            this.swap(i, this.getParent(i));
+            i = this.getParent(i);
+        }
+    }
+    shiftDown(i) {
+        //交换值并且删除最后一个值
+        this.swap(i, this.size() - 1);
+        this.heap.pop();
+
+        while (this.getLeftChild(i) < this.size()) {
+            let j = this.getLeftChild(i);
+            if (j + 1 < this.size() && this.heap[j] < this.heap[j + 1]) j++;
+            if (this.heap[i] >= this.heap[j]) break;
+            this.swap(i, j);
+            i = j;
+        }
+    }
+    swap(i, j) {
+        const tmp = this.heap[i];
+        this.heap[i] = this.heap[j];
+        this.heap[j] = tmp;
+    }
+    getMaxHeap() {
+        return this.heap
+    }
+}
+
+
`,17),o=[e];function c(l,i){return s(),a("div",null,o)}const k=n(t,[["render",c],["__file","栈_堆🍊.html.vue"]]),r=JSON.parse('{"path":"/algorithm/%E6%A0%88_%E5%A0%86%F0%9F%8D%8A.html","title":"栈_堆🍊","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"20.有效的括号","slug":"_20-有效的括号","link":"#_20-有效的括号","children":[]},{"level":2,"title":"155.最小栈","slug":"_155-最小栈","link":"#_155-最小栈","children":[]},{"level":2,"title":"394.字符串解码","slug":"_394-字符串解码","link":"#_394-字符串解码","children":[]},{"level":2,"title":"739.每日温度","slug":"_739-每日温度","link":"#_739-每日温度","children":[]},{"level":2,"title":"84.柱状图中最大的矩形(不会)","slug":"_84-柱状图中最大的矩形-不会","link":"#_84-柱状图中最大的矩形-不会","children":[]},{"level":2,"title":"844. 比较含退格的字符串","slug":"_844-比较含退格的字符串","link":"#_844-比较含退格的字符串","children":[]},{"level":2,"title":"215. 数组中的第K个最大元素","slug":"_215-数组中的第k个最大元素","link":"#_215-数组中的第k个最大元素","children":[]},{"level":2,"title":"347. 前 K 个高频元素","slug":"_347-前-k-个高频元素","link":"#_347-前-k-个高频元素","children":[]}],"filePathRelative":"algorithm/栈_堆🍊.md","git":{"createdTime":1715588813000,"updatedTime":1715588813000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":1}]},"readingTime":{"minutes":4.35,"words":1304}}');export{k as comp,r as data}; diff --git "a/assets/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.html-DiuLzUKa.js" "b/assets/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.html-DiuLzUKa.js" new file mode 100644 index 0000000..2460aea --- /dev/null +++ "b/assets/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.html-DiuLzUKa.js" @@ -0,0 +1,129 @@ +import{_ as e,r as t,o as p,c as l,a as n,b as o,d as i,e as s}from"./app-B-BkP2m_.js";const c={},r=s(`

正则表达式

正则表达式速查

匹配模式:
+    i:忽略大小写
+    g:执行全局匹配(查找所有匹配而非在找到第一个匹配后停止)。
+    m:执行多行匹配。
+
+
+正则表达式模式
+方括号:(好像里面不能写括号)
+    [abc]:查找方括号之间的任何字符。
+    [0-9]:查找任何从 0 至 9 的数字。
+    (x|y):查找由 | 分隔的任何选项。
+    [a-z]:查找任何小写的字母。[A-z]:字母
+    [^ ]:除了。。。
+    [^0-9]:除了数字
+量词:(只对前面的一个内容起作用,内容可以用()括号圈起来为一个)
+    n+	匹配任何包含至少一个 n 的字符串。
+    n*	匹配任何包含零个或多个 n 的字符串。
+    n?	匹配任何包含零个或一个 n 的字符串。(要么没有,要么就一个)
+    n{X}	匹配包含 X 个 n 的序列的字符串。
+    n{X,Y}	匹配包含 X 至 Y 个 n 的序列的字符串。
+    n{X,}	匹配包含至少 X 个 n 的序列的字符串。
+    n$	匹配任何结尾为 n 的字符串。
+    ^n	匹配任何开头为 n 的字符串。
+    ?=n	匹配任何其后紧接指定字符串 n 的字符串。有点像以什么结尾的一样
+    ?!n	匹配任何其后没有紧接指定字符串 n 的字符串。
+
+
+元字符:
+    .	查找单个字符,除了换行和行结束符。
+    \\w	查找单词字符。
+    \\W	查找非单词字符。
+    \\d	查找数字。
+    \\D	查找非数字字符。
+    \\s	查找空白字符。
+    \\S	查找非空白字符。
+    \\b	匹配单词边界。
+    \\B	匹配非单词边界。
+    \\0	查找 NUL 字符。
+    \\n	查找换行符。
+    \\f	查找换页符。
+    \\r	查找回车符。
+    \\t	查找制表符。
+    \\v	查找垂直制表符。
+    \\xxx	查找以八进制数 xxx 规定的字符。
+    \\xdd	查找以十六进制数 dd 规定的字符。
+    \\uxxxx	查找以十六进制数 xxxx 规定的 Unicode 字符。
+注意点:
+    \\w 匹配包括下划线的任何单词字符,等同于[A-Za-z0-9_]
+    \\W 匹配任何非单词字符,等同于[^A-Za-z0-9_]
+
+    \\s 匹配空格、换行、tab缩进等所有的空白
+    \\S 匹配非空白,跟\\s刚好相反。
+
+    "gssghs"    \\w  true
+    "123433"    \\w  true
+    "_"         \\w  true
+    "     "     \\s  true    
+
+
+
+RegExp 对象方法:
+    test():它通过模式来搜索字符串,然后根据结果返回 true 或 false。
+    exec():	检索字符串中指定的值。返回找到的值,并确定其位置。
+        -返回一个数组,其中存放匹配的结果。如果未找到匹配,则返回值为 null。
+        -  此数组的第 0 个元素是与正则表达式相匹配的文本,后续捕获组。
+
+
+支持正则表达式的 String 对象的方法:
+split()
+    -可以将一个字符串拆分为一个数组
+    -方法中可以传递一个正则表达式作为一个参数,这样方法会根据正则表达式去拆分字符串
+search()
+    -可以搜索字符串是否含有指定内容
+    -如果搜索到指定内容,则会返回第一次出现的索引,如果没有返回-1
+    -也可以接受一个正则表达式,根据去查找
+    -search()只会查找第一个,即使设置全局匹配也是这样
+match()
+    -根据正则表达式,从一个字符串中将符合条件的内容提取出来
+    -默认情况下,我们match只会找到第一个符合条件的内容,找到以后停止搜索,
+        我们可以设置正则表达式全局匹配,这样就能匹配到所有符合要求的
+        可以设置多个匹配模式,模式顺序任意
+    -match()匹配到的内容会封装到一个数组中返回
+replace()
+    -可以将字符串指定的内容替换为新的内容
+    -参数:1、被替换的内容,可以接受一个正则表达式作为一个参数。2、新的内容
+    -默认只会替换一个
+
+
+汉字:[\\u4e00-\\u9fa5]
+

推荐文章

`,4),d={href:"https://juejin.cn/post/6844903943684751368",target:"_blank",rel:"noopener noreferrer"},u=s(`

正则应用

信息脱敏

const phone="13212345678"
+const phoneReg=/^(\\d{3})(?:\\d{5})(\\d{3})$/
+// 这里用了捕获组和非捕获组
+
+const str=phone.replace(phoneReg,"$1*****$2")
+console.log(str)// 132*****678
+

Markdown图片格式转H5 img标签

// 正则 + repalce方法
+let str="![图片](./images/md.jpg)"
+let imgReg=/^!\\[(.*)\\]\\((.*)\\)$/
+
+// 除了字符串,还能函数!!!
+const result=str.replace(imgReg,(match,alt,src)=>{
+    console.log(match);//'![图片](./images/md.jpg)' 
+    return \`<img src="\${src}" alt="\${alt}">\`
+})
+console.log(result);//<img src="./images/md.jpg" alt="图片"> 
+

正则引用——子表达式

// 引用: 后面可以用\\1引用编号为1的子表达式,依次类推,比如:
+var pattern = /(A|B)(\\d{5})not([o-9])\\1\\2/;//pattern在最后引用了第一个和第二个子表达式。
+// 注意:这里的引用是对与子表达式匹配的字符串的引用,而不是简单的对子表达式的引用。例如:
+var pattern = /([0-9])AA\\1/;
+// pattern不等价于正则表达式([0-9])AA[0-9],
+// 而是指字符串AA后面的数字必须和前面的相同,即形如1AA1这样的字符串!
+
+//特殊正则匹配
+let str="A12345not7A12345"
+var pattern = /(A|B)(\\d{5})not([o-9])\\1\\2/;
+console.log(pattern.exec(str));
+//['A12345not7A12345' , 'A' , '12345' , ' 7 ' , index: 0,input: 'A12345not7A12345' ,groups: undefined ]
+

贪婪匹配和非贪婪匹配

const sanitizedwithoutScript = text.replace(/<script[^>]*>.*?<\\/script>/gi,"");
+

非贪婪匹配(non-greedy matching)是正则表达式中的一个概念,与贪婪匹配相对。

  • 在贪婪匹配中,正则引擎会尽可能多地匹配字符,直到达到最长的可能匹配。
  • 而非贪婪匹配则尽可能少地匹配字符,直到达到最短的可能匹配。

例如,考虑正则表达式a+b。

如果用于贪婪匹配,它会匹配尽可能多的a字符,直到遇到一个b字符。如果用于非贪婪匹配,它会匹配尽可能少的a字符,直到遇到一个b字符。

  • 在大多数现代正则表达式引擎中,可以通过在量词后面添加一个问号来实现非贪婪匹配,例如a+?、*?、+?等。
  • 在您提供的正则表达式中,.*?就是一个非贪婪的量词,它会尽可能少地匹配字符,直到遇到下一个<或</script>。

非贪婪匹配和贪婪匹配的主要区别在于它们在匹配字符串时的优先级和范围。

在贪婪模式下,匹配器会尽可能多地匹配符合要求的字符,直到不能再匹配为止。
例如,正则表达式a.b在匹配字符串"abbcab"时,会匹配整个字符串"abbcab",而不是期望的"ab"。

而非贪婪模式则相反,匹配器会尽可能少地匹配符合要求的字符,直到满足要求为止。
例如,正则表达式a.
?b在匹配相同字符串"abbcab"时,只会匹配到第一个"ab",而不是整个字符串。简而言之,贪婪模式尝试匹配尽可能多的字符,而非贪婪模式则尝试匹配尽可能少的字符。

八个常用的正则

手机号码
  • 前两位一般是 13 / 14 / 15 / 17 / 18
  • 号码总长为 11 位
let reg = /^1[34578]\\d{9}$/g;
+
QQ 号码
  • 首先第一个数不为 0
  • 5-10 位的 QQ 号码
let reg = /^[1-9][0-9]{4,9}$/g;
+
十六进制颜色
  • 第一个符号 # 可有可无
  • 十六进制 0-9a-f
  • #49D1CC 和 #0AB
let reg = /#?([0-9a-fA-F]{6}|[0-9a-fA-F]{3})/g;
+
邮箱
  • 允许输入的邮箱名称包含所有大小写字母、所有数字、以及_-.三个符号
  • luoyu2003@outlook.com
let reg = /^([A-Za-z0-9_\\-\\.]+)@([A-Za-z0-9_\\-\\.]+)\\.([A-Za-z]{2,6})$/g;
+
URL
  • 协议的几种类型,协议可有可无
  • 域名 顶级域名 和 根域名(特殊情况 com.cn)
  • path 路径
  • 最后可能以 / 结尾
let reg =
+  /^((https?|ftp|file):\\/\\/)?([\\da-z\\.\\-]+)\\.([a-z\\.]{2,6})([\\/\\w\\.\\-]*)*\\/?/g;
+
HTML 标签

?:出现在括号的开头表示不需要捕获该组!!!

let reg = /^<([a-z]+)([^>]+)*(?:(.*)<\\/\\1>|\\s+\\/>)$/gm;
+
IPV4 地址
  • 地址是由 4 组 0-255 的数字组成
  • 每一组 0-255 我们需要进行数字范围拆分(正则没有直接表示数字范围的代码)
  • 我们可以分为 0-199 | 200-249 | 250-255 三种范围
let reg =
+  /^(([01]?[0-9][0-9]?|2[0-4][0-9]|25[0-5])\\.){3}([01]?[0-9][0-9]?|2[0-4][0-9]|25[0-5])$/g;
+
日期 YYYY-mm-dd
  • 注意格式
  • 月份、日两位,不足前面补零
  • 日有三种情况 0-9 | 10-29 | 30-31
let reg = /^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/gm;
+
`,40);function g(k,m){const a=t("ExternalLinkIcon");return p(),l("div",null,[r,n("p",null,[n("a",d,[o("位置匹配 理解正则中的(?=p)、(?!p)、(?<=p)、(?矩阵🍇

1329. 将矩阵按对角线排序

/**
+ * @param {number[][]} mat
+ * @return {number[][]}
+ */
+var diagonalSort = function (mat) {
+    const n = mat.length;
+    const m = mat[0].length;
+    const diag = new Array(m + n).fill().map(() => []);
+    //先进行收集管道数据
+    for (let i = 0; i < n; i++) {
+        for (let j = 0; j < m; j++) {
+            diag[i - j + m].push(mat[i][j]);
+        }
+    }
+    //进行排序
+    diag.forEach(d => d.sort((a, b) => b - a));
+    //进行填充
+    for (let i = 0; i < n; i++) {
+        for (let j = 0; j < m; j++) {
+            mat[i][j] = diag[i - j + m].pop();
+        }
+    }
+    return mat;
+};
+

54. 螺旋矩阵

/**
+ * @param {number[][]} matrix
+ * @return {number[]}
+ */
+var spiralOrder = function (matrix) {
+    if (matrix.length == 0 || matrix[0].length == 0) { return [] }
+    //数据的准备
+    let row = matrix.length, col = matrix[0].length
+    const direction = [[0, 1], [1, 0], [0, -1], [-1, 0]]
+    const visited = Array.from({ length: row }, () => new Array(col).fill(false))
+    let total=row*col
+    let curRow=0,curCol=0,directionIndex=0
+    let order=new Array(total)
+
+    for(let i=0;i<total;i++){
+        //具体操作
+        visited[curRow][curCol]=true
+        order[i]=matrix[curRow][curCol]
+        //方便下一次操作
+        let newRow=curRow+direction[directionIndex][0]
+        let newCol=curCol+direction[directionIndex][1]
+        
+        //进行范围的判断
+        if(!(newRow>=0 &&newRow<row && newCol>=0 && newCol<col && !(visited[newRow][newCol]))){
+            directionIndex=(directionIndex+1)%4
+        }
+
+        curRow+=direction[directionIndex][0]
+        curCol+=direction[directionIndex][1]
+
+    }
+    return order
+};
+

289. 生命游戏

/**
+ * @param {number[][]} board
+ * @return {void} Do not return anything, modify board in-place instead.
+ */
+var gameOfLife = function (board) {
+    // 在增加一个数组
+    const m = board.length, n = board[0].length
+    const other = Array.from({ length: m }, () => new Array(n).fill(0))
+    for(let i=0;i<m;i++){
+        for(let j=0;j<n;j++){
+            if(getOnes(board,i,j)<2){
+                other[i][j]=0
+            }else if(getOnes(board,i,j)==2){
+                other[i][j]=board[i][j]
+            }else if(getOnes(board,i,j)==3){
+                other[i][j]=1
+            }else if(getOnes(board,i,j)>3){
+                other[i][j]=0
+            }
+        }
+    }
+    //身上复制
+    for(let i=0;i<m;i++){
+        for(let j=0;j<n;j++){
+            board[i][j]=other[i][j]
+        }
+    }
+};
+//获取周围的一些 "1"
+function getOnes(board, i, j) {
+    const arr = [
+        board[i]?.[j + 1],
+        board[i]?.[j - 1],
+        board[i + 1]?.[j],
+        board[i + 1]?.[j - 1],
+        board[i + 1]?.[j + 1],
+        board[i - 1]?.[j],
+        board[i - 1]?.[j + 1],
+        board[i - 1]?.[j - 1]
+    ]
+    return arr.filter(item => item == 1).length
+}
+

48. 旋转图像

/**
+ * @param {number[][]} matrix
+ * @return {void} Do not return anything, modify matrix in-place instead.
+ */
+var rotate = function (matrix) {
+   //两遍循环的问题
+   let n=matrix.length
+   for(let i=0;i<Math.floor(n/2);i++){
+    for(let j=0;j<Math.floor((n+1)/2);j++){
+        let tmp=matrix[i][j]
+        matrix[i][j]=matrix[n-1-j][i]
+        matrix[n-1-j][i]=matrix[n-1-i][n-1-j]
+        matrix[n-1-i][n-1-j]=matrix[j][n-1-i]
+        matrix[j][n-1-i]=tmp
+    }
+   }
+};
+

73. 矩阵置零

/**
+ * @param {number[][]} matrix
+ * @return {void} Do not return anything, modify matrix in-place instead.
+ */
+var setZeroes = function(matrix) {
+    //把零的位置全部存下啦
+    let m=matrix.length;
+    let n=matrix[0].length
+    obj={
+        i:new Set(),
+        j:new Set()
+    }
+    
+    //把零的位置遍历一遍
+    for(let i=0;i<m;i++){
+        for(let j=0;j<n;j++){
+            if(matrix[i][j]==0){
+                obj.i.add(i)
+                obj.j.add(j)
+            }
+        }
+    }
+
+    for(let i=0;i<m;i++){
+        for(let j=0;j<n;j++){
+            if(obj.i.has(i) ||obj.j.has(j)){
+                matrix[i][j]=0
+            }
+        }
+    }  
+};
+
`,11),e=[o];function c(l,u){return s(),a("div",null,e)}const k=n(t,[["render",c],["__file","矩阵🍇.html.vue"]]),r=JSON.parse('{"path":"/algorithm/%E7%9F%A9%E9%98%B5%F0%9F%8D%87.html","title":"矩阵🍇","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"1329. 将矩阵按对角线排序","slug":"_1329-将矩阵按对角线排序","link":"#_1329-将矩阵按对角线排序","children":[]},{"level":2,"title":"54. 螺旋矩阵","slug":"_54-螺旋矩阵","link":"#_54-螺旋矩阵","children":[]},{"level":2,"title":"289. 生命游戏","slug":"_289-生命游戏","link":"#_289-生命游戏","children":[]},{"level":2,"title":"48. 旋转图像","slug":"_48-旋转图像","link":"#_48-旋转图像","children":[]},{"level":2,"title":"73. 矩阵置零","slug":"_73-矩阵置零","link":"#_73-矩阵置零","children":[]}],"filePathRelative":"algorithm/矩阵🍇.md","git":{"createdTime":1715588813000,"updatedTime":1715588813000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":1}]},"readingTime":{"minutes":2.23,"words":669}}');export{k as comp,r as data}; diff --git "a/assets/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.html-OxIjlNSK.js" "b/assets/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.html-OxIjlNSK.js" new file mode 100644 index 0000000..2e5e177 --- /dev/null +++ "b/assets/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.html-OxIjlNSK.js" @@ -0,0 +1 @@ +import{_ as o,r as T,o as a,c as p,a as l,b as t,d as c,e}from"./app-B-BkP2m_.js";const n={},s=e('

计算机网络

UDP 和 TCP

UDP

UDP,用户数据报协议,是网络五层模型传输层上面的协议。

  1. UDP是一个面向报文的协议。意思就是UDP只是对报文进行搬运,不会对报文进行才分和拼接操作。
  2. UDP不可靠,无连接,它接收到什么数据就返回什么数据,不会对数据进行备份,也不会关心对方能不能接收到数据。
  3. UDP高效:因为UDP没有TCP那么复杂,头部开销很小,相比TCP至少20个字节要少得多。
  4. UDP支持一对一、多对多、多对一的传输方式。
  5. 应用场景:在某些实时性要求较高的场景,如电话会议、视频直播等。

TCP

TCP传输控制协议,是一种面向连接的、可靠的、基于字节流的传输层通信协议

  1. 面向连接:TCP的连接过程一共有以下三步,三次握手、传输数据、四次挥手
  2. TCP的可靠性:通过将数据分成报文段、超时重传机制、首部和数据的校验和保证数据的可靠性。
  3. TCP只支持一对一的传输方式
  4. 因为TCP头部至少是会有20个字节的数据,还要在发送请求之前进行三次握手,所以它的传输效率是相比UDP来说是比较低的。

HTTP/HTTPS

',9),r={href:"https://www.runoob.com/http/http-tutorial.html",target:"_blank",rel:"noopener noreferrer"},u=e('
  1. 客户端向服务器发送请求报文,服务端就需要向客户端回送响应报文,http规定了请求响应的报文格式。
  2. 请求报文格式:请求行的请求方法、请求URL、http版本;请求头部、空行和请求体
  3. 响应报文格式:状态行的协议版本、状态码和描述状态;响应头部、空行和响应体
  4. HTTP是基于传输层TCP协议的,需要TCP三次握手后建立可靠连接才能进行应用层的通信。
  5. HTTP 是一种无状态 (stateless) 协议, HTTP协议本身不会对发送过的请求和相应的通信状态进行持久化处理。这样做的目的是为了保持HTTP协议的简单性,从而能够快速处理大量的事务, 提高效率。
  6. HTTPS:HTTP 协议中没有加密机制,但可以通 过和 SSL(Secure Socket Layer, 安全套接层 )或 TLS(Transport Layer Security, 安全层传输协议)的组合使用,加密 HTTP 的通信内容。属于通信加密,即在整个通信线路中加密。
  7. HTTPS 采用共享密钥加密(对称)和公开密钥加密(非对称)两者并用的混合加密机制。

HTTP 与 HTTPS 区别

加密:

  • HTTP:数据传输过程中不加密,容易被截获和篡改。
  • HTTPS:使用SSL/TLS协议对传输的数据进行加密,保护数据传输过程中的安全性。

端口

  • HTTP:默认使用端口80。
  • HTTPS:默认使用端口443。

安全性:

  • HTTP:不提供数据加密,安全性较低。
  • HTTPS:提供数据加密和完整性校验,安全性较高。

证书:

  • HTTP:不需要证书。
  • HTTPS:需要SSL证书来启用加密,并验证服务器的身份。

性能:

  • HTTP:由于不加密数据,性能略高于HTTPS。
  • HTTPS:由于需要进行加密和解密,可能会有一定的性能开销。

搜索引擎优化(SEO):

  • HTTP:搜索引擎可能会对没有使用HTTPS的网站进行降权。
  • HTTPS:搜索引擎倾向于优先索引和展示使用HTTPS的网站。

浏览器显示

  • HTTP:在大多数现代浏览器中,HTTP网站通常显示为"不安全"。
  • HTTPS:浏览器会显示一个锁形图标,表示网站是安全的。

成本:

  • HTTP:通常免费。
  • HTTPS:需要购买SSL证书,可能会有一定的成本。

应用场景:

  • HTTP:适用于不需要传输敏感信息的网站,如新闻网站、博客等。
  • HTTPS:适用于需要传输敏感信息的网站,如网上银行、在线购物、电子邮件等。
',20);function h(P,d){const i=T("ExternalLinkIcon");return a(),p("div",null,[s,l("p",null,[l("a",r,[t("菜鸟网站 HTTP"),c(i)]),t("是应用层维持客户端和服务端进行网络通信的协议")]),u])}const S=o(n,[["render",h],["__file","计算机网络.html.vue"]]),b=JSON.parse('{"path":"/computer/%E8%AE%A1%E7%AE%97%E6%9C%BA%E7%BD%91%E7%BB%9C.html","title":"计算机网络","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"UDP 和 TCP","slug":"udp-和-tcp","link":"#udp-和-tcp","children":[]},{"level":2,"title":"HTTP/HTTPS","slug":"http-https","link":"#http-https","children":[]}],"filePathRelative":"computer/计算机网络.md","git":{"createdTime":1715780535000,"updatedTime":1716306105000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":2}]},"readingTime":{"minutes":3.56,"words":1067}}');export{S as comp,b as data}; diff --git "a/assets/\350\256\276\350\256\241\346\250\241\345\274\217.html-B3u9fd1m.js" "b/assets/\350\256\276\350\256\241\346\250\241\345\274\217.html-B3u9fd1m.js" new file mode 100644 index 0000000..35ea854 --- /dev/null +++ "b/assets/\350\256\276\350\256\241\346\250\241\345\274\217.html-B3u9fd1m.js" @@ -0,0 +1 @@ +import{_ as t,o,c as a,a as e}from"./app-B-BkP2m_.js";const c={},s=e("h1",{id:"设计模式",tabindex:"-1"},[e("a",{class:"header-anchor",href:"#设计模式"},[e("span",null,"设计模式")])],-1),n=e("p",null,"正在更新……",-1),r=[s,n];function i(_,l){return o(),a("div",null,r)}const d=t(c,[["render",i],["__file","设计模式.html.vue"]]),h=JSON.parse('{"path":"/computer/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F.html","title":"设计模式","lang":"zh-CN","frontmatter":{},"headers":[],"filePathRelative":"computer/设计模式.md","git":{"createdTime":1715780535000,"updatedTime":1715780535000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":1}]},"readingTime":{"minutes":0.03,"words":8}}');export{d as comp,h as data}; diff --git "a/assets/\350\264\252\345\277\203\360\237\215\211.html-DiD3SKMy.js" "b/assets/\350\264\252\345\277\203\360\237\215\211.html-DiD3SKMy.js" new file mode 100644 index 0000000..295fd65 --- /dev/null +++ "b/assets/\350\264\252\345\277\203\360\237\215\211.html-DiD3SKMy.js" @@ -0,0 +1,58 @@ +import{_ as n,o as s,c as a,e}from"./app-B-BkP2m_.js";const p={},t=e(`

贪心🍉

55.跳跃游戏

/**
+ * @param {number[]} nums
+ * @return {boolean}
+ */
+//  由题目描述,我们需要达到最后一个下标,那么最后一个下标的数字其实是可以不用考虑的。
+//  那么我们可以假设只有两个数字(比如 [2,4][2, 4][2,4]),这个时候第一个数字如果是大于等于 111 的数就成立;
+//  如果是三个数字的话(比如 [3,0,4][3, 0, 4][3,0,4]),第一个数字大于等于 222 时成立。
+//  依此类推,一个数字可以到达的位置必须是这个数字标记的长度值,
+//  有:nums[i]>=jnums[i] >= jnums[i]>=j 成立时才可以到达后面第 jjj 个目标。
+
+var canJump = function(nums) {
+    // 必须到达end下标的数字
+    let end = nums.length - 1;
+
+    for (let i = nums.length - 2; i >= 0; i--) {
+        if (end - i <= nums[i]) {
+            end = i;
+        }
+    }
+
+    return end == 0;
+};
+

121. 买卖股票的最佳时机

/**
+ * @param {number[]} prices
+ * @return {number}
+ */
+var maxProfit = function (prices) {
+    let maxprofit = 0
+    let minprice = Infinity
+    for (let i = 0; i < prices.length; i++) {
+        if (minprice > prices[i]) {
+            minprice = prices[i]
+        } else if (maxprofit < prices[i] - minprice) {
+            maxprofit = prices[i] - minprice
+        }
+    }
+    return maxprofit
+};
+

45. 跳跃游戏 II

/**
+ * @param {number[]} nums
+ * @return {number}
+ */
+var jump = function(nums) {
+    let curIndex = 0
+    let nextIndex = 0
+    let steps = 0
+    // 以最小的步数增加最大的覆盖范围,直到覆盖范围覆盖了终点
+    for(let i = 0; i < nums.length - 1; i++) {
+        nextIndex = Math.max(nums[i] + i, nextIndex)
+        if(i === curIndex) {
+            curIndex = nextIndex
+            steps++
+        }
+    }
+
+    return steps
+};
+
`,7),o=[t];function c(i,l){return s(),a("div",null,o)}const r=n(p,[["render",c],["__file","贪心🍉.html.vue"]]),k=JSON.parse('{"path":"/algorithm/%E8%B4%AA%E5%BF%83%F0%9F%8D%89.html","title":"贪心🍉","lang":"zh-CN","frontmatter":{},"headers":[{"level":2,"title":"55.跳跃游戏","slug":"_55-跳跃游戏","link":"#_55-跳跃游戏","children":[]},{"level":2,"title":"121. 买卖股票的最佳时机","slug":"_121-买卖股票的最佳时机","link":"#_121-买卖股票的最佳时机","children":[]},{"level":2,"title":"45. 跳跃游戏 II","slug":"_45-跳跃游戏-ii","link":"#_45-跳跃游戏-ii","children":[]}],"filePathRelative":"algorithm/贪心🍉.md","git":{"createdTime":1715588813000,"updatedTime":1715588813000,"contributors":[{"name":"xiaoyu","email":"luoyu2003@outlook.com","commits":1}]},"readingTime":{"minutes":1.18,"words":353}}');export{r as comp,k as data}; diff --git a/base/AJAX.html b/base/AJAX.html new file mode 100644 index 0000000..a181807 --- /dev/null +++ b/base/AJAX.html @@ -0,0 +1,295 @@ + + + + + + + + + 异步请求 AJAX | 🍰 小雨的学习记录 + + + + + +

异步请求 AJAX

XMLHttpRequest

手写一个基本且常规的原生请求模板

// 创建一个XMLHttpRequest对象
+const xhr = new XMLHttpRequest();
+// 打开一个 URL
+xhr.open("get", "http://127.0.0.1:8000/server");
+// 最后发送请求
+xhr.send();
+
+//两种进行处理的方式
+// >>>>>>>>>>>>> first-start
+xhr.onreadystatechange = function () {
+  if (xhr.readyState === 4) {
+    if (xhr.status >= 200 && xhr.status < 300) {
+      result.innerHTML = xhr.response;
+    }
+  }
+};
+// >>>>>>>>>>>>> first-end
+// >>>>>>>>>>>>> second-start
+xhr.addEventListener("load", reqListener);
+function reqListener() {
+  console.log(this.responseText);
+}
+// >>>>>>>>>>>>> second-end
+

重要属性介绍:

  • XMLHttpRequest.onreadystatechange:当 readyState 属性发生变化时,调用的事件处理器。
  • XMLHttpRequest.readyState:返回 一个无符号短整型(unsigned short)数字,代表请求的状态码。

    readystate 属性有五个,保存 XMLHttpRequest 的状态。

       0:请求未初始化
    +   1:服务器连接已建立
    +   2:请求已收到
    +   3:正在处理请求
    +   4:请求已完成且响应已就绪
    +
  • XMLHttpRequest.response:返回一个 ArrayBuffer、Blob、Document,或 DOMString,具体是哪种类型取决于 XMLHttpRequest.responseType 的值。其中包含整个响应实体(response entity body)。
  • XMLHttpRequest.responseText:返回一个 DOMString,该 DOMString 包含对请求的响应,如果请求未成功或尚未发送,则返回 null。
  • XMLHttpRequest.responseType:一个用于定义响应类型的枚举值(enumerated value)。
  • XMLHttpRequest.responseURL:返回经过序列化(serialized)的响应 URL,如果该 URL 为空,则返回空字符串。
  • XMLHttpRequest.responseXML:返回一个 Document,其中包含该请求的响应,如果请求未成功、尚未发送或是不能被解析为 XML 或 HTML,则返回 null。
  • XMLHttpRequest.status:返回一个无符号短整型(unsigned short)数字,代表请求的响应状态。
  • XMLHttpRequest.statusText:返回一个 DOMString,其中包含 HTTP 服务器返回的响应状态。与 XMLHTTPRequest.status 不同的是,它包含完整的响应状态文本(例如,"200 OK")。
  • XMLHttpRequest.timeout:一个无符号长整型(unsigned long)数字,表示该请求的最大请求时间(毫秒),若超出该时间,请求会自动终止。
  • XMLHttpRequest.upload:XMLHttpRequestUpload,代表上传进度。
  • XMLHttpRequest.withCredentials:一个布尔值,用来指定跨域 Access-Control 请求是否应当带有授权信息,如 cookie 或授权 header 头。

这里还有一些实例方法和事件,在实际遇到了可以去XMLHttpRequestopen in new window上进行查看

GET 请求

// 1.创建对象
+const xhr = new XMLHttpRequest();
+// 设置响应体为json
+xhr.responseType = "json"; //如果要获取JSON数据的,就这样设置
+//get方式设置请求参数
+xhr.open("get", "http://127.0.0.1:8000/server?a=100&b=200&c=300");
+// 3.发送
+xhr.send();
+// 4.事件绑定,处理服务器端的返回数据 onreadystatechange
+xhr.onreadystatechange = function () {
+  // 判断(服务端反悔了所有的结果)
+  if (xhr.readyState === 4) {
+    // 判断响应状态码 200 403 404 500 401
+    // 2XX:成功
+    if (xhr.status >= 200 && xhr.status < 300) {
+      // 处理结果  行 头 空行 体
+      // 1.响应行
+      console.log(xhr.status); //状态码
+      console.log(xhr.statusText); //状态字符串
+      console.log(xhr.getAllResponseHeaders); //所有响应头
+      console.log(xhr.response); //响应体
+      result.innerHTML = xhr.response;
+    }
+  }
+};
+

POST 请求

// 1.创建对象
+const xhr = new XMLHttpRequest();
+// 2.初始化,设置请求方法和URL
+xhr.open("post", "http://127.0.0.1:8000/server"); //post匹配服务器也应该为post
+
+// 设置请求头
+xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
+xhr.setRequestHeader("name", "luoyu"); //自定义请求头
+
+// 3.发送(请求体)
+xhr.send("a=100&b=200&c=300"); //post的参数在send()中可以有,注意是post
+xhr.onreadystatechange = function () {
+  // 判断(服务端返回了所有的结果)
+  if (xhr.readyState === 4) {
+    if (xhr.status >= 200 && xhr.status < 300) {
+      result.innerHTML = xhr.response;
+    }
+  }
+};
+

超时与网络异常

const xhr = new XMLHttpRequest();
+// 超时设置 2s 设置,2s钟还没响应就取消
+xhr.timeout = 2000;
+// 设置超时回调
+xhr.ontimeout = function () {
+  alert("你的网络请求超时了!");
+};
+// 网络异常回调
+xhr.onerror = function () {
+  alert("你的网络出现了异常");
+};
+xhr.open("get", "http://127.0.0.1:8000/delay");
+xhr.send();
+xhr.onreadystatechange = function () {
+  if (xhr.readyState === 4) {
+    if (xhr.status >= 200 && xhr.status < 300) {
+      result.innerHTML = xhr.response;
+    }
+  }
+};
+

请求重复问题

let isSending = false; //标识是否在发送AJAX请求
+let x = null;
+const btns = document.querySelectorAll("button");
+btns[0].onclick = function () {
+  if (isSending) x.abort();
+  x = new XMLHttpRequest(); //现在发现个问题,这个要作为对象来new一个
+  isSending = true;
+  x.open("get", "http://127.0.0.1:8000/delay");
+  x.send();
+  // 这里的话我给请求的地方进行了三秒的延迟
+  x.onreadystatechange = function () {
+    if (x.readyState === 4) {
+      isSending = false;
+    }
+  };
+};
+

监测进度

var oReq = new XMLHttpRequest();
+
+// 在请求调用 open() 之前添加事件监听。否则 progress 事件将不会被触发。
+oReq.addEventListener("progress", updateProgress);
+oReq.addEventListener("load", transferComplete);
+oReq.addEventListener("error", transferFailed);
+oReq.addEventListener("abort", transferCanceled);
+
+oReq.open();
+
+// ...
+
+// 服务端到客户端的传输进程(下载)
+function updateProgress(oEvent) {
+  if (oEvent.lengthComputable) {
+    var percentComplete = (oEvent.loaded / oEvent.total) * 100;
+    // ...
+  } else {
+    // 总大小未知时不能计算进程信息
+  }
+}
+
+function transferComplete(evt) {
+  console.log("The transfer is complete.");
+}
+
+function transferFailed(evt) {
+  console.log("An error occurred while transferring the file.");
+}
+
+function transferCanceled(evt) {
+  console.log("The transfer has been canceled by the user.");
+}
+

绕过缓存

在面试的时候,面试官问过我,关于强缓存,在没到达过期时间之前如何更新呢?因为你可能更改了服务端资源。——更新路径

有一个跨浏览器兼容的方法,就是给 URL 添加时间戳。请确保你酌情地添加了 "?" or "&" 。例如,将:

http://example.com/bar.html -> http://example.com/bar.html?12345
+http://example.com/bar.html?foobar=baz -> http://example.com/bar.html?foobar=baz&12345
+

因为本地缓存都是以 URL 作为索引的,这样就可以使每个请求都是唯一的,也就可以这样来绕开缓存。

你也可以用下面的方法自动更改缓存:

Copy to Clipboard
+const req = new XMLHttpRequest();
+
+req.open("GET", url + (/\?/.test(url) ? "&" : "?") + new Date().getTime());
+req.send(null);
+

同步请求和异步请求

XMLHttpRequest 支持同步和异步通信。但是,一般来说,出于性能原因,异步请求应优先于同步请求。 同步请求阻止代码的执行,这会导致屏幕上出现“冻结”和无响应的用户体验。

xhr.open("GET", "/bar/foo.txt", true); //异步,默认
+xhr.open("GET", "http://www.mozilla.org/", false); //同步
+

注意:当您使用 async=false 时,请不要编写 onreadystatechange 函数 - 把代码放到 send() 语句后面即可:

xmlhttp.open("GET","/try/ajax/ajax_info.txt",false);
+xmlhttp.send();
+document.getElementById("myDiv").innerHTML=xmlhttp.responseText;
+

备注: 从 Gecko 30.0,Blink 39.0 和 Edge 13 开始,主线程上的同步请求由于对用户体验的负面影响而被弃用。同步 XHR 不允许所有新的 XHR 功能(如 timeout 或 abort)。这样做会调用 InvalidAccessError。

fetch

Fetch有几个特点的了解一下,面试可能问,深挖你的知识点!

  • 当接收到一个代表错误的 HTTP 状态码时,从 fetch() 返回的 Promise 不会被标记为 reject,即使响应的 HTTP 状态码是 404 或 500。相反,它会将 Promise 状态标记为 resolve(如果响应的 HTTP 状态码不在 200 - 299 的范围内,则设置 resolve 返回值的 ok 属性为 false),仅当网络故障时或请求被阻止时,才会标记为 reject。
  • fetch 不会发送跨域 cookie,除非你使用了 credentials 的初始化选项。

fetch 发送 GET 请求

fetch("http://ajax-base-api-t.itheima.net/api/getbooks")
+  .then((response) => {
+    //这个response是一个Response对象,需要通过一个异步操作取出其中的内容
+    return response.json();
+  })
+  .then((data) => {
+    //经过response.json()处理过的数据
+    console.log(data);
+  })
+  .catch((err) => {
+    console.log(err);
+  });
+
+/* 下面使用async-await改写 :把代码封装成async异步函数 */
+async function getData() {
+  try {
+    //先获取Response对象
+    let response = await fetch(
+      "http://ajax-base-api-t.itheima.net/api/getbooks"
+    );
+    console.log(response);
+
+    //需要通过response.json() 取出response对象中的结果
+    let json = await response.json();
+    console.log(json);
+  } catch (error) {
+    console.log(error);
+  }
+}
+getData();
+

fetch 发送 POST 请求

//post发送:json格式
+async function add() {
+  let obj = {
+    bookname: "魔法书之如何学好前端",
+    author: "阿利亚",
+    publisher: "格兰芬多",
+  };
+  let res = await fetch("http://ajax-base-api-t.itheima.net/api/addbook", {
+    method: "post",
+    headers: {
+      "Content-Type": "application/json",
+    },
+    body: JSON.stringify(obj),
+  });
+  let json = await res.json();
+  console.log(json);
+}
+add();
+

fetch 封装

//封装http函数(fetch请求)
+async function http(obj) {
+  let { method, url, params, data } = obj;
+  let res;
+  //params需要处理-->转化成key1=value1&key2=value2的形式
+  if (params) {
+    //固定写法:将params参数拼接成参数字符串
+    let str = new URLSearchParams(params).toString();
+    //拼接到url上去
+    url += "?" + str;
+  }
+
+  //data需要处理-->如果有data,此时需要写完整的代码headers...
+  if (data) {
+    res = await fetch(url, {
+      method: method,
+      headers: {
+        "Content-Type": "application/json",
+      },
+      body: JSON.stringify(data),
+    });
+  } else {
+    res = await fetch(url);
+  }
+  //把获取的结果经过处理之后,返回出去
+  return res.json();
+}
+
+//测试代码1
+async function fn1() {
+  //通过http函数发送get请求获取数据
+  let result = await http({
+    method: "get",
+    url: "http://ajax-base-api-t.itheima.net/api/getbooks",
+    params: {
+      id: 2,
+    },
+  });
+  console.log(result);
+}
+fn1();
+
+//测试代码2
+async function fn2() {
+  //通过http函数发送post请求获取数据
+  let result = await http({
+    method: "post",
+    url: "http://ajax-base-api-t.itheima.net/api/addbook",
+    data: {
+      bookname: "如何高新就业",
+      author: "大佬",
+      publisher: "哈哈出版社",
+    },
+  });
+  console.log(result);
+}
+fn2();
+

更多的详情,请求响应和相关方法可以查看MDN-Fetchopen in new window

axiso 请求库

由于项目中经常会用这个库来进行请求,有问题一般是去查看Axiso文档open in new window

Last Updated:
Contributors: xiaoyu
+ + + diff --git a/base/CSS3.html b/base/CSS3.html new file mode 100644 index 0000000..3402946 --- /dev/null +++ b/base/CSS3.html @@ -0,0 +1,364 @@ + + + + + + + + + CSS3 | 🍰 小雨的学习记录 + + + + + +

CSS3

Grid 布局

参考文章
最强大的 CSS 布局 —— Grid 布局open in new window

  1. grid-template-columns grid-gap repeat() minmax() auto-fill fr
<!DOCTYPE html>
+<html lang="en-us">
+  <head>
+    <meta charset="utf-8">
+    <meta name="viewport" content="width=device-width">
+    <title>CSS Grid starting point</title>
+    <style>
+        body {
+            width: 90%;
+            max-width: 900px;
+            margin: 2em auto;
+            font: .9em/1.2 Arial, Helvetica, sans-serif;
+        }
+
+        .container > div {
+            border-radius: 5px;
+            padding: 10px;
+            background-color: rgb(207,232,220);
+            border: 2px solid rgb(79,185,227);
+        }
+        /* 补充 */
+        .container {
+            display: grid;
+            /* 加三个宽度为200px的列。 */
+            grid-template-columns: 200px 200px 200px ;
+            /* 除了长度和百分比,我们也可以用fr这个单位来灵活地定义网格的行与列的大小。
+            fr这个单位表示了可用空间的一个比例 */
+            grid-template-columns: 2fr 1fr 1fr;
+            /* 使用 grid-column-gap (en-US) 属性来定义列间隙;使用 grid-row-gap (en-US) 来定义行间隙;
+            使用 grid-gap (en-US) 可以同时设定两者。
+            !!!!注意:间隙距离可以用任何长度单位包括百分比来表示,但不能使用fr单位 */
+            grid-gap: 20px 10px;/* 行 列 */
+            gap: 20px 10px;/* 两个一样 */
+            /* 使用repeat来重复构建具有某些宽度配置的某些列
+           repeat(3, 1fr):得到了 3 个1fr的列 
+           repeat(2, 2fr 1fr): 相当于填入了2fr 1fr 2fr 1fr*/
+            grid-template-columns: repeat(3, 1fr);
+            /* 隐式网格中生成的行/列大小是参数默认是auto,大小会根据放入的内容自动调整。
+            当然,你也可以使用grid-auto-rows和grid-auto-columns属性手动设定隐式网格的大小。
+            下面的例子将grid-auto-rows设为了100px,然后你可以看到那些隐式网格中的行
+            (因为这个例子里没有设定grid-template-rows,因此,所有行都位于隐式网格内)现在都是 100 像素高了。
+            简单来说,隐式网格就是为了放显式网格放不下的元素,浏览器根据已经定义的显式网格自动生成的网格部分。 */
+            grid-auto-rows: 100px;
+            /* minmax 函数为一个行/列的尺寸设置了取值范围。比如设定为 minmax(100px, auto),那么尺寸就至少为 100 像素,
+            并且如果内容尺寸大于 100 像素则会根据内容自动调整。 */
+            grid-auto-rows: minmax(100px,auto);
+            /* 
+            自动使用多列填充
+              grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+            */
+        }
+    </style>
+  </head>
+
+<body>
+    <h1>Simple grid example</h1>
+    <div class="container">
+        <div>就能(´ڡ`ლ)好吃的.∑(っ°Д°;)っ卧槽,不见了</div>
+        <div>Two</div>
+        <div>Three</div>
+        <div>Four Lorem ipsum dolor sit amet consectetur adipisicing elit. At doloribus error animi eius labore rerum quo saepe nihil veritatis! Necessitatibus, similique facere? Voluptatem eos consequatur tempora non alias. Repudiandae, nihil!</div>
+        <div>Five</div>
+        <div>Six</div>
+        <div>Seven Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequuntur blanditiis exercitationem eos veritatis officia, enim dolores nostrum minima iure, repellat eius, similique suscipit dolorem eaque? Porro dolores quidem consequuntur facilis!</div>
+    </div>
+</body>
+</html>
+

  1. grid-template-areas

grid-template-areas属性的使用规则如下:

  • 你需要填满网格的每个格子
  • 对于某个横跨多个格子的元素,重复写上那个元素grid-area属性定义的区域名字
  • 所有名字只能出现在一个连续的区域,不能在不同的位置出现
  • 一个连续的区域必须是一个矩形
  • 使用.符号,让一个格子留空
<!DOCTYPE html>
+<html lang="en-us">
+  <head>
+    <meta charset="utf-8" />
+    <meta name="viewport" content="width=device-width" />
+    <title>CSS Grid - line-based placement starting point</title>
+    <style>
+      body {
+        width: 90%;
+        max-width: 900px;
+        margin: 2em auto;
+        font: 0.9em/1.2 Arial, Helvetica, sans-serif;
+      }
+
+      header,
+      footer {
+        border-radius: 5px;
+        padding: 10px;
+        background-color: rgb(207, 232, 220);
+        border: 2px solid rgb(79, 185, 227);
+      }
+
+      aside {
+        border-right: 1px solid #999;
+      }
+      .container {
+        display: grid;
+        grid-template-areas:
+          "header header"
+          "sidebar content"
+          "footer footer";
+        grid-template-columns: 1fr 3fr;
+        grid-gap: 20px;
+      }
+      /* 
+选择器(目标){
+  grid-area:区域名字;
+}
+*/
+      header {
+        grid-area: header;
+      }
+
+      article {
+        grid-area: content;
+      }
+
+      aside {
+        grid-area: sidebar;
+      }
+
+      footer {
+        grid-area: footer;
+      }
+    </style>
+  </head>
+
+  <body>
+    <div class="container">
+      <header>This is my lovely blog</header>
+      <article>
+        <h1>My article</h1>
+        <p>
+          Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras
+          porttitor imperdiet nunc, at ultricies tellus laoreet sit amet. Sed
+          auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet
+          orci vel, viverra egestas ligula. Curabitur vehicula tellus neque, ac
+          ornare ex malesuada et. In vitae convallis lacus. Aliquam erat
+          volutpat. Suspendisse ac imperdiet turpis. Aenean finibus sollicitudin
+          eros pharetra congue. Duis ornare egestas augue ut luctus. Proin
+          blandit quam nec lacus varius commodo et a urna. Ut id ornare felis,
+          eget fermentum sapien.
+        </p>
+
+        <p>
+          Nam vulputate diam nec tempor bibendum. Donec luctus augue eget
+          malesuada ultrices. Phasellus turpis est, posuere sit amet dapibus ut,
+          facilisis sed est. Nam id risus quis ante semper consectetur eget
+          aliquam lorem. Vivamus tristique elit dolor, sed pretium metus
+          suscipit vel. Mauris ultricies lectus sed lobortis finibus. Vivamus eu
+          urna eget velit cursus viverra quis vestibulum sem. Aliquam tincidunt
+          eget purus in interdum. Cum sociis natoque penatibus et magnis dis
+          parturient montes, nascetur ridiculus mus.
+        </p>
+      </article>
+      <aside>
+        <h2>Other things</h2>
+        <p>
+          Nam vulputate diam nec tempor bibendum. Donec luctus augue eget
+          malesuada ultrices. Phasellus turpis est, posuere sit amet dapibus ut,
+          facilisis sed est.
+        </p>
+      </aside>
+      <footer>Contact me@mysite.com</footer>
+    </div>
+  </body>
+</html>
+

  1. grid-template
<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>grid-template</title>
+    <style>
+        body{
+            min-height: 100vh;
+            display: flex;
+            align-items: center;
+            justify-content:space-around;
+        }
+        .content{
+            width: 600px;
+            height: 400px;
+            border: 1px solid red;
+            display: grid;
+            gap: 10px;
+            /* 这个属性是所简写属性:grid-template-rows、grid-template-columns与grid-template-areas。 */
+            /* 下面这个是缩写!!! 最下面一行是  grid-template-columns*/
+            grid-template:
+            "a a a" 1fr
+            "b c c" 1fr
+            "b c c" 1fr /
+            1fr 1fr 1fr;
+           }
+        .a{
+            grid-area: a;
+            background-color: lime;
+        }
+        .b{
+            grid-area: b;
+            background-color: aqua;
+        }
+        .c{
+            grid-area: c;
+            background-color: blueviolet;
+        }
+
+        .wrap{
+            width: 600px;
+            height: 400px;
+            border: 1px solid red;
+            display: grid;
+            gap: 10px;
+            grid-template:100px auto / 100px 1fr 1fr;/* 先是规定行,后是规定列!!! */
+        }
+        .plot{
+            background-color: brown;
+        }
+    </style>
+</head>
+<body>
+    <div class="content">
+        <div class="a">小雨</div>
+        <div class="b">好哇好哇</div>
+        <div class="c">华为</div>
+    </div>
+    <div class="wrap">
+        <div class="plot">1</div>
+        <div class="plot">2</div>
+        <div class="plot">3</div>
+        <div class="plot">4</div>
+        <div class="plot">5</div>
+        <div class="plot">6</div>
+    </div>
+</body>
+</html>
+

  1. grid-area grid-row grid-column
<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>grid-area</title>
+    <style>
+        body{
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            min-height: 100vh;
+            margin: 0;
+            padding: 0;
+        }
+        .content{
+            width:500px ;
+            height: 500px;
+            /* border: 1px salmon solid; */
+            position: relative;
+        }
+        .bg,.cover{
+            width: inherit;
+            height: inherit;
+            display: grid;
+            grid-template: repeat(5,1fr)/repeat(5,1fr);
+            gap: 5px 5px;
+            position: absolute;
+            z-index: 10;
+        }
+        .bg>.plot{
+            background-color: lawngreen;
+        }
+        .cover{
+            z-index: 20;
+        }
+        .cover>div{
+            border-radius: 50%;
+        }
+        .item1{
+            background-color: blue;
+            grid-row: 1/3;
+            grid-column: 1/3;
+        }
+        .item2{
+            background-color: palevioletred;
+        }
+        .item3{
+            background-color: yellow;
+            grid-area: 3/3/6/6;/* top / left / bottom / right */
+        }
+    </style>
+</head>
+<body>
+    <!-- 
+        CSS 属性 grid-area 是一种对于 grid-row-start (en-US)、grid-column-start (en-US)、grid-row-end (en-US) 和 grid-column-end (en-US) 的简写,
+        通过基线(line),跨度(span)或没有(自动)的网格放置在 grid row 中指定一个网格项的大小和位置,继而确定 grid area 的边界。
+     -->
+     <div class="content">
+        <div class="bg">
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+            <div class="plot"></div>
+        </div>
+        <div class="cover">
+            <div class="item1"></div>
+            <div class="item2"></div>
+            <div class="item3"></div>
+        </div>
+     </div>
+</body>
+</html>
+

Last Updated:
Contributors: xiaoyu
+ + + diff --git "a/base/JS\346\250\241\345\235\227\345\214\226\345\216\206\347\250\213.html" "b/base/JS\346\250\241\345\235\227\345\214\226\345\216\206\347\250\213.html" new file mode 100644 index 0000000..ed74336 --- /dev/null +++ "b/base/JS\346\250\241\345\235\227\345\214\226\345\216\206\347\250\213.html" @@ -0,0 +1,422 @@ + + + + + + + + + JS 模块化历程 | 🍰 小雨的学习记录 + + + + + +

JS 模块化历程

JS模块化.png

模块化的历程

这个是面试过程中面试官经常问到的点,需要好好准备一下! 分享一个不错的博客链接 模块化open in new window

全局的 function 模式

<!-- test.html -->
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Document</title>
+  </head>
+  <body>
+    <script src="./module.js"></script>
+    <script>
+      foo();
+      bar();
+
+      msg = "小雨呀";
+      foo();
+    </script>
+  </body>
+</html>
+
// module.js
+/* 全局函数模式:将不同的功能封装成不同的全局函数…… */
+let msg="xiaoyu"
+function foo(){
+    console.log("foo()",msg);
+}
+
+function bar(){
+    console.log("bar()",msg);
+}
+

Namespace 模式

<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Document</title>
+</head>
+<body>
+    <script src="./module.js"></script>
+    <script>
+        obj.foo()
+        obj.msg="小雨"
+        obj.foo()
+    </script>
+</body>
+</html>
+
// module.js
+/* namespace 模式:简单对象封装…… */
+let obj={
+    msg:"xiaoyu",
+    foo(){
+        console.log("foo()",this.msg);
+    }
+}
+

IIFE 模式

<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Document</title>
+</head>
+<body>
+    <script src="./index.js"></script>
+    <script>
+        util.foo()
+    </script>
+</body>
+</html>
+
// index.js
+/* IIFE模式:匿名函数自调用(闭包) */
+// IIFE模式
+
+let util=(function(){
+    let msg="xiaoyu";
+    function foo(){
+        console.log("foo()",msg);
+    }
+    var module={
+        foo
+    }
+    
+    return module
+})()
+

IIFE 增强模式

<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Document</title>
+</head>
+<body>
+    <script src="./index.js"></script>
+    <script>
+        module()
+    </script>
+</body>
+</html>
+
// index.js
+/* IIFE增强模式:引入依赖  这是现代模块化实现的基石 */
+
+(function(window,document){
+    let msg="xiaoyu";
+    function foo(){
+        console.log("foo()",msg);
+    }
+    window.module=foo
+    document.querySelector("body").style.backgroundColor="red"
+})(window,document)
+

AMD

NO-AMD

<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Document</title>
+</head>
+<body>
+    <!-- 注意顺序不能变化,这种依赖一开始就写好的!!! -->
+    <script src="./js/dataService.js"></script>
+    <script src="./js/alerter.js"></script>
+    <script src="./app.js"></script>
+</body>
+</html>
+
// js/dataService.js
+// 定义一个没有依赖的模块
+(function(){
+    let name="dataService.js"
+    function getName(){
+        return name;
+    }
+    window.dataService={getName}
+})(window)
+
// js/alerter.js
+//定义一个有依赖的模块
+(function(window,dataService){
+    let msg="alerter.js"
+    function showMsg(){
+        console.log(msg,dataService.getName());
+    }
+    window.alerter={showMsg}
+})(window,dataService)
+
// app.js
+(function(alerter){
+    alerter.showMsg()
+})(alerter)
+

require.js

<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Document</title>
+</head>
+<body>
+    <!-- 入口,运用!!! 这个require.js自己去下载-->
+    <script data-main="./js/main.js" src="./js/lib/require.js"></script>
+</body>
+</html> 
+
// js/main.js
+(function(){
+    //66666 注意这个配置!!!
+    requirejs.config({
+        baseUrl:"js/",//基本的路径,出发点在根目录下
+        paths:{//配置路径
+            alerter:"./modules/alerter",
+            dataService:"./modules/dataService"
+        }
+    })
+
+    require(["alerter"],function(alerter){
+        alerter.showMsg();
+    })
+})()
+
+// https://www.runoob.com/w3cnote/requirejs-tutorial-1.html
+
// js/modules/dataService.js
+// 定义没有依赖的模块
+
+define(function() {
+    // 'use strict';
+    let name="dataService.js"
+    function getName(){
+        return name;
+    }
+
+    //暴露模块
+    return {getName}
+});
+
// js/modules/alerter.js
+// 定义有依赖的模块
+define([
+    "dataService"
+], function(dataService) {
+    // 'use strict';
+    let msg="alerter.js";
+    function showMsg(){
+        console.log(msg,dataService.getName());
+    }
+
+    return {showMsg}
+});
+

CMD - sea.js

<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Document</title>
+</head>
+<body>
+    <script src="./js/libs/sea.js"></script>
+    <script>
+        seajs.use("./js/modules/main.js")
+    </script>
+</body>
+</html>
+
// ./js/modules/main.js
+//定义没有依赖的模块
+define(function(require){
+    let module1=require("./module1")
+    module1.foo()
+    let module4=require("./module4")
+    module4.fun2()
+})
+
// ./js/modules/module1.js
+//定义没有依赖的模块
+define(function(require,exports,module){
+    let msg="module1";
+    function foo(){
+        return msg;
+    }
+    //暴露模块
+    module.exports={foo}
+})
+
// ./js/modules/module4.js
+//定义有依赖的模块
+define(function(require,exports,module){
+    let msg="module4";
+    //  同步
+    let module2=require("./module2")
+    module2()
+    //异步引用
+    require.async("./module3.js",function(module3){
+        module3.fun()
+    })
+    function fun2(){
+        console.log(msg);
+    }
+    exports.fun2=fun2
+})
+
// module2.js
+//定义没有依赖的模块
+define(function(require,exports,module){
+    let msg="module2";
+    function bar(){
+        console.log(msg);
+    }
+    //暴露模块
+    module.exports=bar
+})
+
// module3.js
+//定义没有依赖的模块
+define(function(require,exports,module){
+    let msg="module3";
+    function fun(){
+        console.log(msg);
+    }
+    //暴露模块
+    exports.fun=fun
+})
+

CommonJS

{
+  "name": "commonjs-node",
+  "version": "1.0.0",
+  "description": "",
+  "main": "main.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "author": "",
+  "license": "ISC"
+}
+
// main.js
+// 将其他的模块汇集到主模块
+let module1 =require("./modules/module1")
+let module2 =require("./modules/module2")
+let module3 =require("./modules/module3")
+
+module1.foo()
+module2()
+module3.bar()
+module3.foo()
+
// modules/module1.js
+// module.exports = value 暴露一个对象
+
+module.exports={
+    msg:"module1",
+    foo(){
+        console.log("foo()",this.msg);
+    }
+}
+
// modules/module2.js
+// 暴露一个函数   module.exports = function(){}
+
+module.exports=function(){
+    console.log("module2");
+}
+
// modules/module3.js
+// exports.xxx = value 
+exports.foo = function(){
+    console.log("foo() module3");
+}
+
+exports.bar = function(){
+    console.log("bar() module3");
+}
+

ES Module

{
+  "name": "es6",
+  "version": "1.0.0",
+  "description": "",
+  "main": "main.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "keywords": [],
+  "author": "",
+  "license": "ISC",
+  "type": "module"
+}
+
<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>Document</title>
+</head>
+<body>
+    <!-- ES6 注意路径哈  node 和 浏览器环境-->
+    <script type="module" src="./main.js"></script>
+</body>
+</html>
+
// main.js
+//注意js类型为module
+// 默认导出不带括号,其他带括号
+// 括号里面的名字得匹配引入的,括号外的可以变名字
+import Person,{name,age,sayName,obj} from './js/count.js'
+
+console.log(name,age,sayName(),obj);
+const p1=new Person('xiaoma',18)
+console.log(p1);
+
+import hh from "./js/test.js"
+hh()
+
// js/count.js
+// ES6模块功能主要由两个命令构成:export 和 import
+// export用于规定模块的对外接口 import用于输入其他模块提供的功能
+// 一个模块就是一个独立的文件
+export const name='zhnagsan'
+export const age=18
+export function sayName(){
+    return 'my name is 小马哥'
+}
+/* export{
+    name,
+    age,
+    sayName
+} */
+export const obj={
+    foo:'foo'
+}
+
+
+class Person{
+    constructor(name,age){
+        this.name=name;
+        this.age=age;
+    }
+    sayName(){
+        return this.name
+    }
+    sayAge(){
+        return this.age
+    }
+}
+// 默认导出
+export default Person
+
// js/test.js
+export default function(){
+    console.log("sb");
+}
+
Last Updated:
Contributors: xiaoyu
+ + + diff --git a/base/index.html b/base/index.html new file mode 100644 index 0000000..eb8000c --- /dev/null +++ b/base/index.html @@ -0,0 +1,37 @@ + + + + + + + + + 前言 | 🍰 小雨的学习记录 + + + + + +

前言

前端基础技术栈


  • HTML5 / CSS3 / JS / TS
  • less / sass / postcss
  • axios
  • Vue / React
  • Vuex / Pinia / Redux
  • MUI / ElementPlus / Ant Design
  • Webpack / Vite
  • Git
  • Java / Node
  • canvas / SVG / D3 / three.js / Echarts
  • Springboot / Express / Koa
  • ……

深入了解


  • 浏览器原理、缓存机制
  • 前端SPA应用性能优化、首屏优化
  • 网络原理、安全对策
  • 框架、插件、第三方库底层原理
  • 小程序、混合、原生、桌面应用开发
  • 项目自动化部署CI/CD
  • ……

学习还得继续进行下去……

Last Updated:
Contributors: xiaoyu
+ + + diff --git "a/base/\345\223\246\357\274\201\345\217\210\345\255\246\345\210\260\344\272\206\357\274\201.html" "b/base/\345\223\246\357\274\201\345\217\210\345\255\246\345\210\260\344\272\206\357\274\201.html" new file mode 100644 index 0000000..061bccf --- /dev/null +++ "b/base/\345\223\246\357\274\201\345\217\210\345\255\246\345\210\260\344\272\206\357\274\201.html" @@ -0,0 +1,600 @@ + + + + + + + + + 哦!又学到了! | 🍰 小雨的学习记录 + + + + + +

哦!又学到了!

append 和 appendchild 方法的区别

<!--
+ELement.append()方法在当前ELement的最后一个子节点之后插入一组Node对象或字符串对象。
+被插入的字符串对象等价为Text,节点其与Node.appendchild()的差异:
+
+Element.append()允许附加字符串对象,而 Node.appendchild()只接受Node对象。
+ELement.append()没有返回值,而 Node.appendChild()返回附加的Node对象。
+ELement.append()可以附加多个节点和字符串,而 Node.appendchiLd()只能附加一个节点。
+-->
+<div class="first"></div>
+<div class="second"></div>
+<script>
+  let first = document.queryselector(".first");
+  let second = document.querySelector(".second");
+  let br = document.createElement("br");
+  first.append("小雨鸽", br, "<h1>真开心!</h1>");
+  //  first.appendchiLd("KJKj")//parameter 1 is not of type 'Node' .
+  let div = document.createElement("div");
+  div.innerHTML = "<h1>哈哈哈哈哈</h1>";
+  second.appendchild(div);
+</script>
+

特殊的 Array.from

<div id="uu">
+  <ul>
+    <li>小雨</li>
+    <li>小雨</li>
+    <li>小雨</li>
+    <li>小雨</li>
+    <li>小雨</li>
+  </ul>
+</div>
+
+<script>
+  // Array.from()将类数组(类数组对象、arguments、Nodelist)转化成普通数组
+  let list=document.queryselectorAll("#uu li")
+  console.log(list);
+  let arr=Array.from(list)
+  console.log(arr);
+  let obj={
+    0: "hello",
+    1:"KKK",
+    2:"hhhhhh",
+    length: 3
+   }
+  console.log(Array.from(obj));
+  function HH(...args){
+      console.log(args);
+      console.1og(arguments);
+      console.log(Array.from(arguments));
+  }
+  HH(1,2,3,4,5)
+
+  //当让也可以用解构赋值
+  arr=[...arguments]
+</script>
+

快速生成一个二维数组

const arr = Array.from({ length: rows }, () => new Array(cols).fill(0));
+

H5 的拖拽事件

document.addEventListener("DOMContentLoaded", () => {
+  const gardenCanvas = document.getElementById("garden-canvas");
+  const draggables = document.querySelectorAl1(".draggable");
+
+  // 设置各植物图标的拖放事件
+  draggables.forEach((draggable) => {
+    draggable.addEventListener("dragstart", handleDragstart);
+  });
+
+  // 设置花园画布的拖放事件
+  gardenCanvas.addEventListener("dragover", handleDragover);
+  gardencanvas.addEventListener("drop", handleDrop);
+
+  function handleDragstart(e) {
+    e.dataTransfer.clearData();
+    e.dataTransfer.setData("text/plain", e.target.src); //掌握在拖、放两个对象之间传递数据的方法
+    e.dataTransfer.effectAllowed = "copy"; //只允许复制操作
+  }
+
+  function handleDragover(e) {
+    e.preventDefault(); //阻止默认行为以允许放置
+    e.dataTransfer.dropEffect = "copy"; //显示为复制操作
+  }
+  function handleDrop(e) {
+    e.preventDefault();
+    // TOD0:补全代码
+    let img = document.createElement("img");
+    img.setAttribute("src", e.dataTransfer.getData("text/plain"));
+    img.styLe.cssText = `position: absolute;top:${e.layerY - 50}px;left:${
+      e.layerX - 50
+    }px;`;
+    img.className = "draggable";
+    gardenCanvas.appendchi1d(img);
+  }
+});
+

历史记录 localstorage

<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>localstorage实例</title>
+  </head>
+  <body>
+    <input type="text" />
+    <button>点击收搜</button>
+    <h2>历史展示区</h2>
+    <section>
+      <ol class="history"></ol>
+    </section>
+
+    <script>
+      const input = document.querySelector("input[type='text']");
+      const button = document.querySelector("button");
+      const history = document.querySelector(".history");
+
+      if (localStorage.length > 0) {
+        for (let i = 0; i < localStorage.length; i++) {
+          let key = localStorage.key(i);
+          let li = document.createElement("li");
+          let litext = document.createTextNode(localStorage.getItem(key));
+          li.appendChild(litext);
+          history.appendChild(li);
+
+          li.addEventListener("click", function () {
+            localStorage.removeItem(key);
+            this.parentNode.removeChild(this);
+          });
+        }
+      }
+
+      button.addEventListener("click", function () {
+        if (input.value) {
+          let key = new Date().valueOf();
+          let value = input.value;
+          localStorage.setItem(key, value);
+          input.value = "";
+
+          let li = document.createElement("li");
+          let litext = document.createTextNode(localStorage.getItem(key));
+          li.appendChild(litext);
+          history.appendChild(li);
+
+          li.addEventListener("click", function () {
+            localStorage.removeItem(key);
+            this.parentNode.removeChild(this);
+          });
+        }
+      });
+    </script>
+  </body>
+</html>
+
<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>测试cookie</title>
+  </head>
+  <body>
+    <form>
+      <label>用户名</label>
+      <input type="text" />
+      <label>密码</label>
+      <input type="password" />
+      <input type="checkbox" id="remenberMe" />
+      <label for="remenberMe">记住我</label>
+      <input type="submit" value="登录" />
+    </form>
+    <script>
+      const username = document.querySelector("input[type='text']");
+      const checkbox = document.querySelector("input[type='checkbox']");
+      const submit = document.querySelector("input[type='submit']");
+      let arrays = document.cookie
+        .split("; ")
+        .map((cookie) => cookie.split("="));
+      let cookie = {};
+      for (let i = 0; i < arrays.length; i++) {
+        let key = arrays[i][0];
+        let value = arrays[i][1];
+        cookie[key] = decodeURIComponent(value);
+        console.log(value);
+      }
+      // console.log(cookie);
+      if (document.cookie) {
+        username.value = cookie.username;
+        checkbox.checked = true;
+      }
+      submit.addEventListener("click", (e) => {
+        if (checkbox.checked && username.value != "") {
+          let key = "username";
+          let value = encodeURIComponent(username.value);
+          console.log(username.value);
+          let twoDays = 2 * 24 * 60 * 60;
+
+          document.cookie = `${key}=${value}; max-age=${twoDays}`;
+        }
+        e.preventDefault();
+      });
+    </script>
+  </body>
+</html>
+

cookie 一般这样的形式:注意有空格和多个等号!!!

'lang=zh-cn; ollina=1702090; tfstk=f2ksLjCh=asas..; yuque_ctoken=ht8P_P8b8PHMMnnrY1u-SqQz; current_theme=default'
+

获取 cookie 的值

function getCookie(cname) {
+  var name = cname + "=";
+  var ca = document.cookie.split(";");
+  for (var i = 0; i < ca.length; i++) {
+    var c = ca[i].trim();
+    if (c.indexOf(name) == 0) return c.substring(name.length, c.length); //字符串截取
+  }
+  return "";
+}
+

使用 JavaScript 创建 Cookie

JavaScript 可以使用 document.cookie 属性来创建 、读取、及删除 cookie。

JavaScript 中,创建 cookie 如下所示:
document.cookie="username=John Doe";
您还可以为 cookie 添加一个过期时间(以 UTC 或 GMT 时间)。默认情况下,cookie 在浏览器关闭时删除:
document.cookie="username=John Doe; expires=Thu, 18 Dec 2043 12:00:00 GMT";
您可以使用 path 参数告诉浏览器 cookie 的路径。默认情况下,cookie 属于当前页面。
document.cookie="username=John Doe; expires=Thu, 18 Dec 2043 12:00:00 GMT; path=/";

使用 JavaScript 读取 Cookie

在 JavaScript 中, 可以使用以下代码来读取 cookie:
var x = document.cookie;不能读取到设置了 HttpOnly 的值,还要注意创建和读取不是一回事!!!

document.cookie 将以字符串的方式返回所有的 cookie,类型格式: cookie1=value; cookie2=value; cookie3=value;

使用 JavaScript 修改 Cookie

在 JavaScript 中,修改 cookie 类似于创建 cookie,如下所示:
document.cookie="username=John Smith; expires=Thu, 18 Dec 2043 12:00:00 GMT; path=/";
旧的 cookie 将被覆盖。

使用 JavaScript 删除 Cookie

删除 cookie 非常简单。您只需要设置 expires 参数为以前的时间即可,如下所示,设置为 Thu, 01 Jan 1970 00:00:00 GMT:
document.cookie = "username=; expires=Thu, 01 Jan 1970 00:00:00 GMT";
注意,当您删除时不必指定 cookie 的值。

图片懒加载

一种简易的实现方式

<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>一种简易的实现方式</title>
+    <style>
+      img {
+        display: block;
+        height: 200px;
+        margin: 30px;
+      }
+      p {
+        padding: 30px;
+      }
+    </style>
+  </head>
+  <body>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Ratione quam,
+      ducimus deleniti beatae dicta eius neque sed quos fuga dolorum in
+      accusantium vitae ipsum placeat quia molestiae, vero natus repellat!
+    </p>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Ratione quam,
+      ducimus deleniti beatae dicta eius neque sed quos fuga dolorum in
+      accusantium vitae ipsum placeat quia molestiae, vero natus repellat!
+    </p>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Ratione quam,
+      ducimus deleniti beatae dicta eius neque sed quos fuga dolorum in
+      accusantium vitae ipsum placeat quia molestiae, vero natus repellat!
+    </p>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Ratione quam,
+      ducimus deleniti beatae dicta eius neque sed quos fuga dolorum in
+      accusantium vitae ipsum placeat quia molestiae, vero natus repellat!
+    </p>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Ratione quam,
+      ducimus deleniti beatae dicta eius neque sed quos fuga dolorum in
+      accusantium vitae ipsum placeat quia molestiae, vero natus repellat!
+    </p>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Ratione quam,
+      ducimus deleniti beatae dicta eius neque sed quos fuga dolorum in
+      accusantium vitae ipsum placeat quia molestiae, vero natus repellat!
+    </p>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Ratione quam,
+      ducimus deleniti beatae dicta eius neque sed quos fuga dolorum in
+      accusantium vitae ipsum placeat quia molestiae, vero natus repellat!
+    </p>
+
+    <img data-src="1.jpg" alt="" />
+    <img data-src="2.jpg" alt="" />
+    <img data-src="3.jpg" alt="" />
+
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam qui
+      veritatis dolor quos culpa magni cupiditate quas repellat obcaecati.
+      Molestias facere quibusdam culpa excepturi fugiat iste at esse error
+      inventore.
+    </p>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam qui
+      veritatis dolor quos culpa magni cupiditate quas repellat obcaecati.
+      Molestias facere quibusdam culpa excepturi fugiat iste at esse error
+      inventore.
+    </p>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam qui
+      veritatis dolor quos culpa magni cupiditate quas repellat obcaecati.
+      Molestias facere quibusdam culpa excepturi fugiat iste at esse error
+      inventore.
+    </p>
+
+    <script>
+      let imgs = document.querySelectorAll("img");
+
+      window.addEventListener("scroll", (e) => {
+        imgs.forEach((img) => {
+          const imgTop = img.getBoundingClientRect().top;
+          if (imgTop < window.innerHeight) {
+            const data_src = img.dataset.src;
+            img.setAttribute("src", data_src);
+          }
+          console.log("滚动事件触发了"); //这样其实很浪费资源的!!!
+        });
+      });
+    </script>
+  </body>
+</html>
+

intersectionObserver 实现方式

<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>intersectionObserver实现方式</title>
+    <style>
+      img {
+        /* display: block; */
+        height: 200px;
+        margin: 30px;
+      }
+      p {
+        padding: 30px;
+      }
+    </style>
+  </head>
+  <body>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Ratione quam,
+      ducimus deleniti beatae dicta eius neque sed quos fuga dolorum in
+      accusantium vitae ipsum placeat quia molestiae, vero natus repellat!
+    </p>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Ratione quam,
+      ducimus deleniti beatae dicta eius neque sed quos fuga dolorum in
+      accusantium vitae ipsum placeat quia molestiae, vero natus repellat!
+    </p>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Ratione quam,
+      ducimus deleniti beatae dicta eius neque sed quos fuga dolorum in
+      accusantium vitae ipsum placeat quia molestiae, vero natus repellat!
+    </p>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Ratione quam,
+      ducimus deleniti beatae dicta eius neque sed quos fuga dolorum in
+      accusantium vitae ipsum placeat quia molestiae, vero natus repellat!
+    </p>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Ratione quam,
+      ducimus deleniti beatae dicta eius neque sed quos fuga dolorum in
+      accusantium vitae ipsum placeat quia molestiae, vero natus repellat!
+    </p>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Ratione quam,
+      ducimus deleniti beatae dicta eius neque sed quos fuga dolorum in
+      accusantium vitae ipsum placeat quia molestiae, vero natus repellat!
+    </p>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Ratione quam,
+      ducimus deleniti beatae dicta eius neque sed quos fuga dolorum in
+      accusantium vitae ipsum placeat quia molestiae, vero natus repellat!
+    </p>
+
+    <img data-src="1.jpg" alt="" />
+    <img data-src="2.jpg" alt="" />
+    <img data-src="3.jpg" alt="" />
+
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam qui
+      veritatis dolor quos culpa magni cupiditate quas repellat obcaecati.
+      Molestias facere quibusdam culpa excepturi fugiat iste at esse error
+      inventore.
+    </p>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam qui
+      veritatis dolor quos culpa magni cupiditate quas repellat obcaecati.
+      Molestias facere quibusdam culpa excepturi fugiat iste at esse error
+      inventore.
+    </p>
+    <p>
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Veniam qui
+      veritatis dolor quos culpa magni cupiditate quas repellat obcaecati.
+      Molestias facere quibusdam culpa excepturi fugiat iste at esse error
+      inventore.
+    </p>
+
+    <script>
+      /* intersectionObserver 交叉观察
+        IntersectionObserver 接口(从属于 Intersection Observer API)提供了一种异步观察目标元素与其祖先元素或顶级文档视口(viewport)交叉状态的方法。其祖先元素或视口被称为根(root)。
+        当一个 IntersectionObserver 对象被创建时,其被配置为监听根中一段给定比例的可见区域。
+        一旦 IntersectionObserver 被创建,则无法更改其配置,所以一个给定的观察者对象只能用来监听可见区域的特定变化值;
+        然而,你可以在同一个观察者对象中配置监听多个目标元素。
+        */
+
+      const images = document.querySelectorAll("img");
+
+      const callback = (entries) => {
+        entries.forEach((entry) => {
+          // console.log(entry);
+          if (entry.isIntersecting) {
+            const image = entry.target;
+            let data_src = image.dataset.data;
+            image.setAttribute("src", data_src);
+            observer.unobserve(image); //取消观察!!!
+            console.log("触发");
+          }
+        });
+        // console.log("看见了触发,看不见了也触发");
+      };
+      const observer = new IntersectionObserver(callback);
+
+      images.forEach((img) => {
+        //进行观察
+        observer.observe(img);
+      });
+    </script>
+  </body>
+</html>
+

多行省略

<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>多行省略</title>
+    <style>
+      p.p1 {
+        line-height: 30px;
+        width: 50%;
+        border: 1px solid red;
+        font-size: 20px;
+        /* text-overflow 属性并不会强制“溢出”事件的发生,因此为了能让文本能够溢出容器,你需要在元素上添加几个额外的属性:overflow 和 white-space */
+        white-space: nowrap;
+        overflow: hidden;
+
+        text-overflow: ellipsis; /* text-overflow 属性只对那些在块级元素溢出的内容有效 */
+      }
+      /* 第一种方法 :有比较大的局限性 */
+      p.p2 {
+        height: 110px;
+        width: 50%;
+        border: 1px black solid;
+        font-size: 20px;
+
+        overflow: hidden;
+        display: -webkit-box;
+        -webkit-box-orient: vertical;
+        -webkit-line-clamp: 4;
+        /* 简洁明了 这是私有属性 */
+      }
+      /* 第二种方法 伪元素创建  如果不是纯色的就不好弄了*/
+      p.p3 {
+        height: 110px;
+        width: 70%;
+        border: 1px black solid;
+        font-size: 20px;
+
+        overflow: hidden;
+        position: relative;
+        padding-right: 1em;
+        text-align: justify;
+      }
+      p.p3::before {
+        content: "...";
+        position: absolute;
+        right: 0;
+        bottom: 0;
+      }
+      p.p3::after {
+        content: "";
+        width: 1em;
+        height: 10em;
+        background-color: lime;
+        position: absolute;
+        display: inline;
+        right: 0;
+        margin-top: 0.5em;
+      }
+      /* 第三种方法:渐变色 */
+      p.p4 {
+        height: 110px;
+        width: 70%;
+        border: 1px black solid;
+        font-size: 20px;
+
+        overflow: hidden;
+        position: relative;
+        text-align: justify;
+      }
+      p.p4::after {
+        content: "";
+        position: absolute;
+        height: 1.2em;
+        width: 20%;
+        background: linear-gradient(to right, rgba(255, 255, 255, 0), #fff 80%);
+        right: 0;
+        bottom: 0;
+        margin-bottom: 0.2em;
+      }
+    </style>
+  </head>
+  <body>
+    <!-- 多行省略一直是个头疼的问题,因为规范的CSS里 text-overflow:ellipsis只适用于单行文本 -->
+    <p class="p1">
+      Lorem ipsum dolor sit amet, consectetur adipisicing elit. Excepturi itaque
+      hic unde qui quae aut porro veritatis facere vero eius. Perferendis nulla
+      rem autem incidunt porro culpa quis veniam obcaecati.
+    </p>
+    <p class="p2">
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Officia nisi
+      dolorum recusandae ea mollitia autem atque delectus incidunt placeat
+      deleniti doloremque suscipit rerum, corrupti ab sed ut quasi soluta est.
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Ipsum explicabo
+      earum obcaecati corrupti iusto quasi blanditiis cumque dolor, consequuntur
+      adipisci molestiae. Dolor eum laborum ipsum sapiente qui voluptatum
+      nostrum ex.
+    </p>
+    <p class="p3">
+      Lorem ipsum dolor sit amet consectetur adipisicing elit. Similique quidem
+      nihil, quaerat necessitatibus voluptatibus reiciendis. Nemo recusandae
+      officiis sapiente esse quis commodi corrupti soluta laboriosam, ut,
+      deleniti modi illum! In.
+    </p>
+    <p class="p4">
+      Lorem ipsum, dolor sit amet consectetur adipisicing elit. Pariatur
+      accusantium corporis, quae quasi consectetur necessitatibus tempore
+      perferendis rem impedit deleniti, dignissimos saepe. Amet error aliquam
+      veritatis deserunt laborum beatae cupiditate.
+    </p>
+  </body>
+</html>
+

数组扁平化

1. reduce 实现

const arr = [1, [2, [3, 4, 5]]];
+// 扁平化数组,用reduce函数
+function flatten(arr) {
+  return arr.reduce((pre, cur) => {
+    return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);
+  }, []);
+}
+

2. 数组方法 flat 实现

const arr = [1, [2, [3, [4, 5]]]];
+
+arr.flat(Infinity);
+

3. split + toString 实现

function flatten(arr) {
+  return arr
+    .toString()
+    .split(",")
+    .map((i) => Number(i));
+}
+

4. 正则 + JSON 实现

function flatten(arr) {
+  let str = JSON.stringify(arr);
+  str = str.replace(/(\[|\])/g, "");
+  // 拼接最外层,变成JSON能解析的格式
+  str = "[" + str + "]";
+  return JSON.parse(str);
+}
+

5. 扩展运算符 实现

function flatten(arr) {
+  while (arr.some((i) => Array.isArray(i))) {
+    arr = [].concat(...arr);
+    //注意这个concat方法特性  数组(解构一层) 或 元素 --> 元素
+  }
+  return arr;
+}
+

6. 普通递归 实现

function flatten(arr) {
+  let result = [];
+  for (let i = 0; i < arr.length; i++) {
+    // 当前元素是一个数组,对其进行递归展平
+    if (Array.isArray(arr[i])) {
+      // 递归展平结果拼接到结果数组
+      result = result.concat(flatten(arr[i]));
+    } else {
+      // 否则直接加入结果数组
+      result.push(arr[i]);
+    }
+  }
+  return result;
+}
+
Last Updated:
Contributors: xiaoyu
+ + + diff --git "a/base/\346\211\213\345\206\231\351\242\230.html" "b/base/\346\211\213\345\206\231\351\242\230.html" new file mode 100644 index 0000000..dad24a2 --- /dev/null +++ "b/base/\346\211\213\345\206\231\351\242\230.html" @@ -0,0 +1,2750 @@ + + + + + + + + + 手写题 | 🍰 小雨的学习记录 + + + + + +

手写题

前端面试常考

1.实现一个 call 函数

Function.prototype.myCall = function (context) {
+  var context = context || window;
+  //给context添加一个属性
+  //getValue.call(a,'yck',24) => a.fn=getValue
+  context.fn = this;
+  //将context后面的参数全部取出
+  var args = [...arguments].slice(1);
+  //进行实现,这里改变了this->context
+  //getValue.call(a,'yck',24) => a.fn('yck',24)
+  var reslut = context.fn(...args);
+  //删除 fn
+  delete context.fn;
+  return reslut;
+};
+
+/* 黑马:手写call
+  1、定义myCall方法
+  2、设置this并调用原函数
+  3、接收剩余参数并返回结果
+  4、使用Symbol调优
+*/
+Function.prototype.myCall = function (thisArg, ...args) {
+  //下面这里有个小小的隐患,如果thisArg传进来的对象已经存在了f这个属性,那么下面这行代码会覆盖之前的值
+  //解决:给thisArg加一个一定不重名的新属性
+  let key = Symbol("key");
+  thisArg[key] = this;
+  const res = thisArg[key](...args);
+  //这里要去除,要不然后面的对象会新出现这个属性
+  delete thisArg[key];
+  return res;
+};
+

2.实现一个 apply 函数

Function.prototype.myApply = function (context) {
+  var context = context || window;
+  context.fn = this;
+
+  var reslut;
+  //需要判断是否存储第二个参数,如果存在,就将第二个参数展开
+  if (arguments[1]) {
+    reslut = context.fn(...arguments[1]);
+  } else {
+    reslut = context.fn();
+  }
+
+  delete context.fn;
+  return reslut;
+};
+
+/* 手写apply函数  */
+Function.prototype.myApply = function (thisArg, args) {
+  let fn = Symbol("fn");
+  thisArg[fn] = this;
+  const result = thisArg[fn](args);
+  delete thisArg[fn];
+  return result;
+};
+

3.实现一个 bind 函数

/* 首先了解一下bind函数 */
+//bind() 最简单的用法是创建一个函数,无论如何调用,它都会使用特定的 this 值进行调用。
+// 顶级的“this”绑定到“globalThis”。
+this.x = 9;
+const module = {
+  x: 81,
+  getX() {
+    return this.x;
+  },
+};
+
+// “getX”的“this”参数绑定到“module”。
+console.log(module.getX()); // 81
+
+const retrieveX = module.getX;
+// “retrieveX”的“this”参数在非严格模式下绑定到“globalThis”。
+console.log(retrieveX()); // 9
+
+// 创建一个新函数“boundGetX”,并将“this”参数绑定到“module”。
+const boundGetX = retrieveX.bind(module);
+console.log(boundGetX()); // 81
+
+//bind() 的另一个简单用法是创建一个具有预设初始参数的函数。
+function list(...args) {
+  return args;
+}
+
+function addArguments(arg1, arg2) {
+  return arg1 + arg2;
+}
+
+console.log(list(1, 2, 3)); // [1, 2, 3]
+
+console.log(addArguments(1, 2)); // 3
+
+// 创建一个带有预设前导参数的函数
+const leadingThirtySevenList = list.bind(null, 37);
+
+// 创建一个带有预设第一个参数的函数。
+const addThirtySeven = addArguments.bind(null, 37);
+
+console.log(leadingThirtySevenList()); // [37]
+console.log(leadingThirtySevenList(1, 2, 3)); // [37, 1, 2, 3]
+console.log(addThirtySeven(5)); // 42
+console.log(addThirtySeven(5, 10)); // 42
+//(最后一个参数 10 被忽略)
+
+//实现一个bind函数
+Function.prototype.myBind = function (context) {
+  if (typeof this !== "function") {
+    throw new TypeError("Error");
+  }
+  var _this = this;
+  var args = [...arguments].slice(1); //这里的arguments是myBind这个函数的参数
+  //返回一个函数
+  return function F() {
+    //因为返回了一个函数,我们可以new F(),所以需要判断
+    if (this instanceof F) {
+      return new _this(...args, ...arguments); //这里的arguments是F这个函数的参数
+    }
+    return _this.apply(context, args.concat(...arguments));
+  };
+};
+
+/* 实现一个bind方法 
+  1、定义myBind方法
+  2、返回绑定this的新函数
+  3、合并绑定和新传入的参数 
+  */
+//1.定义myBind方法
+Function.prototype.myBind = function (thisArg, ...args) {
+  // 2.返回绑定this的新函数
+  return (...reArgs) => {
+    // this:原函数(原函数.myBind)
+    return this.call(thisArg, ...args, ...reArgs);
+  };
+};
+
+//实现一个bind函数
+Function.prototype.myBind = function (thisArg, ...args) {
+  let _this = this;
+  return function () {
+    return _this.call(thisArg, ...args, ...arguments);
+  };
+};
+

4.实现 instanceof

//instanceof 可以正确判断对象的基本类型,因为内部机制是通过判断对象的原型链中是不是能够找到类型的 prototype
+
+function instance(left, right) {
+  //对象 和 类型
+  //获得类型的原型
+  let prototype = right.prototype;
+  //   获得对象的原型
+  left = left.__proto__;
+  //   判断对象类型是否等于类型的原型
+  while (true) {
+    if (left === null) {
+      //已经往上找不到了,没有原型了
+      return false;
+    }
+    if (prototype === left) {
+      return true;
+    }
+    left = left.__proto__;
+  }
+}
+
+let arr = [];
+console.log(instance(arr, Array));
+

5.实现一个 new

/* 
+在调用new的过程中会发生如下四件事情
+1.新生成了一个对象
+2.链接到了原型
+3.绑定this
+4.返回新对象
+*/
+function _new(constructor, ...args) {
+  //创建一个新的对象
+  var obj = new Object();
+  //链接到原型
+  obj.__proto__ = constructor.prototype;
+  //绑定this,执行构造函数
+  var res = constructor.apply(obj, args);
+  //确保new出来的是一个对象
+  return typeof res === "object" ? res : obj;
+}
+
+function Fun(name) {
+  this.name = name;
+}
+console.log(new Fun("xiao"));
+console.log(_new(Fun, "xiaoyu"));
+

6.Generator-id 生成器

// **需求:**使用`Generator`实现一个id生成器id
+
+function* idGenerator() {
+  let id = 0;
+  while (true) {
+    yield id++;
+  }
+}
+
+const idMaker = idGenerator();
+
+const { value: id1 } = idMaker.next();
+const { value: id2 } = idMaker.next();
+const { value: id3 } = idMaker.next();
+
+console.log(id1, id2, id3);
+

7.函数柯里化一道面试题

/**
+ * 改写函数,实现如下效果
+ *
+ * function sum(a,b,c,d){
+ *  return a+b+c+d
+ * }
+ *
+ * //改写函数,参数传递5个即可累加实现
+ * sum(1)(2)(3)(4)(5)
+ * sum(1)(2,3)(4)(5)
+ * sum(1)(2,3,4)(5)
+ * sum(1,2)(3,4,5)
+ *  */
+
+let arr = []; //保存不定长数组
+function sum(...args) {
+  arr.push(...args);
+  if (arr.length >= 5) {
+    //进行累加
+    let res = arr.slice(0, 5).reduce((cur, pre) => pre + cur, 0);
+    arr = [];
+    return res;
+  } else {
+    return sum;
+  }
+}
+
+console.log(sum(1)(2)(3, 5, 4, 1));
+

8.实现一个管道函数

function fn1(x) {
+  return x + 1;
+}
+function fn2(x) {
+  return x * 2;
+}
+
+// function pine(...fns){
+//     return function(x){
+//         return fns.reduce((acc,fn)=>{
+//             return fn(acc)
+//         },x)
+//     }
+// }
+
+const pineline = pine([fn1, fn2]);
+const output = pineline(5);
+console.log(output);
+
+function pine(fns) {
+  return function (x) {
+    return fns.reduce((prevRes, fn) => fn(prevRes), x);
+  };
+}
+

9.手写 loadsh_get 方法

// input
+const obj = {
+  选择器: { to: { toutiao: "FE Coder" } },
+  target: [1, 2, { name: "byted" }],
+};
+get(obj, "选择器.to.toutiao", "target[0]", "target[2].name");
+
+// output
+["FE coder", 1, "byted"];
+
+function get(object, ...path) {
+  return path.map((item) => {
+    let res = object;
+    item
+      .replace(/\[/g, ".")
+      .replace(/\]/g, "")
+      .split(".")
+      .map((path) => (res = res && res[path]));
+    return res;
+  });
+}
+
在表达式 obj & & obj ['a']中,计算第一个对象。
+
+如果是 false 整个表达式就是 false 结果就是第一个操作数,
+
+如果对象是真实的,那么第二部分
+
+Obj['a']将被求值,其结果将是表达式的最终结果。
+
+let obj = { a: 42 };
+let result = obj && obj['a']; // result will be 42
+
+obj = null;
+result = obj && obj['a']; // result will be null
+








 
 
 
 
 

10.手写 nextTick 方法

export function myNextTick(fn) {
+  let app = document.getElementById("app");
+  var observerOptions = {
+    childList: true, //观察目标子节点的变化
+    attributes: true, //观察属性变动
+    subtree: true, //观察后代节点,默认为false
+  };
+
+  //让fn()在DOM更新完成后执行
+  //创建一个DOM监听器
+  let observer = new MutationObserver((el) => {
+    //当被监听的DOM更新完成时,该回调会触发
+    console.log(el);
+    fn();
+  });
+  observer.observe(app, observerOptions);
+}
+
+// 另一种
+function nextTick(fn){
+    Promise.resolve().then(fn)
+}
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 


 
 
 

11.allComplete

// 手写一个方法,使用Promise.all,实现所有都resolved/reject时才返回,并返回所有的结果
+let p1 = new Promise((resolve, reject) => {
+  setTimeout(() => {
+    reject(100);
+  }, 1000);
+});
+let p2 = new Promise((resolve, reject) => {
+  setTimeout(() => {
+    resolve(200);
+  }, 2000);
+});
+let p3 = new Promise((resolve, reject) => {
+  setTimeout(() => {
+    resolve(300);
+  }, 3000);
+});
+
+Promise.allSettled([p1, p2, p3]).then((val) => {
+  console.log(val);
+  //   [
+  //     { status: "fulfilled", value: 100 },
+  //     { status: "fulfilled", value: 200 },
+  //     { status: "fulfilled", value: 300 },
+  //   ];
+});
+
+//方法就是让他们全部进行兑现!
+function allComplete(arr) {
+  return Promise.all(
+    arr.map((promise) => {
+      return new Promise((resolve) => promise.then(resolve, resolve));
+    })
+  );
+}
+

12.防抖与节流


+// 防抖与节流          共同点               区别               应用场景
+// 防抖:debounce   在事件频繁触发时       只执行最后一次      input输入
+// 节流:throttle   减少事件执行的次数     有规律地执行        拖拽、scroll
+
+//防抖
+function debounce(fn, delay){
+    let timer=null
+    return function(...args){
+        if(timer){
+            clearTimeout(timer)
+        }
+        timer=setTimeout(()=>{
+            fn.apply(this, args)
+        }, delay)
+    }
+}
+
+//节流
+function throttle(fn, delay){
+    let timer=null
+    return function(...args){
+        if(!timer){
+            timer=setTimeout(()=>{
+                fn.apply(this, args)
+                timer=null
+            }, delay)
+        }
+    }
+}
+
+
+//节流的另一种写法
+function throttle1(fn,delay){
+    let pre=0;
+    return function(){
+        let now=new Date();
+        if(now-pre>delay){
+            fn.apply(this,arguments)
+            pre=now;
+        }
+    }
+}
+
+

13.深拷贝浅拷贝

let obj = {
+  id: "1",
+  name: "luoyu",
+  msg: {
+    age: 18,
+  },
+};
+//浅拷贝:只会完整拷贝浅层,深层拷贝的是地址
+let o = {};
+for (let k in obj) {
+  o[k] = obj[k];
+}
+o.msg.age = 20;
+o.id = "2";
+o.name = "luolin";
+console.log(o);
+console.log(obj); //深层中obj对象的msg.age受到影响
+
+//还有一种浅拷贝操作
+Object.assign(o, obj); //将obj浅拷贝给o,实际开发中用assign方法实现浅拷贝
+console.log(o);
+console.log(obj);
+
+console.log("--------------------------");
+//深拷贝:每一级数据都会被拷贝
+o = {};
+//封装函数
+function deepCopy(newobj, oldobj) {
+  for (let k in oldobj) {
+    //判断我们的属性值属于哪种数据类型
+    //1.获取属性值  oldobj[k]
+    var item = oldobj[k];
+    if (item instanceof Array) {
+      //数组放在上面,因为数组也属于对象
+      //2.判断这个值是否是数组
+      newobj[k] = [];
+      deepCopy(newobj[k], item);
+    } else if (item instanceof Object) {
+      //3.判断这个值是否是对象
+      newobj[k] = {};
+      deepCopy(newobj[k], item);
+    } else {
+      ///属于简单数据类型
+      newobj[k] = item;
+    }
+  }
+}
+deepCopy(o, obj);
+o.msg.age = 23; //对obj没有了影响
+console.log(o);
+console.log(obj);
+

手写 Promise

完整 Promise

<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>Document</title>
+  </head>
+  <body>
+    <h1>手写Promise</h1>
+    <script>
+      const PENDING = "pending";
+      const FULFILLED = "fulfilled";
+      const REJECTED = "rejected";
+      function runAsynctask(callback) {
+        if (typeof queueMicrotask === "function") {
+          queueMicrotask(callback);
+        } else if (typeof MutationObserver === "function") {
+          let obs = new MutationObserver(callback);
+          let divNode = document.createElement("div");
+          obs.observe(divNode, { childList: true });
+          divNode.innerHTML = "RAIN";
+        } else {
+          setTimeout(callback, 0);
+        }
+      }
+      function resolvePromise(p2, x, resolve, reject) {
+        if (x === p2) reject(new TypeError("chining error in Promise"));
+        if (x instanceof RainPromise) {
+          x.then(
+            (res) => resolve(res),
+            (err) => reject(err)
+          );
+        } else {
+          resolve(x);
+        }
+      }
+      class RainPromise {
+        state = PENDING;
+        result = undefined;
+        #handler = [];
+        constructor(func) {
+          const resolve = (res) => {
+            if (this.state === PENDING) {
+              this.state = FULFILLED;
+              this.result = res;
+              this.#handler.forEach((onFulfilled) => {
+                onFulfilled();
+              });
+            }
+          };
+          const reject = (res) => {
+            if (this.state === PENDING) {
+              this.state = REJECTED;
+              this.result = res;
+              this.#handler.forEach((onRejected) => {
+                onRejected();
+              });
+            }
+          };
+
+          func(resolve, reject);
+        }
+        // 实例方法 then()
+        then(onFulfilled, onRejected) {
+          onFulfilled =
+            typeof onFulfilled === "function" ? onFulfilled : (x) => x;
+          onRejected =
+            typeof onRejected === "function"
+              ? onRejected
+              : (x) => {
+                  throw x;
+                };
+
+          const p2 = new RainPromise((resolve, reject) => {
+            if (this.state === FULFILLED) {
+              runAsynctask(() => {
+                try {
+                  let x = onFulfilled(this.result);
+                  if (x === p2)
+                    reject(new TypeError("chining error in Promise"));
+                  if (x instanceof RainPromise) {
+                    x.then(
+                      (res) => resolve(res),
+                      (err) => reject(err)
+                    );
+                  } else {
+                    resolve(x);
+                  }
+                } catch (err) {
+                  reject(err);
+                }
+              });
+            } else if (this.state === REJECTED) {
+              runAsynctask(() => {
+                try {
+                  let x = onRejected(this.result);
+                  resolvePromise(p2, x, resolve, reject);
+                } catch (error) {
+                  reject(error);
+                }
+              });
+            } else if (this.state === PENDING) {
+              this.#handler.push({
+                onFulfilled: () => {
+                  runAsynctask(() => {
+                    try {
+                      let x = onFulfilled(this.result);
+                      resolvePromise(p2, x, resolve, reject);
+                    } catch (error) {
+                      reject(error);
+                    }
+                  });
+                },
+                onRejected: () => {
+                  runAsynctask(() => {
+                    try {
+                      let x = onRejected(this.result);
+                      resolvePromise(p2, x, resolve, reject);
+                    } catch (error) {
+                      reject(error);
+                    }
+                  });
+                },
+              });
+            }
+          });
+          return p2;
+        }
+
+        /* 实例方法 catch */
+        catch(undefined, onRejected) {
+          return this.then(undefined, onRejected);
+        }
+        /* 实例方法 finally */
+        finally(onFinally, onFinally) {
+          return this.then(onFinally, onFinally);
+        }
+        /* 静态方法 resolve */
+        static resolve(value) {
+          if (value instanceof RainPromise) {
+            return value;
+          }
+          return new RainPromise((resolve) => {
+            resolve(value);
+          });
+        }
+        /* 静态方法 reject */
+        static reject(error) {
+          return new RainPromise((undefined, reject) => {
+            reject(error);
+          });
+        }
+        /* 静态方法 race */
+        static race(promises) {
+          return new RainPromise((resolve, reject) => {
+            if (!Array.isArray(promises)) {
+              return reject(new TypeError("Argument is not Array"));
+            }
+            //对空没有处理,那就是pending
+            promises.forEach((p) => {
+              RainPromise.resolve(p).then(
+                (res) => {
+                  resolve(res);
+                },
+                (err) => {
+                  reject(err);
+                }
+              );
+            });
+          });
+        }
+        /* 静态方法 all */
+        static all(promises) {
+          return new RainPromise((resolve, reject) => {
+            if (!Array.isArray(promises)) {
+              return reject(new TypeError("Argument is not Array"));
+            }
+            promises.length === 0 && resolve(promises);
+            const result = [];
+            let count = 0;
+            promises.forEach((p, index) => {
+              RainPromise.resolve(p).then(
+                (res) => {
+                  result[index] = res;
+                  count++;
+                  count === promises.length && resolve(result);
+                },
+                (err) => {
+                  reject(err);
+                }
+              );
+            });
+          });
+        }
+        /* 静态方法 allsettled */
+        static allsettled(promises) {
+          return new RainPromise((resolve, reject) => {
+            if (!Array.isArray(promises)) {
+              return reject(new TypeError("Argument is not Array"));
+            }
+            promises.length === 0 && resolve(promises);
+            const result = [];
+            let count = 0;
+            promises.forEach((p, index) => {
+              RainPromise.resolve(p).then(
+                (res) => {
+                  result[index] = { state: FULFILLED, value: res };
+                  count++;
+                  count === promises.length && resolve(result);
+                },
+                (err) => {
+                  result[index] = { state: REJECTED, value: err };
+                  count++;
+                  count === promises.length && resolve(result);
+                }
+              );
+            });
+          });
+        }
+        /* 静态方法 any */
+        static any(promises) {
+          return new RainPromise((resolve, reject) => {
+            if (!Array.isArray(promises)) {
+              return reject(new TypeError("Argument is not Array"));
+            }
+            promises.length === 0 &&
+              reject(new AggregateError(promises, "All promise were rejected"));
+            const errors = [];
+            let count = 0;
+            promises.forEach((p, index) => {
+              RainPromise.resolve(p).then(
+                (res) => {
+                  resolve(res);
+                },
+                (err) => {
+                  errors[index] = err;
+                  count++;
+                  count === promises.length &&
+                    reject(
+                      new AggregateError(errors, "All promise were rejected")
+                    );
+                }
+              );
+            });
+          });
+        }
+      }
+    </script>
+  </body>
+</html>
+

01-构造函数

/**
+ *  构造函数
+ *  1.定义类
+ *  2.添加构造函数
+ *  3.定义resolve/reject
+ *  4.执行回调函数
+ * */
+//1.定义类
+class HMPromise {
+  // 2.添加构造函数
+  constructor(func) {
+    //3.定义resolve/reject
+    const resolve = (result) => {};
+    const reject = (result) => {};
+
+    //4.执行回调函数
+    func(resolve, reject);
+  }
+}
+
+/* 测试代码 */
+let p = new HMPromise((resolve, reject) => {
+  console.log("LLL");
+  resolve("成功");
+  // reject("失败")
+});
+








 
 
 
 
 
 
 
 
 
 
 







02-状态及原因

/**
+ *  状态及原因
+ *  1.添加状态(pending/fulfilled/rejected)
+ *  2.添加原因
+ *  3.调整resolve/reject
+ *  4.状态不可逆
+ * */
+const PENDING = "pending";
+const FULFILLED = "fulfilled";
+const REJECTED = "rejected";
+
+class HMPromise {
+  // 添加状态(pending/fulfilled/rejected)
+  // 添加原因
+  state = PENDING;
+  result = undefined;
+
+  constructor(func) {
+    const resolve = (result) => {
+      //状态不可逆
+      if (this.state === PENDING) {
+        //调整resolve/reject
+        this.state = FULFILLED;
+        this.result = result;
+      }
+    };
+    const reject = (result) => {
+      if (this.state === PENDING) {
+        this.state = REJECTED;
+        this.result = result;
+      }
+    };
+
+    func(resolve, reject);
+  }
+}
+
+/* 测试代码 */
+let p = new HMPromise((resolve, reject) => {
+  resolve("成功");
+  reject("失败");
+});
+







 
 
 




 
 


 
 
 
 
 
 
 
 
 
 
 
 
 
 










03-then 的方法-成功和失败的回调

/**
+ *  成功和失败的回调
+ *  1.添加实例方法
+ *  2.参数判断(参考文档)
+ *      2.1.执行成功的回调
+ *      2.2.执行失败的回调
+ *
+ * */
+const PENDING = "pending";
+const FULFILLED = "fulfilled";
+const REJECTED = "rejected";
+
+class HMPromise {
+  // 添加状态(pending/fulfilled/rejected)
+  // 添加原因
+  state = PENDING;
+  result = undefined;
+
+  constructor(func) {
+    const resolve = (result) => {
+      //状态不可逆
+      if (this.state === PENDING) {
+        //调整resolve/reject
+        this.state = FULFILLED;
+        this.result = result;
+      }
+    };
+    const reject = (result) => {
+      if (this.state === PENDING) {
+        this.state = REJECTED;
+        this.result = result;
+      }
+    };
+
+    func(resolve, reject);
+  }
+
+  //1.添加实例方法
+  then(onFulfilled, onRejected) {
+    //2.参数判断(参考文档)
+    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (x) => x;
+    onRejected =
+      typeof onRejected === "function"
+        ? onRejected
+        : (x) => {
+            throw x;
+          };
+
+    //执行成功的回调
+    if (this.state === FULFILLED) {
+      onFulfilled(this.result);
+    } else if (this.state === REJECTED) {
+      onRejected(this.result);
+    }
+  }
+}
+
+/* 测试代码 */
+let p = new HMPromise((resolve, reject) => {
+  resolve("成功");
+  reject("失败");
+});
+
+p.then(
+  (val) => {
+    console.log(val);
+  },
+  (err) => {
+    console.log(err);
+  }
+);
+






































 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 















04-then 的方法

/**
+ *  异步及多次调用
+ *  1.定义实例属性
+ *  2.保存回调函数
+ *  3.调用成功的回调
+ *  4.调用失败的回调
+ *
+ * */
+const PENDING = "pending";
+const FULFILLED = "fulfilled";
+const REJECTED = "rejected";
+
+class HMPromise {
+  // 添加状态(pending/fulfilled/rejected)
+  // 添加原因
+  state = PENDING;
+  result = undefined;
+  // 定义实例属性,用来保存我们的回调函数
+  //# 私有的,外部访问不到
+  #handlers = []; //[{onFulfilled,onRejected}...]
+
+  constructor(func) {
+    const resolve = (result) => {
+      //状态不可逆
+      if (this.state === PENDING) {
+        //调整resolve/reject
+        this.state = FULFILLED;
+        this.result = result;
+        //遍历取出
+        this.#handlers.forEach(({ onFulfilled }) => {
+          onFulfilled(this.result);
+        });
+      }
+    };
+    const reject = (result) => {
+      if (this.state === PENDING) {
+        this.state = REJECTED;
+        this.result = result;
+        this.#handlers.forEach(({ onRejected }) => {
+          onRejected(this.result);
+        });
+      }
+    };
+
+    func(resolve, reject);
+  }
+
+  //1.添加实例方法
+  then(onFulfilled, onRejected) {
+    //2.参数判断(参考文档)
+    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (x) => x;
+    onRejected =
+      typeof onRejected === "function"
+        ? onRejected
+        : (x) => {
+            throw x;
+          };
+
+    //执行成功的回调
+    if (this.state === FULFILLED) {
+      onFulfilled(this.result);
+    } else if (this.state === REJECTED) {
+      onRejected(this.result);
+    } else if (this.state === PENDING) {
+      //是pending状态的时候还不需要去执行函数,可以先保存起来
+      this.#handlers.push({
+        onFulfilled,
+        onRejected,
+      });
+    }
+  }
+}
+
+/* 测试代码 */
+let p = new HMPromise((resolve, reject) => {
+  setTimeout(() => {
+    resolve("成功");
+    // reject("失败")
+  }, 2000);
+});
+
+p.then(
+  (val) => {
+    console.log("then1" + val);
+  },
+  (err) => {
+    console.log("then1" + err);
+  }
+);
+
+p.then(
+  (val) => {
+    console.log("then2" + val);
+  },
+  (err) => {
+    console.log("then2" + err);
+  }
+);
+



















 









 
 
 






 
 
 






















 
 
 
 
 
 
 




























05-异步任务 API

/**
+ *  异步及多次调用
+ *  1.定义实例属性
+ *  2.保存回调函数
+ *  3.调用成功的回调
+ *  4.调用失败的回调
+ *
+ * */
+const PENDING = "pending";
+const FULFILLED = "fulfilled";
+const REJECTED = "rejected";
+
+class HMPromise {
+  // 添加状态(pending/fulfilled/rejected)
+  // 添加原因
+  state = PENDING;
+  result = undefined;
+  // 定义实例属性,用来保存我们的回调函数
+  //# 私有的,外部访问不到
+  #handlers = []; //[{onFulfilled,onRejected}...]
+
+  constructor(func) {
+    const resolve = (result) => {
+      //状态不可逆
+      if (this.state === PENDING) {
+        //调整resolve/reject
+        this.state = FULFILLED;
+        this.result = result;
+        //遍历取出
+        this.#handlers.forEach(({ onFulfilled }) => {
+          onFulfilled(this.result);
+        });
+      }
+    };
+    const reject = (result) => {
+      if (this.state === PENDING) {
+        this.state = REJECTED;
+        this.result = result;
+        this.#handlers.forEach(({ onRejected }) => {
+          onRejected(this.result);
+        });
+      }
+    };
+
+    func(resolve, reject);
+  }
+
+  //1.添加实例方法
+  then(onFulfilled, onRejected) {
+    //2.参数判断(参考文档)
+    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (x) => x;
+    onRejected =
+      typeof onRejected === "function"
+        ? onRejected
+        : (x) => {
+            throw x;
+          };
+
+    //执行成功的回调
+    if (this.state === FULFILLED) {
+      onFulfilled(this.result);
+    } else if (this.state === REJECTED) {
+      onRejected(this.result);
+    } else if (this.state === PENDING) {
+      //是pending状态的时候还不需要去执行函数,可以先保存起来
+      this.#handlers.push({
+        onFulfilled,
+        onRejected,
+      });
+    }
+  }
+}
+
+/* 测试代码 */
+console.log("top");
+let p = new HMPromise((resolve, reject) => {
+  resolve("success");
+});
+p.then((val) => {
+  console.log(val);
+});
+console.log("bottom");
+
+/**
+ * 异步任务:
+ * Vue:Promise.then,MutationObserver,setImmediate,setTimeout
+ * 我们选用:queueMicrotask MutationObserver setTimeout
+ *    Promise.then:手写Promise,不考虑这个
+ *    queueMicrotask:node11,新式浏览(不包括IE11)
+ *    MutationObserver:node不支持,IE11支持
+ *    setImmediate:IE10,11,支持,edge12-18支持(不考虑)
+ *    setTimeout:node,浏览器
+ * */
+
+//    ---------------异步任务1  queueMicrotask  -----------------
+console.log(1);
+queueMicrotask(() => {
+  console.log("queueMicrotask");
+});
+console.log(2);
+
+//    ---------------异步任务2 MutationObserver -----------------
+console.log(1);
+// 创建观察者,并传入回调函数
+const obs = new MutationObserver(() => {
+  console.log("mutationObserver");
+});
+//创建元素,并添加监听
+const divNode = document.createElement("div");
+//参数1 观察DOM节点
+//参数2 观察的选项(childList 观察子节点的改变)
+obs.observe(divNode, { childList: true });
+// 3.修改元素内容
+divNode.innerHTML = "itheima 666";
+console.log(2);
+
+// ------------- 异步任务 setTimeout --------------------------------
+



















































































 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

06-异步任务-函数封装

/**
+ * 异步任务-函数封装
+ *  1. 定义函数
+ *  2. 调用核心API(queueMicrotask,MutationObserver,setTimeout)
+ *  3. 使用封装函数
+ */
+
+// 函数封装
+function runAsynctask(callback) {
+  if (typeof queueMicrotask === "function") {
+    queueMicrotask(callback);
+  } else if (typeof MutationObserver === "function") {
+    const obs = new MutationObserver(callback);
+    const divNode = document.createElement("div");
+    obs.observe(divNode, { childList: true });
+    divNode.innerHTML = "hhh";
+  } else {
+    setTimeout(callback, 0);
+  }
+}
+
+const PENDING = "pending";
+const FULFILLED = "fulfilled";
+const REJECTED = "rejected";
+
+class HMPromise {
+  // 添加状态(pending/fulfilled/rejected)
+  // 添加原因
+  state = PENDING;
+  result = undefined;
+  // 定义实例属性,用来保存我们的回调函数
+  //# 私有的,外部访问不到
+  #handlers = []; //[{onFulfilled,onRejected}...]
+
+  constructor(func) {
+    const resolve = (result) => {
+      //状态不可逆
+      if (this.state === PENDING) {
+        //调整resolve/reject
+        this.state = FULFILLED;
+        this.result = result;
+        //遍历取出
+        this.#handlers.forEach(({ onFulfilled }) => {
+          // onFulfilled(this.result)
+          onFulfilled();
+        });
+      }
+    };
+    const reject = (result) => {
+      if (this.state === PENDING) {
+        this.state = REJECTED;
+        this.result = result;
+        this.#handlers.forEach(({ onRejected }) => {
+          // onRejected(this.result)
+          onRejected();
+        });
+      }
+    };
+
+    func(resolve, reject);
+  }
+
+  //1.添加实例方法
+  then(onFulfilled, onRejected) {
+    //2.参数判断(参考文档)
+    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (x) => x;
+    onRejected =
+      typeof onRejected === "function"
+        ? onRejected
+        : (x) => {
+            throw x;
+          };
+
+    //执行成功的回调
+    if (this.state === FULFILLED) {
+      runAsynctask(() => {
+        onFulfilled(this.result);
+      });
+    } else if (this.state === REJECTED) {
+      runAsynctask(() => {
+        onRejected(this.result);
+      });
+    } else if (this.state === PENDING) {
+      //是pending状态的时候还不需要去执行函数,可以先保存起来
+      this.#handlers.push({
+        onFulfilled: () => {
+          runAsynctask(() => {
+            onFulfilled(this.result);
+          });
+        },
+        onRejected: () => {
+          runAsynctask(() => {
+            onRejected(this.result);
+          });
+        },
+      });
+    }
+  }
+}
+
+/* 测试代码 */
+console.log("top");
+let p = new HMPromise((resolve, reject) => {
+  resolve("success");
+});
+p.then((val) => {
+  console.log(val);
+});
+console.log("bottom");
+








 
 
 
 
 
 
 
 
 
 
 
 






















































 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 












7-链式编程-处理返回值异常

/**
+ * 链式编程-处理返回值和普通内容(fulfilled状态)
+ * 1.返回新Promise实例
+ * 2.获取返回值
+ *  2.1.处理返回值
+ *  2.2.处理异常
+ */
+
+// 函数封装
+function runAsynctask(callback) {
+  if (typeof queueMicrotask === "function") {
+    queueMicrotask(callback);
+  } else if (typeof MutationObserver === "function") {
+    const obs = new MutationObserver(callback);
+    const divNode = document.createElement("div");
+    obs.observe(divNode, { childList: true });
+    divNode.innerHTML = "hhh";
+  } else {
+    setTimeout(callback, 0);
+  }
+}
+
+const PENDING = "pending";
+const FULFILLED = "fulfilled";
+const REJECTED = "rejected";
+
+class HMPromise {
+  // 添加状态(pending/fulfilled/rejected)
+  // 添加原因
+  state = PENDING;
+  result = undefined;
+  // 定义实例属性,用来保存我们的回调函数
+  //# 私有的,外部访问不到
+  #handlers = []; //[{onFulfilled,onRejected}...]
+
+  constructor(func) {
+    const resolve = (result) => {
+      //状态不可逆
+      if (this.state === PENDING) {
+        //调整resolve/reject
+        this.state = FULFILLED;
+        this.result = result;
+        //遍历取出
+        this.#handlers.forEach(({ onFulfilled }) => {
+          // onFulfilled(this.result)
+          onFulfilled();
+        });
+      }
+    };
+    const reject = (result) => {
+      if (this.state === PENDING) {
+        this.state = REJECTED;
+        this.result = result;
+        this.#handlers.forEach(({ onRejected }) => {
+          // onRejected(this.result)
+          onRejected();
+        });
+      }
+    };
+
+    func(resolve, reject);
+  }
+
+  // then方法
+  // 1.返回新Promise实例
+  // 2.获取任意返回值
+  // 2.1处理返回值
+  // 2.2处理异常
+  //1.添加实例方法
+  then(onFulfilled, onRejected) {
+    //2.参数判断(参考文档)
+    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (x) => x;
+    onRejected =
+      typeof onRejected === "function"
+        ? onRejected
+        : (x) => {
+            throw x;
+          };
+
+    const p2 = new HMPromise((resolve, reject) => {
+      //执行成功的回调
+      if (this.state === FULFILLED) {
+        runAsynctask(() => {
+          try {
+            //获取返回值
+            const x = onFulfilled(this.result);
+            // console.log("x",x);
+            //处理返回值
+            resolve(x);
+          } catch (error) {
+            // console.log("捕获异常:"+error);
+            reject(error);
+          }
+        });
+      } else if (this.state === REJECTED) {
+        runAsynctask(() => {
+          onRejected(this.result);
+        });
+      } else if (this.state === PENDING) {
+        //是pending状态的时候还不需要去执行函数,可以先保存起来
+        this.#handlers.push({
+          onFulfilled: () => {
+            runAsynctask(() => {
+              onFulfilled(this.result);
+            });
+          },
+          onRejected: () => {
+            runAsynctask(() => {
+              onRejected(this.result);
+            });
+          },
+        });
+      }
+    });
+    return p2;
+  }
+}
+
+/* 测试代码 */
+const p = new HMPromise((resolve, reject) => {
+  resolve(1);
+});
+p.then((res) => {
+  console.log("p1", res);
+  throw "throw-err";
+  return 2;
+}).then(
+  (res) => {
+    console.log("p2", res);
+  },
+  (err) => {
+    console.log("p2", err);
+  }
+);
+

8-链式编程-处理返回 Promise

/**
+ * 链式编程-处理返回值Promise
+ * 1.处理返回值Promise
+ * 2.调用then方法
+ */
+
+// 函数封装
+function runAsynctask(callback) {
+  if (typeof queueMicrotask === "function") {
+    queueMicrotask(callback);
+  } else if (typeof MutationObserver === "function") {
+    const obs = new MutationObserver(callback);
+    const divNode = document.createElement("div");
+    obs.observe(divNode, { childList: true });
+    divNode.innerHTML = "hhh";
+  } else {
+    setTimeout(callback, 0);
+  }
+}
+
+const PENDING = "pending";
+const FULFILLED = "fulfilled";
+const REJECTED = "rejected";
+
+class HMPromise {
+  // 添加状态(pending/fulfilled/rejected)
+  // 添加原因
+  state = PENDING;
+  result = undefined;
+  // 定义实例属性,用来保存我们的回调函数
+  //# 私有的,外部访问不到
+  #handlers = []; //[{onFulfilled,onRejected}...]
+
+  constructor(func) {
+    const resolve = (result) => {
+      //状态不可逆
+      if (this.state === PENDING) {
+        //调整resolve/reject
+        this.state = FULFILLED;
+        this.result = result;
+        //遍历取出
+        this.#handlers.forEach(({ onFulfilled }) => {
+          // onFulfilled(this.result)
+          onFulfilled();
+        });
+      }
+    };
+    const reject = (result) => {
+      if (this.state === PENDING) {
+        this.state = REJECTED;
+        this.result = result;
+        this.#handlers.forEach(({ onRejected }) => {
+          // onRejected(this.result)
+          onRejected();
+        });
+      }
+    };
+
+    func(resolve, reject);
+  }
+
+  // then方法
+  // 1.返回新Promise实例
+  // 2.获取任意返回值
+  // 2.1处理返回值
+  // 2.2处理异常
+  //1.添加实例方法
+  then(onFulfilled, onRejected) {
+    //2.参数判断(参考文档)
+    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (x) => x;
+    onRejected =
+      typeof onRejected === "function"
+        ? onRejected
+        : (x) => {
+            throw x;
+          };
+
+    const p2 = new HMPromise((resolve, reject) => {
+      //执行成功的回调
+      if (this.state === FULFILLED) {
+        runAsynctask(() => {
+          try {
+            //获取返回值
+            const x = onFulfilled(this.result);
+            // console.log("x",x);
+            // 1.处理返回值Promise
+            if (x instanceof HMPromise) {
+              //2.调用then方法
+              x.then(
+                (res) => resolve(res),
+                (err) => reject(err)
+              );
+            } else {
+              //处理返回值
+              resolve(x);
+            }
+          } catch (error) {
+            // console.log("捕获异常:"+error);
+            reject(error);
+          }
+        });
+      } else if (this.state === REJECTED) {
+        runAsynctask(() => {
+          onRejected(this.result);
+        });
+      } else if (this.state === PENDING) {
+        //是pending状态的时候还不需要去执行函数,可以先保存起来
+        this.#handlers.push({
+          onFulfilled: () => {
+            runAsynctask(() => {
+              onFulfilled(this.result);
+            });
+          },
+          onRejected: () => {
+            runAsynctask(() => {
+              onRejected(this.result);
+            });
+          },
+        });
+      }
+    });
+    return p2;
+  }
+}
+
+/* 测试代码 */
+const p = new HMPromise((resolve, reject) => {
+  resolve(1);
+});
+p.then((res) => {
+  return new HMPromise((resolve, reject) => {
+    // resolve(1)
+    reject("err");
+  });
+}).then(
+  (res) => {
+    console.log("p2", res);
+  },
+  (err) => {
+    console.log("p2", err);
+  }
+);
+

9-链式编程-处理重复引用

/**
+ * 链式编程-处理重复引用
+ */
+
+// 函数封装
+function runAsynctask(callback) {
+  if (typeof queueMicrotask === "function") {
+    queueMicrotask(callback);
+  } else if (typeof MutationObserver === "function") {
+    const obs = new MutationObserver(callback);
+    const divNode = document.createElement("div");
+    obs.observe(divNode, { childList: true });
+    divNode.innerHTML = "hhh";
+  } else {
+    setTimeout(callback, 0);
+  }
+}
+
+const PENDING = "pending";
+const FULFILLED = "fulfilled";
+const REJECTED = "rejected";
+
+class HMPromise {
+  // 添加状态(pending/fulfilled/rejected)
+  // 添加原因
+  state = PENDING;
+  result = undefined;
+  // 定义实例属性,用来保存我们的回调函数
+  //# 私有的,外部访问不到
+  #handlers = []; //[{onFulfilled,onRejected}...]
+
+  constructor(func) {
+    const resolve = (result) => {
+      //状态不可逆
+      if (this.state === PENDING) {
+        //调整resolve/reject
+        this.state = FULFILLED;
+        this.result = result;
+        //遍历取出
+        this.#handlers.forEach(({ onFulfilled }) => {
+          // onFulfilled(this.result)
+          onFulfilled();
+        });
+      }
+    };
+    const reject = (result) => {
+      if (this.state === PENDING) {
+        this.state = REJECTED;
+        this.result = result;
+        this.#handlers.forEach(({ onRejected }) => {
+          // onRejected(this.result)
+          onRejected();
+        });
+      }
+    };
+
+    func(resolve, reject);
+  }
+
+  // then方法
+  // 1.返回新Promise实例
+  // 2.获取任意返回值
+  // 2.1处理返回值
+  // 2.2处理异常
+  //1.添加实例方法
+  then(onFulfilled, onRejected) {
+    //2.参数判断(参考文档)
+    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (x) => x;
+    onRejected =
+      typeof onRejected === "function"
+        ? onRejected
+        : (x) => {
+            throw x;
+          };
+
+    const p2 = new HMPromise((resolve, reject) => {
+      //执行成功的回调
+      if (this.state === FULFILLED) {
+        runAsynctask(() => {
+          try {
+            //获取返回值
+            const x = onFulfilled(this.result);
+            //处理重复引用
+            if (x === p2) {
+              throw new TypeError(
+                "Chaining cycle detected for promise #<Promise>"
+              );
+            }
+            // console.log("x",x);
+            // 1.处理返回值Promise
+            if (x instanceof HMPromise) {
+              //2.调用then方法
+              x.then(
+                (res) => resolve(res),
+                (err) => reject(err)
+              );
+            } else {
+              //处理返回值
+              resolve(x);
+            }
+          } catch (error) {
+            // console.log("捕获异常:"+error);
+            reject(error);
+          }
+        });
+      } else if (this.state === REJECTED) {
+        runAsynctask(() => {
+          onRejected(this.result);
+        });
+      } else if (this.state === PENDING) {
+        //是pending状态的时候还不需要去执行函数,可以先保存起来
+        this.#handlers.push({
+          onFulfilled: () => {
+            runAsynctask(() => {
+              onFulfilled(this.result);
+            });
+          },
+          onRejected: () => {
+            runAsynctask(() => {
+              onRejected(this.result);
+            });
+          },
+        });
+      }
+    });
+    return p2;
+  }
+}
+
+/* 原生Promise测试 */
+//    const p=new Promise((resolve,reject)=>{
+//     resolve(1)
+//    })
+//    const p2=p.then(res=>{
+//     return p2
+//    })
+/* 报错信息:Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise> */
+
+/* 测试  手写Promise */
+const p = new HMPromise((resolve, reject) => {
+  resolve(1);
+});
+
+const p2 = p.then((res) => {
+  return p2;
+});
+
+p2.then(
+  (res) => {},
+  (err) => {
+    console.log("err", err);
+  }
+);
+

10-链式编程-rejected 状态

// 函数封装
+function runAsynctask(callback) {
+  if (typeof queueMicrotask === "function") {
+    queueMicrotask(callback);
+  } else if (typeof MutationObserver === "function") {
+    const obs = new MutationObserver(callback);
+    const divNode = document.createElement("div");
+    obs.observe(divNode, { childList: true });
+    divNode.innerHTML = "hhh";
+  } else {
+    setTimeout(callback, 0);
+  }
+}
+
+const PENDING = "pending";
+const FULFILLED = "fulfilled";
+const REJECTED = "rejected";
+
+class HMPromise {
+  // 添加状态(pending/fulfilled/rejected)
+  // 添加原因
+  state = PENDING;
+  result = undefined;
+  // 定义实例属性,用来保存我们的回调函数
+  //# 私有的,外部访问不到
+  #handlers = []; //[{onFulfilled,onRejected}...]
+
+  constructor(func) {
+    const resolve = (result) => {
+      //状态不可逆
+      if (this.state === PENDING) {
+        //调整resolve/reject
+        this.state = FULFILLED;
+        this.result = result;
+        //遍历取出
+        this.#handlers.forEach(({ onFulfilled }) => {
+          // onFulfilled(this.result)
+          onFulfilled();
+        });
+      }
+    };
+    const reject = (result) => {
+      if (this.state === PENDING) {
+        this.state = REJECTED;
+        this.result = result;
+        this.#handlers.forEach(({ onRejected }) => {
+          // onRejected(this.result)
+          onRejected();
+        });
+      }
+    };
+
+    func(resolve, reject);
+  }
+
+  // then方法
+  //1.处理异常
+  // 2.获取返回值
+  // 3.抽取函数
+  // 4.调用函数
+  then(onFulfilled, onRejected) {
+    //2.参数判断(参考文档)
+    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (x) => x;
+    onRejected =
+      typeof onRejected === "function"
+        ? onRejected
+        : (x) => {
+            throw x;
+          };
+
+    const p2 = new HMPromise((resolve, reject) => {
+      //执行成功的回调
+      if (this.state === FULFILLED) {
+        runAsynctask(() => {
+          try {
+            //获取返回值
+            const x = onFulfilled(this.result);
+            //处理重复引用
+            if (x === p2) {
+              throw new TypeError(
+                "Chaining cycle detected for promise #<Promise>"
+              );
+            }
+            // console.log("x",x);
+            // 1.处理返回值Promise
+            if (x instanceof HMPromise) {
+              //2.调用then方法
+              x.then(
+                (res) => resolve(res),
+                (err) => reject(err)
+              );
+            } else {
+              //处理返回值
+              resolve(x);
+            }
+          } catch (error) {
+            // console.log("捕获异常:"+error);
+            reject(error);
+          }
+        });
+      } else if (this.state === REJECTED) {
+        runAsynctask(() => {
+          //1.处理异常
+          try {
+            //2.获取返回值
+            const x = onRejected(this.result);
+            //3.下面有一步进行函数抽取的
+            //4 调用函数
+            resolvePromise(p2, x, resolve, reject);
+          } catch (err) {
+            reject(err);
+          }
+        });
+      } else if (this.state === PENDING) {
+        //是pending状态的时候还不需要去执行函数,可以先保存起来
+        this.#handlers.push({
+          onFulfilled: () => {
+            runAsynctask(() => {
+              // 1.处理异常
+              try {
+                //2.获取返回值
+                const x = onFulfilled(this.result);
+                //3.调用函数
+                resolvePromise(p2, x, resolve, reject);
+              } catch (error) {
+                reject(error);
+              }
+            });
+          },
+          onRejected: () => {
+            runAsynctask(() => {
+              // 1.处理异常
+              try {
+                //2.获取返回值
+                constx = onRejected(this.result);
+                //3.调用函数
+                resolvePromise(p2, x, resolve, reject);
+              } catch (error) {
+                reject(error);
+              }
+            });
+          },
+        });
+      }
+    });
+    return p2;
+  }
+}
+
+//3.抽取函数
+function resolvePromise(p2, x, resolve, reject) {
+  if (x === p2) {
+    throw new TypeError("Chaining cycle detected for promise #<Promise>");
+  }
+  // console.log("x",x);
+  // 1.处理返回值Promise
+  if (x instanceof HMPromise) {
+    //2.调用then方法
+    x.then(
+      (res) => resolve(res),
+      (err) => reject(err)
+    );
+  } else {
+    //处理返回值
+    resolve(x);
+  }
+}
+
+/* 原生Promise测试 */
+//    const p=new Promise((resolve,reject)=>{
+//     resolve(1)
+//    })
+//    const p2=p.then(res=>{
+//     return p2
+//    })
+/* 报错信息:Uncaught (in promise) TypeError: Chaining cycle detected for promise #<Promise> */
+
+/* 测试  手写Promise */
+const p = new HMPromise((resolve, reject) => {
+  setTimeout(() => {
+    resolve(1);
+  }, 2000);
+});
+
+const p2 = p.then((res) => {
+  throw "error";
+  //  return p2
+  // return 2
+  return new HMPromise((resolve, reject) => {
+    resolve("HMPromise-2");
+  });
+});
+
+p2.then(
+  (res) => {
+    console.log("res:", res);
+  },
+  (err) => {
+    console.log("err", err);
+  }
+);
+

11-实例方法-catch-finally

// 函数封装
+function runAsynctask(callback) {
+  if (typeof queueMicrotask === "function") {
+    queueMicrotask(callback);
+  } else if (typeof MutationObserver === "function") {
+    const obs = new MutationObserver(callback);
+    const divNode = document.createElement("div");
+    obs.observe(divNode, { childList: true });
+    divNode.innerHTML = "hhh";
+  } else {
+    setTimeout(callback, 0);
+  }
+}
+
+const PENDING = "pending";
+const FULFILLED = "fulfilled";
+const REJECTED = "rejected";
+
+class HMPromise {
+  // 添加状态(pending/fulfilled/rejected)
+  // 添加原因
+  state = PENDING;
+  result = undefined;
+  // 定义实例属性,用来保存我们的回调函数
+  //# 私有的,外部访问不到
+  #handlers = []; //[{onFulfilled,onRejected}...]
+
+  constructor(func) {
+    const resolve = (result) => {
+      //状态不可逆
+      if (this.state === PENDING) {
+        //调整resolve/reject
+        this.state = FULFILLED;
+        this.result = result;
+        //遍历取出
+        this.#handlers.forEach(({ onFulfilled }) => {
+          // onFulfilled(this.result)
+          onFulfilled();
+        });
+      }
+    };
+    const reject = (result) => {
+      if (this.state === PENDING) {
+        this.state = REJECTED;
+        this.result = result;
+        this.#handlers.forEach(({ onRejected }) => {
+          // onRejected(this.result)
+          onRejected();
+        });
+      }
+    };
+
+    //   处理异常
+    try {
+      func(resolve, reject);
+    } catch (error) {
+      reject(error);
+    }
+  }
+
+  // then方法
+  //1.处理异常
+  // 2.获取返回值
+  // 3.抽取函数
+  // 4.调用函数
+  then(onFulfilled, onRejected) {
+    //2.参数判断(参考文档)
+    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (x) => x;
+    onRejected =
+      typeof onRejected === "function"
+        ? onRejected
+        : (x) => {
+            throw x;
+          };
+
+    const p2 = new HMPromise((resolve, reject) => {
+      //执行成功的回调
+      if (this.state === FULFILLED) {
+        runAsynctask(() => {
+          try {
+            //获取返回值
+            const x = onFulfilled(this.result);
+            //处理重复引用
+            if (x === p2) {
+              throw new TypeError(
+                "Chaining cycle detected for promise #<Promise>"
+              );
+            }
+            // console.log("x",x);
+            // 1.处理返回值Promise
+            if (x instanceof HMPromise) {
+              //2.调用then方法
+              x.then(
+                (res) => resolve(res),
+                (err) => reject(err)
+              );
+            } else {
+              //处理返回值
+              resolve(x);
+            }
+          } catch (error) {
+            // console.log("捕获异常:"+error);
+            reject(error);
+          }
+        });
+      } else if (this.state === REJECTED) {
+        runAsynctask(() => {
+          //1.处理异常
+          try {
+            //2.获取返回值
+            const x = onRejected(this.result);
+            //3.下面有一步进行函数抽取的
+            //4 调用函数
+            resolvePromise(p2, x, resolve, reject);
+          } catch (err) {
+            reject(err);
+          }
+        });
+      } else if (this.state === PENDING) {
+        //是pending状态的时候还不需要去执行函数,可以先保存起来
+        this.#handlers.push({
+          onFulfilled: () => {
+            runAsynctask(() => {
+              // 1.处理异常
+              try {
+                //2.获取返回值
+                const x = onFulfilled(this.result);
+                //3.调用函数
+                resolvePromise(p2, x, resolve, reject);
+              } catch (error) {
+                reject(error);
+              }
+            });
+          },
+          onRejected: () => {
+            runAsynctask(() => {
+              // 1.处理异常
+              try {
+                //2.获取返回值
+                constx = onRejected(this.result);
+                //3.调用函数
+                resolvePromise(p2, x, resolve, reject);
+              } catch (error) {
+                reject(error);
+              }
+            });
+          },
+        });
+      }
+    });
+    return p2;
+  }
+
+  /**
+   * catch方法
+   * 1.内部调用then方法
+   * 2.处理异常
+   */
+  catch(onRejected) {
+    //1.内部调用then方法(MDN文档中说的如是)
+    return this.then(undefined, onRejected);
+  }
+
+  /**
+   * finally方法
+   * 1.内部调用then方法
+   */
+  finally(onFinally) {
+    return this.then(onFinally, onFinally);
+  }
+}
+
+//3.抽取函数
+function resolvePromise(p2, x, resolve, reject) {
+  if (x === p2) {
+    throw new TypeError("Chaining cycle detected for promise #<Promise>");
+  }
+  // console.log("x",x);
+  // 1.处理返回值Promise
+  if (x instanceof HMPromise) {
+    //2.调用then方法
+    x.then(
+      (res) => resolve(res),
+      (err) => reject(err)
+    );
+  } else {
+    //处理返回值
+    resolve(x);
+  }
+}
+
+/* 测试  手写Promise */
+const p = new HMPromise((resolve, reject) => {
+  // resolve("LLLL")
+  // reject("reject-err")
+  //需要处理实例化的异常
+  throw "throw err";
+});
+
+p.then((res) => {
+  console.log("res", res);
+})
+  .catch((err) => {
+    console.log("err", err);
+  })
+  .finally(() => {
+    console.log("finally");
+  });
+

12-静态方法

// 函数封装
+function runAsynctask(callback) {
+  if (typeof queueMicrotask === "function") {
+    queueMicrotask(callback);
+  } else if (typeof MutationObserver === "function") {
+    const obs = new MutationObserver(callback);
+    const divNode = document.createElement("div");
+    obs.observe(divNode, { childList: true });
+    divNode.innerHTML = "hhh";
+  } else {
+    setTimeout(callback, 0);
+  }
+}
+
+const PENDING = "pending";
+const FULFILLED = "fulfilled";
+const REJECTED = "rejected";
+
+class HMPromise {
+  // 添加状态(pending/fulfilled/rejected)
+  // 添加原因
+  state = PENDING;
+  result = undefined;
+  // 定义实例属性,用来保存我们的回调函数
+  //# 私有的,外部访问不到
+  #handlers = []; //[{onFulfilled,onRejected}...]
+
+  constructor(func) {
+    const resolve = (result) => {
+      //状态不可逆
+      if (this.state === PENDING) {
+        //调整resolve/reject
+        this.state = FULFILLED;
+        this.result = result;
+        //遍历取出
+        this.#handlers.forEach(({ onFulfilled }) => {
+          // onFulfilled(this.result)
+          onFulfilled();
+        });
+      }
+    };
+    const reject = (result) => {
+      if (this.state === PENDING) {
+        this.state = REJECTED;
+        this.result = result;
+        this.#handlers.forEach(({ onRejected }) => {
+          // onRejected(this.result)
+          onRejected();
+        });
+      }
+    };
+
+    //   处理异常
+    try {
+      func(resolve, reject);
+    } catch (error) {
+      reject(error);
+    }
+  }
+
+  // then方法
+  //1.处理异常
+  // 2.获取返回值
+  // 3.抽取函数
+  // 4.调用函数
+  then(onFulfilled, onRejected) {
+    //2.参数判断(参考文档)
+    onFulfilled = typeof onFulfilled === "function" ? onFulfilled : (x) => x;
+    onRejected =
+      typeof onRejected === "function"
+        ? onRejected
+        : (x) => {
+            throw x;
+          };
+
+    const p2 = new HMPromise((resolve, reject) => {
+      //执行成功的回调
+      if (this.state === FULFILLED) {
+        runAsynctask(() => {
+          try {
+            //获取返回值
+            const x = onFulfilled(this.result);
+            //处理重复引用
+            if (x === p2) {
+              throw new TypeError(
+                "Chaining cycle detected for promise #<Promise>"
+              );
+            }
+            // console.log("x",x);
+            // 1.处理返回值Promise
+            if (x instanceof HMPromise) {
+              //2.调用then方法
+              x.then(
+                (res) => resolve(res),
+                (err) => reject(err)
+              );
+            } else {
+              //处理返回值
+              resolve(x);
+            }
+          } catch (error) {
+            // console.log("捕获异常:"+error);
+            reject(error);
+          }
+        });
+      } else if (this.state === REJECTED) {
+        runAsynctask(() => {
+          //1.处理异常
+          try {
+            //2.获取返回值
+            const x = onRejected(this.result);
+            //3.下面有一步进行函数抽取的
+            //4 调用函数
+            resolvePromise(p2, x, resolve, reject);
+          } catch (err) {
+            reject(err);
+          }
+        });
+      } else if (this.state === PENDING) {
+        //是pending状态的时候还不需要去执行函数,可以先保存起来
+        this.#handlers.push({
+          onFulfilled: () => {
+            runAsynctask(() => {
+              // 1.处理异常
+              try {
+                //2.获取返回值
+                const x = onFulfilled(this.result);
+                //3.调用函数
+                resolvePromise(p2, x, resolve, reject);
+              } catch (error) {
+                reject(error);
+              }
+            });
+          },
+          onRejected: () => {
+            runAsynctask(() => {
+              // 1.处理异常
+              try {
+                //2.获取返回值
+                constx = onRejected(this.result);
+                //3.调用函数
+                resolvePromise(p2, x, resolve, reject);
+              } catch (error) {
+                reject(error);
+              }
+            });
+          },
+        });
+      }
+    });
+    return p2;
+  }
+
+  /**
+   * catch方法
+   * 1.内部调用then方法
+   * 2.处理异常
+   */
+  catch(onRejected) {
+    //1.内部调用then方法(MDN文档中说的如是)
+    return this.then(undefined, onRejected);
+  }
+
+  /**
+   * finally方法
+   * 1.内部调用then方法
+   */
+  finally(onFinally) {
+    return this.then(onFinally, onFinally);
+  }
+
+  /**
+   * 静态方法-resolve
+   * 1.判断传入值
+   * 2.1.Promise直接返回
+   * 2.2.转为Promise并返回(fulfilled状态)
+   */
+  static resolve(value) {
+    //1.判断传入值
+    if (value instanceof HMPromise) {
+      // 2.1.Promise直接返回
+      return value;
+    }
+    // 2.2.转为Promise并返回(fulfilled状态)
+    return new HMPromise((resolve) => {
+      resolve(value);
+    });
+  }
+
+  /**
+   *  静态方法-reject
+   * 1.返回rejected状态的Promise
+   *  */
+  static reject(value) {
+    // 1.返回rejected状态的Promise
+    return new HMPromise((undefined, reject) => {
+      reject(value);
+    });
+  }
+
+  /**
+   * 静态方法啊-race
+   * 1、返回Promise
+   * 2、判断是否为数组 错误信息(Argument is not iterable)
+   * 3、等待一个敲定
+   */
+  static race(promises) {
+    //1、返回一个Promise
+    return new HMPromise((resolve, reject) => {
+      //2、判断是否为数组
+      if (!Array.isArray(promises)) {
+        return reject(new TypeError("Argument is not iterable"));
+      }
+      // 3、等待一个敲定
+      promises.forEach((p) => {
+        HMPromise.resolve(p).then(
+          (res) => resolve(res),
+          (err) => {
+            reject(err);
+          }
+        );
+      });
+    });
+  }
+
+  /**
+   * 静态方法-all
+   * 1.返回Promise实例
+   * 2.判断是否为数组 错误信息 :Argument is not iterable
+   * 3.空数组直接兑现
+   * 4.处理全部兑现
+   *    4.1记录结果
+   *    4.2.判断全部兑现
+   * 5.处理第一个拒绝
+   */
+  static all(promises) {
+    //1.返回Promise实例
+    return new Promise((resolve, reject) => {
+      // 2.判断是否为数组
+      if (!Array.isArray(promises)) {
+        return reject(new TypeError("Argument is not iterable"));
+      }
+      // 3.空数组直接兑现
+      promises.length === 0 && resolve(promises);
+      // 4.1记录结果
+      const result = [];
+      let count = 0;
+      promises.forEach((p, index) => {
+        HMPromise.resolve(p).then(
+          (res) => {
+            result[index] = res; //用索引来填充数组,不要去用push
+            //4.2.判断全部兑现,用次数来判断(保证能获取到所有的结果!!!),不要用记录结果数组的长度判断
+            count++;
+            count === promises.length && resolve(result);
+          },
+          (err) => {
+            // 5.处理第一个拒绝
+            reject(err);
+          }
+        );
+      });
+    });
+  }
+
+  /**
+   * 静态方法- allsettled
+   * 1.返回Promise
+   * 2.数组判断 错误信息:Argument is not iterable
+   * 3.为空数组直接敲定
+   * 4.等待全部敲定
+   * 4.1记录结果
+   * 4.2处理兑现{status:'fulfilled',value:''}
+   * 4.3处理拒绝{status:'rejected',reason:''}
+   */
+  static allSettled(promises) {
+    //1.返回Promise
+    return new HMPromise((resolve, reject) => {
+      //2.数组判断
+      if (!Array.isArray(promises)) {
+        return reject(new TypeError("Argument is not iterable"));
+      }
+      //3.数组为空直接敲定
+      promises.length === 0 && resolve(promises);
+      //4.等待全部敲定
+      //4.1记录结果
+      const result = [];
+      let count = 0;
+      promises.forEach((p, index) => {
+        HMPromise.resolve(p).then(
+          (res) => {
+            // 4.2处理兑现{status:'fulfilled',value:''}
+            result[index] = { status: FULFILLED, value: res };
+            count++;
+            count === promises.length && resolve(result);
+          },
+          (err) => {
+            // 4.3处理拒绝{status:'rejected',reason:''}
+            result[index] = { status: REJECTED, reason: err };
+            count++;
+            count === promises.length && resolve(result);
+          }
+        );
+      });
+    });
+  }
+
+  /**
+   * 静态方法-any
+   * 1.返回Promise,数组判断 错误信息:Argument is not iterable
+   * 2.空数组直接拒绝 aggregateError: All promise were rejected
+   * AggregateError([错误原因1...],All Promise were rejected)
+   * 3.等待结果
+   *  3.1.第一个兑现
+   *  3.2.全部拒绝
+   */
+  static any(promises) {
+    // 返回Promise,数组判断
+    return new HMPromise((resolve, reject) => {
+      if (!Array.isArray(promises)) {
+        return reject(new TypeError("Argument is not iterable"));
+      }
+      // 2.空数组直接拒绝 aggregateError: All promise were rejected
+      promises.length === 0 &&
+        reject(new AggregateError(promises, "All promise were rejected"));
+      //3.等待结果
+      const errors = [];
+      let count = 0;
+      promises.forEach((p, index) => {
+        HMPromise.resolve(p).then(
+          (res) => {
+            //3.1 第一个兑现
+            resolve(res);
+          },
+          (err) => {
+            //3.2 全部拒绝
+            errors[index] = err;
+            count++;
+            count === promises.length &&
+              reject(new AggregateError(errors, "All promise were rejected"));
+          }
+        );
+      });
+    });
+  }
+}
+
+//3.抽取函数
+function resolvePromise(p2, x, resolve, reject) {
+  if (x === p2) {
+    throw new TypeError("Chaining cycle detected for promise #<Promise>");
+  }
+  // console.log("x",x);
+  // 1.处理返回值Promise
+  if (x instanceof HMPromise) {
+    //2.调用then方法
+    x.then(
+      (res) => resolve(res),
+      (err) => reject(err)
+    );
+  } else {
+    //处理返回值
+    resolve(x);
+  }
+}
+
+/* 静态方法 resolve*/
+HMPromise.resolve(
+  new HMPromise((resolve, reject) => {
+    // resolve("成功")
+    // reject("失败")
+    // throw "error"
+  })
+).then(
+  (res) => {
+    console.log("res", res);
+  },
+  (err) => {
+    console.log("err", err);
+  }
+);
+
+HMPromise.resolve("hello").then((res) => {
+  // console.log("res",res);
+});
+
+/* 静态方法 reject */
+HMPromise.reject("error").catch((res) => {
+  // console.log("res",res);
+});
+
+/* 测试代码 race */
+//   const p1=new HMPromise((resolve,reject)=>{
+//     setTimeout(()=>{
+//         resolve(1)
+//     },2000)
+//   })
+//   const p2=new HMPromise((resolve,reject)=>{
+//     setTimeout(()=>{
+//         reject(2)
+//     },1000)
+//   })
+
+//   HMPromise.race([p1,p2,"itheima"]).then(res=>{
+//     console.log("res",res);
+//   },err=>{
+//     console.log("err",err);
+//   })
+
+/* 测试代码 - all */
+// const p1=HMPromise.resolve(1)
+// const p2=new HMPromise((resolve,reject)=>{
+//   setTimeout(()=>{
+//       resolve(2)
+//       // reject("error")
+//   },1000)
+// })
+// const p3=3;
+// HMPromise.all([p1,p2,p3]).then(res=>{
+//   console.log("res",res);
+// },err=>{
+//   console.log("err",err);
+// })
+
+/**测试代码 原生Promise
+ *   */
+// const p1 =HMPromise.resolve(1)
+// const p2=2;
+// const p3=new HMPromise((resolve,reject)=>{
+//   setTimeout(()=>{
+//     reject(3)
+//   },1000)
+// })
+
+/* 静态方法 测试 -- allsettled */
+// HMPromise.allSettled([p1,p2,p3]).then(res=>{
+//   console.log("res",res);
+// },err=>{
+//   console.log("err",err);
+// })
+
+/**
+ * 原生静态方法--Promise.allsettled
+ * 1.传入Promise都变成已敲定,即可获取兑现的结果
+ * 2.结果数组[{status: 'fulfilled', value: 1},
+ * {status: 'fulfilled', value: 2}
+ * {status: 'rejected', reason: 3}]
+ * 3.结果数组的顺序和传入的Promise数组顺序一致
+ * 4.空数组直接兑现
+ * 5.不传入数组,直接报错
+ */
+// Promise.allSettled([p1,p2,p3]).then(res=>{
+//   console.log("res",res);
+// },err=>{
+//   console.log("err",err);
+// })
+
+/** 测试代码  原生 Promise */
+const p1 = new HMPromise((resolve, reject) => {
+  setTimeout(() => {
+    reject(1);
+  }, 2000);
+});
+
+const p2 = 2;
+
+const p3 = new HMPromise((resolve, reject) => {
+  setTimeout(() => {
+    resolve(3);
+    // reject(3)
+  }, 1000);
+});
+
+/**
+ * 测试静态方法-any
+ * 1.参数:Promise数组
+ * 2.结果:
+ * 2.1获得第一个成功的原因!
+ * 2.2获得所有的拒绝原因 aggregateError: All promise were rejected
+ * 2.3 传入空数组,直接拒绝 aggregateError: All promise were rejected
+ * 2.4 不传入数组,直接报错
+ */
+// Promise.any([p1,p2,p3]).then(res=>{
+//   console.log("res",res);
+// },err=>{
+//   console.log("err",err);
+// })
+
+/* 测试手写 Any */
+HMPromise.any([]).then(
+  (res) => {
+    console.log("res", res);
+  },
+  (err) => {
+    console.dir(err);
+  }
+);
+

设计模式

单例模式

<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>单例模式</title>
+</head>
+<body>
+    <h1>单例模式</h1>
+    <script>
+        class SingleTon{
+            static #instance
+            static getInstance(){
+                if(this.#instance!==undefined){
+                    this.#instance=new SingleTon()
+                }
+                return this.#instance
+            }
+        }
+        let s1=SingleTon.getInstance()
+        let s2=SingleTon.getInstance()
+        console.log(s1===s2);
+    </script>
+</body>
+</html>
+

观察者模式

// 被观察者
+class Subject {
+  constructor() {
+    this.observerList = [];
+  }
+
+  addObserver(observer) {
+    this.observerList.push(observer);
+  }
+
+  removeObserver(observer) {
+    const index = this.observerList.findIndex((o) => o.name === observer.name);
+    this.observerList.splice(index, 1);
+  }
+
+  notifyObservers(message) {
+    const observers = this.observerList;
+    observers.forEach((observer) => observer.notified(message));
+  }
+}
+// 观察者
+class Observer {
+  constructor(name, subject) {
+    this.name = name;
+    // 观察者主动申请加入被观察者的列表
+    if (subject) {
+      subject.addObserver(this);
+    }
+  }
+
+  notified(message) {
+    console.log(this.name, "got message", message);
+  }
+}
+
+//   使用
+const subject = new Subject();
+const observerA = new Observer("observerA", subject);
+const observerB = new Observer("observerB");
+subject.addObserver(observerB); //被观察者主动将观察者加入列表
+subject.notifyObservers("Hello from subject");
+subject.removeObserver(observerA);
+subject.notifyObservers("Hello again");
+

发布订阅

class PubSub {
+  constructor() {
+    this.messages = {};
+    this.listeners = {};
+  }
+
+  publish(type, content) {
+    const existContent = this.messages[type];
+    if (!existContent) {
+      this.messages[type] = [];
+    }
+    this.messages[type].push(content);
+  }
+
+  subscribe(type, cb) {
+    const existListener = this.listeners[type];
+    if (!existListener) {
+      this.listeners[type] = [];
+    }
+    this.listeners[type].push(cb);
+  }
+
+  notify(type) {
+    const messages = this.messages[type];
+    const subscribers = this.listeners[type] || [];
+    subscribers.forEach((cb) => cb(messages));
+  }
+}
+
+class Publisher {
+  constructor(name, context) {
+    this.name = name;
+    this.context = context;
+  }
+
+  publish(type, content) {
+    this.context.publish(type, content);
+  }
+}
+
+class Subscriber {
+  constructor(name, context) {
+    this.name = name;
+    this.context = context;
+  }
+
+  subscribe(type, cb) {
+    this.context.subscribe(type, cb);
+  }
+}
+
+function main() {
+  const TYPE_A = "music";
+  const TYPE_B = "movie";
+  const TYPE_C = "novel";
+
+  const pubsub = new PubSub();
+
+  const publisherA = new Publisher("publisherA", pubsub);
+  publisherA.publish(TYPE_A, "we are young");
+  publisherA.publish(TYPE_B, "the silicon valley");
+  const publisherB = new Publisher("publisherB", pubsub);
+  publisherB.publish(TYPE_A, "stronger");
+  const publisherC = new Publisher("publisherC", pubsub);
+  publisherC.publish(TYPE_B, "imitation game");
+
+  const subscriberA = new Subscriber("subscriberA", pubsub);
+  subscriberA.subscribe(TYPE_A, (res) => {
+    console.log("subscriberA received", res);
+  });
+  const subscriberB = new Subscriber("subscriberB", pubsub);
+  subscriberB.subscribe(TYPE_C, (res) => {
+    console.log("subscriberB received", res);
+  });
+  const subscriberC = new Subscriber("subscriberC", pubsub);
+  subscriberC.subscribe(TYPE_B, (res) => {
+    console.log("subscriberC received", res);
+  });
+
+  pubsub.notify(TYPE_A);
+  pubsub.notify(TYPE_B);
+  pubsub.notify(TYPE_C);
+}
+
+main();
+
+// subscriberA received [ 'we are young', 'stronger' ]
+// subscriberC received [ 'the silicon valley', 'imitation game' ]
+// subscriberB received undefined
+

发布订阅另一种

<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title>发布订阅模式</title>
+</head>
+<body>
+    <h1>发布订阅模式</h1>
+    <button id="on">注册事件</button>
+    <button id="off">注销事件event1</button>
+    <button id="emit">触发事件</button>
+    <button id="onOnce">注册一次性事件</button>
+    <button id="emitOnce">触发一次性事件</button>
+    <script>
+        class PubSub{
+            //存储事件
+            #handlers={
+                //结构:事件名:[callback1,callback2]
+            }
+            //注册事件
+            $on(event,func){
+                if(this.#handlers[event]==undefined){
+                    this.#handlers[event]=[]
+                }
+                this.#handlers[event].push(func)
+            }
+            //触发事件
+            $emit(event,...args){
+                let funcs=this.#handlers[event] || []
+                funcs.forEach((callback)=>{
+                    callback(...args)
+                })
+            }
+            //注销事件
+            $off(event){
+                this.#handlers[event]=undefined
+            }
+            //一次性触发事件
+            $once(event,callback){
+                this.$on(event,(...args)=>{
+                    callback(...args)
+                    this.$off(event)
+                })
+            }
+        }
+
+        const bus=new PubSub()
+        //进行测试
+        on.addEventListener("click",function(){
+            bus.$on("event1",()=>console.log("event1"))
+            bus.$on("event2",(a,b)=>console.log(a,b))
+            bus.$on("event2",(a,b)=>console.log("event2",a,b))
+        })
+        emit.addEventListener('click',function(){
+            bus.$emit("event1")
+            bus.$emit("event2",1,2)
+        })
+        off.addEventListener('click',function(){
+            bus.$off('event1')
+        })
+        onOnce.addEventListener('click',function(){
+            bus.$once("event3",()=>{console.log("读书很难吗?");})
+        })
+        emitOnce.addEventListener('click',function(){
+            bus.$emit("event3")
+        })
+    </script>
+</body>
+</html>
+
Last Updated:
Contributors: xiaoyu
+ + + diff --git "a/base/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.html" "b/base/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.html" new file mode 100644 index 0000000..661b071 --- /dev/null +++ "b/base/\346\255\243\345\210\231\350\241\250\350\276\276\345\274\217.html" @@ -0,0 +1,165 @@ + + + + + + + + + 正则表达式 | 🍰 小雨的学习记录 + + + + + +

正则表达式

正则表达式速查

匹配模式:
+    i:忽略大小写
+    g:执行全局匹配(查找所有匹配而非在找到第一个匹配后停止)。
+    m:执行多行匹配。
+
+
+正则表达式模式
+方括号:(好像里面不能写括号)
+    [abc]:查找方括号之间的任何字符。
+    [0-9]:查找任何从 0 至 9 的数字。
+    (x|y):查找由 | 分隔的任何选项。
+    [a-z]:查找任何小写的字母。[A-z]:字母
+    [^ ]:除了。。。
+    [^0-9]:除了数字
+量词:(只对前面的一个内容起作用,内容可以用()括号圈起来为一个)
+    n+	匹配任何包含至少一个 n 的字符串。
+    n*	匹配任何包含零个或多个 n 的字符串。
+    n?	匹配任何包含零个或一个 n 的字符串。(要么没有,要么就一个)
+    n{X}	匹配包含 X 个 n 的序列的字符串。
+    n{X,Y}	匹配包含 X 至 Y 个 n 的序列的字符串。
+    n{X,}	匹配包含至少 X 个 n 的序列的字符串。
+    n$	匹配任何结尾为 n 的字符串。
+    ^n	匹配任何开头为 n 的字符串。
+    ?=n	匹配任何其后紧接指定字符串 n 的字符串。有点像以什么结尾的一样
+    ?!n	匹配任何其后没有紧接指定字符串 n 的字符串。
+
+
+元字符:
+    .	查找单个字符,除了换行和行结束符。
+    \w	查找单词字符。
+    \W	查找非单词字符。
+    \d	查找数字。
+    \D	查找非数字字符。
+    \s	查找空白字符。
+    \S	查找非空白字符。
+    \b	匹配单词边界。
+    \B	匹配非单词边界。
+    \0	查找 NUL 字符。
+    \n	查找换行符。
+    \f	查找换页符。
+    \r	查找回车符。
+    \t	查找制表符。
+    \v	查找垂直制表符。
+    \xxx	查找以八进制数 xxx 规定的字符。
+    \xdd	查找以十六进制数 dd 规定的字符。
+    \uxxxx	查找以十六进制数 xxxx 规定的 Unicode 字符。
+注意点:
+    \w 匹配包括下划线的任何单词字符,等同于[A-Za-z0-9_]
+    \W 匹配任何非单词字符,等同于[^A-Za-z0-9_]
+
+    \s 匹配空格、换行、tab缩进等所有的空白
+    \S 匹配非空白,跟\s刚好相反。
+
+    "gssghs"    \w  true
+    "123433"    \w  true
+    "_"         \w  true
+    "     "     \s  true    
+
+
+
+RegExp 对象方法:
+    test():它通过模式来搜索字符串,然后根据结果返回 true 或 false。
+    exec():	检索字符串中指定的值。返回找到的值,并确定其位置。
+        -返回一个数组,其中存放匹配的结果。如果未找到匹配,则返回值为 null。
+        -  此数组的第 0 个元素是与正则表达式相匹配的文本,后续捕获组。
+
+
+支持正则表达式的 String 对象的方法:
+split()
+    -可以将一个字符串拆分为一个数组
+    -方法中可以传递一个正则表达式作为一个参数,这样方法会根据正则表达式去拆分字符串
+search()
+    -可以搜索字符串是否含有指定内容
+    -如果搜索到指定内容,则会返回第一次出现的索引,如果没有返回-1
+    -也可以接受一个正则表达式,根据去查找
+    -search()只会查找第一个,即使设置全局匹配也是这样
+match()
+    -根据正则表达式,从一个字符串中将符合条件的内容提取出来
+    -默认情况下,我们match只会找到第一个符合条件的内容,找到以后停止搜索,
+        我们可以设置正则表达式全局匹配,这样就能匹配到所有符合要求的
+        可以设置多个匹配模式,模式顺序任意
+    -match()匹配到的内容会封装到一个数组中返回
+replace()
+    -可以将字符串指定的内容替换为新的内容
+    -参数:1、被替换的内容,可以接受一个正则表达式作为一个参数。2、新的内容
+    -默认只会替换一个
+
+
+汉字:[\u4e00-\u9fa5]
+

推荐文章

位置匹配 理解正则中的(?=p)、(?!p)、(?<=p)、(?<!p)open in new window

正则应用

信息脱敏

const phone="13212345678"
+const phoneReg=/^(\d{3})(?:\d{5})(\d{3})$/
+// 这里用了捕获组和非捕获组
+
+const str=phone.replace(phoneReg,"$1*****$2")
+console.log(str)// 132*****678
+

Markdown图片格式转H5 img标签

// 正则 + repalce方法
+let str="![图片](./images/md.jpg)"
+let imgReg=/^!\[(.*)\]\((.*)\)$/
+
+// 除了字符串,还能函数!!!
+const result=str.replace(imgReg,(match,alt,src)=>{
+    console.log(match);//'![图片](./images/md.jpg)' 
+    return `<img src="${src}" alt="${alt}">`
+})
+console.log(result);//<img src="./images/md.jpg" alt="图片"> 
+

正则引用——子表达式

// 引用: 后面可以用\1引用编号为1的子表达式,依次类推,比如:
+var pattern = /(A|B)(\d{5})not([o-9])\1\2/;//pattern在最后引用了第一个和第二个子表达式。
+// 注意:这里的引用是对与子表达式匹配的字符串的引用,而不是简单的对子表达式的引用。例如:
+var pattern = /([0-9])AA\1/;
+// pattern不等价于正则表达式([0-9])AA[0-9],
+// 而是指字符串AA后面的数字必须和前面的相同,即形如1AA1这样的字符串!
+
+//特殊正则匹配
+let str="A12345not7A12345"
+var pattern = /(A|B)(\d{5})not([o-9])\1\2/;
+console.log(pattern.exec(str));
+//['A12345not7A12345' , 'A' , '12345' , ' 7 ' , index: 0,input: 'A12345not7A12345' ,groups: undefined ]
+

贪婪匹配和非贪婪匹配

const sanitizedwithoutScript = text.replace(/<script[^>]*>.*?<\/script>/gi,"");
+

非贪婪匹配(non-greedy matching)是正则表达式中的一个概念,与贪婪匹配相对。

  • 在贪婪匹配中,正则引擎会尽可能多地匹配字符,直到达到最长的可能匹配。
  • 而非贪婪匹配则尽可能少地匹配字符,直到达到最短的可能匹配。

例如,考虑正则表达式a+b。

如果用于贪婪匹配,它会匹配尽可能多的a字符,直到遇到一个b字符。如果用于非贪婪匹配,它会匹配尽可能少的a字符,直到遇到一个b字符。

  • 在大多数现代正则表达式引擎中,可以通过在量词后面添加一个问号来实现非贪婪匹配,例如a+?、*?、+?等。
  • 在您提供的正则表达式中,.*?就是一个非贪婪的量词,它会尽可能少地匹配字符,直到遇到下一个<或</script>。

非贪婪匹配和贪婪匹配的主要区别在于它们在匹配字符串时的优先级和范围。

在贪婪模式下,匹配器会尽可能多地匹配符合要求的字符,直到不能再匹配为止。
例如,正则表达式a.b在匹配字符串"abbcab"时,会匹配整个字符串"abbcab",而不是期望的"ab"。

而非贪婪模式则相反,匹配器会尽可能少地匹配符合要求的字符,直到满足要求为止。
例如,正则表达式a.
?b在匹配相同字符串"abbcab"时,只会匹配到第一个"ab",而不是整个字符串。简而言之,贪婪模式尝试匹配尽可能多的字符,而非贪婪模式则尝试匹配尽可能少的字符。

八个常用的正则

手机号码
  • 前两位一般是 13 / 14 / 15 / 17 / 18
  • 号码总长为 11 位
let reg = /^1[34578]\d{9}$/g;
+
QQ 号码
  • 首先第一个数不为 0
  • 5-10 位的 QQ 号码
let reg = /^[1-9][0-9]{4,9}$/g;
+
十六进制颜色
  • 第一个符号 # 可有可无
  • 十六进制 0-9a-f
  • #49D1CC 和 #0AB
let reg = /#?([0-9a-fA-F]{6}|[0-9a-fA-F]{3})/g;
+
邮箱
  • 允许输入的邮箱名称包含所有大小写字母、所有数字、以及_-.三个符号
  • luoyu2003@outlook.com
let reg = /^([A-Za-z0-9_\-\.]+)@([A-Za-z0-9_\-\.]+)\.([A-Za-z]{2,6})$/g;
+
URL
  • 协议的几种类型,协议可有可无
  • 域名 顶级域名 和 根域名(特殊情况 com.cn)
  • path 路径
  • 最后可能以 / 结尾
let reg =
+  /^((https?|ftp|file):\/\/)?([\da-z\.\-]+)\.([a-z\.]{2,6})([\/\w\.\-]*)*\/?/g;
+
HTML 标签

?:出现在括号的开头表示不需要捕获该组!!!

let reg = /^<([a-z]+)([^>]+)*(?:(.*)<\/\1>|\s+\/>)$/gm;
+
IPV4 地址
  • 地址是由 4 组 0-255 的数字组成
  • 每一组 0-255 我们需要进行数字范围拆分(正则没有直接表示数字范围的代码)
  • 我们可以分为 0-199 | 200-249 | 250-255 三种范围
let reg =
+  /^(([01]?[0-9][0-9]?|2[0-4][0-9]|25[0-5])\.){3}([01]?[0-9][0-9]?|2[0-4][0-9]|25[0-5])$/g;
+
日期 YYYY-mm-dd
  • 注意格式
  • 月份、日两位,不足前面补零
  • 日有三种情况 0-9 | 10-29 | 30-31
let reg = /^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$/gm;
+
Last Updated:
Contributors: xiaoyu
+ + + diff --git a/computer/Git.html b/computer/Git.html new file mode 100644 index 0000000..10d9701 --- /dev/null +++ b/computer/Git.html @@ -0,0 +1,58 @@ + + + + + + + + + Git | 🍰 小雨的学习记录 + + + + + +

Git

常用 Git 命令清单open in new window

Git合作开发场景

使用 stash 的一个场景

问题场景:
+甲和乙同时修改master分支代码。
+甲修改了一部分,在本地,未提交
+乙修改了一部分代码,提交到了远程
+甲如何更新到乙修改的代码,同时本地修改保留?
+
+解决:
+1、执行git stash #暂存这些变更
+2、git pull origin #拉取远程代码
+3、git stash pop #重新应用储藏的变更
+4、再次提交自己的代码到远程
+    git commit -a -m "提交说明"
+    git push origin master
+

使用 stash 的另一个场景

问题场景:
+甲同学在自己的分支上开发进行一半了。
+但是代码还不想进行提交(切换分支要清空工作区)。
+现在要修改别的分支问题的时候。
+
+1、git stash:保存开发到一半的代码
+2、git commit -m '修改问题'
+3、git stash pop:将代码追加到最新的提交之后
+
Last Updated:
Contributors: xiaoyu
+ + + diff --git a/computer/Linux.html b/computer/Linux.html new file mode 100644 index 0000000..359ccd1 --- /dev/null +++ b/computer/Linux.html @@ -0,0 +1,37 @@ + + + + + + + + + Linux | 🍰 小雨的学习记录 + + + + + + + + + diff --git "a/computer/Web\345\272\224\347\224\250\345\256\211\345\205\250.html" "b/computer/Web\345\272\224\347\224\250\345\256\211\345\205\250.html" new file mode 100644 index 0000000..d4c52b9 --- /dev/null +++ "b/computer/Web\345\272\224\347\224\250\345\256\211\345\205\250.html" @@ -0,0 +1,37 @@ + + + + + + + + + Web应用安全 | 🍰 小雨的学习记录 + + + + + + + + + diff --git a/computer/index.html b/computer/index.html new file mode 100644 index 0000000..4b9ec91 --- /dev/null +++ b/computer/index.html @@ -0,0 +1,37 @@ + + + + + + + + + 介绍 | 🍰 小雨的学习记录 + + + + + +

介绍

大学里课上也学了这么多开发上面的课程,怎么说也得总结总结嘛!其实面试也考,考的还好,主要是数据结构与算法会考得难一些

我想了想大学里在喜欢听的课程:

高等数学、数据结构与算法、Java、JavaEE、Web应用技术、设计模式、计算机网络、信息安全、Linux

其中Java、JavaEE、Web应用技术、Linux、计算机网络都是提前学习过的,所以上课非常轻松。但是我好像好久没去搞Java、Linux这些,差不多忘了差不多了。Java的那些特性,数据结构方法的使用,Linux的开关防火墙、一些常用命令……不过我精通Vim!菜鸡的我^_^

Last Updated:
Contributors: xiaoyu
+ + + diff --git "a/computer/\346\223\215\344\275\234\347\263\273\347\273\237_\347\274\226\350\257\221\345\216\237\347\220\206.html" "b/computer/\346\223\215\344\275\234\347\263\273\347\273\237_\347\274\226\350\257\221\345\216\237\347\220\206.html" new file mode 100644 index 0000000..e2f5730 --- /dev/null +++ "b/computer/\346\223\215\344\275\234\347\263\273\347\273\237_\347\274\226\350\257\221\345\216\237\347\220\206.html" @@ -0,0 +1,37 @@ + + + + + + + + + 操作系统与编译原理 | 🍰 小雨的学习记录 + + + + + + + + + diff --git "a/computer/\346\225\260\346\215\256\345\272\223.html" "b/computer/\346\225\260\346\215\256\345\272\223.html" new file mode 100644 index 0000000..49f081d --- /dev/null +++ "b/computer/\346\225\260\346\215\256\345\272\223.html" @@ -0,0 +1,37 @@ + + + + + + + + + 数据库 | 🍰 小雨的学习记录 + + + + + + + + + diff --git "a/computer/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.html" "b/computer/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.html" new file mode 100644 index 0000000..f36791d --- /dev/null +++ "b/computer/\350\256\241\347\256\227\346\234\272\347\275\221\347\273\234.html" @@ -0,0 +1,37 @@ + + + + + + + + + 计算机网络 | 🍰 小雨的学习记录 + + + + + +

计算机网络

UDP 和 TCP

UDP

UDP,用户数据报协议,是网络五层模型传输层上面的协议。

  1. UDP是一个面向报文的协议。意思就是UDP只是对报文进行搬运,不会对报文进行才分和拼接操作。
  2. UDP不可靠,无连接,它接收到什么数据就返回什么数据,不会对数据进行备份,也不会关心对方能不能接收到数据。
  3. UDP高效:因为UDP没有TCP那么复杂,头部开销很小,相比TCP至少20个字节要少得多。
  4. UDP支持一对一、多对多、多对一的传输方式。
  5. 应用场景:在某些实时性要求较高的场景,如电话会议、视频直播等。

TCP

TCP传输控制协议,是一种面向连接的、可靠的、基于字节流的传输层通信协议

  1. 面向连接:TCP的连接过程一共有以下三步,三次握手、传输数据、四次挥手
  2. TCP的可靠性:通过将数据分成报文段、超时重传机制、首部和数据的校验和保证数据的可靠性。
  3. TCP只支持一对一的传输方式
  4. 因为TCP头部至少是会有20个字节的数据,还要在发送请求之前进行三次握手,所以它的传输效率是相比UDP来说是比较低的。

HTTP/HTTPS

菜鸟网站 HTTPopen in new window是应用层维持客户端和服务端进行网络通信的协议

  1. 客户端向服务器发送请求报文,服务端就需要向客户端回送响应报文,http规定了请求响应的报文格式。
  2. 请求报文格式:请求行的请求方法、请求URL、http版本;请求头部、空行和请求体
  3. 响应报文格式:状态行的协议版本、状态码和描述状态;响应头部、空行和响应体
  4. HTTP是基于传输层TCP协议的,需要TCP三次握手后建立可靠连接才能进行应用层的通信。
  5. HTTP 是一种无状态 (stateless) 协议, HTTP协议本身不会对发送过的请求和相应的通信状态进行持久化处理。这样做的目的是为了保持HTTP协议的简单性,从而能够快速处理大量的事务, 提高效率。
  6. HTTPS:HTTP 协议中没有加密机制,但可以通 过和 SSL(Secure Socket Layer, 安全套接层 )或 TLS(Transport Layer Security, 安全层传输协议)的组合使用,加密 HTTP 的通信内容。属于通信加密,即在整个通信线路中加密。
  7. HTTPS 采用共享密钥加密(对称)和公开密钥加密(非对称)两者并用的混合加密机制。

HTTP 与 HTTPS 区别

加密:

  • HTTP:数据传输过程中不加密,容易被截获和篡改。
  • HTTPS:使用SSL/TLS协议对传输的数据进行加密,保护数据传输过程中的安全性。

端口

  • HTTP:默认使用端口80。
  • HTTPS:默认使用端口443。

安全性:

  • HTTP:不提供数据加密,安全性较低。
  • HTTPS:提供数据加密和完整性校验,安全性较高。

证书:

  • HTTP:不需要证书。
  • HTTPS:需要SSL证书来启用加密,并验证服务器的身份。

性能:

  • HTTP:由于不加密数据,性能略高于HTTPS。
  • HTTPS:由于需要进行加密和解密,可能会有一定的性能开销。

搜索引擎优化(SEO):

  • HTTP:搜索引擎可能会对没有使用HTTPS的网站进行降权。
  • HTTPS:搜索引擎倾向于优先索引和展示使用HTTPS的网站。

浏览器显示

  • HTTP:在大多数现代浏览器中,HTTP网站通常显示为"不安全"。
  • HTTPS:浏览器会显示一个锁形图标,表示网站是安全的。

成本:

  • HTTP:通常免费。
  • HTTPS:需要购买SSL证书,可能会有一定的成本。

应用场景:

  • HTTP:适用于不需要传输敏感信息的网站,如新闻网站、博客等。
  • HTTPS:适用于需要传输敏感信息的网站,如网上银行、在线购物、电子邮件等。
Last Updated:
Contributors: xiaoyu
+ + + diff --git "a/computer/\350\256\276\350\256\241\346\250\241\345\274\217.html" "b/computer/\350\256\276\350\256\241\346\250\241\345\274\217.html" new file mode 100644 index 0000000..aef5ecd --- /dev/null +++ "b/computer/\350\256\276\350\256\241\346\250\241\345\274\217.html" @@ -0,0 +1,37 @@ + + + + + + + + + 设计模式 | 🍰 小雨的学习记录 + + + + + + + + + diff --git a/demo/grid-area.html b/demo/grid-area.html new file mode 100644 index 0000000..dbf83e0 --- /dev/null +++ b/demo/grid-area.html @@ -0,0 +1,94 @@ + + + + + + grid-area + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + \ No newline at end of file diff --git a/demo/grid-one.html b/demo/grid-one.html new file mode 100644 index 0000000..b508f2d --- /dev/null +++ b/demo/grid-one.html @@ -0,0 +1,67 @@ + + + + + + CSS Grid starting point + + + + +

Simple grid example

+
+
就能(´ڡ`ლ)好吃的.∑(っ°Д°;)っ卧槽,不见了
+
Two
+
Three
+
Four Lorem ipsum dolor sit amet consectetur adipisicing elit. At doloribus error animi eius labore rerum quo saepe nihil veritatis! Necessitatibus, similique facere? Voluptatem eos consequatur tempora non alias. Repudiandae, nihil!
+
Five
+
Six
+
Seven Lorem ipsum dolor sit amet consectetur adipisicing elit. Consequuntur blanditiis exercitationem eos veritatis officia, enim dolores nostrum minima iure, repellat eius, similique suscipit dolorem eaque? Porro dolores quidem consequuntur facilis!
+
+ + diff --git a/demo/grid-template-areas.html b/demo/grid-template-areas.html new file mode 100644 index 0000000..ed0b910 --- /dev/null +++ b/demo/grid-template-areas.html @@ -0,0 +1,97 @@ + + + + + + CSS Grid - line-based placement starting point + + + + +
+
This is my lovely blog
+
+

My article

+

+ Duis felis orci, pulvinar id metus ut, rutrum luctus orci. Cras + porttitor imperdiet nunc, at ultricies tellus laoreet sit amet. Sed + auctor cursus massa at porta. Integer ligula ipsum, tristique sit amet + orci vel, viverra egestas ligula. Curabitur vehicula tellus neque, ac + ornare ex malesuada et. In vitae convallis lacus. Aliquam erat + volutpat. Suspendisse ac imperdiet turpis. Aenean finibus sollicitudin + eros pharetra congue. Duis ornare egestas augue ut luctus. Proin + blandit quam nec lacus varius commodo et a urna. Ut id ornare felis, + eget fermentum sapien. +

+ +

+ Nam vulputate diam nec tempor bibendum. Donec luctus augue eget + malesuada ultrices. Phasellus turpis est, posuere sit amet dapibus ut, + facilisis sed est. Nam id risus quis ante semper consectetur eget + aliquam lorem. Vivamus tristique elit dolor, sed pretium metus + suscipit vel. Mauris ultricies lectus sed lobortis finibus. Vivamus eu + urna eget velit cursus viverra quis vestibulum sem. Aliquam tincidunt + eget purus in interdum. Cum sociis natoque penatibus et magnis dis + parturient montes, nascetur ridiculus mus. +

+
+ +
Contact me@mysite.com
+
+ + diff --git a/demo/grid-template.html b/demo/grid-template.html new file mode 100644 index 0000000..1ed2d65 --- /dev/null +++ b/demo/grid-template.html @@ -0,0 +1,69 @@ + + + + + + grid-template + + + +
+
小雨
+
好哇好哇
+
华为
+
+
+
1
+
2
+
3
+
4
+
5
+
6
+
+ + \ No newline at end of file diff --git a/imgs/favicon.ico b/imgs/favicon.ico new file mode 100644 index 0000000..e6fc5bb Binary files /dev/null and b/imgs/favicon.ico differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..97352f3 --- /dev/null +++ b/index.html @@ -0,0 +1,37 @@ + + + + + + + + + 首页 | 🍰 小雨的学习记录 + + + + + +

🍰 小雨的学习记录

在互联网的广阔天地,深知技术日新月异,不进则退,对前端开发的热爱,源于对生活持续学习、不断进步的态度

内容介绍 开始学习 →

💡 技术栈

JS/HTML/CSS Vue React Webpack vite Axios Pinia Redux TS/JSX Express Next.js less/sass Node Java SpringBoot git ……

🛠️ 学习路线

从基础到TodoList项目,基础框架项目,JS高级,再到前端所要了解的网络及安全知识、浏览器原理、技术相关底层原理、前端性能优化,最后企业实际项目

📦 项目/面试经验

项目难点 / 面试经验 / 实习经历 / 学习思考 / 相关建议

+ + + diff --git a/interview/CSRF.html b/interview/CSRF.html new file mode 100644 index 0000000..2dc9ce5 --- /dev/null +++ b/interview/CSRF.html @@ -0,0 +1,90 @@ + + + + + + + + + CSRF,如何防御CSRF攻击 | 🍰 小雨的学习记录 + + + + + +

CSRF,如何防御CSRF攻击

跨站请求伪造(CSRF)是一种冒充受信任用户,向服务器发送非预期请求的攻击方式。攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。 当恶意网站、电子邮件、博客、即时消息或程序诱骗经过身份验证的用户的 Web 浏览器在受信任的站点上执行不需要的操作时,就会发生跨站点请求伪造 (CSRF)open in new window 攻击。如果目标用户已通过站点身份验证,则未受保护的目标站点无法区分合法的授权请求和伪造的经过身份验证的请求。 由于浏览器请求会自动包含所有 cookie,包括会话 cookie,因此除非使用适当的授权,否则此攻击会起作用,这意味着目标站点的质询-响应机制不会验证请求者的身份和权限。实际上,CSRF 攻击使目标系统在受害者不知情的情况下通过受害者的浏览器执行攻击者指定的功能(通常直到提交未经授权的操作之后)。 但是,成功的 CSRF 攻击只能利用易受攻击的应用程序暴露的功能和用户的权限。根据用户的凭据,攻击者可以转移资金、更改密码、进行未经授权的购买、提升目标帐户的权限或执行允许用户执行的任何操作。

一个典型的CSRF攻击有着如下的流程

  • 受害者登录a.com,并保留了登录凭证(Cookie)。
  • 攻击者引诱受害者访问了b.com。
  • b.com 向 a.com 发送了一个请求:a.com/act=xx。浏览器会默认携带a.com的Cookie。
  • a.com接收到请求后,对请求进行验证,并确认是受害者的凭证,误以为是受害者自己发送的请求。
  • a.com以受害者的名义执行了act=xx。
  • 攻击完成,攻击者在受害者不知情的情况下,冒充受害者,让a.com执行了自己定义的操作。

几种常见的攻击类型

GET类型的CSRF GET类型的CSRF利用非常简单,只需要一个HTTP请求,一般会这样利用:

![](http://bank.example/withdraw?account=xiaoming&amount=10000&for=hacker)
+

在受害者访问含有这个img的页面后,浏览器会自动向http://bank.example/withdraw?account=xiaoming&amount=10000&for=hacker发出一次HTTP请求。bank.example就会收到包含受害者登录信息的一次跨域请求。 POST类型的CSRF 这种类型的CSRF利用起来通常使用的是一个自动提交的表单,如:

<form action="http://bank.example/withdraw" method=POST>
+    <input type="hidden" name="account" value="xiaoming" />
+    <input type="hidden" name="amount" value="10000" />
+    <input type="hidden" name="for" value="hacker" />
+</form>
+<script> document.forms[0].submit(); </script>
+

访问该页面后,表单会自动提交,相当于模拟用户完成了一次POST操作。 POST类型的攻击通常比GET要求更加严格一点,但仍并不复杂。任何个人网站、博客,被黑客上传页面的网站都有可能是发起攻击的来源,后端接口不能将安全寄托在仅允许POST上面。 链接类型的CSRF 链接类型的CSRF并不常见,比起其他两种用户打开页面就中招的情况,这种需要用户点击链接才会触发。这种类型通常是在论坛中发布的图片中嵌入恶意链接,或者以广告的形式诱导用户中招,攻击者通常会以比较夸张的词语诱骗用户点击,例如:

<a href="http://test.com/csrf/withdraw.php?amount=1000&for=hacker" taget="_blank">
+  重磅消息!!
+  <a/>
+

由于之前用户登录了信任的网站A,并且保存登录状态,只要用户主动访问上面的这个PHP页面,则表示攻击成功。

如何进行防御

CSRF通常从第三方网站发起,被攻击的网站无法防止攻击发生,只能通过增强自己网站针对CSRF的防护能力来提升安全性。 上文中讲了CSRF的两个特点:

  • CSRF(通常)发生在第三方域名。
  • CSRF攻击者不能获取到Cookie等信息,只是使用。

针对这两点,我们可以专门制定防护策略,如下:

  • 阻止不明外域的访问
    • 同源检测
    • Samesite Cookie
  • 提交时要求附加本域才能获取的信息
    • CSRF Token
    • 双重Cookie验证

同源检测

既然CSRF大多来自第三方网站,那么我们就直接禁止外域(或者不受信任的域名)对我们发起请求。 那么问题来了,我们如何判断请求是否来自外域呢? 在HTTP协议中,每一个异步请求都会携带两个Header,用于标记来源域名:

  • Origin Header
  • Referer Header

这两个Header在浏览器发起请求时,大多数情况会自动带上,并且不能由前端自定义内容。 服务器可以通过解析这两个Header中的域名,确定请求的来源域。

使用Origin Header确定来源域名

在部分与CSRF有关的请求中,请求的Header中会携带Origin字段。字段内包含请求的域名(不包含path及query)。 如果Origin存在,那么直接使用Origin中的字段确认来源域名就可以。 但是Origin在以下两种情况下并不存在:

  • IE11同源策略: IE 11 不会在跨站CORS请求上添加Origin标头,Referer头将仍然是唯一的标识。最根本原因是因为IE 11对同源的定义和其他浏览器有不同,有两个主要的区别,可以参考MDN Same-origin_policy#IE_Exceptionsopen in new window
  • 302重定向: 在302重定向之后Origin不包含在重定向的请求中,因为Origin可能会被认为是其他来源的敏感信息。对于302重定向的情况来说都是定向到新的服务器上的URL,因此浏览器不想将Origin泄漏到新的服务器上。

使用Referer Header确定来源域名

根据HTTP协议,在HTTP头中有一个字段叫Referer,记录了该HTTP请求的来源地址。 对于Ajax请求,图片和script等资源请求,Referer为发起请求的页面地址。对于页面跳转,Referer为打开页面历史记录的前一个页面地址。因此我们使用Referer中链接的Origin部分可以得知请求的来源域名。 这种方法并非万无一失,Referer的值是由浏览器提供的,虽然HTTP协议上有明确的要求,但是每个浏览器对于Referer的具体实现可能有差别,并不能保证浏览器自身没有安全漏洞。使用验证 Referer 值的方法,就是把安全性都依赖于第三方(即浏览器)来保障,从理论上来讲,这样并不是很安全。在部分情况下,攻击者可以隐藏,甚至修改自己请求的Referer。 2014年,W3C的Web应用安全工作组发布了Referrer Policy草案,对浏览器该如何发送Referer做了详细的规定。截止现在新版浏览器大部分已经支持了这份草案,我们终于可以灵活地控制自己网站的Referer策略了。新版的Referrer Policy规定了五种Referer策略:No Referrer、No Referrer When Downgrade、Origin Only、Origin When Cross-origin、和 Unsafe URL。之前就存在的三种策略:never、default和always,在新标准里换了个名称。他们的对应关系如下:

策略名称属性值(新)属性值(旧)
No Referrerno-Referrernever
No Referrer When Downgradeno-Referrer-when-downgradedefault
Origin Only(same or strict) originorigin
Origin When Cross Origin(strict) origin-when-crossorigin-
Unsafe URLunsafe-urlalways

根据上面的表格因此需要把Referrer Policy的策略设置成same-origin,对于同源的链接和引用,会发送Referer,referer值为Host不带Path;跨域访问则不携带Referer。例如:aaa.com引用bbb.com的资源,不会发送Referer。 设置Referrer Policy的方法有三种:

  1. 在CSP设置
  2. 页面头部增加meta标签
  3. a标签增加referrerpolicy属性

上面说的这些比较多,但我们可以知道一个问题:攻击者可以在自己的请求中隐藏Referer。如果攻击者将自己的请求这样填写:

![](https://awps-assets.meituan.net/mit-x/blog-images-bundle-2018b/ff0cdbee.example/withdraw?amount=10000&for=hacker)
+

那么这个请求发起的攻击将不携带Referer。 另外在以下情况下Referer没有或者不可信:

  1. IE6、7下使用window.location.href=url进行界面的跳转,会丢失Referer。
  2. IE6、7下使用window.open,也会缺失Referer。
  3. HTTPS页面跳转到HTTP页面,所有浏览器Referer都丢失。
  4. 点击Flash上到达另外一个网站的时候,Referer的情况就比较杂乱,不太可信。

无法确认来源域名情况

当Origin和Referer头文件不存在时该怎么办?如果Origin和Referer都不存在,建议直接进行阻止,特别是如果您没有使用随机CSRF Token(参考下方)作为第二次检查。

如何阻止外域请求

通过Header的验证,我们可以知道发起请求的来源域名,这些来源域名可能是网站本域,或者子域名,或者有授权的第三方域名,又或者来自不可信的未知域名。 我们已经知道了请求域名是否是来自不可信的域名,我们直接阻止掉这些的请求,就能防御CSRF攻击了吗? 且慢!当一个请求是页面请求(比如网站的主页),而来源是搜索引擎的链接(例如百度的搜索结果),也会被当成疑似CSRF攻击。所以在判断的时候需要过滤掉页面请求情况,通常Header符合以下情况:

Accept: text/html
+Method: GET
+

但相应的,页面请求就暴露在了CSRF的攻击范围之中。如果你的网站中,在页面的GET请求中对当前用户做了什么操作的话,防范就失效了。 例如,下面的页面请求:

GET https://example.com/addComment?comment=XXX&dest=orderId
+

注:这种严格来说并不一定存在CSRF攻击的风险,但仍然有很多网站经常把主文档GET请求挂上参数来实现产品功能,但是这样做对于自身来说是存在安全风险的。 另外,前面说过,CSRF大多数情况下来自第三方域名,但并不能排除本域发起。如果攻击者有权限在本域发布评论(含链接、图片等,统称UGC),那么它可以直接在本域发起攻击,这种情况下同源策略无法达到防护的作用。 综上所述:同源验证是一个相对简单的防范方法,能够防范绝大多数的CSRF攻击。但这并不是万无一失的,对于安全性要求较高,或者有较多用户输入内容的网站,我们就要对关键的接口做额外的防护措施。

CSRF Token

前面讲到CSRF的另一个特征是,攻击者无法直接窃取到用户的信息(Cookie,Header,网站内容等),仅仅是冒用Cookie中的信息。 而CSRF攻击之所以能够成功,是因为服务器误把攻击者发送的请求当成了用户自己的请求。那么我们可以要求所有的用户请求都携带一个CSRF攻击者无法获取到的Token。服务器通过校验请求是否携带正确的Token,来把正常的请求和攻击的请求区分开,也可以防范CSRF的攻击。

原理

CSRF Token的防护策略分为三个步骤: 1. 将CSRF Token输出到页面中 首先,用户打开页面的时候,服务器需要给这个用户生成一个Token,该Token通过加密算法对数据进行加密,一般Token都包括随机字符串和时间戳的组合,显然在提交时Token不能再放在Cookie中了,否则又会被攻击者冒用。因此,为了安全起见Token最好还是存在服务器的Session中,之后在每次页面加载时,使用JS遍历整个DOM树,对于DOM中所有的a和form标签后加入Token。这样可以解决大部分的请求,但是对于在页面加载之后动态生成的HTML代码,这种方法就没有作用,还需要程序员在编码时手动添加Token。 2. 页面提交的请求携带这个Token 对于GET请求,Token将附在请求地址之后,这样URL 就变成 http://url?csrftoken=tokenvalue。open in new window 而对于 POST 请求来说,要在 form 的最后加上:

<input type=”hidden” name=”csrftoken” value=”tokenvalue”/>
+

这样,就把Token以参数的形式加入请求了。 3. 服务器验证Token是否正确 当用户从客户端得到了Token,再次提交给服务器的时候,服务器需要判断Token的有效性,验证过程是先解密Token,对比加密字符串以及时间戳,如果加密字符串一致且时间未过期,那么这个Token就是有效的。 这种方法要比之前检查Referer或者Origin要安全一些,Token可以在产生并放于Session之中,然后在每次请求时把Token从Session中拿出,与请求中的Token进行比对,但这种方法的比较麻烦的在于如何把Token以参数的形式加入请求。 下面将以Java为例,介绍一些CSRF Token的服务端校验逻辑,代码如下:

HttpServletRequest req = (HttpServletRequest)request; 
+HttpSession s = req.getSession(); 
+ 
+// 从 session 中得到 csrftoken 属性
+String sToken = (String)s.getAttribute("csrftoken"); 
+if(sToken == null){ 
+   // 产生新的 token 放入 session 中
+   sToken = generateToken(); 
+   s.setAttribute("csrftoken",sToken); 
+   chain.doFilter(request, response); 
+} else{ 
+   // 从 HTTP 头中取得 csrftoken 
+   String xhrToken = req.getHeader(“csrftoken”); 
+   // 从请求参数中取得 csrftoken 
+   String pToken = req.getParameter(“csrftoken”); 
+   if(sToken != null && xhrToken != null && sToken.equals(xhrToken)){ 
+       chain.doFilter(request, response); 
+   }else if(sToken != null && pToken != null && sToken.equals(pToken)){ 
+       chain.doFilter(request, response); 
+   }else{ 
+       request.getRequestDispatcher(“error.jsp”).forward(request,response); 
+   } 
+}
+

代码源自IBM developerworks CSRFopen in new window 这个Token的值必须是随机生成的,这样它就不会被攻击者猜到,考虑利用Java应用程序的java.security.SecureRandom类来生成足够长的随机标记,替代生成算法包括使用256位BASE64编码哈希,选择这种生成算法的开发人员必须确保在散列数据中使用随机性和唯一性来生成随机标识。通常,开发人员只需为当前会话生成一次Token。在初始生成此Token之后,该值将存储在会话中,并用于每个后续请求,直到会话过期。当最终用户发出请求时,服务器端必须验证请求中Token的存在性和有效性,与会话中找到的Token相比较。如果在请求中找不到Token,或者提供的值与会话中的值不匹配,则应中止请求,应重置Token并将事件记录为正在进行的潜在CSRF攻击。

分布式校验

在大型网站中,使用Session存储CSRF Token会带来很大的压力。访问单台服务器session是同一个。但是现在的大型网站中,我们的服务器通常不止一台,可能是几十台甚至几百台之多,甚至多个机房都可能在不同的省份,用户发起的HTTP请求通常要经过像Ngnix之类的负载均衡器之后,再路由到具体的服务器上,由于Session默认存储在单机服务器内存中,因此在分布式环境下同一个用户发送的多次HTTP请求可能会先后落到不同的服务器上,导致后面发起的HTTP请求无法拿到之前的HTTP请求存储在服务器中的Session数据,从而使得Session机制在分布式环境下失效,因此在分布式集群中CSRF Token需要存储在Redis之类的公共存储空间。 由于使用Session存储,读取和验证CSRF Token会引起比较大的复杂度和性能问题,目前很多网站采用Encrypted Token Pattern方式。这种方法的Token是一个计算出来的结果,而非随机生成的字符串。这样在校验时无需再去读取存储的Token,只用再次计算一次即可。 这种Token的值通常是使用UserID、时间戳和随机数,通过加密的方法生成。这样既可以保证分布式服务的Token一致,又能保证Token不容易被破解。 在token解密成功之后,服务器可以访问解析值,Token中包含的UserID和时间戳将会被拿来被验证有效性,将UserID与当前登录的UserID进行比较,并将时间戳与当前时间进行比较。

总结

Token是一个比较有效的CSRF防护方法,只要页面没有XSS漏洞泄露Token,那么接口的CSRF攻击就无法成功。 但是此方法的实现比较复杂,需要给每一个页面都写入Token(前端无法使用纯静态页面),每一个Form及Ajax请求都携带这个Token,后端对每一个接口都进行校验,并保证页面Token及请求Token一致。这就使得这个防护策略不能在通用的拦截上统一拦截处理,而需要每一个页面和接口都添加对应的输出和校验。这种方法工作量巨大,且有可能遗漏。 验证码和密码其实也可以起到CSRF Token的作用哦,而且更安全。为什么很多银行等网站会要求已经登录的用户在转账时再次输入密码,现在是不是有一定道理了?

双重Cookie验证

在会话中存储CSRF Token比较繁琐,而且不能在通用的拦截上统一处理所有的接口。 那么另一种防御措施是使用双重提交Cookie。利用CSRF攻击不能获取到用户Cookie的特点,我们可以要求Ajax和表单请求携带一个Cookie中的值。 双重Cookie采用以下流程:

  • 在用户访问网站页面时,向请求域名注入一个Cookie,内容为随机字符串(例如csrfcookie=v8g9e4ksfhw)。
  • 在前端向后端发起请求时,取出Cookie,并添加到URL的参数中(接上例POST https://www.a.com/comment?csrfcookie=v8g9e4ksfhw)。
  • 后端接口验证Cookie中的字段与URL参数中的字段是否一致,不一致则拒绝。

此方法相对于CSRF Token就简单了许多。可以直接通过前后端拦截的的方法自动化实现。后端校验也更加方便,只需进行请求中字段的对比,而不需要再进行查询和存储Token。 当然,此方法并没有大规模应用,其在大型网站上的安全性还是没有CSRF Token高,原因我们举例进行说明。 由于任何跨域都会导致前端无法获取Cookie中的字段(包括子域名之间),于是发生了如下情况:

  • 如果用户访问的网站为www.a.com,而后端的api域名为api.a.com。那么在www.a.com下,前端拿不到api.a.com的Cookie,也就无法完成双重Cookie认证。
  • 于是这个认证Cookie必须被种在a.com下,这样每个子域都可以访问。
  • 任何一个子域都可以修改a.com下的Cookie。
  • 某个子域名存在漏洞被XSS攻击(例如upload.a.com)。虽然这个子域下并没有什么值得窃取的信息。但攻击者修改了a.com下的Cookie。
  • 攻击者可以直接使用自己配置的Cookie,对XSS中招的用户再向www.a.com下,发起CSRF攻击。

总结:

用双重Cookie防御CSRF的优点:

  • 无需使用Session,适用面更广,易于实施。
  • Token储存于客户端中,不会给服务器带来压力。
  • 相对于Token,实施成本更低,可以在前后端统一拦截校验,而不需要一个个接口和页面添加。

缺点:

  • Cookie中增加了额外的字段。
  • 如果有其他漏洞(例如XSS),攻击者可以注入Cookie,那么该防御方式失效。
  • 难以做到子域名的隔离。
  • 为了确保Cookie传输安全,采用这种防御方式的最好确保用整站HTTPS的方式,如果还没切HTTPS的使用这种方式也会有风险。

Samesite Cookie属性

防止CSRF攻击的办法已经有上面的预防措施。为了从源头上解决这个问题,Google起草了一份草案来改进HTTP协议,那就是为Set-Cookie响应头新增Samesite属性,它用来标明这个 Cookie是个“同站 Cookie”,同站Cookie只能作为第一方Cookie,不能作为第三方Cookie,Samesite 有两个属性值,分别是 Strict 和 Lax,下面分别讲解:

Samesite=Strict

这种称为严格模式,表明这个 Cookie 在任何情况下都不可能作为第三方 Cookie,绝无例外。比如说 b.com 设置了如下 Cookie:

Set-Cookie: foo=1; Samesite=Strict
+Set-Cookie: bar=2; Samesite=Lax
+Set-Cookie: baz=3
+

我们在 a.com 下发起对 b.com 的任意请求,foo 这个 Cookie 都不会被包含在 Cookie 请求头中,但 bar 会。举个实际的例子就是,假如淘宝网站用来识别用户登录与否的 Cookie 被设置成了 Samesite=Strict,那么用户从百度搜索页面甚至天猫页面的链接点击进入淘宝后,淘宝都不会是登录状态,因为淘宝的服务器不会接受到那个 Cookie,其它网站发起的对淘宝的任意请求都不会带上那个 Cookie。

Samesite=Lax

这种称为宽松模式,比 Strict 放宽了点限制:假如这个请求是这种请求(改变了当前页面或者打开了新页面)且同时是个GET请求,则这个Cookie可以作为第三方Cookie。比如说 b.com设置了如下Cookie:

Set-Cookie: foo=1; Samesite=Strict
+Set-Cookie: bar=2; Samesite=Lax
+Set-Cookie: baz=3
+

当用户从 a.com 点击链接进入 b.com 时,foo 这个 Cookie 不会被包含在 Cookie 请求头中,但 bar 和 baz 会,也就是说用户在不同网站之间通过链接跳转是不受影响了。但假如这个请求是从 a.com 发起的对 b.com 的异步请求,或者页面跳转是通过表单的 post 提交触发的,则bar也不会发送。 生成Token放到Cookie中并且设置Cookie的Samesite,Java代码如下:

private void addTokenCookieAndHeader(HttpServletRequest httpRequest, HttpServletResponse httpResponse) {
+        //生成token
+        String sToken = this.generateToken();
+        //手动添加Cookie实现支持“Samesite=strict”
+        //Cookie添加双重验证
+        String CookieSpec = String.format("%s=%s; Path=%s; HttpOnly; Samesite=Strict", this.determineCookieName(httpRequest), sToken, httpRequest.getRequestURI());
+        httpResponse.addHeader("Set-Cookie", CookieSpec);
+        httpResponse.setHeader(CSRF_TOKEN_NAME, token);
+    }
+

代码源自OWASP Cross-Site_Request_Forgery #Implementation exampleopen in new window

我们应该如何使用SamesiteCookie

如果SamesiteCookie被设置为Strict,浏览器在任何跨域请求中都不会携带Cookie,新标签重新打开也不携带,所以说CSRF攻击基本没有机会。 但是跳转子域名或者是新标签重新打开刚登陆的网站,之前的Cookie都不会存在。尤其是有登录的网站,那么我们新打开一个标签进入,或者跳转到子域名的网站,都需要重新登录。对于用户来讲,可能体验不会很好。 如果SamesiteCookie被设置为Lax,那么其他网站通过页面跳转过来的时候可以使用Cookie,可以保障外域连接打开页面时用户的登录状态。但相应的,其安全性也比较低。 另外一个问题是Samesite的兼容性不是很好,现阶段除了从新版Chrome和Firefox支持以外,Safari以及iOS Safari都还不支持,现阶段看来暂时还不能普及。 而且,SamesiteCookie目前有一个致命的缺陷:不支持子域。例如,种在topic.a.com下的Cookie,并不能使用a.com下种植的SamesiteCookie。这就导致了当我们网站有多个子域名时,不能使用SamesiteCookie在主域名存储用户登录信息。每个子域名都需要用户重新登录一次。 总之,SamesiteCookie是一个可能替代同源验证的方案,但目前还并不成熟,其应用场景有待观望。

防止网站被利用

前面所说的,都是被攻击的网站如何做好防护。而非防止攻击的发生,CSRF的攻击可以来自:

  • 攻击者自己的网站。
  • 有文件上传漏洞的网站。
  • 第三方论坛等用户内容。
  • 被攻击网站自己的评论功能等。

对于来自黑客自己的网站,我们无法防护。但对其他情况,那么如何防止自己的网站被利用成为攻击的源头呢?

  • 严格管理所有的上传接口,防止任何预期之外的上传内容(例如HTML)。
  • 添加Header X-Content-Type-Options: nosniff 防止黑客上传HTML内容的资源(例如图片)被解析为网页。
  • 对于用户上传的图片,进行转存或者校验。不要直接使用用户填写的图片链接。
  • 当前用户打开其他用户填写的链接时,需告知风险(这也是很多论坛不允许直接在内容中发布外域链接的原因之一,不仅仅是为了用户留存,也有安全考虑)。
Last Updated:
Contributors: xiaoyu
+ + + diff --git a/interview/codeReview.html b/interview/codeReview.html new file mode 100644 index 0000000..e403ef0 --- /dev/null +++ b/interview/codeReview.html @@ -0,0 +1,37 @@ + + + + + + + + + 会做代码的Review吗? | 🍰 小雨的学习记录 + + + + + +

会做代码的Review吗?

1、什么是CodeReview?

Code Review(CR)即代码评审,又名代码走查,是一种通过复查代码来提高代码质量的过程,一般体现在一个团队的开发过程中。CR要求团队成员有意识地、系统地检查彼此的代码,从而验证需求、发现错误,同时指出其中不合规范的“低质量”代码,从而提高整个团队的代码质量。

一次 CR 可以是一次 Commit,也可以是一次 Merge Request。因此,实践课系统支持团队内部的 MR 评审以及 Commit 评审,供大家学习和交流。

2、为什么要CodeReview?

(1)旁观者清。

  • 对于同一段业务代码,由于看待问题的角度不同,评审者可能会比开发者更容易发现其中的问题,或是找到更有效的解决方案,共同维护团队的代码质量。
  • 提高代码质量和可维护性, 可读性等。
  • 查漏补缺, 发现一些潜在的问题点等。
  • 最佳实践, 能够更好更快的完成任务的方法。
  • 知识分享, Review他人代码时, 其实也是一个学习的过程, 自己也会反思&总结。

(2)快速了解业务。

  • 理想状态下,团队中的每个人都需要对整个项目的各个部分都很熟悉,当然,在项目很大时这是不现实的。通过代码审查至少可以让每个人了解更多的业务模块,同时也能达到人员互备的目的。
  • 人员互备:通过 CR,评审者也相当于参与了这次开发,相当于一种人力“备份”,当你休假或正在忙别的需求的时候,这时“备份”或许就能帮上你的忙了。

(3)开发者能够获得什么?

  • 对需求的理解得到加深。
  • 表达能力得到加强。
  • 逻辑能力得到训练。
  • 心理承受能力得到提高。 (4)评审者能够获得什么?
  • 快速上手业务需求和全局的架构。
  • 统一大家约定俗成的代码风格。
  • 优秀的设计思路和业务逻辑。

CodeReview中的常见问题【渡一教育】_哔哩哔哩_bilibiliopen in new window

第一次被代码审查(code review)的经历,太真实了!_哔哩哔哩_bilibiliopen in new window

code review 真的有必要吗,到底在review什么?_哔哩哔哩_bilibiliopen in new window

Last Updated:
Contributors: xiaoyu
+ + + diff --git a/interview/coding.html b/interview/coding.html new file mode 100644 index 0000000..7c67b40 --- /dev/null +++ b/interview/coding.html @@ -0,0 +1,502 @@ + + + + + + + + + 项目的编码规范 | 🍰 小雨的学习记录 + + + + + +

项目的编码规范

前端代码规范

规范的目的是为了编写高质量的代码,让你的团队成员每天得心情都是愉悦的,大家在一起是快乐的。
引自《阿里规约》的开头片段:
---现代软件架构的复杂性需要协同开发完成,如何高效地协同呢?无规矩不成方圆,无规范难以协同,比如,制订交通法规表面上是要限制行车权,实际上是保障公众的人身安全,试想如果没有限速,没有红绿灯,谁还敢上路行驶。对软件来说,适当的规范和标准绝不是消灭代码内容的创造性、优雅性,而是限制过度个性化,以一种普遍认可的统一方式一起做事,提升协作效率,降低沟通成本。代码的字里行间流淌的是软件系统的血液,质量的提升是尽可能少踩坑,杜绝踩重复的坑,切实提升系统稳定性,码出质量。

一.编程规约

(一) 命名规范

1.1.1 项目命名

全部采用小写方式,以中线分隔。

正:mall-management-system
反:mall_management-system / mallManagementSystem

1.1.2 目录命名

全部采用小写方式, 以中划线分隔,有复数结构时,要采用复数命名法, 缩写不用复数。

正例: scripts/styles/components/images/utils/layouts/demo-styles/demo-scripts/img/doc
反例: script/style/demo_scripts/demoStyles/imgs/docs
【特殊】VUE的项目中的components中的组件目录,使用kebab-case命名。
正例: head-search/page-loading/authorized/notice-icon
反例: HeadSearch/PageLoading
【特殊】VUE的项目中的除components组件目录外的所有目录也使用kebab-case命名。
正例: page-one/shopping-car/user-management
反例: ShoppingCar/UserManagement

1.1.3 JS、CSS、SCSS、HTML、PNG 文件命名

全部采用小写方式, 以中划线分隔。

正例: render-dom.js /signup.css/index.html/company-logo.png
反例: renderDom.js/UserManagement.html

1.1.4 命名严谨性

代码中的命名严禁使用拼音与英文混合的方式,更不允许直接使用中文的方式。 说明:正确的 英文拼写和语法可以让阅读者易于理解,避免歧义。注意,即使纯拼音命名方式也要避免采用

正例:henan/luoyang/rmb等国际通用的名称,可视同英文
反例:DazhePromotion[打折]/ getPingfenByName()[评分]/ int某变量= 3

杜绝完全不规范的缩写,避免望文不知义:

反例: AbstractClass“缩写"命名成 AbsClass ;
condition“缩写"命名成condi,此类随意缩写严重降低了代码的可阅读性。

(二) HTML 规范 (Vue Template 同样适用)

1.2.1 HTML 类型

推荐使用 HTML5 的文档类型申明:(建议使用text/html格式的 HTML。避免使用XHTML。XHTML 以及它的属性,比如application/xhtml+xml在浏览器中的应用支持与优化空间都十分有限)。

  • 规定字符编码
  • IE兼容模式
  • 规定字符编码
  • doctype大写

正例:

<!DOCTYPE html>
+<html>
+  <head> 
+      <meta http-equiv="X-UA-Compatible" content="IE=Edge" /> 
+	  <meta charset="UTF-8" /> 
+	  <title>Page title</title> 
+  </head>
+  <body> 
+	 <img src="images/company-logo.png" alt="Company">
+ </body> 
+  </html>
+

1.2.2 缩进

缩进使用 2 个空格(一个 tab);

嵌套的节点应该缩进。

1.2.3 分块注释

在每一个块状元素,列表元素和表格元素后,加上一对 HTML 注释。

1.2.4 语义化标签

HTML5 中新增很多语义化标签,所以优先使用语义化标签,避免一个页面都是 div 或者 p 标 签。

正例

<header></header> 
+<footer></footer>
+

反例

<div> 
+  <p></p>
+ </div>
+

1.2.5 引号

使用双引号(" ") 而不是单引号(' ') 。

正例:<div class="box"></div>
反例:<div class='box'></div>

(三) CSS 规范

1.3.1 命名

  • 类名使用小写字母,以中划线分隔
  • id采用驼峰式命名
  • scss中的变量、函数、混合、placeholder采用驼峰式命名

ID和class的名称总是使用可以反应元素目的和用途的名称,或其他通用的名称,代替表象和晦涩难懂的名称。

不推荐:

.fw-800 {
+    font-weight: 800;
+  }
+  .red {
+    color: red; 
+   }
+

推荐:

.heavy {
+   font-weight: 800;
+  }
+.important { 
+  color: red; 
+  }
+

1.3.2 选择器

1) css 选择器中避免使用标签名

从结构、表现、行为分离的原则来看,应该尽量避免css中出现HTML标签,并且在css选择器中出现标签名会存在潜在的问题。

2) 使用直接子选择器

很多前端开发人员写选择器链的时候不使用直接子选择器(注:直接子选择器和后代选择器的区别)。有时,这可能会导致疼痛的设计问题并且有时候可能会很耗性能。然而,在任何情况下,这是一个非常不好的做法。如果你不写很通用的,需要匹配到DOM末端的选择器,你应该总是考虑直接子选择器。

不推荐:

.content .title {
+   font-size: 2rem;
+  }
+ 
+

推荐:

.content > .title {
+   font-size: 2rem;
+ }
+

1.3.3 尽量使用缩写属性

不推荐:

border-top-style: none; 
+font-family: palatino, georgia, serif; 
+font-size: 100%; line-height: 1.6; 
+padding-bottom: 2em; 
+padding-left: 1em;
+ padding-right: 1em; 
+ padding-top: 0;
+

推荐:

border-top: 0; 
+font: 100%/1.6 palatino, georgia, serif; 
+padding: 0 1em 2em;
+

1.3.4 每个选择器及属性独占一行

不推荐:

button { 
+	width: 100px; 
+	height: 50px;
+	color: #fff;
+	background: #00a0e9;
+  }
+

推荐:

button {
+  width: 100px; height: 50px;
+  color: #fff;
+  background: #00a0e9; 
+}
+

1.3.5 省略 0 后面的单位

不推荐:

 div {
+	 padding-bottom: 0px; 
+	 margin: 0em;
+ }
+

推荐:

div {
+    padding-bottom: 0; 
+    margin: 0; 
+}
+

1.3.6 避免使用 ID 选择器及全局标签选择器防止污染全局样式

不推荐:

#header {
+ padding-bottom: 0px; 
+ margin: 0em;
+}
+

推荐:

.header { 
+	padding-bottom: 0px; 
+	margin: 0em; 
+}
+

(四) LESS 规范

1.4.1 代码组织

1) 将公共 less 文件放置在 style/less/common 文件夹

例: // color.less,common.less

2) 按以下顺序组织

1、@import;
2、变量声明;
3、样式声明;

@import "mixins/size.less"; 
+@default-text-color: #333; 
+.page {
+ width: 960px; 
+ margin: 0 auto; 
+}
+

1.4.2 避免嵌套层级过多

将嵌套深度限制在3级。对于超过4级的嵌套,给予重新评估。这可以避免出现过于详实的CSS 选择器。避免大量的嵌套规则。当可读性受到影响时,将之打断。推荐避免出现多于20行的嵌套规则出现。

不推荐:

 .main {
+   .title { 
+      .name { 
+           color: #fff;  
+         } 
+     }
+}
+

推荐:

.main-title {
+   .name { color: #fff; }
+    }
+

(五) Javascript 规范

1.5.1 命名

1) 采用小写驼峰命名 lowerCamelCase,代码中的命名均不能以下划线, 也不能以下划线或美元符号结束

反例: _name / name_ / name$

2) 方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风 格,必须遵从驼峰形式

正例: localValue / getHttpMessage() / inputUserId
其中 method 方法命名必须是 动词 或者 动词+名词 形式
正例: saveShopCarData /openShopCarInfoDialog
反例: save / open / show / go
特此说明,增删查改,详情统一使用如下 5 个单词,不得使用其他(目的是为了统一各个端)

add / update / delete / detail / get 
+附: 函数方法常用的动词: 
+get 获取/set 设置, 
+add 增加/remove 删除, 
+create 创建/destory 销毁, 
+start 启动/stop 停止, 
+open 打开/close 关闭, 
+read 读取/write 写入, 
+load 载入/save 保存,
+begin 开始/end 结束, 
+backup 备份/restore 恢复,
+import 导入/export 导出, 
+split 分割/merge 合并,
+inject 注入/extract 提取,
+attach 附着/detach 脱离, 
+bind 绑定/separate 分离, 
+view 查看/browse 浏览, 
+edit 编辑/modify 修改,
+select 选取/mark 标记, 
+copy 复制/paste 粘贴,
+undo 撤销/redo 重做, 
+insert 插入/delete 移除,
+add 加入/append 添加, 
+clean 清理/clear 清除,
+index 索引/sort 排序,
+find 查找/search 搜索, 
+increase 增加/decrease 减少, 
+play 播放/pause 暂停, 
+launch 启动/run 运行, 
+compile 编译/execute 执行, 
+debug 调试/trace 跟踪, 
+observe 观察/listen 监听,
+build 构建/publish 发布,
+input 输入/output 输出,
+encode 编码/decode 解码, 
+encrypt 加密/decrypt 解密, 
+compress 压缩/decompress 解压缩, 
+pack 打包/unpack 解包,
+parse 解析/emit 生成,
+connect 连接/disconnect 断开,
+send 发送/receive 接收, 
+download 下载/upload 上传, 
+refresh 刷新/synchronize 同步,
+update 更新/revert 复原, 
+lock 锁定/unlock 解锁, 
+check out 签出/check in 签入, 
+submit 提交/commit 交付, 
+push 推/pull 拉,
+expand 展开/collapse 折叠, 
+enter 进入/exit 退出,
+abort 放弃/quit 离开, 
+obsolete 废弃/depreciate 废旧, 
+collect 收集/aggregate 聚集
+
3) 常量命名全部大写,单词间用下划线隔开,力求语义表达完整清楚, 不要嫌名字长

正例: MAX_STOCK_COUNT 反例: MAX_COUNT

1.5.2 代码格式

1) 使用 2 个空格进行缩进

正例:

if (x < y) {
+ x += 10;
+  } else {
+   x += 1; 
+}
+
2) 不同逻辑、不同语义、不同业务的代码之间插入一个空行分隔开来以 提升可读性

说明:任何情形,没有必要插入多个空行进行隔开。

1.5.3 字符串

统一使用单引号(‘),不使用双引号(“)。这在创建 HTML 字符串非常有好处: 正例:

   let str = 'foo';
+   let testDiv = '<div id="test"></div>'; 
+

反例:

let str = 'foo'; 
+let testDiv = "<div id='test'></div>";
+

1.5.4 对象声明

1) 使用字面值创建对象

正例: let user = {};反例: let user = new Object();

2) 使用字面量来代替对象构造器

正例: var user = { age: 0, name: 1, city: 3 };反例:

var user = new Object(); 
+user.age = 0; 
+user.name = 0; 
+user.city = 0; 
+

1.5.5 使用 ES6+

必须优先使用ES6+中新增的语法糖和函数。这将简化你的程序,并让你的代码更加灵活和可复用。比如箭头函数、await/async,解构,let , for ...of 等等。

1.5.6 括号

下列关键字后必须有大括号(即使代码块的内容只有一行) : if, else, for,while, do, switch,try,catch, finally, with。

正例:

if (condition) { 
+doSomething();
+ }
+

反例:

if (condition) doSomething();
+

1.5.7 undefined 判断

永远不要直接使用 undefined 进行变量判断;使用 typeof 和字符串’undefined’对变量进行判断。

正例:

 if (typeof person === 'undefined') { ... }
+

反例:

if (person === undefined) { ... }
+

1.5.8 条件判断和循环最多三层

条件判断能使用三目运算符和逻辑运算符解决的,就不要使用条件判断,但是谨记不要写太长的三目运算符。如果超过3层请抽成函数,并写清楚注释。

1.5.9 this 的转换命名

对上下文this的引用只能使用'self来命名。

1.5.10 慎用 console.log

因console.log大量使用会有性能问题,所以在非webpack项目中谨慎使用log 功能。

二、Vue 项目规范

(一) Vue 编码基础

vue 项目规范以 Vue 官方规范 (https://cn.vuejs.org/v2/style-guide/open in new window) 中的 A 规范为基础,在其上面进行项目开发,故所有代码均遵守该规范。 请仔仔细细阅读 Vue 官方规范,切记,此为第一步。

2.1.1. 组件规范

1) 组件名为多个单词。

组件名应该始终是多个单词组成(大于等于 2),且命名规范为KebabCase格式。 这样做可以避免跟现有的以及未来的 HTML 元素相冲突,因为所有的 HTML 元素名称都是单个单词的。

正例:

export default {
+  name: 'TodoItem'
+  // ...
+};
+

反例:


+export default {
+  name: 'Todo',
+  // ...
+}
+export default {
+  name: 'todo-item',
+  // ...
+}
+
2) 组件文件名为 pascal-case 格式

正例:

components/
+|- my-component.vue
+

反例:

components/
+|- myComponent.vue
+|- MyComponent.vue
+
3) 基础组件文件名为 base 开头,使用完整单词而不是缩写。

正例:

components/
+|- base-button.vue
+|- base-table.vue
+|- base-icon.vue
+

反例:

components/
+|- MyButton.vue
+|- VueTable.vue
+|- Icon.vue
+
4) 和父组件紧密耦合的子组件应该以父组件名作为前缀命名

正例:

components/
+|- todo-list.vue
+|- todo-list-item.vue
+|- todo-list-item-button.vue
+|- user-profile-options.vue (完整单词)
+

反例:

components/
+|- TodoList.vue
+|- TodoItem.vue
+|- TodoButton.vue
+|- UProfOpts.vue (使用了缩写)
+
5) 在 Template 模版中使用组件,应使用 PascalCase 模式,并且使用自闭合组件。

正例:

<!-- 在单文件组件、字符串模板和 JSX 中 -->
+<MyComponent />
+<Row><table :column="data"/></Row>
+

反例:

<my-component /> <row><table :column="data"/></row>
+
6) 组件的 data 必须是一个函数

当在组件中使用data属性的时候(除了new Vue外的任何地方),它的值必须是返回一个对象的函数。因为如果直接是一个对象的话,子组件之间的属性值会互相影响。

正例:

export default {
+  data () {
+    return {
+      name: 'jack'
+    }
+  }
+}
+

反例:

export default {
+  data: {
+    name: 'jack'
+  }
+}
+
7) Prop 定义应该尽量详细
  • 必须使用 camelCase 驼峰命名
  • 必须指定类型
  • 必须加上注释,表明其含义
  • 必须加上 required 或者 default,两者二选其一
  • 如果有业务需要,必须加上 validator 验证 正例:
 props: {
+  // 组件状态,用于控制组件的颜色
+   status: {
+     type: String,
+     required: true,
+     validator: function (value) {
+       return [
+         'succ',
+         'info',
+         'error'
+       ].indexOf(value) !== -1
+     }
+   },
+    // 用户级别,用于显示皇冠个数
+   userLevel:{
+      type: String,
+      required: true
+   }
+}
+
8) 为组件样式设置作用域

正例:

<template>
+  <button class="btn btn-close">X</button>
+</template>
+<!-- 使用 `scoped` 特性 -->
+<style scoped>
+  .btn-close {
+    background-color: red;
+  }
+</style>
+

反例:

<template>
+  <button class="btn btn-close">X</button>
+</template>
+<!-- 没有使用 `scoped` 特性 -->
+<style>
+  .btn-close {
+    background-color: red;
+  }
+</style>
+
9) 如果特性元素较多,应该主动换行。

正例:

<MyComponent foo="a" bar="b" baz="c"
+    foo="a" bar="b" baz="c"
+    foo="a" bar="b" baz="c"
+ />
+

反例:

<MyComponent foo="a" bar="b" baz="c" foo="a" bar="b" baz="c" foo="a" bar="b" baz="c" foo="a" bar="b" baz="c"/>
+

2.1.2. 模板中使用简单的表达式

组件模板应该只包含简单的表达式,复杂的表达式则应该重构为计算属性或方法。复杂表达式会让你的模板变得不那么声明式。我们应该尽量描述应该出现的是什么,而非如何计算那个值。而且计算属性和方法使得代码可以重用。

正例:

<template>
+  <p>{{ normalizedFullName }}</p>
+</template>
+// 复杂表达式已经移入一个计算属性
+computed: {
+  normalizedFullName: function () {
+    return this.fullName.split(' ').map(function (word) {
+      return word[0].toUpperCase() + word.slice(1)
+    }).join(' ')
+  }
+}
+

反例:

<template>
+  <p>
+       {{
+          fullName.split(' ').map(function (word) {
+             return word[0].toUpperCase() + word.slice(1)
+           }).join(' ')
+        }}
+  </p>
+</template>
+

2.1.3 指令都使用缩写形式

指令推荐都使用缩写形式,(用 : 表示 v-bind: 、用 @ 表示 v-on: 和用 # 表示 v-slot:)

正例:

<input
+  @input="onInput"
+  @focus="onFocus"
+>
+

反例:

<input
+  v-on:input="onInput"
+  @focus="onFocus"
+>
+

2.1.4 标签顺序保持一致

单文件组件应该总是让标签顺序保持为

正例:

<template>...</template>
+<script>...</script>
+<style>...</style>
+

反例:

<template>...</template>
+<style>...</style>
+<script>...</script>
+

2.1.5 必须为 v-for 设置键值 key

2.1.6 v-show 与 v-if 选择

如果运行时,需要非常频繁地切换,使用 v-show ;如果在运行时,条件很少改变,使用 v-if。

2.1.7 script 标签内部结构顺序

components > props > data > computed > watch > filter > 钩子函数(钩子函数按其执行顺序) > methods

2.1.8 Vue Router 规范

1) 页面跳转数据传递使用路由参数

页面跳转,例如 A 页面跳转到 B 页面,需要将 A 页面的数据传递到 B 页面,推荐使用 路由参数进行传参,而不是将需要传递的数据保存 vuex,然后在 B 页面取出 vuex 的数据,因为如果在 B 页面刷新会导致 vuex 数据丢失,导致 B 页面无法正常显示数据。 正例:

let id = ' 123';
+this.$router.push({ name: 'userCenter', query: { id: id } });
+
2) 使用路由懒加载(延迟加载)机制
{
+    path: '/uploadAttachment',
+    name: 'uploadAttachment',
+    meta: {
+      title: '上传附件'
+    },
+    component: () => import('@/view/components/uploadAttachment/index.vue')
+  },
+

3) router 中的命名规范 path、childrenPoints 命名规范采用kebab-case命名规范(尽量vue文件的目录结构保持一致,因为目录、文件名都是kebab-case,这样很方便找到对应的文件) name 命名规范采用KebabCase命名规范且和component组件名保持一致!(因为要保持keep-alive特性,keep-alive按照component的name进行缓存,所以两者必须高度保持一致)

// 动态加载
+export const reload = [
+  {
+    path: '/reload',
+    name: 'reload',
+    component: Main,
+    meta: {
+      title: '动态加载',
+      icon: 'icon iconfont'
+    },
+    children: [
+      {
+        path: '/reload/smart-reload-list',
+        name: 'SmartReloadList',
+        meta: {
+          title: 'SmartReload',
+          childrenPoints: [
+            {
+              title: '查询',
+              name: 'smart-reload-search'
+            },
+            {
+              title: '执行reload',
+              name: 'smart-reload-update'
+            },
+            {
+              title: '查看执行结果',
+              name: 'smart-reload-result'
+            }
+          ]
+        },
+        component: () =>
+          import('@/views/reload/smart-reload/smart-reload-list.vue')
+      }
+    ]
+  }
+];
+
4) router 中的 path 命名规范

path除了采用kebab-case命名规范以外,必须以 / 开头,即使是children里的path也要以 / 开头。如下示例 目的: 经常有这样的场景:某个页面有问题,要立刻找到这个vue文件,如果不用以/开头,path为parent和children组成的,可能经常需要在router文件里搜索多次才能找到,而如果以/开头,则能立刻搜索到对应的组件

{
+    path: '/file',
+    name: 'File',
+    component: Main,
+    meta: {
+      title: '文件服务',
+      icon: 'ios-cloud-upload'
+    },
+    children: [
+      {
+        path: '/file/file-list',
+        name: 'FileList',
+        component: () => import('@/views/file/file-list.vue')
+      },
+      {
+        path: '/file/file-add',
+        name: 'FileAdd',
+        component: () => import('@/views/file/file-add.vue')
+      },
+      {
+        path: '/file/file-update',
+        name: 'FileUpdate',
+        component: () => import('@/views/file/file-update.vue')
+      }
+    ]
+  }
+

(二) Vue 项目目录规范

2.2.1 基础

vue 项目中的所有命名一定要与后端命名统一。 比如权限:后端 privilege, 前端无论 router , store, api 等都必须使用 privielege 单词! 2.2.2 使用 Vue-cli 脚手架 使用 vue-cli3 来初始化项目,项目名按照上面的命名规范。 2.2.3 目录说明 目录名按照上面的命名规范,其中 components 组件用大写驼峰,其余除 components 组件目录外的所有目录均使用 kebab-case 命名。

src                                  源码目录
+|-- api                              所有api接口
+|-- assets                           静态资源,images, icons, styles等
+|-- components                       公用组件
+|-- config                           配置信息
+|-- constants                        常量信息,项目所有Enum, 全局常量等
+|-- directives                       自定义指令
+|-- filters                          过滤器,全局工具
+|-- datas                            模拟数据,临时存放
+|-- lib                              外部引用的插件存放及修改文件
+|-- mock                             模拟接口,临时存放
+|-- plugins                          插件,全局使用
+|-- router                           路由,统一管理
+|-- store                            vuex, 统一管理
+|-- themes                           自定义样式主题
+|-- views                            视图目录
+|   |-- role                                 role模块名
+|   |-- |-- role-list.vue                    role列表页面
+|   |-- |-- role-add.vue                     role新建页面
+|   |-- |-- role-update.vue                  role更新页面
+|   |-- |-- index.less                       role模块样式
+|   |-- |-- components                       role模块通用组件文件夹
+|   |-- employee                             employee模块
+
  1. api 目录 文件、变量命名要与后端保持一致。 此目录对应后端 API 接口,按照后端一个 controller 一个 api js 文件。若项目较大时,可以按照业务划分子目录,并与后端保持一致。 api 中的方法名字要与后端 api url 尽量保持语义高度一致性。 对于 api 中的每个方法要添加注释,注释与后端 swagger 文档保持一致。 正例: 后端 url: EmployeeController.java
/employee/add
+/employee/delete/{id}
+/employee/update
+

前端: employee.js

  // 添加员工
+  addEmployee: (data) => {
+    return postAxios('/employee/add', data)
+  },
+  // 更新员工信息
+  updateEmployee: (data) => {
+    return postAxios('/employee/update', data)
+  },
+    // 删除员工
+  deleteEmployee: (employeeId) => {
+    return postAxios('/employee/delete/' + employeeId)
+   },
+
2) assets 目录

assets 为静态资源,里面存放 images, styles, icons 等静态资源,静态资源命名格式为 kebab-case

|assets
+|-- icons
+|-- images
+|   |-- background-color.png
+|   |-- upload-header.png
+|-- styles
+

3) components 目录

此目录应按照组件进行目录划分,目录命名为 KebabCase,组件命名规则也为 KebabCase

|components
+|-- error-log
+|   |-- index.vue
+|   |-- index.less
+|-- markdown-editor
+|   |-- index.vue
+|   |-- index.js
+|-- kebab-case
+
4) constants 目录

此目录存放项目所有常量,如果常量在 vue 中使用,请使用 vue-enum 插件(https://www.npmjs.com/package/vue-enumopen in new window) 目录结构:

|constants
+|-- index.js
+|-- role.js
+|-- employee.js
+

例子: employee.js

export const EMPLOYEE_STATUS = {
+  NORMAL: {
+    value: 1,
+    desc: '正常'
+  },
+  DISABLED: {
+    value: 1,
+    desc: '禁用'
+  },
+  DELETED: {
+    value: 2,
+    desc: '已删除'
+  }
+};
+export const EMPLOYEE_ACCOUNT_TYPE = {
+  QQ: {
+    value: 1,
+    desc: 'QQ登录'
+  },
+  WECHAT: {
+    value: 2,
+    desc: '微信登录'
+  },
+  DINGDING: {
+    value: 3,
+    desc: '钉钉登录'
+  },
+  USERNAME: {
+    value: 4,
+    desc: '用户名密码登录'
+  }
+};
+export default {
+  EMPLOYEE_STATUS,
+  EMPLOYEE_ACCOUNT_TYPE
+};
+

5) router 与 store 目录

这两个目录一定要将业务进行拆分,不能放到一个 js 文件里。 router 尽量按照 views 中的结构保持一致 store 按照业务进行拆分不同的 js 文件

6) views 目录

命名要与后端、router、api 等保持一致 components 中组件要使用 PascalCase 规则

|-- views                                    视图目录
+|   |-- role                                 role模块名
+|   |   |-- role-list.vue                    role列表页面
+|   |   |-- role-add.vue                     role新建页面
+|   |   |-- role-update.vue                  role更新页面
+|   |   |-- index.less                      role模块样式
+|   |   |-- components                      role模块通用组件文件夹
+|   |   |   |-- role-header.vue             role头部组件
+|   |   |   |-- role-modal.vue              role弹出框组件
+|   |-- employee                            employee模块
+|   |-- behavior-log                        行为日志log模块
+|   |-- code-generator                      代码生成器模块
+

2.2.4 注释说明 整理必须加注释的地方

公共组件使用说明 api 目录的接口 js 文件必须加注释 store 中的 state, mutation, action 等必须加注释 vue 文件中的 template 必须加注释,若文件较大添加 start end 注释 vue 文件的 methods,每个 method 必须添加注释 vue 文件的 data, 非常见单词要加注释

2.2.5 其他

1) 尽量不要手动操作 DOM

因使用 vue 框架,所以在项目开发中尽量使用 vue 的数据驱动更新 DOM,尽量(不到万不得已)不要手动操作 DOM,包括:增删改 dom 元素、以及更改样式、添加事件等。

2) 删除无用代码

因使用了 git/svn 等代码版本工具,对于无用代码必须及时删除,例如:一些调试的 console 语句、无用的弃用功能代码。

前后端分离必备的接口规范

没有任何接口约定规范情况下各自干各自的,导致我们在产品项目开发过程中,前后端的接口联调对接工作量占比在30%-50%左右,甚至会更高,往往前后端接口联调对接及系统间的联调对接都是整个产品项目研发的软肋。 本文的主要初衷就是规范约定先行,尽量避免沟通联调产生的不必要的问题,让大家身心愉快地专注于各自擅长的领域。

一文搞懂什么是RESTful APIopen in new window前后端接口规范 - RESTful 版open in new window

Last Updated:
Contributors: xiaoyu
+ + + diff --git a/interview/codingStyle.html b/interview/codingStyle.html new file mode 100644 index 0000000..f32b60a --- /dev/null +++ b/interview/codingStyle.html @@ -0,0 +1,37 @@ + + + + + + + + + 前端代码风格上的工具 | 🍰 小雨的学习记录 + + + + + +

前端代码风格上的工具

Vue3项目创建时可选用的代码格式化 Prettier

1.png

npm create vue@latest这一指令将会安装并执行 create-vueopen in new window,它是 Vue 官方的项目脚手架工具。 这里引入了ESLint和Prettier代码格式化工具。 npm run format进行代码格式化。

代码格式化的魅力2.png3.png4.png5.png6.png7.png

由于每次格式化的时候,我们都需要使用指令的话会比较麻烦。我们也可以在VScode上面安装prettier插件。

配置.prettierrc文件:

  • useTabs:使用tab缩进还是空格缩进,选择false时表示使用空格;
  • tabWidth:tab是空格的情况下,是几个空格,选择2个;
  • printWidth:当行字符的长度,推荐80;
  • singleQuote:使用单引号还是双引号,选择true,使用单引号;
  • trailingComma:在多行输入的尾逗号是否添加,设置为 none;
  • semi:语句末尾是否要加分号,默认值true,选择false表示不加;
  • 更多的配置可以查看官方文档open in new window

项目中引入 ESLint

这个时候通过 npm run dev 跑起来的开发环境就已经开启了 ESlint 的校验,错误的提示将会出现在终端中。预期是希望这些错误能够直接高亮在 VSCode 中,因此需要继续开启 VSCode 的插件。下面这个插件能根据你项目根目录下的ESLint配置文件进行高亮显示!!!

8.png文件的配置规则参考:规则参考 - ESLint - 插件化的 JavaScript 代码检查工具open in new window9.png配置规则:配置规则 - ESLint - 插件化的 JavaScript 代码检查工具open in new window10.png11.png ESLint的几个配置去向:配置 ESLint - ESLint - 插件化的 JavaScript 代码检查工具open in new window12.png

Last Updated:
Contributors: xiaoyu
+ + + diff --git a/interview/index.html b/interview/index.html new file mode 100644 index 0000000..cfdb6f2 --- /dev/null +++ b/interview/index.html @@ -0,0 +1,37 @@ + + + + + + + + + 面试经历及问题 | 🍰 小雨的学习记录 + + + + + +

[TOC]

面试经历及问题

我记录的问题可能有缺少……因为后面只想记录自己面试没有回答好的问题。

4-8——美团金融

  1. 前端模块化是什么?
  2. 项目编码规范
  3. Vue2和Vue3的特点区别
  4. Proxy的优缺点?
  5. HTTP和HTTPS的区别
  6. 闭包,this指向?
  7. call、apply、bind的区别?
  8. HTTP状态码和业务状态码?
  9. 手写instanceOf
  10. 算法最小树深度
  11. 还有很多……忘记了,没有录音

4-12——腾讯云一面

  1. 自我介绍
  2. 编码规范
  3. 代码风格上有没有用工具吗?
  4. 会做代码review吗?
  5. 谈了一下我的比赛经历
  6. 你学前端多久了啊?
  7. 你了解事件委托吗?
  8. 事件委托为什么能作用在其父节点上,利用了什么机制?
  9. 使用未声明的变量会干嘛?
  10. 第一个函数题(受教了),判断输出的。
  11. 第二个函数题,考作用域和变量提升的 FOO
  12. 什么是作用域?那作用域链呢?作用域的顶端是什么?
  13. 我们浏览器的全局对象是?
  14. 一个闭包输出题,setTimeout var 循环输出
  15. 了解过闭包吗?
  16. 想要这个题目正常打印01234该怎么解决?
  17. 为什么setTimeout输出的时间不准确?
  18. 有了解过JS的事件循环吗?
  19. 一个异步同步输出打印的题目
  20. new Promise中这个大的for循环会影响执行顺序吗?
  21. 用过new关键字去创建对象吗,说说这个执行流程
  22. 怎么改变this指向?
  23. call、apply、bind的区别?
  24. 箭头函数和普通函数的区别?
  25. 手写实现call函数
  26. 讲一下原型链
  27. 你有写过继承吗?
  28. 了解那些http的状态吗,仔细讲讲
  29. promise有什么特点?
  30. promise的几种状态有了解吗?
  31. 一个Promise的题目,封装请求的
  32. async await 和Promise有什么区别?
  33. 你用过生成器吗?
  34. 你知道什么是同源策略吗?
  35. 跨域有几种方式?
  36. XSS 和XSS防御
  37. CSRF 和 CSRF防御
  38. Vue的选项数据绑定原理
  39. 出了一个Vue相关,实现计算器的改错题。
  40. 聊一下我的项目,里面遇到的难点,怎么解决的,有什么收获
  41. 你平时怎么学前端的?
  42. 你搞过可视化吗?
  43. 平时学习中有写过博客吗?
  44. 面试官介绍业务和实习一般做什么内容。
  45. 继承了解吗?

整场下来一个半小时多一点,问题基本上是答出来了,面试管对我感觉也很好,在面试后二十分钟内就给我调整成复试状态了。

4-17——腾讯云二面

腾讯二面面试官给我的压力太大了,虽然录了音,我不敢再去听了QAQ,答得也还行,也有一些没有回答太好的,我记记。

  1. ETAG怎么生成的?
  2. 浏览器缓存的这些策略的应用场景
  3. ES6之前没有Promise怎么来进行的异步任务?
  4. git merge 和 git rebase 的区别?
  5. 谈谈你对Vue的设计目标和思想的理解?
  6. TLS这个过程你有了解吗?
  7. webpack中loader和plugin的使用场景和项目中的用法。
  8. 热跟新的实现机制,大概了解,webpack中的一些问题

4-19——领健

这家面试有意思,我是说题目。

  1. 一个页面同时渲染500个头像图片

假设一个界面,你屏幕窗口这么大的界面,上面显示了500个头像,img图像一次显示了500个,有没有优化方案。这个500个加载还是很慢的。

  1. 数组取第一个
  2. 箭头函数和普通函数(项目中那些地方必须只能用普通函数或者箭头函数)
  3. 你项目中深拷贝的一个方式
  4. 一个JSON相关的问题
  5. TS的枚举你知道吗,enum值转为ES5是什么样子的?
  6. 终极大题

还是你跟我聊天这个屏幕这么大,现在有这么一组数据,现在长度是500, [{x,y,r},{x,y,r},{x,y,r}……]x,y是表示位置。r是每个半径。要把这500个圆画在这个屏幕上

4-19——小米一面

  1. TS实现一个函数(检查TS)
  2. setTimeout的一个问题:用setTimeout模拟setInterval,进行定时打印!!!
  3. 4:3的一个问题,padding-bottom,center .实现一个长宽4:3的矩形,用padding-bottom:75%;width:100%;
  4. 一个跟定时任务相关的,纯前端实现

一个alter一天开始登录的时候只执行一次,后续就不会在去执行了。 我的解题思路:本地存储+时间判断对比(大概对了)

4-25——腾讯云三面

这个面试过程还可以,但是我有大大的疑惑。面试过程有两点没回答好,其他都还行。 基本二面三面是围绕项目来讲的!!!

  1. 在这个Vue模板上用到的那个事件绑定,是怎么做的?是和原生绑定事件一样吗?
  2. 你这里用到的Pinia进行数据的持久化,为什么会有这个情况,你有深入了解吗?
  3. SPA首屏渲染做的优化方案,讲你在项目中怎么解决这个问题。

4-26——小米二面

  1. 发布订阅、观察者模式的区别?
  2. 手写JS发布订阅
  3. treeshcking是怎么做到,是在框架层面还是ES6上面
  4. cookie相关的知识,获取cookie,JS操作cookie
  5. 使用 JavaScript 读取 Cookie
  6. 你能讲一下浏览器的存储吗,他们之间的一些区别?
  7. HTTP3之余HTTP2的改变,请讲一下。
  8. 了解Pinia底层原理吗,它是怎么进行状态管理的?

5-7——腾讯金融

5-10——海康威视

Last Updated:
Contributors: xiaoyu
+ + + diff --git a/interview/other.html b/interview/other.html new file mode 100644 index 0000000..67e0358 --- /dev/null +++ b/interview/other.html @@ -0,0 +1,39 @@ + + + + + + + + + 其他的一些小问题 | 🍰 小雨的学习记录 + + + + + +

其他的一些小问题

proxy的优缺点?

Object.defineProperty的缺陷:

  1. 无法检测到对象属性的新增或删除

    由于js的动态性,可以为对象追加新的属性或者删除其中某个属性, 这点对经过Object.defineProperty方法建立的响应式对象来说,只能追踪对象已有数据是否被修改,无法追踪新增属性和删除属性,这就需要另外处理。

  2. 不能监听数组的变化(对数组基于下标的修改、对于 .length 修改的监测)

    vue在实现数组的响应式时,它使用了一些hack,把无法监听数组的情况通过重写数组的部分方法来实现响式,这也只限制在数组的push/pop/shift/unshift/splice/sort/reverse七个方法,其他数组方法及数组的使用则无法检测到, 解决方法主要是使用proxy属性,这个proxy属性是ES6中新增的一个属性,proxy属性也是一个构造函数,他也可以通过new的方式创建这个函数,表示修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种元编程proxy可以理解为在目标对象之前架设一层拦截,外界对该对象的访问,都必须经过这层拦截,因此提出了一种机制,可以对外界的网文进行过滤和改写,proxy这个词是代理,用来表示由他代理某些操作,可以译为代理器

proxy代理的特点:

  • proxy直接代理的是整个对象而非对象属性
  • proxy的代理针对的是整个对象而不是像object.defineProperty针对某个属性
  • 只需要做一层代理就可以监听同级结构下的所有属性变化,包括新增的属性和删除的属性
  • proxy代理身上定义的方法共有13种,其中我们最常用的就是set和get,但是他本身还有其他的13种方法

proxy的劣势:

兼容性问题,虽然proxy相对越object.defineProperty有很有优势,但是并不是说proxy,就是完全的没有劣势,主要表现在以下的两个方面:

  1. proxy有兼容性问题,无完全的polyfill: proxy为ES6新出的API,浏览器对其的支持情况可在w3c规范中查到,通过查找我们可以知道, 虽然大部分浏览器支持proxy特性,但是一些浏览器或者低版本不支持proxy, 因此proxy有兼容性问题,那能否像ES6其他特性有polyfill解决方案呢?, 这时我们通过查询babel文档,发现在使用babel对代码进行降级处理的时候,并没有合适的polyfill

  2. 第二个问题就是性能问题,proxy的性能其实比promise还差, 这就需要在性能和简单实用上进行权衡,例如vue3使用proxy后, 其对对象及数组的拦截很容易实现数据的响应式,尤其是数组

     虽然proxy有性能和兼容性处理,但是proxy作为新标准将受到浏览器厂商重点持续的性能优化,
    + 性能这块会逐步得到改善
    +

面试官: 实现双向绑定Proxy比defineproperty优劣如何? - 掘金open in new window

Vue的双向绑定原理(腾讯)

vue的双向绑定原理与实现 - 掘金open in new window

安全验证 - 知乎open in new window

HTTP请求方法:幂等和非幂等?

幂等性和安全性是http请求方法的特性, 比如 get请求方法是具有安全的

安全性(此次请求不会修改后台):

** 仅指该方法的多次调用不会产生副作用,不涉及传统意义上的“安全”,这里的副作用是指资源状态。** 即,安全的方法不会修改资源状态,尽管多次调用的返回值可能不一样(被其他非安全方法修改过)。


幂等性(多次请求一个url,返回值不变):

** 是指该方法多次调用返回的效果(形式)一致,客户端可以重复调用并且期望同样的结果。一次调用和多次调用产生的效果是一致的,都是对一个变量进行赋值。**

————————————————————————————————方法名 安全性 幂等性 请求方法的作用get √ √ 请求指定的页面信息,并返回实体主体head √ √ 只请求页面的首部options √ √ 允许客户端查看服务器的性能delete × √ 请求服务器删除指定的数据put × √ 从客户端向服务器传送的数据取代指定的文档的内容post × × 请求服务器接受所指定的文档作为对所标识的URI的新的从属实体————————————————————————————————

方法名安全性幂等性请求方法的作用
get请求指定的页面信息,并返回实体主体
head只请求页面的首部
options允许客户端查看服务器的性能
delete×请求服务器删除指定的数据
put×从客户端向服务器传送的数据取代指定的文档的内容
post××请求服务器接受所指定的文档作为对所标识的URI的新的从属实体

method.pngPOST和GET谁更安全?

get更安全

get比post安全? -->get对于服务器是安全的–> get是幂等的,post是非幂等的

post更安全

① GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留。

② 浏览器有跨域访问的限制,如果是get的话,jsonp很容易突破跨域的限制。但是post跨域比较不容易。

为什么put和delete是幂等,而patch则是非幂等的? 重点来了,put 和 patch 都是用于更新数据资源的。 区别 在于

put 做更新操作时候是提交一整个更新后的实体(即全部),而不是需要修改的实体中的部分属性。当 URI 指向一个存在的资源,服务器要做的事就是查找并替换。

patch 做更新操作的时候是请求中的实体是一组将要应用到实体的更改(即部分),而不是像 PUT 请求那样是要替换旧资源的实体。可以理解为:PATCH 请求中的实体保存的是修改资源的指令,该指令指导服务器来对资源做出修改。

怎么理解呢?要明白并理解 RESTful 核心就是 面向资源编程,如下:

** PUT /flowers/1 #修改 序号为1的花(flowers) 的全部信息**

put【幂等】:用于更新资源,没有的话则执行创建操作。每次执行请求时都会先判断一下序号为1的花信息是否存在,不存在则创建,否则视为更新。很显然,请求携带的数据每次都是一样的,所以不论请求多少次,最终的结果都是后台存在这么一个资源(创建或更新)。

** PATCH /flowers/1/variety/lily/num/331 #假设url采用pathinfo模式,修改 序号为1的花(flowers) 的品种信息为百合,数量修改位431朵**

patch【非幂等】:用于更新资源,即数据实体的一部分属性,该数据必然存在,否则失去更新意义。每次执行请求时都会先判断一下序号为1的花信息是否存在,存在则更新数据信息,这里有两个属性要改,做的处理可能是这样的:品种(variety)直接改为百合(lily),而数量(num)假设原本存在100朵,我们要修改到 431 朵,所以增加 331 朵。很显然,多次请求时,会重复增加 331 ,属性数量就无法保持 431 。而 PUT 请求不论执行多少次,属性数量永远都是 431 , PATCH 则会改变,处于不可控的地位,所以说 PUT 方法是幂等的,而 PATCH 方法不是幂等的。

** DELETE /flowers/1 #删除 序号为1的花(flowers) 的全部信息**

delete【幂等】: 用于删除资源,会将资源从后台删除。每次执行请求时都会先判断一下序号为1的花信息是否存在,存在则删除,否则不做任何操作。很显然,无论执行多少次资源的状态总是被删除的,不会有其它副作用的影响。

内存泄漏问题?

一文帮你解决前端开发中的内存泄露问题open in new window

前端常见内存泄漏及解决方案 - 掘金open in new window

如何查找和解决前端内存泄漏问题? - 排查和分析技巧详解 - 掘金open in new window

前端开发中,使用base64图片的弊端是什么?

  1. 造成网页阻塞 弊端主要不在于 base64 编码后比原图要大,而是因为如果把大图片编码到 html / css 中,会造成后者体积明显增加,明显影响网页的打开速度。如果用外链图片的话,图片可以在页面渲染完成后继续加载,不会造成阻塞。如果 base64 是被编码到 css/js 中,是可以缓存的,因为 css/js 文件可以缓存。 假设base64编码后的字符串长度为256kb,用户的网速为每个连接32kb/s,而除去这个字符串外html大小仅为32kb,其中图片前后各16kb 那么不考虑其他资源加载的情况下,用户会先在半秒后看到这个图片上面的内容,然后花费8秒加载图片,再在半秒后看到完整的网页
  2. 有兼容性问题 使用 base64 的另外一个弊端是 IE 的兼容性问题。IE 8 以下不支持 data url,IE 8 开始支持 data url,却有大小限制,32k(未测试)。
  3. 用法上面的问题 还有一个问题是,如果构建工具比较落后(或者没有构建工具),手动插入 base64 是很蛋疼的,编辑器会卡到哭。

什么是Gzip?

gzip是一种数据的压缩格式,或者说是一种文件格式。

Gzip原本用户UNIX系统的文件压缩,后来逐渐成为Internet最主流的数据压缩格式。当用户访问我们的web站点时,服务器就将我们的网页文件进行压缩,将压缩后的文件传输到客户端,对于纯文本文件我们可以至少压缩到原大小的40%,这样大大提高了传输效率,页面便可更快的加载出来。

gzip是一种数据的压缩格式,也可以说是文件格式。linux系统该文件后缀为.gz 。使用gzip需要web容器,浏览器的支持。

  • 配置 js、text、json、css 这种纯文本进行压缩,效率极高
  • 压缩需要消化CPU,对于大文件(音乐/视频/图片)的压缩,会增加服务器压力。
Last Updated:
Contributors: xiaoyu
+ + + diff --git a/interview/statusCode.html b/interview/statusCode.html new file mode 100644 index 0000000..99601ff --- /dev/null +++ b/interview/statusCode.html @@ -0,0 +1,39 @@ + + + + + + + + + 项目中状态码的设置?设置在HTTP状态码还是返回业务状态码? | 🍰 小雨的学习记录 + + + + + +

项目中状态码的设置?设置在HTTP状态码还是返回业务状态码?

HTTP 状态码用于表示 Web 服务器对请求的处理情况,是 HTTP 协议规定的一种标准表示方式。而业务状态码是为了满足应用程序特定的业务逻辑需求,提供更具体和细粒度的响应状态。在设计接口时,我们应根据情况综合考虑使用HTTP状态码open in new window和业务状态码,以提供清晰、一致和易理解的接口响应。

HTTP 状态码

HTTP 状态码是由 HTTP 协议定义的,用于表示 Web 服务器对请求的响应状态,每一个状态码都有特定的含义。虽然开发者可以自定义 HTTP 状态码,但并不推荐这样做,因为这可能会引起混淆或者与将来的 HTTP 规范相冲突。HTTP 状态码的值是三位数字,其中第一位数字表示响应类别open in new window,目前有以下五个类别:

  • 1xx:表示请求已被接收,需要继续处理。
  • 2xx:表示请求已成功被服务器接收、理解、并接受。
  • 3xx:重定向,需要客户端采取进一步的操作才能完成请求。
  • 4xx:客户端错误,表示请求包含语法错误或者无法完成请求。
  • 5xx:服务器错误,服务器在处理请求的过程中发生了错误。

HTTP 状态码是一种标准的约定,用于表示请求的处理情况。客户端在接收到这些状态码后,可以根据不同的状态码采取相应的处理措施。如果需要表达更具体的状态信息,通常的做法是在 HTTP 响应 body 中返回业务状态码,而不是自定义 HTTP 状态码。业务状态码是由应用或服务自己定义的,可以根据实际的业务需求进行定义,比如表示用户不存在、商品库存不足、支付失败等状态。

业务状态码

业务状态码是在 HTTP 状态码之上,由应用程序自身定义的,以反映特定业务逻辑的状态。这些状态码可以针对不同的操作不同的条件提供更详细更具体的信息,以便客户端能够更好地理解和处理业务流程,根据不同的状态码采取相应的处理措施。 业务状态码通常定义在响应的数据(Response Body)中,与其他响应数据一起返回给客户端。拿登录接口举个例子,登录成功后,使用 HTTP 状态码200,业务状态码1(也可以约定其他的值)来表示,响应数据格式如下:

如果账号或者密码不正确,使用 HTTP 状态码200,业务状态码1001(业务状态码可以根据自己或团队整体情况而定)来表示,响应数据格式如下:

业务状态码是需要根据具体应用程序的需求和上下文定义的,可以根据业务逻辑和操作类型自定义状态码的值。另外,针对同一个应用来说,业务状态码类型要保持一致,统一使用整型或统一使用字符串,建议统一使用整型。

{"code":1, "data":null,"msg":""}
+
{"code":1001, "data":null,"msg":"账号或密码错误"}
+
Last Updated:
Contributors: xiaoyu
+ + + diff --git a/intro/asset.html b/intro/asset.html new file mode 100644 index 0000000..362edfe --- /dev/null +++ b/intro/asset.html @@ -0,0 +1,37 @@ + + + + + + + + + 学习资料 | 🍰 小雨的学习记录 + + + + + +

学习资料

基础入门

深入学习

  • 我的网页收藏
  • 除了广泛学习还需了解技术的底层原理,看博客文章,上手实践!
Last Updated:
Contributors: xiaoyu
+ + + diff --git a/intro/group.html b/intro/group.html new file mode 100644 index 0000000..d531be8 --- /dev/null +++ b/intro/group.html @@ -0,0 +1,37 @@ + + + + + + + + + 我的网页收藏 | 🍰 小雨的学习记录 + + + + + +

我的网页收藏

收藏夹栏

2024前端学习

ESLint 中文网
全网最热门的30个UI设计网站合集 - 知乎
TypeScript 阮一峰 | 阮一峰 TypeScript 教程
Less 快速入门 | Less.js 中文文档 - Less 中文网
Sass世界上最成熟、稳定和强大的CSS扩展语言 | Sass中文网
Introduction | eslint-plugin-vue
Next.js - React 应用开发框架 | Next.js中文网
Next.js by Vercel - The React Framework
代码随想录
Hello 算法
LangChain中文网:500页中文文档教程,助力大模型LLM应用开发从入门到精通
产品经理的人工智能学习库 - easyAI
Material UI: React components that implement Material Design
首页 | VuePress

工具

在线位图转矢量图;JPG转矢量;PNG转SVG,EPS,AI矢量格式 - AI改图神器
MC.JS

前端面试

web前端面试 - 面试官系列

京东
天猫
淘宝
百度
网址导航

Lenovo

Lenovo
丁香园_医疗领域的连接者_丁香园生物医药科技网
网易云游戏平台
在线阅读
Self-Paced Online Courses - MATLAB & Simulink
慕课网-程序员的梦工厂
2020全国大学生数学建模竞赛论文展示(C170) - 2020全国大学生数学建模竞赛论文展示 - 中国大学生在线
2021高教社杯全国大学生数学建模竞赛论文展示 — 中国大学生在线
Fotor Editor |Fotor - 照片在线编辑工具
下载 SQL Server Management Studio (SSMS) - SQL Server Management Studio (SSMS) | Microsoft Learn
ENSP安装教程【手把手教学】-云社区-华为云
基础配置命令 - FIT AP V200R019C00 命令参考 - 华为
网盾带你读好书之——“圣经”《TCP/IP详解卷一》
湖南中医药大学强智教务管理系统
SPSSPRO-免费专业的在线数据分析平台
Lenovo Support

ensp

二层交换机与路由器对接上网配置示例 - Sx300系列交换机 典型配置案例 - 华为
前言 - Sx300系列交换机 典型配置案例 - 华为
(49条消息) 第三讲:交换机原理及配置_yu.deqiang的博客-CSDN博客

前端

Vue.js - 渐进式 JavaScript 框架 | Vue.js
BootCDN - Bootstrap 中文网开源项目免费 CDN 加速服务
新人掌
菜鸟教程 - 学的不仅是技术,更是梦想!
Documentation - Apache ECharts
Element - 网站快速成型工具
免费API - 提供免费接口调用平台
fs 文件系统 | Node.js API 文档
Bootstrap中文网
PixiJS
学习 CSS 布局
Flexbox Froggy - 一个用来学CSS flexbox的游戏
Neumorphism/Soft UI CSS shadow generator
Front End | JavaScript Fun | 前端工坊
Share Icons & Symbols
uiGradients - Beautiful colored gradients
吼叫者.js - 现代网络音频Javascript库 - 金火工作室
iconfont-阿里巴巴矢量图标库
Freenom - 人人都熟悉的名字
上云精选_云服务器秒杀_开发者上云推荐-腾讯云
我的域名 - 域名注册 - 控制台
宝塔面板 - 简单好用的Linux/Windows服务器运维管理面板
批量操作 - DNSPod-免费智能DNS解析服务商-电信_网通_教育网,智能DNS
picwish.cn/upload?action=process&resourceId=f0a1381d-1f3e-40f9-92cb-07ead98bcfa6&url=https%3A%2F%2Fpicwishsz.oss-cn-shenzhen.aliyuncs.com%2Fefa%2Fefa87988-d6e0-4b2a-91f3-5b85bd6cd3ca.jpg%3FExpires%3D1685993374%26OSSAccessKeyId%3DLTAI5tGjJnh66c1txANiRBQN%26Signature%3Dq%2Ft5bBuiqlIPSDe8eEXEcKRsXbc%253D&filename=2625833729.png
超级简历WonderCV - HR推荐简历模板,智能简历制作工具,专业中英文简历模板免费下载
超级简历WonderCV - HR推荐简历模板,智能简历制作工具,专业中英文简历模板免费下载
开始使用 - Layui 文档
Empowering the world to develop technology through collective knowledge - Stack Overflow
Stack Overflow中文网
CSS list-style(列表样式)
w3school 在线教程
AlloyImageWEB图像处理
Axios 中文文档 | Axios 中文网 | Axios 是一个基于 promise 的网络请求库,可以用于浏览器和 node.js
工作台 - 1024Code
接口管理平台 - 1024Code
印记中文 - 深入挖掘国外前端新领域,为中国 Web 前端开发人员提供优质文档
Swiper中文网-轮播图幻灯片js插件,H5页面前端开发
Developer Roadmaps - roadmap.sh
Deno - 一个 安全的 JavaScript 和 TypeScript 运行时环境 | Deno 中文文档 | Deno 中文网
Touch Typing Practice Online
VeeValidate
Slidev
Astro
Bun — A fast all-in-one JavaScript runtime
Codepip | Learn to code by playing games
前端知识点汇总
灵题库-前端题库
【web前端开发】公号平台官网平台 | 一个专注于web前端开发领域技术学习与研究的平台
主页 - 飞书云文档
Stack Overflow - Where Developers Learn, Share, & Build Careers
node尚硅谷2023学习
Express 中文网
21信管-JavaEE
EJS -- 嵌入式 JavaScript 模板引擎 | EJS 中文文档
Mongoose v7.5.0: Models
首页 | 尚硅谷 Web 前端之 Webpack5 教程
Loaders | webpack 中文文档
工作台 - Gitee.com
Emoji大全 | Emoji表情符号词典 📓 | EmojiAll中文官方网站
Vue.js
React Native 中文网 · 使用React来编写原生应用的框架
蓝桥杯历年真题和模拟题练习登记
WebSocket 教程 - 阮一峰的网络日志
React
豆包 - 你的 AI 朋友
React 官方中文文档
华为人才在线
ws - npm
ws中文文档|ws js中文教程|解析 | npm中文文档
微信开放平台
开始 & 安装 | Mockjs中文文档-自建文档非官方!
第十四届蓝桥杯国赛出题 - 蓝桥云课
第十五届蓝桥杯(Web 应用开发)模拟赛 1 期-大学组 - 蓝桥云课
文心一言
Docs
一个 Vue 3 UI 框架 | Element Plus
Themes Gallery — Typora
Maven Repository: Search/Browse/Explore
煎饼搜音乐下载网 - 全网音乐免费下载 搜你妹音乐在线下载高清无损超清音乐网
Pinia | The intuitive store for Vue.js
首页 - FontAwesome 字体图标中文Icon
TypeScript 中文手册 - TypeScript 中文手册
Tags - vue-devtools - GitCode
Installation | Vue Devtools
小程序
GitHub - TencentCloudBase/Good-practice-tutorial-recommended: 优秀实践教程推荐
魏则西整个事件过程
Svg Wave - A free & beautiful gradient SVG wave Generator.
好玩的 CSS - 40 个有趣的 CSS 网站 - 掘金
光谱计划
登录
工作台 · 语雀
极简插件_Chrome扩展插件商店_优质crx应用下载
Vue3 + Vite + TypeScript + Element-Plus:从零到一构建企业级后台管理系统(前后端开源)_vue3 element plus admin-CSDN博客
Vite | 下一代的前端工具链
老陈打码 | 麒跃科技
5. 第一个3D案例—透视投影相机 | Three.js中文网
Three.js中文网
前端进阶之旅
HTTP 教程 | 菜鸟教程
比赛 - 蓝桥云课
D3 的 Observable |用于定制数据可视化的 JavaScript 库
labuladong 的算法笔记 | labuladong 的算法笔记
Mock.js文档备用站
👨‍💻 LeetCode 精选 TOP 面试题 - 力扣(LeetCode)
VueUse中文文档 | VueUse中文文档
一个 Vue 3 UI 框架 | Element Plus
Code Guide by @AlloyTeam
前端面试自我介绍怎么介绍? - 知乎
前端面试--自我介绍 - 简书
如何看待技术匠心? - Yee's Blog 个人生活网站分享 | 王大白
🤔为什么需要前端工程师? - 掘金
web前端简历个人技能该怎么写? - 知乎
前端合成图片插件html2canvas.js - 知乎
面试官:Vue要做权限管理该怎么做?控制到按钮级别的权限怎么做?-腾讯云开发者社区-腾讯云
2021 腾讯校招 + 后台开发面经(已 offer)_腾讯校招 后台开发-CSDN博客
职场密码-AI智能简历制作与优化个人简历模板自荐信一键生成
93 Beautiful CSS box-shadow examples - CSS Scan
Home | pinia-plugin-persistedstate
如何配置一份一目了然的招聘数据可视化看板? - 知乎
Login
2024实习生招聘-前端开发、UI开发_牛客
Web前端导航
(1 封私信) 一个优秀的前端工程师简历应该是怎样的? - 知乎
HTTP/3 Check
面试官:说说什么是进程?什么是线程?区别? | web前端面试 - 面试官系列
面试官:说说你对webpack的理解?解决了什么问题? | web前端面试 - 面试官系列
业务 - Tencent 腾讯
Aotu.io「凹凸实验室」

学习资源

超星
牛客网 - 找工作神器|笔试题库|面试经验|实习招聘内推,求职就业一站解决_牛客网
论文必备|34个国内外社会调查数据介绍及分享 - 知乎
头歌实践教学平台
HNUCM-OJ
首页 — 全国大学生TMT行业赛事
免费学习测试
中国大学生服务外包——创新创业大赛
题库 - 力扣 (LeetCode) 全球极客挚爱的技术成长平台
当当网
稀土掘金
第十三届蓝桥杯(Web 应用开发)线上模拟赛 - 蓝桥云课
Download
中国知网
首页 - 湖南省智慧资助服务平台
单词森林
听风听雨 · 来自旷野的舒适纯音_精选集_乐库频道_酷狗网
前端

开源项目

vue-element-admin
Vue2-Admin: Vue2-Admin 是一个后台管理系统解决方案,采用前后端分离技术开发。它使用了最新的技术栈,提供了丰富的功能组件,希望本项目可以帮助到您。
Fresh Background Gradients | WebGradients.com 💎
Vue3.3 + Vite+ Element-Plus + TypeScript 从0到1搭建企业级后台管理系统(前后端开源) - 掘金

鸿蒙开发

华为HarmonyOS智能终端操作系统官网 | 应用设备分布式开发者生态
HarmonyOS应用开发官网 - 华为HarmonyOS打造全场景新服务
创建自定义组件-自定义组件-基本语法-学习ArkTS语言-入门-HarmonyOS应用开发
应用开发导读

厂子

百度校园招聘
字节跳动
首页 | 美团招聘
前端开发实习生-飞书办公套件 - 字节跳动
前端开发实习生-飞书 - 字节跳动
前端开发实习生-商业产品与技术 - 字节跳动
知乎招聘 - 智者四海(北京)技术有限公司
小红书校园招聘
校园招聘 | 美团招聘
职位详情 | 美团招聘
前端开发实习生-抖音电商 - 字节跳动
阿里巴巴控股集团校园招聘
首页 | 腾讯招聘
HTTP/3 Check - www.hnucm.edu.cn
HTTP/3 Check
高德地图招聘官网
高德地图招聘官网
投递记录
字节跳动内推
滴滴 -实习生招聘
滴滴 -实习生招聘

Last Updated:
Contributors: xiaoyu
+ + + diff --git a/intro/index.html b/intro/index.html new file mode 100644 index 0000000..d87ab7a --- /dev/null +++ b/intro/index.html @@ -0,0 +1,37 @@ + + + + + + + + + 学习路线 | 🍰 小雨的学习记录 + + + + + + + + + diff --git a/intro/learn.html b/intro/learn.html new file mode 100644 index 0000000..3126bcc --- /dev/null +++ b/intro/learn.html @@ -0,0 +1,37 @@ + + + + + + + + + 我能学到什么 | 🍰 小雨的学习记录 + + + + + + + + + diff --git a/intro/pre.html b/intro/pre.html new file mode 100644 index 0000000..6d92e19 --- /dev/null +++ b/intro/pre.html @@ -0,0 +1,37 @@ + + + + + + + + + 前序 | 🍰 小雨的学习记录 + + + + + +

前序

  • 入门前端真的特别容易,但是想要深入发展,你必须得沉下心来虚心学习。前端的内容日新月异,有的时候你会感觉自己跟不上技术发展的步伐,但是你又想着啥都学才能赶上。其实不是这样,你只要沉下心来好好专研一个方面的了解底层原理打扎实基础。你的学习过程会变得非常简单!

  • 学习是自己的事情,有时候鞭策自己好好学习,一段时间后你会发现超越了许多同学……

  • 选好自己的方向很重要,越早越好。当我准备自己的实习才发现,我的信息有多么的闭塞,你刚找实习了,别人就早已经有了三段实习经历。我想我要是大二就能有段实习那该多好,我能有更多的认识。

  • 这里记录了我学习的一些记录,一般是我不是很熟悉、或者经常碰到没记着、我觉得重要的…… 关于基础的知识我也提供了一些链接,也会在该站点展示。

  • 后续将会增加一些插件,提供更好的体验(也包括提交评论)……

Last Updated:
Contributors: xiaoyu
+ + + diff --git "a/pdfs/\347\274\226\350\257\221\345\216\237\347\220\206\345\257\274\350\257\273.pdf" "b/pdfs/\347\274\226\350\257\221\345\216\237\347\220\206\345\257\274\350\257\273.pdf" new file mode 100644 index 0000000..33ac6fb Binary files /dev/null and "b/pdfs/\347\274\226\350\257\221\345\216\237\347\220\206\345\257\274\350\257\273.pdf" differ diff --git a/project/index.html b/project/index.html new file mode 100644 index 0000000..eb7e688 --- /dev/null +++ b/project/index.html @@ -0,0 +1,37 @@ + + + + + + + + + 介绍 | 🍰 小雨的学习记录 + + + + + + + + + diff --git a/project/react-cli.html b/project/react-cli.html new file mode 100644 index 0000000..cd276ea --- /dev/null +++ b/project/react-cli.html @@ -0,0 +1,878 @@ + + + + + + + + + React 脚手架 | 🍰 小雨的学习记录 + + + + + +

React 脚手架

开发模式配置

// webpack.dev.js
+const path = require("path");
+const ESLintWebpackPlugin = require("eslint-webpack-plugin");
+const HtmlWebpackPlugin = require("html-webpack-plugin");
+const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
+const CopyPlugin = require("copy-webpack-plugin");
+
+const getStyleLoaders = (preProcessor) => {
+  return [
+    "style-loader",
+    "css-loader",
+    {
+      loader: "postcss-loader",
+      options: {
+        postcssOptions: {
+          plugins: [
+            "postcss-preset-env", // 能解决大多数样式兼容性问题
+          ],
+        },
+      },
+    },
+    preProcessor,
+  ].filter(Boolean);
+};
+
+module.exports = {
+  entry: "./src/main.js",
+  output: {
+    path: undefined,
+    filename: "static/js/[name].js",
+    chunkFilename: "static/js/[name].chunk.js",
+    assetModuleFilename: "static/js/[hash:10][ext][query]",
+  },
+  module: {
+    rules: [
+      {
+        oneOf: [
+          {
+            // 用来匹配 .css 结尾的文件
+            test: /\.css$/,
+            // use 数组里面 Loader 执行顺序是从右到左
+            use: getStyleLoaders(),
+          },
+          {
+            test: /\.less$/,
+            use: getStyleLoaders("less-loader"),
+          },
+          {
+            test: /\.s[ac]ss$/,
+            use: getStyleLoaders("sass-loader"),
+          },
+          {
+            test: /\.styl$/,
+            use: getStyleLoaders("stylus-loader"),
+          },
+          {
+            test: /\.(png|jpe?g|gif|svg)$/,
+            type: "asset",
+            parser: {
+              dataUrlCondition: {
+                maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
+              },
+            },
+          },
+          {
+            test: /\.(ttf|woff2?)$/,
+            type: "asset/resource",
+          },
+          {
+            test: /\.(jsx|js)$/,
+            include: path.resolve(__dirname, "../src"),
+            loader: "babel-loader",
+            options: {
+              cacheDirectory: true,
+              cacheCompression: false,
+              plugins: [
+                // "@babel/plugin-transform-runtime", // presets中包含了
+                "react-refresh/babel", // 开启js的HMR功能
+              ],
+            },
+          },
+        ],
+      },
+    ],
+  },
+  plugins: [
+    new ESLintWebpackPlugin({
+      context: path.resolve(__dirname, "../src"),
+      exclude: "node_modules",
+      cache: true,
+      cacheLocation: path.resolve(
+        __dirname,
+        "../node_modules/.cache/.eslintcache"
+      ),
+    }),
+    new HtmlWebpackPlugin({
+      template: path.resolve(__dirname, "../public/index.html"),
+    }),
+    new ReactRefreshWebpackPlugin(), // 解决js的HMR功能运行时全局变量的问题
+    // 将public下面的资源复制到dist目录去(除了index.html)
+    new CopyPlugin({
+      patterns: [
+        {
+          from: path.resolve(__dirname, "../public"),
+          to: path.resolve(__dirname, "../dist"),
+          toType: "dir",
+          noErrorOnMissing: true, // 不生成错误
+          globOptions: {
+            // 忽略文件
+            ignore: ["**/index.html"],
+          },
+          info: {
+            // 跳过terser压缩js
+            minimized: true,
+          },
+        },
+      ],
+    }),
+  ],
+  optimization: {
+    splitChunks: {
+      chunks: "all",
+    },
+    runtimeChunk: {
+      name: (entrypoint) => `runtime~${entrypoint.name}`,
+    },
+  },
+  resolve: {
+    extensions: [".jsx", ".js", ".json"], // 自动补全文件扩展名,让jsx可以使用
+  },
+  devServer: {
+    open: true,
+    host: "localhost",
+    port: 3000,
+    hot: true,
+    compress: true,
+    historyApiFallback: true, // 解决react-router刷新404问题
+  },
+  mode: "development",
+  devtool: "cheap-module-source-map",
+};
+

生产模式配置

// webpack.prod.js
+const path = require("path");
+const ESLintWebpackPlugin = require("eslint-webpack-plugin");
+const HtmlWebpackPlugin = require("html-webpack-plugin");
+const MiniCssExtractPlugin = require("mini-css-extract-plugin");
+const TerserWebpackPlugin = require("terser-webpack-plugin");
+const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
+const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
+const CopyPlugin = require("copy-webpack-plugin");
+
+const getStyleLoaders = (preProcessor) => {
+  return [
+    MiniCssExtractPlugin.loader,
+    "css-loader",
+    {
+      loader: "postcss-loader",
+      options: {
+        postcssOptions: {
+          plugins: [
+            "postcss-preset-env", // 能解决大多数样式兼容性问题
+          ],
+        },
+      },
+    },
+    preProcessor,
+  ].filter(Boolean);
+};
+
+module.exports = {
+  entry: "./src/main.js",
+  output: {
+    path: path.resolve(__dirname, "../dist"),
+    filename: "static/js/[name].[contenthash:10].js",
+    chunkFilename: "static/js/[name].[contenthash:10].chunk.js",
+    assetModuleFilename: "static/js/[hash:10][ext][query]",
+    clean: true,
+  },
+  module: {
+    rules: [
+      {
+        oneOf: [
+          {
+            // 用来匹配 .css 结尾的文件
+            test: /\.css$/,
+            // use 数组里面 Loader 执行顺序是从右到左
+            use: getStyleLoaders(),
+          },
+          {
+            test: /\.less$/,
+            use: getStyleLoaders("less-loader"),
+          },
+          {
+            test: /\.s[ac]ss$/,
+            use: getStyleLoaders("sass-loader"),
+          },
+          {
+            test: /\.styl$/,
+            use: getStyleLoaders("stylus-loader"),
+          },
+          {
+            test: /\.(png|jpe?g|gif|svg)$/,
+            type: "asset",
+            parser: {
+              dataUrlCondition: {
+                maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
+              },
+            },
+          },
+          {
+            test: /\.(ttf|woff2?)$/,
+            type: "asset/resource",
+          },
+          {
+            test: /\.(jsx|js)$/,
+            include: path.resolve(__dirname, "../src"),
+            loader: "babel-loader",
+            options: {
+              cacheDirectory: true,
+              cacheCompression: false,
+              plugins: [
+                // "@babel/plugin-transform-runtime" // presets中包含了
+              ],
+            },
+          },
+        ],
+      },
+    ],
+  },
+  plugins: [
+    new ESLintWebpackPlugin({
+      context: path.resolve(__dirname, "../src"),
+      exclude: "node_modules",
+      cache: true,
+      cacheLocation: path.resolve(
+        __dirname,
+        "../node_modules/.cache/.eslintcache"
+      ),
+    }),
+    new HtmlWebpackPlugin({
+      template: path.resolve(__dirname, "../public/index.html"),
+    }),
+    new MiniCssExtractPlugin({
+      filename: "static/css/[name].[contenthash:10].css",
+      chunkFilename: "static/css/[name].[contenthash:10].chunk.css",
+    }),
+    // 将public下面的资源复制到dist目录去(除了index.html)
+    new CopyPlugin({
+      patterns: [
+        {
+          from: path.resolve(__dirname, "../public"),
+          to: path.resolve(__dirname, "../dist"),
+          toType: "dir",
+          noErrorOnMissing: true, // 不生成错误
+          globOptions: {
+            // 忽略文件
+            ignore: ["**/index.html"],
+          },
+          info: {
+            // 跳过terser压缩js
+            minimized: true,
+          },
+        },
+      ],
+    }),
+  ],
+  optimization: {
+    // 压缩的操作
+    minimizer: [
+      new CssMinimizerPlugin(),
+      new TerserWebpackPlugin(),
+      new ImageMinimizerPlugin({
+        minimizer: {
+          implementation: ImageMinimizerPlugin.imageminGenerate,
+          options: {
+            plugins: [
+              ["gifsicle", { interlaced: true }],
+              ["jpegtran", { progressive: true }],
+              ["optipng", { optimizationLevel: 5 }],
+              [
+                "svgo",
+                {
+                  plugins: [
+                    "preset-default",
+                    "prefixIds",
+                    {
+                      name: "sortAttrs",
+                      params: {
+                        xmlnsOrder: "alphabetical",
+                      },
+                    },
+                  ],
+                },
+              ],
+            ],
+          },
+        },
+      }),
+    ],
+    splitChunks: {
+      chunks: "all",
+    },
+    runtimeChunk: {
+      name: (entrypoint) => `runtime~${entrypoint.name}`,
+    },
+  },
+  resolve: {
+    extensions: [".jsx", ".js", ".json"],
+  },
+  mode: "production",
+  devtool: "source-map",
+};
+

其他配置

  • package.json
{
+  "name": "react-cli",
+  "version": "1.0.0",
+  "description": "",
+  "main": "index.js",
+  "scripts": {
+    "start": "npm run dev",
+    "dev": "cross-env NODE_ENV=development webpack serve --config ./config/webpack.dev.js",
+    "build": "cross-env NODE_ENV=production webpack --config ./config/webpack.prod.js"
+  },
+  "keywords": [],
+  "author": "",
+  "license": "ISC",
+  "devDependencies": {
+    "@babel/core": "^7.17.10",
+    "@pmmmwh/react-refresh-webpack-plugin": "^0.5.5",
+    "babel-loader": "^8.2.5",
+    "babel-preset-react-app": "^10.0.1",
+    "copy-webpack-plugin": "^10.2.4",
+    "cross-env": "^7.0.3",
+    "css-loader": "^6.7.1",
+    "css-minimizer-webpack-plugin": "^3.4.1",
+    "eslint-config-react-app": "^7.0.1",
+    "eslint-webpack-plugin": "^3.1.1",
+    "html-webpack-plugin": "^5.5.0",
+    "image-minimizer-webpack-plugin": "^3.2.3",
+    "imagemin": "^8.0.1",
+    "imagemin-gifsicle": "^7.0.0",
+    "imagemin-jpegtran": "^7.0.0",
+    "imagemin-optipng": "^8.0.0",
+    "imagemin-svgo": "^10.0.1",
+    "less-loader": "^10.2.0",
+    "mini-css-extract-plugin": "^2.6.0",
+    "postcss-loader": "^6.2.1",
+    "postcss-preset-env": "^7.5.0",
+    "react-refresh": "^0.13.0",
+    "sass-loader": "^12.6.0",
+    "style-loader": "^3.3.1",
+    "stylus-loader": "^6.2.0",
+    "webpack": "^5.72.0",
+    "webpack-cli": "^4.9.2",
+    "webpack-dev-server": "^4.9.0"
+  },
+  "dependencies": {
+    "antd": "^4.20.2",
+    "react": "^18.1.0",
+    "react-dom": "^18.1.0",
+    "react-router-dom": "^6.3.0"
+  },
+  "browserslist": ["last 2 version", "> 1%", "not dead"]
+}
+
  • .eslintrc.js
module.exports = {
+  extends: ["react-app"], // 继承 react 官方规则
+  parserOptions: {
+    babelOptions: {
+      presets: [
+        // 解决页面报错问题
+        ["babel-preset-react-app", false],
+        "babel-preset-react-app/prod",
+      ],
+    },
+  },
+};
+
  • babel.config.js
module.exports = {
+  // 使用react官方规则
+  presets: ["react-app"],
+};
+

合并开发和生产配置

  • webpack.config.js
const path = require("path");
+const ESLintWebpackPlugin = require("eslint-webpack-plugin");
+const HtmlWebpackPlugin = require("html-webpack-plugin");
+const MiniCssExtractPlugin = require("mini-css-extract-plugin");
+const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
+const TerserWebpackPlugin = require("terser-webpack-plugin");
+const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
+const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
+
+// 需要通过 cross-env 定义环境变量
+const isProduction = process.env.NODE_ENV === "production";
+
+const getStyleLoaders = (preProcessor) => {
+  return [
+    isProduction ? MiniCssExtractPlugin.loader : "style-loader",
+    "css-loader",
+    {
+      loader: "postcss-loader",
+      options: {
+        postcssOptions: {
+          plugins: [
+            "postcss-preset-env", // 能解决大多数样式兼容性问题
+          ],
+        },
+      },
+    },
+    preProcessor,
+  ].filter(Boolean);
+};
+
+module.exports = {
+  entry: "./src/main.js",
+  output: {
+    path: isProduction ? path.resolve(__dirname, "../dist") : undefined,
+    filename: isProduction
+      ? "static/js/[name].[contenthash:10].js"
+      : "static/js/[name].js",
+    chunkFilename: isProduction
+      ? "static/js/[name].[contenthash:10].chunk.js"
+      : "static/js/[name].chunk.js",
+    assetModuleFilename: "static/js/[hash:10][ext][query]",
+    clean: true,
+  },
+  module: {
+    rules: [
+      {
+        oneOf: [
+          {
+            // 用来匹配 .css 结尾的文件
+            test: /\.css$/,
+            // use 数组里面 Loader 执行顺序是从右到左
+            use: getStyleLoaders(),
+          },
+          {
+            test: /\.less$/,
+            use: getStyleLoaders("less-loader"),
+          },
+          {
+            test: /\.s[ac]ss$/,
+            use: getStyleLoaders("sass-loader"),
+          },
+          {
+            test: /\.styl$/,
+            use: getStyleLoaders("stylus-loader"),
+          },
+          {
+            test: /\.(png|jpe?g|gif|svg)$/,
+            type: "asset",
+            parser: {
+              dataUrlCondition: {
+                maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
+              },
+            },
+          },
+          {
+            test: /\.(ttf|woff2?)$/,
+            type: "asset/resource",
+          },
+          {
+            test: /\.(jsx|js)$/,
+            include: path.resolve(__dirname, "../src"),
+            loader: "babel-loader",
+            options: {
+              cacheDirectory: true, // 开启babel编译缓存
+              cacheCompression: false, // 缓存文件不要压缩
+              plugins: [
+                // "@babel/plugin-transform-runtime",  // presets中包含了
+                !isProduction && "react-refresh/babel",
+              ].filter(Boolean),
+            },
+          },
+        ],
+      },
+    ],
+  },
+  plugins: [
+    new ESLintWebpackPlugin({
+      extensions: [".js", ".jsx"],
+      context: path.resolve(__dirname, "../src"),
+      exclude: "node_modules",
+      cache: true,
+      cacheLocation: path.resolve(
+        __dirname,
+        "../node_modules/.cache/.eslintcache"
+      ),
+    }),
+    new HtmlWebpackPlugin({
+      template: path.resolve(__dirname, "../public/index.html"),
+    }),
+    isProduction &&
+      new MiniCssExtractPlugin({
+        filename: "static/css/[name].[contenthash:10].css",
+        chunkFilename: "static/css/[name].[contenthash:10].chunk.css",
+      }),
+    !isProduction && new ReactRefreshWebpackPlugin(),
+  ].filter(Boolean),
+  optimization: {
+    minimize: isProduction,
+    // 压缩的操作
+    minimizer: [
+      // 压缩css
+      new CssMinimizerPlugin(),
+      // 压缩js
+      new TerserWebpackPlugin(),
+      // 压缩图片
+      new ImageMinimizerPlugin({
+        minimizer: {
+          implementation: ImageMinimizerPlugin.imageminGenerate,
+          options: {
+            plugins: [
+              ["gifsicle", { interlaced: true }],
+              ["jpegtran", { progressive: true }],
+              ["optipng", { optimizationLevel: 5 }],
+              [
+                "svgo",
+                {
+                  plugins: [
+                    "preset-default",
+                    "prefixIds",
+                    {
+                      name: "sortAttrs",
+                      params: {
+                        xmlnsOrder: "alphabetical",
+                      },
+                    },
+                  ],
+                },
+              ],
+            ],
+          },
+        },
+      }),
+    ],
+    // 代码分割配置
+    splitChunks: {
+      chunks: "all",
+      // 其他都用默认值
+    },
+    runtimeChunk: {
+      name: (entrypoint) => `runtime~${entrypoint.name}`,
+    },
+  },
+  resolve: {
+    extensions: [".jsx", ".js", ".json"],
+  },
+  devServer: {
+    open: true,
+    host: "localhost",
+    port: 3000,
+    hot: true,
+    compress: true,
+    historyApiFallback: true,
+  },
+  mode: isProduction ? "production" : "development",
+  devtool: isProduction ? "source-map" : "cheap-module-source-map",
+};
+
  • 修改运行指令 package.json
{
+  "name": "react-cli",
+  "version": "1.0.0",
+  "description": "",
+  "main": "index.js",
+  "scripts": {
+    "start": "npm run dev",
+    "dev": "cross-env NODE_ENV=development webpack serve --config ./config/webpack.config.js",
+    "build": "cross-env NODE_ENV=production webpack --config ./config/webpack.config.js"
+  },
+  "keywords": [],
+  "author": "",
+  "license": "ISC",
+  "devDependencies": {
+    "@babel/core": "^7.17.10",
+    "@pmmmwh/react-refresh-webpack-plugin": "^0.5.5",
+    "babel-loader": "^8.2.5",
+    "babel-preset-react-app": "^10.0.1",
+    "cross-env": "^7.0.3",
+    "css-loader": "^6.7.1",
+    "css-minimizer-webpack-plugin": "^3.4.1",
+    "eslint-config-react-app": "^7.0.1",
+    "eslint-webpack-plugin": "^3.1.1",
+    "html-webpack-plugin": "^5.5.0",
+    "image-minimizer-webpack-plugin": "^3.2.3",
+    "imagemin": "^8.0.1",
+    "imagemin-gifsicle": "^7.0.0",
+    "imagemin-jpegtran": "^7.0.0",
+    "imagemin-optipng": "^8.0.0",
+    "imagemin-svgo": "^10.0.1",
+    "less-loader": "^10.2.0",
+    "mini-css-extract-plugin": "^2.6.0",
+    "react-refresh": "^0.13.0",
+    "sass-loader": "^12.6.0",
+    "style-loader": "^3.3.1",
+    "stylus-loader": "^6.2.0",
+    "webpack": "^5.72.0",
+    "webpack-cli": "^4.9.2",
+    "webpack-dev-server": "^4.9.0"
+  },
+  "dependencies": {
+    "react": "^18.1.0",
+    "react-dom": "^18.1.0",
+    "react-router-dom": "^6.3.0"
+  },
+  "browserslist": ["last 2 version", "> 1%", "not dead"]
+}
+







 
 






































优化配置

const path = require("path");
+const ESLintWebpackPlugin = require("eslint-webpack-plugin");
+const HtmlWebpackPlugin = require("html-webpack-plugin");
+const MiniCssExtractPlugin = require("mini-css-extract-plugin");
+const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
+const TerserWebpackPlugin = require("terser-webpack-plugin");
+const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
+const ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
+const CopyPlugin = require("copy-webpack-plugin");
+
+const isProduction = process.env.NODE_ENV === "production";
+
+const getStyleLoaders = (preProcessor) => {
+  return [
+    isProduction ? MiniCssExtractPlugin.loader : "style-loader",
+    "css-loader",
+    {
+      loader: "postcss-loader",
+      options: {
+        postcssOptions: {
+          plugins: [
+            "postcss-preset-env",
+          ],
+        },
+      },
+    },
+    preProcessor && {
+      loader: preProcessor,
+      options:
+        preProcessor === "less-loader"
+          ? {
+              // antd的自定义主题
+              lessOptions: {
+                modifyVars: {
+                  // 其他主题色:https://ant.design/docs/react/customize-theme-cn
+                  "@primary-color": "#1DA57A", // 全局主色
+                },
+                javascriptEnabled: true,
+              },
+            }
+          : {},
+    },
+  ].filter(Boolean);
+};
+
+module.exports = {
+  entry: "./src/main.js",
+  output: {
+    path: isProduction ? path.resolve(__dirname, "../dist") : undefined,
+    filename: isProduction
+      ? "static/js/[name].[contenthash:10].js"
+      : "static/js/[name].js",
+    chunkFilename: isProduction
+      ? "static/js/[name].[contenthash:10].chunk.js"
+      : "static/js/[name].chunk.js",
+    assetModuleFilename: "static/js/[hash:10][ext][query]",
+    clean: true,
+  },
+  module: {
+    rules: [
+      {
+        oneOf: [
+          {
+            test: /\.css$/,
+            use: getStyleLoaders(),
+          },
+          {
+            test: /\.less$/,
+            use: getStyleLoaders("less-loader"),
+          },
+          {
+            test: /\.s[ac]ss$/,
+            use: getStyleLoaders("sass-loader"),
+          },
+          {
+            test: /\.styl$/,
+            use: getStyleLoaders("stylus-loader"),
+          },
+          {
+            test: /\.(png|jpe?g|gif|svg)$/,
+            type: "asset",
+            parser: {
+              dataUrlCondition: {
+                maxSize: 10 * 1024,
+              },
+            },
+          },
+          {
+            test: /\.(ttf|woff2?)$/,
+            type: "asset/resource",
+          },
+          {
+            test: /\.(jsx|js)$/,
+            include: path.resolve(__dirname, "../src"),
+            loader: "babel-loader",
+            options: {
+              cacheDirectory: true,
+              cacheCompression: false,
+              plugins: [
+                // "@babel/plugin-transform-runtime",  // presets中包含了
+                !isProduction && "react-refresh/babel",
+              ].filter(Boolean),
+            },
+          },
+        ],
+      },
+    ],
+  },
+  plugins: [
+    new ESLintWebpackPlugin({
+      extensions: [".js", ".jsx"],
+      context: path.resolve(__dirname, "../src"),
+      exclude: "node_modules",
+      cache: true,
+      cacheLocation: path.resolve(
+        __dirname,
+        "../node_modules/.cache/.eslintcache"
+      ),
+    }),
+    new HtmlWebpackPlugin({
+      template: path.resolve(__dirname, "../public/index.html"),
+    }),
+    isProduction &&
+      new MiniCssExtractPlugin({
+        filename: "static/css/[name].[contenthash:10].css",
+        chunkFilename: "static/css/[name].[contenthash:10].chunk.css",
+      }),
+    !isProduction && new ReactRefreshWebpackPlugin(),
+    // 将public下面的资源复制到dist目录去(除了index.html)
+    new CopyPlugin({
+      patterns: [
+        {
+          from: path.resolve(__dirname, "../public"),
+          to: path.resolve(__dirname, "../dist"),
+          toType: "dir",
+          noErrorOnMissing: true, // 不生成错误
+          globOptions: {
+            // 忽略文件
+            ignore: ["**/index.html"],
+          },
+          info: {
+            // 跳过terser压缩js
+            minimized: true,
+          },
+        },
+      ],
+    }),
+  ].filter(Boolean),
+  optimization: {
+    minimize: isProduction,
+    // 压缩的操作
+    minimizer: [
+      // 压缩css
+      new CssMinimizerPlugin(),
+      // 压缩js
+      new TerserWebpackPlugin(),
+      // 压缩图片
+      new ImageMinimizerPlugin({
+        minimizer: {
+          implementation: ImageMinimizerPlugin.imageminGenerate,
+          options: {
+            plugins: [
+              ["gifsicle", { interlaced: true }],
+              ["jpegtran", { progressive: true }],
+              ["optipng", { optimizationLevel: 5 }],
+              [
+                "svgo",
+                {
+                  plugins: [
+                    "preset-default",
+                    "prefixIds",
+                    {
+                      name: "sortAttrs",
+                      params: {
+                        xmlnsOrder: "alphabetical",
+                      },
+                    },
+                  ],
+                },
+              ],
+            ],
+          },
+        },
+      }),
+    ],
+    // 代码分割配置
+    splitChunks: {
+      chunks: "all",
+      cacheGroups: {
+        // layouts通常是admin项目的主体布局组件,所有路由组件都要使用的
+        // 可以单独打包,从而复用
+        // 如果项目中没有,请删除
+        layouts: {
+          name: "layouts",
+          test: path.resolve(__dirname, "../src/layouts"),
+          priority: 40,
+        },
+        // 如果项目中使用antd,此时将所有node_modules打包在一起,那么打包输出文件会比较大。
+        // 所以我们将node_modules中比较大的模块单独打包,从而并行加载速度更好
+        // 如果项目中没有,请删除
+        antd: {
+          name: "chunk-antd",
+          test: /[\\/]node_modules[\\/]antd(.*)/,
+          priority: 30,
+        },
+        // 将react相关的库单独打包,减少node_modules的chunk体积。
+        react: {
+          name: "react",
+          test: /[\\/]node_modules[\\/]react(.*)?[\\/]/,
+          chunks: "initial",
+          priority: 20,
+        },
+        libs: {
+          name: "chunk-libs",
+          test: /[\\/]node_modules[\\/]/,
+          priority: 10, // 权重最低,优先考虑前面内容
+          chunks: "initial",
+        },
+      },
+    },
+    runtimeChunk: {
+      name: (entrypoint) => `runtime~${entrypoint.name}`,
+    },
+  },
+  resolve: {
+    extensions: [".jsx", ".js", ".json"],
+  },
+  devServer: {
+    open: true,
+    host: "localhost",
+    port: 3000,
+    hot: true,
+    compress: true,
+    historyApiFallback: true,
+  },
+  mode: isProduction ? "production" : "development",
+  devtool: isProduction ? "source-map" : "cheap-module-source-map",
+  performance: false, // 关闭性能分析,提示速度
+};
+


























 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 


















































































































































 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 


















 

Last Updated:
Contributors: xiaoyu
+ + + diff --git a/project/summary.html b/project/summary.html new file mode 100644 index 0000000..795db38 --- /dev/null +++ b/project/summary.html @@ -0,0 +1,37 @@ + + + + + + + + + 总结 | 🍰 小雨的学习记录 + + + + + + + + + diff --git a/project/vue-cli.html b/project/vue-cli.html new file mode 100644 index 0000000..345f539 --- /dev/null +++ b/project/vue-cli.html @@ -0,0 +1,883 @@ + + + + + + + + + Vue 脚手架 | 🍰 小雨的学习记录 + + + + + +

Vue 脚手架

开发模式配置

// webpack.dev.js
+const path = require("path");
+const ESLintWebpackPlugin = require("eslint-webpack-plugin");
+const HtmlWebpackPlugin = require("html-webpack-plugin");
+const { VueLoaderPlugin } = require("vue-loader");
+const { DefinePlugin } = require("webpack");
+const CopyPlugin = require("copy-webpack-plugin");
+
+const getStyleLoaders = (preProcessor) => {
+  return [
+    "vue-style-loader",
+    "css-loader",
+    {
+      loader: "postcss-loader",
+      options: {
+        postcssOptions: {
+          plugins: [
+            "postcss-preset-env", // 能解决大多数样式兼容性问题
+          ],
+        },
+      },
+    },
+    preProcessor,
+  ].filter(Boolean);
+};
+
+module.exports = {
+  entry: "./src/main.js",
+  output: {
+    path: undefined,
+    filename: "static/js/[name].js",
+    chunkFilename: "static/js/[name].chunk.js",
+    assetModuleFilename: "static/js/[hash:10][ext][query]",
+  },
+  module: {
+    rules: [
+      {
+        // 用来匹配 .css 结尾的文件
+        test: /\.css$/,
+        // use 数组里面 Loader 执行顺序是从右到左
+        use: getStyleLoaders(),
+      },
+      {
+        test: /\.less$/,
+        use: getStyleLoaders("less-loader"),
+      },
+      {
+        test: /\.s[ac]ss$/,
+        use: getStyleLoaders("sass-loader"),
+      },
+      {
+        test: /\.styl$/,
+        use: getStyleLoaders("stylus-loader"),
+      },
+      {
+        test: /\.(png|jpe?g|gif|svg)$/,
+        type: "asset",
+        parser: {
+          dataUrlCondition: {
+            maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
+          },
+        },
+      },
+      {
+        test: /\.(ttf|woff2?)$/,
+        type: "asset/resource",
+      },
+      {
+        test: /\.(jsx|js)$/,
+        include: path.resolve(__dirname, "../src"),
+        loader: "babel-loader",
+        options: {
+          cacheDirectory: true,
+          cacheCompression: false,
+          plugins: [
+            // "@babel/plugin-transform-runtime" // presets中包含了
+          ],
+        },
+      },
+      // vue-loader不支持oneOf
+      {
+        test: /\.vue$/,
+        loader: "vue-loader", // 内部会给vue文件注入HMR功能代码
+        options: {
+          // 开启缓存
+          cacheDirectory: path.resolve(
+            __dirname,
+            "node_modules/.cache/vue-loader"
+          ),
+        },
+      },
+    ],
+  },
+  plugins: [
+    new ESLintWebpackPlugin({
+      context: path.resolve(__dirname, "../src"),
+      exclude: "node_modules",
+      cache: true,
+      cacheLocation: path.resolve(
+        __dirname,
+        "../node_modules/.cache/.eslintcache"
+      ),
+    }),
+    new HtmlWebpackPlugin({
+      template: path.resolve(__dirname, "../public/index.html"),
+    }),
+    new CopyPlugin({
+      patterns: [
+        {
+          from: path.resolve(__dirname, "../public"),
+          to: path.resolve(__dirname, "../dist"),
+          toType: "dir",
+          noErrorOnMissing: true,
+          globOptions: {
+            ignore: ["**/index.html"],
+          },
+          info: {
+            minimized: true,
+          },
+        },
+      ],
+    }),
+    new VueLoaderPlugin(),
+    // 解决页面警告
+    new DefinePlugin({
+      __VUE_OPTIONS_API__: "true",
+      __VUE_PROD_DEVTOOLS__: "false",
+    }),
+  ],
+  optimization: {
+    splitChunks: {
+      chunks: "all",
+    },
+    runtimeChunk: {
+      name: (entrypoint) => `runtime~${entrypoint.name}`,
+    },
+  },
+  resolve: {
+    extensions: [".vue", ".js", ".json"], // 自动补全文件扩展名,让vue可以使用
+  },
+  devServer: {
+    open: true,
+    host: "localhost",
+    port: 3000,
+    hot: true,
+    compress: true,
+    historyApiFallback: true, // 解决vue-router刷新404问题
+  },
+  mode: "development",
+  devtool: "cheap-module-source-map",
+};
+

生产模式配置

// webpack.prod.js
+const path = require("path");
+const ESLintWebpackPlugin = require("eslint-webpack-plugin");
+const HtmlWebpackPlugin = require("html-webpack-plugin");
+const MiniCssExtractPlugin = require("mini-css-extract-plugin");
+const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
+const TerserWebpackPlugin = require("terser-webpack-plugin");
+const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
+const { VueLoaderPlugin } = require("vue-loader");
+const { DefinePlugin } = require("webpack");
+
+const getStyleLoaders = (preProcessor) => {
+  return [
+    MiniCssExtractPlugin.loader,
+    "css-loader",
+    {
+      loader: "postcss-loader",
+      options: {
+        postcssOptions: {
+          plugins: [
+            "postcss-preset-env", // 能解决大多数样式兼容性问题
+          ],
+        },
+      },
+    },
+    preProcessor,
+  ].filter(Boolean);
+};
+
+module.exports = {
+  entry: "./src/main.js",
+  output: {
+    path: undefined,
+    filename: "static/js/[name].[contenthash:10].js",
+    chunkFilename: "static/js/[name].[contenthash:10].chunk.js",
+    assetModuleFilename: "static/js/[hash:10][ext][query]",
+    clean: true,
+  },
+  module: {
+    rules: [
+      {
+        // 用来匹配 .css 结尾的文件
+        test: /\.css$/,
+        // use 数组里面 Loader 执行顺序是从右到左
+        use: getStyleLoaders(),
+      },
+      {
+        test: /\.less$/,
+        use: getStyleLoaders("less-loader"),
+      },
+      {
+        test: /\.s[ac]ss$/,
+        use: getStyleLoaders("sass-loader"),
+      },
+      {
+        test: /\.styl$/,
+        use: getStyleLoaders("stylus-loader"),
+      },
+      {
+        test: /\.(png|jpe?g|gif|svg)$/,
+        type: "asset",
+        parser: {
+          dataUrlCondition: {
+            maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
+          },
+        },
+      },
+      {
+        test: /\.(ttf|woff2?)$/,
+        type: "asset/resource",
+      },
+      {
+        test: /\.(jsx|js)$/,
+        include: path.resolve(__dirname, "../src"),
+        loader: "babel-loader",
+        options: {
+          cacheDirectory: true,
+          cacheCompression: false,
+          plugins: [
+            // "@babel/plugin-transform-runtime" // presets中包含了
+          ],
+        },
+      },
+      // vue-loader不支持oneOf
+      {
+        test: /\.vue$/,
+        loader: "vue-loader", // 内部会给vue文件注入HMR功能代码
+        options: {
+          // 开启缓存
+          cacheDirectory: path.resolve(
+            __dirname,
+            "node_modules/.cache/vue-loader"
+          ),
+        },
+      },
+    ],
+  },
+  plugins: [
+    new ESLintWebpackPlugin({
+      context: path.resolve(__dirname, "../src"),
+      exclude: "node_modules",
+      cache: true,
+      cacheLocation: path.resolve(
+        __dirname,
+        "../node_modules/.cache/.eslintcache"
+      ),
+    }),
+    new HtmlWebpackPlugin({
+      template: path.resolve(__dirname, "../public/index.html"),
+    }),
+    new CopyPlugin({
+      patterns: [
+        {
+          from: path.resolve(__dirname, "../public"),
+          to: path.resolve(__dirname, "../dist"),
+          toType: "dir",
+          noErrorOnMissing: true,
+          globOptions: {
+            ignore: ["**/index.html"],
+          },
+          info: {
+            minimized: true,
+          },
+        },
+      ],
+    }),
+    new MiniCssExtractPlugin({
+      filename: "static/css/[name].[contenthash:10].css",
+      chunkFilename: "static/css/[name].[contenthash:10].chunk.css",
+    }),
+    new VueLoaderPlugin(),
+    new DefinePlugin({
+      __VUE_OPTIONS_API__: "true",
+      __VUE_PROD_DEVTOOLS__: "false",
+    }),
+  ],
+  optimization: {
+    // 压缩的操作
+    minimizer: [
+      new CssMinimizerPlugin(),
+      new TerserWebpackPlugin(),
+      new ImageMinimizerPlugin({
+        minimizer: {
+          implementation: ImageMinimizerPlugin.imageminGenerate,
+          options: {
+            plugins: [
+              ["gifsicle", { interlaced: true }],
+              ["jpegtran", { progressive: true }],
+              ["optipng", { optimizationLevel: 5 }],
+              [
+                "svgo",
+                {
+                  plugins: [
+                    "preset-default",
+                    "prefixIds",
+                    {
+                      name: "sortAttrs",
+                      params: {
+                        xmlnsOrder: "alphabetical",
+                      },
+                    },
+                  ],
+                },
+              ],
+            ],
+          },
+        },
+      }),
+    ],
+    splitChunks: {
+      chunks: "all",
+    },
+    runtimeChunk: {
+      name: (entrypoint) => `runtime~${entrypoint.name}`,
+    },
+  },
+  resolve: {
+    extensions: [".vue", ".js", ".json"],
+  },
+  mode: "production",
+  devtool: "source-map",
+};
+

其他配置

  • package.json
{
+  "name": "vue-cli",
+  "version": "1.0.0",
+  "description": "",
+  "main": "main.js",
+  "scripts": {
+    "start": "npm run dev",
+    "dev": "cross-env NODE_ENV=development webpack serve --config ./config/webpack.dev.js",
+    "build": "cross-env NODE_ENV=production webpack --config ./config/webpack.prod.js"
+  },
+  "keywords": [],
+  "author": "",
+  "license": "ISC",
+  "devDependencies": {
+    "@babel/core": "^7.17.10",
+    "@babel/eslint-parser": "^7.17.0",
+    "@vue/cli-plugin-babel": "^5.0.4",
+    "babel-loader": "^8.2.5",
+    "copy-webpack-plugin": "^10.2.4",
+    "cross-env": "^7.0.3",
+    "css-loader": "^6.7.1",
+    "css-minimizer-webpack-plugin": "^3.4.1",
+    "eslint-plugin-vue": "^8.7.1",
+    "eslint-webpack-plugin": "^3.1.1",
+    "html-webpack-plugin": "^5.5.0",
+    "image-minimizer-webpack-plugin": "^3.2.3",
+    "imagemin": "^8.0.1",
+    "imagemin-gifsicle": "^7.0.0",
+    "imagemin-jpegtran": "^7.0.0",
+    "imagemin-optipng": "^8.0.0",
+    "imagemin-svgo": "^10.0.1",
+    "less-loader": "^10.2.0",
+    "mini-css-extract-plugin": "^2.6.0",
+    "postcss-preset-env": "^7.5.0",
+    "sass-loader": "^12.6.0",
+    "stylus-loader": "^6.2.0",
+    "vue-loader": "^17.0.0",
+    "vue-style-loader": "^4.1.3",
+    "vue-template-compiler": "^2.6.14",
+    "webpack": "^5.72.0",
+    "webpack-cli": "^4.9.2",
+    "webpack-dev-server": "^4.9.0"
+  },
+  "dependencies": {
+    "vue": "^3.2.33",
+    "vue-router": "^4.0.15"
+  },
+  "browserslist": ["last 2 version", "> 1%", "not dead"]
+}
+
  • .eslintrc.js
module.exports = {
+  root: true,
+  env: {
+    node: true,
+  },
+  extends: ["plugin:vue/vue3-essential", "eslint:recommended"],
+  parserOptions: {
+    parser: "@babel/eslint-parser",
+  },
+};
+
  • babel.config.js
module.exports = {
+  presets: ["@vue/cli-plugin-babel/preset"],
+};
+

合并开发和生产配置

// webpack.config.js
+const path = require("path");
+const ESLintWebpackPlugin = require("eslint-webpack-plugin");
+const HtmlWebpackPlugin = require("html-webpack-plugin");
+const MiniCssExtractPlugin = require("mini-css-extract-plugin");
+const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
+const TerserWebpackPlugin = require("terser-webpack-plugin");
+const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
+const { VueLoaderPlugin } = require("vue-loader");
+const { DefinePlugin } = require("webpack");
+const CopyPlugin = require("copy-webpack-plugin");
+
+// 需要通过 cross-env 定义环境变量
+const isProduction = process.env.NODE_ENV === "production";
+
+const getStyleLoaders = (preProcessor) => {
+  return [
+    isProduction ? MiniCssExtractPlugin.loader : "vue-style-loader",
+    "css-loader",
+    {
+      loader: "postcss-loader",
+      options: {
+        postcssOptions: {
+          plugins: ["postcss-preset-env"],
+        },
+      },
+    },
+    preProcessor,
+  ].filter(Boolean);
+};
+
+module.exports = {
+  entry: "./src/main.js",
+  output: {
+    path: isProduction ? path.resolve(__dirname, "../dist") : undefined,
+    filename: isProduction
+      ? "static/js/[name].[contenthash:10].js"
+      : "static/js/[name].js",
+    chunkFilename: isProduction
+      ? "static/js/[name].[contenthash:10].chunk.js"
+      : "static/js/[name].chunk.js",
+    assetModuleFilename: "static/js/[hash:10][ext][query]",
+    clean: true,
+  },
+  module: {
+    rules: [
+      {
+        // 用来匹配 .css 结尾的文件
+        test: /\.css$/,
+        // use 数组里面 Loader 执行顺序是从右到左
+        use: getStyleLoaders(),
+      },
+      {
+        test: /\.less$/,
+        use: getStyleLoaders("less-loader"),
+      },
+      {
+        test: /\.s[ac]ss$/,
+        use: getStyleLoaders("sass-loader"),
+      },
+      {
+        test: /\.styl$/,
+        use: getStyleLoaders("stylus-loader"),
+      },
+      {
+        test: /\.(png|jpe?g|gif|svg)$/,
+        type: "asset",
+        parser: {
+          dataUrlCondition: {
+            maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
+          },
+        },
+      },
+      {
+        test: /\.(ttf|woff2?)$/,
+        type: "asset/resource",
+      },
+      {
+        test: /\.(jsx|js)$/,
+        include: path.resolve(__dirname, "../src"),
+        loader: "babel-loader",
+        options: {
+          cacheDirectory: true,
+          cacheCompression: false,
+          plugins: [
+            // "@babel/plugin-transform-runtime" // presets中包含了
+          ],
+        },
+      },
+      // vue-loader不支持oneOf
+      {
+        test: /\.vue$/,
+        loader: "vue-loader", // 内部会给vue文件注入HMR功能代码
+        options: {
+          // 开启缓存
+          cacheDirectory: path.resolve(
+            __dirname,
+            "node_modules/.cache/vue-loader"
+          ),
+        },
+      },
+    ],
+  },
+  plugins: [
+    new ESLintWebpackPlugin({
+      context: path.resolve(__dirname, "../src"),
+      exclude: "node_modules",
+      cache: true,
+      cacheLocation: path.resolve(
+        __dirname,
+        "../node_modules/.cache/.eslintcache"
+      ),
+    }),
+    new HtmlWebpackPlugin({
+      template: path.resolve(__dirname, "../public/index.html"),
+    }),
+    new CopyPlugin({
+      patterns: [
+        {
+          from: path.resolve(__dirname, "../public"),
+          to: path.resolve(__dirname, "../dist"),
+          toType: "dir",
+          noErrorOnMissing: true,
+          globOptions: {
+            ignore: ["**/index.html"],
+          },
+          info: {
+            minimized: true,
+          },
+        },
+      ],
+    }),
+    isProduction &&
+      new MiniCssExtractPlugin({
+        filename: "static/css/[name].[contenthash:10].css",
+        chunkFilename: "static/css/[name].[contenthash:10].chunk.css",
+      }),
+    new VueLoaderPlugin(),
+    new DefinePlugin({
+      __VUE_OPTIONS_API__: "true",
+      __VUE_PROD_DEVTOOLS__: "false",
+    }),
+  ].filter(Boolean),
+  optimization: {
+    minimize: isProduction,
+    // 压缩的操作
+    minimizer: [
+      new CssMinimizerPlugin(),
+      new TerserWebpackPlugin(),
+      new ImageMinimizerPlugin({
+        minimizer: {
+          implementation: ImageMinimizerPlugin.imageminGenerate,
+          options: {
+            plugins: [
+              ["gifsicle", { interlaced: true }],
+              ["jpegtran", { progressive: true }],
+              ["optipng", { optimizationLevel: 5 }],
+              [
+                "svgo",
+                {
+                  plugins: [
+                    "preset-default",
+                    "prefixIds",
+                    {
+                      name: "sortAttrs",
+                      params: {
+                        xmlnsOrder: "alphabetical",
+                      },
+                    },
+                  ],
+                },
+              ],
+            ],
+          },
+        },
+      }),
+    ],
+    splitChunks: {
+      chunks: "all",
+    },
+    runtimeChunk: {
+      name: (entrypoint) => `runtime~${entrypoint.name}`,
+    },
+  },
+  resolve: {
+    extensions: [".vue", ".js", ".json"],
+  },
+  devServer: {
+    open: true,
+    host: "localhost",
+    port: 3000,
+    hot: true,
+    compress: true,
+    historyApiFallback: true, // 解决vue-router刷新404问题
+  },
+  mode: isProduction ? "production" : "development",
+  devtool: isProduction ? "source-map" : "cheap-module-source-map",
+};
+

优化配置

const path = require("path");
+const ESLintWebpackPlugin = require("eslint-webpack-plugin");
+const HtmlWebpackPlugin = require("html-webpack-plugin");
+const MiniCssExtractPlugin = require("mini-css-extract-plugin");
+const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
+const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
+const TerserWebpackPlugin = require("terser-webpack-plugin");
+const CopyPlugin = require("copy-webpack-plugin");
+const { VueLoaderPlugin } = require("vue-loader");
+const { DefinePlugin } = require("webpack");
+const AutoImport = require("unplugin-auto-import/webpack");
+const Components = require("unplugin-vue-components/webpack");
+const { ElementPlusResolver } = require("unplugin-vue-components/resolvers");
+// 需要通过 cross-env 定义环境变量
+const isProduction = process.env.NODE_ENV === "production";
+
+const getStyleLoaders = (preProcessor) => {
+  return [
+    isProduction ? MiniCssExtractPlugin.loader : "vue-style-loader",
+    "css-loader",
+    {
+      loader: "postcss-loader",
+      options: {
+        postcssOptions: {
+          plugins: ["postcss-preset-env"],
+        },
+      },
+    },
+    preProcessor && {
+      loader: preProcessor,
+      options:
+        preProcessor === "sass-loader"
+          ? {
+              // 自定义主题:自动引入我们定义的scss文件
+              additionalData: `@use "@/styles/element/index.scss" as *;`,
+            }
+          : {},
+    },
+  ].filter(Boolean);
+};
+
+module.exports = {
+  entry: "./src/main.js",
+  output: {
+    path: isProduction ? path.resolve(__dirname, "../dist") : undefined,
+    filename: isProduction
+      ? "static/js/[name].[contenthash:10].js"
+      : "static/js/[name].js",
+    chunkFilename: isProduction
+      ? "static/js/[name].[contenthash:10].chunk.js"
+      : "static/js/[name].chunk.js",
+    assetModuleFilename: "static/js/[hash:10][ext][query]",
+    clean: true,
+  },
+  module: {
+    rules: [
+      {
+        test: /\.css$/,
+        use: getStyleLoaders(),
+      },
+      {
+        test: /\.less$/,
+        use: getStyleLoaders("less-loader"),
+      },
+      {
+        test: /\.s[ac]ss$/,
+        use: getStyleLoaders("sass-loader"),
+      },
+      {
+        test: /\.styl$/,
+        use: getStyleLoaders("stylus-loader"),
+      },
+      {
+        test: /\.(png|jpe?g|gif|svg)$/,
+        type: "asset",
+        parser: {
+          dataUrlCondition: {
+            maxSize: 10 * 1024,
+          },
+        },
+      },
+      {
+        test: /\.(ttf|woff2?)$/,
+        type: "asset/resource",
+      },
+      {
+        test: /\.(jsx|js)$/,
+        include: path.resolve(__dirname, "../src"),
+        loader: "babel-loader",
+        options: {
+          cacheDirectory: true,
+          cacheCompression: false,
+          plugins: [
+            // "@babel/plugin-transform-runtime" // presets中包含了
+          ],
+        },
+      },
+      // vue-loader不支持oneOf
+      {
+        test: /\.vue$/,
+        loader: "vue-loader", // 内部会给vue文件注入HMR功能代码
+        options: {
+          // 开启缓存
+          cacheDirectory: path.resolve(
+            __dirname,
+            "node_modules/.cache/vue-loader"
+          ),
+        },
+      },
+    ],
+  },
+  plugins: [
+    new ESLintWebpackPlugin({
+      context: path.resolve(__dirname, "../src"),
+      exclude: "node_modules",
+      cache: true,
+      cacheLocation: path.resolve(
+        __dirname,
+        "../node_modules/.cache/.eslintcache"
+      ),
+    }),
+    new HtmlWebpackPlugin({
+      template: path.resolve(__dirname, "../public/index.html"),
+    }),
+    new CopyPlugin({
+      patterns: [
+        {
+          from: path.resolve(__dirname, "../public"),
+          to: path.resolve(__dirname, "../dist"),
+          toType: "dir",
+          noErrorOnMissing: true,
+          globOptions: {
+            ignore: ["**/index.html"],
+          },
+          info: {
+            minimized: true,
+          },
+        },
+      ],
+    }),
+    isProduction &&
+      new MiniCssExtractPlugin({
+        filename: "static/css/[name].[contenthash:10].css",
+        chunkFilename: "static/css/[name].[contenthash:10].chunk.css",
+      }),
+    new VueLoaderPlugin(),
+    new DefinePlugin({
+      __VUE_OPTIONS_API__: "true",
+      __VUE_PROD_DEVTOOLS__: "false",
+    }),
+    // 按需加载element-plus组件样式
+    AutoImport({
+      resolvers: [ElementPlusResolver()],
+    }),
+    Components({
+      resolvers: [
+        ElementPlusResolver({
+          importStyle: "sass", // 自定义主题
+        }),
+      ],
+    }),
+  ].filter(Boolean),
+  optimization: {
+    minimize: isProduction,
+    // 压缩的操作
+    minimizer: [
+      new CssMinimizerPlugin(),
+      new TerserWebpackPlugin(),
+      new ImageMinimizerPlugin({
+        minimizer: {
+          implementation: ImageMinimizerPlugin.imageminGenerate,
+          options: {
+            plugins: [
+              ["gifsicle", { interlaced: true }],
+              ["jpegtran", { progressive: true }],
+              ["optipng", { optimizationLevel: 5 }],
+              [
+                "svgo",
+                {
+                  plugins: [
+                    "preset-default",
+                    "prefixIds",
+                    {
+                      name: "sortAttrs",
+                      params: {
+                        xmlnsOrder: "alphabetical",
+                      },
+                    },
+                  ],
+                },
+              ],
+            ],
+          },
+        },
+      }),
+    ],
+    splitChunks: {
+      chunks: "all",
+      cacheGroups: {
+        // layouts通常是admin项目的主体布局组件,所有路由组件都要使用的
+        // 可以单独打包,从而复用
+        // 如果项目中没有,请删除
+        layouts: {
+          name: "layouts",
+          test: path.resolve(__dirname, "../src/layouts"),
+          priority: 40,
+        },
+        // 如果项目中使用element-plus,此时将所有node_modules打包在一起,那么打包输出文件会比较大。
+        // 所以我们将node_modules中比较大的模块单独打包,从而并行加载速度更好
+        // 如果项目中没有,请删除
+        elementUI: {
+          name: "chunk-elementPlus",
+          test: /[\\/]node_modules[\\/]_?element-plus(.*)/,
+          priority: 30,
+        },
+        // 将vue相关的库单独打包,减少node_modules的chunk体积。
+        vue: {
+          name: "vue",
+          test: /[\\/]node_modules[\\/]vue(.*)[\\/]/,
+          chunks: "initial",
+          priority: 20,
+        },
+        libs: {
+          name: "chunk-libs",
+          test: /[\\/]node_modules[\\/]/,
+          priority: 10, // 权重最低,优先考虑前面内容
+          chunks: "initial",
+        },
+      },
+    },
+    runtimeChunk: {
+      name: (entrypoint) => `runtime~${entrypoint.name}`,
+    },
+  },
+  resolve: {
+    extensions: [".vue", ".js", ".json"],
+    alias: {
+      // 路径别名
+      "@": path.resolve(__dirname, "../src"),
+    },
+  },
+  devServer: {
+    open: true,
+    host: "localhost",
+    port: 3000,
+    hot: true,
+    compress: true,
+    historyApiFallback: true, // 解决vue-router刷新404问题
+  },
+  mode: isProduction ? "production" : "development",
+  devtool: isProduction ? "source-map" : "cheap-module-source-map",
+  performance: false,
+};
+










 
 
 















 
 
 
 
 
 
 
 
 
 
















































































































 
 
 
 
 
 
 
 
 
 
 





































 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 







 
 
 
 











 

Last Updated:
Contributors: xiaoyu
+ + +