diff --git a/404.html b/404.html index 527d7a4e..4eb579c5 100644 --- a/404.html +++ b/404.html @@ -6,14 +6,14 @@ 404 | Sunny's blog - + -
Skip to content

404

PAGE NOT FOUND

But if you don't change your direction, and if you keep looking, you may end up where you are heading.
- - +
Skip to content

404

PAGE NOT FOUND

But if you don't change your direction, and if you keep looking, you may end up where you are heading.
+ + \ No newline at end of file diff --git "a/algorithm/\360\237\224\245\345\210\267\351\242\230\344\271\213\346\216\242\347\264\242\346\234\200\344\274\230\350\247\243.html" "b/algorithm/\360\237\224\245\345\210\267\351\242\230\344\271\213\346\216\242\347\264\242\346\234\200\344\274\230\350\247\243.html" index c8354cd8..1813570c 100644 --- "a/algorithm/\360\237\224\245\345\210\267\351\242\230\344\271\213\346\216\242\347\264\242\346\234\200\344\274\230\350\247\243.html" +++ "b/algorithm/\360\237\224\245\345\210\267\351\242\230\344\271\213\346\216\242\347\264\242\346\234\200\344\274\230\350\247\243.html" @@ -6,13 +6,13 @@ 🔥刷题之探索最优解 WIP | Sunny's blog - - + + -
Skip to content
On this page

🔥刷题之探索最优解 WIP

DANGER

WIP

正在创作

绳子盖住的最多点 WIP

一、题目描述: 给定一个有序数组arr 从左到右依次表示X轴上从左往右点的位置,给定一个正整数k, 返回如果有一根长度为k的绳子,最多能盖住几个点,绳子的边缘点碰到X轴上的点,也算盖住。 示例: 示例 1: 输入:arr = [1, 5, 13, 14, 15, 32, 43], k = 2 输出:3 示例 2: 输入:arr = [1, 5, 13, 14, 15, 32, 43], k = 5 输出:3

js
// 方法一:
+    
Skip to content
On this page

🔥刷题之探索最优解 WIP

DANGER

WIP

正在创作

绳子盖住的最多点 WIP

一、题目描述: 给定一个有序数组arr 从左到右依次表示X轴上从左往右点的位置,给定一个正整数k, 返回如果有一根长度为k的绳子,最多能盖住几个点,绳子的边缘点碰到X轴上的点,也算盖住。 示例: 示例 1: 输入:arr = [1, 5, 13, 14, 15, 32, 43], k = 2 输出:3 示例 2: 输入:arr = [1, 5, 13, 14, 15, 32, 43], k = 5 输出:3

js
// 方法一:
 function rope(arr, k) {
   let res = 1
   for (let i = 0; i < arr.length; i++) {
@@ -86,9 +86,9 @@
   return max
 }
 
-

总结:在解决问题的基础上,再进行优化。方法一使用二分复杂度为O(nlogn),使用窗口法就可以将复杂度将为O(n)。

- - +

总结:在解决问题的基础上,再进行优化。方法一使用二分复杂度为O(nlogn),使用窗口法就可以将复杂度将为O(n)。

+ + \ No newline at end of file diff --git a/article/cms.html b/article/cms.html index 21071c65..c5de614d 100644 --- a/article/cms.html +++ b/article/cms.html @@ -6,15 +6,15 @@ 一站式-后台前端解决方案调研 | Sunny's blog - - + + -
Skip to content
On this page

一站式-后台前端解决方案调研

TIP

搜索方式:github 搜名称

Hooks-Admin

react-admin

vue-element-admin

Antd Pro Vue - 背靠阿里,代码过硬,大型项目首选

Vue vben admin - 宝藏后台管理 基于 Vue3 UI清新 功能扎实

Naive Ui admin 适合小项目

vue3-antd-admin

gin-vue-admin

vue-pure-admin

vue3-composition-admin

vue-admin-perfect

gin-vue-admin

vue-vben-admin

Geeker-Admin

soybean-admin

vue-admin-box

vue-next-admin

vue-admin-better

v3-admin-vite

vue-manage-system

vue3-admin-plus

https://github.com/RainManGO/vue3-composition-admin

https://github.com/jzfai/vue3-admin-plus

https://github.com/tobe-fe-dalao/fast-vue3

https://github.com/ibwei/vue3-ts-base

https://github.com/jzfai/vue3-admin-ts

https://github.com/zouzhibin/vue-admin-perfect

- - +
Skip to content
On this page

一站式-后台前端解决方案调研

TIP

搜索方式:github 搜名称

Hooks-Admin

react-admin

vue-element-admin

Antd Pro Vue - 背靠阿里,代码过硬,大型项目首选

Vue vben admin - 宝藏后台管理 基于 Vue3 UI清新 功能扎实

Naive Ui admin 适合小项目

vue3-antd-admin

gin-vue-admin

vue-pure-admin

vue3-composition-admin

vue-admin-perfect

gin-vue-admin

vue-vben-admin

Geeker-Admin

soybean-admin

vue-admin-box

vue-next-admin

vue-admin-better

v3-admin-vite

vue-manage-system

vue3-admin-plus

https://github.com/RainManGO/vue3-composition-admin

https://github.com/jzfai/vue3-admin-plus

https://github.com/tobe-fe-dalao/fast-vue3

https://github.com/ibwei/vue3-ts-base

https://github.com/jzfai/vue3-admin-ts

https://github.com/zouzhibin/vue-admin-perfect

+ + \ No newline at end of file diff --git a/assets/2024-04-09-16-52-36.f7ed461a.png b/assets/2024-04-09-16-52-36.f7ed461a.png new file mode 100644 index 00000000..3a2b1c8e Binary files /dev/null and b/assets/2024-04-09-16-52-36.f7ed461a.png differ diff --git a/assets/2024-04-09-20-41-59.c3e21810.png b/assets/2024-04-09-20-41-59.c3e21810.png new file mode 100644 index 00000000..58ca8600 Binary files /dev/null and b/assets/2024-04-09-20-41-59.c3e21810.png differ diff --git "a/assets/algorithm_\360\237\224\245\345\210\267\351\242\230\344\271\213\346\216\242\347\264\242\346\234\200\344\274\230\350\247\243.md.fb0aa636.js" "b/assets/algorithm_\360\237\224\245\345\210\267\351\242\230\344\271\213\346\216\242\347\264\242\346\234\200\344\274\230\350\247\243.md.4f0a301b.js" similarity index 99% rename from "assets/algorithm_\360\237\224\245\345\210\267\351\242\230\344\271\213\346\216\242\347\264\242\346\234\200\344\274\230\350\247\243.md.fb0aa636.js" rename to "assets/algorithm_\360\237\224\245\345\210\267\351\242\230\344\271\213\346\216\242\347\264\242\346\234\200\344\274\230\350\247\243.md.4f0a301b.js" index 7834b505..322fc38c 100644 --- "a/assets/algorithm_\360\237\224\245\345\210\267\351\242\230\344\271\213\346\216\242\347\264\242\346\234\200\344\274\230\350\247\243.md.fb0aa636.js" +++ "b/assets/algorithm_\360\237\224\245\345\210\267\351\242\230\344\271\213\346\216\242\347\264\242\346\234\200\344\274\230\350\247\243.md.4f0a301b.js" @@ -1,4 +1,4 @@ -import{_ as s,o as n,c as a,a as l}from"./app.679ab08c.js";const C=JSON.parse('{"title":"🔥刷题之探索最优解 WIP","description":"","frontmatter":{},"headers":[{"level":2,"title":"绳子盖住的最多点 WIP","slug":"绳子盖住的最多点-wip","link":"#绳子盖住的最多点-wip","children":[]}],"relativePath":"algorithm/🔥刷题之探索最优解.md","lastUpdated":1673072248000}'),p={name:"algorithm/🔥刷题之探索最优解.md"},o=l(`

🔥刷题之探索最优解 WIP

DANGER

WIP

正在创作

绳子盖住的最多点 WIP

一、题目描述: 给定一个有序数组arr 从左到右依次表示X轴上从左往右点的位置,给定一个正整数k, 返回如果有一根长度为k的绳子,最多能盖住几个点,绳子的边缘点碰到X轴上的点,也算盖住。 示例: 示例 1: 输入:arr = [1, 5, 13, 14, 15, 32, 43], k = 2 输出:3 示例 2: 输入:arr = [1, 5, 13, 14, 15, 32, 43], k = 5 输出:3

js
// 方法一:
+import{_ as s,o as n,c as a,a as l}from"./app.815d1813.js";const C=JSON.parse('{"title":"🔥刷题之探索最优解 WIP","description":"","frontmatter":{},"headers":[{"level":2,"title":"绳子盖住的最多点 WIP","slug":"绳子盖住的最多点-wip","link":"#绳子盖住的最多点-wip","children":[]}],"relativePath":"algorithm/🔥刷题之探索最优解.md","lastUpdated":1673072248000}'),p={name:"algorithm/🔥刷题之探索最优解.md"},o=l(`

🔥刷题之探索最优解 WIP

DANGER

WIP

正在创作

绳子盖住的最多点 WIP

一、题目描述: 给定一个有序数组arr 从左到右依次表示X轴上从左往右点的位置,给定一个正整数k, 返回如果有一根长度为k的绳子,最多能盖住几个点,绳子的边缘点碰到X轴上的点,也算盖住。 示例: 示例 1: 输入:arr = [1, 5, 13, 14, 15, 32, 43], k = 2 输出:3 示例 2: 输入:arr = [1, 5, 13, 14, 15, 32, 43], k = 5 输出:3

js
// 方法一:
 function rope(arr, k) {
   let res = 1
   for (let i = 0; i < arr.length; i++) {
diff --git "a/assets/algorithm_\360\237\224\245\345\210\267\351\242\230\344\271\213\346\216\242\347\264\242\346\234\200\344\274\230\350\247\243.md.fb0aa636.lean.js" "b/assets/algorithm_\360\237\224\245\345\210\267\351\242\230\344\271\213\346\216\242\347\264\242\346\234\200\344\274\230\350\247\243.md.4f0a301b.lean.js"
similarity index 89%
rename from "assets/algorithm_\360\237\224\245\345\210\267\351\242\230\344\271\213\346\216\242\347\264\242\346\234\200\344\274\230\350\247\243.md.fb0aa636.lean.js"
rename to "assets/algorithm_\360\237\224\245\345\210\267\351\242\230\344\271\213\346\216\242\347\264\242\346\234\200\344\274\230\350\247\243.md.4f0a301b.lean.js"
index 4f42171c..c25f6437 100644
--- "a/assets/algorithm_\360\237\224\245\345\210\267\351\242\230\344\271\213\346\216\242\347\264\242\346\234\200\344\274\230\350\247\243.md.fb0aa636.lean.js"
+++ "b/assets/algorithm_\360\237\224\245\345\210\267\351\242\230\344\271\213\346\216\242\347\264\242\346\234\200\344\274\230\350\247\243.md.4f0a301b.lean.js"
@@ -1 +1 @@
-import{_ as s,o as n,c as a,a as l}from"./app.679ab08c.js";const C=JSON.parse('{"title":"🔥刷题之探索最优解 WIP","description":"","frontmatter":{},"headers":[{"level":2,"title":"绳子盖住的最多点 WIP","slug":"绳子盖住的最多点-wip","link":"#绳子盖住的最多点-wip","children":[]}],"relativePath":"algorithm/🔥刷题之探索最优解.md","lastUpdated":1673072248000}'),p={name:"algorithm/🔥刷题之探索最优解.md"},o=l("",6),e=[o];function r(B,c,t,y,F,i){return n(),a("div",null,e)}const m=s(p,[["render",r]]);export{C as __pageData,m as default};
+import{_ as s,o as n,c as a,a as l}from"./app.815d1813.js";const C=JSON.parse('{"title":"🔥刷题之探索最优解 WIP","description":"","frontmatter":{},"headers":[{"level":2,"title":"绳子盖住的最多点 WIP","slug":"绳子盖住的最多点-wip","link":"#绳子盖住的最多点-wip","children":[]}],"relativePath":"algorithm/🔥刷题之探索最优解.md","lastUpdated":1673072248000}'),p={name:"algorithm/🔥刷题之探索最优解.md"},o=l("",6),e=[o];function r(B,c,t,y,F,i){return n(),a("div",null,e)}const m=s(p,[["render",r]]);export{C as __pageData,m as default};
diff --git a/assets/app.679ab08c.js b/assets/app.815d1813.js
similarity index 68%
rename from assets/app.679ab08c.js
rename to assets/app.815d1813.js
index 040f45d0..010a7727 100644
--- a/assets/app.679ab08c.js
+++ b/assets/app.815d1813.js
@@ -2,23 +2,23 @@
 * @vue/shared v3.4.21
 * (c) 2018-present Yuxi (Evan) You and Vue contributors
 * @license MIT
-**/function Hs(e,t){const n=new Set(e.split(","));return t?s=>n.has(s.toLowerCase()):s=>n.has(s)}const me={},Nt=[],Oe=()=>{},jr=()=>!1,pn=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),Fs=e=>e.startsWith("onUpdate:"),ke=Object.assign,Ds=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},zr=Object.prototype.hasOwnProperty,ie=(e,t)=>zr.call(e,t),G=Array.isArray,Ot=e=>zn(e)==="[object Map]",ki=e=>zn(e)==="[object Set]",ee=e=>typeof e=="function",ge=e=>typeof e=="string",Wt=e=>typeof e=="symbol",he=e=>e!==null&&typeof e=="object",wi=e=>(he(e)||ee(e))&&ee(e.then)&&ee(e.catch),xi=Object.prototype.toString,zn=e=>xi.call(e),Kr=e=>zn(e).slice(8,-1),$i=e=>zn(e)==="[object Object]",Us=e=>ge(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,Rt=Hs(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),Kn=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},Gr=/-(\w)/g,Ze=Kn(e=>e.replace(Gr,(t,n)=>n?n.toUpperCase():"")),qr=/\B([A-Z])/g,Yt=Kn(e=>e.replace(qr,"-$1").toLowerCase()),Gn=Kn(e=>e.charAt(0).toUpperCase()+e.slice(1)),ds=Kn(e=>e?`on${Gn(e)}`:""),ht=(e,t)=>!Object.is(e,t),ps=(e,t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,value:n})},Wr=e=>{const t=parseFloat(e);return isNaN(t)?e:t},Yr=e=>{const t=ge(e)?Number(e):NaN;return isNaN(t)?e:t};let bo;const Pi=()=>bo||(bo=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function qn(e){if(G(e)){const t={};for(let n=0;n{if(n){const s=n.split(Qr);s.length>1&&(t[s[0].trim()]=s[1].trim())}}),t}function pe(e){let t="";if(ge(e))t=e;else if(G(e))for(let n=0;nge(e)?e:e==null?"":G(e)||he(e)&&(e.toString===xi||!ee(e.toString))?JSON.stringify(e,Ci,2):String(e),Ci=(e,t)=>t&&t.__v_isRef?Ci(e,t.value):Ot(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((n,[s,o],i)=>(n[_s(s,i)+" =>"]=o,n),{})}:ki(t)?{[`Set(${t.size})`]:[...t.values()].map(n=>_s(n))}:Wt(t)?_s(t):he(t)&&!G(t)&&!$i(t)?String(t):t,_s=(e,t="")=>{var n;return Wt(e)?`Symbol(${(n=e.description)!=null?n:t})`:e};/**
+**/function Hs(e,t){const n=new Set(e.split(","));return t?s=>n.has(s.toLowerCase()):s=>n.has(s)}const me={},Nt=[],Oe=()=>{},jr=()=>!1,pn=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),Fs=e=>e.startsWith("onUpdate:"),ke=Object.assign,Ds=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},zr=Object.prototype.hasOwnProperty,ie=(e,t)=>zr.call(e,t),G=Array.isArray,Ot=e=>zn(e)==="[object Map]",ki=e=>zn(e)==="[object Set]",ee=e=>typeof e=="function",ge=e=>typeof e=="string",Wt=e=>typeof e=="symbol",he=e=>e!==null&&typeof e=="object",xi=e=>(he(e)||ee(e))&&ee(e.then)&&ee(e.catch),wi=Object.prototype.toString,zn=e=>wi.call(e),Kr=e=>zn(e).slice(8,-1),$i=e=>zn(e)==="[object Object]",Us=e=>ge(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,Rt=Hs(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),Kn=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},Gr=/-(\w)/g,Ze=Kn(e=>e.replace(Gr,(t,n)=>n?n.toUpperCase():"")),qr=/\B([A-Z])/g,Jt=Kn(e=>e.replace(qr,"-$1").toLowerCase()),Gn=Kn(e=>e.charAt(0).toUpperCase()+e.slice(1)),ds=Kn(e=>e?`on${Gn(e)}`:""),ht=(e,t)=>!Object.is(e,t),ps=(e,t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,value:n})},Wr=e=>{const t=parseFloat(e);return isNaN(t)?e:t},Jr=e=>{const t=ge(e)?Number(e):NaN;return isNaN(t)?e:t};let bo;const Pi=()=>bo||(bo=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function qn(e){if(G(e)){const t={};for(let n=0;n{if(n){const s=n.split(Qr);s.length>1&&(t[s[0].trim()]=s[1].trim())}}),t}function pe(e){let t="";if(ge(e))t=e;else if(G(e))for(let n=0;nge(e)?e:e==null?"":G(e)||he(e)&&(e.toString===wi||!ee(e.toString))?JSON.stringify(e,Ci,2):String(e),Ci=(e,t)=>t&&t.__v_isRef?Ci(e,t.value):Ot(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((n,[s,o],i)=>(n[_s(s,i)+" =>"]=o,n),{})}:ki(t)?{[`Set(${t.size})`]:[...t.values()].map(n=>_s(n))}:Wt(t)?_s(t):he(t)&&!G(t)&&!$i(t)?String(t):t,_s=(e,t="")=>{var n;return Wt(e)?`Symbol(${(n=e.description)!=null?n:t})`:e};/**
 * @vue/reactivity v3.4.21
 * (c) 2018-present Yuxi (Evan) You and Vue contributors
 * @license MIT
-**/let Ne;class nl{constructor(t=!1){this.detached=t,this._active=!0,this.effects=[],this.cleanups=[],this.parent=Ne,!t&&Ne&&(this.index=(Ne.scopes||(Ne.scopes=[])).push(this)-1)}get active(){return this._active}run(t){if(this._active){const n=Ne;try{return Ne=this,t()}finally{Ne=n}}}on(){Ne=this}off(){Ne=this.parent}stop(t){if(this._active){let n,s;for(n=0,s=this.effects.length;n=4))break}this._dirtyLevel===1&&(this._dirtyLevel=0),Tt()}return this._dirtyLevel>=4}set dirty(t){this._dirtyLevel=t?4:0}run(){if(this._dirtyLevel=0,!this.active)return this.fn();let t=pt,n=St;try{return pt=!0,St=this,this._runnings++,ko(this),this.fn()}finally{wo(this),this._runnings--,St=n,pt=t}}stop(){var t;this.active&&(ko(this),wo(this),(t=this.onStop)==null||t.call(this),this.active=!1)}}function il(e){return e.value}function ko(e){e._trackId++,e._depsLength=0}function wo(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},Cs=new WeakMap,Ct=Symbol(""),Vs=Symbol("");function Me(e,t,n){if(pt&&St){let s=Cs.get(e);s||Cs.set(e,s=new Map);let o=s.get(n);o||s.set(n,o=Ai(()=>s.delete(n))),Ei(St,o)}}function st(e,t,n,s,o,i){const r=Cs.get(e);if(!r)return;let l=[];if(t==="clear")l=[...r.values()];else if(n==="length"&&G(e)){const c=Number(s);r.forEach((f,p)=>{(p==="length"||!Wt(p)&&p>=c)&&l.push(f)})}else switch(n!==void 0&&l.push(r.get(n)),t){case"add":G(e)?Us(n)&&l.push(r.get("length")):(l.push(r.get(Ct)),Ot(e)&&l.push(r.get(Vs)));break;case"delete":G(e)||(l.push(r.get(Ct)),Ot(e)&&l.push(r.get(Vs)));break;case"set":Ot(e)&&l.push(r.get(Ct));break}zs();for(const c of l)c&&Mi(c,4);Ks()}const rl=Hs("__proto__,__v_isRef,__isVue"),Ii=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>e!=="arguments"&&e!=="caller").map(e=>Symbol[e]).filter(Wt)),xo=ll();function ll(){const e={};return["includes","indexOf","lastIndexOf"].forEach(t=>{e[t]=function(...n){const s=ae(this);for(let i=0,r=this.length;i{e[t]=function(...n){Vt(),zs();const s=ae(this)[t].apply(this,n);return Ks(),Tt(),s}}),e}function cl(e){const t=ae(this);return Me(t,"has",e),t.hasOwnProperty(e)}class Ni{constructor(t=!1,n=!1){this._isReadonly=t,this._isShallow=n}get(t,n,s){const o=this._isReadonly,i=this._isShallow;if(n==="__v_isReactive")return!o;if(n==="__v_isReadonly")return o;if(n==="__v_isShallow")return i;if(n==="__v_raw")return s===(o?i?kl:Hi:i?Bi:Ri).get(t)||Object.getPrototypeOf(t)===Object.getPrototypeOf(s)?t:void 0;const r=G(t);if(!o){if(r&&ie(xo,n))return Reflect.get(xo,n,s);if(n==="hasOwnProperty")return cl}const l=Reflect.get(t,n,s);return(Wt(n)?Ii.has(n):rl(n))||(o||Me(t,"get",n),i)?l:Ae(l)?r&&Us(n)?l:l.value:he(l)?o?Ws(l):Yn(l):l}}class Oi extends Ni{constructor(t=!1){super(!1,t)}set(t,n,s,o){let i=t[n];if(!this._isShallow){const c=zt(i);if(!Mn(s)&&!zt(s)&&(i=ae(i),s=ae(s)),!G(t)&&Ae(i)&&!Ae(s))return c?!1:(i.value=s,!0)}const r=G(t)&&Us(n)?Number(n)e,Wn=e=>Reflect.getPrototypeOf(e);function mn(e,t,n=!1,s=!1){e=e.__v_raw;const o=ae(e),i=ae(t);n||(ht(t,i)&&Me(o,"get",t),Me(o,"get",i));const{has:r}=Wn(o),l=s?Gs:n?Js:ln;if(r.call(o,t))return l(e.get(t));if(r.call(o,i))return l(e.get(i));e!==o&&e.get(t)}function gn(e,t=!1){const n=this.__v_raw,s=ae(n),o=ae(e);return t||(ht(e,o)&&Me(s,"has",e),Me(s,"has",o)),e===o?n.has(e):n.has(e)||n.has(o)}function yn(e,t=!1){return e=e.__v_raw,!t&&Me(ae(e),"iterate",Ct),Reflect.get(e,"size",e)}function $o(e){e=ae(e);const t=ae(this);return Wn(t).has.call(t,e)||(t.add(e),st(t,"add",e,e)),this}function Po(e,t){t=ae(t);const n=ae(this),{has:s,get:o}=Wn(n);let i=s.call(n,e);i||(e=ae(e),i=s.call(n,e));const r=o.call(n,e);return n.set(e,t),i?ht(t,r)&&st(n,"set",e,t):st(n,"add",e,t),this}function So(e){const t=ae(this),{has:n,get:s}=Wn(t);let o=n.call(t,e);o||(e=ae(e),o=n.call(t,e)),s&&s.call(t,e);const i=t.delete(e);return o&&st(t,"delete",e,void 0),i}function Co(){const e=ae(this),t=e.size!==0,n=e.clear();return t&&st(e,"clear",void 0,void 0),n}function bn(e,t){return function(s,o){const i=this,r=i.__v_raw,l=ae(r),c=t?Gs:e?Js:ln;return!e&&Me(l,"iterate",Ct),r.forEach((f,p)=>s.call(o,c(f),c(p),i))}}function kn(e,t,n){return function(...s){const o=this.__v_raw,i=ae(o),r=Ot(i),l=e==="entries"||e===Symbol.iterator&&r,c=e==="keys"&&r,f=o[e](...s),p=n?Gs:t?Js:ln;return!t&&Me(i,"iterate",c?Vs:Ct),{next(){const{value:_,done:b}=f.next();return b?{value:_,done:b}:{value:l?[p(_[0]),p(_[1])]:p(_),done:b}},[Symbol.iterator](){return this}}}}function it(e){return function(...t){return e==="delete"?!1:e==="clear"?void 0:this}}function pl(){const e={get(i){return mn(this,i)},get size(){return yn(this)},has:gn,add:$o,set:Po,delete:So,clear:Co,forEach:bn(!1,!1)},t={get(i){return mn(this,i,!1,!0)},get size(){return yn(this)},has:gn,add:$o,set:Po,delete:So,clear:Co,forEach:bn(!1,!0)},n={get(i){return mn(this,i,!0)},get size(){return yn(this,!0)},has(i){return gn.call(this,i,!0)},add:it("add"),set:it("set"),delete:it("delete"),clear:it("clear"),forEach:bn(!0,!1)},s={get(i){return mn(this,i,!0,!0)},get size(){return yn(this,!0)},has(i){return gn.call(this,i,!0)},add:it("add"),set:it("set"),delete:it("delete"),clear:it("clear"),forEach:bn(!0,!0)};return["keys","values","entries",Symbol.iterator].forEach(i=>{e[i]=kn(i,!1,!1),n[i]=kn(i,!0,!1),t[i]=kn(i,!1,!0),s[i]=kn(i,!0,!0)}),[e,n,t,s]}const[_l,hl,vl,ml]=pl();function qs(e,t){const n=t?e?ml:vl:e?hl:_l;return(s,o,i)=>o==="__v_isReactive"?!e:o==="__v_isReadonly"?e:o==="__v_raw"?s:Reflect.get(ie(n,o)&&o in s?n:s,o,i)}const gl={get:qs(!1,!1)},yl={get:qs(!1,!0)},bl={get:qs(!0,!1)},Ri=new WeakMap,Bi=new WeakMap,Hi=new WeakMap,kl=new WeakMap;function wl(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function xl(e){return e.__v_skip||!Object.isExtensible(e)?0:wl(Kr(e))}function Yn(e){return zt(e)?e:Ys(e,!1,ul,gl,Ri)}function $l(e){return Ys(e,!1,dl,yl,Bi)}function Ws(e){return Ys(e,!0,fl,bl,Hi)}function Ys(e,t,n,s,o){if(!he(e)||e.__v_raw&&!(t&&e.__v_isReactive))return e;const i=o.get(e);if(i)return i;const r=xl(e);if(r===0)return e;const l=new Proxy(e,r===2?s:n);return o.set(e,l),l}function Bt(e){return zt(e)?Bt(e.__v_raw):!!(e&&e.__v_isReactive)}function zt(e){return!!(e&&e.__v_isReadonly)}function Mn(e){return!!(e&&e.__v_isShallow)}function Fi(e){return Bt(e)||zt(e)}function ae(e){const t=e&&e.__v_raw;return t?ae(t):e}function en(e){return Object.isExtensible(e)&&En(e,"__v_skip",!0),e}const ln=e=>he(e)?Yn(e):e,Js=e=>he(e)?Ws(e):e;class Di{constructor(t,n,s,o){this.getter=t,this._setter=n,this.dep=void 0,this.__v_isRef=!0,this.__v_isReadonly=!1,this.effect=new js(()=>t(this._value),()=>Cn(this,this.effect._dirtyLevel===2?2:3)),this.effect.computed=this,this.effect.active=this._cacheable=!o,this.__v_isReadonly=s}get value(){const t=ae(this);return(!t._cacheable||t.effect.dirty)&&ht(t._value,t._value=t.effect.run())&&Cn(t,4),Ui(t),t.effect._dirtyLevel>=2&&Cn(t,2),t._value}set value(t){this._setter(t)}get _dirty(){return this.effect.dirty}set _dirty(t){this.effect.dirty=t}}function Pl(e,t,n=!1){let s,o;const i=ee(e);return i?(s=e,o=Oe):(s=e.get,o=e.set),new Di(s,o,i||!o,n)}function Ui(e){var t;pt&&St&&(e=ae(e),Ei(St,(t=e.dep)!=null?t:e.dep=Ai(()=>e.dep=void 0,e instanceof Di?e:void 0)))}function Cn(e,t=4,n){e=ae(e);const s=e.dep;s&&Mi(s,t)}function Ae(e){return!!(e&&e.__v_isRef===!0)}function _e(e){return ji(e,!1)}function Sl(e){return ji(e,!0)}function ji(e,t){return Ae(e)?e:new Cl(e,t)}class Cl{constructor(t,n){this.__v_isShallow=n,this.dep=void 0,this.__v_isRef=!0,this._rawValue=n?t:ae(t),this._value=n?t:ln(t)}get value(){return Ui(this),this._value}set value(t){const n=this.__v_isShallow||Mn(t)||zt(t);t=n?t:ae(t),ht(t,this._rawValue)&&(this._rawValue=t,this._value=n?t:ln(t),Cn(this,4))}}function y(e){return Ae(e)?e.value:e}const Vl={get:(e,t,n)=>y(Reflect.get(e,t,n)),set:(e,t,n,s)=>{const o=e[t];return Ae(o)&&!Ae(n)?(o.value=n,!0):Reflect.set(e,t,n,s)}};function zi(e){return Bt(e)?e:new Proxy(e,Vl)}/**
+**/let Ne;class nl{constructor(t=!1){this.detached=t,this._active=!0,this.effects=[],this.cleanups=[],this.parent=Ne,!t&&Ne&&(this.index=(Ne.scopes||(Ne.scopes=[])).push(this)-1)}get active(){return this._active}run(t){if(this._active){const n=Ne;try{return Ne=this,t()}finally{Ne=n}}}on(){Ne=this}off(){Ne=this.parent}stop(t){if(this._active){let n,s;for(n=0,s=this.effects.length;n=4))break}this._dirtyLevel===1&&(this._dirtyLevel=0),Tt()}return this._dirtyLevel>=4}set dirty(t){this._dirtyLevel=t?4:0}run(){if(this._dirtyLevel=0,!this.active)return this.fn();let t=pt,n=St;try{return pt=!0,St=this,this._runnings++,ko(this),this.fn()}finally{xo(this),this._runnings--,St=n,pt=t}}stop(){var t;this.active&&(ko(this),xo(this),(t=this.onStop)==null||t.call(this),this.active=!1)}}function il(e){return e.value}function ko(e){e._trackId++,e._depsLength=0}function xo(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},Cs=new WeakMap,Ct=Symbol(""),Vs=Symbol("");function Me(e,t,n){if(pt&&St){let s=Cs.get(e);s||Cs.set(e,s=new Map);let o=s.get(n);o||s.set(n,o=Ai(()=>s.delete(n))),Ei(St,o)}}function st(e,t,n,s,o,i){const r=Cs.get(e);if(!r)return;let l=[];if(t==="clear")l=[...r.values()];else if(n==="length"&&G(e)){const c=Number(s);r.forEach((f,p)=>{(p==="length"||!Wt(p)&&p>=c)&&l.push(f)})}else switch(n!==void 0&&l.push(r.get(n)),t){case"add":G(e)?Us(n)&&l.push(r.get("length")):(l.push(r.get(Ct)),Ot(e)&&l.push(r.get(Vs)));break;case"delete":G(e)||(l.push(r.get(Ct)),Ot(e)&&l.push(r.get(Vs)));break;case"set":Ot(e)&&l.push(r.get(Ct));break}zs();for(const c of l)c&&Mi(c,4);Ks()}const rl=Hs("__proto__,__v_isRef,__isVue"),Ii=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>e!=="arguments"&&e!=="caller").map(e=>Symbol[e]).filter(Wt)),wo=ll();function ll(){const e={};return["includes","indexOf","lastIndexOf"].forEach(t=>{e[t]=function(...n){const s=ae(this);for(let i=0,r=this.length;i{e[t]=function(...n){Vt(),zs();const s=ae(this)[t].apply(this,n);return Ks(),Tt(),s}}),e}function cl(e){const t=ae(this);return Me(t,"has",e),t.hasOwnProperty(e)}class Ni{constructor(t=!1,n=!1){this._isReadonly=t,this._isShallow=n}get(t,n,s){const o=this._isReadonly,i=this._isShallow;if(n==="__v_isReactive")return!o;if(n==="__v_isReadonly")return o;if(n==="__v_isShallow")return i;if(n==="__v_raw")return s===(o?i?kl:Hi:i?Bi:Ri).get(t)||Object.getPrototypeOf(t)===Object.getPrototypeOf(s)?t:void 0;const r=G(t);if(!o){if(r&&ie(wo,n))return Reflect.get(wo,n,s);if(n==="hasOwnProperty")return cl}const l=Reflect.get(t,n,s);return(Wt(n)?Ii.has(n):rl(n))||(o||Me(t,"get",n),i)?l:Ae(l)?r&&Us(n)?l:l.value:he(l)?o?Ws(l):Jn(l):l}}class Oi extends Ni{constructor(t=!1){super(!1,t)}set(t,n,s,o){let i=t[n];if(!this._isShallow){const c=zt(i);if(!Mn(s)&&!zt(s)&&(i=ae(i),s=ae(s)),!G(t)&&Ae(i)&&!Ae(s))return c?!1:(i.value=s,!0)}const r=G(t)&&Us(n)?Number(n)e,Wn=e=>Reflect.getPrototypeOf(e);function mn(e,t,n=!1,s=!1){e=e.__v_raw;const o=ae(e),i=ae(t);n||(ht(t,i)&&Me(o,"get",t),Me(o,"get",i));const{has:r}=Wn(o),l=s?Gs:n?Ys:ln;if(r.call(o,t))return l(e.get(t));if(r.call(o,i))return l(e.get(i));e!==o&&e.get(t)}function gn(e,t=!1){const n=this.__v_raw,s=ae(n),o=ae(e);return t||(ht(e,o)&&Me(s,"has",e),Me(s,"has",o)),e===o?n.has(e):n.has(e)||n.has(o)}function yn(e,t=!1){return e=e.__v_raw,!t&&Me(ae(e),"iterate",Ct),Reflect.get(e,"size",e)}function $o(e){e=ae(e);const t=ae(this);return Wn(t).has.call(t,e)||(t.add(e),st(t,"add",e,e)),this}function Po(e,t){t=ae(t);const n=ae(this),{has:s,get:o}=Wn(n);let i=s.call(n,e);i||(e=ae(e),i=s.call(n,e));const r=o.call(n,e);return n.set(e,t),i?ht(t,r)&&st(n,"set",e,t):st(n,"add",e,t),this}function So(e){const t=ae(this),{has:n,get:s}=Wn(t);let o=n.call(t,e);o||(e=ae(e),o=n.call(t,e)),s&&s.call(t,e);const i=t.delete(e);return o&&st(t,"delete",e,void 0),i}function Co(){const e=ae(this),t=e.size!==0,n=e.clear();return t&&st(e,"clear",void 0,void 0),n}function bn(e,t){return function(s,o){const i=this,r=i.__v_raw,l=ae(r),c=t?Gs:e?Ys:ln;return!e&&Me(l,"iterate",Ct),r.forEach((f,p)=>s.call(o,c(f),c(p),i))}}function kn(e,t,n){return function(...s){const o=this.__v_raw,i=ae(o),r=Ot(i),l=e==="entries"||e===Symbol.iterator&&r,c=e==="keys"&&r,f=o[e](...s),p=n?Gs:t?Ys:ln;return!t&&Me(i,"iterate",c?Vs:Ct),{next(){const{value:_,done:b}=f.next();return b?{value:_,done:b}:{value:l?[p(_[0]),p(_[1])]:p(_),done:b}},[Symbol.iterator](){return this}}}}function it(e){return function(...t){return e==="delete"?!1:e==="clear"?void 0:this}}function pl(){const e={get(i){return mn(this,i)},get size(){return yn(this)},has:gn,add:$o,set:Po,delete:So,clear:Co,forEach:bn(!1,!1)},t={get(i){return mn(this,i,!1,!0)},get size(){return yn(this)},has:gn,add:$o,set:Po,delete:So,clear:Co,forEach:bn(!1,!0)},n={get(i){return mn(this,i,!0)},get size(){return yn(this,!0)},has(i){return gn.call(this,i,!0)},add:it("add"),set:it("set"),delete:it("delete"),clear:it("clear"),forEach:bn(!0,!1)},s={get(i){return mn(this,i,!0,!0)},get size(){return yn(this,!0)},has(i){return gn.call(this,i,!0)},add:it("add"),set:it("set"),delete:it("delete"),clear:it("clear"),forEach:bn(!0,!0)};return["keys","values","entries",Symbol.iterator].forEach(i=>{e[i]=kn(i,!1,!1),n[i]=kn(i,!0,!1),t[i]=kn(i,!1,!0),s[i]=kn(i,!0,!0)}),[e,n,t,s]}const[_l,hl,vl,ml]=pl();function qs(e,t){const n=t?e?ml:vl:e?hl:_l;return(s,o,i)=>o==="__v_isReactive"?!e:o==="__v_isReadonly"?e:o==="__v_raw"?s:Reflect.get(ie(n,o)&&o in s?n:s,o,i)}const gl={get:qs(!1,!1)},yl={get:qs(!1,!0)},bl={get:qs(!0,!1)},Ri=new WeakMap,Bi=new WeakMap,Hi=new WeakMap,kl=new WeakMap;function xl(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function wl(e){return e.__v_skip||!Object.isExtensible(e)?0:xl(Kr(e))}function Jn(e){return zt(e)?e:Js(e,!1,ul,gl,Ri)}function $l(e){return Js(e,!1,dl,yl,Bi)}function Ws(e){return Js(e,!0,fl,bl,Hi)}function Js(e,t,n,s,o){if(!he(e)||e.__v_raw&&!(t&&e.__v_isReactive))return e;const i=o.get(e);if(i)return i;const r=wl(e);if(r===0)return e;const l=new Proxy(e,r===2?s:n);return o.set(e,l),l}function Bt(e){return zt(e)?Bt(e.__v_raw):!!(e&&e.__v_isReactive)}function zt(e){return!!(e&&e.__v_isReadonly)}function Mn(e){return!!(e&&e.__v_isShallow)}function Fi(e){return Bt(e)||zt(e)}function ae(e){const t=e&&e.__v_raw;return t?ae(t):e}function en(e){return Object.isExtensible(e)&&En(e,"__v_skip",!0),e}const ln=e=>he(e)?Jn(e):e,Ys=e=>he(e)?Ws(e):e;class Di{constructor(t,n,s,o){this.getter=t,this._setter=n,this.dep=void 0,this.__v_isRef=!0,this.__v_isReadonly=!1,this.effect=new js(()=>t(this._value),()=>Cn(this,this.effect._dirtyLevel===2?2:3)),this.effect.computed=this,this.effect.active=this._cacheable=!o,this.__v_isReadonly=s}get value(){const t=ae(this);return(!t._cacheable||t.effect.dirty)&&ht(t._value,t._value=t.effect.run())&&Cn(t,4),Ui(t),t.effect._dirtyLevel>=2&&Cn(t,2),t._value}set value(t){this._setter(t)}get _dirty(){return this.effect.dirty}set _dirty(t){this.effect.dirty=t}}function Pl(e,t,n=!1){let s,o;const i=ee(e);return i?(s=e,o=Oe):(s=e.get,o=e.set),new Di(s,o,i||!o,n)}function Ui(e){var t;pt&&St&&(e=ae(e),Ei(St,(t=e.dep)!=null?t:e.dep=Ai(()=>e.dep=void 0,e instanceof Di?e:void 0)))}function Cn(e,t=4,n){e=ae(e);const s=e.dep;s&&Mi(s,t)}function Ae(e){return!!(e&&e.__v_isRef===!0)}function _e(e){return ji(e,!1)}function Sl(e){return ji(e,!0)}function ji(e,t){return Ae(e)?e:new Cl(e,t)}class Cl{constructor(t,n){this.__v_isShallow=n,this.dep=void 0,this.__v_isRef=!0,this._rawValue=n?t:ae(t),this._value=n?t:ln(t)}get value(){return Ui(this),this._value}set value(t){const n=this.__v_isShallow||Mn(t)||zt(t);t=n?t:ae(t),ht(t,this._rawValue)&&(this._rawValue=t,this._value=n?t:ln(t),Cn(this,4))}}function y(e){return Ae(e)?e.value:e}const Vl={get:(e,t,n)=>y(Reflect.get(e,t,n)),set:(e,t,n,s)=>{const o=e[t];return Ae(o)&&!Ae(n)?(o.value=n,!0):Reflect.set(e,t,n,s)}};function zi(e){return Bt(e)?e:new Proxy(e,Vl)}/**
 * @vue/runtime-core v3.4.21
 * (c) 2018-present Yuxi (Evan) You and Vue contributors
 * @license MIT
-**/function _t(e,t,n,s){try{return s?e(...s):e()}catch(o){Jn(o,t,n)}}function Fe(e,t,n,s){if(ee(e)){const i=_t(e,t,n,s);return i&&wi(i)&&i.catch(r=>{Jn(r,t,n)}),i}const o=[];for(let i=0;i>>1,o=Pe[s],i=an(o);iQe&&Pe.splice(t,1)}function Ml(e){G(e)?Ht.push(...e):(!at||!at.includes(e,e.allowRecurse?xt+1:xt))&&Ht.push(e),Gi()}function Vo(e,t,n=cn?Qe+1:0){for(;nan(n)-an(s));if(Ht.length=0,at){at.push(...t);return}for(at=t,xt=0;xte.id==null?1/0:e.id,Al=(e,t)=>{const n=an(e)-an(t);if(n===0){if(e.pre&&!t.pre)return-1;if(t.pre&&!e.pre)return 1}return n};function qi(e){Ts=!1,cn=!0,Pe.sort(Al);const t=Oe;try{for(Qe=0;Qege(S)?S.trim():S)),_&&(o=n.map(Wr))}let l,c=s[l=ds(t)]||s[l=ds(Ze(t))];!c&&i&&(c=s[l=ds(Yt(t))]),c&&Fe(c,e,6,o);const f=s[l+"Once"];if(f){if(!e.emitted)e.emitted={};else if(e.emitted[l])return;e.emitted[l]=!0,Fe(f,e,6,o)}}function Wi(e,t,n=!1){const s=t.emitsCache,o=s.get(e);if(o!==void 0)return o;const i=e.emits;let r={},l=!1;if(!ee(e)){const c=f=>{const p=Wi(f,t,!0);p&&(l=!0,ke(r,p))};!n&&t.mixins.length&&t.mixins.forEach(c),e.extends&&c(e.extends),e.mixins&&e.mixins.forEach(c)}return!i&&!l?(he(e)&&s.set(e,null),null):(G(i)?i.forEach(c=>r[c]=null):ke(r,i),he(e)&&s.set(e,r),r)}function Qn(e,t){return!e||!pn(t)?!1:(t=t.slice(2).replace(/Once$/,""),ie(e,t[0].toLowerCase()+t.slice(1))||ie(e,Yt(t))||ie(e,t))}let Se=null,Xn=null;function In(e){const t=Se;return Se=e,Xn=e&&e.type.__scopeId||null,t}function Ge(e){Xn=e}function qe(){Xn=null}function I(e,t=Se,n){if(!t||e._n)return e;const s=(...o)=>{s._d&&Fo(-1);const i=In(t);let r;try{r=e(...o)}finally{In(i),s._d&&Fo(1)}return r};return s._n=!0,s._c=!0,s._d=!0,s}function hs(e){const{type:t,vnode:n,proxy:s,withProxy:o,props:i,propsOptions:[r],slots:l,attrs:c,emit:f,render:p,renderCache:_,data:b,setupState:S,ctx:J,inheritAttrs:q}=e;let ne,le;const ye=In(e);try{if(n.shapeFlag&4){const $=o||s,F=$;ne=je(p.call(F,$,_,i,S,b,J)),le=c}else{const $=t;ne=je($.length>1?$(i,{attrs:c,slots:l,emit:f}):$(i,null)),le=t.props?c:Nl(c)}}catch($){sn.length=0,Jn($,e,1),ne=T(Re)}let v=ne;if(le&&q!==!1){const $=Object.keys(le),{shapeFlag:F}=v;$.length&&F&7&&(r&&$.some(Fs)&&(le=Ol(le,r)),v=vt(v,le))}return n.dirs&&(v=vt(v),v.dirs=v.dirs?v.dirs.concat(n.dirs):n.dirs),n.transition&&(v.transition=n.transition),ne=v,In(ye),ne}const Nl=e=>{let t;for(const n in e)(n==="class"||n==="style"||pn(n))&&((t||(t={}))[n]=e[n]);return t},Ol=(e,t)=>{const n={};for(const s in e)(!Fs(s)||!(s.slice(9)in t))&&(n[s]=e[s]);return n};function Rl(e,t,n){const{props:s,children:o,component:i}=e,{props:r,children:l,patchFlag:c}=t,f=i.emitsOptions;if(t.dirs||t.transition)return!0;if(n&&c>=0){if(c&1024)return!0;if(c&16)return s?To(s,r,f):!!r;if(c&8){const p=t.dynamicProps;for(let _=0;_e.__isSuspense;function Qi(e,t){t&&t.pendingBranch?G(e)?t.effects.push(...e):t.effects.push(e):Ml(e)}const Fl=Symbol.for("v-scx"),Dl=()=>Ke(Fl);function Kt(e,t){return Zn(e,null,t)}function Xi(e,t){return Zn(e,null,{flush:"post"})}const wn={};function Xe(e,t,n){return Zn(e,t,n)}function Zn(e,t,{immediate:n,deep:s,flush:o,once:i,onTrack:r,onTrigger:l}=me){if(t&&i){const N=t;t=(...Z)=>{N(...Z),F()}}const c=$e,f=N=>s===!0?N:It(N,s===!1?1:void 0);let p,_=!1,b=!1;if(Ae(e)?(p=()=>e.value,_=Mn(e)):Bt(e)?(p=()=>f(e),_=!0):G(e)?(b=!0,_=e.some(N=>Bt(N)||Mn(N)),p=()=>e.map(N=>{if(Ae(N))return N.value;if(Bt(N))return f(N);if(ee(N))return _t(N,c,2)})):ee(e)?t?p=()=>_t(e,c,2):p=()=>(S&&S(),Fe(e,c,3,[J])):p=Oe,t&&s){const N=p;p=()=>It(N())}let S,J=N=>{S=v.onStop=()=>{_t(N,c,4),S=v.onStop=void 0}},q;if(os)if(J=Oe,t?n&&Fe(t,c,3,[p(),b?[]:void 0,J]):p(),o==="sync"){const N=Dl();q=N.__watcherHandles||(N.__watcherHandles=[])}else return Oe;let ne=b?new Array(e.length).fill(wn):wn;const le=()=>{if(!(!v.active||!v.dirty))if(t){const N=v.run();(s||_||(b?N.some((Z,R)=>ht(Z,ne[R])):ht(N,ne)))&&(S&&S(),Fe(t,c,3,[N,ne===wn?void 0:b&&ne[0]===wn?[]:ne,J]),ne=N)}else v.run()};le.allowRecurse=!!t;let ye;o==="sync"?ye=le:o==="post"?ye=()=>Ee(le,c&&c.suspense):(le.pre=!0,c&&(le.id=c.uid),ye=()=>Zs(le));const v=new js(p,Oe,ye),$=Vi(),F=()=>{v.stop(),$&&Ds($.effects,v)};return t?n?le():ne=v.run():o==="post"?Ee(v.run.bind(v),c&&c.suspense):v.run(),q&&q.push(F),F}function Ul(e,t,n){const s=this.proxy,o=ge(e)?e.includes(".")?Zi(s,e):()=>s[e]:e.bind(s,s);let i;ee(t)?i=t:(i=t.handler,n=t);const r=_n(this),l=Zn(o,i.bind(s),n);return r(),l}function Zi(e,t){const n=t.split(".");return()=>{let s=e;for(let o=0;o0){if(n>=t)return e;n++}if(s=s||new Set,s.has(e))return e;if(s.add(e),Ae(e))It(e.value,t,n,s);else if(G(e))for(let o=0;o{It(o,t,n,s)});else if($i(e))for(const o in e)It(e[o],t,n,s);return e}function Je(e,t,n,s){const o=e.dirs,i=t&&t.dirs;for(let r=0;r{e.isMounted=!0}),or(()=>{e.isUnmounting=!0}),e}const Be=[Function,Array],er={mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:Be,onEnter:Be,onAfterEnter:Be,onEnterCancelled:Be,onBeforeLeave:Be,onLeave:Be,onAfterLeave:Be,onLeaveCancelled:Be,onBeforeAppear:Be,onAppear:Be,onAfterAppear:Be,onAppearCancelled:Be},zl={name:"BaseTransition",props:er,setup(e,{slots:t}){const n=ro(),s=jl();return()=>{const o=t.default&&nr(t.default(),!0);if(!o||!o.length)return;let i=o[0];if(o.length>1){for(const b of o)if(b.type!==Re){i=b;break}}const r=ae(e),{mode:l}=r;if(s.isLeaving)return vs(i);const c=Eo(i);if(!c)return vs(i);const f=Ls(c,r,s,n);Es(c,f);const p=n.subTree,_=p&&Eo(p);if(_&&_.type!==Re&&!$t(c,_)){const b=Ls(_,r,s,n);if(Es(_,b),l==="out-in")return s.isLeaving=!0,b.afterLeave=()=>{s.isLeaving=!1,n.update.active!==!1&&(n.effect.dirty=!0,n.update())},vs(i);l==="in-out"&&c.type!==Re&&(b.delayLeave=(S,J,q)=>{const ne=tr(s,_);ne[String(_.key)]=_,S[ut]=()=>{J(),S[ut]=void 0,delete f.delayedLeave},f.delayedLeave=q})}return i}}},Kl=zl;function tr(e,t){const{leavingVNodes:n}=e;let s=n.get(t.type);return s||(s=Object.create(null),n.set(t.type,s)),s}function Ls(e,t,n,s){const{appear:o,mode:i,persisted:r=!1,onBeforeEnter:l,onEnter:c,onAfterEnter:f,onEnterCancelled:p,onBeforeLeave:_,onLeave:b,onAfterLeave:S,onLeaveCancelled:J,onBeforeAppear:q,onAppear:ne,onAfterAppear:le,onAppearCancelled:ye}=t,v=String(e.key),$=tr(n,e),F=(R,W)=>{R&&Fe(R,s,9,W)},N=(R,W)=>{const H=W[1];F(R,W),G(R)?R.every(ce=>ce.length<=1)&&H():R.length<=1&&H()},Z={mode:i,persisted:r,beforeEnter(R){let W=l;if(!n.isMounted)if(o)W=q||l;else return;R[ut]&&R[ut](!0);const H=$[v];H&&$t(e,H)&&H.el[ut]&&H.el[ut](),F(W,[R])},enter(R){let W=c,H=f,ce=p;if(!n.isMounted)if(o)W=ne||c,H=le||f,ce=ye||p;else return;let M=!1;const te=R[xn]=be=>{M||(M=!0,be?F(ce,[R]):F(H,[R]),Z.delayedLeave&&Z.delayedLeave(),R[xn]=void 0)};W?N(W,[R,te]):te()},leave(R,W){const H=String(e.key);if(R[xn]&&R[xn](!0),n.isUnmounting)return W();F(_,[R]);let ce=!1;const M=R[ut]=te=>{ce||(ce=!0,W(),te?F(J,[R]):F(S,[R]),R[ut]=void 0,$[H]===e&&delete $[H])};$[H]=e,b?N(b,[R,M]):M()},clone(R){return Ls(R,t,n,s)}};return Z}function vs(e){if(es(e))return e=vt(e),e.children=null,e}function Eo(e){return es(e)?e.children?e.children[0]:void 0:e}function Es(e,t){e.shapeFlag&6&&e.component?Es(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 nr(e,t=!1,n){let s=[],o=0;for(let i=0;i1)for(let i=0;ike({name:e.name},t,{setup:e}))():e}const Ft=e=>!!e.type.__asyncLoader,es=e=>e.type.__isKeepAlive;function Gl(e,t){sr(e,"a",t)}function ql(e,t){sr(e,"da",t)}function sr(e,t,n=$e){const s=e.__wdc||(e.__wdc=()=>{let o=n;for(;o;){if(o.isDeactivated)return;o=o.parent}return e()});if(ts(t,s,n),n){let o=n.parent;for(;o&&o.parent;)es(o.parent.vnode)&&Wl(s,t,n,o),o=o.parent}}function Wl(e,t,n,s){const o=ts(t,e,s,!0);mt(()=>{Ds(s[t],o)},n)}function ts(e,t,n=$e,s=!1){if(n){const o=n[e]||(n[e]=[]),i=t.__weh||(t.__weh=(...r)=>{if(n.isUnmounted)return;Vt();const l=_n(n),c=Fe(t,n,e,r);return l(),Tt(),c});return s?o.unshift(i):o.push(i),i}}const ot=e=>(t,n=$e)=>(!os||e==="sp")&&ts(e,(...s)=>t(...s),n),Yl=ot("bm"),De=ot("m"),Jl=ot("bu"),no=ot("u"),or=ot("bum"),mt=ot("um"),Ql=ot("sp"),Xl=ot("rtg"),Zl=ot("rtc");function ec(e,t=$e){ts("ec",e,t)}function Ce(e,t,n,s){let o;const i=n&&n[s];if(G(e)||ge(e)){o=new Array(e.length);for(let r=0,l=e.length;rt(r,l,void 0,i&&i[l]));else{const r=Object.keys(e);o=new Array(r.length);for(let l=0,c=r.length;lRn(t)?!(t.type===Re||t.type===Q&&!ir(t.children)):!0)?e:null}const Ms=e=>e?gr(e)?lo(e)||e.proxy:Ms(e.parent):null,tn=ke(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=>Ms(e.parent),$root:e=>Ms(e.root),$emit:e=>e.emit,$options:e=>so(e),$forceUpdate:e=>e.f||(e.f=()=>{e.effect.dirty=!0,Zs(e.update)}),$nextTick:e=>e.n||(e.n=Xs.bind(e.proxy)),$watch:e=>Ul.bind(e)}),ms=(e,t)=>e!==me&&!e.__isScriptSetup&&ie(e,t),tc={get({_:e},t){const{ctx:n,setupState:s,data:o,props:i,accessCache:r,type:l,appContext:c}=e;let f;if(t[0]!=="$"){const S=r[t];if(S!==void 0)switch(S){case 1:return s[t];case 2:return o[t];case 4:return n[t];case 3:return i[t]}else{if(ms(s,t))return r[t]=1,s[t];if(o!==me&&ie(o,t))return r[t]=2,o[t];if((f=e.propsOptions[0])&&ie(f,t))return r[t]=3,i[t];if(n!==me&&ie(n,t))return r[t]=4,n[t];As&&(r[t]=0)}}const p=tn[t];let _,b;if(p)return t==="$attrs"&&Me(e,"get",t),p(e);if((_=l.__cssModules)&&(_=_[t]))return _;if(n!==me&&ie(n,t))return r[t]=4,n[t];if(b=c.config.globalProperties,ie(b,t))return b[t]},set({_:e},t,n){const{data:s,setupState:o,ctx:i}=e;return ms(o,t)?(o[t]=n,!0):s!==me&&ie(s,t)?(s[t]=n,!0):ie(e.props,t)||t[0]==="$"&&t.slice(1)in e?!1:(i[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:s,appContext:o,propsOptions:i}},r){let l;return!!n[r]||e!==me&&ie(e,r)||ms(t,r)||(l=i[0])&&ie(l,r)||ie(s,r)||ie(tn,r)||ie(o.config.globalProperties,r)},defineProperty(e,t,n){return n.get!=null?e._.accessCache[t]=0:ie(n,"value")&&this.set(e,t,n.value,null),Reflect.defineProperty(e,t,n)}};function Mo(e){return G(e)?e.reduce((t,n)=>(t[n]=null,t),{}):e}let As=!0;function nc(e){const t=so(e),n=e.proxy,s=e.ctx;As=!1,t.beforeCreate&&Ao(t.beforeCreate,e,"bc");const{data:o,computed:i,methods:r,watch:l,provide:c,inject:f,created:p,beforeMount:_,mounted:b,beforeUpdate:S,updated:J,activated:q,deactivated:ne,beforeDestroy:le,beforeUnmount:ye,destroyed:v,unmounted:$,render:F,renderTracked:N,renderTriggered:Z,errorCaptured:R,serverPrefetch:W,expose:H,inheritAttrs:ce,components:M,directives:te,filters:be}=t;if(f&&sc(f,s,null),r)for(const oe in r){const U=r[oe];ee(U)&&(s[oe]=U.bind(n))}if(o){const oe=o.call(n,n);he(oe)&&(e.data=Yn(oe))}if(As=!0,i)for(const oe in i){const U=i[oe],tt=ee(U)?U.bind(n,n):ee(U.get)?U.get.bind(n,n):Oe,hn=!ee(U)&&ee(U.set)?U.set.bind(n):Oe,yt=re({get:tt,set:hn});Object.defineProperty(s,oe,{enumerable:!0,configurable:!0,get:()=>yt.value,set:We=>yt.value=We})}if(l)for(const oe in l)rr(l[oe],s,n,oe);if(c){const oe=ee(c)?c.call(n):c;Reflect.ownKeys(oe).forEach(U=>{ns(U,oe[U])})}p&&Ao(p,e,"c");function j(oe,U){G(U)?U.forEach(tt=>oe(tt.bind(n))):U&&oe(U.bind(n))}if(j(Yl,_),j(De,b),j(Jl,S),j(no,J),j(Gl,q),j(ql,ne),j(ec,R),j(Zl,N),j(Xl,Z),j(or,ye),j(mt,$),j(Ql,W),G(H))if(H.length){const oe=e.exposed||(e.exposed={});H.forEach(U=>{Object.defineProperty(oe,U,{get:()=>n[U],set:tt=>n[U]=tt})})}else e.exposed||(e.exposed={});F&&e.render===Oe&&(e.render=F),ce!=null&&(e.inheritAttrs=ce),M&&(e.components=M),te&&(e.directives=te)}function sc(e,t,n=Oe){G(e)&&(e=Is(e));for(const s in e){const o=e[s];let i;he(o)?"default"in o?i=Ke(o.from||s,o.default,!0):i=Ke(o.from||s):i=Ke(o),Ae(i)?Object.defineProperty(t,s,{enumerable:!0,configurable:!0,get:()=>i.value,set:r=>i.value=r}):t[s]=i}}function Ao(e,t,n){Fe(G(e)?e.map(s=>s.bind(t.proxy)):e.bind(t.proxy),t,n)}function rr(e,t,n,s){const o=s.includes(".")?Zi(n,s):()=>n[s];if(ge(e)){const i=t[e];ee(i)&&Xe(o,i)}else if(ee(e))Xe(o,e.bind(n));else if(he(e))if(G(e))e.forEach(i=>rr(i,t,n,s));else{const i=ee(e.handler)?e.handler.bind(n):t[e.handler];ee(i)&&Xe(o,i,e)}}function so(e){const t=e.type,{mixins:n,extends:s}=t,{mixins:o,optionsCache:i,config:{optionMergeStrategies:r}}=e.appContext,l=i.get(t);let c;return l?c=l:!o.length&&!n&&!s?c=t:(c={},o.length&&o.forEach(f=>Nn(c,f,r,!0)),Nn(c,t,r)),he(t)&&i.set(t,c),c}function Nn(e,t,n,s=!1){const{mixins:o,extends:i}=t;i&&Nn(e,i,n,!0),o&&o.forEach(r=>Nn(e,r,n,!0));for(const r in t)if(!(s&&r==="expose")){const l=oc[r]||n&&n[r];e[r]=l?l(e[r],t[r]):t[r]}return e}const oc={data:Io,props:No,emits:No,methods:Zt,computed:Zt,beforeCreate:Ve,created:Ve,beforeMount:Ve,mounted:Ve,beforeUpdate:Ve,updated:Ve,beforeDestroy:Ve,beforeUnmount:Ve,destroyed:Ve,unmounted:Ve,activated:Ve,deactivated:Ve,errorCaptured:Ve,serverPrefetch:Ve,components:Zt,directives:Zt,watch:rc,provide:Io,inject:ic};function Io(e,t){return t?e?function(){return ke(ee(e)?e.call(this,this):e,ee(t)?t.call(this,this):t)}:t:e}function ic(e,t){return Zt(Is(e),Is(t))}function Is(e){if(G(e)){const t={};for(let n=0;n1)return n&&ee(t)?t.call(s&&s.proxy):t}}function ac(e,t,n,s=!1){const o={},i={};En(i,ss,1),e.propsDefaults=Object.create(null),cr(e,t,o,i);for(const r in e.propsOptions[0])r in o||(o[r]=void 0);n?e.props=s?o:$l(o):e.type.props?e.props=o:e.props=i,e.attrs=i}function uc(e,t,n,s){const{props:o,attrs:i,vnode:{patchFlag:r}}=e,l=ae(o),[c]=e.propsOptions;let f=!1;if((s||r>0)&&!(r&16)){if(r&8){const p=e.vnode.dynamicProps;for(let _=0;_{c=!0;const[b,S]=ar(_,t,!0);ke(r,b),S&&l.push(...S)};!n&&t.mixins.length&&t.mixins.forEach(p),e.extends&&p(e.extends),e.mixins&&e.mixins.forEach(p)}if(!i&&!c)return he(e)&&s.set(e,Nt),Nt;if(G(i))for(let p=0;p-1,S[1]=q<0||J-1||ie(S,"default"))&&l.push(_)}}}const f=[r,l];return he(e)&&s.set(e,f),f}function Oo(e){return e[0]!=="$"&&!Rt(e)}function Ro(e){return e===null?"null":typeof e=="function"?e.name||"":typeof e=="object"&&e.constructor&&e.constructor.name||""}function Bo(e,t){return Ro(e)===Ro(t)}function Ho(e,t){return G(t)?t.findIndex(n=>Bo(n,e)):ee(t)&&Bo(t,e)?0:-1}const ur=e=>e[0]==="_"||e==="$stable",oo=e=>G(e)?e.map(je):[je(e)],fc=(e,t,n)=>{if(t._n)return t;const s=I((...o)=>oo(t(...o)),n);return s._c=!1,s},fr=(e,t,n)=>{const s=e._ctx;for(const o in e){if(ur(o))continue;const i=e[o];if(ee(i))t[o]=fc(o,i,s);else if(i!=null){const r=oo(i);t[o]=()=>r}}},dr=(e,t)=>{const n=oo(t);e.slots.default=()=>n},dc=(e,t)=>{if(e.vnode.shapeFlag&32){const n=t._;n?(e.slots=ae(t),En(t,"_",n)):fr(t,e.slots={})}else e.slots={},t&&dr(e,t);En(e.slots,ss,1)},pc=(e,t,n)=>{const{vnode:s,slots:o}=e;let i=!0,r=me;if(s.shapeFlag&32){const l=t._;l?n&&l===1?i=!1:(ke(o,t),!n&&l===1&&delete o._):(i=!t.$stable,fr(t,o)),r=t}else t&&(dr(e,t),r={default:1});if(i)for(const l in o)!ur(l)&&r[l]==null&&delete o[l]};function On(e,t,n,s,o=!1){if(G(e)){e.forEach((b,S)=>On(b,t&&(G(t)?t[S]:t),n,s,o));return}if(Ft(s)&&!o)return;const i=s.shapeFlag&4?lo(s.component)||s.component.proxy:s.el,r=o?null:i,{i:l,r:c}=e,f=t&&t.r,p=l.refs===me?l.refs={}:l.refs,_=l.setupState;if(f!=null&&f!==c&&(ge(f)?(p[f]=null,ie(_,f)&&(_[f]=null)):Ae(f)&&(f.value=null)),ee(c))_t(c,l,12,[r,p]);else{const b=ge(c),S=Ae(c);if(b||S){const J=()=>{if(e.f){const q=b?ie(_,c)?_[c]:p[c]:c.value;o?G(q)&&Ds(q,i):G(q)?q.includes(i)||q.push(i):b?(p[c]=[i],ie(_,c)&&(_[c]=p[c])):(c.value=[i],e.k&&(p[e.k]=c.value))}else b?(p[c]=r,ie(_,c)&&(_[c]=r)):S&&(c.value=r,e.k&&(p[e.k]=r))};r?(J.id=-1,Ee(J,n)):J()}}}let rt=!1;const _c=e=>e.namespaceURI.includes("svg")&&e.tagName!=="foreignObject",hc=e=>e.namespaceURI.includes("MathML"),$n=e=>{if(_c(e))return"svg";if(hc(e))return"mathml"},Pn=e=>e.nodeType===8;function vc(e){const{mt:t,p:n,o:{patchProp:s,createText:o,nextSibling:i,parentNode:r,remove:l,insert:c,createComment:f}}=e,p=(v,$)=>{if(!$.hasChildNodes()){n(null,v,$),An(),$._vnode=v;return}rt=!1,_($.firstChild,v,null,null,null),An(),$._vnode=v,rt&&console.error("Hydration completed but contains mismatches.")},_=(v,$,F,N,Z,R=!1)=>{const W=Pn(v)&&v.data==="[",H=()=>q(v,$,F,N,Z,W),{type:ce,ref:M,shapeFlag:te,patchFlag:be}=$;let xe=v.nodeType;$.el=v,be===-2&&(R=!1,$.dynamicChildren=null);let j=null;switch(ce){case Gt:xe!==3?$.children===""?(c($.el=o(""),r(v),v),j=v):j=H():(v.data!==$.children&&(rt=!0,v.data=$.children),j=i(v));break;case Re:ye(v)?(j=i(v),le($.el=v.content.firstChild,v,F)):xe!==8||W?j=H():j=i(v);break;case Dt:if(W&&(v=i(v),xe=v.nodeType),xe===1||xe===3){j=v;const oe=!$.children.length;for(let U=0;U<$.staticCount;U++)oe&&($.children+=j.nodeType===1?j.outerHTML:j.data),U===$.staticCount-1&&($.anchor=j),j=i(j);return W?i(j):j}else H();break;case Q:W?j=J(v,$,F,N,Z,R):j=H();break;default:if(te&1)(xe!==1||$.type.toLowerCase()!==v.tagName.toLowerCase())&&!ye(v)?j=H():j=b(v,$,F,N,Z,R);else if(te&6){$.slotScopeIds=Z;const oe=r(v);if(W?j=ne(v):Pn(v)&&v.data==="teleport start"?j=ne(v,v.data,"teleport end"):j=i(v),t($,oe,null,F,N,$n(oe),R),Ft($)){let U;W?(U=T(Q),U.anchor=j?j.previousSibling:oe.lastChild):U=v.nodeType===3?Le(""):T("div"),U.el=v,$.component.subTree=U}}else te&64?xe!==8?j=H():j=$.type.hydrate(v,$,F,N,Z,R,e,S):te&128&&(j=$.type.hydrate(v,$,F,N,$n(r(v)),Z,R,e,_))}return M!=null&&On(M,null,N,$),j},b=(v,$,F,N,Z,R)=>{R=R||!!$.dynamicChildren;const{type:W,props:H,patchFlag:ce,shapeFlag:M,dirs:te,transition:be}=$,xe=W==="input"||W==="option";if(xe||ce!==-1){te&&Je($,null,F,"created");let j=!1;if(ye(v)){j=pr(N,be)&&F&&F.vnode.props&&F.vnode.props.appear;const U=v.content.firstChild;j&&be.beforeEnter(U),le(U,v,F),$.el=v=U}if(M&16&&!(H&&(H.innerHTML||H.textContent))){let U=S(v.firstChild,$,v,F,N,Z,R);for(;U;){rt=!0;const tt=U;U=U.nextSibling,l(tt)}}else M&8&&v.textContent!==$.children&&(rt=!0,v.textContent=$.children);if(H)if(xe||!R||ce&48)for(const U in H)(xe&&(U.endsWith("value")||U==="indeterminate")||pn(U)&&!Rt(U)||U[0]===".")&&s(v,U,null,H[U],void 0,void 0,F);else H.onClick&&s(v,"onClick",null,H.onClick,void 0,void 0,F);let oe;(oe=H&&H.onVnodeBeforeMount)&&He(oe,F,$),te&&Je($,null,F,"beforeMount"),((oe=H&&H.onVnodeMounted)||te||j)&&Qi(()=>{oe&&He(oe,F,$),j&&be.enter(v),te&&Je($,null,F,"mounted")},N)}return v.nextSibling},S=(v,$,F,N,Z,R,W)=>{W=W||!!$.dynamicChildren;const H=$.children,ce=H.length;for(let M=0;M{const{slotScopeIds:W}=$;W&&(Z=Z?Z.concat(W):W);const H=r(v),ce=S(i(v),$,H,F,N,Z,R);return ce&&Pn(ce)&&ce.data==="]"?i($.anchor=ce):(rt=!0,c($.anchor=f("]"),H,ce),ce)},q=(v,$,F,N,Z,R)=>{if(rt=!0,$.el=null,R){const ce=ne(v);for(;;){const M=i(v);if(M&&M!==ce)l(M);else break}}const W=i(v),H=r(v);return l(v),n(null,$,H,W,F,N,$n(H),Z),W},ne=(v,$="[",F="]")=>{let N=0;for(;v;)if(v=i(v),v&&Pn(v)&&(v.data===$&&N++,v.data===F)){if(N===0)return i(v);N--}return v},le=(v,$,F)=>{const N=$.parentNode;N&&N.replaceChild(v,$);let Z=F;for(;Z;)Z.vnode.el===$&&(Z.vnode.el=Z.subTree.el=v),Z=Z.parent},ye=v=>v.nodeType===1&&v.tagName.toLowerCase()==="template";return[p,_]}const Ee=Qi;function mc(e){return gc(e,vc)}function gc(e,t){const n=Pi();n.__VUE__=!0;const{insert:s,remove:o,patchProp:i,createElement:r,createText:l,createComment:c,setText:f,setElementText:p,parentNode:_,nextSibling:b,setScopeId:S=Oe,insertStaticContent:J}=e,q=(a,u,h,k=null,w=null,C=null,E=void 0,P=null,V=!!u.dynamicChildren)=>{if(a===u)return;a&&!$t(a,u)&&(k=vn(a),We(a,w,C,!0),a=null),u.patchFlag===-2&&(V=!1,u.dynamicChildren=null);const{type:x,ref:A,shapeFlag:z}=u;switch(x){case Gt:ne(a,u,h,k);break;case Re:le(a,u,h,k);break;case Dt:a==null&&ye(u,h,k,E);break;case Q:M(a,u,h,k,w,C,E,P,V);break;default:z&1?F(a,u,h,k,w,C,E,P,V):z&6?te(a,u,h,k,w,C,E,P,V):(z&64||z&128)&&x.process(a,u,h,k,w,C,E,P,V,Mt)}A!=null&&w&&On(A,a&&a.ref,C,u||a,!u)},ne=(a,u,h,k)=>{if(a==null)s(u.el=l(u.children),h,k);else{const w=u.el=a.el;u.children!==a.children&&f(w,u.children)}},le=(a,u,h,k)=>{a==null?s(u.el=c(u.children||""),h,k):u.el=a.el},ye=(a,u,h,k)=>{[a.el,a.anchor]=J(a.children,u,h,k,a.el,a.anchor)},v=({el:a,anchor:u},h,k)=>{let w;for(;a&&a!==u;)w=b(a),s(a,h,k),a=w;s(u,h,k)},$=({el:a,anchor:u})=>{let h;for(;a&&a!==u;)h=b(a),o(a),a=h;o(u)},F=(a,u,h,k,w,C,E,P,V)=>{u.type==="svg"?E="svg":u.type==="math"&&(E="mathml"),a==null?N(u,h,k,w,C,E,P,V):W(a,u,w,C,E,P,V)},N=(a,u,h,k,w,C,E,P)=>{let V,x;const{props:A,shapeFlag:z,transition:D,dirs:Y}=a;if(V=a.el=r(a.type,C,A&&A.is,A),z&8?p(V,a.children):z&16&&R(a.children,V,null,k,w,gs(a,C),E,P),Y&&Je(a,null,k,"created"),Z(V,a,a.scopeId,E,k),A){for(const de in A)de!=="value"&&!Rt(de)&&i(V,de,null,A[de],C,a.children,k,w,nt);"value"in A&&i(V,"value",null,A.value,C),(x=A.onVnodeBeforeMount)&&He(x,k,a)}Y&&Je(a,null,k,"beforeMount");const se=pr(w,D);se&&D.beforeEnter(V),s(V,u,h),((x=A&&A.onVnodeMounted)||se||Y)&&Ee(()=>{x&&He(x,k,a),se&&D.enter(V),Y&&Je(a,null,k,"mounted")},w)},Z=(a,u,h,k,w)=>{if(h&&S(a,h),k)for(let C=0;C{for(let x=V;x{const P=u.el=a.el;let{patchFlag:V,dynamicChildren:x,dirs:A}=u;V|=a.patchFlag&16;const z=a.props||me,D=u.props||me;let Y;if(h&&bt(h,!1),(Y=D.onVnodeBeforeUpdate)&&He(Y,h,u,a),A&&Je(u,a,h,"beforeUpdate"),h&&bt(h,!0),x?H(a.dynamicChildren,x,P,h,k,gs(u,w),C):E||U(a,u,P,null,h,k,gs(u,w),C,!1),V>0){if(V&16)ce(P,u,z,D,h,k,w);else if(V&2&&z.class!==D.class&&i(P,"class",null,D.class,w),V&4&&i(P,"style",z.style,D.style,w),V&8){const se=u.dynamicProps;for(let de=0;de{Y&&He(Y,h,u,a),A&&Je(u,a,h,"updated")},k)},H=(a,u,h,k,w,C,E)=>{for(let P=0;P{if(h!==k){if(h!==me)for(const P in h)!Rt(P)&&!(P in k)&&i(a,P,h[P],null,E,u.children,w,C,nt);for(const P in k){if(Rt(P))continue;const V=k[P],x=h[P];V!==x&&P!=="value"&&i(a,P,x,V,E,u.children,w,C,nt)}"value"in k&&i(a,"value",h.value,k.value,E)}},M=(a,u,h,k,w,C,E,P,V)=>{const x=u.el=a?a.el:l(""),A=u.anchor=a?a.anchor:l("");let{patchFlag:z,dynamicChildren:D,slotScopeIds:Y}=u;Y&&(P=P?P.concat(Y):Y),a==null?(s(x,h,k),s(A,h,k),R(u.children||[],h,A,w,C,E,P,V)):z>0&&z&64&&D&&a.dynamicChildren?(H(a.dynamicChildren,D,h,w,C,E,P),(u.key!=null||w&&u===w.subTree)&&_r(a,u,!0)):U(a,u,h,A,w,C,E,P,V)},te=(a,u,h,k,w,C,E,P,V)=>{u.slotScopeIds=P,a==null?u.shapeFlag&512?w.ctx.activate(u,h,k,E,V):be(u,h,k,w,C,E,V):xe(a,u,V)},be=(a,u,h,k,w,C,E)=>{const P=a.component=Cc(a,k,w);if(es(a)&&(P.ctx.renderer=Mt),Vc(P),P.asyncDep){if(w&&w.registerDep(P,j),!a.el){const V=P.subTree=T(Re);le(null,V,u,h)}}else j(P,a,u,h,w,C,E)},xe=(a,u,h)=>{const k=u.component=a.component;if(Rl(a,u,h))if(k.asyncDep&&!k.asyncResolved){oe(k,u,h);return}else k.next=u,El(k.update),k.effect.dirty=!0,k.update();else u.el=a.el,k.vnode=u},j=(a,u,h,k,w,C,E)=>{const P=()=>{if(a.isMounted){let{next:A,bu:z,u:D,parent:Y,vnode:se}=a;{const At=hr(a);if(At){A&&(A.el=se.el,oe(a,A,E)),At.asyncDep.then(()=>{a.isUnmounted||P()});return}}let de=A,ve;bt(a,!1),A?(A.el=se.el,oe(a,A,E)):A=se,z&&ps(z),(ve=A.props&&A.props.onVnodeBeforeUpdate)&&He(ve,Y,A,se),bt(a,!0);const we=hs(a),Ue=a.subTree;a.subTree=we,q(Ue,we,_(Ue.el),vn(Ue),a,w,C),A.el=we.el,de===null&&Bl(a,we.el),D&&Ee(D,w),(ve=A.props&&A.props.onVnodeUpdated)&&Ee(()=>He(ve,Y,A,se),w)}else{let A;const{el:z,props:D}=u,{bm:Y,m:se,parent:de}=a,ve=Ft(u);if(bt(a,!1),Y&&ps(Y),!ve&&(A=D&&D.onVnodeBeforeMount)&&He(A,de,u),bt(a,!0),z&&fs){const we=()=>{a.subTree=hs(a),fs(z,a.subTree,a,w,null)};ve?u.type.__asyncLoader().then(()=>!a.isUnmounted&&we()):we()}else{const we=a.subTree=hs(a);q(null,we,h,k,a,w,C),u.el=we.el}if(se&&Ee(se,w),!ve&&(A=D&&D.onVnodeMounted)){const we=u;Ee(()=>He(A,de,we),w)}(u.shapeFlag&256||de&&Ft(de.vnode)&&de.vnode.shapeFlag&256)&&a.a&&Ee(a.a,w),a.isMounted=!0,u=h=k=null}},V=a.effect=new js(P,Oe,()=>Zs(x),a.scope),x=a.update=()=>{V.dirty&&V.run()};x.id=a.uid,bt(a,!0),x()},oe=(a,u,h)=>{u.component=a;const k=a.vnode.props;a.vnode=u,a.next=null,uc(a,u.props,k,h),pc(a,u.children,h),Vt(),Vo(a),Tt()},U=(a,u,h,k,w,C,E,P,V=!1)=>{const x=a&&a.children,A=a?a.shapeFlag:0,z=u.children,{patchFlag:D,shapeFlag:Y}=u;if(D>0){if(D&128){hn(x,z,h,k,w,C,E,P,V);return}else if(D&256){tt(x,z,h,k,w,C,E,P,V);return}}Y&8?(A&16&&nt(x,w,C),z!==x&&p(h,z)):A&16?Y&16?hn(x,z,h,k,w,C,E,P,V):nt(x,w,C,!0):(A&8&&p(h,""),Y&16&&R(z,h,k,w,C,E,P,V))},tt=(a,u,h,k,w,C,E,P,V)=>{a=a||Nt,u=u||Nt;const x=a.length,A=u.length,z=Math.min(x,A);let D;for(D=0;DA?nt(a,w,C,!0,!1,z):R(u,h,k,w,C,E,P,V,z)},hn=(a,u,h,k,w,C,E,P,V)=>{let x=0;const A=u.length;let z=a.length-1,D=A-1;for(;x<=z&&x<=D;){const Y=a[x],se=u[x]=V?ft(u[x]):je(u[x]);if($t(Y,se))q(Y,se,h,null,w,C,E,P,V);else break;x++}for(;x<=z&&x<=D;){const Y=a[z],se=u[D]=V?ft(u[D]):je(u[D]);if($t(Y,se))q(Y,se,h,null,w,C,E,P,V);else break;z--,D--}if(x>z){if(x<=D){const Y=D+1,se=YD)for(;x<=z;)We(a[x],w,C,!0),x++;else{const Y=x,se=x,de=new Map;for(x=se;x<=D;x++){const Ie=u[x]=V?ft(u[x]):je(u[x]);Ie.key!=null&&de.set(Ie.key,x)}let ve,we=0;const Ue=D-se+1;let At=!1,mo=0;const Qt=new Array(Ue);for(x=0;x=Ue){We(Ie,w,C,!0);continue}let Ye;if(Ie.key!=null)Ye=de.get(Ie.key);else for(ve=se;ve<=D;ve++)if(Qt[ve-se]===0&&$t(Ie,u[ve])){Ye=ve;break}Ye===void 0?We(Ie,w,C,!0):(Qt[Ye-se]=x+1,Ye>=mo?mo=Ye:At=!0,q(Ie,u[Ye],h,null,w,C,E,P,V),we++)}const go=At?yc(Qt):Nt;for(ve=go.length-1,x=Ue-1;x>=0;x--){const Ie=se+x,Ye=u[Ie],yo=Ie+1{const{el:C,type:E,transition:P,children:V,shapeFlag:x}=a;if(x&6){yt(a.component.subTree,u,h,k);return}if(x&128){a.suspense.move(u,h,k);return}if(x&64){E.move(a,u,h,Mt);return}if(E===Q){s(C,u,h);for(let z=0;zP.enter(C),w);else{const{leave:z,delayLeave:D,afterLeave:Y}=P,se=()=>s(C,u,h),de=()=>{z(C,()=>{se(),Y&&Y()})};D?D(C,se,de):de()}else s(C,u,h)},We=(a,u,h,k=!1,w=!1)=>{const{type:C,props:E,ref:P,children:V,dynamicChildren:x,shapeFlag:A,patchFlag:z,dirs:D}=a;if(P!=null&&On(P,null,h,a,!0),A&256){u.ctx.deactivate(a);return}const Y=A&1&&D,se=!Ft(a);let de;if(se&&(de=E&&E.onVnodeBeforeUnmount)&&He(de,u,a),A&6)Ur(a.component,h,k);else{if(A&128){a.suspense.unmount(h,k);return}Y&&Je(a,null,u,"beforeUnmount"),A&64?a.type.remove(a,u,h,w,Mt,k):x&&(C!==Q||z>0&&z&64)?nt(x,u,h,!1,!0):(C===Q&&z&384||!w&&A&16)&&nt(V,u,h),k&&ho(a)}(se&&(de=E&&E.onVnodeUnmounted)||Y)&&Ee(()=>{de&&He(de,u,a),Y&&Je(a,null,u,"unmounted")},h)},ho=a=>{const{type:u,el:h,anchor:k,transition:w}=a;if(u===Q){Dr(h,k);return}if(u===Dt){$(a);return}const C=()=>{o(h),w&&!w.persisted&&w.afterLeave&&w.afterLeave()};if(a.shapeFlag&1&&w&&!w.persisted){const{leave:E,delayLeave:P}=w,V=()=>E(h,C);P?P(a.el,C,V):V()}else C()},Dr=(a,u)=>{let h;for(;a!==u;)h=b(a),o(a),a=h;o(u)},Ur=(a,u,h)=>{const{bum:k,scope:w,update:C,subTree:E,um:P}=a;k&&ps(k),w.stop(),C&&(C.active=!1,We(E,a,u,h)),P&&Ee(P,u),Ee(()=>{a.isUnmounted=!0},u),u&&u.pendingBranch&&!u.isUnmounted&&a.asyncDep&&!a.asyncResolved&&a.suspenseId===u.pendingId&&(u.deps--,u.deps===0&&u.resolve())},nt=(a,u,h,k=!1,w=!1,C=0)=>{for(let E=C;Ea.shapeFlag&6?vn(a.component.subTree):a.shapeFlag&128?a.suspense.next():b(a.anchor||a.el);let as=!1;const vo=(a,u,h)=>{a==null?u._vnode&&We(u._vnode,null,null,!0):q(u._vnode||null,a,u,null,null,null,h),as||(as=!0,Vo(),An(),as=!1),u._vnode=a},Mt={p:q,um:We,m:yt,r:ho,mt:be,mc:R,pc:U,pbc:H,n:vn,o:e};let us,fs;return t&&([us,fs]=t(Mt)),{render:vo,hydrate:us,createApp:cc(vo,us)}}function gs({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 bt({effect:e,update:t},n){e.allowRecurse=t.allowRecurse=n}function pr(e,t){return(!e||e&&!e.pendingBranch)&&t&&!t.persisted}function _r(e,t,n=!1){const s=e.children,o=t.children;if(G(s)&&G(o))for(let i=0;i>1,e[n[l]]0&&(t[s]=n[i-1]),n[i]=s)}}for(i=n.length,r=n[i-1];i-- >0;)n[i]=r,r=t[r];return n}function hr(e){const t=e.subTree.component;if(t)return t.asyncDep&&!t.asyncResolved?t:hr(t)}const bc=e=>e.__isTeleport,Q=Symbol.for("v-fgt"),Gt=Symbol.for("v-txt"),Re=Symbol.for("v-cmt"),Dt=Symbol.for("v-stc"),sn=[];let ze=null;function d(e=!1){sn.push(ze=e?null:[])}function kc(){sn.pop(),ze=sn[sn.length-1]||null}let un=1;function Fo(e){un+=e}function vr(e){return e.dynamicChildren=un>0?ze||Nt:null,kc(),un>0&&ze&&ze.push(e),e}function m(e,t,n,s,o,i){return vr(g(e,t,n,s,o,i,!0))}function X(e,t,n,s,o){return vr(T(e,t,n,s,o,!0))}function Rn(e){return e?e.__v_isVNode===!0:!1}function $t(e,t){return e.type===t.type&&e.key===t.key}const ss="__vInternal",mr=({key:e})=>e??null,Vn=({ref:e,ref_key:t,ref_for:n})=>(typeof e=="number"&&(e=""+e),e!=null?ge(e)||Ae(e)||ee(e)?{i:Se,r:e,k:t,f:!!n}:e:null);function g(e,t=null,n=null,s=0,o=null,i=e===Q?0:1,r=!1,l=!1){const c={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&mr(t),ref:t&&Vn(t),scopeId:Xn,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:i,patchFlag:s,dynamicProps:o,dynamicChildren:null,appContext:null,ctx:Se};return l?(io(c,n),i&128&&e.normalize(c)):n&&(c.shapeFlag|=ge(n)?8:16),un>0&&!r&&ze&&(c.patchFlag>0||i&6)&&c.patchFlag!==32&&ze.push(c),c}const T=wc;function wc(e,t=null,n=null,s=0,o=null,i=!1){if((!e||e===Yi)&&(e=Re),Rn(e)){const l=vt(e,t,!0);return n&&io(l,n),un>0&&!i&&ze&&(l.shapeFlag&6?ze[ze.indexOf(e)]=l:ze.push(l)),l.patchFlag|=-2,l}if(Ac(e)&&(e=e.__vccOpts),t){t=xc(t);let{class:l,style:c}=t;l&&!ge(l)&&(t.class=pe(l)),he(c)&&(Fi(c)&&!G(c)&&(c=ke({},c)),t.style=qn(c))}const r=ge(e)?1:Hl(e)?128:bc(e)?64:he(e)?4:ee(e)?2:0;return g(e,t,n,s,o,r,i,!0)}function xc(e){return e?Fi(e)||ss in e?ke({},e):e:null}function vt(e,t,n=!1){const{props:s,ref:o,patchFlag:i,children:r}=e,l=t?Tn(s||{},t):s;return{__v_isVNode:!0,__v_skip:!0,type:e.type,props:l,key:l&&mr(l),ref:t&&t.ref?n&&o?G(o)?o.concat(Vn(t)):[o,Vn(t)]:Vn(t):o,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:r,target:e.target,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==Q?i===-1?16:i|16:i,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:e.transition,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&vt(e.ssContent),ssFallback:e.ssFallback&&vt(e.ssFallback),el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce}}function Le(e=" ",t=0){return T(Gt,null,e,t)}function $c(e,t){const n=T(Dt,null,e);return n.staticCount=t,n}function K(e="",t=!1){return t?(d(),X(Re,null,e)):T(Re,null,e)}function je(e){return e==null||typeof e=="boolean"?T(Re):G(e)?T(Q,null,e.slice()):typeof e=="object"?ft(e):T(Gt,null,String(e))}function ft(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:vt(e)}function io(e,t){let n=0;const{shapeFlag:s}=e;if(t==null)t=null;else if(G(t))n=16;else if(typeof t=="object")if(s&65){const o=t.default;o&&(o._c&&(o._d=!1),io(e,o()),o._c&&(o._d=!0));return}else{n=32;const o=t._;!o&&!(ss in t)?t._ctx=Se:o===3&&Se&&(Se.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else ee(t)?(t={default:t,_ctx:Se},n=32):(t=String(t),s&64?(n=16,t=[Le(t)]):n=8);e.children=t,e.shapeFlag|=n}function Tn(...e){const t={};for(let n=0;n$e||Se;let Bn,Os;{const e=Pi(),t=(n,s)=>{let o;return(o=e[n])||(o=e[n]=[]),o.push(s),i=>{o.length>1?o.forEach(r=>r(i)):o[0](i)}};Bn=t("__VUE_INSTANCE_SETTERS__",n=>$e=n),Os=t("__VUE_SSR_SETTERS__",n=>os=n)}const _n=e=>{const t=$e;return Bn(e),e.scope.on(),()=>{e.scope.off(),Bn(t)}},Do=()=>{$e&&$e.scope.off(),Bn(null)};function gr(e){return e.vnode.shapeFlag&4}let os=!1;function Vc(e,t=!1){t&&Os(t);const{props:n,children:s}=e.vnode,o=gr(e);ac(e,n,o,t),dc(e,s);const i=o?Tc(e,t):void 0;return t&&Os(!1),i}function Tc(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=en(new Proxy(e.ctx,tc));const{setup:s}=n;if(s){const o=e.setupContext=s.length>1?Ec(e):null,i=_n(e);Vt();const r=_t(s,e,0,[e.props,o]);if(Tt(),i(),wi(r)){if(r.then(Do,Do),t)return r.then(l=>{Uo(e,l,t)}).catch(l=>{Jn(l,e,0)});e.asyncDep=r}else Uo(e,r,t)}else yr(e,t)}function Uo(e,t,n){ee(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:he(t)&&(e.setupState=zi(t)),yr(e,n)}let jo;function yr(e,t,n){const s=e.type;if(!e.render){if(!t&&jo&&!s.render){const o=s.template||so(e).template;if(o){const{isCustomElement:i,compilerOptions:r}=e.appContext.config,{delimiters:l,compilerOptions:c}=s,f=ke(ke({isCustomElement:i,delimiters:l},r),c);s.render=jo(o,f)}}e.render=s.render||Oe}{const o=_n(e);Vt();try{nc(e)}finally{Tt(),o()}}}function Lc(e){return e.attrsProxy||(e.attrsProxy=new Proxy(e.attrs,{get(t,n){return Me(e,"get","$attrs"),t[n]}}))}function Ec(e){const t=n=>{e.exposed=n||{}};return{get attrs(){return Lc(e)},slots:e.slots,emit:e.emit,expose:t}}function lo(e){if(e.exposed)return e.exposeProxy||(e.exposeProxy=new Proxy(zi(en(e.exposed)),{get(t,n){if(n in t)return t[n];if(n in tn)return tn[n](e)},has(t,n){return n in t||n in tn}}))}function Mc(e,t=!0){return ee(e)?e.displayName||e.name:e.name||t&&e.__name}function Ac(e){return ee(e)&&"__vccOpts"in e}const re=(e,t)=>Pl(e,t,os);function Hn(e,t,n){const s=arguments.length;return s===2?he(t)&&!G(t)?Rn(t)?T(e,null,[t]):T(e,t):T(e,null,t):(s>3?n=Array.prototype.slice.call(arguments,2):s===3&&Rn(n)&&(n=[n]),T(e,t,n))}const Ic="3.4.21";/**
+**/function _t(e,t,n,s){try{return s?e(...s):e()}catch(o){Yn(o,t,n)}}function Fe(e,t,n,s){if(ee(e)){const i=_t(e,t,n,s);return i&&xi(i)&&i.catch(r=>{Yn(r,t,n)}),i}const o=[];for(let i=0;i>>1,o=Pe[s],i=an(o);iQe&&Pe.splice(t,1)}function Ml(e){G(e)?Ht.push(...e):(!at||!at.includes(e,e.allowRecurse?wt+1:wt))&&Ht.push(e),Gi()}function Vo(e,t,n=cn?Qe+1:0){for(;nan(n)-an(s));if(Ht.length=0,at){at.push(...t);return}for(at=t,wt=0;wte.id==null?1/0:e.id,Al=(e,t)=>{const n=an(e)-an(t);if(n===0){if(e.pre&&!t.pre)return-1;if(t.pre&&!e.pre)return 1}return n};function qi(e){Ts=!1,cn=!0,Pe.sort(Al);const t=Oe;try{for(Qe=0;Qege(S)?S.trim():S)),_&&(o=n.map(Wr))}let l,c=s[l=ds(t)]||s[l=ds(Ze(t))];!c&&i&&(c=s[l=ds(Jt(t))]),c&&Fe(c,e,6,o);const f=s[l+"Once"];if(f){if(!e.emitted)e.emitted={};else if(e.emitted[l])return;e.emitted[l]=!0,Fe(f,e,6,o)}}function Wi(e,t,n=!1){const s=t.emitsCache,o=s.get(e);if(o!==void 0)return o;const i=e.emits;let r={},l=!1;if(!ee(e)){const c=f=>{const p=Wi(f,t,!0);p&&(l=!0,ke(r,p))};!n&&t.mixins.length&&t.mixins.forEach(c),e.extends&&c(e.extends),e.mixins&&e.mixins.forEach(c)}return!i&&!l?(he(e)&&s.set(e,null),null):(G(i)?i.forEach(c=>r[c]=null):ke(r,i),he(e)&&s.set(e,r),r)}function Qn(e,t){return!e||!pn(t)?!1:(t=t.slice(2).replace(/Once$/,""),ie(e,t[0].toLowerCase()+t.slice(1))||ie(e,Jt(t))||ie(e,t))}let Se=null,Xn=null;function In(e){const t=Se;return Se=e,Xn=e&&e.type.__scopeId||null,t}function Ge(e){Xn=e}function qe(){Xn=null}function I(e,t=Se,n){if(!t||e._n)return e;const s=(...o)=>{s._d&&Fo(-1);const i=In(t);let r;try{r=e(...o)}finally{In(i),s._d&&Fo(1)}return r};return s._n=!0,s._c=!0,s._d=!0,s}function hs(e){const{type:t,vnode:n,proxy:s,withProxy:o,props:i,propsOptions:[r],slots:l,attrs:c,emit:f,render:p,renderCache:_,data:b,setupState:S,ctx:Y,inheritAttrs:q}=e;let ne,le;const ye=In(e);try{if(n.shapeFlag&4){const $=o||s,F=$;ne=je(p.call(F,$,_,i,S,b,Y)),le=c}else{const $=t;ne=je($.length>1?$(i,{attrs:c,slots:l,emit:f}):$(i,null)),le=t.props?c:Nl(c)}}catch($){sn.length=0,Yn($,e,1),ne=T(Re)}let v=ne;if(le&&q!==!1){const $=Object.keys(le),{shapeFlag:F}=v;$.length&&F&7&&(r&&$.some(Fs)&&(le=Ol(le,r)),v=vt(v,le))}return n.dirs&&(v=vt(v),v.dirs=v.dirs?v.dirs.concat(n.dirs):n.dirs),n.transition&&(v.transition=n.transition),ne=v,In(ye),ne}const Nl=e=>{let t;for(const n in e)(n==="class"||n==="style"||pn(n))&&((t||(t={}))[n]=e[n]);return t},Ol=(e,t)=>{const n={};for(const s in e)(!Fs(s)||!(s.slice(9)in t))&&(n[s]=e[s]);return n};function Rl(e,t,n){const{props:s,children:o,component:i}=e,{props:r,children:l,patchFlag:c}=t,f=i.emitsOptions;if(t.dirs||t.transition)return!0;if(n&&c>=0){if(c&1024)return!0;if(c&16)return s?To(s,r,f):!!r;if(c&8){const p=t.dynamicProps;for(let _=0;_e.__isSuspense;function Qi(e,t){t&&t.pendingBranch?G(e)?t.effects.push(...e):t.effects.push(e):Ml(e)}const Fl=Symbol.for("v-scx"),Dl=()=>Ke(Fl);function Kt(e,t){return Zn(e,null,t)}function Xi(e,t){return Zn(e,null,{flush:"post"})}const xn={};function Xe(e,t,n){return Zn(e,t,n)}function Zn(e,t,{immediate:n,deep:s,flush:o,once:i,onTrack:r,onTrigger:l}=me){if(t&&i){const N=t;t=(...Z)=>{N(...Z),F()}}const c=$e,f=N=>s===!0?N:It(N,s===!1?1:void 0);let p,_=!1,b=!1;if(Ae(e)?(p=()=>e.value,_=Mn(e)):Bt(e)?(p=()=>f(e),_=!0):G(e)?(b=!0,_=e.some(N=>Bt(N)||Mn(N)),p=()=>e.map(N=>{if(Ae(N))return N.value;if(Bt(N))return f(N);if(ee(N))return _t(N,c,2)})):ee(e)?t?p=()=>_t(e,c,2):p=()=>(S&&S(),Fe(e,c,3,[Y])):p=Oe,t&&s){const N=p;p=()=>It(N())}let S,Y=N=>{S=v.onStop=()=>{_t(N,c,4),S=v.onStop=void 0}},q;if(os)if(Y=Oe,t?n&&Fe(t,c,3,[p(),b?[]:void 0,Y]):p(),o==="sync"){const N=Dl();q=N.__watcherHandles||(N.__watcherHandles=[])}else return Oe;let ne=b?new Array(e.length).fill(xn):xn;const le=()=>{if(!(!v.active||!v.dirty))if(t){const N=v.run();(s||_||(b?N.some((Z,R)=>ht(Z,ne[R])):ht(N,ne)))&&(S&&S(),Fe(t,c,3,[N,ne===xn?void 0:b&&ne[0]===xn?[]:ne,Y]),ne=N)}else v.run()};le.allowRecurse=!!t;let ye;o==="sync"?ye=le:o==="post"?ye=()=>Ee(le,c&&c.suspense):(le.pre=!0,c&&(le.id=c.uid),ye=()=>Zs(le));const v=new js(p,Oe,ye),$=Vi(),F=()=>{v.stop(),$&&Ds($.effects,v)};return t?n?le():ne=v.run():o==="post"?Ee(v.run.bind(v),c&&c.suspense):v.run(),q&&q.push(F),F}function Ul(e,t,n){const s=this.proxy,o=ge(e)?e.includes(".")?Zi(s,e):()=>s[e]:e.bind(s,s);let i;ee(t)?i=t:(i=t.handler,n=t);const r=_n(this),l=Zn(o,i.bind(s),n);return r(),l}function Zi(e,t){const n=t.split(".");return()=>{let s=e;for(let o=0;o0){if(n>=t)return e;n++}if(s=s||new Set,s.has(e))return e;if(s.add(e),Ae(e))It(e.value,t,n,s);else if(G(e))for(let o=0;o{It(o,t,n,s)});else if($i(e))for(const o in e)It(e[o],t,n,s);return e}function Ye(e,t,n,s){const o=e.dirs,i=t&&t.dirs;for(let r=0;r{e.isMounted=!0}),or(()=>{e.isUnmounting=!0}),e}const Be=[Function,Array],er={mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:Be,onEnter:Be,onAfterEnter:Be,onEnterCancelled:Be,onBeforeLeave:Be,onLeave:Be,onAfterLeave:Be,onLeaveCancelled:Be,onBeforeAppear:Be,onAppear:Be,onAfterAppear:Be,onAppearCancelled:Be},zl={name:"BaseTransition",props:er,setup(e,{slots:t}){const n=ro(),s=jl();return()=>{const o=t.default&&nr(t.default(),!0);if(!o||!o.length)return;let i=o[0];if(o.length>1){for(const b of o)if(b.type!==Re){i=b;break}}const r=ae(e),{mode:l}=r;if(s.isLeaving)return vs(i);const c=Eo(i);if(!c)return vs(i);const f=Ls(c,r,s,n);Es(c,f);const p=n.subTree,_=p&&Eo(p);if(_&&_.type!==Re&&!$t(c,_)){const b=Ls(_,r,s,n);if(Es(_,b),l==="out-in")return s.isLeaving=!0,b.afterLeave=()=>{s.isLeaving=!1,n.update.active!==!1&&(n.effect.dirty=!0,n.update())},vs(i);l==="in-out"&&c.type!==Re&&(b.delayLeave=(S,Y,q)=>{const ne=tr(s,_);ne[String(_.key)]=_,S[ut]=()=>{Y(),S[ut]=void 0,delete f.delayedLeave},f.delayedLeave=q})}return i}}},Kl=zl;function tr(e,t){const{leavingVNodes:n}=e;let s=n.get(t.type);return s||(s=Object.create(null),n.set(t.type,s)),s}function Ls(e,t,n,s){const{appear:o,mode:i,persisted:r=!1,onBeforeEnter:l,onEnter:c,onAfterEnter:f,onEnterCancelled:p,onBeforeLeave:_,onLeave:b,onAfterLeave:S,onLeaveCancelled:Y,onBeforeAppear:q,onAppear:ne,onAfterAppear:le,onAppearCancelled:ye}=t,v=String(e.key),$=tr(n,e),F=(R,W)=>{R&&Fe(R,s,9,W)},N=(R,W)=>{const H=W[1];F(R,W),G(R)?R.every(ce=>ce.length<=1)&&H():R.length<=1&&H()},Z={mode:i,persisted:r,beforeEnter(R){let W=l;if(!n.isMounted)if(o)W=q||l;else return;R[ut]&&R[ut](!0);const H=$[v];H&&$t(e,H)&&H.el[ut]&&H.el[ut](),F(W,[R])},enter(R){let W=c,H=f,ce=p;if(!n.isMounted)if(o)W=ne||c,H=le||f,ce=ye||p;else return;let M=!1;const te=R[wn]=be=>{M||(M=!0,be?F(ce,[R]):F(H,[R]),Z.delayedLeave&&Z.delayedLeave(),R[wn]=void 0)};W?N(W,[R,te]):te()},leave(R,W){const H=String(e.key);if(R[wn]&&R[wn](!0),n.isUnmounting)return W();F(_,[R]);let ce=!1;const M=R[ut]=te=>{ce||(ce=!0,W(),te?F(Y,[R]):F(S,[R]),R[ut]=void 0,$[H]===e&&delete $[H])};$[H]=e,b?N(b,[R,M]):M()},clone(R){return Ls(R,t,n,s)}};return Z}function vs(e){if(es(e))return e=vt(e),e.children=null,e}function Eo(e){return es(e)?e.children?e.children[0]:void 0:e}function Es(e,t){e.shapeFlag&6&&e.component?Es(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 nr(e,t=!1,n){let s=[],o=0;for(let i=0;i1)for(let i=0;ike({name:e.name},t,{setup:e}))():e}const Ft=e=>!!e.type.__asyncLoader,es=e=>e.type.__isKeepAlive;function Gl(e,t){sr(e,"a",t)}function ql(e,t){sr(e,"da",t)}function sr(e,t,n=$e){const s=e.__wdc||(e.__wdc=()=>{let o=n;for(;o;){if(o.isDeactivated)return;o=o.parent}return e()});if(ts(t,s,n),n){let o=n.parent;for(;o&&o.parent;)es(o.parent.vnode)&&Wl(s,t,n,o),o=o.parent}}function Wl(e,t,n,s){const o=ts(t,e,s,!0);mt(()=>{Ds(s[t],o)},n)}function ts(e,t,n=$e,s=!1){if(n){const o=n[e]||(n[e]=[]),i=t.__weh||(t.__weh=(...r)=>{if(n.isUnmounted)return;Vt();const l=_n(n),c=Fe(t,n,e,r);return l(),Tt(),c});return s?o.unshift(i):o.push(i),i}}const ot=e=>(t,n=$e)=>(!os||e==="sp")&&ts(e,(...s)=>t(...s),n),Jl=ot("bm"),De=ot("m"),Yl=ot("bu"),no=ot("u"),or=ot("bum"),mt=ot("um"),Ql=ot("sp"),Xl=ot("rtg"),Zl=ot("rtc");function ec(e,t=$e){ts("ec",e,t)}function Ce(e,t,n,s){let o;const i=n&&n[s];if(G(e)||ge(e)){o=new Array(e.length);for(let r=0,l=e.length;rt(r,l,void 0,i&&i[l]));else{const r=Object.keys(e);o=new Array(r.length);for(let l=0,c=r.length;lRn(t)?!(t.type===Re||t.type===Q&&!ir(t.children)):!0)?e:null}const Ms=e=>e?gr(e)?lo(e)||e.proxy:Ms(e.parent):null,tn=ke(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=>Ms(e.parent),$root:e=>Ms(e.root),$emit:e=>e.emit,$options:e=>so(e),$forceUpdate:e=>e.f||(e.f=()=>{e.effect.dirty=!0,Zs(e.update)}),$nextTick:e=>e.n||(e.n=Xs.bind(e.proxy)),$watch:e=>Ul.bind(e)}),ms=(e,t)=>e!==me&&!e.__isScriptSetup&&ie(e,t),tc={get({_:e},t){const{ctx:n,setupState:s,data:o,props:i,accessCache:r,type:l,appContext:c}=e;let f;if(t[0]!=="$"){const S=r[t];if(S!==void 0)switch(S){case 1:return s[t];case 2:return o[t];case 4:return n[t];case 3:return i[t]}else{if(ms(s,t))return r[t]=1,s[t];if(o!==me&&ie(o,t))return r[t]=2,o[t];if((f=e.propsOptions[0])&&ie(f,t))return r[t]=3,i[t];if(n!==me&&ie(n,t))return r[t]=4,n[t];As&&(r[t]=0)}}const p=tn[t];let _,b;if(p)return t==="$attrs"&&Me(e,"get",t),p(e);if((_=l.__cssModules)&&(_=_[t]))return _;if(n!==me&&ie(n,t))return r[t]=4,n[t];if(b=c.config.globalProperties,ie(b,t))return b[t]},set({_:e},t,n){const{data:s,setupState:o,ctx:i}=e;return ms(o,t)?(o[t]=n,!0):s!==me&&ie(s,t)?(s[t]=n,!0):ie(e.props,t)||t[0]==="$"&&t.slice(1)in e?!1:(i[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:s,appContext:o,propsOptions:i}},r){let l;return!!n[r]||e!==me&&ie(e,r)||ms(t,r)||(l=i[0])&&ie(l,r)||ie(s,r)||ie(tn,r)||ie(o.config.globalProperties,r)},defineProperty(e,t,n){return n.get!=null?e._.accessCache[t]=0:ie(n,"value")&&this.set(e,t,n.value,null),Reflect.defineProperty(e,t,n)}};function Mo(e){return G(e)?e.reduce((t,n)=>(t[n]=null,t),{}):e}let As=!0;function nc(e){const t=so(e),n=e.proxy,s=e.ctx;As=!1,t.beforeCreate&&Ao(t.beforeCreate,e,"bc");const{data:o,computed:i,methods:r,watch:l,provide:c,inject:f,created:p,beforeMount:_,mounted:b,beforeUpdate:S,updated:Y,activated:q,deactivated:ne,beforeDestroy:le,beforeUnmount:ye,destroyed:v,unmounted:$,render:F,renderTracked:N,renderTriggered:Z,errorCaptured:R,serverPrefetch:W,expose:H,inheritAttrs:ce,components:M,directives:te,filters:be}=t;if(f&&sc(f,s,null),r)for(const oe in r){const U=r[oe];ee(U)&&(s[oe]=U.bind(n))}if(o){const oe=o.call(n,n);he(oe)&&(e.data=Jn(oe))}if(As=!0,i)for(const oe in i){const U=i[oe],tt=ee(U)?U.bind(n,n):ee(U.get)?U.get.bind(n,n):Oe,hn=!ee(U)&&ee(U.set)?U.set.bind(n):Oe,yt=re({get:tt,set:hn});Object.defineProperty(s,oe,{enumerable:!0,configurable:!0,get:()=>yt.value,set:We=>yt.value=We})}if(l)for(const oe in l)rr(l[oe],s,n,oe);if(c){const oe=ee(c)?c.call(n):c;Reflect.ownKeys(oe).forEach(U=>{ns(U,oe[U])})}p&&Ao(p,e,"c");function j(oe,U){G(U)?U.forEach(tt=>oe(tt.bind(n))):U&&oe(U.bind(n))}if(j(Jl,_),j(De,b),j(Yl,S),j(no,Y),j(Gl,q),j(ql,ne),j(ec,R),j(Zl,N),j(Xl,Z),j(or,ye),j(mt,$),j(Ql,W),G(H))if(H.length){const oe=e.exposed||(e.exposed={});H.forEach(U=>{Object.defineProperty(oe,U,{get:()=>n[U],set:tt=>n[U]=tt})})}else e.exposed||(e.exposed={});F&&e.render===Oe&&(e.render=F),ce!=null&&(e.inheritAttrs=ce),M&&(e.components=M),te&&(e.directives=te)}function sc(e,t,n=Oe){G(e)&&(e=Is(e));for(const s in e){const o=e[s];let i;he(o)?"default"in o?i=Ke(o.from||s,o.default,!0):i=Ke(o.from||s):i=Ke(o),Ae(i)?Object.defineProperty(t,s,{enumerable:!0,configurable:!0,get:()=>i.value,set:r=>i.value=r}):t[s]=i}}function Ao(e,t,n){Fe(G(e)?e.map(s=>s.bind(t.proxy)):e.bind(t.proxy),t,n)}function rr(e,t,n,s){const o=s.includes(".")?Zi(n,s):()=>n[s];if(ge(e)){const i=t[e];ee(i)&&Xe(o,i)}else if(ee(e))Xe(o,e.bind(n));else if(he(e))if(G(e))e.forEach(i=>rr(i,t,n,s));else{const i=ee(e.handler)?e.handler.bind(n):t[e.handler];ee(i)&&Xe(o,i,e)}}function so(e){const t=e.type,{mixins:n,extends:s}=t,{mixins:o,optionsCache:i,config:{optionMergeStrategies:r}}=e.appContext,l=i.get(t);let c;return l?c=l:!o.length&&!n&&!s?c=t:(c={},o.length&&o.forEach(f=>Nn(c,f,r,!0)),Nn(c,t,r)),he(t)&&i.set(t,c),c}function Nn(e,t,n,s=!1){const{mixins:o,extends:i}=t;i&&Nn(e,i,n,!0),o&&o.forEach(r=>Nn(e,r,n,!0));for(const r in t)if(!(s&&r==="expose")){const l=oc[r]||n&&n[r];e[r]=l?l(e[r],t[r]):t[r]}return e}const oc={data:Io,props:No,emits:No,methods:Zt,computed:Zt,beforeCreate:Ve,created:Ve,beforeMount:Ve,mounted:Ve,beforeUpdate:Ve,updated:Ve,beforeDestroy:Ve,beforeUnmount:Ve,destroyed:Ve,unmounted:Ve,activated:Ve,deactivated:Ve,errorCaptured:Ve,serverPrefetch:Ve,components:Zt,directives:Zt,watch:rc,provide:Io,inject:ic};function Io(e,t){return t?e?function(){return ke(ee(e)?e.call(this,this):e,ee(t)?t.call(this,this):t)}:t:e}function ic(e,t){return Zt(Is(e),Is(t))}function Is(e){if(G(e)){const t={};for(let n=0;n1)return n&&ee(t)?t.call(s&&s.proxy):t}}function ac(e,t,n,s=!1){const o={},i={};En(i,ss,1),e.propsDefaults=Object.create(null),cr(e,t,o,i);for(const r in e.propsOptions[0])r in o||(o[r]=void 0);n?e.props=s?o:$l(o):e.type.props?e.props=o:e.props=i,e.attrs=i}function uc(e,t,n,s){const{props:o,attrs:i,vnode:{patchFlag:r}}=e,l=ae(o),[c]=e.propsOptions;let f=!1;if((s||r>0)&&!(r&16)){if(r&8){const p=e.vnode.dynamicProps;for(let _=0;_{c=!0;const[b,S]=ar(_,t,!0);ke(r,b),S&&l.push(...S)};!n&&t.mixins.length&&t.mixins.forEach(p),e.extends&&p(e.extends),e.mixins&&e.mixins.forEach(p)}if(!i&&!c)return he(e)&&s.set(e,Nt),Nt;if(G(i))for(let p=0;p-1,S[1]=q<0||Y-1||ie(S,"default"))&&l.push(_)}}}const f=[r,l];return he(e)&&s.set(e,f),f}function Oo(e){return e[0]!=="$"&&!Rt(e)}function Ro(e){return e===null?"null":typeof e=="function"?e.name||"":typeof e=="object"&&e.constructor&&e.constructor.name||""}function Bo(e,t){return Ro(e)===Ro(t)}function Ho(e,t){return G(t)?t.findIndex(n=>Bo(n,e)):ee(t)&&Bo(t,e)?0:-1}const ur=e=>e[0]==="_"||e==="$stable",oo=e=>G(e)?e.map(je):[je(e)],fc=(e,t,n)=>{if(t._n)return t;const s=I((...o)=>oo(t(...o)),n);return s._c=!1,s},fr=(e,t,n)=>{const s=e._ctx;for(const o in e){if(ur(o))continue;const i=e[o];if(ee(i))t[o]=fc(o,i,s);else if(i!=null){const r=oo(i);t[o]=()=>r}}},dr=(e,t)=>{const n=oo(t);e.slots.default=()=>n},dc=(e,t)=>{if(e.vnode.shapeFlag&32){const n=t._;n?(e.slots=ae(t),En(t,"_",n)):fr(t,e.slots={})}else e.slots={},t&&dr(e,t);En(e.slots,ss,1)},pc=(e,t,n)=>{const{vnode:s,slots:o}=e;let i=!0,r=me;if(s.shapeFlag&32){const l=t._;l?n&&l===1?i=!1:(ke(o,t),!n&&l===1&&delete o._):(i=!t.$stable,fr(t,o)),r=t}else t&&(dr(e,t),r={default:1});if(i)for(const l in o)!ur(l)&&r[l]==null&&delete o[l]};function On(e,t,n,s,o=!1){if(G(e)){e.forEach((b,S)=>On(b,t&&(G(t)?t[S]:t),n,s,o));return}if(Ft(s)&&!o)return;const i=s.shapeFlag&4?lo(s.component)||s.component.proxy:s.el,r=o?null:i,{i:l,r:c}=e,f=t&&t.r,p=l.refs===me?l.refs={}:l.refs,_=l.setupState;if(f!=null&&f!==c&&(ge(f)?(p[f]=null,ie(_,f)&&(_[f]=null)):Ae(f)&&(f.value=null)),ee(c))_t(c,l,12,[r,p]);else{const b=ge(c),S=Ae(c);if(b||S){const Y=()=>{if(e.f){const q=b?ie(_,c)?_[c]:p[c]:c.value;o?G(q)&&Ds(q,i):G(q)?q.includes(i)||q.push(i):b?(p[c]=[i],ie(_,c)&&(_[c]=p[c])):(c.value=[i],e.k&&(p[e.k]=c.value))}else b?(p[c]=r,ie(_,c)&&(_[c]=r)):S&&(c.value=r,e.k&&(p[e.k]=r))};r?(Y.id=-1,Ee(Y,n)):Y()}}}let rt=!1;const _c=e=>e.namespaceURI.includes("svg")&&e.tagName!=="foreignObject",hc=e=>e.namespaceURI.includes("MathML"),$n=e=>{if(_c(e))return"svg";if(hc(e))return"mathml"},Pn=e=>e.nodeType===8;function vc(e){const{mt:t,p:n,o:{patchProp:s,createText:o,nextSibling:i,parentNode:r,remove:l,insert:c,createComment:f}}=e,p=(v,$)=>{if(!$.hasChildNodes()){n(null,v,$),An(),$._vnode=v;return}rt=!1,_($.firstChild,v,null,null,null),An(),$._vnode=v,rt&&console.error("Hydration completed but contains mismatches.")},_=(v,$,F,N,Z,R=!1)=>{const W=Pn(v)&&v.data==="[",H=()=>q(v,$,F,N,Z,W),{type:ce,ref:M,shapeFlag:te,patchFlag:be}=$;let we=v.nodeType;$.el=v,be===-2&&(R=!1,$.dynamicChildren=null);let j=null;switch(ce){case Gt:we!==3?$.children===""?(c($.el=o(""),r(v),v),j=v):j=H():(v.data!==$.children&&(rt=!0,v.data=$.children),j=i(v));break;case Re:ye(v)?(j=i(v),le($.el=v.content.firstChild,v,F)):we!==8||W?j=H():j=i(v);break;case Dt:if(W&&(v=i(v),we=v.nodeType),we===1||we===3){j=v;const oe=!$.children.length;for(let U=0;U<$.staticCount;U++)oe&&($.children+=j.nodeType===1?j.outerHTML:j.data),U===$.staticCount-1&&($.anchor=j),j=i(j);return W?i(j):j}else H();break;case Q:W?j=Y(v,$,F,N,Z,R):j=H();break;default:if(te&1)(we!==1||$.type.toLowerCase()!==v.tagName.toLowerCase())&&!ye(v)?j=H():j=b(v,$,F,N,Z,R);else if(te&6){$.slotScopeIds=Z;const oe=r(v);if(W?j=ne(v):Pn(v)&&v.data==="teleport start"?j=ne(v,v.data,"teleport end"):j=i(v),t($,oe,null,F,N,$n(oe),R),Ft($)){let U;W?(U=T(Q),U.anchor=j?j.previousSibling:oe.lastChild):U=v.nodeType===3?Le(""):T("div"),U.el=v,$.component.subTree=U}}else te&64?we!==8?j=H():j=$.type.hydrate(v,$,F,N,Z,R,e,S):te&128&&(j=$.type.hydrate(v,$,F,N,$n(r(v)),Z,R,e,_))}return M!=null&&On(M,null,N,$),j},b=(v,$,F,N,Z,R)=>{R=R||!!$.dynamicChildren;const{type:W,props:H,patchFlag:ce,shapeFlag:M,dirs:te,transition:be}=$,we=W==="input"||W==="option";if(we||ce!==-1){te&&Ye($,null,F,"created");let j=!1;if(ye(v)){j=pr(N,be)&&F&&F.vnode.props&&F.vnode.props.appear;const U=v.content.firstChild;j&&be.beforeEnter(U),le(U,v,F),$.el=v=U}if(M&16&&!(H&&(H.innerHTML||H.textContent))){let U=S(v.firstChild,$,v,F,N,Z,R);for(;U;){rt=!0;const tt=U;U=U.nextSibling,l(tt)}}else M&8&&v.textContent!==$.children&&(rt=!0,v.textContent=$.children);if(H)if(we||!R||ce&48)for(const U in H)(we&&(U.endsWith("value")||U==="indeterminate")||pn(U)&&!Rt(U)||U[0]===".")&&s(v,U,null,H[U],void 0,void 0,F);else H.onClick&&s(v,"onClick",null,H.onClick,void 0,void 0,F);let oe;(oe=H&&H.onVnodeBeforeMount)&&He(oe,F,$),te&&Ye($,null,F,"beforeMount"),((oe=H&&H.onVnodeMounted)||te||j)&&Qi(()=>{oe&&He(oe,F,$),j&&be.enter(v),te&&Ye($,null,F,"mounted")},N)}return v.nextSibling},S=(v,$,F,N,Z,R,W)=>{W=W||!!$.dynamicChildren;const H=$.children,ce=H.length;for(let M=0;M{const{slotScopeIds:W}=$;W&&(Z=Z?Z.concat(W):W);const H=r(v),ce=S(i(v),$,H,F,N,Z,R);return ce&&Pn(ce)&&ce.data==="]"?i($.anchor=ce):(rt=!0,c($.anchor=f("]"),H,ce),ce)},q=(v,$,F,N,Z,R)=>{if(rt=!0,$.el=null,R){const ce=ne(v);for(;;){const M=i(v);if(M&&M!==ce)l(M);else break}}const W=i(v),H=r(v);return l(v),n(null,$,H,W,F,N,$n(H),Z),W},ne=(v,$="[",F="]")=>{let N=0;for(;v;)if(v=i(v),v&&Pn(v)&&(v.data===$&&N++,v.data===F)){if(N===0)return i(v);N--}return v},le=(v,$,F)=>{const N=$.parentNode;N&&N.replaceChild(v,$);let Z=F;for(;Z;)Z.vnode.el===$&&(Z.vnode.el=Z.subTree.el=v),Z=Z.parent},ye=v=>v.nodeType===1&&v.tagName.toLowerCase()==="template";return[p,_]}const Ee=Qi;function mc(e){return gc(e,vc)}function gc(e,t){const n=Pi();n.__VUE__=!0;const{insert:s,remove:o,patchProp:i,createElement:r,createText:l,createComment:c,setText:f,setElementText:p,parentNode:_,nextSibling:b,setScopeId:S=Oe,insertStaticContent:Y}=e,q=(a,u,h,k=null,x=null,C=null,E=void 0,P=null,V=!!u.dynamicChildren)=>{if(a===u)return;a&&!$t(a,u)&&(k=vn(a),We(a,x,C,!0),a=null),u.patchFlag===-2&&(V=!1,u.dynamicChildren=null);const{type:w,ref:A,shapeFlag:z}=u;switch(w){case Gt:ne(a,u,h,k);break;case Re:le(a,u,h,k);break;case Dt:a==null&&ye(u,h,k,E);break;case Q:M(a,u,h,k,x,C,E,P,V);break;default:z&1?F(a,u,h,k,x,C,E,P,V):z&6?te(a,u,h,k,x,C,E,P,V):(z&64||z&128)&&w.process(a,u,h,k,x,C,E,P,V,Mt)}A!=null&&x&&On(A,a&&a.ref,C,u||a,!u)},ne=(a,u,h,k)=>{if(a==null)s(u.el=l(u.children),h,k);else{const x=u.el=a.el;u.children!==a.children&&f(x,u.children)}},le=(a,u,h,k)=>{a==null?s(u.el=c(u.children||""),h,k):u.el=a.el},ye=(a,u,h,k)=>{[a.el,a.anchor]=Y(a.children,u,h,k,a.el,a.anchor)},v=({el:a,anchor:u},h,k)=>{let x;for(;a&&a!==u;)x=b(a),s(a,h,k),a=x;s(u,h,k)},$=({el:a,anchor:u})=>{let h;for(;a&&a!==u;)h=b(a),o(a),a=h;o(u)},F=(a,u,h,k,x,C,E,P,V)=>{u.type==="svg"?E="svg":u.type==="math"&&(E="mathml"),a==null?N(u,h,k,x,C,E,P,V):W(a,u,x,C,E,P,V)},N=(a,u,h,k,x,C,E,P)=>{let V,w;const{props:A,shapeFlag:z,transition:D,dirs:J}=a;if(V=a.el=r(a.type,C,A&&A.is,A),z&8?p(V,a.children):z&16&&R(a.children,V,null,k,x,gs(a,C),E,P),J&&Ye(a,null,k,"created"),Z(V,a,a.scopeId,E,k),A){for(const de in A)de!=="value"&&!Rt(de)&&i(V,de,null,A[de],C,a.children,k,x,nt);"value"in A&&i(V,"value",null,A.value,C),(w=A.onVnodeBeforeMount)&&He(w,k,a)}J&&Ye(a,null,k,"beforeMount");const se=pr(x,D);se&&D.beforeEnter(V),s(V,u,h),((w=A&&A.onVnodeMounted)||se||J)&&Ee(()=>{w&&He(w,k,a),se&&D.enter(V),J&&Ye(a,null,k,"mounted")},x)},Z=(a,u,h,k,x)=>{if(h&&S(a,h),k)for(let C=0;C{for(let w=V;w{const P=u.el=a.el;let{patchFlag:V,dynamicChildren:w,dirs:A}=u;V|=a.patchFlag&16;const z=a.props||me,D=u.props||me;let J;if(h&&bt(h,!1),(J=D.onVnodeBeforeUpdate)&&He(J,h,u,a),A&&Ye(u,a,h,"beforeUpdate"),h&&bt(h,!0),w?H(a.dynamicChildren,w,P,h,k,gs(u,x),C):E||U(a,u,P,null,h,k,gs(u,x),C,!1),V>0){if(V&16)ce(P,u,z,D,h,k,x);else if(V&2&&z.class!==D.class&&i(P,"class",null,D.class,x),V&4&&i(P,"style",z.style,D.style,x),V&8){const se=u.dynamicProps;for(let de=0;de{J&&He(J,h,u,a),A&&Ye(u,a,h,"updated")},k)},H=(a,u,h,k,x,C,E)=>{for(let P=0;P{if(h!==k){if(h!==me)for(const P in h)!Rt(P)&&!(P in k)&&i(a,P,h[P],null,E,u.children,x,C,nt);for(const P in k){if(Rt(P))continue;const V=k[P],w=h[P];V!==w&&P!=="value"&&i(a,P,w,V,E,u.children,x,C,nt)}"value"in k&&i(a,"value",h.value,k.value,E)}},M=(a,u,h,k,x,C,E,P,V)=>{const w=u.el=a?a.el:l(""),A=u.anchor=a?a.anchor:l("");let{patchFlag:z,dynamicChildren:D,slotScopeIds:J}=u;J&&(P=P?P.concat(J):J),a==null?(s(w,h,k),s(A,h,k),R(u.children||[],h,A,x,C,E,P,V)):z>0&&z&64&&D&&a.dynamicChildren?(H(a.dynamicChildren,D,h,x,C,E,P),(u.key!=null||x&&u===x.subTree)&&_r(a,u,!0)):U(a,u,h,A,x,C,E,P,V)},te=(a,u,h,k,x,C,E,P,V)=>{u.slotScopeIds=P,a==null?u.shapeFlag&512?x.ctx.activate(u,h,k,E,V):be(u,h,k,x,C,E,V):we(a,u,V)},be=(a,u,h,k,x,C,E)=>{const P=a.component=Cc(a,k,x);if(es(a)&&(P.ctx.renderer=Mt),Vc(P),P.asyncDep){if(x&&x.registerDep(P,j),!a.el){const V=P.subTree=T(Re);le(null,V,u,h)}}else j(P,a,u,h,x,C,E)},we=(a,u,h)=>{const k=u.component=a.component;if(Rl(a,u,h))if(k.asyncDep&&!k.asyncResolved){oe(k,u,h);return}else k.next=u,El(k.update),k.effect.dirty=!0,k.update();else u.el=a.el,k.vnode=u},j=(a,u,h,k,x,C,E)=>{const P=()=>{if(a.isMounted){let{next:A,bu:z,u:D,parent:J,vnode:se}=a;{const At=hr(a);if(At){A&&(A.el=se.el,oe(a,A,E)),At.asyncDep.then(()=>{a.isUnmounted||P()});return}}let de=A,ve;bt(a,!1),A?(A.el=se.el,oe(a,A,E)):A=se,z&&ps(z),(ve=A.props&&A.props.onVnodeBeforeUpdate)&&He(ve,J,A,se),bt(a,!0);const xe=hs(a),Ue=a.subTree;a.subTree=xe,q(Ue,xe,_(Ue.el),vn(Ue),a,x,C),A.el=xe.el,de===null&&Bl(a,xe.el),D&&Ee(D,x),(ve=A.props&&A.props.onVnodeUpdated)&&Ee(()=>He(ve,J,A,se),x)}else{let A;const{el:z,props:D}=u,{bm:J,m:se,parent:de}=a,ve=Ft(u);if(bt(a,!1),J&&ps(J),!ve&&(A=D&&D.onVnodeBeforeMount)&&He(A,de,u),bt(a,!0),z&&fs){const xe=()=>{a.subTree=hs(a),fs(z,a.subTree,a,x,null)};ve?u.type.__asyncLoader().then(()=>!a.isUnmounted&&xe()):xe()}else{const xe=a.subTree=hs(a);q(null,xe,h,k,a,x,C),u.el=xe.el}if(se&&Ee(se,x),!ve&&(A=D&&D.onVnodeMounted)){const xe=u;Ee(()=>He(A,de,xe),x)}(u.shapeFlag&256||de&&Ft(de.vnode)&&de.vnode.shapeFlag&256)&&a.a&&Ee(a.a,x),a.isMounted=!0,u=h=k=null}},V=a.effect=new js(P,Oe,()=>Zs(w),a.scope),w=a.update=()=>{V.dirty&&V.run()};w.id=a.uid,bt(a,!0),w()},oe=(a,u,h)=>{u.component=a;const k=a.vnode.props;a.vnode=u,a.next=null,uc(a,u.props,k,h),pc(a,u.children,h),Vt(),Vo(a),Tt()},U=(a,u,h,k,x,C,E,P,V=!1)=>{const w=a&&a.children,A=a?a.shapeFlag:0,z=u.children,{patchFlag:D,shapeFlag:J}=u;if(D>0){if(D&128){hn(w,z,h,k,x,C,E,P,V);return}else if(D&256){tt(w,z,h,k,x,C,E,P,V);return}}J&8?(A&16&&nt(w,x,C),z!==w&&p(h,z)):A&16?J&16?hn(w,z,h,k,x,C,E,P,V):nt(w,x,C,!0):(A&8&&p(h,""),J&16&&R(z,h,k,x,C,E,P,V))},tt=(a,u,h,k,x,C,E,P,V)=>{a=a||Nt,u=u||Nt;const w=a.length,A=u.length,z=Math.min(w,A);let D;for(D=0;DA?nt(a,x,C,!0,!1,z):R(u,h,k,x,C,E,P,V,z)},hn=(a,u,h,k,x,C,E,P,V)=>{let w=0;const A=u.length;let z=a.length-1,D=A-1;for(;w<=z&&w<=D;){const J=a[w],se=u[w]=V?ft(u[w]):je(u[w]);if($t(J,se))q(J,se,h,null,x,C,E,P,V);else break;w++}for(;w<=z&&w<=D;){const J=a[z],se=u[D]=V?ft(u[D]):je(u[D]);if($t(J,se))q(J,se,h,null,x,C,E,P,V);else break;z--,D--}if(w>z){if(w<=D){const J=D+1,se=JD)for(;w<=z;)We(a[w],x,C,!0),w++;else{const J=w,se=w,de=new Map;for(w=se;w<=D;w++){const Ie=u[w]=V?ft(u[w]):je(u[w]);Ie.key!=null&&de.set(Ie.key,w)}let ve,xe=0;const Ue=D-se+1;let At=!1,mo=0;const Qt=new Array(Ue);for(w=0;w=Ue){We(Ie,x,C,!0);continue}let Je;if(Ie.key!=null)Je=de.get(Ie.key);else for(ve=se;ve<=D;ve++)if(Qt[ve-se]===0&&$t(Ie,u[ve])){Je=ve;break}Je===void 0?We(Ie,x,C,!0):(Qt[Je-se]=w+1,Je>=mo?mo=Je:At=!0,q(Ie,u[Je],h,null,x,C,E,P,V),xe++)}const go=At?yc(Qt):Nt;for(ve=go.length-1,w=Ue-1;w>=0;w--){const Ie=se+w,Je=u[Ie],yo=Ie+1{const{el:C,type:E,transition:P,children:V,shapeFlag:w}=a;if(w&6){yt(a.component.subTree,u,h,k);return}if(w&128){a.suspense.move(u,h,k);return}if(w&64){E.move(a,u,h,Mt);return}if(E===Q){s(C,u,h);for(let z=0;zP.enter(C),x);else{const{leave:z,delayLeave:D,afterLeave:J}=P,se=()=>s(C,u,h),de=()=>{z(C,()=>{se(),J&&J()})};D?D(C,se,de):de()}else s(C,u,h)},We=(a,u,h,k=!1,x=!1)=>{const{type:C,props:E,ref:P,children:V,dynamicChildren:w,shapeFlag:A,patchFlag:z,dirs:D}=a;if(P!=null&&On(P,null,h,a,!0),A&256){u.ctx.deactivate(a);return}const J=A&1&&D,se=!Ft(a);let de;if(se&&(de=E&&E.onVnodeBeforeUnmount)&&He(de,u,a),A&6)Ur(a.component,h,k);else{if(A&128){a.suspense.unmount(h,k);return}J&&Ye(a,null,u,"beforeUnmount"),A&64?a.type.remove(a,u,h,x,Mt,k):w&&(C!==Q||z>0&&z&64)?nt(w,u,h,!1,!0):(C===Q&&z&384||!x&&A&16)&&nt(V,u,h),k&&ho(a)}(se&&(de=E&&E.onVnodeUnmounted)||J)&&Ee(()=>{de&&He(de,u,a),J&&Ye(a,null,u,"unmounted")},h)},ho=a=>{const{type:u,el:h,anchor:k,transition:x}=a;if(u===Q){Dr(h,k);return}if(u===Dt){$(a);return}const C=()=>{o(h),x&&!x.persisted&&x.afterLeave&&x.afterLeave()};if(a.shapeFlag&1&&x&&!x.persisted){const{leave:E,delayLeave:P}=x,V=()=>E(h,C);P?P(a.el,C,V):V()}else C()},Dr=(a,u)=>{let h;for(;a!==u;)h=b(a),o(a),a=h;o(u)},Ur=(a,u,h)=>{const{bum:k,scope:x,update:C,subTree:E,um:P}=a;k&&ps(k),x.stop(),C&&(C.active=!1,We(E,a,u,h)),P&&Ee(P,u),Ee(()=>{a.isUnmounted=!0},u),u&&u.pendingBranch&&!u.isUnmounted&&a.asyncDep&&!a.asyncResolved&&a.suspenseId===u.pendingId&&(u.deps--,u.deps===0&&u.resolve())},nt=(a,u,h,k=!1,x=!1,C=0)=>{for(let E=C;Ea.shapeFlag&6?vn(a.component.subTree):a.shapeFlag&128?a.suspense.next():b(a.anchor||a.el);let as=!1;const vo=(a,u,h)=>{a==null?u._vnode&&We(u._vnode,null,null,!0):q(u._vnode||null,a,u,null,null,null,h),as||(as=!0,Vo(),An(),as=!1),u._vnode=a},Mt={p:q,um:We,m:yt,r:ho,mt:be,mc:R,pc:U,pbc:H,n:vn,o:e};let us,fs;return t&&([us,fs]=t(Mt)),{render:vo,hydrate:us,createApp:cc(vo,us)}}function gs({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 bt({effect:e,update:t},n){e.allowRecurse=t.allowRecurse=n}function pr(e,t){return(!e||e&&!e.pendingBranch)&&t&&!t.persisted}function _r(e,t,n=!1){const s=e.children,o=t.children;if(G(s)&&G(o))for(let i=0;i>1,e[n[l]]0&&(t[s]=n[i-1]),n[i]=s)}}for(i=n.length,r=n[i-1];i-- >0;)n[i]=r,r=t[r];return n}function hr(e){const t=e.subTree.component;if(t)return t.asyncDep&&!t.asyncResolved?t:hr(t)}const bc=e=>e.__isTeleport,Q=Symbol.for("v-fgt"),Gt=Symbol.for("v-txt"),Re=Symbol.for("v-cmt"),Dt=Symbol.for("v-stc"),sn=[];let ze=null;function d(e=!1){sn.push(ze=e?null:[])}function kc(){sn.pop(),ze=sn[sn.length-1]||null}let un=1;function Fo(e){un+=e}function vr(e){return e.dynamicChildren=un>0?ze||Nt:null,kc(),un>0&&ze&&ze.push(e),e}function m(e,t,n,s,o,i){return vr(g(e,t,n,s,o,i,!0))}function X(e,t,n,s,o){return vr(T(e,t,n,s,o,!0))}function Rn(e){return e?e.__v_isVNode===!0:!1}function $t(e,t){return e.type===t.type&&e.key===t.key}const ss="__vInternal",mr=({key:e})=>e??null,Vn=({ref:e,ref_key:t,ref_for:n})=>(typeof e=="number"&&(e=""+e),e!=null?ge(e)||Ae(e)||ee(e)?{i:Se,r:e,k:t,f:!!n}:e:null);function g(e,t=null,n=null,s=0,o=null,i=e===Q?0:1,r=!1,l=!1){const c={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&mr(t),ref:t&&Vn(t),scopeId:Xn,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:i,patchFlag:s,dynamicProps:o,dynamicChildren:null,appContext:null,ctx:Se};return l?(io(c,n),i&128&&e.normalize(c)):n&&(c.shapeFlag|=ge(n)?8:16),un>0&&!r&&ze&&(c.patchFlag>0||i&6)&&c.patchFlag!==32&&ze.push(c),c}const T=xc;function xc(e,t=null,n=null,s=0,o=null,i=!1){if((!e||e===Ji)&&(e=Re),Rn(e)){const l=vt(e,t,!0);return n&&io(l,n),un>0&&!i&&ze&&(l.shapeFlag&6?ze[ze.indexOf(e)]=l:ze.push(l)),l.patchFlag|=-2,l}if(Ac(e)&&(e=e.__vccOpts),t){t=wc(t);let{class:l,style:c}=t;l&&!ge(l)&&(t.class=pe(l)),he(c)&&(Fi(c)&&!G(c)&&(c=ke({},c)),t.style=qn(c))}const r=ge(e)?1:Hl(e)?128:bc(e)?64:he(e)?4:ee(e)?2:0;return g(e,t,n,s,o,r,i,!0)}function wc(e){return e?Fi(e)||ss in e?ke({},e):e:null}function vt(e,t,n=!1){const{props:s,ref:o,patchFlag:i,children:r}=e,l=t?Tn(s||{},t):s;return{__v_isVNode:!0,__v_skip:!0,type:e.type,props:l,key:l&&mr(l),ref:t&&t.ref?n&&o?G(o)?o.concat(Vn(t)):[o,Vn(t)]:Vn(t):o,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:r,target:e.target,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==Q?i===-1?16:i|16:i,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:e.transition,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&vt(e.ssContent),ssFallback:e.ssFallback&&vt(e.ssFallback),el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce}}function Le(e=" ",t=0){return T(Gt,null,e,t)}function $c(e,t){const n=T(Dt,null,e);return n.staticCount=t,n}function K(e="",t=!1){return t?(d(),X(Re,null,e)):T(Re,null,e)}function je(e){return e==null||typeof e=="boolean"?T(Re):G(e)?T(Q,null,e.slice()):typeof e=="object"?ft(e):T(Gt,null,String(e))}function ft(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:vt(e)}function io(e,t){let n=0;const{shapeFlag:s}=e;if(t==null)t=null;else if(G(t))n=16;else if(typeof t=="object")if(s&65){const o=t.default;o&&(o._c&&(o._d=!1),io(e,o()),o._c&&(o._d=!0));return}else{n=32;const o=t._;!o&&!(ss in t)?t._ctx=Se:o===3&&Se&&(Se.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else ee(t)?(t={default:t,_ctx:Se},n=32):(t=String(t),s&64?(n=16,t=[Le(t)]):n=8);e.children=t,e.shapeFlag|=n}function Tn(...e){const t={};for(let n=0;n$e||Se;let Bn,Os;{const e=Pi(),t=(n,s)=>{let o;return(o=e[n])||(o=e[n]=[]),o.push(s),i=>{o.length>1?o.forEach(r=>r(i)):o[0](i)}};Bn=t("__VUE_INSTANCE_SETTERS__",n=>$e=n),Os=t("__VUE_SSR_SETTERS__",n=>os=n)}const _n=e=>{const t=$e;return Bn(e),e.scope.on(),()=>{e.scope.off(),Bn(t)}},Do=()=>{$e&&$e.scope.off(),Bn(null)};function gr(e){return e.vnode.shapeFlag&4}let os=!1;function Vc(e,t=!1){t&&Os(t);const{props:n,children:s}=e.vnode,o=gr(e);ac(e,n,o,t),dc(e,s);const i=o?Tc(e,t):void 0;return t&&Os(!1),i}function Tc(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=en(new Proxy(e.ctx,tc));const{setup:s}=n;if(s){const o=e.setupContext=s.length>1?Ec(e):null,i=_n(e);Vt();const r=_t(s,e,0,[e.props,o]);if(Tt(),i(),xi(r)){if(r.then(Do,Do),t)return r.then(l=>{Uo(e,l,t)}).catch(l=>{Yn(l,e,0)});e.asyncDep=r}else Uo(e,r,t)}else yr(e,t)}function Uo(e,t,n){ee(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:he(t)&&(e.setupState=zi(t)),yr(e,n)}let jo;function yr(e,t,n){const s=e.type;if(!e.render){if(!t&&jo&&!s.render){const o=s.template||so(e).template;if(o){const{isCustomElement:i,compilerOptions:r}=e.appContext.config,{delimiters:l,compilerOptions:c}=s,f=ke(ke({isCustomElement:i,delimiters:l},r),c);s.render=jo(o,f)}}e.render=s.render||Oe}{const o=_n(e);Vt();try{nc(e)}finally{Tt(),o()}}}function Lc(e){return e.attrsProxy||(e.attrsProxy=new Proxy(e.attrs,{get(t,n){return Me(e,"get","$attrs"),t[n]}}))}function Ec(e){const t=n=>{e.exposed=n||{}};return{get attrs(){return Lc(e)},slots:e.slots,emit:e.emit,expose:t}}function lo(e){if(e.exposed)return e.exposeProxy||(e.exposeProxy=new Proxy(zi(en(e.exposed)),{get(t,n){if(n in t)return t[n];if(n in tn)return tn[n](e)},has(t,n){return n in t||n in tn}}))}function Mc(e,t=!0){return ee(e)?e.displayName||e.name:e.name||t&&e.__name}function Ac(e){return ee(e)&&"__vccOpts"in e}const re=(e,t)=>Pl(e,t,os);function Hn(e,t,n){const s=arguments.length;return s===2?he(t)&&!G(t)?Rn(t)?T(e,null,[t]):T(e,t):T(e,null,t):(s>3?n=Array.prototype.slice.call(arguments,2):s===3&&Rn(n)&&(n=[n]),T(e,t,n))}const Ic="3.4.21";/**
 * @vue/runtime-dom v3.4.21
 * (c) 2018-present Yuxi (Evan) You and Vue contributors
 * @license MIT
-**/const Nc="http://www.w3.org/2000/svg",Oc="http://www.w3.org/1998/Math/MathML",dt=typeof document<"u"?document:null,zo=dt&&dt.createElement("template"),Rc={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,s)=>{const o=t==="svg"?dt.createElementNS(Nc,e):t==="mathml"?dt.createElementNS(Oc,e):dt.createElement(e,n?{is:n}:void 0);return e==="select"&&s&&s.multiple!=null&&o.setAttribute("multiple",s.multiple),o},createText:e=>dt.createTextNode(e),createComment:e=>dt.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>dt.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,s,o,i){const r=n?n.previousSibling:t.lastChild;if(o&&(o===i||o.nextSibling))for(;t.insertBefore(o.cloneNode(!0),n),!(o===i||!(o=o.nextSibling)););else{zo.innerHTML=s==="svg"?`${e}`:s==="mathml"?`${e}`:e;const l=zo.content;if(s==="svg"||s==="mathml"){const c=l.firstChild;for(;c.firstChild;)l.appendChild(c.firstChild);l.removeChild(c)}t.insertBefore(l,n)}return[r?r.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}},lt="transition",Xt="animation",fn=Symbol("_vtc"),is=(e,{slots:t})=>Hn(Kl,Bc(e),t);is.displayName="Transition";const br={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};is.props=ke({},er,br);const kt=(e,t=[])=>{G(e)?e.forEach(n=>n(...t)):e&&e(...t)},Ko=e=>e?G(e)?e.some(t=>t.length>1):e.length>1:!1;function Bc(e){const t={};for(const M in e)M in br||(t[M]=e[M]);if(e.css===!1)return t;const{name:n="v",type:s,duration:o,enterFromClass:i=`${n}-enter-from`,enterActiveClass:r=`${n}-enter-active`,enterToClass:l=`${n}-enter-to`,appearFromClass:c=i,appearActiveClass:f=r,appearToClass:p=l,leaveFromClass:_=`${n}-leave-from`,leaveActiveClass:b=`${n}-leave-active`,leaveToClass:S=`${n}-leave-to`}=e,J=Hc(o),q=J&&J[0],ne=J&&J[1],{onBeforeEnter:le,onEnter:ye,onEnterCancelled:v,onLeave:$,onLeaveCancelled:F,onBeforeAppear:N=le,onAppear:Z=ye,onAppearCancelled:R=v}=t,W=(M,te,be)=>{wt(M,te?p:l),wt(M,te?f:r),be&&be()},H=(M,te)=>{M._isLeaving=!1,wt(M,_),wt(M,S),wt(M,b),te&&te()},ce=M=>(te,be)=>{const xe=M?Z:ye,j=()=>W(te,M,be);kt(xe,[te,j]),Go(()=>{wt(te,M?c:i),ct(te,M?p:l),Ko(xe)||qo(te,s,q,j)})};return ke(t,{onBeforeEnter(M){kt(le,[M]),ct(M,i),ct(M,r)},onBeforeAppear(M){kt(N,[M]),ct(M,c),ct(M,f)},onEnter:ce(!1),onAppear:ce(!0),onLeave(M,te){M._isLeaving=!0;const be=()=>H(M,te);ct(M,_),Uc(),ct(M,b),Go(()=>{M._isLeaving&&(wt(M,_),ct(M,S),Ko($)||qo(M,s,ne,be))}),kt($,[M,be])},onEnterCancelled(M){W(M,!1),kt(v,[M])},onAppearCancelled(M){W(M,!0),kt(R,[M])},onLeaveCancelled(M){H(M),kt(F,[M])}})}function Hc(e){if(e==null)return null;if(he(e))return[ys(e.enter),ys(e.leave)];{const t=ys(e);return[t,t]}}function ys(e){return Yr(e)}function ct(e,t){t.split(/\s+/).forEach(n=>n&&e.classList.add(n)),(e[fn]||(e[fn]=new Set)).add(t)}function wt(e,t){t.split(/\s+/).forEach(s=>s&&e.classList.remove(s));const n=e[fn];n&&(n.delete(t),n.size||(e[fn]=void 0))}function Go(e){requestAnimationFrame(()=>{requestAnimationFrame(e)})}let Fc=0;function qo(e,t,n,s){const o=e._endId=++Fc,i=()=>{o===e._endId&&s()};if(n)return setTimeout(i,n);const{type:r,timeout:l,propCount:c}=Dc(e,t);if(!r)return s();const f=r+"end";let p=0;const _=()=>{e.removeEventListener(f,b),i()},b=S=>{S.target===e&&++p>=c&&_()};setTimeout(()=>{p(n[J]||"").split(", "),o=s(`${lt}Delay`),i=s(`${lt}Duration`),r=Wo(o,i),l=s(`${Xt}Delay`),c=s(`${Xt}Duration`),f=Wo(l,c);let p=null,_=0,b=0;t===lt?r>0&&(p=lt,_=r,b=i.length):t===Xt?f>0&&(p=Xt,_=f,b=c.length):(_=Math.max(r,f),p=_>0?r>f?lt:Xt:null,b=p?p===lt?i.length:c.length:0);const S=p===lt&&/\b(transform|all)(,|$)/.test(s(`${lt}Property`).toString());return{type:p,timeout:_,propCount:b,hasTransform:S}}function Wo(e,t){for(;e.lengthYo(n)+Yo(e[s])))}function Yo(e){return e==="auto"?0:Number(e.slice(0,-1).replace(",","."))*1e3}function Uc(){return document.body.offsetHeight}function jc(e,t,n){const s=e[fn];s&&(t=(t?[t,...s]:[...s]).join(" ")),t==null?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}const Jo=Symbol("_vod"),zc=Symbol("_vsh"),kr=Symbol("");function Kc(e){const t=ro();if(!t)return;const n=t.ut=(o=e(t.proxy))=>{Array.from(document.querySelectorAll(`[data-v-owner="${t.uid}"]`)).forEach(i=>Bs(i,o))},s=()=>{const o=e(t.proxy);Rs(t.subTree,o),n(o)};Xi(s),De(()=>{const o=new MutationObserver(s);o.observe(t.subTree.el.parentNode,{childList:!0}),mt(()=>o.disconnect())})}function Rs(e,t){if(e.shapeFlag&128){const n=e.suspense;e=n.activeBranch,n.pendingBranch&&!n.isHydrating&&n.effects.push(()=>{Rs(n.activeBranch,t)})}for(;e.component;)e=e.component.subTree;if(e.shapeFlag&1&&e.el)Bs(e.el,t);else if(e.type===Q)e.children.forEach(n=>Rs(n,t));else if(e.type===Dt){let{el:n,anchor:s}=e;for(;n&&(Bs(n,t),n!==s);)n=n.nextSibling}}function Bs(e,t){if(e.nodeType===1){const n=e.style;let s="";for(const o in t)n.setProperty(`--${o}`,t[o]),s+=`--${o}: ${t[o]};`;n[kr]=s}}const Gc=/(^|;)\s*display\s*:/;function qc(e,t,n){const s=e.style,o=ge(n);let i=!1;if(n&&!o){if(t)if(ge(t))for(const r of t.split(";")){const l=r.slice(0,r.indexOf(":")).trim();n[l]==null&&Ln(s,l,"")}else for(const r in t)n[r]==null&&Ln(s,r,"");for(const r in n)r==="display"&&(i=!0),Ln(s,r,n[r])}else if(o){if(t!==n){const r=s[kr];r&&(n+=";"+r),s.cssText=n,i=Gc.test(n)}}else t&&e.removeAttribute("style");Jo in e&&(e[Jo]=i?s.display:"",e[zc]&&(s.display="none"))}const Qo=/\s*!important$/;function Ln(e,t,n){if(G(n))n.forEach(s=>Ln(e,t,s));else if(n==null&&(n=""),t.startsWith("--"))e.setProperty(t,n);else{const s=Wc(e,t);Qo.test(n)?e.setProperty(Yt(s),n.replace(Qo,""),"important"):e[s]=n}}const Xo=["Webkit","Moz","ms"],bs={};function Wc(e,t){const n=bs[t];if(n)return n;let s=Ze(t);if(s!=="filter"&&s in e)return bs[t]=s;s=Gn(s);for(let o=0;oks||(ta.then(()=>ks=0),ks=Date.now());function sa(e,t){const n=s=>{if(!s._vts)s._vts=Date.now();else if(s._vts<=n.attached)return;Fe(oa(s,n.value),t,5,[s])};return n.value=e,n.attached=na(),n}function oa(e,t){if(G(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map(s=>o=>!o._stopped&&s&&s(o))}else return t}const ni=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,ia=(e,t,n,s,o,i,r,l,c)=>{const f=o==="svg";t==="class"?jc(e,s,f):t==="style"?qc(e,n,s):pn(t)?Fs(t)||Zc(e,t,n,s,r):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):ra(e,t,s,f))?Jc(e,t,s,i,r,l,c):(t==="true-value"?e._trueValue=s:t==="false-value"&&(e._falseValue=s),Yc(e,t,s,f))};function ra(e,t,n,s){if(s)return!!(t==="innerHTML"||t==="textContent"||t in e&&ni(t)&&ee(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 o=e.tagName;if(o==="IMG"||o==="VIDEO"||o==="CANVAS"||o==="SOURCE")return!1}return ni(t)&&ge(n)?!1:t in e}const la=["ctrl","shift","alt","meta"],ca={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&e.button!==0,middle:e=>"button"in e&&e.button!==1,right:e=>"button"in e&&e.button!==2,exact:(e,t)=>la.some(n=>e[`${n}Key`]&&!t.includes(n))},aa=(e,t)=>{const n=e._withMods||(e._withMods={}),s=t.join(".");return n[s]||(n[s]=(o,...i)=>{for(let r=0;r{const t=fa().createApp(...e),{mount:n}=t;return t.mount=s=>{const o=_a(s);if(o)return n(o,!0,pa(o))},t};function pa(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function _a(e){return ge(e)?document.querySelector(e):e}const O=(e,t)=>{const n=e.__vccOpts||e;for(const[s,o]of t)n[s]=o;return n},ha="modulepreload",va=function(e){return"/blog/"+e},oi={},ma=function(t,n,s){if(!n||n.length===0)return t();const o=document.getElementsByTagName("link");return Promise.all(n.map(i=>{if(i=va(i),i in oi)return;oi[i]=!0;const r=i.endsWith(".css"),l=r?'[rel="stylesheet"]':"";if(!!s)for(let p=o.length-1;p>=0;p--){const _=o[p];if(_.href===i&&(!r||_.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${i}"]${l}`))return;const f=document.createElement("link");if(f.rel=r?"stylesheet":ha,r||(f.as="script",f.crossOrigin=""),f.href=i,document.head.appendChild(f),r)return new Promise((p,_)=>{f.addEventListener("load",p),f.addEventListener("error",()=>_(new Error(`Unable to preload CSS for ${i}`)))})})).then(()=>t()).catch(i=>{const r=new Event("vite:preloadError",{cancelable:!0});if(r.payload=i,window.dispatchEvent(r),!r.defaultPrevented)throw i})};const ga=B({__name:"VPBadge",props:{text:{},type:{}},setup(e){return(t,n)=>(d(),m("span",{class:pe(["VPBadge",t.type??"tip"])},[L(t.$slots,"default",{},()=>[Le(fe(t.text),1)],!0)],2))}});const ya=O(ga,[["__scopeId","data-v-e3a0c872"]]),ba=JSON.parse(`{"lang":"en-US","title":"Sunny's blog","description":"composition api form validator for vue","base":"/blog/","head":[],"appearance":true,"themeConfig":{"outline":[1,3],"sidebar":[{"text":"Introduction","collapsible":true,"items":[{"text":"Getting Started","link":"/getting-started"}]},{"text":"前端工程化","collapsible":true,"items":[{"text":"前端工程化 OnePage","link":"/front-end-engineering/engineering-onepage"},{"text":"前端性能优化方法论","link":"/front-end-engineering/performance"},{"text":"webpack常用拓展","link":"/front-end-engineering/webpack常用拓展"},{"text":"CSS工程化","link":"/front-end-engineering/CSS工程化"},{"text":"JS 兼容性","link":"/front-end-engineering/jscompatibility"},{"text":"webpack5 模块联邦","link":"/front-end-engineering/webpack5-mf"},{"text":"pnpm原理","link":"/front-end-engineering/pnpm原理"},{"text":"模块化","link":"/front-end-engineering/modularization"},{"text":"包管理器","link":"/front-end-engineering/PackageManager"}]},{"text":"Vuejs 21篇","collapsible":true,"items":[{"text":"那些年,被问烂了的 Vuejs 面试题","link":"/vue/vue-interview"},{"text":"虚拟DOM详解","link":"/vue/vdom"},{"text":"Vuejs 组件通信概览","link":"/vue/component-communication"},{"text":"探索 v-model 原理","link":"/vue/v-model"},{"text":"Vuejs 数据响应原理","link":"/vue/reactive"},{"text":"Vue2 diff算法原理","link":"/vue/diff"},{"text":"Vue 生命周期","link":"/vue/lifecycle"},{"text":"computed","link":"/vue/computed"},{"text":"keep-alive 与 LRU 算法","link":"/vue/keep-alive-LRU"},{"text":"Vue3 新特性","link":"/vue/vue3-onepage"},{"text":"vue-cli 到底帮我们做了什么","link":"/vue/vue-cli"},{"text":"Vue 编译器为什么如此强大","link":"/vue/vue-compile"},{"text":"$nextTick 工作原理","link":"/vue/nextTick"},{"text":"Vue 和 React 的核心区别","link":"/vue/vs"},{"text":"一篇精通 slot","link":"/vue/slot"},{"text":"Vue SSR 服务端渲染","link":"/vue/SSR"},{"text":"Vue 指令篇","link":"/vue/directive"},{"text":"VueRouter 路由从使用到源码","link":"/vue/vue-router"},{"text":"Vuex 思想和源码","link":"/vue/vuex"},{"text":"今天我是面试官","link":"/vue/interviewer"},{"text":"vue 手写篇","link":"/vue/challages"}]},{"text":"React 16篇","collapsible":true,"items":[{"text":"React onePage","link":"/react/"},{"text":"那些年,被问烂了的 React 面试题","link":"/react/react-interview"},{"text":"React Hooks","link":"/react/hooks"},{"text":"React 组件通信","link":"/react/component-communication"},{"text":"React Context","link":"/react/context"},{"text":"React 生命周期","link":"/react/lifecycle"},{"text":"React 事件机制","link":"/react/event"},{"text":"React 渲染原理","link":"/react/render"},{"text":"React 动画","link":"/react/transition"},{"text":"React Router","link":"/react/ReactRouter"},{"text":"JS 应用的状态容器,提供可预测的状态管理 Redux","link":"/react/Redux"},{"text":"视图-仓库-路由","link":"/react/react-redux-router"},{"text":"dva 设计思想","link":"/react/dva"},{"text":"Umi 企业级前端框架","link":"/react/umi"},{"text":"React 调试工具","link":"/react/utils"},{"text":"Fiber","link":"/react/Fiber"}]},{"text":"TypeScript","items":[{"text":"TypeScript OnePage","link":"/ts/TypeScript-onePage"}]},{"text":"前端三件套","collapsible":true,"items":[{"text":"异步处理","link":"/js/异步处理"},{"text":"代理与反射","link":"/js/代理与反射"},{"text":"迭代器和生成器","link":"/js/迭代器和生成器"},{"text":"HTML_CSS 面试题","link":"/html-css/interview"},{"text":"HTML5 OnePage","link":"/html-css/HTML"},{"text":"CSS3 OnePage","link":"/html-css/CSS"},{"text":"Canvas 和 svg","link":"/html-css/canvas-svg"},{"text":"CSS3动画","link":"/html-css/animation"},{"text":"CSS3选择器","link":"/html-css/selector"},{"text":"显示器的成像原理","link":"/html-css/principle"},{"text":"拖拽 API","link":"/html-css/drag"}]},{"text":"知识碎片","items":[{"text":"如何实现准时的setTimeout","link":"/fe-utils/setTimeout"},{"text":"Git","link":"/fe-utils/git"},{"text":"前端 JavaScript 必会工具库合集","link":"/fe-utils/js工具库"},{"text":"一站式-后台前端解决方案","link":"/article/cms"},{"text":"涌现出来的新tools","link":"/fe-utils/tool"}]},{"text":"算法","collapsible":true,"items":[{"text":"🔥刷题之探索最优解","link":"/algorithm/🔥刷题之探索最优解"}]},{"text":"面试系列","collapsible":true,"items":[{"text":"面试官:你还有问题要问我吗","link":"/interview/面试官:你还有问题要问我吗"},{"text":"算法笔试","link":"/interview/算法笔试"}]}],"nav":[{"text":"前端工程化","collapsible":true,"items":[{"text":"前端工程化 OnePage","link":"/front-end-engineering/engineering-onepage"},{"text":"前端性能优化方法论","link":"/front-end-engineering/performance"},{"text":"webpack常用拓展","link":"/front-end-engineering/webpack常用拓展"},{"text":"CSS工程化","link":"/front-end-engineering/CSS工程化"},{"text":"JS 兼容性","link":"/front-end-engineering/jscompatibility"},{"text":"webpack5 模块联邦","link":"/front-end-engineering/webpack5-mf"},{"text":"pnpm原理","link":"/front-end-engineering/pnpm原理"},{"text":"模块化","link":"/front-end-engineering/modularization"},{"text":"包管理器","link":"/front-end-engineering/PackageManager"}]},{"text":"Vuejs 21篇","collapsible":true,"items":[{"text":"那些年,被问烂了的 Vuejs 面试题","link":"/vue/vue-interview"},{"text":"虚拟DOM详解","link":"/vue/vdom"},{"text":"Vuejs 组件通信概览","link":"/vue/component-communication"},{"text":"探索 v-model 原理","link":"/vue/v-model"},{"text":"Vuejs 数据响应原理","link":"/vue/reactive"},{"text":"Vue2 diff算法原理","link":"/vue/diff"},{"text":"Vue 生命周期","link":"/vue/lifecycle"},{"text":"computed","link":"/vue/computed"},{"text":"keep-alive 与 LRU 算法","link":"/vue/keep-alive-LRU"},{"text":"Vue3 新特性","link":"/vue/vue3-onepage"},{"text":"vue-cli 到底帮我们做了什么","link":"/vue/vue-cli"},{"text":"Vue 编译器为什么如此强大","link":"/vue/vue-compile"},{"text":"$nextTick 工作原理","link":"/vue/nextTick"},{"text":"Vue 和 React 的核心区别","link":"/vue/vs"},{"text":"一篇精通 slot","link":"/vue/slot"},{"text":"Vue SSR 服务端渲染","link":"/vue/SSR"},{"text":"Vue 指令篇","link":"/vue/directive"},{"text":"VueRouter 路由从使用到源码","link":"/vue/vue-router"},{"text":"Vuex 思想和源码","link":"/vue/vuex"},{"text":"今天我是面试官","link":"/vue/interviewer"},{"text":"vue 手写篇","link":"/vue/challages"}]},{"text":"React 16篇","collapsible":true,"items":[{"text":"React onePage","link":"/react/"},{"text":"那些年,被问烂了的 React 面试题","link":"/react/react-interview"},{"text":"React Hooks","link":"/react/hooks"},{"text":"React 组件通信","link":"/react/component-communication"},{"text":"React Context","link":"/react/context"},{"text":"React 生命周期","link":"/react/lifecycle"},{"text":"React 事件机制","link":"/react/event"},{"text":"React 渲染原理","link":"/react/render"},{"text":"React 动画","link":"/react/transition"},{"text":"React Router","link":"/react/ReactRouter"},{"text":"JS 应用的状态容器,提供可预测的状态管理 Redux","link":"/react/Redux"},{"text":"视图-仓库-路由","link":"/react/react-redux-router"},{"text":"dva 设计思想","link":"/react/dva"},{"text":"Umi 企业级前端框架","link":"/react/umi"},{"text":"React 调试工具","link":"/react/utils"},{"text":"Fiber","link":"/react/Fiber"}]},{"text":"TypeScript","items":[{"text":"TypeScript OnePage","link":"/ts/TypeScript-onePage"}]}],"socialLinks":[{"icon":"github","link":"https://github.com/Sunny-117/blog"}],"footer":{"copyright":"Copyright © 2022-present sunny-117"},"editLink":{"pattern":"https://github.com/Sunny-117/blog","text":"Edit this page on Gitlab"},"lastUpdatedText":"Last Updated"},"locales":{},"langs":{},"scrollOffset":90,"cleanUrls":"disabled"}`),rs=/^[a-z]+:/i,ka=/^pathname:\/\//,ii="vitepress-theme-appearance",Te=typeof window<"u",wr={relativePath:"",title:"404",description:"Not Found",headers:[],frontmatter:{sidebar:!1,layout:"page"},lastUpdated:0};function wa(e,t){t.sort((n,s)=>{const o=s.split("/").length-n.split("/").length;return o!==0?o:s.length-n.length});for(const n of t)if(e.startsWith(n))return n}function ri(e,t){const n=wa(t,Object.keys(e));return n?e[n]:void 0}function xa(e){const{locales:t}=e.themeConfig||{},n=e.locales;return t&&n?Object.keys(t).reduce((s,o)=>(s[o]={label:t[o].label,lang:n[o].lang},s),{}):{}}function $a(e,t){t=Sa(e,t);const n=ri(e.locales||{},t),s=ri(e.themeConfig.locales||{},t);return Object.assign({},e,n,{themeConfig:Object.assign({},e.themeConfig,s,{locales:{}}),lang:(n||e).lang,locales:{},langs:xa(e)})}function xr(e,t){const n=t.title||e.title,s=t.titleTemplate??e.titleTemplate;if(typeof s=="string"&&s.includes(":title"))return s.replace(/:title/g,n);const o=Pa(e.title,s);return`${n}${o}`}function Pa(e,t){return t===!1?"":t===!0||t===void 0?` | ${e}`:e===t?"":` | ${t}`}function Sa(e,t){if(!Te)return t;const n=e.base,s=n.endsWith("/")?n.slice(0,-1):n;return t.slice(s.length)}function Ca(e,t){const[n,s]=t;if(n!=="meta")return!1;const o=Object.entries(s)[0];return o==null?!1:e.some(([i,r])=>i===n&&r[o[0]]===o[1])}function Va(e,t){return[...e.filter(n=>!Ca(t,n)),...t]}const Ta=/[\u0000-\u001F"#$&*+,:;<=>?[\]^`{|}\u007F]/g,La=/^[a-z]:/i;function li(e){const t=La.exec(e),n=t?t[0]:"";return n+e.slice(n.length).replace(Ta,"_").replace(/(^|\/)_+(?=[^/]*$)/,"$1")}function Ea(e,t){return`${e}${t}`.replace(/\/+/g,"/")}function dn(e){return rs.test(e)?e:Ea(qt.value.base,e)}function $r(e){let t=e.replace(/\.html$/,"");if(t=decodeURIComponent(t),t.endsWith("/")&&(t+="index"),Te){const n="/blog/";t=li(t.slice(n.length).replace(/\//g,"_")||"index")+".md";const s=__VP_HASH_MAP__[t.toLowerCase()];t=`${n}assets/${t}.${s}.js`}else t=`./${li(t.slice(1).replace(/\//g,"_"))}.md.js`;return t}const Pr=Symbol(),qt=Sl(ba);function Ma(e){const t=re(()=>$a(qt.value,e.path));return{site:t,theme:re(()=>t.value.themeConfig),page:re(()=>e.data),frontmatter:re(()=>e.data.frontmatter),lang:re(()=>t.value.lang),localePath:re(()=>{const{langs:n,lang:s}=t.value,o=Object.keys(n).find(i=>n[i].lang===s);return dn(o||"/")}),title:re(()=>xr(t.value,e.data)),description:re(()=>e.data.description||t.value.description),isDark:_e(!1)}}function ue(){const e=Ke(Pr);if(!e)throw new Error("vitepress data not properly injected in app");return e}const Sr=Symbol(),ci="http://a.com",Aa=()=>({path:"/",component:null,data:wr});function Ia(e,t){const n=Yn(Aa()),s={route:n,go:o};async function o(l=Te?location.href:"/"){var f,p;await((f=s.onBeforeRouteChange)==null?void 0:f.call(s,l));const c=new URL(l,ci);qt.value.cleanUrls==="disabled"&&!c.pathname.endsWith("/")&&!c.pathname.endsWith(".html")&&(c.pathname+=".html",l=c.pathname+c.search+c.hash),Te&&(history.replaceState({scrollPosition:window.scrollY},document.title),history.pushState(null,"",l)),await r(l),await((p=s.onAfterRouteChanged)==null?void 0:p.call(s,l))}let i=null;async function r(l,c=0,f=!1){const p=new URL(l,ci),_=i=p.pathname;try{let b=await e(_);if(i===_){i=null;const{default:S,__pageData:J}=b;if(!S)throw new Error(`Invalid route component: ${S}`);n.path=Te?_:dn(_),n.component=en(S),n.data=en(J),Te&&Xs(()=>{if(p.hash&&!c){let q=null;try{q=document.querySelector(decodeURIComponent(p.hash))}catch(ne){console.warn(ne)}if(q){ai(q,p.hash);return}}window.scrollTo(0,c)})}}catch(b){if(!/fetch/.test(b.message)&&!/^\/404(\.html|\/)?$/.test(l)&&console.error(b),!f)try{const S=await fetch(qt.value.base+"hashmap.json");window.__VP_HASH_MAP__=await S.json(),await r(l,c,!0);return}catch{}i===_&&(i=null,n.path=Te?_:dn(_),n.component=t?en(t):null,n.data=wr)}}return Te&&(window.addEventListener("click",l=>{if(l.target.closest("button"))return;const f=l.target.closest("a");if(f&&!f.closest(".vp-raw")&&!f.download){const{href:p,origin:_,pathname:b,hash:S,search:J,target:q}=f,ne=window.location,le=b.match(/\.\w+$/);!l.ctrlKey&&!l.shiftKey&&!l.altKey&&!l.metaKey&&q!=="_blank"&&_===ne.origin&&!(le&&le[0]!==".html")&&(l.preventDefault(),b===ne.pathname&&J===ne.search?S&&S!==ne.hash&&(history.pushState(null,"",S),window.dispatchEvent(new Event("hashchange")),ai(f,S,f.classList.contains("header-anchor"))):o(p))}},{capture:!0}),window.addEventListener("popstate",l=>{r(location.href,l.state&&l.state.scrollPosition||0)}),window.addEventListener("hashchange",l=>{l.preventDefault()})),s}function Na(){const e=Ke(Sr);if(!e)throw new Error("useRouter() is called without provider.");return e}function gt(){return Na().route}function ai(e,t,n=!1){let s=null;try{s=e.classList.contains("header-anchor")?e:document.querySelector(decodeURIComponent(t))}catch(o){console.warn(o)}if(s){let o=qt.value.scrollOffset;typeof o=="string"&&(o=document.querySelector(o).getBoundingClientRect().bottom+24);const i=parseInt(window.getComputedStyle(s).paddingTop,10),r=window.scrollY+s.getBoundingClientRect().top-o+i;!n||Math.abs(r-window.scrollY)>window.innerHeight?window.scrollTo(0,r):window.scrollTo({left:0,top:r,behavior:"smooth"})}}const Oa=B({name:"VitePressContent",props:{onContentUpdated:Function},setup(e){const t=gt();return no(()=>{var n;(n=e.onContentUpdated)==null||n.call(e)}),()=>Hn("div",{style:{position:"relative"}},[t.component?Hn(t.component):null])}}),Cr=/#.*$/,Ra=/(index)?\.(md|html)$/,Ba=typeof window<"u",Ha=_e(Ba?location.hash:"");function Fa(e){return rs.test(e)}function Da(e,t){let n,s=!1;return()=>{n&&clearTimeout(n),s?n=setTimeout(e,t):(e(),s=!0,setTimeout(()=>{s=!1},t))}}function Jt(e,t,n=!1){if(t===void 0)return!1;if(e=fi(`/${e}`),n)return new RegExp(t).test(e);if(fi(t)!==e)return!1;const s=t.match(Cr);return s?Ha.value===s[0]:!0}function ui(e){return/^\//.test(e)?e:`/${e}`}function fi(e){return decodeURI(e).replace(Cr,"").replace(Ra,"")}function Fn(e){if(Fa(e))return e.replace(ka,"");const{site:t}=ue(),{pathname:n,search:s,hash:o}=new URL(e,"http://example.com"),i=n.endsWith("/")||n.endsWith(".html")?e:`${n.replace(/(\.md)?$/,t.value.cleanUrls==="disabled"?".html":"")}${s}${o}`;return dn(i)}function Vr(e,t){if(Array.isArray(e))return e;if(e==null)return[];t=ui(t);const n=Object.keys(e).sort((s,o)=>o.split("/").length-s.split("/").length).find(s=>t.startsWith(ui(s)));return n?e[n]:[]}function Ua(e){const t=[];function n(s){for(const o of s)o.link&&t.push({...o,link:o.link}),"items"in o&&n(o.items)}for(const s of e)n(s.items);return t}function et(){const e=gt(),{theme:t,frontmatter:n}=ue(),s=_e(!1),o=re(()=>{const p=t.value.sidebar,_=e.data.relativePath;return p?Vr(p,_):[]}),i=re(()=>n.value.sidebar!==!1&&o.value.length>0&&n.value.layout!=="home"),r=re(()=>n.value.layout!=="home"&&n.value.aside!==!1);function l(){s.value=!0}function c(){s.value=!1}function f(){s.value?c():l()}return{isOpen:s,sidebar:o,hasSidebar:i,hasAside:r,open:l,close:c,toggle:f}}function ja(e,t){let n;Kt(()=>{n=e.value?document.activeElement:void 0}),De(()=>{window.addEventListener("keyup",s)}),mt(()=>{window.removeEventListener("keyup",s)});function s(o){o.key==="Escape"&&e.value&&(t(),n==null||n.focus())}}const za=B({__name:"VPSkipLink",setup(e){const t=gt(),n=_e();Xe(()=>t.path,()=>n.value.focus());function s({target:o}){const i=document.querySelector(o.hash);if(i){const r=()=>{i.removeAttribute("tabindex"),i.removeEventListener("blur",r)};i.setAttribute("tabindex","-1"),i.addEventListener("blur",r),i.focus(),window.scrollTo(0,0)}}return(o,i)=>(d(),m(Q,null,[g("span",{ref_key:"backToTop",ref:n,tabindex:"-1"},null,512),g("a",{href:"#VPContent",class:"VPSkipLink visually-hidden",onClick:s}," Skip to content ")],64))}});const Ka=O(za,[["__scopeId","data-v-7c1bd20b"]]),Ga={key:0,class:"VPBackdrop"},qa=B({__name:"VPBackdrop",props:{show:{type:Boolean}},setup(e){return(t,n)=>(d(),X(is,{name:"fade"},{default:I(()=>[t.show?(d(),m("div",Ga)):K("",!0)]),_:1}))}});const Wa=O(qa,[["__scopeId","data-v-5b48b308"]]);function Ya(){const e=_e(!1);function t(){e.value=!0,window.addEventListener("resize",o)}function n(){e.value=!1,window.removeEventListener("resize",o)}function s(){e.value?n():t()}function o(){window.outerWidth>=768&&n()}const i=gt();return Xe(()=>i.path,n),{isScreenOpen:e,openScreen:t,closeScreen:n,toggleScreen:s}}const Ja=["src","alt"],Qa={inheritAttrs:!1},Xa=B({...Qa,__name:"VPImage",props:{image:{},alt:{}},setup(e){return(t,n)=>{const s=Lt("VPImage",!0);return t.image?(d(),m(Q,{key:0},[typeof t.image=="string"||"src"in t.image?(d(),m("img",Tn({key:0,class:"VPImage"},typeof t.image=="string"?t.$attrs:{...t.image,...t.$attrs},{src:y(dn)(typeof t.image=="string"?t.image:t.image.src),alt:t.alt??(typeof t.image=="string"?"":t.image.alt||"")}),null,16,Ja)):(d(),m(Q,{key:1},[T(s,Tn({class:"dark",image:t.image.dark,alt:typeof t.image.dark=="string"?t.image.alt:t.image.dark.alt||t.image.alt},t.$attrs),null,16,["image","alt"]),T(s,Tn({class:"light",image:t.image.light,alt:typeof t.image.light=="string"?t.image.alt:t.image.light.alt||t.image.alt},t.$attrs),null,16,["image","alt"])],64))],64)):K("",!0)}}});const Tr=O(Xa,[["__scopeId","data-v-d61f9ddc"]]),Za=["href"],eu=B({__name:"VPNavBarTitle",setup(e){const{site:t,theme:n}=ue(),{hasSidebar:s}=et();return(o,i)=>(d(),m("div",{class:pe(["VPNavBarTitle",{"has-sidebar":y(s)}])},[g("a",{class:"title",href:y(t).base},[L(o.$slots,"nav-bar-title-before",{},void 0,!0),T(Tr,{class:"logo",image:y(n).logo},null,8,["image"]),y(n).siteTitle?(d(),m(Q,{key:0},[Le(fe(y(n).siteTitle),1)],64)):y(n).siteTitle===void 0?(d(),m(Q,{key:1},[Le(fe(y(t).title),1)],64)):K("",!0),L(o.$slots,"nav-bar-title-after",{},void 0,!0)],8,Za)],2))}});const tu=O(eu,[["__scopeId","data-v-e9161676"]]);const nu={key:0,class:"VPNavBarSearch"},su={type:"button",class:"DocSearch DocSearch-Button","aria-label":"Search"},ou={class:"DocSearch-Button-Container"},iu=g("svg",{class:"DocSearch-Search-Icon",width:"20",height:"20",viewBox:"0 0 20 20"},[g("path",{d:"M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z",stroke:"currentColor",fill:"none","fill-rule":"evenodd","stroke-linecap":"round","stroke-linejoin":"round"})],-1),ru={class:"DocSearch-Button-Placeholder"},lu=g("span",{class:"DocSearch-Button-Keys"},[g("kbd",{class:"DocSearch-Button-Key"}),g("kbd",{class:"DocSearch-Button-Key"},"K")],-1),cu=B({__name:"VPNavBarSearch",setup(e){Kc(r=>({"52169baa":o.value}));const t=()=>null,{theme:n}=ue(),s=_e(!1),o=_e("'Meta'");De(()=>{if(!n.value.algolia)return;o.value=/(Mac|iPhone|iPod|iPad)/i.test(navigator.platform)?"'⌘'":"'Ctrl'";const r=c=>{c.key==="k"&&(c.ctrlKey||c.metaKey)&&(c.preventDefault(),i(),l())},l=()=>{window.removeEventListener("keydown",r)};window.addEventListener("keydown",r),mt(l)});function i(){s.value||(s.value=!0)}return(r,l)=>{var c;return y(n).algolia?(d(),m("div",nu,[s.value?(d(),X(y(t),{key:0})):(d(),m("div",{key:1,id:"docsearch",onClick:i},[g("button",su,[g("span",ou,[iu,g("span",ru,fe(((c=y(n).algolia)==null?void 0:c.buttonText)||"Search"),1)]),lu])]))])):K("",!0)}}});const au={},uu={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",height:"24px",viewBox:"0 0 24 24",width:"24px"},fu=g("path",{d:"M0 0h24v24H0V0z",fill:"none"},null,-1),du=g("path",{d:"M9 5v2h6.59L4 18.59 5.41 20 17 8.41V15h2V5H9z"},null,-1),pu=[fu,du];function _u(e,t){return d(),m("svg",uu,pu)}const hu=O(au,[["render",_u]]),vu=B({__name:"VPLink",props:{href:{},noIcon:{type:Boolean}},setup(e){const t=e,n=re(()=>t.href&&rs.test(t.href));return(s,o)=>(d(),X(to(s.href?"a":"span"),{class:pe(["VPLink",{link:s.href}]),href:s.href?y(Fn)(s.href):void 0,target:n.value?"_blank":void 0,rel:n.value?"noreferrer":void 0},{default:I(()=>[L(s.$slots,"default",{},void 0,!0),n.value&&!s.noIcon?(d(),X(hu,{key:0,class:"icon"})):K("",!0)]),_:3},8,["class","href","target","rel"]))}});const Et=O(vu,[["__scopeId","data-v-c145185f"]]),mu=B({__name:"VPNavBarMenuLink",props:{item:{}},setup(e){const{page:t}=ue();return(n,s)=>(d(),X(Et,{class:pe({VPNavBarMenuLink:!0,active:y(Jt)(y(t).relativePath,n.item.activeMatch||n.item.link,!!n.item.activeMatch)}),href:n.item.link,noIcon:!0},{default:I(()=>[Le(fe(n.item.text),1)]),_:1},8,["class","href"]))}});const gu=O(mu,[["__scopeId","data-v-428c9a29"]]),co=_e();let Lr=!1,xs=0;function yu(e){const t=_e(!1);if(typeof window<"u"){!Lr&&bu(),xs++;const n=Xe(co,s=>{var o,i,r;s===e.el.value||(o=e.el.value)!=null&&o.contains(s)?(t.value=!0,(i=e.onFocus)==null||i.call(e)):(t.value=!1,(r=e.onBlur)==null||r.call(e))});mt(()=>{n(),xs--,xs||ku()})}return Ws(t)}function bu(){document.addEventListener("focusin",Er),Lr=!0,co.value=document.activeElement}function ku(){document.removeEventListener("focusin",Er)}function Er(){co.value=document.activeElement}const wu={},xu={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},$u=g("path",{d:"M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"},null,-1),Pu=[$u];function Su(e,t){return d(),m("svg",xu,Pu)}const Mr=O(wu,[["render",Su]]),Cu={},Vu={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},Tu=g("circle",{cx:"12",cy:"12",r:"2"},null,-1),Lu=g("circle",{cx:"19",cy:"12",r:"2"},null,-1),Eu=g("circle",{cx:"5",cy:"12",r:"2"},null,-1),Mu=[Tu,Lu,Eu];function Au(e,t){return d(),m("svg",Vu,Mu)}const Iu=O(Cu,[["render",Au]]),Nu={class:"VPMenuLink"},Ou=B({__name:"VPMenuLink",props:{item:{}},setup(e){const{page:t}=ue();return(n,s)=>(d(),m("div",Nu,[T(Et,{class:pe({active:y(Jt)(y(t).relativePath,n.item.activeMatch||n.item.link)}),href:n.item.link},{default:I(()=>[Le(fe(n.item.text),1)]),_:1},8,["class","href"])]))}});const ls=O(Ou,[["__scopeId","data-v-ff9fa6d5"]]),Ru={class:"VPMenuGroup"},Bu={key:0,class:"title"},Hu=B({__name:"VPMenuGroup",props:{text:{},items:{}},setup(e){return(t,n)=>(d(),m("div",Ru,[t.text?(d(),m("p",Bu,fe(t.text),1)):K("",!0),(d(!0),m(Q,null,Ce(t.items,s=>(d(),m(Q,null,["link"in s?(d(),X(ls,{key:0,item:s},null,8,["item"])):K("",!0)],64))),256))]))}});const Fu=O(Hu,[["__scopeId","data-v-d0845d3f"]]),Du={class:"VPMenu"},Uu={key:0,class:"items"},ju=B({__name:"VPMenu",props:{items:{}},setup(e){return(t,n)=>(d(),m("div",Du,[t.items?(d(),m("div",Uu,[(d(!0),m(Q,null,Ce(t.items,s=>(d(),m(Q,{key:s.text},["link"in s?(d(),X(ls,{key:0,item:s},null,8,["item"])):(d(),X(Fu,{key:1,text:s.text,items:s.items},null,8,["text","items"]))],64))),128))])):K("",!0),L(t.$slots,"default",{},void 0,!0)]))}});const zu=O(ju,[["__scopeId","data-v-796849f7"]]),Ku=["aria-expanded","aria-label"],Gu={key:0,class:"text"},qu={class:"menu"},Wu=B({__name:"VPFlyout",props:{icon:{},button:{},label:{},items:{}},setup(e){const t=_e(!1),n=_e();yu({el:n,onBlur:s});function s(){t.value=!1}return(o,i)=>(d(),m("div",{class:"VPFlyout",ref_key:"el",ref:n,onMouseenter:i[1]||(i[1]=r=>t.value=!0),onMouseleave:i[2]||(i[2]=r=>t.value=!1)},[g("button",{type:"button",class:"button","aria-haspopup":"true","aria-expanded":t.value,"aria-label":o.label,onClick:i[0]||(i[0]=r=>t.value=!t.value)},[o.button||o.icon?(d(),m("span",Gu,[o.icon?(d(),X(to(o.icon),{key:0,class:"option-icon"})):K("",!0),Le(" "+fe(o.button)+" ",1),T(Mr,{class:"text-icon"})])):(d(),X(Iu,{key:1,class:"icon"}))],8,Ku),g("div",qu,[T(zu,{items:o.items},{default:I(()=>[L(o.$slots,"default",{},void 0,!0)]),_:3},8,["items"])])],544))}});const ao=O(Wu,[["__scopeId","data-v-5f738fe3"]]),Yu=B({__name:"VPNavBarMenuGroup",props:{item:{}},setup(e){const{page:t}=ue();return(n,s)=>(d(),X(ao,{class:pe({VPNavBarMenuGroup:!0,active:y(Jt)(y(t).relativePath,n.item.activeMatch,!!n.item.activeMatch)}),button:n.item.text,items:n.item.items},null,8,["class","button","items"]))}}),Ju=e=>(Ge("data-v-e6b3a2a4"),e=e(),qe(),e),Qu={key:0,"aria-labelledby":"main-nav-aria-label",class:"VPNavBarMenu"},Xu=Ju(()=>g("span",{id:"main-nav-aria-label",class:"visually-hidden"},"Main Navigation",-1)),Zu=B({__name:"VPNavBarMenu",setup(e){const{theme:t}=ue();return(n,s)=>y(t).nav?(d(),m("nav",Qu,[Xu,(d(!0),m(Q,null,Ce(y(t).nav,o=>(d(),m(Q,{key:o.text},["link"in o?(d(),X(gu,{key:0,item:o},null,8,["item"])):(d(),X(Yu,{key:1,item:o},null,8,["item"]))],64))),128))])):K("",!0)}});const ef=O(Zu,[["__scopeId","data-v-e6b3a2a4"]]),tf={},nf={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},sf=g("path",{d:"M0 0h24v24H0z",fill:"none"},null,-1),of=g("path",{d:" M12.87 15.07l-2.54-2.51.03-.03c1.74-1.94 2.98-4.17 3.71-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z ",class:"css-c4d79v"},null,-1),rf=[sf,of];function lf(e,t){return d(),m("svg",nf,rf)}const Ar=O(tf,[["render",lf]]),cf={class:"items"},af={class:"title"},uf=B({__name:"VPNavBarTranslations",setup(e){const{theme:t}=ue();return(n,s)=>y(t).localeLinks?(d(),X(ao,{key:0,class:"VPNavBarTranslations",icon:Ar},{default:I(()=>[g("div",cf,[g("p",af,fe(y(t).localeLinks.text),1),(d(!0),m(Q,null,Ce(y(t).localeLinks.items,o=>(d(),X(ls,{key:o.link,item:o},null,8,["item"]))),128))])]),_:1})):K("",!0)}});const ff=O(uf,[["__scopeId","data-v-613d3281"]]);const df={},pf={class:"VPSwitch",type:"button",role:"switch"},_f={class:"check"},hf={key:0,class:"icon"};function vf(e,t){return d(),m("button",pf,[g("span",_f,[e.$slots.default?(d(),m("span",hf,[L(e.$slots,"default",{},void 0,!0)])):K("",!0)])])}const mf=O(df,[["render",vf],["__scopeId","data-v-8e78fc3c"]]),gf={},yf={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},bf=$c('',9),kf=[bf];function wf(e,t){return d(),m("svg",yf,kf)}const xf=O(gf,[["render",wf]]),$f={},Pf={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},Sf=g("path",{d:"M12.1,22c-0.3,0-0.6,0-0.9,0c-5.5-0.5-9.5-5.4-9-10.9c0.4-4.8,4.2-8.6,9-9c0.4,0,0.8,0.2,1,0.5c0.2,0.3,0.2,0.8-0.1,1.1c-2,2.7-1.4,6.4,1.3,8.4c2.1,1.6,5,1.6,7.1,0c0.3-0.2,0.7-0.3,1.1-0.1c0.3,0.2,0.5,0.6,0.5,1c-0.2,2.7-1.5,5.1-3.6,6.8C16.6,21.2,14.4,22,12.1,22zM9.3,4.4c-2.9,1-5,3.6-5.2,6.8c-0.4,4.4,2.8,8.3,7.2,8.7c2.1,0.2,4.2-0.4,5.8-1.8c1.1-0.9,1.9-2.1,2.4-3.4c-2.5,0.9-5.3,0.5-7.5-1.1C9.2,11.4,8.1,7.7,9.3,4.4z"},null,-1),Cf=[Sf];function Vf(e,t){return d(),m("svg",Pf,Cf)}const Tf=O($f,[["render",Vf]]),Lf=B({__name:"VPSwitchAppearance",setup(e){const{site:t,isDark:n}=ue(),s=_e(!1),o=typeof localStorage<"u"?i():()=>{};De(()=>{s.value=document.documentElement.classList.contains("dark")});function i(){const r=window.matchMedia("(prefers-color-scheme: dark)"),l=document.documentElement.classList;let c=localStorage.getItem(ii),f=t.value.appearance==="dark"&&c==null||(c==="auto"||c==null?r.matches:c==="dark");r.onchange=b=>{c==="auto"&&_(f=b.matches)};function p(){_(f=!f),c=f?r.matches?"auto":"dark":r.matches?"light":"auto",localStorage.setItem(ii,c)}function _(b){const S=document.createElement("style");S.type="text/css",S.appendChild(document.createTextNode(`:not(.VPSwitchAppearance):not(.VPSwitchAppearance *) {
+**/const Nc="http://www.w3.org/2000/svg",Oc="http://www.w3.org/1998/Math/MathML",dt=typeof document<"u"?document:null,zo=dt&&dt.createElement("template"),Rc={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,s)=>{const o=t==="svg"?dt.createElementNS(Nc,e):t==="mathml"?dt.createElementNS(Oc,e):dt.createElement(e,n?{is:n}:void 0);return e==="select"&&s&&s.multiple!=null&&o.setAttribute("multiple",s.multiple),o},createText:e=>dt.createTextNode(e),createComment:e=>dt.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>dt.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,s,o,i){const r=n?n.previousSibling:t.lastChild;if(o&&(o===i||o.nextSibling))for(;t.insertBefore(o.cloneNode(!0),n),!(o===i||!(o=o.nextSibling)););else{zo.innerHTML=s==="svg"?`${e}`:s==="mathml"?`${e}`:e;const l=zo.content;if(s==="svg"||s==="mathml"){const c=l.firstChild;for(;c.firstChild;)l.appendChild(c.firstChild);l.removeChild(c)}t.insertBefore(l,n)}return[r?r.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}},lt="transition",Xt="animation",fn=Symbol("_vtc"),is=(e,{slots:t})=>Hn(Kl,Bc(e),t);is.displayName="Transition";const br={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};is.props=ke({},er,br);const kt=(e,t=[])=>{G(e)?e.forEach(n=>n(...t)):e&&e(...t)},Ko=e=>e?G(e)?e.some(t=>t.length>1):e.length>1:!1;function Bc(e){const t={};for(const M in e)M in br||(t[M]=e[M]);if(e.css===!1)return t;const{name:n="v",type:s,duration:o,enterFromClass:i=`${n}-enter-from`,enterActiveClass:r=`${n}-enter-active`,enterToClass:l=`${n}-enter-to`,appearFromClass:c=i,appearActiveClass:f=r,appearToClass:p=l,leaveFromClass:_=`${n}-leave-from`,leaveActiveClass:b=`${n}-leave-active`,leaveToClass:S=`${n}-leave-to`}=e,Y=Hc(o),q=Y&&Y[0],ne=Y&&Y[1],{onBeforeEnter:le,onEnter:ye,onEnterCancelled:v,onLeave:$,onLeaveCancelled:F,onBeforeAppear:N=le,onAppear:Z=ye,onAppearCancelled:R=v}=t,W=(M,te,be)=>{xt(M,te?p:l),xt(M,te?f:r),be&&be()},H=(M,te)=>{M._isLeaving=!1,xt(M,_),xt(M,S),xt(M,b),te&&te()},ce=M=>(te,be)=>{const we=M?Z:ye,j=()=>W(te,M,be);kt(we,[te,j]),Go(()=>{xt(te,M?c:i),ct(te,M?p:l),Ko(we)||qo(te,s,q,j)})};return ke(t,{onBeforeEnter(M){kt(le,[M]),ct(M,i),ct(M,r)},onBeforeAppear(M){kt(N,[M]),ct(M,c),ct(M,f)},onEnter:ce(!1),onAppear:ce(!0),onLeave(M,te){M._isLeaving=!0;const be=()=>H(M,te);ct(M,_),Uc(),ct(M,b),Go(()=>{M._isLeaving&&(xt(M,_),ct(M,S),Ko($)||qo(M,s,ne,be))}),kt($,[M,be])},onEnterCancelled(M){W(M,!1),kt(v,[M])},onAppearCancelled(M){W(M,!0),kt(R,[M])},onLeaveCancelled(M){H(M),kt(F,[M])}})}function Hc(e){if(e==null)return null;if(he(e))return[ys(e.enter),ys(e.leave)];{const t=ys(e);return[t,t]}}function ys(e){return Jr(e)}function ct(e,t){t.split(/\s+/).forEach(n=>n&&e.classList.add(n)),(e[fn]||(e[fn]=new Set)).add(t)}function xt(e,t){t.split(/\s+/).forEach(s=>s&&e.classList.remove(s));const n=e[fn];n&&(n.delete(t),n.size||(e[fn]=void 0))}function Go(e){requestAnimationFrame(()=>{requestAnimationFrame(e)})}let Fc=0;function qo(e,t,n,s){const o=e._endId=++Fc,i=()=>{o===e._endId&&s()};if(n)return setTimeout(i,n);const{type:r,timeout:l,propCount:c}=Dc(e,t);if(!r)return s();const f=r+"end";let p=0;const _=()=>{e.removeEventListener(f,b),i()},b=S=>{S.target===e&&++p>=c&&_()};setTimeout(()=>{p(n[Y]||"").split(", "),o=s(`${lt}Delay`),i=s(`${lt}Duration`),r=Wo(o,i),l=s(`${Xt}Delay`),c=s(`${Xt}Duration`),f=Wo(l,c);let p=null,_=0,b=0;t===lt?r>0&&(p=lt,_=r,b=i.length):t===Xt?f>0&&(p=Xt,_=f,b=c.length):(_=Math.max(r,f),p=_>0?r>f?lt:Xt:null,b=p?p===lt?i.length:c.length:0);const S=p===lt&&/\b(transform|all)(,|$)/.test(s(`${lt}Property`).toString());return{type:p,timeout:_,propCount:b,hasTransform:S}}function Wo(e,t){for(;e.lengthJo(n)+Jo(e[s])))}function Jo(e){return e==="auto"?0:Number(e.slice(0,-1).replace(",","."))*1e3}function Uc(){return document.body.offsetHeight}function jc(e,t,n){const s=e[fn];s&&(t=(t?[t,...s]:[...s]).join(" ")),t==null?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}const Yo=Symbol("_vod"),zc=Symbol("_vsh"),kr=Symbol("");function Kc(e){const t=ro();if(!t)return;const n=t.ut=(o=e(t.proxy))=>{Array.from(document.querySelectorAll(`[data-v-owner="${t.uid}"]`)).forEach(i=>Bs(i,o))},s=()=>{const o=e(t.proxy);Rs(t.subTree,o),n(o)};Xi(s),De(()=>{const o=new MutationObserver(s);o.observe(t.subTree.el.parentNode,{childList:!0}),mt(()=>o.disconnect())})}function Rs(e,t){if(e.shapeFlag&128){const n=e.suspense;e=n.activeBranch,n.pendingBranch&&!n.isHydrating&&n.effects.push(()=>{Rs(n.activeBranch,t)})}for(;e.component;)e=e.component.subTree;if(e.shapeFlag&1&&e.el)Bs(e.el,t);else if(e.type===Q)e.children.forEach(n=>Rs(n,t));else if(e.type===Dt){let{el:n,anchor:s}=e;for(;n&&(Bs(n,t),n!==s);)n=n.nextSibling}}function Bs(e,t){if(e.nodeType===1){const n=e.style;let s="";for(const o in t)n.setProperty(`--${o}`,t[o]),s+=`--${o}: ${t[o]};`;n[kr]=s}}const Gc=/(^|;)\s*display\s*:/;function qc(e,t,n){const s=e.style,o=ge(n);let i=!1;if(n&&!o){if(t)if(ge(t))for(const r of t.split(";")){const l=r.slice(0,r.indexOf(":")).trim();n[l]==null&&Ln(s,l,"")}else for(const r in t)n[r]==null&&Ln(s,r,"");for(const r in n)r==="display"&&(i=!0),Ln(s,r,n[r])}else if(o){if(t!==n){const r=s[kr];r&&(n+=";"+r),s.cssText=n,i=Gc.test(n)}}else t&&e.removeAttribute("style");Yo in e&&(e[Yo]=i?s.display:"",e[zc]&&(s.display="none"))}const Qo=/\s*!important$/;function Ln(e,t,n){if(G(n))n.forEach(s=>Ln(e,t,s));else if(n==null&&(n=""),t.startsWith("--"))e.setProperty(t,n);else{const s=Wc(e,t);Qo.test(n)?e.setProperty(Jt(s),n.replace(Qo,""),"important"):e[s]=n}}const Xo=["Webkit","Moz","ms"],bs={};function Wc(e,t){const n=bs[t];if(n)return n;let s=Ze(t);if(s!=="filter"&&s in e)return bs[t]=s;s=Gn(s);for(let o=0;oks||(ta.then(()=>ks=0),ks=Date.now());function sa(e,t){const n=s=>{if(!s._vts)s._vts=Date.now();else if(s._vts<=n.attached)return;Fe(oa(s,n.value),t,5,[s])};return n.value=e,n.attached=na(),n}function oa(e,t){if(G(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map(s=>o=>!o._stopped&&s&&s(o))}else return t}const ni=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,ia=(e,t,n,s,o,i,r,l,c)=>{const f=o==="svg";t==="class"?jc(e,s,f):t==="style"?qc(e,n,s):pn(t)?Fs(t)||Zc(e,t,n,s,r):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):ra(e,t,s,f))?Yc(e,t,s,i,r,l,c):(t==="true-value"?e._trueValue=s:t==="false-value"&&(e._falseValue=s),Jc(e,t,s,f))};function ra(e,t,n,s){if(s)return!!(t==="innerHTML"||t==="textContent"||t in e&&ni(t)&&ee(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 o=e.tagName;if(o==="IMG"||o==="VIDEO"||o==="CANVAS"||o==="SOURCE")return!1}return ni(t)&&ge(n)?!1:t in e}const la=["ctrl","shift","alt","meta"],ca={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&e.button!==0,middle:e=>"button"in e&&e.button!==1,right:e=>"button"in e&&e.button!==2,exact:(e,t)=>la.some(n=>e[`${n}Key`]&&!t.includes(n))},aa=(e,t)=>{const n=e._withMods||(e._withMods={}),s=t.join(".");return n[s]||(n[s]=(o,...i)=>{for(let r=0;r{const t=fa().createApp(...e),{mount:n}=t;return t.mount=s=>{const o=_a(s);if(o)return n(o,!0,pa(o))},t};function pa(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function _a(e){return ge(e)?document.querySelector(e):e}const O=(e,t)=>{const n=e.__vccOpts||e;for(const[s,o]of t)n[s]=o;return n},ha="modulepreload",va=function(e){return"/blog/"+e},oi={},ma=function(t,n,s){if(!n||n.length===0)return t();const o=document.getElementsByTagName("link");return Promise.all(n.map(i=>{if(i=va(i),i in oi)return;oi[i]=!0;const r=i.endsWith(".css"),l=r?'[rel="stylesheet"]':"";if(!!s)for(let p=o.length-1;p>=0;p--){const _=o[p];if(_.href===i&&(!r||_.rel==="stylesheet"))return}else if(document.querySelector(`link[href="${i}"]${l}`))return;const f=document.createElement("link");if(f.rel=r?"stylesheet":ha,r||(f.as="script",f.crossOrigin=""),f.href=i,document.head.appendChild(f),r)return new Promise((p,_)=>{f.addEventListener("load",p),f.addEventListener("error",()=>_(new Error(`Unable to preload CSS for ${i}`)))})})).then(()=>t()).catch(i=>{const r=new Event("vite:preloadError",{cancelable:!0});if(r.payload=i,window.dispatchEvent(r),!r.defaultPrevented)throw i})};const ga=B({__name:"VPBadge",props:{text:{},type:{}},setup(e){return(t,n)=>(d(),m("span",{class:pe(["VPBadge",t.type??"tip"])},[L(t.$slots,"default",{},()=>[Le(fe(t.text),1)],!0)],2))}});const ya=O(ga,[["__scopeId","data-v-e3a0c872"]]),ba=JSON.parse(`{"lang":"en-US","title":"Sunny's blog","description":"composition api form validator for vue","base":"/blog/","head":[],"appearance":true,"themeConfig":{"outline":[1,3],"sidebar":[{"text":"Introduction","collapsible":true,"items":[{"text":"Getting Started","link":"/getting-started"}]},{"text":"知识碎片(部分文章转载)","items":[{"text":"手撕babel插件-消灭console!","link":"/fragment/babel-console"},{"text":"Monorepo","link":"/fragment/Monorepo"},{"text":"🔥 微内核架构在前端的实现及其应用","link":"/fragment/微内核架构"},{"text":"如何实现准时的setTimeout","link":"/fragment/setTimeout"},{"text":"Git","link":"/fe-utils/git"},{"text":"前端 JavaScript 必会工具库合集","link":"/fe-utils/js工具库"},{"text":"一站式-后台前端解决方案","link":"/article/cms"},{"text":"涌现出来的新tools","link":"/fe-utils/tool"}]},{"text":"前端工程化","collapsible":true,"items":[{"text":"前端工程化 OnePage","link":"/front-end-engineering/engineering-onepage"},{"text":"前端性能优化方法论","link":"/front-end-engineering/performance"},{"text":"webpack常用拓展","link":"/front-end-engineering/webpack常用拓展"},{"text":"CSS工程化","link":"/front-end-engineering/CSS工程化"},{"text":"JS 兼容性","link":"/front-end-engineering/jscompatibility"},{"text":"webpack5 模块联邦","link":"/front-end-engineering/webpack5-mf"},{"text":"pnpm原理","link":"/front-end-engineering/pnpm原理"},{"text":"模块化","link":"/front-end-engineering/modularization"},{"text":"包管理器","link":"/front-end-engineering/PackageManager"}]},{"text":"Vuejs 21篇","collapsible":true,"items":[{"text":"那些年,被问烂了的 Vuejs 面试题","link":"/vue/vue-interview"},{"text":"虚拟DOM详解","link":"/vue/vdom"},{"text":"Vuejs 组件通信概览","link":"/vue/component-communication"},{"text":"探索 v-model 原理","link":"/vue/v-model"},{"text":"Vuejs 数据响应原理","link":"/vue/reactive"},{"text":"Vue2 diff算法原理","link":"/vue/diff"},{"text":"Vue 生命周期","link":"/vue/lifecycle"},{"text":"computed","link":"/vue/computed"},{"text":"keep-alive 与 LRU 算法","link":"/vue/keep-alive-LRU"},{"text":"Vue3 新特性","link":"/vue/vue3-onepage"},{"text":"vue-cli 到底帮我们做了什么","link":"/vue/vue-cli"},{"text":"Vue 编译器为什么如此强大","link":"/vue/vue-compile"},{"text":"$nextTick 工作原理","link":"/vue/nextTick"},{"text":"Vue 和 React 的核心区别","link":"/vue/vs"},{"text":"一篇精通 slot","link":"/vue/slot"},{"text":"Vue SSR 服务端渲染","link":"/vue/SSR"},{"text":"Vue 指令篇","link":"/vue/directive"},{"text":"VueRouter 路由从使用到源码","link":"/vue/vue-router"},{"text":"Vuex 思想和源码","link":"/vue/vuex"},{"text":"今天我是面试官","link":"/vue/interviewer"},{"text":"vue 手写篇","link":"/vue/challages"}]},{"text":"React 16篇","collapsible":true,"items":[{"text":"React onePage","link":"/react/"},{"text":"那些年,被问烂了的 React 面试题","link":"/react/react-interview"},{"text":"React Hooks","link":"/react/hooks"},{"text":"React 组件通信","link":"/react/component-communication"},{"text":"React Context","link":"/react/context"},{"text":"React 生命周期","link":"/react/lifecycle"},{"text":"React 事件机制","link":"/react/event"},{"text":"React 渲染原理","link":"/react/render"},{"text":"React 动画","link":"/react/transition"},{"text":"React Router","link":"/react/ReactRouter"},{"text":"JS 应用的状态容器,提供可预测的状态管理 Redux","link":"/react/Redux"},{"text":"视图-仓库-路由","link":"/react/react-redux-router"},{"text":"dva 设计思想","link":"/react/dva"},{"text":"Umi 企业级前端框架","link":"/react/umi"},{"text":"React 调试工具","link":"/react/utils"},{"text":"Fiber","link":"/react/Fiber"}]},{"text":"TypeScript","items":[{"text":"TypeScript OnePage","link":"/ts/TypeScript-onePage"}]},{"text":"前端三件套","collapsible":true,"items":[{"text":"异步处理","link":"/js/异步处理"},{"text":"代理与反射","link":"/js/代理与反射"},{"text":"迭代器和生成器","link":"/js/迭代器和生成器"},{"text":"HTML_CSS 面试题","link":"/html-css/interview"},{"text":"HTML5 OnePage","link":"/html-css/HTML"},{"text":"CSS3 OnePage","link":"/html-css/CSS"},{"text":"Canvas 和 svg","link":"/html-css/canvas-svg"},{"text":"CSS3动画","link":"/html-css/animation"},{"text":"CSS3选择器","link":"/html-css/selector"},{"text":"显示器的成像原理","link":"/html-css/principle"},{"text":"拖拽 API","link":"/html-css/drag"}]},{"text":"算法","collapsible":true,"items":[{"text":"🔥刷题之探索最优解","link":"/algorithm/🔥刷题之探索最优解"}]},{"text":"面试系列","collapsible":true,"items":[{"text":"面试官:你还有问题要问我吗","link":"/interview/面试官:你还有问题要问我吗"},{"text":"算法笔试","link":"/interview/算法笔试"}]}],"nav":[{"text":"知识碎片(部分文章转载)","items":[{"text":"手撕babel插件-消灭console!","link":"/fragment/babel-console"},{"text":"Monorepo","link":"/fragment/Monorepo"},{"text":"🔥 微内核架构在前端的实现及其应用","link":"/fragment/微内核架构"},{"text":"如何实现准时的setTimeout","link":"/fragment/setTimeout"},{"text":"Git","link":"/fe-utils/git"},{"text":"前端 JavaScript 必会工具库合集","link":"/fe-utils/js工具库"},{"text":"一站式-后台前端解决方案","link":"/article/cms"},{"text":"涌现出来的新tools","link":"/fe-utils/tool"}]},{"text":"前端工程化","collapsible":true,"items":[{"text":"前端工程化 OnePage","link":"/front-end-engineering/engineering-onepage"},{"text":"前端性能优化方法论","link":"/front-end-engineering/performance"},{"text":"webpack常用拓展","link":"/front-end-engineering/webpack常用拓展"},{"text":"CSS工程化","link":"/front-end-engineering/CSS工程化"},{"text":"JS 兼容性","link":"/front-end-engineering/jscompatibility"},{"text":"webpack5 模块联邦","link":"/front-end-engineering/webpack5-mf"},{"text":"pnpm原理","link":"/front-end-engineering/pnpm原理"},{"text":"模块化","link":"/front-end-engineering/modularization"},{"text":"包管理器","link":"/front-end-engineering/PackageManager"}]},{"text":"Vuejs 21篇","collapsible":true,"items":[{"text":"那些年,被问烂了的 Vuejs 面试题","link":"/vue/vue-interview"},{"text":"虚拟DOM详解","link":"/vue/vdom"},{"text":"Vuejs 组件通信概览","link":"/vue/component-communication"},{"text":"探索 v-model 原理","link":"/vue/v-model"},{"text":"Vuejs 数据响应原理","link":"/vue/reactive"},{"text":"Vue2 diff算法原理","link":"/vue/diff"},{"text":"Vue 生命周期","link":"/vue/lifecycle"},{"text":"computed","link":"/vue/computed"},{"text":"keep-alive 与 LRU 算法","link":"/vue/keep-alive-LRU"},{"text":"Vue3 新特性","link":"/vue/vue3-onepage"},{"text":"vue-cli 到底帮我们做了什么","link":"/vue/vue-cli"},{"text":"Vue 编译器为什么如此强大","link":"/vue/vue-compile"},{"text":"$nextTick 工作原理","link":"/vue/nextTick"},{"text":"Vue 和 React 的核心区别","link":"/vue/vs"},{"text":"一篇精通 slot","link":"/vue/slot"},{"text":"Vue SSR 服务端渲染","link":"/vue/SSR"},{"text":"Vue 指令篇","link":"/vue/directive"},{"text":"VueRouter 路由从使用到源码","link":"/vue/vue-router"},{"text":"Vuex 思想和源码","link":"/vue/vuex"},{"text":"今天我是面试官","link":"/vue/interviewer"},{"text":"vue 手写篇","link":"/vue/challages"}]},{"text":"React 16篇","collapsible":true,"items":[{"text":"React onePage","link":"/react/"},{"text":"那些年,被问烂了的 React 面试题","link":"/react/react-interview"},{"text":"React Hooks","link":"/react/hooks"},{"text":"React 组件通信","link":"/react/component-communication"},{"text":"React Context","link":"/react/context"},{"text":"React 生命周期","link":"/react/lifecycle"},{"text":"React 事件机制","link":"/react/event"},{"text":"React 渲染原理","link":"/react/render"},{"text":"React 动画","link":"/react/transition"},{"text":"React Router","link":"/react/ReactRouter"},{"text":"JS 应用的状态容器,提供可预测的状态管理 Redux","link":"/react/Redux"},{"text":"视图-仓库-路由","link":"/react/react-redux-router"},{"text":"dva 设计思想","link":"/react/dva"},{"text":"Umi 企业级前端框架","link":"/react/umi"},{"text":"React 调试工具","link":"/react/utils"},{"text":"Fiber","link":"/react/Fiber"}]}],"socialLinks":[{"icon":"github","link":"https://github.com/Sunny-117/blog"}],"footer":{"copyright":"Copyright © 2022-present sunny-117"},"editLink":{"pattern":"https://github.com/Sunny-117/blog","text":"Edit this page on Gitlab"},"lastUpdatedText":"Last Updated"},"locales":{},"langs":{},"scrollOffset":90,"cleanUrls":"disabled"}`),rs=/^[a-z]+:/i,ka=/^pathname:\/\//,ii="vitepress-theme-appearance",Te=typeof window<"u",xr={relativePath:"",title:"404",description:"Not Found",headers:[],frontmatter:{sidebar:!1,layout:"page"},lastUpdated:0};function xa(e,t){t.sort((n,s)=>{const o=s.split("/").length-n.split("/").length;return o!==0?o:s.length-n.length});for(const n of t)if(e.startsWith(n))return n}function ri(e,t){const n=xa(t,Object.keys(e));return n?e[n]:void 0}function wa(e){const{locales:t}=e.themeConfig||{},n=e.locales;return t&&n?Object.keys(t).reduce((s,o)=>(s[o]={label:t[o].label,lang:n[o].lang},s),{}):{}}function $a(e,t){t=Sa(e,t);const n=ri(e.locales||{},t),s=ri(e.themeConfig.locales||{},t);return Object.assign({},e,n,{themeConfig:Object.assign({},e.themeConfig,s,{locales:{}}),lang:(n||e).lang,locales:{},langs:wa(e)})}function wr(e,t){const n=t.title||e.title,s=t.titleTemplate??e.titleTemplate;if(typeof s=="string"&&s.includes(":title"))return s.replace(/:title/g,n);const o=Pa(e.title,s);return`${n}${o}`}function Pa(e,t){return t===!1?"":t===!0||t===void 0?` | ${e}`:e===t?"":` | ${t}`}function Sa(e,t){if(!Te)return t;const n=e.base,s=n.endsWith("/")?n.slice(0,-1):n;return t.slice(s.length)}function Ca(e,t){const[n,s]=t;if(n!=="meta")return!1;const o=Object.entries(s)[0];return o==null?!1:e.some(([i,r])=>i===n&&r[o[0]]===o[1])}function Va(e,t){return[...e.filter(n=>!Ca(t,n)),...t]}const Ta=/[\u0000-\u001F"#$&*+,:;<=>?[\]^`{|}\u007F]/g,La=/^[a-z]:/i;function li(e){const t=La.exec(e),n=t?t[0]:"";return n+e.slice(n.length).replace(Ta,"_").replace(/(^|\/)_+(?=[^/]*$)/,"$1")}function Ea(e,t){return`${e}${t}`.replace(/\/+/g,"/")}function dn(e){return rs.test(e)?e:Ea(qt.value.base,e)}function $r(e){let t=e.replace(/\.html$/,"");if(t=decodeURIComponent(t),t.endsWith("/")&&(t+="index"),Te){const n="/blog/";t=li(t.slice(n.length).replace(/\//g,"_")||"index")+".md";const s=__VP_HASH_MAP__[t.toLowerCase()];t=`${n}assets/${t}.${s}.js`}else t=`./${li(t.slice(1).replace(/\//g,"_"))}.md.js`;return t}const Pr=Symbol(),qt=Sl(ba);function Ma(e){const t=re(()=>$a(qt.value,e.path));return{site:t,theme:re(()=>t.value.themeConfig),page:re(()=>e.data),frontmatter:re(()=>e.data.frontmatter),lang:re(()=>t.value.lang),localePath:re(()=>{const{langs:n,lang:s}=t.value,o=Object.keys(n).find(i=>n[i].lang===s);return dn(o||"/")}),title:re(()=>wr(t.value,e.data)),description:re(()=>e.data.description||t.value.description),isDark:_e(!1)}}function ue(){const e=Ke(Pr);if(!e)throw new Error("vitepress data not properly injected in app");return e}const Sr=Symbol(),ci="http://a.com",Aa=()=>({path:"/",component:null,data:xr});function Ia(e,t){const n=Jn(Aa()),s={route:n,go:o};async function o(l=Te?location.href:"/"){var f,p;await((f=s.onBeforeRouteChange)==null?void 0:f.call(s,l));const c=new URL(l,ci);qt.value.cleanUrls==="disabled"&&!c.pathname.endsWith("/")&&!c.pathname.endsWith(".html")&&(c.pathname+=".html",l=c.pathname+c.search+c.hash),Te&&(history.replaceState({scrollPosition:window.scrollY},document.title),history.pushState(null,"",l)),await r(l),await((p=s.onAfterRouteChanged)==null?void 0:p.call(s,l))}let i=null;async function r(l,c=0,f=!1){const p=new URL(l,ci),_=i=p.pathname;try{let b=await e(_);if(i===_){i=null;const{default:S,__pageData:Y}=b;if(!S)throw new Error(`Invalid route component: ${S}`);n.path=Te?_:dn(_),n.component=en(S),n.data=en(Y),Te&&Xs(()=>{if(p.hash&&!c){let q=null;try{q=document.querySelector(decodeURIComponent(p.hash))}catch(ne){console.warn(ne)}if(q){ai(q,p.hash);return}}window.scrollTo(0,c)})}}catch(b){if(!/fetch/.test(b.message)&&!/^\/404(\.html|\/)?$/.test(l)&&console.error(b),!f)try{const S=await fetch(qt.value.base+"hashmap.json");window.__VP_HASH_MAP__=await S.json(),await r(l,c,!0);return}catch{}i===_&&(i=null,n.path=Te?_:dn(_),n.component=t?en(t):null,n.data=xr)}}return Te&&(window.addEventListener("click",l=>{if(l.target.closest("button"))return;const f=l.target.closest("a");if(f&&!f.closest(".vp-raw")&&!f.download){const{href:p,origin:_,pathname:b,hash:S,search:Y,target:q}=f,ne=window.location,le=b.match(/\.\w+$/);!l.ctrlKey&&!l.shiftKey&&!l.altKey&&!l.metaKey&&q!=="_blank"&&_===ne.origin&&!(le&&le[0]!==".html")&&(l.preventDefault(),b===ne.pathname&&Y===ne.search?S&&S!==ne.hash&&(history.pushState(null,"",S),window.dispatchEvent(new Event("hashchange")),ai(f,S,f.classList.contains("header-anchor"))):o(p))}},{capture:!0}),window.addEventListener("popstate",l=>{r(location.href,l.state&&l.state.scrollPosition||0)}),window.addEventListener("hashchange",l=>{l.preventDefault()})),s}function Na(){const e=Ke(Sr);if(!e)throw new Error("useRouter() is called without provider.");return e}function gt(){return Na().route}function ai(e,t,n=!1){let s=null;try{s=e.classList.contains("header-anchor")?e:document.querySelector(decodeURIComponent(t))}catch(o){console.warn(o)}if(s){let o=qt.value.scrollOffset;typeof o=="string"&&(o=document.querySelector(o).getBoundingClientRect().bottom+24);const i=parseInt(window.getComputedStyle(s).paddingTop,10),r=window.scrollY+s.getBoundingClientRect().top-o+i;!n||Math.abs(r-window.scrollY)>window.innerHeight?window.scrollTo(0,r):window.scrollTo({left:0,top:r,behavior:"smooth"})}}const Oa=B({name:"VitePressContent",props:{onContentUpdated:Function},setup(e){const t=gt();return no(()=>{var n;(n=e.onContentUpdated)==null||n.call(e)}),()=>Hn("div",{style:{position:"relative"}},[t.component?Hn(t.component):null])}}),Cr=/#.*$/,Ra=/(index)?\.(md|html)$/,Ba=typeof window<"u",Ha=_e(Ba?location.hash:"");function Fa(e){return rs.test(e)}function Da(e,t){let n,s=!1;return()=>{n&&clearTimeout(n),s?n=setTimeout(e,t):(e(),s=!0,setTimeout(()=>{s=!1},t))}}function Yt(e,t,n=!1){if(t===void 0)return!1;if(e=fi(`/${e}`),n)return new RegExp(t).test(e);if(fi(t)!==e)return!1;const s=t.match(Cr);return s?Ha.value===s[0]:!0}function ui(e){return/^\//.test(e)?e:`/${e}`}function fi(e){return decodeURI(e).replace(Cr,"").replace(Ra,"")}function Fn(e){if(Fa(e))return e.replace(ka,"");const{site:t}=ue(),{pathname:n,search:s,hash:o}=new URL(e,"http://example.com"),i=n.endsWith("/")||n.endsWith(".html")?e:`${n.replace(/(\.md)?$/,t.value.cleanUrls==="disabled"?".html":"")}${s}${o}`;return dn(i)}function Vr(e,t){if(Array.isArray(e))return e;if(e==null)return[];t=ui(t);const n=Object.keys(e).sort((s,o)=>o.split("/").length-s.split("/").length).find(s=>t.startsWith(ui(s)));return n?e[n]:[]}function Ua(e){const t=[];function n(s){for(const o of s)o.link&&t.push({...o,link:o.link}),"items"in o&&n(o.items)}for(const s of e)n(s.items);return t}function et(){const e=gt(),{theme:t,frontmatter:n}=ue(),s=_e(!1),o=re(()=>{const p=t.value.sidebar,_=e.data.relativePath;return p?Vr(p,_):[]}),i=re(()=>n.value.sidebar!==!1&&o.value.length>0&&n.value.layout!=="home"),r=re(()=>n.value.layout!=="home"&&n.value.aside!==!1);function l(){s.value=!0}function c(){s.value=!1}function f(){s.value?c():l()}return{isOpen:s,sidebar:o,hasSidebar:i,hasAside:r,open:l,close:c,toggle:f}}function ja(e,t){let n;Kt(()=>{n=e.value?document.activeElement:void 0}),De(()=>{window.addEventListener("keyup",s)}),mt(()=>{window.removeEventListener("keyup",s)});function s(o){o.key==="Escape"&&e.value&&(t(),n==null||n.focus())}}const za=B({__name:"VPSkipLink",setup(e){const t=gt(),n=_e();Xe(()=>t.path,()=>n.value.focus());function s({target:o}){const i=document.querySelector(o.hash);if(i){const r=()=>{i.removeAttribute("tabindex"),i.removeEventListener("blur",r)};i.setAttribute("tabindex","-1"),i.addEventListener("blur",r),i.focus(),window.scrollTo(0,0)}}return(o,i)=>(d(),m(Q,null,[g("span",{ref_key:"backToTop",ref:n,tabindex:"-1"},null,512),g("a",{href:"#VPContent",class:"VPSkipLink visually-hidden",onClick:s}," Skip to content ")],64))}});const Ka=O(za,[["__scopeId","data-v-7c1bd20b"]]),Ga={key:0,class:"VPBackdrop"},qa=B({__name:"VPBackdrop",props:{show:{type:Boolean}},setup(e){return(t,n)=>(d(),X(is,{name:"fade"},{default:I(()=>[t.show?(d(),m("div",Ga)):K("",!0)]),_:1}))}});const Wa=O(qa,[["__scopeId","data-v-5b48b308"]]);function Ja(){const e=_e(!1);function t(){e.value=!0,window.addEventListener("resize",o)}function n(){e.value=!1,window.removeEventListener("resize",o)}function s(){e.value?n():t()}function o(){window.outerWidth>=768&&n()}const i=gt();return Xe(()=>i.path,n),{isScreenOpen:e,openScreen:t,closeScreen:n,toggleScreen:s}}const Ya=["src","alt"],Qa={inheritAttrs:!1},Xa=B({...Qa,__name:"VPImage",props:{image:{},alt:{}},setup(e){return(t,n)=>{const s=Lt("VPImage",!0);return t.image?(d(),m(Q,{key:0},[typeof t.image=="string"||"src"in t.image?(d(),m("img",Tn({key:0,class:"VPImage"},typeof t.image=="string"?t.$attrs:{...t.image,...t.$attrs},{src:y(dn)(typeof t.image=="string"?t.image:t.image.src),alt:t.alt??(typeof t.image=="string"?"":t.image.alt||"")}),null,16,Ya)):(d(),m(Q,{key:1},[T(s,Tn({class:"dark",image:t.image.dark,alt:typeof t.image.dark=="string"?t.image.alt:t.image.dark.alt||t.image.alt},t.$attrs),null,16,["image","alt"]),T(s,Tn({class:"light",image:t.image.light,alt:typeof t.image.light=="string"?t.image.alt:t.image.light.alt||t.image.alt},t.$attrs),null,16,["image","alt"])],64))],64)):K("",!0)}}});const Tr=O(Xa,[["__scopeId","data-v-d61f9ddc"]]),Za=["href"],eu=B({__name:"VPNavBarTitle",setup(e){const{site:t,theme:n}=ue(),{hasSidebar:s}=et();return(o,i)=>(d(),m("div",{class:pe(["VPNavBarTitle",{"has-sidebar":y(s)}])},[g("a",{class:"title",href:y(t).base},[L(o.$slots,"nav-bar-title-before",{},void 0,!0),T(Tr,{class:"logo",image:y(n).logo},null,8,["image"]),y(n).siteTitle?(d(),m(Q,{key:0},[Le(fe(y(n).siteTitle),1)],64)):y(n).siteTitle===void 0?(d(),m(Q,{key:1},[Le(fe(y(t).title),1)],64)):K("",!0),L(o.$slots,"nav-bar-title-after",{},void 0,!0)],8,Za)],2))}});const tu=O(eu,[["__scopeId","data-v-e9161676"]]);const nu={key:0,class:"VPNavBarSearch"},su={type:"button",class:"DocSearch DocSearch-Button","aria-label":"Search"},ou={class:"DocSearch-Button-Container"},iu=g("svg",{class:"DocSearch-Search-Icon",width:"20",height:"20",viewBox:"0 0 20 20"},[g("path",{d:"M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z",stroke:"currentColor",fill:"none","fill-rule":"evenodd","stroke-linecap":"round","stroke-linejoin":"round"})],-1),ru={class:"DocSearch-Button-Placeholder"},lu=g("span",{class:"DocSearch-Button-Keys"},[g("kbd",{class:"DocSearch-Button-Key"}),g("kbd",{class:"DocSearch-Button-Key"},"K")],-1),cu=B({__name:"VPNavBarSearch",setup(e){Kc(r=>({"52169baa":o.value}));const t=()=>null,{theme:n}=ue(),s=_e(!1),o=_e("'Meta'");De(()=>{if(!n.value.algolia)return;o.value=/(Mac|iPhone|iPod|iPad)/i.test(navigator.platform)?"'⌘'":"'Ctrl'";const r=c=>{c.key==="k"&&(c.ctrlKey||c.metaKey)&&(c.preventDefault(),i(),l())},l=()=>{window.removeEventListener("keydown",r)};window.addEventListener("keydown",r),mt(l)});function i(){s.value||(s.value=!0)}return(r,l)=>{var c;return y(n).algolia?(d(),m("div",nu,[s.value?(d(),X(y(t),{key:0})):(d(),m("div",{key:1,id:"docsearch",onClick:i},[g("button",su,[g("span",ou,[iu,g("span",ru,fe(((c=y(n).algolia)==null?void 0:c.buttonText)||"Search"),1)]),lu])]))])):K("",!0)}}});const au={},uu={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",height:"24px",viewBox:"0 0 24 24",width:"24px"},fu=g("path",{d:"M0 0h24v24H0V0z",fill:"none"},null,-1),du=g("path",{d:"M9 5v2h6.59L4 18.59 5.41 20 17 8.41V15h2V5H9z"},null,-1),pu=[fu,du];function _u(e,t){return d(),m("svg",uu,pu)}const hu=O(au,[["render",_u]]),vu=B({__name:"VPLink",props:{href:{},noIcon:{type:Boolean}},setup(e){const t=e,n=re(()=>t.href&&rs.test(t.href));return(s,o)=>(d(),X(to(s.href?"a":"span"),{class:pe(["VPLink",{link:s.href}]),href:s.href?y(Fn)(s.href):void 0,target:n.value?"_blank":void 0,rel:n.value?"noreferrer":void 0},{default:I(()=>[L(s.$slots,"default",{},void 0,!0),n.value&&!s.noIcon?(d(),X(hu,{key:0,class:"icon"})):K("",!0)]),_:3},8,["class","href","target","rel"]))}});const Et=O(vu,[["__scopeId","data-v-c145185f"]]),mu=B({__name:"VPNavBarMenuLink",props:{item:{}},setup(e){const{page:t}=ue();return(n,s)=>(d(),X(Et,{class:pe({VPNavBarMenuLink:!0,active:y(Yt)(y(t).relativePath,n.item.activeMatch||n.item.link,!!n.item.activeMatch)}),href:n.item.link,noIcon:!0},{default:I(()=>[Le(fe(n.item.text),1)]),_:1},8,["class","href"]))}});const gu=O(mu,[["__scopeId","data-v-428c9a29"]]),co=_e();let Lr=!1,ws=0;function yu(e){const t=_e(!1);if(typeof window<"u"){!Lr&&bu(),ws++;const n=Xe(co,s=>{var o,i,r;s===e.el.value||(o=e.el.value)!=null&&o.contains(s)?(t.value=!0,(i=e.onFocus)==null||i.call(e)):(t.value=!1,(r=e.onBlur)==null||r.call(e))});mt(()=>{n(),ws--,ws||ku()})}return Ws(t)}function bu(){document.addEventListener("focusin",Er),Lr=!0,co.value=document.activeElement}function ku(){document.removeEventListener("focusin",Er)}function Er(){co.value=document.activeElement}const xu={},wu={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},$u=g("path",{d:"M12,16c-0.3,0-0.5-0.1-0.7-0.3l-6-6c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l5.3,5.3l5.3-5.3c0.4-0.4,1-0.4,1.4,0s0.4,1,0,1.4l-6,6C12.5,15.9,12.3,16,12,16z"},null,-1),Pu=[$u];function Su(e,t){return d(),m("svg",wu,Pu)}const Mr=O(xu,[["render",Su]]),Cu={},Vu={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},Tu=g("circle",{cx:"12",cy:"12",r:"2"},null,-1),Lu=g("circle",{cx:"19",cy:"12",r:"2"},null,-1),Eu=g("circle",{cx:"5",cy:"12",r:"2"},null,-1),Mu=[Tu,Lu,Eu];function Au(e,t){return d(),m("svg",Vu,Mu)}const Iu=O(Cu,[["render",Au]]),Nu={class:"VPMenuLink"},Ou=B({__name:"VPMenuLink",props:{item:{}},setup(e){const{page:t}=ue();return(n,s)=>(d(),m("div",Nu,[T(Et,{class:pe({active:y(Yt)(y(t).relativePath,n.item.activeMatch||n.item.link)}),href:n.item.link},{default:I(()=>[Le(fe(n.item.text),1)]),_:1},8,["class","href"])]))}});const ls=O(Ou,[["__scopeId","data-v-ff9fa6d5"]]),Ru={class:"VPMenuGroup"},Bu={key:0,class:"title"},Hu=B({__name:"VPMenuGroup",props:{text:{},items:{}},setup(e){return(t,n)=>(d(),m("div",Ru,[t.text?(d(),m("p",Bu,fe(t.text),1)):K("",!0),(d(!0),m(Q,null,Ce(t.items,s=>(d(),m(Q,null,["link"in s?(d(),X(ls,{key:0,item:s},null,8,["item"])):K("",!0)],64))),256))]))}});const Fu=O(Hu,[["__scopeId","data-v-d0845d3f"]]),Du={class:"VPMenu"},Uu={key:0,class:"items"},ju=B({__name:"VPMenu",props:{items:{}},setup(e){return(t,n)=>(d(),m("div",Du,[t.items?(d(),m("div",Uu,[(d(!0),m(Q,null,Ce(t.items,s=>(d(),m(Q,{key:s.text},["link"in s?(d(),X(ls,{key:0,item:s},null,8,["item"])):(d(),X(Fu,{key:1,text:s.text,items:s.items},null,8,["text","items"]))],64))),128))])):K("",!0),L(t.$slots,"default",{},void 0,!0)]))}});const zu=O(ju,[["__scopeId","data-v-796849f7"]]),Ku=["aria-expanded","aria-label"],Gu={key:0,class:"text"},qu={class:"menu"},Wu=B({__name:"VPFlyout",props:{icon:{},button:{},label:{},items:{}},setup(e){const t=_e(!1),n=_e();yu({el:n,onBlur:s});function s(){t.value=!1}return(o,i)=>(d(),m("div",{class:"VPFlyout",ref_key:"el",ref:n,onMouseenter:i[1]||(i[1]=r=>t.value=!0),onMouseleave:i[2]||(i[2]=r=>t.value=!1)},[g("button",{type:"button",class:"button","aria-haspopup":"true","aria-expanded":t.value,"aria-label":o.label,onClick:i[0]||(i[0]=r=>t.value=!t.value)},[o.button||o.icon?(d(),m("span",Gu,[o.icon?(d(),X(to(o.icon),{key:0,class:"option-icon"})):K("",!0),Le(" "+fe(o.button)+" ",1),T(Mr,{class:"text-icon"})])):(d(),X(Iu,{key:1,class:"icon"}))],8,Ku),g("div",qu,[T(zu,{items:o.items},{default:I(()=>[L(o.$slots,"default",{},void 0,!0)]),_:3},8,["items"])])],544))}});const ao=O(Wu,[["__scopeId","data-v-5f738fe3"]]),Ju=B({__name:"VPNavBarMenuGroup",props:{item:{}},setup(e){const{page:t}=ue();return(n,s)=>(d(),X(ao,{class:pe({VPNavBarMenuGroup:!0,active:y(Yt)(y(t).relativePath,n.item.activeMatch,!!n.item.activeMatch)}),button:n.item.text,items:n.item.items},null,8,["class","button","items"]))}}),Yu=e=>(Ge("data-v-e6b3a2a4"),e=e(),qe(),e),Qu={key:0,"aria-labelledby":"main-nav-aria-label",class:"VPNavBarMenu"},Xu=Yu(()=>g("span",{id:"main-nav-aria-label",class:"visually-hidden"},"Main Navigation",-1)),Zu=B({__name:"VPNavBarMenu",setup(e){const{theme:t}=ue();return(n,s)=>y(t).nav?(d(),m("nav",Qu,[Xu,(d(!0),m(Q,null,Ce(y(t).nav,o=>(d(),m(Q,{key:o.text},["link"in o?(d(),X(gu,{key:0,item:o},null,8,["item"])):(d(),X(Ju,{key:1,item:o},null,8,["item"]))],64))),128))])):K("",!0)}});const ef=O(Zu,[["__scopeId","data-v-e6b3a2a4"]]),tf={},nf={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},sf=g("path",{d:"M0 0h24v24H0z",fill:"none"},null,-1),of=g("path",{d:" M12.87 15.07l-2.54-2.51.03-.03c1.74-1.94 2.98-4.17 3.71-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z ",class:"css-c4d79v"},null,-1),rf=[sf,of];function lf(e,t){return d(),m("svg",nf,rf)}const Ar=O(tf,[["render",lf]]),cf={class:"items"},af={class:"title"},uf=B({__name:"VPNavBarTranslations",setup(e){const{theme:t}=ue();return(n,s)=>y(t).localeLinks?(d(),X(ao,{key:0,class:"VPNavBarTranslations",icon:Ar},{default:I(()=>[g("div",cf,[g("p",af,fe(y(t).localeLinks.text),1),(d(!0),m(Q,null,Ce(y(t).localeLinks.items,o=>(d(),X(ls,{key:o.link,item:o},null,8,["item"]))),128))])]),_:1})):K("",!0)}});const ff=O(uf,[["__scopeId","data-v-613d3281"]]);const df={},pf={class:"VPSwitch",type:"button",role:"switch"},_f={class:"check"},hf={key:0,class:"icon"};function vf(e,t){return d(),m("button",pf,[g("span",_f,[e.$slots.default?(d(),m("span",hf,[L(e.$slots,"default",{},void 0,!0)])):K("",!0)])])}const mf=O(df,[["render",vf],["__scopeId","data-v-8e78fc3c"]]),gf={},yf={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},bf=$c('',9),kf=[bf];function xf(e,t){return d(),m("svg",yf,kf)}const wf=O(gf,[["render",xf]]),$f={},Pf={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},Sf=g("path",{d:"M12.1,22c-0.3,0-0.6,0-0.9,0c-5.5-0.5-9.5-5.4-9-10.9c0.4-4.8,4.2-8.6,9-9c0.4,0,0.8,0.2,1,0.5c0.2,0.3,0.2,0.8-0.1,1.1c-2,2.7-1.4,6.4,1.3,8.4c2.1,1.6,5,1.6,7.1,0c0.3-0.2,0.7-0.3,1.1-0.1c0.3,0.2,0.5,0.6,0.5,1c-0.2,2.7-1.5,5.1-3.6,6.8C16.6,21.2,14.4,22,12.1,22zM9.3,4.4c-2.9,1-5,3.6-5.2,6.8c-0.4,4.4,2.8,8.3,7.2,8.7c2.1,0.2,4.2-0.4,5.8-1.8c1.1-0.9,1.9-2.1,2.4-3.4c-2.5,0.9-5.3,0.5-7.5-1.1C9.2,11.4,8.1,7.7,9.3,4.4z"},null,-1),Cf=[Sf];function Vf(e,t){return d(),m("svg",Pf,Cf)}const Tf=O($f,[["render",Vf]]),Lf=B({__name:"VPSwitchAppearance",setup(e){const{site:t,isDark:n}=ue(),s=_e(!1),o=typeof localStorage<"u"?i():()=>{};De(()=>{s.value=document.documentElement.classList.contains("dark")});function i(){const r=window.matchMedia("(prefers-color-scheme: dark)"),l=document.documentElement.classList;let c=localStorage.getItem(ii),f=t.value.appearance==="dark"&&c==null||(c==="auto"||c==null?r.matches:c==="dark");r.onchange=b=>{c==="auto"&&_(f=b.matches)};function p(){_(f=!f),c=f?r.matches?"auto":"dark":r.matches?"light":"auto",localStorage.setItem(ii,c)}function _(b){const S=document.createElement("style");S.type="text/css",S.appendChild(document.createTextNode(`:not(.VPSwitchAppearance):not(.VPSwitchAppearance *) {
   -webkit-transition: none !important;
   -moz-transition: none !important;
   -o-transition: none !important;
   -ms-transition: none !important;
   transition: none !important;
-}`)),document.head.appendChild(S),s.value=b,l[b?"add":"remove"]("dark"),window.getComputedStyle(S).opacity,document.head.removeChild(S)}return p}return Xe(s,r=>{n.value=r}),(r,l)=>(d(),X(mf,{class:"VPSwitchAppearance","aria-label":"toggle dark mode","aria-checked":s.value,onClick:y(o)},{default:I(()=>[T(xf,{class:"sun"}),T(Tf,{class:"moon"})]),_:1},8,["aria-checked","onClick"]))}});const uo=O(Lf,[["__scopeId","data-v-d3b077f5"]]),Ef={key:0,class:"VPNavBarAppearance"},Mf=B({__name:"VPNavBarAppearance",setup(e){const{site:t}=ue();return(n,s)=>y(t).appearance?(d(),m("div",Ef,[T(uo)])):K("",!0)}});const Af=O(Mf,[["__scopeId","data-v-64df1c2d"]]),If={discord:'Discord',facebook:'Facebook',github:'GitHub',instagram:'Instagram',linkedin:'LinkedIn',slack:'Slack',twitter:'Twitter',youtube:'YouTube'},Nf=["href","innerHTML"],Of=B({__name:"VPSocialLink",props:{icon:{},link:{}},setup(e){const t=e,n=re(()=>typeof t.icon=="object"?t.icon.svg:If[t.icon]);return(s,o)=>(d(),m("a",{class:"VPSocialLink",href:s.link,target:"_blank",rel:"noopener",innerHTML:n.value},null,8,Nf))}});const Rf=O(Of,[["__scopeId","data-v-3beb663e"]]),Bf={class:"VPSocialLinks"},Hf=B({__name:"VPSocialLinks",props:{links:{}},setup(e){return(t,n)=>(d(),m("div",Bf,[(d(!0),m(Q,null,Ce(t.links,({link:s,icon:o})=>(d(),X(Rf,{key:s,icon:o,link:s},null,8,["icon","link"]))),128))]))}});const fo=O(Hf,[["__scopeId","data-v-7ec36bc2"]]),Ff=B({__name:"VPNavBarSocialLinks",setup(e){const{theme:t}=ue();return(n,s)=>y(t).socialLinks?(d(),X(fo,{key:0,class:"VPNavBarSocialLinks",links:y(t).socialLinks},null,8,["links"])):K("",!0)}});const Df=O(Ff,[["__scopeId","data-v-c7827124"]]),Uf=e=>(Ge("data-v-913b60b5"),e=e(),qe(),e),jf={key:0,class:"group"},zf={class:"trans-title"},Kf={key:1,class:"group"},Gf={class:"item appearance"},qf=Uf(()=>g("p",{class:"label"},"Appearance",-1)),Wf={class:"appearance-action"},Yf={key:2,class:"group"},Jf={class:"item social-links"},Qf=B({__name:"VPNavBarExtra",setup(e){const{site:t,theme:n}=ue(),s=re(()=>n.value.localeLinks||t.value.appearance||n.value.socialLinks);return(o,i)=>s.value?(d(),X(ao,{key:0,class:"VPNavBarExtra",label:"extra navigation"},{default:I(()=>[y(n).localeLinks?(d(),m("div",jf,[g("p",zf,fe(y(n).localeLinks.text),1),(d(!0),m(Q,null,Ce(y(n).localeLinks.items,r=>(d(),X(ls,{key:r.link,item:r},null,8,["item"]))),128))])):K("",!0),y(t).appearance?(d(),m("div",Kf,[g("div",Gf,[qf,g("div",Wf,[T(uo)])])])):K("",!0),y(n).socialLinks?(d(),m("div",Yf,[g("div",Jf,[T(fo,{class:"social-links-list",links:y(n).socialLinks},null,8,["links"])])])):K("",!0)]),_:1})):K("",!0)}});const Xf=O(Qf,[["__scopeId","data-v-913b60b5"]]),Zf=e=>(Ge("data-v-b2330608"),e=e(),qe(),e),ed=["aria-expanded"],td=Zf(()=>g("span",{class:"container"},[g("span",{class:"top"}),g("span",{class:"middle"}),g("span",{class:"bottom"})],-1)),nd=[td],sd=B({__name:"VPNavBarHamburger",props:{active:{type:Boolean}},emits:["click"],setup(e){return(t,n)=>(d(),m("button",{type:"button",class:pe(["VPNavBarHamburger",{active:t.active}]),"aria-label":"mobile navigation","aria-expanded":t.active,"aria-controls":"VPNavScreen",onClick:n[0]||(n[0]=s=>t.$emit("click"))},nd,10,ed))}});const od=O(sd,[["__scopeId","data-v-b2330608"]]),id={class:"container"},rd={class:"content"},ld=B({__name:"VPNavBar",props:{isScreenOpen:{type:Boolean}},emits:["toggle-screen"],setup(e){const{hasSidebar:t}=et();return(n,s)=>(d(),m("div",{class:pe(["VPNavBar",{"has-sidebar":y(t)}])},[g("div",id,[T(tu,null,{"nav-bar-title-before":I(()=>[L(n.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":I(()=>[L(n.$slots,"nav-bar-title-after",{},void 0,!0)]),_:3}),g("div",rd,[L(n.$slots,"nav-bar-content-before",{},void 0,!0),T(cu,{class:"search"}),T(ef,{class:"menu"}),T(ff,{class:"translations"}),T(Af,{class:"appearance"}),T(Df,{class:"social-links"}),T(Xf,{class:"extra"}),L(n.$slots,"nav-bar-content-after",{},void 0,!0),T(od,{class:"hamburger",active:n.isScreenOpen,onClick:s[0]||(s[0]=o=>n.$emit("toggle-screen"))},null,8,["active"])])])],2))}});const cd=O(ld,[["__scopeId","data-v-01cc589f"]]);function ad(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t1),Ut=[],Un=!1,_o=-1,on=void 0,Pt=void 0,rn=void 0,Ir=function(t){return Ut.some(function(n){return!!(n.options.allowTouchMove&&n.options.allowTouchMove(t))})},jn=function(t){var n=t||window.event;return Ir(n.target)||n.touches.length>1?!0:(n.preventDefault&&n.preventDefault(),!1)},ud=function(t){if(rn===void 0){var n=!!t&&t.reserveScrollBarGap===!0,s=window.innerWidth-document.documentElement.clientWidth;if(n&&s>0){var o=parseInt(window.getComputedStyle(document.body).getPropertyValue("padding-right"),10);rn=document.body.style.paddingRight,document.body.style.paddingRight=o+s+"px"}}on===void 0&&(on=document.body.style.overflow,document.body.style.overflow="hidden")},fd=function(){rn!==void 0&&(document.body.style.paddingRight=rn,rn=void 0),on!==void 0&&(document.body.style.overflow=on,on=void 0)},dd=function(){return window.requestAnimationFrame(function(){if(Pt===void 0){Pt={position:document.body.style.position,top:document.body.style.top,left:document.body.style.left};var t=window,n=t.scrollY,s=t.scrollX,o=t.innerHeight;document.body.style.position="fixed",document.body.style.top=-n,document.body.style.left=-s,setTimeout(function(){return window.requestAnimationFrame(function(){var i=o-window.innerHeight;i&&n>=o&&(document.body.style.top=-(n+i))})},300)}})},pd=function(){if(Pt!==void 0){var t=-parseInt(document.body.style.top,10),n=-parseInt(document.body.style.left,10);document.body.style.position=Pt.position,document.body.style.top=Pt.top,document.body.style.left=Pt.left,window.scrollTo(n,t),Pt=void 0}},_d=function(t){return t?t.scrollHeight-t.scrollTop<=t.clientHeight:!1},hd=function(t,n){var s=t.targetTouches[0].clientY-_o;return Ir(t.target)?!1:n&&n.scrollTop===0&&s>0||_d(n)&&s<0?jn(t):(t.stopPropagation(),!0)},Nr=function(t,n){if(!t){console.error("disableBodyScroll unsuccessful - targetElement must be provided when calling disableBodyScroll on IOS devices.");return}if(!Ut.some(function(o){return o.targetElement===t})){var s={targetElement:t,options:n||{}};Ut=[].concat(ad(Ut),[s]),Dn?dd():ud(n),Dn&&(t.ontouchstart=function(o){o.targetTouches.length===1&&(_o=o.targetTouches[0].clientY)},t.ontouchmove=function(o){o.targetTouches.length===1&&hd(o,t)},Un||(document.addEventListener("touchmove",jn,po?{passive:!1}:void 0),Un=!0))}},Or=function(){Dn&&(Ut.forEach(function(t){t.targetElement.ontouchstart=null,t.targetElement.ontouchmove=null}),Un&&(document.removeEventListener("touchmove",jn,po?{passive:!1}:void 0),Un=!1),_o=-1),Dn?pd():fd(),Ut=[]};const vd=B({__name:"VPNavScreenMenuLink",props:{text:{},link:{}},setup(e){const t=Ke("close-screen");return(n,s)=>(d(),X(Et,{class:"VPNavScreenMenuLink",href:n.link,onClick:y(t)},{default:I(()=>[Le(fe(n.text),1)]),_:1},8,["href","onClick"]))}});const md=O(vd,[["__scopeId","data-v-a0728f16"]]),gd={},yd={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},bd=g("path",{d:"M18.9,10.9h-6v-6c0-0.6-0.4-1-1-1s-1,0.4-1,1v6h-6c-0.6,0-1,0.4-1,1s0.4,1,1,1h6v6c0,0.6,0.4,1,1,1s1-0.4,1-1v-6h6c0.6,0,1-0.4,1-1S19.5,10.9,18.9,10.9z"},null,-1),kd=[bd];function wd(e,t){return d(),m("svg",yd,kd)}const xd=O(gd,[["render",wd]]),$d=B({__name:"VPNavScreenMenuGroupLink",props:{text:{},link:{}},setup(e){const t=Ke("close-screen");return(n,s)=>(d(),X(Et,{class:"VPNavScreenMenuGroupLink",href:n.link,onClick:y(t)},{default:I(()=>[Le(fe(n.text),1)]),_:1},8,["href","onClick"]))}});const Rr=O($d,[["__scopeId","data-v-a90a76c1"]]),Pd={class:"VPNavScreenMenuGroupSection"},Sd={key:0,class:"title"},Cd=B({__name:"VPNavScreenMenuGroupSection",props:{text:{},items:{}},setup(e){return(t,n)=>(d(),m("div",Pd,[t.text?(d(),m("p",Sd,fe(t.text),1)):K("",!0),(d(!0),m(Q,null,Ce(t.items,s=>(d(),X(Rr,{key:s.text,text:s.text,link:s.link},null,8,["text","link"]))),128))]))}});const Vd=O(Cd,[["__scopeId","data-v-4525d52f"]]),Td=["aria-controls","aria-expanded"],Ld={class:"button-text"},Ed=["id"],Md={key:1,class:"group"},Ad=B({__name:"VPNavScreenMenuGroup",props:{text:{},items:{}},setup(e){const t=e,n=_e(!1),s=re(()=>`NavScreenGroup-${t.text.replace(" ","-").toLowerCase()}`);function o(){n.value=!n.value}return(i,r)=>(d(),m("div",{class:pe(["VPNavScreenMenuGroup",{open:n.value}])},[g("button",{class:"button","aria-controls":s.value,"aria-expanded":n.value,onClick:o},[g("span",Ld,fe(i.text),1),T(xd,{class:"button-icon"})],8,Td),g("div",{id:s.value,class:"items"},[(d(!0),m(Q,null,Ce(i.items,l=>(d(),m(Q,{key:l.text},["link"in l?(d(),m("div",{key:l.text,class:"item"},[T(Rr,{text:l.text,link:l.link},null,8,["text","link"])])):(d(),m("div",Md,[T(Vd,{text:l.text,items:l.items},null,8,["text","items"])]))],64))),128))],8,Ed)],2))}});const Id=O(Ad,[["__scopeId","data-v-706622bc"]]),Nd={key:0,class:"VPNavScreenMenu"},Od=B({__name:"VPNavScreenMenu",setup(e){const{theme:t}=ue();return(n,s)=>y(t).nav?(d(),m("nav",Nd,[(d(!0),m(Q,null,Ce(y(t).nav,o=>(d(),m(Q,{key:o.text},["link"in o?(d(),X(md,{key:0,text:o.text,link:o.link},null,8,["text","link"])):(d(),X(Id,{key:1,text:o.text||"",items:o.items},null,8,["text","items"]))],64))),128))])):K("",!0)}}),Rd=e=>(Ge("data-v-6f750383"),e=e(),qe(),e),Bd={key:0,class:"VPNavScreenAppearance"},Hd=Rd(()=>g("p",{class:"text"},"Appearance",-1)),Fd=B({__name:"VPNavScreenAppearance",setup(e){const{site:t}=ue();return(n,s)=>y(t).appearance?(d(),m("div",Bd,[Hd,T(uo)])):K("",!0)}});const Dd=O(Fd,[["__scopeId","data-v-6f750383"]]),Ud={class:"list"},jd=["href"],zd=B({__name:"VPNavScreenTranslations",setup(e){const{theme:t}=ue(),n=_e(!1);function s(){n.value=!n.value}return(o,i)=>y(t).localeLinks?(d(),m("div",{key:0,class:pe(["VPNavScreenTranslations",{open:n.value}])},[g("button",{class:"title",onClick:s},[T(Ar,{class:"icon lang"}),Le(" "+fe(y(t).localeLinks.text)+" ",1),T(Mr,{class:"icon chevron"})]),g("ul",Ud,[(d(!0),m(Q,null,Ce(y(t).localeLinks.items,r=>(d(),m("li",{key:r.link,class:"item"},[g("a",{class:"link",href:r.link},fe(r.text),9,jd)]))),128))])],2)):K("",!0)}});const Kd=O(zd,[["__scopeId","data-v-45be1f42"]]),Gd=B({__name:"VPNavScreenSocialLinks",setup(e){const{theme:t}=ue();return(n,s)=>y(t).socialLinks?(d(),X(fo,{key:0,class:"VPNavScreenSocialLinks",links:y(t).socialLinks},null,8,["links"])):K("",!0)}}),qd={class:"container"},Wd=B({__name:"VPNavScreen",props:{open:{type:Boolean}},setup(e){const t=_e(null);function n(){Nr(t.value,{reserveScrollBarGap:!0})}function s(){Or()}return(o,i)=>(d(),X(is,{name:"fade",onEnter:n,onAfterLeave:s},{default:I(()=>[o.open?(d(),m("div",{key:0,class:"VPNavScreen",ref_key:"screen",ref:t},[g("div",qd,[L(o.$slots,"nav-screen-content-before",{},void 0,!0),T(Od,{class:"menu"}),T(Kd,{class:"translations"}),T(Dd,{class:"appearance"}),T(Gd,{class:"social-links"}),L(o.$slots,"nav-screen-content-after",{},void 0,!0)])],512)):K("",!0)]),_:3}))}});const Yd=O(Wd,[["__scopeId","data-v-ad2a381f"]]),Jd=B({__name:"VPNav",setup(e){const{isScreenOpen:t,closeScreen:n,toggleScreen:s}=Ya(),{hasSidebar:o}=et();return ns("close-screen",n),(i,r)=>(d(),m("header",{class:pe(["VPNav",{"no-sidebar":!y(o)}])},[T(cd,{"is-screen-open":y(t),onToggleScreen:y(s)},{"nav-bar-title-before":I(()=>[L(i.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":I(()=>[L(i.$slots,"nav-bar-title-after",{},void 0,!0)]),"nav-bar-content-before":I(()=>[L(i.$slots,"nav-bar-content-before",{},void 0,!0)]),"nav-bar-content-after":I(()=>[L(i.$slots,"nav-bar-content-after",{},void 0,!0)]),_:3},8,["is-screen-open","onToggleScreen"]),T(Yd,{open:y(t)},{"nav-screen-content-before":I(()=>[L(i.$slots,"nav-screen-content-before",{},void 0,!0)]),"nav-screen-content-after":I(()=>[L(i.$slots,"nav-screen-content-after",{},void 0,!0)]),_:3},8,["open"])],2))}});const Qd=O(Jd,[["__scopeId","data-v-8b5778ac"]]),Xd={},Zd={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},ep=g("path",{d:"M17,11H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h14c0.6,0,1,0.4,1,1S17.6,11,17,11z"},null,-1),tp=g("path",{d:"M21,7H3C2.4,7,2,6.6,2,6s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,7,21,7z"},null,-1),np=g("path",{d:"M21,15H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,15,21,15z"},null,-1),sp=g("path",{d:"M17,19H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h14c0.6,0,1,0.4,1,1S17.6,19,17,19z"},null,-1),op=[ep,tp,np,sp];function ip(e,t){return d(),m("svg",Zd,op)}const rp=O(Xd,[["render",ip]]),lp=e=>(Ge("data-v-bcaee864"),e=e(),qe(),e),cp={key:0,class:"VPLocalNav"},ap=["aria-expanded"],up=lp(()=>g("span",{class:"menu-text"},"Menu",-1)),fp=B({__name:"VPLocalNav",props:{open:{type:Boolean}},emits:["open-menu"],setup(e){const{hasSidebar:t}=et();function n(){window.scrollTo({top:0,left:0,behavior:"smooth"})}return(s,o)=>y(t)?(d(),m("div",cp,[g("button",{class:"menu","aria-expanded":s.open,"aria-controls":"VPSidebarNav",onClick:o[0]||(o[0]=i=>s.$emit("open-menu"))},[T(rp,{class:"menu-icon"}),up],8,ap),g("a",{class:"top-link",href:"#",onClick:n}," Return to top ")])):K("",!0)}});const dp=O(fp,[["__scopeId","data-v-bcaee864"]]),pp={},_p={version:"1.1",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},hp=g("path",{d:"M19,2H5C3.3,2,2,3.3,2,5v14c0,1.7,1.3,3,3,3h14c1.7,0,3-1.3,3-3V5C22,3.3,20.7,2,19,2z M20,19c0,0.6-0.4,1-1,1H5c-0.6,0-1-0.4-1-1V5c0-0.6,0.4-1,1-1h14c0.6,0,1,0.4,1,1V19z"},null,-1),vp=g("path",{d:"M16,11h-3V8c0-0.6-0.4-1-1-1s-1,0.4-1,1v3H8c-0.6,0-1,0.4-1,1s0.4,1,1,1h3v3c0,0.6,0.4,1,1,1s1-0.4,1-1v-3h3c0.6,0,1-0.4,1-1S16.6,11,16,11z"},null,-1),mp=[hp,vp];function gp(e,t){return d(),m("svg",_p,mp)}const yp=O(pp,[["render",gp]]),bp={},kp={xmlns:"http://www.w3.org/2000/svg","xmlns:xlink":"http://www.w3.org/1999/xlink",viewBox:"0 0 24 24"},wp=g("path",{d:"M19,2H5C3.3,2,2,3.3,2,5v14c0,1.7,1.3,3,3,3h14c1.7,0,3-1.3,3-3V5C22,3.3,20.7,2,19,2zM20,19c0,0.6-0.4,1-1,1H5c-0.6,0-1-0.4-1-1V5c0-0.6,0.4-1,1-1h14c0.6,0,1,0.4,1,1V19z"},null,-1),xp=g("path",{d:"M16,11H8c-0.6,0-1,0.4-1,1s0.4,1,1,1h8c0.6,0,1-0.4,1-1S16.6,11,16,11z"},null,-1),$p=[wp,xp];function Pp(e,t){return d(),m("svg",kp,$p)}const Sp=O(bp,[["render",Pp]]),Cp=["innerHTML"],Vp=B({__name:"VPSidebarLink",props:{item:{},depth:{default:1}},setup(e){const{page:t,frontmatter:n}=ue(),s=re(()=>n.value.sidebarDepth||1/0),o=Ke("close-sidebar");return(i,r)=>{const l=Lt("VPSidebarLink",!0);return d(),m(Q,null,[T(Et,{class:pe(["link",{active:y(Jt)(y(t).relativePath,i.item.link)}]),style:qn({paddingLeft:16*(i.depth-1)+"px"}),href:i.item.link,onClick:y(o)},{default:I(()=>[g("span",{innerHTML:i.item.text,class:pe(["link-text",{light:i.depth>1}])},null,10,Cp)]),_:1},8,["class","style","href","onClick"]),"items"in i.item&&i.depth(d(),X(l,{key:c.link,item:c,depth:i.depth+1},null,8,["item","depth"]))),128)):K("",!0)],64)}}});const Tp=O(Vp,[["__scopeId","data-v-836d4f27"]]),Lp=["role"],Ep=["innerHTML"],Mp={class:"action"},Ap={class:"items"},Ip=B({__name:"VPSidebarGroup",props:{text:{},items:{},collapsible:{type:Boolean},collapsed:{type:Boolean}},setup(e){const t=e,n=_e(!1);Kt(()=>{n.value=!!(t.collapsible&&t.collapsed)});const{page:s}=ue();Kt(()=>{t.items.some(i=>Jt(s.value.relativePath,i.link))&&(n.value=!1)});function o(){t.collapsible&&(n.value=!n.value)}return(i,r)=>(d(),m("section",{class:pe(["VPSidebarGroup",{collapsible:i.collapsible,collapsed:n.value}])},[i.text?(d(),m("div",{key:0,class:"title",role:i.collapsible?"button":void 0,onClick:o},[g("h2",{innerHTML:i.text,class:"title-text"},null,8,Ep),g("div",Mp,[T(Sp,{class:"icon minus"}),T(yp,{class:"icon plus"})])],8,Lp)):K("",!0),g("div",Ap,[(d(!0),m(Q,null,Ce(i.items,l=>(d(),X(Tp,{key:l.link,item:l},null,8,["item"]))),128))])],2))}});const Np=O(Ip,[["__scopeId","data-v-130e8933"]]),Op=e=>(Ge("data-v-1367b3e4"),e=e(),qe(),e),Rp={class:"nav",id:"VPSidebarNav","aria-labelledby":"sidebar-aria-label",tabindex:"-1"},Bp=Op(()=>g("span",{class:"visually-hidden",id:"sidebar-aria-label"}," Sidebar Navigation ",-1)),Hp=B({__name:"VPSidebar",props:{open:{type:Boolean}},setup(e){const{sidebar:t,hasSidebar:n}=et(),s=e;let o=_e(null);function i(){Nr(o.value,{reserveScrollBarGap:!0})}function r(){Or()}return Xi(async()=>{var l;s.open?(i(),(l=o.value)==null||l.focus()):r()}),(l,c)=>y(n)?(d(),m("aside",{key:0,class:pe(["VPSidebar",{open:l.open}]),ref_key:"navEl",ref:o,onClick:c[0]||(c[0]=aa(()=>{},["stop"]))},[g("nav",Rp,[Bp,L(l.$slots,"sidebar-nav-before",{},void 0,!0),(d(!0),m(Q,null,Ce(y(t),f=>(d(),m("div",{key:f.text,class:"group"},[T(Np,{text:f.text,items:f.items,collapsible:f.collapsible,collapsed:f.collapsed},null,8,["text","items","collapsible","collapsed"])]))),128)),L(l.$slots,"sidebar-nav-after",{},void 0,!0)])],2)):K("",!0)}});const Fp=O(Hp,[["__scopeId","data-v-1367b3e4"]]),Dp={},Up={class:"VPPage"};function jp(e,t){const n=Lt("Content");return d(),m("div",Up,[T(n)])}const zp=O(Dp,[["render",jp]]),Kp=B({__name:"VPButton",props:{tag:{},size:{},theme:{},text:{},href:{}},setup(e){const t=e,n=re(()=>[t.size??"medium",t.theme??"brand"]),s=re(()=>t.href&&rs.test(t.href)),o=re(()=>t.tag?t.tag:t.href?"a":"button");return(i,r)=>(d(),X(to(o.value),{class:pe(["VPButton",n.value]),href:i.href?y(Fn)(i.href):void 0,target:s.value?"_blank":void 0,rel:s.value?"noreferrer":void 0},{default:I(()=>[Le(fe(i.text),1)]),_:1},8,["class","href","target","rel"]))}});const Gp=O(Kp,[["__scopeId","data-v-0311ec08"]]),qp=e=>(Ge("data-v-96d2911b"),e=e(),qe(),e),Wp={class:"container"},Yp={class:"main"},Jp={key:0,class:"name"},Qp={class:"clip"},Xp={key:1,class:"text"},Zp={key:2,class:"tagline"},e_={key:3,class:"actions"},t_={key:0,class:"image"},n_={class:"image-container"},s_=qp(()=>g("div",{class:"image-bg"},null,-1)),o_=B({__name:"VPHero",props:{name:{},text:{},tagline:{},image:{},actions:{}},setup(e){return(t,n)=>(d(),m("div",{class:pe(["VPHero",{"has-image":t.image}])},[g("div",Wp,[g("div",Yp,[t.name?(d(),m("h1",Jp,[g("span",Qp,fe(t.name),1)])):K("",!0),t.text?(d(),m("p",Xp,fe(t.text),1)):K("",!0),t.tagline?(d(),m("p",Zp,fe(t.tagline),1)):K("",!0),t.actions?(d(),m("div",e_,[(d(!0),m(Q,null,Ce(t.actions,s=>(d(),m("div",{key:s.link,class:"action"},[T(Gp,{tag:"a",size:"medium",theme:s.theme,text:s.text,href:s.link},null,8,["theme","text","href"])]))),128))])):K("",!0)]),t.image?(d(),m("div",t_,[g("div",n_,[s_,T(Tr,{class:"image-src",image:t.image},null,8,["image"])])])):K("",!0)])],2))}});const i_=O(o_,[["__scopeId","data-v-96d2911b"]]),r_=B({__name:"VPHomeHero",setup(e){const{frontmatter:t}=ue();return(n,s)=>y(t).hero?(d(),X(i_,{key:0,class:"VPHomeHero",name:y(t).hero.name,text:y(t).hero.text,tagline:y(t).hero.tagline,image:y(t).hero.image,actions:y(t).hero.actions},null,8,["name","text","tagline","image","actions"])):K("",!0)}}),l_={},c_={xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},a_=g("path",{d:"M19.9,12.4c0.1-0.2,0.1-0.5,0-0.8c-0.1-0.1-0.1-0.2-0.2-0.3l-7-7c-0.4-0.4-1-0.4-1.4,0s-0.4,1,0,1.4l5.3,5.3H5c-0.6,0-1,0.4-1,1s0.4,1,1,1h11.6l-5.3,5.3c-0.4,0.4-0.4,1,0,1.4c0.2,0.2,0.5,0.3,0.7,0.3s0.5-0.1,0.7-0.3l7-7C19.8,12.6,19.9,12.5,19.9,12.4z"},null,-1),u_=[a_];function f_(e,t){return d(),m("svg",c_,u_)}const d_=O(l_,[["render",f_]]),p_={class:"box"},__={key:0,class:"icon"},h_={class:"title"},v_={class:"details"},m_={key:1,class:"link-text"},g_={class:"link-text-value"},y_=B({__name:"VPFeature",props:{icon:{},title:{},details:{},link:{},linkText:{}},setup(e){return(t,n)=>(d(),X(Et,{class:"VPFeature",href:t.link,"no-icon":!0},{default:I(()=>[g("article",p_,[t.icon?(d(),m("div",__,fe(t.icon),1)):K("",!0),g("h2",h_,fe(t.title),1),g("p",v_,fe(t.details),1),t.linkText?(d(),m("div",m_,[g("p",g_,[Le(fe(t.linkText)+" ",1),T(d_,{class:"link-text-icon"})])])):K("",!0)])]),_:1},8,["href"]))}});const b_=O(y_,[["__scopeId","data-v-cfb3cf73"]]),k_={key:0,class:"VPFeatures"},w_={class:"container"},x_={class:"items"},$_=B({__name:"VPFeatures",props:{features:{}},setup(e){const t=e,n=re(()=>{const s=t.features.length;if(s){if(s===2)return"grid-2";if(s===3)return"grid-3";if(s%3===0)return"grid-6";if(s%2===0)return"grid-4"}else return});return(s,o)=>s.features?(d(),m("div",k_,[g("div",w_,[g("div",x_,[(d(!0),m(Q,null,Ce(s.features,i=>(d(),m("div",{key:i.title,class:pe(["item",[n.value]])},[T(b_,{icon:i.icon,title:i.title,details:i.details,link:i.link,"link-text":i.linkText},null,8,["icon","title","details","link","link-text"])],2))),128))])])])):K("",!0)}});const P_=O($_,[["__scopeId","data-v-03ba64f3"]]),S_=B({__name:"VPHomeFeatures",setup(e){const{frontmatter:t}=ue();return(n,s)=>y(t).features?(d(),X(P_,{key:0,class:"VPHomeFeatures",features:y(t).features},null,8,["features"])):K("",!0)}}),C_={class:"VPHome"},V_=B({__name:"VPHome",setup(e){return(t,n)=>{const s=Lt("Content");return d(),m("div",C_,[L(t.$slots,"home-hero-before",{},void 0,!0),T(r_),L(t.$slots,"home-hero-after",{},void 0,!0),L(t.$slots,"home-features-before",{},void 0,!0),T(S_),L(t.$slots,"home-features-after",{},void 0,!0),T(s)])}}});const T_=O(V_,[["__scopeId","data-v-5a33d42b"]]);var pi;const Br=typeof window<"u";Br&&((pi=window==null?void 0:window.navigator)!=null&&pi.userAgent)&&/iP(ad|hone|od)/.test(window.navigator.userAgent);function L_(e){return e}function E_(e){return Vi()?(ol(e),!0):!1}function M_(e){return typeof e=="function"?re(e):_e(e)}function A_(e,t=!0){ro()?De(e):t?e():Xs(e)}const I_=Br?window:void 0;function N_(e,t=!1){const n=_e(),s=()=>n.value=!!e();return s(),A_(s,t),n}function _i(e,t={}){const{window:n=I_}=t,s=N_(()=>n&&"matchMedia"in n&&typeof n.matchMedia=="function");let o;const i=_e(!1),r=()=>{o&&("removeEventListener"in o?o.removeEventListener("change",l):o.removeListener(l))},l=()=>{s.value&&(r(),o=n.matchMedia(M_(e).value),i.value=o.matches,"addEventListener"in o?o.addEventListener("change",l):o.addListener(l))};return Kt(l),E_(()=>r()),i}const hi=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{},vi="__vueuse_ssr_handlers__";hi[vi]=hi[vi]||{};var mi;(function(e){e.UP="UP",e.RIGHT="RIGHT",e.DOWN="DOWN",e.LEFT="LEFT",e.NONE="NONE"})(mi||(mi={}));var O_=Object.defineProperty,gi=Object.getOwnPropertySymbols,R_=Object.prototype.hasOwnProperty,B_=Object.prototype.propertyIsEnumerable,yi=(e,t,n)=>t in e?O_(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,H_=(e,t)=>{for(var n in t||(t={}))R_.call(t,n)&&yi(e,n,t[n]);if(gi)for(var n of gi(t))B_.call(t,n)&&yi(e,n,t[n]);return e};const F_={easeInSine:[.12,0,.39,0],easeOutSine:[.61,1,.88,1],easeInOutSine:[.37,0,.63,1],easeInQuad:[.11,0,.5,0],easeOutQuad:[.5,1,.89,1],easeInOutQuad:[.45,0,.55,1],easeInCubic:[.32,0,.67,0],easeOutCubic:[.33,1,.68,1],easeInOutCubic:[.65,0,.35,1],easeInQuart:[.5,0,.75,0],easeOutQuart:[.25,1,.5,1],easeInOutQuart:[.76,0,.24,1],easeInQuint:[.64,0,.78,0],easeOutQuint:[.22,1,.36,1],easeInOutQuint:[.83,0,.17,1],easeInExpo:[.7,0,.84,0],easeOutExpo:[.16,1,.3,1],easeInOutExpo:[.87,0,.13,1],easeInCirc:[.55,0,1,.45],easeOutCirc:[0,.55,.45,1],easeInOutCirc:[.85,0,.15,1],easeInBack:[.36,0,.66,-.56],easeOutBack:[.34,1.56,.64,1],easeInOutBack:[.68,-.6,.32,1.6]};H_({linear:L_},F_);function D_(){const{hasSidebar:e}=et(),t=_i("(min-width: 960px)"),n=_i("(min-width: 1280px)");return{isAsideEnabled:re(()=>!n.value&&!t.value?!1:e.value?n.value:t.value)}}const U_=71;function j_(e){if(e===!1)return[];let t=[];return document.querySelectorAll("h2, h3, h4, h5, h6").forEach(n=>{n.textContent&&n.id&&t.push({level:Number(n.tagName[1]),title:n.innerText.replace(/\s+#\s*$/,""),link:`#${n.id}`})}),z_(t,e)}function z_(e,t=2){return K_(e,typeof t=="number"?[t,t]:t==="deep"?[2,6]:t)}function K_(e,t){const n=[];return e=e.map(s=>({...s})),e.forEach((s,o)=>{s.level>=t[0]&&s.level<=t[1]&&G_(o,e,t)&&n.push(s)}),n}function G_(e,t,n){if(e===0)return!0;const s=t[e];for(let o=e-1;o>=0;o--){const i=t[o];if(i.level=n[0]&&i.level<=n[1])return i.children==null&&(i.children=[]),i.children.push(s),!1}return!0}function q_(e,t){const{isAsideEnabled:n}=D_(),s=Da(i,100);let o=null;De(()=>{requestAnimationFrame(i),window.addEventListener("scroll",s)}),no(()=>{r(location.hash)}),mt(()=>{window.removeEventListener("scroll",s)});function i(){if(!n.value)return;const l=[].slice.call(e.value.querySelectorAll(".outline-link")),c=[].slice.call(document.querySelectorAll(".content .header-anchor")).filter(S=>l.some(J=>J.hash===S.hash&&S.offsetParent!==null)),f=window.scrollY,p=window.innerHeight,_=document.body.offsetHeight,b=Math.abs(f+p-_)<1;if(c.length&&b){r(c[c.length-1].hash);return}for(let S=0;S{const s=Lt("VPDocAsideOutlineItem",!0);return d(),m("ul",{class:pe(t.root?"root":"nested")},[(d(!0),m(Q,null,Ce(t.headers,({children:o,link:i,title:r})=>(d(),m("li",null,[g("a",{class:"outline-link",href:i,onClick:n[0]||(n[0]=(...l)=>t.onClick&&t.onClick(...l))},fe(r),9,Y_),o!=null&&o.length?(d(),X(s,{key:0,headers:o,onClick:t.onClick},null,8,["headers","onClick"])):K("",!0)]))),256))],2)}}});const Q_=O(J_,[["__scopeId","data-v-a0ec0882"]]),X_=e=>(Ge("data-v-46ce5e0f"),e=e(),qe(),e),Z_={class:"content"},eh={class:"outline-title"},th={"aria-labelledby":"doc-outline-aria-label"},nh=X_(()=>g("span",{class:"visually-hidden",id:"doc-outline-aria-label"}," Table of Contents for current page ",-1)),sh=B({__name:"VPDocAsideOutline",setup(e){const{frontmatter:t,theme:n}=ue(),s=re(()=>t.value.outline??n.value.outline),o=Ke("onContentUpdated");o.value=()=>{i.value=j_(s.value)};const i=_e([]),r=re(()=>i.value.length>0),l=_e(),c=_e();q_(l,c);function f({target:p}){const _="#"+p.href.split("#")[1],b=document.querySelector(decodeURIComponent(_));b==null||b.focus()}return(p,_)=>(d(),m("div",{class:pe(["VPDocAsideOutline",{"has-outline":r.value}]),ref_key:"container",ref:l},[g("div",Z_,[g("div",{class:"outline-marker",ref_key:"marker",ref:c},null,512),g("div",eh,fe(y(n).outlineTitle||"On this page"),1),g("nav",th,[nh,T(Q_,{headers:i.value,root:!0,onClick:f},null,8,["headers"])])])],2))}});const oh=O(sh,[["__scopeId","data-v-46ce5e0f"]]),ih={class:"VPDocAsideCarbonAds"},rh=B({__name:"VPDocAsideCarbonAds",setup(e){const t=()=>null;return(n,s)=>(d(),m("div",ih,[T(y(t))]))}}),lh=e=>(Ge("data-v-765822e5"),e=e(),qe(),e),ch={class:"VPDocAside"},ah=lh(()=>g("div",{class:"spacer"},null,-1)),uh=B({__name:"VPDocAside",setup(e){const{theme:t}=ue();return(n,s)=>(d(),m("div",ch,[L(n.$slots,"aside-top",{},void 0,!0),L(n.$slots,"aside-outline-before",{},void 0,!0),T(oh),L(n.$slots,"aside-outline-after",{},void 0,!0),ah,L(n.$slots,"aside-ads-before",{},void 0,!0),y(t).carbonAds?(d(),X(rh,{key:0})):K("",!0),L(n.$slots,"aside-ads-after",{},void 0,!0),L(n.$slots,"aside-bottom",{},void 0,!0)]))}});const fh=O(uh,[["__scopeId","data-v-765822e5"]]);function dh(){const{theme:e,page:t}=ue();return re(()=>{const{text:n="Edit this page",pattern:s}=e.value.editLink||{},{relativePath:o}=t.value;return{url:s.replace(/:path/g,o),text:n}})}function ph(){const{page:e,theme:t,frontmatter:n}=ue();return re(()=>{const s=Vr(t.value.sidebar,e.value.relativePath),o=Ua(s),i=o.findIndex(r=>Jt(e.value.relativePath,r.link));return{prev:n.value.prev?{...o[i-1],text:n.value.prev}:o[i-1],next:n.value.next?{...o[i+1],text:n.value.next}:o[i+1]}})}const _h={},hh={xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},vh=g("path",{d:"M18,23H4c-1.7,0-3-1.3-3-3V6c0-1.7,1.3-3,3-3h7c0.6,0,1,0.4,1,1s-0.4,1-1,1H4C3.4,5,3,5.4,3,6v14c0,0.6,0.4,1,1,1h14c0.6,0,1-0.4,1-1v-7c0-0.6,0.4-1,1-1s1,0.4,1,1v7C21,21.7,19.7,23,18,23z"},null,-1),mh=g("path",{d:"M8,17c-0.3,0-0.5-0.1-0.7-0.3C7,16.5,6.9,16.1,7,15.8l1-4c0-0.2,0.1-0.3,0.3-0.5l9.5-9.5c1.2-1.2,3.2-1.2,4.4,0c1.2,1.2,1.2,3.2,0,4.4l-9.5,9.5c-0.1,0.1-0.3,0.2-0.5,0.3l-4,1C8.2,17,8.1,17,8,17zM9.9,12.5l-0.5,2.1l2.1-0.5l9.3-9.3c0.4-0.4,0.4-1.1,0-1.6c-0.4-0.4-1.2-0.4-1.6,0l0,0L9.9,12.5z M18.5,2.5L18.5,2.5L18.5,2.5z"},null,-1),gh=[vh,mh];function yh(e,t){return d(),m("svg",hh,gh)}const bh=O(_h,[["render",yh]]),kh={class:"VPLastUpdated"},wh=["datetime"],xh=B({__name:"VPDocFooterLastUpdated",setup(e){const{theme:t,page:n}=ue(),s=re(()=>new Date(n.value.lastUpdated)),o=re(()=>s.value.toISOString()),i=_e("");return De(()=>{Kt(()=>{i.value=s.value.toLocaleString(window.navigator.language)})}),(r,l)=>(d(),m("p",kh,[Le(fe(y(t).lastUpdatedText??"Last updated")+": ",1),g("time",{datetime:o.value},fe(i.value),9,wh)]))}});const $h=O(xh,[["__scopeId","data-v-2f5384a2"]]),Ph={key:0,class:"VPDocFooter"},Sh={key:0,class:"edit-info"},Ch={key:0,class:"edit-link"},Vh={key:1,class:"last-updated"},Th={key:1,class:"prev-next"},Lh={class:"pager"},Eh=["href"],Mh=["innerHTML"],Ah=["innerHTML"],Ih=["href"],Nh=["innerHTML"],Oh=["innerHTML"],Rh=B({__name:"VPDocFooter",setup(e){const{theme:t,page:n,frontmatter:s}=ue(),o=dh(),i=ph(),r=re(()=>t.value.editLink&&s.value.editLink!==!1),l=re(()=>n.value.lastUpdated&&s.value.lastUpdated!==!1),c=re(()=>r.value||l.value||i.value.prev||i.value.next);return(f,p)=>{var _,b;return c.value?(d(),m("footer",Ph,[r.value||l.value?(d(),m("div",Sh,[r.value?(d(),m("div",Ch,[T(Et,{class:"edit-link-button",href:y(o).url,"no-icon":!0},{default:I(()=>[T(bh,{class:"edit-link-icon"}),Le(" "+fe(y(o).text),1)]),_:1},8,["href"])])):K("",!0),l.value?(d(),m("div",Vh,[T($h)])):K("",!0)])):K("",!0),y(i).prev||y(i).next?(d(),m("div",Th,[g("div",Lh,[y(i).prev?(d(),m("a",{key:0,class:"pager-link prev",href:y(Fn)(y(i).prev.link)},[g("span",{class:"desc",innerHTML:((_=y(t).docFooter)==null?void 0:_.prev)??"Previous page"},null,8,Mh),g("span",{class:"title",innerHTML:y(i).prev.text},null,8,Ah)],8,Eh)):K("",!0)]),g("div",{class:pe(["pager",{"has-prev":y(i).prev}])},[y(i).next?(d(),m("a",{key:0,class:"pager-link next",href:y(Fn)(y(i).next.link)},[g("span",{class:"desc",innerHTML:((b=y(t).docFooter)==null?void 0:b.next)??"Next page"},null,8,Nh),g("span",{class:"title",innerHTML:y(i).next.text},null,8,Oh)],8,Ih)):K("",!0)],2)])):K("",!0)])):K("",!0)}}});const Bh=O(Rh,[["__scopeId","data-v-e915c016"]]),Hh=e=>(Ge("data-v-808f78d8"),e=e(),qe(),e),Fh={class:"container"},Dh={key:0,class:"aside"},Uh=Hh(()=>g("div",{class:"aside-curtain"},null,-1)),jh={class:"aside-container"},zh={class:"aside-content"},Kh={class:"content"},Gh={class:"content-container"},qh={class:"main"},Wh=B({__name:"VPDoc",setup(e){const t=gt(),{hasSidebar:n,hasAside:s}=et(),o=re(()=>t.path.replace(/[./]+/g,"_").replace(/_html$/,"")),i=_e();return ns("onContentUpdated",i),(r,l)=>{const c=Lt("Content");return d(),m("div",{class:pe(["VPDoc",{"has-sidebar":y(n),"has-aside":y(s)}])},[g("div",Fh,[y(s)?(d(),m("div",Dh,[Uh,g("div",jh,[g("div",zh,[T(fh,null,{"aside-top":I(()=>[L(r.$slots,"aside-top",{},void 0,!0)]),"aside-bottom":I(()=>[L(r.$slots,"aside-bottom",{},void 0,!0)]),"aside-outline-before":I(()=>[L(r.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":I(()=>[L(r.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":I(()=>[L(r.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":I(()=>[L(r.$slots,"aside-ads-after",{},void 0,!0)]),_:3})])])])):K("",!0),g("div",Kh,[g("div",Gh,[L(r.$slots,"doc-before",{},void 0,!0),g("main",qh,[T(c,{class:pe(["vp-doc",o.value]),onContentUpdated:i.value},null,8,["class","onContentUpdated"])]),L(r.$slots,"doc-footer-before",{},void 0,!0),T(Bh),L(r.$slots,"doc-after",{},void 0,!0)])])])],2)}}});const Yh=O(Wh,[["__scopeId","data-v-808f78d8"]]),Jh=B({__name:"VPContent",setup(e){const t=gt(),{frontmatter:n}=ue(),{hasSidebar:s}=et(),o=Ke("NotFound");return(i,r)=>(d(),m("div",{class:pe(["VPContent",{"has-sidebar":y(s),"is-home":y(n).layout==="home"}]),id:"VPContent"},[y(t).component===y(o)?(d(),X(y(o),{key:0})):y(n).layout==="page"?(d(),X(zp,{key:1})):y(n).layout==="home"?(d(),X(T_,{key:2},{"home-hero-before":I(()=>[L(i.$slots,"home-hero-before",{},void 0,!0)]),"home-hero-after":I(()=>[L(i.$slots,"home-hero-after",{},void 0,!0)]),"home-features-before":I(()=>[L(i.$slots,"home-features-before",{},void 0,!0)]),"home-features-after":I(()=>[L(i.$slots,"home-features-after",{},void 0,!0)]),_:3})):(d(),X(Yh,{key:3},{"doc-footer-before":I(()=>[L(i.$slots,"doc-footer-before",{},void 0,!0)]),"doc-before":I(()=>[L(i.$slots,"doc-before",{},void 0,!0)]),"doc-after":I(()=>[L(i.$slots,"doc-after",{},void 0,!0)]),"aside-top":I(()=>[L(i.$slots,"aside-top",{},void 0,!0)]),"aside-outline-before":I(()=>[L(i.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":I(()=>[L(i.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":I(()=>[L(i.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":I(()=>[L(i.$slots,"aside-ads-after",{},void 0,!0)]),"aside-bottom":I(()=>[L(i.$slots,"aside-bottom",{},void 0,!0)]),_:3}))],2))}});const Qh=O(Jh,[["__scopeId","data-v-52ef3f18"]]),Xh={class:"container"},Zh=["innerHTML"],e0=["innerHTML"],t0=B({__name:"VPFooter",setup(e){const{theme:t}=ue(),{hasSidebar:n}=et();return(s,o)=>y(t).footer?(d(),m("footer",{key:0,class:pe(["VPFooter",{"has-sidebar":y(n)}])},[g("div",Xh,[y(t).footer.message?(d(),m("p",{key:0,class:"message",innerHTML:y(t).footer.message},null,8,Zh)):K("",!0),y(t).footer.copyright?(d(),m("p",{key:1,class:"copyright",innerHTML:y(t).footer.copyright},null,8,e0)):K("",!0)])],2)):K("",!0)}});const n0=O(t0,[["__scopeId","data-v-a36b15be"]]),s0={key:0,class:"Layout"},o0=B({__name:"Layout",setup(e){const{isOpen:t,open:n,close:s}=et(),o=gt();Xe(()=>o.path,s),ja(t,s),ns("close-sidebar",s);const{frontmatter:i}=ue();return(r,l)=>{const c=Lt("Content");return y(i).layout!==!1?(d(),m("div",s0,[L(r.$slots,"layout-top",{},void 0,!0),T(Ka),T(Wa,{class:"backdrop",show:y(t),onClick:y(s)},null,8,["show","onClick"]),T(Qd,null,{"nav-bar-title-before":I(()=>[L(r.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":I(()=>[L(r.$slots,"nav-bar-title-after",{},void 0,!0)]),"nav-bar-content-before":I(()=>[L(r.$slots,"nav-bar-content-before",{},void 0,!0)]),"nav-bar-content-after":I(()=>[L(r.$slots,"nav-bar-content-after",{},void 0,!0)]),"nav-screen-content-before":I(()=>[L(r.$slots,"nav-screen-content-before",{},void 0,!0)]),"nav-screen-content-after":I(()=>[L(r.$slots,"nav-screen-content-after",{},void 0,!0)]),_:3}),T(dp,{open:y(t),onOpenMenu:y(n)},null,8,["open","onOpenMenu"]),T(Fp,{open:y(t)},{"sidebar-nav-before":I(()=>[L(r.$slots,"sidebar-nav-before",{},void 0,!0)]),"sidebar-nav-after":I(()=>[L(r.$slots,"sidebar-nav-after",{},void 0,!0)]),_:3},8,["open"]),T(Qh,null,{"home-hero-before":I(()=>[L(r.$slots,"home-hero-before",{},void 0,!0)]),"home-hero-after":I(()=>[L(r.$slots,"home-hero-after",{},void 0,!0)]),"home-features-before":I(()=>[L(r.$slots,"home-features-before",{},void 0,!0)]),"home-features-after":I(()=>[L(r.$slots,"home-features-after",{},void 0,!0)]),"doc-footer-before":I(()=>[L(r.$slots,"doc-footer-before",{},void 0,!0)]),"doc-before":I(()=>[L(r.$slots,"doc-before",{},void 0,!0)]),"doc-after":I(()=>[L(r.$slots,"doc-after",{},void 0,!0)]),"aside-top":I(()=>[L(r.$slots,"aside-top",{},void 0,!0)]),"aside-bottom":I(()=>[L(r.$slots,"aside-bottom",{},void 0,!0)]),"aside-outline-before":I(()=>[L(r.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":I(()=>[L(r.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":I(()=>[L(r.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":I(()=>[L(r.$slots,"aside-ads-after",{},void 0,!0)]),_:3}),T(n0),L(r.$slots,"layout-bottom",{},void 0,!0)])):(d(),X(c,{key:1}))}}});const i0=O(o0,[["__scopeId","data-v-f4ae6121"]]),cs=e=>(Ge("data-v-6177b5ce"),e=e(),qe(),e),r0={class:"NotFound"},l0=cs(()=>g("p",{class:"code"},"404",-1)),c0=cs(()=>g("h1",{class:"title"},"PAGE NOT FOUND",-1)),a0=cs(()=>g("div",{class:"divider"},null,-1)),u0=cs(()=>g("blockquote",{class:"quote"}," But if you don't change your direction, and if you keep looking, you may end up where you are heading. ",-1)),f0={class:"action"},d0=["href"],p0=B({__name:"NotFound",setup(e){const{site:t}=ue();return(n,s)=>(d(),m("div",r0,[l0,c0,a0,u0,g("div",f0,[g("a",{class:"link",href:y(t).base,"aria-label":"go to home"}," Take me home ",8,d0)])]))}});const _0=O(p0,[["__scopeId","data-v-6177b5ce"]]);const h0={Layout:i0,NotFound:_0,enhanceApp:({app:e})=>{e.component("Badge",ya)}};const jt={...h0};function v0(e,t){let n=[],s=!0;const o=i=>{if(s){s=!1;return}n.forEach(r=>document.head.removeChild(r)),n=[],i.forEach(r=>{const l=m0(r);document.head.appendChild(l),n.push(l)})};Kt(()=>{const i=e.data,r=t.value,l=i&&i.description,c=i&&i.frontmatter.head||[];document.title=xr(r,i),document.querySelector("meta[name=description]").setAttribute("content",l||r.description),o(Va(r.head,y0(c)))})}function m0([e,t,n]){const s=document.createElement(e);for(const o in t)s.setAttribute(o,t[o]);return n&&(s.innerHTML=n),s}function g0(e){return e[0]==="meta"&&e[1]&&e[1].name==="description"}function y0(e){return e.filter(t=>!g0(t))}const $s=new Set,Hr=()=>document.createElement("link"),b0=e=>{const t=Hr();t.rel="prefetch",t.href=e,document.head.appendChild(t)},k0=e=>{const t=new XMLHttpRequest;t.open("GET",e,t.withCredentials=!0),t.send()};let Sn;const w0=Te&&(Sn=Hr())&&Sn.relList&&Sn.relList.supports&&Sn.relList.supports("prefetch")?b0:k0;function x0(){if(!Te||!window.IntersectionObserver)return;let e;if((e=navigator.connection)&&(e.saveData||/2g/.test(e.effectiveType)))return;const t=window.requestIdleCallback||setTimeout;let n=null;const s=()=>{n&&n.disconnect(),n=new IntersectionObserver(i=>{i.forEach(r=>{if(r.isIntersecting){const l=r.target;n.unobserve(l);const{pathname:c}=l;if(!$s.has(c)){$s.add(c);const f=$r(c);w0(f)}}})}),t(()=>{document.querySelectorAll("#app a").forEach(i=>{const{target:r,hostname:l,pathname:c}=i,f=c.match(/\.\w+$/);f&&f[0]!==".html"||r!=="_blank"&&l===location.hostname&&(c!==location.pathname?n.observe(i):$s.add(c))})})};De(s);const o=gt();Xe(()=>o.path,s),mt(()=>{n&&n.disconnect()})}const $0=B({setup(e,{slots:t}){const n=_e(!1);return De(()=>{n.value=!0}),()=>n.value&&t.default?t.default():null}});function P0(){if(Te){const e=new Map;window.addEventListener("click",t=>{var s;const n=t.target;if(n.matches('div[class*="language-"] > button.copy')){const o=n.parentElement,i=(s=n.nextElementSibling)==null?void 0:s.nextElementSibling;if(!o||!i)return;const r=/language-(shellscript|shell|bash|sh|zsh)/.test(o.className);let l="";i.querySelectorAll("span.line:not(.diff.remove)").forEach(c=>l+=(c.textContent||"")+`
-`),l=l.slice(0,-1),r&&(l=l.replace(/^ *(\$|>) /gm,"").trim()),S0(l).then(()=>{n.classList.add("copied"),clearTimeout(e.get(n));const c=setTimeout(()=>{n.classList.remove("copied"),n.blur(),e.delete(n)},2e3);e.set(n,c)})}})}}async function S0(e){try{return navigator.clipboard.writeText(e)}catch{const t=document.createElement("textarea"),n=document.activeElement;t.value=e,t.setAttribute("readonly",""),t.style.contain="strict",t.style.position="absolute",t.style.left="-9999px",t.style.fontSize="12pt";const s=document.getSelection(),o=s?s.rangeCount>0&&s.getRangeAt(0):null;document.body.appendChild(t),t.select(),t.selectionStart=0,t.selectionEnd=e.length,document.execCommand("copy"),document.body.removeChild(t),o&&(s.removeAllRanges(),s.addRange(o)),n&&n.focus()}}function C0(){Te&&window.addEventListener("click",e=>{var n,s;const t=e.target;if(t.matches(".vp-code-group input")){const o=(n=t.parentElement)==null?void 0:n.parentElement,i=Array.from((o==null?void 0:o.querySelectorAll("input"))||[]).indexOf(t),r=o==null?void 0:o.querySelector('div[class*="language-"].active'),l=(s=o==null?void 0:o.querySelectorAll('div[class*="language-"]'))==null?void 0:s[i];r&&l&&r!==l&&(r.classList.remove("active"),l.classList.add("active"))}})}const Fr=jt.NotFound||(()=>"404 Not Found"),V0=B({name:"VitePressApp",setup(){const{site:e}=ue();return De(()=>{Xe(()=>e.value.lang,t=>{document.documentElement.lang=t},{immediate:!0})}),x0(),P0(),C0(),jt.setup&&jt.setup(),()=>Hn(jt.Layout)}});function T0(){const e=E0(),t=L0();t.provide(Sr,e);const n=Ma(e.route);return t.provide(Pr,n),t.provide("NotFound",Fr),t.component("Content",Oa),t.component("ClientOnly",$0),Object.defineProperty(t.config.globalProperties,"$frontmatter",{get(){return n.frontmatter.value}}),jt.enhanceApp&&jt.enhanceApp({app:t,router:e,siteData:qt}),{app:t,router:e,data:n}}function L0(){return da(V0)}function E0(){let e=Te,t;return Ia(n=>{let s=$r(n);return e&&(t=s),(e||t===s)&&(s=s.replace(/\.js$/,".lean.js")),Te&&(e=!1),ma(()=>import(s),[])},Fr)}if(Te){const{app:e,router:t,data:n}=T0();t.go().then(()=>{v0(t.route,n.site),e.mount("#app")})}export{O as _,$c as a,g as b,m as c,T0 as createApp,Le as d,d as o,fe as t};
+}`)),document.head.appendChild(S),s.value=b,l[b?"add":"remove"]("dark"),window.getComputedStyle(S).opacity,document.head.removeChild(S)}return p}return Xe(s,r=>{n.value=r}),(r,l)=>(d(),X(mf,{class:"VPSwitchAppearance","aria-label":"toggle dark mode","aria-checked":s.value,onClick:y(o)},{default:I(()=>[T(wf,{class:"sun"}),T(Tf,{class:"moon"})]),_:1},8,["aria-checked","onClick"]))}});const uo=O(Lf,[["__scopeId","data-v-d3b077f5"]]),Ef={key:0,class:"VPNavBarAppearance"},Mf=B({__name:"VPNavBarAppearance",setup(e){const{site:t}=ue();return(n,s)=>y(t).appearance?(d(),m("div",Ef,[T(uo)])):K("",!0)}});const Af=O(Mf,[["__scopeId","data-v-64df1c2d"]]),If={discord:'Discord',facebook:'Facebook',github:'GitHub',instagram:'Instagram',linkedin:'LinkedIn',slack:'Slack',twitter:'Twitter',youtube:'YouTube'},Nf=["href","innerHTML"],Of=B({__name:"VPSocialLink",props:{icon:{},link:{}},setup(e){const t=e,n=re(()=>typeof t.icon=="object"?t.icon.svg:If[t.icon]);return(s,o)=>(d(),m("a",{class:"VPSocialLink",href:s.link,target:"_blank",rel:"noopener",innerHTML:n.value},null,8,Nf))}});const Rf=O(Of,[["__scopeId","data-v-3beb663e"]]),Bf={class:"VPSocialLinks"},Hf=B({__name:"VPSocialLinks",props:{links:{}},setup(e){return(t,n)=>(d(),m("div",Bf,[(d(!0),m(Q,null,Ce(t.links,({link:s,icon:o})=>(d(),X(Rf,{key:s,icon:o,link:s},null,8,["icon","link"]))),128))]))}});const fo=O(Hf,[["__scopeId","data-v-7ec36bc2"]]),Ff=B({__name:"VPNavBarSocialLinks",setup(e){const{theme:t}=ue();return(n,s)=>y(t).socialLinks?(d(),X(fo,{key:0,class:"VPNavBarSocialLinks",links:y(t).socialLinks},null,8,["links"])):K("",!0)}});const Df=O(Ff,[["__scopeId","data-v-c7827124"]]),Uf=e=>(Ge("data-v-913b60b5"),e=e(),qe(),e),jf={key:0,class:"group"},zf={class:"trans-title"},Kf={key:1,class:"group"},Gf={class:"item appearance"},qf=Uf(()=>g("p",{class:"label"},"Appearance",-1)),Wf={class:"appearance-action"},Jf={key:2,class:"group"},Yf={class:"item social-links"},Qf=B({__name:"VPNavBarExtra",setup(e){const{site:t,theme:n}=ue(),s=re(()=>n.value.localeLinks||t.value.appearance||n.value.socialLinks);return(o,i)=>s.value?(d(),X(ao,{key:0,class:"VPNavBarExtra",label:"extra navigation"},{default:I(()=>[y(n).localeLinks?(d(),m("div",jf,[g("p",zf,fe(y(n).localeLinks.text),1),(d(!0),m(Q,null,Ce(y(n).localeLinks.items,r=>(d(),X(ls,{key:r.link,item:r},null,8,["item"]))),128))])):K("",!0),y(t).appearance?(d(),m("div",Kf,[g("div",Gf,[qf,g("div",Wf,[T(uo)])])])):K("",!0),y(n).socialLinks?(d(),m("div",Jf,[g("div",Yf,[T(fo,{class:"social-links-list",links:y(n).socialLinks},null,8,["links"])])])):K("",!0)]),_:1})):K("",!0)}});const Xf=O(Qf,[["__scopeId","data-v-913b60b5"]]),Zf=e=>(Ge("data-v-b2330608"),e=e(),qe(),e),ed=["aria-expanded"],td=Zf(()=>g("span",{class:"container"},[g("span",{class:"top"}),g("span",{class:"middle"}),g("span",{class:"bottom"})],-1)),nd=[td],sd=B({__name:"VPNavBarHamburger",props:{active:{type:Boolean}},emits:["click"],setup(e){return(t,n)=>(d(),m("button",{type:"button",class:pe(["VPNavBarHamburger",{active:t.active}]),"aria-label":"mobile navigation","aria-expanded":t.active,"aria-controls":"VPNavScreen",onClick:n[0]||(n[0]=s=>t.$emit("click"))},nd,10,ed))}});const od=O(sd,[["__scopeId","data-v-b2330608"]]),id={class:"container"},rd={class:"content"},ld=B({__name:"VPNavBar",props:{isScreenOpen:{type:Boolean}},emits:["toggle-screen"],setup(e){const{hasSidebar:t}=et();return(n,s)=>(d(),m("div",{class:pe(["VPNavBar",{"has-sidebar":y(t)}])},[g("div",id,[T(tu,null,{"nav-bar-title-before":I(()=>[L(n.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":I(()=>[L(n.$slots,"nav-bar-title-after",{},void 0,!0)]),_:3}),g("div",rd,[L(n.$slots,"nav-bar-content-before",{},void 0,!0),T(cu,{class:"search"}),T(ef,{class:"menu"}),T(ff,{class:"translations"}),T(Af,{class:"appearance"}),T(Df,{class:"social-links"}),T(Xf,{class:"extra"}),L(n.$slots,"nav-bar-content-after",{},void 0,!0),T(od,{class:"hamburger",active:n.isScreenOpen,onClick:s[0]||(s[0]=o=>n.$emit("toggle-screen"))},null,8,["active"])])])],2))}});const cd=O(ld,[["__scopeId","data-v-01cc589f"]]);function ad(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t1),Ut=[],Un=!1,_o=-1,on=void 0,Pt=void 0,rn=void 0,Ir=function(t){return Ut.some(function(n){return!!(n.options.allowTouchMove&&n.options.allowTouchMove(t))})},jn=function(t){var n=t||window.event;return Ir(n.target)||n.touches.length>1?!0:(n.preventDefault&&n.preventDefault(),!1)},ud=function(t){if(rn===void 0){var n=!!t&&t.reserveScrollBarGap===!0,s=window.innerWidth-document.documentElement.clientWidth;if(n&&s>0){var o=parseInt(window.getComputedStyle(document.body).getPropertyValue("padding-right"),10);rn=document.body.style.paddingRight,document.body.style.paddingRight=o+s+"px"}}on===void 0&&(on=document.body.style.overflow,document.body.style.overflow="hidden")},fd=function(){rn!==void 0&&(document.body.style.paddingRight=rn,rn=void 0),on!==void 0&&(document.body.style.overflow=on,on=void 0)},dd=function(){return window.requestAnimationFrame(function(){if(Pt===void 0){Pt={position:document.body.style.position,top:document.body.style.top,left:document.body.style.left};var t=window,n=t.scrollY,s=t.scrollX,o=t.innerHeight;document.body.style.position="fixed",document.body.style.top=-n,document.body.style.left=-s,setTimeout(function(){return window.requestAnimationFrame(function(){var i=o-window.innerHeight;i&&n>=o&&(document.body.style.top=-(n+i))})},300)}})},pd=function(){if(Pt!==void 0){var t=-parseInt(document.body.style.top,10),n=-parseInt(document.body.style.left,10);document.body.style.position=Pt.position,document.body.style.top=Pt.top,document.body.style.left=Pt.left,window.scrollTo(n,t),Pt=void 0}},_d=function(t){return t?t.scrollHeight-t.scrollTop<=t.clientHeight:!1},hd=function(t,n){var s=t.targetTouches[0].clientY-_o;return Ir(t.target)?!1:n&&n.scrollTop===0&&s>0||_d(n)&&s<0?jn(t):(t.stopPropagation(),!0)},Nr=function(t,n){if(!t){console.error("disableBodyScroll unsuccessful - targetElement must be provided when calling disableBodyScroll on IOS devices.");return}if(!Ut.some(function(o){return o.targetElement===t})){var s={targetElement:t,options:n||{}};Ut=[].concat(ad(Ut),[s]),Dn?dd():ud(n),Dn&&(t.ontouchstart=function(o){o.targetTouches.length===1&&(_o=o.targetTouches[0].clientY)},t.ontouchmove=function(o){o.targetTouches.length===1&&hd(o,t)},Un||(document.addEventListener("touchmove",jn,po?{passive:!1}:void 0),Un=!0))}},Or=function(){Dn&&(Ut.forEach(function(t){t.targetElement.ontouchstart=null,t.targetElement.ontouchmove=null}),Un&&(document.removeEventListener("touchmove",jn,po?{passive:!1}:void 0),Un=!1),_o=-1),Dn?pd():fd(),Ut=[]};const vd=B({__name:"VPNavScreenMenuLink",props:{text:{},link:{}},setup(e){const t=Ke("close-screen");return(n,s)=>(d(),X(Et,{class:"VPNavScreenMenuLink",href:n.link,onClick:y(t)},{default:I(()=>[Le(fe(n.text),1)]),_:1},8,["href","onClick"]))}});const md=O(vd,[["__scopeId","data-v-a0728f16"]]),gd={},yd={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},bd=g("path",{d:"M18.9,10.9h-6v-6c0-0.6-0.4-1-1-1s-1,0.4-1,1v6h-6c-0.6,0-1,0.4-1,1s0.4,1,1,1h6v6c0,0.6,0.4,1,1,1s1-0.4,1-1v-6h6c0.6,0,1-0.4,1-1S19.5,10.9,18.9,10.9z"},null,-1),kd=[bd];function xd(e,t){return d(),m("svg",yd,kd)}const wd=O(gd,[["render",xd]]),$d=B({__name:"VPNavScreenMenuGroupLink",props:{text:{},link:{}},setup(e){const t=Ke("close-screen");return(n,s)=>(d(),X(Et,{class:"VPNavScreenMenuGroupLink",href:n.link,onClick:y(t)},{default:I(()=>[Le(fe(n.text),1)]),_:1},8,["href","onClick"]))}});const Rr=O($d,[["__scopeId","data-v-a90a76c1"]]),Pd={class:"VPNavScreenMenuGroupSection"},Sd={key:0,class:"title"},Cd=B({__name:"VPNavScreenMenuGroupSection",props:{text:{},items:{}},setup(e){return(t,n)=>(d(),m("div",Pd,[t.text?(d(),m("p",Sd,fe(t.text),1)):K("",!0),(d(!0),m(Q,null,Ce(t.items,s=>(d(),X(Rr,{key:s.text,text:s.text,link:s.link},null,8,["text","link"]))),128))]))}});const Vd=O(Cd,[["__scopeId","data-v-4525d52f"]]),Td=["aria-controls","aria-expanded"],Ld={class:"button-text"},Ed=["id"],Md={key:1,class:"group"},Ad=B({__name:"VPNavScreenMenuGroup",props:{text:{},items:{}},setup(e){const t=e,n=_e(!1),s=re(()=>`NavScreenGroup-${t.text.replace(" ","-").toLowerCase()}`);function o(){n.value=!n.value}return(i,r)=>(d(),m("div",{class:pe(["VPNavScreenMenuGroup",{open:n.value}])},[g("button",{class:"button","aria-controls":s.value,"aria-expanded":n.value,onClick:o},[g("span",Ld,fe(i.text),1),T(wd,{class:"button-icon"})],8,Td),g("div",{id:s.value,class:"items"},[(d(!0),m(Q,null,Ce(i.items,l=>(d(),m(Q,{key:l.text},["link"in l?(d(),m("div",{key:l.text,class:"item"},[T(Rr,{text:l.text,link:l.link},null,8,["text","link"])])):(d(),m("div",Md,[T(Vd,{text:l.text,items:l.items},null,8,["text","items"])]))],64))),128))],8,Ed)],2))}});const Id=O(Ad,[["__scopeId","data-v-706622bc"]]),Nd={key:0,class:"VPNavScreenMenu"},Od=B({__name:"VPNavScreenMenu",setup(e){const{theme:t}=ue();return(n,s)=>y(t).nav?(d(),m("nav",Nd,[(d(!0),m(Q,null,Ce(y(t).nav,o=>(d(),m(Q,{key:o.text},["link"in o?(d(),X(md,{key:0,text:o.text,link:o.link},null,8,["text","link"])):(d(),X(Id,{key:1,text:o.text||"",items:o.items},null,8,["text","items"]))],64))),128))])):K("",!0)}}),Rd=e=>(Ge("data-v-6f750383"),e=e(),qe(),e),Bd={key:0,class:"VPNavScreenAppearance"},Hd=Rd(()=>g("p",{class:"text"},"Appearance",-1)),Fd=B({__name:"VPNavScreenAppearance",setup(e){const{site:t}=ue();return(n,s)=>y(t).appearance?(d(),m("div",Bd,[Hd,T(uo)])):K("",!0)}});const Dd=O(Fd,[["__scopeId","data-v-6f750383"]]),Ud={class:"list"},jd=["href"],zd=B({__name:"VPNavScreenTranslations",setup(e){const{theme:t}=ue(),n=_e(!1);function s(){n.value=!n.value}return(o,i)=>y(t).localeLinks?(d(),m("div",{key:0,class:pe(["VPNavScreenTranslations",{open:n.value}])},[g("button",{class:"title",onClick:s},[T(Ar,{class:"icon lang"}),Le(" "+fe(y(t).localeLinks.text)+" ",1),T(Mr,{class:"icon chevron"})]),g("ul",Ud,[(d(!0),m(Q,null,Ce(y(t).localeLinks.items,r=>(d(),m("li",{key:r.link,class:"item"},[g("a",{class:"link",href:r.link},fe(r.text),9,jd)]))),128))])],2)):K("",!0)}});const Kd=O(zd,[["__scopeId","data-v-45be1f42"]]),Gd=B({__name:"VPNavScreenSocialLinks",setup(e){const{theme:t}=ue();return(n,s)=>y(t).socialLinks?(d(),X(fo,{key:0,class:"VPNavScreenSocialLinks",links:y(t).socialLinks},null,8,["links"])):K("",!0)}}),qd={class:"container"},Wd=B({__name:"VPNavScreen",props:{open:{type:Boolean}},setup(e){const t=_e(null);function n(){Nr(t.value,{reserveScrollBarGap:!0})}function s(){Or()}return(o,i)=>(d(),X(is,{name:"fade",onEnter:n,onAfterLeave:s},{default:I(()=>[o.open?(d(),m("div",{key:0,class:"VPNavScreen",ref_key:"screen",ref:t},[g("div",qd,[L(o.$slots,"nav-screen-content-before",{},void 0,!0),T(Od,{class:"menu"}),T(Kd,{class:"translations"}),T(Dd,{class:"appearance"}),T(Gd,{class:"social-links"}),L(o.$slots,"nav-screen-content-after",{},void 0,!0)])],512)):K("",!0)]),_:3}))}});const Jd=O(Wd,[["__scopeId","data-v-ad2a381f"]]),Yd=B({__name:"VPNav",setup(e){const{isScreenOpen:t,closeScreen:n,toggleScreen:s}=Ja(),{hasSidebar:o}=et();return ns("close-screen",n),(i,r)=>(d(),m("header",{class:pe(["VPNav",{"no-sidebar":!y(o)}])},[T(cd,{"is-screen-open":y(t),onToggleScreen:y(s)},{"nav-bar-title-before":I(()=>[L(i.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":I(()=>[L(i.$slots,"nav-bar-title-after",{},void 0,!0)]),"nav-bar-content-before":I(()=>[L(i.$slots,"nav-bar-content-before",{},void 0,!0)]),"nav-bar-content-after":I(()=>[L(i.$slots,"nav-bar-content-after",{},void 0,!0)]),_:3},8,["is-screen-open","onToggleScreen"]),T(Jd,{open:y(t)},{"nav-screen-content-before":I(()=>[L(i.$slots,"nav-screen-content-before",{},void 0,!0)]),"nav-screen-content-after":I(()=>[L(i.$slots,"nav-screen-content-after",{},void 0,!0)]),_:3},8,["open"])],2))}});const Qd=O(Yd,[["__scopeId","data-v-8b5778ac"]]),Xd={},Zd={xmlns:"http://www.w3.org/2000/svg","aria-hidden":"true",focusable:"false",viewBox:"0 0 24 24"},ep=g("path",{d:"M17,11H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h14c0.6,0,1,0.4,1,1S17.6,11,17,11z"},null,-1),tp=g("path",{d:"M21,7H3C2.4,7,2,6.6,2,6s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,7,21,7z"},null,-1),np=g("path",{d:"M21,15H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h18c0.6,0,1,0.4,1,1S21.6,15,21,15z"},null,-1),sp=g("path",{d:"M17,19H3c-0.6,0-1-0.4-1-1s0.4-1,1-1h14c0.6,0,1,0.4,1,1S17.6,19,17,19z"},null,-1),op=[ep,tp,np,sp];function ip(e,t){return d(),m("svg",Zd,op)}const rp=O(Xd,[["render",ip]]),lp=e=>(Ge("data-v-bcaee864"),e=e(),qe(),e),cp={key:0,class:"VPLocalNav"},ap=["aria-expanded"],up=lp(()=>g("span",{class:"menu-text"},"Menu",-1)),fp=B({__name:"VPLocalNav",props:{open:{type:Boolean}},emits:["open-menu"],setup(e){const{hasSidebar:t}=et();function n(){window.scrollTo({top:0,left:0,behavior:"smooth"})}return(s,o)=>y(t)?(d(),m("div",cp,[g("button",{class:"menu","aria-expanded":s.open,"aria-controls":"VPSidebarNav",onClick:o[0]||(o[0]=i=>s.$emit("open-menu"))},[T(rp,{class:"menu-icon"}),up],8,ap),g("a",{class:"top-link",href:"#",onClick:n}," Return to top ")])):K("",!0)}});const dp=O(fp,[["__scopeId","data-v-bcaee864"]]),pp={},_p={version:"1.1",xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},hp=g("path",{d:"M19,2H5C3.3,2,2,3.3,2,5v14c0,1.7,1.3,3,3,3h14c1.7,0,3-1.3,3-3V5C22,3.3,20.7,2,19,2z M20,19c0,0.6-0.4,1-1,1H5c-0.6,0-1-0.4-1-1V5c0-0.6,0.4-1,1-1h14c0.6,0,1,0.4,1,1V19z"},null,-1),vp=g("path",{d:"M16,11h-3V8c0-0.6-0.4-1-1-1s-1,0.4-1,1v3H8c-0.6,0-1,0.4-1,1s0.4,1,1,1h3v3c0,0.6,0.4,1,1,1s1-0.4,1-1v-3h3c0.6,0,1-0.4,1-1S16.6,11,16,11z"},null,-1),mp=[hp,vp];function gp(e,t){return d(),m("svg",_p,mp)}const yp=O(pp,[["render",gp]]),bp={},kp={xmlns:"http://www.w3.org/2000/svg","xmlns:xlink":"http://www.w3.org/1999/xlink",viewBox:"0 0 24 24"},xp=g("path",{d:"M19,2H5C3.3,2,2,3.3,2,5v14c0,1.7,1.3,3,3,3h14c1.7,0,3-1.3,3-3V5C22,3.3,20.7,2,19,2zM20,19c0,0.6-0.4,1-1,1H5c-0.6,0-1-0.4-1-1V5c0-0.6,0.4-1,1-1h14c0.6,0,1,0.4,1,1V19z"},null,-1),wp=g("path",{d:"M16,11H8c-0.6,0-1,0.4-1,1s0.4,1,1,1h8c0.6,0,1-0.4,1-1S16.6,11,16,11z"},null,-1),$p=[xp,wp];function Pp(e,t){return d(),m("svg",kp,$p)}const Sp=O(bp,[["render",Pp]]),Cp=["innerHTML"],Vp=B({__name:"VPSidebarLink",props:{item:{},depth:{default:1}},setup(e){const{page:t,frontmatter:n}=ue(),s=re(()=>n.value.sidebarDepth||1/0),o=Ke("close-sidebar");return(i,r)=>{const l=Lt("VPSidebarLink",!0);return d(),m(Q,null,[T(Et,{class:pe(["link",{active:y(Yt)(y(t).relativePath,i.item.link)}]),style:qn({paddingLeft:16*(i.depth-1)+"px"}),href:i.item.link,onClick:y(o)},{default:I(()=>[g("span",{innerHTML:i.item.text,class:pe(["link-text",{light:i.depth>1}])},null,10,Cp)]),_:1},8,["class","style","href","onClick"]),"items"in i.item&&i.depth(d(),X(l,{key:c.link,item:c,depth:i.depth+1},null,8,["item","depth"]))),128)):K("",!0)],64)}}});const Tp=O(Vp,[["__scopeId","data-v-836d4f27"]]),Lp=["role"],Ep=["innerHTML"],Mp={class:"action"},Ap={class:"items"},Ip=B({__name:"VPSidebarGroup",props:{text:{},items:{},collapsible:{type:Boolean},collapsed:{type:Boolean}},setup(e){const t=e,n=_e(!1);Kt(()=>{n.value=!!(t.collapsible&&t.collapsed)});const{page:s}=ue();Kt(()=>{t.items.some(i=>Yt(s.value.relativePath,i.link))&&(n.value=!1)});function o(){t.collapsible&&(n.value=!n.value)}return(i,r)=>(d(),m("section",{class:pe(["VPSidebarGroup",{collapsible:i.collapsible,collapsed:n.value}])},[i.text?(d(),m("div",{key:0,class:"title",role:i.collapsible?"button":void 0,onClick:o},[g("h2",{innerHTML:i.text,class:"title-text"},null,8,Ep),g("div",Mp,[T(Sp,{class:"icon minus"}),T(yp,{class:"icon plus"})])],8,Lp)):K("",!0),g("div",Ap,[(d(!0),m(Q,null,Ce(i.items,l=>(d(),X(Tp,{key:l.link,item:l},null,8,["item"]))),128))])],2))}});const Np=O(Ip,[["__scopeId","data-v-130e8933"]]),Op=e=>(Ge("data-v-1367b3e4"),e=e(),qe(),e),Rp={class:"nav",id:"VPSidebarNav","aria-labelledby":"sidebar-aria-label",tabindex:"-1"},Bp=Op(()=>g("span",{class:"visually-hidden",id:"sidebar-aria-label"}," Sidebar Navigation ",-1)),Hp=B({__name:"VPSidebar",props:{open:{type:Boolean}},setup(e){const{sidebar:t,hasSidebar:n}=et(),s=e;let o=_e(null);function i(){Nr(o.value,{reserveScrollBarGap:!0})}function r(){Or()}return Xi(async()=>{var l;s.open?(i(),(l=o.value)==null||l.focus()):r()}),(l,c)=>y(n)?(d(),m("aside",{key:0,class:pe(["VPSidebar",{open:l.open}]),ref_key:"navEl",ref:o,onClick:c[0]||(c[0]=aa(()=>{},["stop"]))},[g("nav",Rp,[Bp,L(l.$slots,"sidebar-nav-before",{},void 0,!0),(d(!0),m(Q,null,Ce(y(t),f=>(d(),m("div",{key:f.text,class:"group"},[T(Np,{text:f.text,items:f.items,collapsible:f.collapsible,collapsed:f.collapsed},null,8,["text","items","collapsible","collapsed"])]))),128)),L(l.$slots,"sidebar-nav-after",{},void 0,!0)])],2)):K("",!0)}});const Fp=O(Hp,[["__scopeId","data-v-1367b3e4"]]),Dp={},Up={class:"VPPage"};function jp(e,t){const n=Lt("Content");return d(),m("div",Up,[T(n)])}const zp=O(Dp,[["render",jp]]),Kp=B({__name:"VPButton",props:{tag:{},size:{},theme:{},text:{},href:{}},setup(e){const t=e,n=re(()=>[t.size??"medium",t.theme??"brand"]),s=re(()=>t.href&&rs.test(t.href)),o=re(()=>t.tag?t.tag:t.href?"a":"button");return(i,r)=>(d(),X(to(o.value),{class:pe(["VPButton",n.value]),href:i.href?y(Fn)(i.href):void 0,target:s.value?"_blank":void 0,rel:s.value?"noreferrer":void 0},{default:I(()=>[Le(fe(i.text),1)]),_:1},8,["class","href","target","rel"]))}});const Gp=O(Kp,[["__scopeId","data-v-0311ec08"]]),qp=e=>(Ge("data-v-96d2911b"),e=e(),qe(),e),Wp={class:"container"},Jp={class:"main"},Yp={key:0,class:"name"},Qp={class:"clip"},Xp={key:1,class:"text"},Zp={key:2,class:"tagline"},e_={key:3,class:"actions"},t_={key:0,class:"image"},n_={class:"image-container"},s_=qp(()=>g("div",{class:"image-bg"},null,-1)),o_=B({__name:"VPHero",props:{name:{},text:{},tagline:{},image:{},actions:{}},setup(e){return(t,n)=>(d(),m("div",{class:pe(["VPHero",{"has-image":t.image}])},[g("div",Wp,[g("div",Jp,[t.name?(d(),m("h1",Yp,[g("span",Qp,fe(t.name),1)])):K("",!0),t.text?(d(),m("p",Xp,fe(t.text),1)):K("",!0),t.tagline?(d(),m("p",Zp,fe(t.tagline),1)):K("",!0),t.actions?(d(),m("div",e_,[(d(!0),m(Q,null,Ce(t.actions,s=>(d(),m("div",{key:s.link,class:"action"},[T(Gp,{tag:"a",size:"medium",theme:s.theme,text:s.text,href:s.link},null,8,["theme","text","href"])]))),128))])):K("",!0)]),t.image?(d(),m("div",t_,[g("div",n_,[s_,T(Tr,{class:"image-src",image:t.image},null,8,["image"])])])):K("",!0)])],2))}});const i_=O(o_,[["__scopeId","data-v-96d2911b"]]),r_=B({__name:"VPHomeHero",setup(e){const{frontmatter:t}=ue();return(n,s)=>y(t).hero?(d(),X(i_,{key:0,class:"VPHomeHero",name:y(t).hero.name,text:y(t).hero.text,tagline:y(t).hero.tagline,image:y(t).hero.image,actions:y(t).hero.actions},null,8,["name","text","tagline","image","actions"])):K("",!0)}}),l_={},c_={xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},a_=g("path",{d:"M19.9,12.4c0.1-0.2,0.1-0.5,0-0.8c-0.1-0.1-0.1-0.2-0.2-0.3l-7-7c-0.4-0.4-1-0.4-1.4,0s-0.4,1,0,1.4l5.3,5.3H5c-0.6,0-1,0.4-1,1s0.4,1,1,1h11.6l-5.3,5.3c-0.4,0.4-0.4,1,0,1.4c0.2,0.2,0.5,0.3,0.7,0.3s0.5-0.1,0.7-0.3l7-7C19.8,12.6,19.9,12.5,19.9,12.4z"},null,-1),u_=[a_];function f_(e,t){return d(),m("svg",c_,u_)}const d_=O(l_,[["render",f_]]),p_={class:"box"},__={key:0,class:"icon"},h_={class:"title"},v_={class:"details"},m_={key:1,class:"link-text"},g_={class:"link-text-value"},y_=B({__name:"VPFeature",props:{icon:{},title:{},details:{},link:{},linkText:{}},setup(e){return(t,n)=>(d(),X(Et,{class:"VPFeature",href:t.link,"no-icon":!0},{default:I(()=>[g("article",p_,[t.icon?(d(),m("div",__,fe(t.icon),1)):K("",!0),g("h2",h_,fe(t.title),1),g("p",v_,fe(t.details),1),t.linkText?(d(),m("div",m_,[g("p",g_,[Le(fe(t.linkText)+" ",1),T(d_,{class:"link-text-icon"})])])):K("",!0)])]),_:1},8,["href"]))}});const b_=O(y_,[["__scopeId","data-v-cfb3cf73"]]),k_={key:0,class:"VPFeatures"},x_={class:"container"},w_={class:"items"},$_=B({__name:"VPFeatures",props:{features:{}},setup(e){const t=e,n=re(()=>{const s=t.features.length;if(s){if(s===2)return"grid-2";if(s===3)return"grid-3";if(s%3===0)return"grid-6";if(s%2===0)return"grid-4"}else return});return(s,o)=>s.features?(d(),m("div",k_,[g("div",x_,[g("div",w_,[(d(!0),m(Q,null,Ce(s.features,i=>(d(),m("div",{key:i.title,class:pe(["item",[n.value]])},[T(b_,{icon:i.icon,title:i.title,details:i.details,link:i.link,"link-text":i.linkText},null,8,["icon","title","details","link","link-text"])],2))),128))])])])):K("",!0)}});const P_=O($_,[["__scopeId","data-v-03ba64f3"]]),S_=B({__name:"VPHomeFeatures",setup(e){const{frontmatter:t}=ue();return(n,s)=>y(t).features?(d(),X(P_,{key:0,class:"VPHomeFeatures",features:y(t).features},null,8,["features"])):K("",!0)}}),C_={class:"VPHome"},V_=B({__name:"VPHome",setup(e){return(t,n)=>{const s=Lt("Content");return d(),m("div",C_,[L(t.$slots,"home-hero-before",{},void 0,!0),T(r_),L(t.$slots,"home-hero-after",{},void 0,!0),L(t.$slots,"home-features-before",{},void 0,!0),T(S_),L(t.$slots,"home-features-after",{},void 0,!0),T(s)])}}});const T_=O(V_,[["__scopeId","data-v-5a33d42b"]]);var pi;const Br=typeof window<"u";Br&&((pi=window==null?void 0:window.navigator)!=null&&pi.userAgent)&&/iP(ad|hone|od)/.test(window.navigator.userAgent);function L_(e){return e}function E_(e){return Vi()?(ol(e),!0):!1}function M_(e){return typeof e=="function"?re(e):_e(e)}function A_(e,t=!0){ro()?De(e):t?e():Xs(e)}const I_=Br?window:void 0;function N_(e,t=!1){const n=_e(),s=()=>n.value=!!e();return s(),A_(s,t),n}function _i(e,t={}){const{window:n=I_}=t,s=N_(()=>n&&"matchMedia"in n&&typeof n.matchMedia=="function");let o;const i=_e(!1),r=()=>{o&&("removeEventListener"in o?o.removeEventListener("change",l):o.removeListener(l))},l=()=>{s.value&&(r(),o=n.matchMedia(M_(e).value),i.value=o.matches,"addEventListener"in o?o.addEventListener("change",l):o.addListener(l))};return Kt(l),E_(()=>r()),i}const hi=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{},vi="__vueuse_ssr_handlers__";hi[vi]=hi[vi]||{};var mi;(function(e){e.UP="UP",e.RIGHT="RIGHT",e.DOWN="DOWN",e.LEFT="LEFT",e.NONE="NONE"})(mi||(mi={}));var O_=Object.defineProperty,gi=Object.getOwnPropertySymbols,R_=Object.prototype.hasOwnProperty,B_=Object.prototype.propertyIsEnumerable,yi=(e,t,n)=>t in e?O_(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,H_=(e,t)=>{for(var n in t||(t={}))R_.call(t,n)&&yi(e,n,t[n]);if(gi)for(var n of gi(t))B_.call(t,n)&&yi(e,n,t[n]);return e};const F_={easeInSine:[.12,0,.39,0],easeOutSine:[.61,1,.88,1],easeInOutSine:[.37,0,.63,1],easeInQuad:[.11,0,.5,0],easeOutQuad:[.5,1,.89,1],easeInOutQuad:[.45,0,.55,1],easeInCubic:[.32,0,.67,0],easeOutCubic:[.33,1,.68,1],easeInOutCubic:[.65,0,.35,1],easeInQuart:[.5,0,.75,0],easeOutQuart:[.25,1,.5,1],easeInOutQuart:[.76,0,.24,1],easeInQuint:[.64,0,.78,0],easeOutQuint:[.22,1,.36,1],easeInOutQuint:[.83,0,.17,1],easeInExpo:[.7,0,.84,0],easeOutExpo:[.16,1,.3,1],easeInOutExpo:[.87,0,.13,1],easeInCirc:[.55,0,1,.45],easeOutCirc:[0,.55,.45,1],easeInOutCirc:[.85,0,.15,1],easeInBack:[.36,0,.66,-.56],easeOutBack:[.34,1.56,.64,1],easeInOutBack:[.68,-.6,.32,1.6]};H_({linear:L_},F_);function D_(){const{hasSidebar:e}=et(),t=_i("(min-width: 960px)"),n=_i("(min-width: 1280px)");return{isAsideEnabled:re(()=>!n.value&&!t.value?!1:e.value?n.value:t.value)}}const U_=71;function j_(e){if(e===!1)return[];let t=[];return document.querySelectorAll("h2, h3, h4, h5, h6").forEach(n=>{n.textContent&&n.id&&t.push({level:Number(n.tagName[1]),title:n.innerText.replace(/\s+#\s*$/,""),link:`#${n.id}`})}),z_(t,e)}function z_(e,t=2){return K_(e,typeof t=="number"?[t,t]:t==="deep"?[2,6]:t)}function K_(e,t){const n=[];return e=e.map(s=>({...s})),e.forEach((s,o)=>{s.level>=t[0]&&s.level<=t[1]&&G_(o,e,t)&&n.push(s)}),n}function G_(e,t,n){if(e===0)return!0;const s=t[e];for(let o=e-1;o>=0;o--){const i=t[o];if(i.level=n[0]&&i.level<=n[1])return i.children==null&&(i.children=[]),i.children.push(s),!1}return!0}function q_(e,t){const{isAsideEnabled:n}=D_(),s=Da(i,100);let o=null;De(()=>{requestAnimationFrame(i),window.addEventListener("scroll",s)}),no(()=>{r(location.hash)}),mt(()=>{window.removeEventListener("scroll",s)});function i(){if(!n.value)return;const l=[].slice.call(e.value.querySelectorAll(".outline-link")),c=[].slice.call(document.querySelectorAll(".content .header-anchor")).filter(S=>l.some(Y=>Y.hash===S.hash&&S.offsetParent!==null)),f=window.scrollY,p=window.innerHeight,_=document.body.offsetHeight,b=Math.abs(f+p-_)<1;if(c.length&&b){r(c[c.length-1].hash);return}for(let S=0;S{const s=Lt("VPDocAsideOutlineItem",!0);return d(),m("ul",{class:pe(t.root?"root":"nested")},[(d(!0),m(Q,null,Ce(t.headers,({children:o,link:i,title:r})=>(d(),m("li",null,[g("a",{class:"outline-link",href:i,onClick:n[0]||(n[0]=(...l)=>t.onClick&&t.onClick(...l))},fe(r),9,J_),o!=null&&o.length?(d(),X(s,{key:0,headers:o,onClick:t.onClick},null,8,["headers","onClick"])):K("",!0)]))),256))],2)}}});const Q_=O(Y_,[["__scopeId","data-v-a0ec0882"]]),X_=e=>(Ge("data-v-46ce5e0f"),e=e(),qe(),e),Z_={class:"content"},eh={class:"outline-title"},th={"aria-labelledby":"doc-outline-aria-label"},nh=X_(()=>g("span",{class:"visually-hidden",id:"doc-outline-aria-label"}," Table of Contents for current page ",-1)),sh=B({__name:"VPDocAsideOutline",setup(e){const{frontmatter:t,theme:n}=ue(),s=re(()=>t.value.outline??n.value.outline),o=Ke("onContentUpdated");o.value=()=>{i.value=j_(s.value)};const i=_e([]),r=re(()=>i.value.length>0),l=_e(),c=_e();q_(l,c);function f({target:p}){const _="#"+p.href.split("#")[1],b=document.querySelector(decodeURIComponent(_));b==null||b.focus()}return(p,_)=>(d(),m("div",{class:pe(["VPDocAsideOutline",{"has-outline":r.value}]),ref_key:"container",ref:l},[g("div",Z_,[g("div",{class:"outline-marker",ref_key:"marker",ref:c},null,512),g("div",eh,fe(y(n).outlineTitle||"On this page"),1),g("nav",th,[nh,T(Q_,{headers:i.value,root:!0,onClick:f},null,8,["headers"])])])],2))}});const oh=O(sh,[["__scopeId","data-v-46ce5e0f"]]),ih={class:"VPDocAsideCarbonAds"},rh=B({__name:"VPDocAsideCarbonAds",setup(e){const t=()=>null;return(n,s)=>(d(),m("div",ih,[T(y(t))]))}}),lh=e=>(Ge("data-v-765822e5"),e=e(),qe(),e),ch={class:"VPDocAside"},ah=lh(()=>g("div",{class:"spacer"},null,-1)),uh=B({__name:"VPDocAside",setup(e){const{theme:t}=ue();return(n,s)=>(d(),m("div",ch,[L(n.$slots,"aside-top",{},void 0,!0),L(n.$slots,"aside-outline-before",{},void 0,!0),T(oh),L(n.$slots,"aside-outline-after",{},void 0,!0),ah,L(n.$slots,"aside-ads-before",{},void 0,!0),y(t).carbonAds?(d(),X(rh,{key:0})):K("",!0),L(n.$slots,"aside-ads-after",{},void 0,!0),L(n.$slots,"aside-bottom",{},void 0,!0)]))}});const fh=O(uh,[["__scopeId","data-v-765822e5"]]);function dh(){const{theme:e,page:t}=ue();return re(()=>{const{text:n="Edit this page",pattern:s}=e.value.editLink||{},{relativePath:o}=t.value;return{url:s.replace(/:path/g,o),text:n}})}function ph(){const{page:e,theme:t,frontmatter:n}=ue();return re(()=>{const s=Vr(t.value.sidebar,e.value.relativePath),o=Ua(s),i=o.findIndex(r=>Yt(e.value.relativePath,r.link));return{prev:n.value.prev?{...o[i-1],text:n.value.prev}:o[i-1],next:n.value.next?{...o[i+1],text:n.value.next}:o[i+1]}})}const _h={},hh={xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},vh=g("path",{d:"M18,23H4c-1.7,0-3-1.3-3-3V6c0-1.7,1.3-3,3-3h7c0.6,0,1,0.4,1,1s-0.4,1-1,1H4C3.4,5,3,5.4,3,6v14c0,0.6,0.4,1,1,1h14c0.6,0,1-0.4,1-1v-7c0-0.6,0.4-1,1-1s1,0.4,1,1v7C21,21.7,19.7,23,18,23z"},null,-1),mh=g("path",{d:"M8,17c-0.3,0-0.5-0.1-0.7-0.3C7,16.5,6.9,16.1,7,15.8l1-4c0-0.2,0.1-0.3,0.3-0.5l9.5-9.5c1.2-1.2,3.2-1.2,4.4,0c1.2,1.2,1.2,3.2,0,4.4l-9.5,9.5c-0.1,0.1-0.3,0.2-0.5,0.3l-4,1C8.2,17,8.1,17,8,17zM9.9,12.5l-0.5,2.1l2.1-0.5l9.3-9.3c0.4-0.4,0.4-1.1,0-1.6c-0.4-0.4-1.2-0.4-1.6,0l0,0L9.9,12.5z M18.5,2.5L18.5,2.5L18.5,2.5z"},null,-1),gh=[vh,mh];function yh(e,t){return d(),m("svg",hh,gh)}const bh=O(_h,[["render",yh]]),kh={class:"VPLastUpdated"},xh=["datetime"],wh=B({__name:"VPDocFooterLastUpdated",setup(e){const{theme:t,page:n}=ue(),s=re(()=>new Date(n.value.lastUpdated)),o=re(()=>s.value.toISOString()),i=_e("");return De(()=>{Kt(()=>{i.value=s.value.toLocaleString(window.navigator.language)})}),(r,l)=>(d(),m("p",kh,[Le(fe(y(t).lastUpdatedText??"Last updated")+": ",1),g("time",{datetime:o.value},fe(i.value),9,xh)]))}});const $h=O(wh,[["__scopeId","data-v-2f5384a2"]]),Ph={key:0,class:"VPDocFooter"},Sh={key:0,class:"edit-info"},Ch={key:0,class:"edit-link"},Vh={key:1,class:"last-updated"},Th={key:1,class:"prev-next"},Lh={class:"pager"},Eh=["href"],Mh=["innerHTML"],Ah=["innerHTML"],Ih=["href"],Nh=["innerHTML"],Oh=["innerHTML"],Rh=B({__name:"VPDocFooter",setup(e){const{theme:t,page:n,frontmatter:s}=ue(),o=dh(),i=ph(),r=re(()=>t.value.editLink&&s.value.editLink!==!1),l=re(()=>n.value.lastUpdated&&s.value.lastUpdated!==!1),c=re(()=>r.value||l.value||i.value.prev||i.value.next);return(f,p)=>{var _,b;return c.value?(d(),m("footer",Ph,[r.value||l.value?(d(),m("div",Sh,[r.value?(d(),m("div",Ch,[T(Et,{class:"edit-link-button",href:y(o).url,"no-icon":!0},{default:I(()=>[T(bh,{class:"edit-link-icon"}),Le(" "+fe(y(o).text),1)]),_:1},8,["href"])])):K("",!0),l.value?(d(),m("div",Vh,[T($h)])):K("",!0)])):K("",!0),y(i).prev||y(i).next?(d(),m("div",Th,[g("div",Lh,[y(i).prev?(d(),m("a",{key:0,class:"pager-link prev",href:y(Fn)(y(i).prev.link)},[g("span",{class:"desc",innerHTML:((_=y(t).docFooter)==null?void 0:_.prev)??"Previous page"},null,8,Mh),g("span",{class:"title",innerHTML:y(i).prev.text},null,8,Ah)],8,Eh)):K("",!0)]),g("div",{class:pe(["pager",{"has-prev":y(i).prev}])},[y(i).next?(d(),m("a",{key:0,class:"pager-link next",href:y(Fn)(y(i).next.link)},[g("span",{class:"desc",innerHTML:((b=y(t).docFooter)==null?void 0:b.next)??"Next page"},null,8,Nh),g("span",{class:"title",innerHTML:y(i).next.text},null,8,Oh)],8,Ih)):K("",!0)],2)])):K("",!0)])):K("",!0)}}});const Bh=O(Rh,[["__scopeId","data-v-e915c016"]]),Hh=e=>(Ge("data-v-808f78d8"),e=e(),qe(),e),Fh={class:"container"},Dh={key:0,class:"aside"},Uh=Hh(()=>g("div",{class:"aside-curtain"},null,-1)),jh={class:"aside-container"},zh={class:"aside-content"},Kh={class:"content"},Gh={class:"content-container"},qh={class:"main"},Wh=B({__name:"VPDoc",setup(e){const t=gt(),{hasSidebar:n,hasAside:s}=et(),o=re(()=>t.path.replace(/[./]+/g,"_").replace(/_html$/,"")),i=_e();return ns("onContentUpdated",i),(r,l)=>{const c=Lt("Content");return d(),m("div",{class:pe(["VPDoc",{"has-sidebar":y(n),"has-aside":y(s)}])},[g("div",Fh,[y(s)?(d(),m("div",Dh,[Uh,g("div",jh,[g("div",zh,[T(fh,null,{"aside-top":I(()=>[L(r.$slots,"aside-top",{},void 0,!0)]),"aside-bottom":I(()=>[L(r.$slots,"aside-bottom",{},void 0,!0)]),"aside-outline-before":I(()=>[L(r.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":I(()=>[L(r.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":I(()=>[L(r.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":I(()=>[L(r.$slots,"aside-ads-after",{},void 0,!0)]),_:3})])])])):K("",!0),g("div",Kh,[g("div",Gh,[L(r.$slots,"doc-before",{},void 0,!0),g("main",qh,[T(c,{class:pe(["vp-doc",o.value]),onContentUpdated:i.value},null,8,["class","onContentUpdated"])]),L(r.$slots,"doc-footer-before",{},void 0,!0),T(Bh),L(r.$slots,"doc-after",{},void 0,!0)])])])],2)}}});const Jh=O(Wh,[["__scopeId","data-v-808f78d8"]]),Yh=B({__name:"VPContent",setup(e){const t=gt(),{frontmatter:n}=ue(),{hasSidebar:s}=et(),o=Ke("NotFound");return(i,r)=>(d(),m("div",{class:pe(["VPContent",{"has-sidebar":y(s),"is-home":y(n).layout==="home"}]),id:"VPContent"},[y(t).component===y(o)?(d(),X(y(o),{key:0})):y(n).layout==="page"?(d(),X(zp,{key:1})):y(n).layout==="home"?(d(),X(T_,{key:2},{"home-hero-before":I(()=>[L(i.$slots,"home-hero-before",{},void 0,!0)]),"home-hero-after":I(()=>[L(i.$slots,"home-hero-after",{},void 0,!0)]),"home-features-before":I(()=>[L(i.$slots,"home-features-before",{},void 0,!0)]),"home-features-after":I(()=>[L(i.$slots,"home-features-after",{},void 0,!0)]),_:3})):(d(),X(Jh,{key:3},{"doc-footer-before":I(()=>[L(i.$slots,"doc-footer-before",{},void 0,!0)]),"doc-before":I(()=>[L(i.$slots,"doc-before",{},void 0,!0)]),"doc-after":I(()=>[L(i.$slots,"doc-after",{},void 0,!0)]),"aside-top":I(()=>[L(i.$slots,"aside-top",{},void 0,!0)]),"aside-outline-before":I(()=>[L(i.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":I(()=>[L(i.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":I(()=>[L(i.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":I(()=>[L(i.$slots,"aside-ads-after",{},void 0,!0)]),"aside-bottom":I(()=>[L(i.$slots,"aside-bottom",{},void 0,!0)]),_:3}))],2))}});const Qh=O(Yh,[["__scopeId","data-v-52ef3f18"]]),Xh={class:"container"},Zh=["innerHTML"],e0=["innerHTML"],t0=B({__name:"VPFooter",setup(e){const{theme:t}=ue(),{hasSidebar:n}=et();return(s,o)=>y(t).footer?(d(),m("footer",{key:0,class:pe(["VPFooter",{"has-sidebar":y(n)}])},[g("div",Xh,[y(t).footer.message?(d(),m("p",{key:0,class:"message",innerHTML:y(t).footer.message},null,8,Zh)):K("",!0),y(t).footer.copyright?(d(),m("p",{key:1,class:"copyright",innerHTML:y(t).footer.copyright},null,8,e0)):K("",!0)])],2)):K("",!0)}});const n0=O(t0,[["__scopeId","data-v-a36b15be"]]),s0={key:0,class:"Layout"},o0=B({__name:"Layout",setup(e){const{isOpen:t,open:n,close:s}=et(),o=gt();Xe(()=>o.path,s),ja(t,s),ns("close-sidebar",s);const{frontmatter:i}=ue();return(r,l)=>{const c=Lt("Content");return y(i).layout!==!1?(d(),m("div",s0,[L(r.$slots,"layout-top",{},void 0,!0),T(Ka),T(Wa,{class:"backdrop",show:y(t),onClick:y(s)},null,8,["show","onClick"]),T(Qd,null,{"nav-bar-title-before":I(()=>[L(r.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":I(()=>[L(r.$slots,"nav-bar-title-after",{},void 0,!0)]),"nav-bar-content-before":I(()=>[L(r.$slots,"nav-bar-content-before",{},void 0,!0)]),"nav-bar-content-after":I(()=>[L(r.$slots,"nav-bar-content-after",{},void 0,!0)]),"nav-screen-content-before":I(()=>[L(r.$slots,"nav-screen-content-before",{},void 0,!0)]),"nav-screen-content-after":I(()=>[L(r.$slots,"nav-screen-content-after",{},void 0,!0)]),_:3}),T(dp,{open:y(t),onOpenMenu:y(n)},null,8,["open","onOpenMenu"]),T(Fp,{open:y(t)},{"sidebar-nav-before":I(()=>[L(r.$slots,"sidebar-nav-before",{},void 0,!0)]),"sidebar-nav-after":I(()=>[L(r.$slots,"sidebar-nav-after",{},void 0,!0)]),_:3},8,["open"]),T(Qh,null,{"home-hero-before":I(()=>[L(r.$slots,"home-hero-before",{},void 0,!0)]),"home-hero-after":I(()=>[L(r.$slots,"home-hero-after",{},void 0,!0)]),"home-features-before":I(()=>[L(r.$slots,"home-features-before",{},void 0,!0)]),"home-features-after":I(()=>[L(r.$slots,"home-features-after",{},void 0,!0)]),"doc-footer-before":I(()=>[L(r.$slots,"doc-footer-before",{},void 0,!0)]),"doc-before":I(()=>[L(r.$slots,"doc-before",{},void 0,!0)]),"doc-after":I(()=>[L(r.$slots,"doc-after",{},void 0,!0)]),"aside-top":I(()=>[L(r.$slots,"aside-top",{},void 0,!0)]),"aside-bottom":I(()=>[L(r.$slots,"aside-bottom",{},void 0,!0)]),"aside-outline-before":I(()=>[L(r.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":I(()=>[L(r.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":I(()=>[L(r.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":I(()=>[L(r.$slots,"aside-ads-after",{},void 0,!0)]),_:3}),T(n0),L(r.$slots,"layout-bottom",{},void 0,!0)])):(d(),X(c,{key:1}))}}});const i0=O(o0,[["__scopeId","data-v-f4ae6121"]]),cs=e=>(Ge("data-v-6177b5ce"),e=e(),qe(),e),r0={class:"NotFound"},l0=cs(()=>g("p",{class:"code"},"404",-1)),c0=cs(()=>g("h1",{class:"title"},"PAGE NOT FOUND",-1)),a0=cs(()=>g("div",{class:"divider"},null,-1)),u0=cs(()=>g("blockquote",{class:"quote"}," But if you don't change your direction, and if you keep looking, you may end up where you are heading. ",-1)),f0={class:"action"},d0=["href"],p0=B({__name:"NotFound",setup(e){const{site:t}=ue();return(n,s)=>(d(),m("div",r0,[l0,c0,a0,u0,g("div",f0,[g("a",{class:"link",href:y(t).base,"aria-label":"go to home"}," Take me home ",8,d0)])]))}});const _0=O(p0,[["__scopeId","data-v-6177b5ce"]]);const h0={Layout:i0,NotFound:_0,enhanceApp:({app:e})=>{e.component("Badge",ya)}};const jt={...h0};function v0(e,t){let n=[],s=!0;const o=i=>{if(s){s=!1;return}n.forEach(r=>document.head.removeChild(r)),n=[],i.forEach(r=>{const l=m0(r);document.head.appendChild(l),n.push(l)})};Kt(()=>{const i=e.data,r=t.value,l=i&&i.description,c=i&&i.frontmatter.head||[];document.title=wr(r,i),document.querySelector("meta[name=description]").setAttribute("content",l||r.description),o(Va(r.head,y0(c)))})}function m0([e,t,n]){const s=document.createElement(e);for(const o in t)s.setAttribute(o,t[o]);return n&&(s.innerHTML=n),s}function g0(e){return e[0]==="meta"&&e[1]&&e[1].name==="description"}function y0(e){return e.filter(t=>!g0(t))}const $s=new Set,Hr=()=>document.createElement("link"),b0=e=>{const t=Hr();t.rel="prefetch",t.href=e,document.head.appendChild(t)},k0=e=>{const t=new XMLHttpRequest;t.open("GET",e,t.withCredentials=!0),t.send()};let Sn;const x0=Te&&(Sn=Hr())&&Sn.relList&&Sn.relList.supports&&Sn.relList.supports("prefetch")?b0:k0;function w0(){if(!Te||!window.IntersectionObserver)return;let e;if((e=navigator.connection)&&(e.saveData||/2g/.test(e.effectiveType)))return;const t=window.requestIdleCallback||setTimeout;let n=null;const s=()=>{n&&n.disconnect(),n=new IntersectionObserver(i=>{i.forEach(r=>{if(r.isIntersecting){const l=r.target;n.unobserve(l);const{pathname:c}=l;if(!$s.has(c)){$s.add(c);const f=$r(c);x0(f)}}})}),t(()=>{document.querySelectorAll("#app a").forEach(i=>{const{target:r,hostname:l,pathname:c}=i,f=c.match(/\.\w+$/);f&&f[0]!==".html"||r!=="_blank"&&l===location.hostname&&(c!==location.pathname?n.observe(i):$s.add(c))})})};De(s);const o=gt();Xe(()=>o.path,s),mt(()=>{n&&n.disconnect()})}const $0=B({setup(e,{slots:t}){const n=_e(!1);return De(()=>{n.value=!0}),()=>n.value&&t.default?t.default():null}});function P0(){if(Te){const e=new Map;window.addEventListener("click",t=>{var s;const n=t.target;if(n.matches('div[class*="language-"] > button.copy')){const o=n.parentElement,i=(s=n.nextElementSibling)==null?void 0:s.nextElementSibling;if(!o||!i)return;const r=/language-(shellscript|shell|bash|sh|zsh)/.test(o.className);let l="";i.querySelectorAll("span.line:not(.diff.remove)").forEach(c=>l+=(c.textContent||"")+`
+`),l=l.slice(0,-1),r&&(l=l.replace(/^ *(\$|>) /gm,"").trim()),S0(l).then(()=>{n.classList.add("copied"),clearTimeout(e.get(n));const c=setTimeout(()=>{n.classList.remove("copied"),n.blur(),e.delete(n)},2e3);e.set(n,c)})}})}}async function S0(e){try{return navigator.clipboard.writeText(e)}catch{const t=document.createElement("textarea"),n=document.activeElement;t.value=e,t.setAttribute("readonly",""),t.style.contain="strict",t.style.position="absolute",t.style.left="-9999px",t.style.fontSize="12pt";const s=document.getSelection(),o=s?s.rangeCount>0&&s.getRangeAt(0):null;document.body.appendChild(t),t.select(),t.selectionStart=0,t.selectionEnd=e.length,document.execCommand("copy"),document.body.removeChild(t),o&&(s.removeAllRanges(),s.addRange(o)),n&&n.focus()}}function C0(){Te&&window.addEventListener("click",e=>{var n,s;const t=e.target;if(t.matches(".vp-code-group input")){const o=(n=t.parentElement)==null?void 0:n.parentElement,i=Array.from((o==null?void 0:o.querySelectorAll("input"))||[]).indexOf(t),r=o==null?void 0:o.querySelector('div[class*="language-"].active'),l=(s=o==null?void 0:o.querySelectorAll('div[class*="language-"]'))==null?void 0:s[i];r&&l&&r!==l&&(r.classList.remove("active"),l.classList.add("active"))}})}const Fr=jt.NotFound||(()=>"404 Not Found"),V0=B({name:"VitePressApp",setup(){const{site:e}=ue();return De(()=>{Xe(()=>e.value.lang,t=>{document.documentElement.lang=t},{immediate:!0})}),w0(),P0(),C0(),jt.setup&&jt.setup(),()=>Hn(jt.Layout)}});function T0(){const e=E0(),t=L0();t.provide(Sr,e);const n=Ma(e.route);return t.provide(Pr,n),t.provide("NotFound",Fr),t.component("Content",Oa),t.component("ClientOnly",$0),Object.defineProperty(t.config.globalProperties,"$frontmatter",{get(){return n.frontmatter.value}}),jt.enhanceApp&&jt.enhanceApp({app:t,router:e,siteData:qt}),{app:t,router:e,data:n}}function L0(){return da(V0)}function E0(){let e=Te,t;return Ia(n=>{let s=$r(n);return e&&(t=s),(e||t===s)&&(s=s.replace(/\.js$/,".lean.js")),Te&&(e=!1),ma(()=>import(s),[])},Fr)}if(Te){const{app:e,router:t,data:n}=T0();t.go().then(()=>{v0(t.route,n.site),e.mount("#app")})}export{O as _,$c as a,g as b,m as c,T0 as createApp,Le as d,d as o,fe as t};
diff --git a/assets/article_cms.md.c1044b71.js b/assets/article_cms.md.62e92ffa.js
similarity index 96%
rename from assets/article_cms.md.c1044b71.js
rename to assets/article_cms.md.62e92ffa.js
index ae545a20..1d9cd2de 100644
--- a/assets/article_cms.md.c1044b71.js
+++ b/assets/article_cms.md.62e92ffa.js
@@ -1 +1 @@
-import{_ as e,o as t,c as a,a as p}from"./app.679ab08c.js";const l=JSON.parse('{"title":"一站式-后台前端解决方案调研","description":"","frontmatter":{},"headers":[],"relativePath":"article/cms.md","lastUpdated":1710671959000}'),i={name:"article/cms.md"},n=p('

一站式-后台前端解决方案调研

TIP

搜索方式:github 搜名称

Hooks-Admin

react-admin

vue-element-admin

Antd Pro Vue - 背靠阿里,代码过硬,大型项目首选

Vue vben admin - 宝藏后台管理 基于 Vue3 UI清新 功能扎实

Naive Ui admin 适合小项目

vue3-antd-admin

gin-vue-admin

vue-pure-admin

vue3-composition-admin

vue-admin-perfect

gin-vue-admin

vue-vben-admin

Geeker-Admin

soybean-admin

vue-admin-box

vue-next-admin

vue-admin-better

v3-admin-vite

vue-manage-system

vue3-admin-plus

https://github.com/RainManGO/vue3-composition-admin

https://github.com/jzfai/vue3-admin-plus

https://github.com/tobe-fe-dalao/fast-vue3

https://github.com/ibwei/vue3-ts-base

https://github.com/jzfai/vue3-admin-ts

https://github.com/zouzhibin/vue-admin-perfect

',29),r=[n];function o(s,m,u,d,c,h){return t(),a("div",null,r)}const v=e(i,[["render",o]]);export{l as __pageData,v as default}; +import{_ as e,o as t,c as a,a as p}from"./app.815d1813.js";const l=JSON.parse('{"title":"一站式-后台前端解决方案调研","description":"","frontmatter":{},"headers":[],"relativePath":"article/cms.md","lastUpdated":1710671959000}'),i={name:"article/cms.md"},n=p('

一站式-后台前端解决方案调研

TIP

搜索方式:github 搜名称

Hooks-Admin

react-admin

vue-element-admin

Antd Pro Vue - 背靠阿里,代码过硬,大型项目首选

Vue vben admin - 宝藏后台管理 基于 Vue3 UI清新 功能扎实

Naive Ui admin 适合小项目

vue3-antd-admin

gin-vue-admin

vue-pure-admin

vue3-composition-admin

vue-admin-perfect

gin-vue-admin

vue-vben-admin

Geeker-Admin

soybean-admin

vue-admin-box

vue-next-admin

vue-admin-better

v3-admin-vite

vue-manage-system

vue3-admin-plus

https://github.com/RainManGO/vue3-composition-admin

https://github.com/jzfai/vue3-admin-plus

https://github.com/tobe-fe-dalao/fast-vue3

https://github.com/ibwei/vue3-ts-base

https://github.com/jzfai/vue3-admin-ts

https://github.com/zouzhibin/vue-admin-perfect

',29),r=[n];function o(s,m,u,d,c,h){return t(),a("div",null,r)}const v=e(i,[["render",o]]);export{l as __pageData,v as default}; diff --git a/assets/article_cms.md.c1044b71.lean.js b/assets/article_cms.md.62e92ffa.lean.js similarity index 84% rename from assets/article_cms.md.c1044b71.lean.js rename to assets/article_cms.md.62e92ffa.lean.js index 64aff900..e98fa80f 100644 --- a/assets/article_cms.md.c1044b71.lean.js +++ b/assets/article_cms.md.62e92ffa.lean.js @@ -1 +1 @@ -import{_ as e,o as t,c as a,a as p}from"./app.679ab08c.js";const l=JSON.parse('{"title":"一站式-后台前端解决方案调研","description":"","frontmatter":{},"headers":[],"relativePath":"article/cms.md","lastUpdated":1710671959000}'),i={name:"article/cms.md"},n=p("",29),r=[n];function o(s,m,u,d,c,h){return t(),a("div",null,r)}const v=e(i,[["render",o]]);export{l as __pageData,v as default}; +import{_ as e,o as t,c as a,a as p}from"./app.815d1813.js";const l=JSON.parse('{"title":"一站式-后台前端解决方案调研","description":"","frontmatter":{},"headers":[],"relativePath":"article/cms.md","lastUpdated":1710671959000}'),i={name:"article/cms.md"},n=p("",29),r=[n];function o(s,m,u,d,c,h){return t(),a("div",null,r)}const v=e(i,[["render",o]]);export{l as __pageData,v as default}; diff --git a/assets/fe-utils_git.md.1d44e18f.js b/assets/fe-utils_git.md.6864052b.js similarity index 99% rename from assets/fe-utils_git.md.1d44e18f.js rename to assets/fe-utils_git.md.6864052b.js index 4f2f0433..ad160a97 100644 --- a/assets/fe-utils_git.md.1d44e18f.js +++ b/assets/fe-utils_git.md.6864052b.js @@ -1,4 +1,4 @@ -import{_ as s,o as a,c as n,a as l}from"./app.679ab08c.js";const b=JSON.parse('{"title":"常用命令","description":"","frontmatter":{},"headers":[{"level":2,"title":"git merge 和 git rebase","slug":"git-merge-和-git-rebase","link":"#git-merge-和-git-rebase","children":[]},{"level":2,"title":"git pull 和 git fetch","slug":"git-pull-和-git-fetch","link":"#git-pull-和-git-fetch","children":[]},{"level":2,"title":"创建 SSH Key","slug":"创建-ssh-key","link":"#创建-ssh-key","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":[]},{"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":"忽略文件配置(.gitignore)","slug":"忽略文件配置-gitignore","link":"#忽略文件配置-gitignore","children":[]},{"level":2,"title":"参考资料","slug":"参考资料","link":"#参考资料","children":[]}],"relativePath":"fe-utils/git.md","lastUpdated":1707646177000}'),p={name:"fe-utils/git.md"},e=l(`

常用命令

git merge 和 git rebase

git pull 和 git fetch

创建 SSH Key

shell
$ ssh-keygen -t rsa -C "youremail@example.com"
+import{_ as s,o as a,c as n,a as l}from"./app.815d1813.js";const b=JSON.parse('{"title":"常用命令","description":"","frontmatter":{},"headers":[{"level":2,"title":"git merge 和 git rebase","slug":"git-merge-和-git-rebase","link":"#git-merge-和-git-rebase","children":[]},{"level":2,"title":"git pull 和 git fetch","slug":"git-pull-和-git-fetch","link":"#git-pull-和-git-fetch","children":[]},{"level":2,"title":"创建 SSH Key","slug":"创建-ssh-key","link":"#创建-ssh-key","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":[]},{"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":"忽略文件配置(.gitignore)","slug":"忽略文件配置-gitignore","link":"#忽略文件配置-gitignore","children":[]},{"level":2,"title":"参考资料","slug":"参考资料","link":"#参考资料","children":[]}],"relativePath":"fe-utils/git.md","lastUpdated":1707646177000}'),p={name:"fe-utils/git.md"},e=l(`

常用命令

git merge 和 git rebase

git pull 和 git fetch

创建 SSH Key

shell
$ ssh-keygen -t rsa -C "youremail@example.com"
 
$ ssh-keygen -t rsa -C "youremail@example.com"
 

测试 key 是否配置成功

shell
$ ssh -T git@gitee.com
 
$ ssh -T git@gitee.com
diff --git a/assets/fe-utils_git.md.1d44e18f.lean.js b/assets/fe-utils_git.md.6864052b.lean.js
similarity index 96%
rename from assets/fe-utils_git.md.1d44e18f.lean.js
rename to assets/fe-utils_git.md.6864052b.lean.js
index 8389e9c7..8767ce75 100644
--- a/assets/fe-utils_git.md.1d44e18f.lean.js
+++ b/assets/fe-utils_git.md.6864052b.lean.js
@@ -1 +1 @@
-import{_ as s,o as a,c as n,a as l}from"./app.679ab08c.js";const b=JSON.parse('{"title":"常用命令","description":"","frontmatter":{},"headers":[{"level":2,"title":"git merge 和 git rebase","slug":"git-merge-和-git-rebase","link":"#git-merge-和-git-rebase","children":[]},{"level":2,"title":"git pull 和 git fetch","slug":"git-pull-和-git-fetch","link":"#git-pull-和-git-fetch","children":[]},{"level":2,"title":"创建 SSH Key","slug":"创建-ssh-key","link":"#创建-ssh-key","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":[]},{"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":"忽略文件配置(.gitignore)","slug":"忽略文件配置-gitignore","link":"#忽略文件配置-gitignore","children":[]},{"level":2,"title":"参考资料","slug":"参考资料","link":"#参考资料","children":[]}],"relativePath":"fe-utils/git.md","lastUpdated":1707646177000}'),p={name:"fe-utils/git.md"},e=l("",193),o=[e];function c(r,t,i,d,B,y){return a(),n("div",null,o)}const h=s(p,[["render",c]]);export{b as __pageData,h as default};
+import{_ as s,o as a,c as n,a as l}from"./app.815d1813.js";const b=JSON.parse('{"title":"常用命令","description":"","frontmatter":{},"headers":[{"level":2,"title":"git merge 和 git rebase","slug":"git-merge-和-git-rebase","link":"#git-merge-和-git-rebase","children":[]},{"level":2,"title":"git pull 和 git fetch","slug":"git-pull-和-git-fetch","link":"#git-pull-和-git-fetch","children":[]},{"level":2,"title":"创建 SSH Key","slug":"创建-ssh-key","link":"#创建-ssh-key","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":[]},{"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":"忽略文件配置(.gitignore)","slug":"忽略文件配置-gitignore","link":"#忽略文件配置-gitignore","children":[]},{"level":2,"title":"参考资料","slug":"参考资料","link":"#参考资料","children":[]}],"relativePath":"fe-utils/git.md","lastUpdated":1707646177000}'),p={name:"fe-utils/git.md"},e=l("",193),o=[e];function c(r,t,i,d,B,y){return a(),n("div",null,o)}const h=s(p,[["render",c]]);export{b as __pageData,h as default};
diff --git "a/assets/fe-utils_js\345\267\245\345\205\267\345\272\223.md.505fefb0.js" "b/assets/fe-utils_js\345\267\245\345\205\267\345\272\223.md.7df3c3c8.js"
similarity index 99%
rename from "assets/fe-utils_js\345\267\245\345\205\267\345\272\223.md.505fefb0.js"
rename to "assets/fe-utils_js\345\267\245\345\205\267\345\272\223.md.7df3c3c8.js"
index d2f250de..87692dc6 100644
--- "a/assets/fe-utils_js\345\267\245\345\205\267\345\272\223.md.505fefb0.js"
+++ "b/assets/fe-utils_js\345\267\245\345\205\267\345\272\223.md.7df3c3c8.js"
@@ -1,4 +1,4 @@
-import{_ as s,o as a,c as n,a as l}from"./app.679ab08c.js";const p="/blog/assets/2023-01-06-18-09-43.38e75eb4.png",h=JSON.parse('{"title":"前端 JavaScript 必会工具库合集","description":"","frontmatter":{},"headers":[{"level":2,"title":"工具库集合","slug":"工具库集合","link":"#工具库集合","children":[{"level":3,"title":"jQuery: 让操作DOM变得更容易","slug":"jquery-让操作dom变得更容易","link":"#jquery-让操作dom变得更容易","children":[]},{"level":3,"title":"Lodash: 你能想到的工具函数它都帮你写了","slug":"lodash-你能想到的工具函数它都帮你写了","link":"#lodash-你能想到的工具函数它都帮你写了","children":[]},{"level":3,"title":"Animate.css: 常见的CSS动画效果都帮你写好了","slug":"animate-css-常见的css动画效果都帮你写好了","link":"#animate-css-常见的css动画效果都帮你写好了","children":[]},{"level":3,"title":"Axios:让网络请求变得更简单","slug":"axios-让网络请求变得更简单","link":"#axios-让网络请求变得更简单","children":[]},{"level":3,"title":"MockJS:ajax拦截和模拟数据生成","slug":"mockjs-ajax拦截和模拟数据生成","link":"#mockjs-ajax拦截和模拟数据生成","children":[]},{"level":3,"title":"Moment:让日期处理更容易","slug":"moment-让日期处理更容易","link":"#moment-让日期处理更容易","children":[]},{"level":3,"title":"ECharts:搞定所有你能想到的图表📈","slug":"echarts-搞定所有你能想到的图表📈","link":"#echarts-搞定所有你能想到的图表📈","children":[]},{"level":3,"title":"animejs:简单好用的JS动画库","slug":"animejs-简单好用的js动画库","link":"#animejs-简单好用的js动画库","children":[]},{"level":3,"title":"editormd:markdown编辑器","slug":"editormd-markdown编辑器","link":"#editormd-markdown编辑器","children":[]},{"level":3,"title":"validate:简单好用的JS对象验证库","slug":"validate-简单好用的js对象验证库","link":"#validate-简单好用的js对象验证库","children":[]},{"level":3,"title":"date-fns:功能和Moment几乎相同","slug":"date-fns-功能和moment几乎相同","link":"#date-fns-功能和moment几乎相同","children":[]},{"level":3,"title":"zepto:功能和jQuery几乎相同","slug":"zepto-功能和jquery几乎相同","link":"#zepto-功能和jquery几乎相同","children":[]},{"level":3,"title":"nprogress:简单好用的进度条插件YouTube就使用的是它","slug":"nprogress-简单好用的进度条插件youtube就使用的是它","link":"#nprogress-简单好用的进度条插件youtube就使用的是它","children":[]},{"level":3,"title":"qs:一个用于解析url的小工具","slug":"qs-一个用于解析url的小工具","link":"#qs-一个用于解析url的小工具","children":[]}]},{"level":2,"title":"使用方式","slug":"使用方式","link":"#使用方式","children":[{"level":3,"title":"对于第三方库,除了下载使用,还可以通过CDN在线使用","slug":"对于第三方库-除了下载使用-还可以通过cdn在线使用","link":"#对于第三方库-除了下载使用-还可以通过cdn在线使用","children":[]},{"level":3,"title":"JQuery","slug":"jquery","link":"#jquery","children":[]},{"level":3,"title":"Lodash","slug":"lodash","link":"#lodash","children":[]},{"level":3,"title":"Animate.css","slug":"animate-css","link":"#animate-css","children":[]},{"level":3,"title":"Axios","slug":"axios","link":"#axios","children":[]},{"level":3,"title":"MockJS","slug":"mockjs","link":"#mockjs","children":[]},{"level":3,"title":"Moment","slug":"moment","link":"#moment","children":[]}]}],"relativePath":"fe-utils/js工具库.md","lastUpdated":1673001921000}'),e={name:"fe-utils/js工具库.md"},o=l('

前端 JavaScript 必会工具库合集

工具库集合

jQuery: 让操作DOM变得更容易

官网:https://jquery.com/

中文网:https://jquery.cuishifeng.cn/

Lodash: 你能想到的工具函数它都帮你写了

官网:https://lodash.com/docs

中文网:https://www.lodashjs.com/

Animate.css: 常见的CSS动画效果都帮你写好了

官网:https://animate.style/

Axios:让网络请求变得更简单

官网:https://axios-http.com/zh/

MockJS:ajax拦截和模拟数据生成

官网:http://mockjs.com/

Moment:让日期处理更容易

官网:https://momentjs.com/

中文网:http://momentjs.cn/

ECharts:搞定所有你能想到的图表📈

官网:https://echarts.apache.org/zh

animejs:简单好用的JS动画库

官网:https://animejs.com/

editormd:markdown编辑器

官网:https://pandao.github.io/editor.md | |

validate:简单好用的JS对象验证库

官网:http://validatejs.org/

date-fns:功能和Moment几乎相同

官网:https://date-fns.org/

支持tree shaking

zepto:功能和jQuery几乎相同

官网:https://zeptojs.com/

对移动端支持更好,包体积更小

nprogress:简单好用的进度条插件YouTube就使用的是它

官网:https://github.com/rstacruz/nprogress

qs:一个用于解析url的小工具

官网:https://github.com/ljharb/qs

使用方式

对于第三方库,除了下载使用,还可以通过CDN在线使用

科普知识:CDN

CDN称之为内容分发网络(Content Delivery Network)。

简单来说,就是提供很多的服务器,用户访问时,自动就近选择服务器给用户提供资源

国内使用广泛的免费CDN站点:https://www.bootcdn.cn/

JQuery

官网:https://jquery.com/

中文网:https://jquery.cuishifeng.cn/

CDN:https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js

针对DOM的操作无非以下几种:

  • 获取它
  • 创建它
  • 监听它
  • 改变它

JQuery可以让上面整个过程更加轻松

jQuery函数

jQuery提供了一个函数,名称为jQuery,也可以写作$

该函数提供了强大的DOM控制能力

通过下面的示例,可以快速理解jQuery的核心功能

javascript
// 获取类样式为container的所有DOM
+import{_ as s,o as a,c as n,a as l}from"./app.815d1813.js";const p="/blog/assets/2023-01-06-18-09-43.38e75eb4.png",h=JSON.parse('{"title":"前端 JavaScript 必会工具库合集","description":"","frontmatter":{},"headers":[{"level":2,"title":"工具库集合","slug":"工具库集合","link":"#工具库集合","children":[{"level":3,"title":"jQuery: 让操作DOM变得更容易","slug":"jquery-让操作dom变得更容易","link":"#jquery-让操作dom变得更容易","children":[]},{"level":3,"title":"Lodash: 你能想到的工具函数它都帮你写了","slug":"lodash-你能想到的工具函数它都帮你写了","link":"#lodash-你能想到的工具函数它都帮你写了","children":[]},{"level":3,"title":"Animate.css: 常见的CSS动画效果都帮你写好了","slug":"animate-css-常见的css动画效果都帮你写好了","link":"#animate-css-常见的css动画效果都帮你写好了","children":[]},{"level":3,"title":"Axios:让网络请求变得更简单","slug":"axios-让网络请求变得更简单","link":"#axios-让网络请求变得更简单","children":[]},{"level":3,"title":"MockJS:ajax拦截和模拟数据生成","slug":"mockjs-ajax拦截和模拟数据生成","link":"#mockjs-ajax拦截和模拟数据生成","children":[]},{"level":3,"title":"Moment:让日期处理更容易","slug":"moment-让日期处理更容易","link":"#moment-让日期处理更容易","children":[]},{"level":3,"title":"ECharts:搞定所有你能想到的图表📈","slug":"echarts-搞定所有你能想到的图表📈","link":"#echarts-搞定所有你能想到的图表📈","children":[]},{"level":3,"title":"animejs:简单好用的JS动画库","slug":"animejs-简单好用的js动画库","link":"#animejs-简单好用的js动画库","children":[]},{"level":3,"title":"editormd:markdown编辑器","slug":"editormd-markdown编辑器","link":"#editormd-markdown编辑器","children":[]},{"level":3,"title":"validate:简单好用的JS对象验证库","slug":"validate-简单好用的js对象验证库","link":"#validate-简单好用的js对象验证库","children":[]},{"level":3,"title":"date-fns:功能和Moment几乎相同","slug":"date-fns-功能和moment几乎相同","link":"#date-fns-功能和moment几乎相同","children":[]},{"level":3,"title":"zepto:功能和jQuery几乎相同","slug":"zepto-功能和jquery几乎相同","link":"#zepto-功能和jquery几乎相同","children":[]},{"level":3,"title":"nprogress:简单好用的进度条插件YouTube就使用的是它","slug":"nprogress-简单好用的进度条插件youtube就使用的是它","link":"#nprogress-简单好用的进度条插件youtube就使用的是它","children":[]},{"level":3,"title":"qs:一个用于解析url的小工具","slug":"qs-一个用于解析url的小工具","link":"#qs-一个用于解析url的小工具","children":[]}]},{"level":2,"title":"使用方式","slug":"使用方式","link":"#使用方式","children":[{"level":3,"title":"对于第三方库,除了下载使用,还可以通过CDN在线使用","slug":"对于第三方库-除了下载使用-还可以通过cdn在线使用","link":"#对于第三方库-除了下载使用-还可以通过cdn在线使用","children":[]},{"level":3,"title":"JQuery","slug":"jquery","link":"#jquery","children":[]},{"level":3,"title":"Lodash","slug":"lodash","link":"#lodash","children":[]},{"level":3,"title":"Animate.css","slug":"animate-css","link":"#animate-css","children":[]},{"level":3,"title":"Axios","slug":"axios","link":"#axios","children":[]},{"level":3,"title":"MockJS","slug":"mockjs","link":"#mockjs","children":[]},{"level":3,"title":"Moment","slug":"moment","link":"#moment","children":[]}]}],"relativePath":"fe-utils/js工具库.md","lastUpdated":1673001921000}'),e={name:"fe-utils/js工具库.md"},o=l('

前端 JavaScript 必会工具库合集

工具库集合

jQuery: 让操作DOM变得更容易

官网:https://jquery.com/

中文网:https://jquery.cuishifeng.cn/

Lodash: 你能想到的工具函数它都帮你写了

官网:https://lodash.com/docs

中文网:https://www.lodashjs.com/

Animate.css: 常见的CSS动画效果都帮你写好了

官网:https://animate.style/

Axios:让网络请求变得更简单

官网:https://axios-http.com/zh/

MockJS:ajax拦截和模拟数据生成

官网:http://mockjs.com/

Moment:让日期处理更容易

官网:https://momentjs.com/

中文网:http://momentjs.cn/

ECharts:搞定所有你能想到的图表📈

官网:https://echarts.apache.org/zh

animejs:简单好用的JS动画库

官网:https://animejs.com/

editormd:markdown编辑器

官网:https://pandao.github.io/editor.md | |

validate:简单好用的JS对象验证库

官网:http://validatejs.org/

date-fns:功能和Moment几乎相同

官网:https://date-fns.org/

支持tree shaking

zepto:功能和jQuery几乎相同

官网:https://zeptojs.com/

对移动端支持更好,包体积更小

nprogress:简单好用的进度条插件YouTube就使用的是它

官网:https://github.com/rstacruz/nprogress

qs:一个用于解析url的小工具

官网:https://github.com/ljharb/qs

使用方式

对于第三方库,除了下载使用,还可以通过CDN在线使用

科普知识:CDN

CDN称之为内容分发网络(Content Delivery Network)。

简单来说,就是提供很多的服务器,用户访问时,自动就近选择服务器给用户提供资源

国内使用广泛的免费CDN站点:https://www.bootcdn.cn/

JQuery

官网:https://jquery.com/

中文网:https://jquery.cuishifeng.cn/

CDN:https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js

针对DOM的操作无非以下几种:

  • 获取它
  • 创建它
  • 监听它
  • 改变它

JQuery可以让上面整个过程更加轻松

jQuery函数

jQuery提供了一个函数,名称为jQuery,也可以写作$

该函数提供了强大的DOM控制能力

通过下面的示例,可以快速理解jQuery的核心功能

javascript
// 获取类样式为container的所有DOM
 const container = $(".container")
 
 // 获取container后面的兄弟元素,元素类样式必须包含list
diff --git "a/assets/fe-utils_js\345\267\245\345\205\267\345\272\223.md.505fefb0.lean.js" "b/assets/fe-utils_js\345\267\245\345\205\267\345\272\223.md.7df3c3c8.lean.js"
similarity index 98%
rename from "assets/fe-utils_js\345\267\245\345\205\267\345\272\223.md.505fefb0.lean.js"
rename to "assets/fe-utils_js\345\267\245\345\205\267\345\272\223.md.7df3c3c8.lean.js"
index 437957ec..75018c69 100644
--- "a/assets/fe-utils_js\345\267\245\345\205\267\345\272\223.md.505fefb0.lean.js"
+++ "b/assets/fe-utils_js\345\267\245\345\205\267\345\272\223.md.7df3c3c8.lean.js"
@@ -1 +1 @@
-import{_ as s,o as a,c as n,a as l}from"./app.679ab08c.js";const p="/blog/assets/2023-01-06-18-09-43.38e75eb4.png",h=JSON.parse('{"title":"前端 JavaScript 必会工具库合集","description":"","frontmatter":{},"headers":[{"level":2,"title":"工具库集合","slug":"工具库集合","link":"#工具库集合","children":[{"level":3,"title":"jQuery: 让操作DOM变得更容易","slug":"jquery-让操作dom变得更容易","link":"#jquery-让操作dom变得更容易","children":[]},{"level":3,"title":"Lodash: 你能想到的工具函数它都帮你写了","slug":"lodash-你能想到的工具函数它都帮你写了","link":"#lodash-你能想到的工具函数它都帮你写了","children":[]},{"level":3,"title":"Animate.css: 常见的CSS动画效果都帮你写好了","slug":"animate-css-常见的css动画效果都帮你写好了","link":"#animate-css-常见的css动画效果都帮你写好了","children":[]},{"level":3,"title":"Axios:让网络请求变得更简单","slug":"axios-让网络请求变得更简单","link":"#axios-让网络请求变得更简单","children":[]},{"level":3,"title":"MockJS:ajax拦截和模拟数据生成","slug":"mockjs-ajax拦截和模拟数据生成","link":"#mockjs-ajax拦截和模拟数据生成","children":[]},{"level":3,"title":"Moment:让日期处理更容易","slug":"moment-让日期处理更容易","link":"#moment-让日期处理更容易","children":[]},{"level":3,"title":"ECharts:搞定所有你能想到的图表📈","slug":"echarts-搞定所有你能想到的图表📈","link":"#echarts-搞定所有你能想到的图表📈","children":[]},{"level":3,"title":"animejs:简单好用的JS动画库","slug":"animejs-简单好用的js动画库","link":"#animejs-简单好用的js动画库","children":[]},{"level":3,"title":"editormd:markdown编辑器","slug":"editormd-markdown编辑器","link":"#editormd-markdown编辑器","children":[]},{"level":3,"title":"validate:简单好用的JS对象验证库","slug":"validate-简单好用的js对象验证库","link":"#validate-简单好用的js对象验证库","children":[]},{"level":3,"title":"date-fns:功能和Moment几乎相同","slug":"date-fns-功能和moment几乎相同","link":"#date-fns-功能和moment几乎相同","children":[]},{"level":3,"title":"zepto:功能和jQuery几乎相同","slug":"zepto-功能和jquery几乎相同","link":"#zepto-功能和jquery几乎相同","children":[]},{"level":3,"title":"nprogress:简单好用的进度条插件YouTube就使用的是它","slug":"nprogress-简单好用的进度条插件youtube就使用的是它","link":"#nprogress-简单好用的进度条插件youtube就使用的是它","children":[]},{"level":3,"title":"qs:一个用于解析url的小工具","slug":"qs-一个用于解析url的小工具","link":"#qs-一个用于解析url的小工具","children":[]}]},{"level":2,"title":"使用方式","slug":"使用方式","link":"#使用方式","children":[{"level":3,"title":"对于第三方库,除了下载使用,还可以通过CDN在线使用","slug":"对于第三方库-除了下载使用-还可以通过cdn在线使用","link":"#对于第三方库-除了下载使用-还可以通过cdn在线使用","children":[]},{"level":3,"title":"JQuery","slug":"jquery","link":"#jquery","children":[]},{"level":3,"title":"Lodash","slug":"lodash","link":"#lodash","children":[]},{"level":3,"title":"Animate.css","slug":"animate-css","link":"#animate-css","children":[]},{"level":3,"title":"Axios","slug":"axios","link":"#axios","children":[]},{"level":3,"title":"MockJS","slug":"mockjs","link":"#mockjs","children":[]},{"level":3,"title":"Moment","slug":"moment","link":"#moment","children":[]}]}],"relativePath":"fe-utils/js工具库.md","lastUpdated":1673001921000}'),e={name:"fe-utils/js工具库.md"},o=l("",152),t=[o];function r(c,i,B,y,d,F){return a(),n("div",null,t)}const m=s(e,[["render",r]]);export{h as __pageData,m as default};
+import{_ as s,o as a,c as n,a as l}from"./app.815d1813.js";const p="/blog/assets/2023-01-06-18-09-43.38e75eb4.png",h=JSON.parse('{"title":"前端 JavaScript 必会工具库合集","description":"","frontmatter":{},"headers":[{"level":2,"title":"工具库集合","slug":"工具库集合","link":"#工具库集合","children":[{"level":3,"title":"jQuery: 让操作DOM变得更容易","slug":"jquery-让操作dom变得更容易","link":"#jquery-让操作dom变得更容易","children":[]},{"level":3,"title":"Lodash: 你能想到的工具函数它都帮你写了","slug":"lodash-你能想到的工具函数它都帮你写了","link":"#lodash-你能想到的工具函数它都帮你写了","children":[]},{"level":3,"title":"Animate.css: 常见的CSS动画效果都帮你写好了","slug":"animate-css-常见的css动画效果都帮你写好了","link":"#animate-css-常见的css动画效果都帮你写好了","children":[]},{"level":3,"title":"Axios:让网络请求变得更简单","slug":"axios-让网络请求变得更简单","link":"#axios-让网络请求变得更简单","children":[]},{"level":3,"title":"MockJS:ajax拦截和模拟数据生成","slug":"mockjs-ajax拦截和模拟数据生成","link":"#mockjs-ajax拦截和模拟数据生成","children":[]},{"level":3,"title":"Moment:让日期处理更容易","slug":"moment-让日期处理更容易","link":"#moment-让日期处理更容易","children":[]},{"level":3,"title":"ECharts:搞定所有你能想到的图表📈","slug":"echarts-搞定所有你能想到的图表📈","link":"#echarts-搞定所有你能想到的图表📈","children":[]},{"level":3,"title":"animejs:简单好用的JS动画库","slug":"animejs-简单好用的js动画库","link":"#animejs-简单好用的js动画库","children":[]},{"level":3,"title":"editormd:markdown编辑器","slug":"editormd-markdown编辑器","link":"#editormd-markdown编辑器","children":[]},{"level":3,"title":"validate:简单好用的JS对象验证库","slug":"validate-简单好用的js对象验证库","link":"#validate-简单好用的js对象验证库","children":[]},{"level":3,"title":"date-fns:功能和Moment几乎相同","slug":"date-fns-功能和moment几乎相同","link":"#date-fns-功能和moment几乎相同","children":[]},{"level":3,"title":"zepto:功能和jQuery几乎相同","slug":"zepto-功能和jquery几乎相同","link":"#zepto-功能和jquery几乎相同","children":[]},{"level":3,"title":"nprogress:简单好用的进度条插件YouTube就使用的是它","slug":"nprogress-简单好用的进度条插件youtube就使用的是它","link":"#nprogress-简单好用的进度条插件youtube就使用的是它","children":[]},{"level":3,"title":"qs:一个用于解析url的小工具","slug":"qs-一个用于解析url的小工具","link":"#qs-一个用于解析url的小工具","children":[]}]},{"level":2,"title":"使用方式","slug":"使用方式","link":"#使用方式","children":[{"level":3,"title":"对于第三方库,除了下载使用,还可以通过CDN在线使用","slug":"对于第三方库-除了下载使用-还可以通过cdn在线使用","link":"#对于第三方库-除了下载使用-还可以通过cdn在线使用","children":[]},{"level":3,"title":"JQuery","slug":"jquery","link":"#jquery","children":[]},{"level":3,"title":"Lodash","slug":"lodash","link":"#lodash","children":[]},{"level":3,"title":"Animate.css","slug":"animate-css","link":"#animate-css","children":[]},{"level":3,"title":"Axios","slug":"axios","link":"#axios","children":[]},{"level":3,"title":"MockJS","slug":"mockjs","link":"#mockjs","children":[]},{"level":3,"title":"Moment","slug":"moment","link":"#moment","children":[]}]}],"relativePath":"fe-utils/js工具库.md","lastUpdated":1673001921000}'),e={name:"fe-utils/js工具库.md"},o=l("",152),t=[o];function r(c,i,B,y,d,F){return a(),n("div",null,t)}const m=s(e,[["render",r]]);export{h as __pageData,m as default};
diff --git a/assets/fe-utils_tool.md.91c28681.js b/assets/fe-utils_tool.md.3087f19e.js
similarity index 93%
rename from assets/fe-utils_tool.md.91c28681.js
rename to assets/fe-utils_tool.md.3087f19e.js
index bf85b883..5589e761 100644
--- a/assets/fe-utils_tool.md.91c28681.js
+++ b/assets/fe-utils_tool.md.3087f19e.js
@@ -1 +1 @@
-import{_ as e,o,c as a,b as t,d as s}from"./app.679ab08c.js";const M=JSON.parse('{"title":"涌现出来的新tools","description":"","frontmatter":{},"headers":[],"relativePath":"fe-utils/tool.md","lastUpdated":1710679304000}'),n={name:"fe-utils/tool.md"},i=t("h1",{id:"涌现出来的新tools",tabindex:"-1"},[s("涌现出来的新tools "),t("a",{class:"header-anchor",href:"#涌现出来的新tools","aria-hidden":"true"},"#")],-1),r=t("p",null,"apifox:一款国产的 API 管理神器",-1),c=t("p",null,"集成了 Postman + Swagger + Mock + JMeter 众多功能",-1),l=t("p",null,"可以让生成 api 文档、api调试、api Mock、api 自动化测试 变的十分高效,简单。",-1),d=t("p",null,"eolink | 国内第一个集Swagger+Postman+Mock+Jmeter单点工具于一身的 api 管理平台 | 以 api 为中心的前后端开发流程",-1),p=[i,r,c,l,d];function _(h,u,f,m,k,x){return o(),a("div",null,p)}const P=e(n,[["render",_]]);export{M as __pageData,P as default};
+import{_ as e,o,c as a,b as t,d as s}from"./app.815d1813.js";const M=JSON.parse('{"title":"涌现出来的新tools","description":"","frontmatter":{},"headers":[],"relativePath":"fe-utils/tool.md","lastUpdated":1710679304000}'),n={name:"fe-utils/tool.md"},i=t("h1",{id:"涌现出来的新tools",tabindex:"-1"},[s("涌现出来的新tools "),t("a",{class:"header-anchor",href:"#涌现出来的新tools","aria-hidden":"true"},"#")],-1),r=t("p",null,"apifox:一款国产的 API 管理神器",-1),c=t("p",null,"集成了 Postman + Swagger + Mock + JMeter 众多功能",-1),l=t("p",null,"可以让生成 api 文档、api调试、api Mock、api 自动化测试 变的十分高效,简单。",-1),d=t("p",null,"eolink | 国内第一个集Swagger+Postman+Mock+Jmeter单点工具于一身的 api 管理平台 | 以 api 为中心的前后端开发流程",-1),p=[i,r,c,l,d];function _(h,u,f,m,k,x){return o(),a("div",null,p)}const P=e(n,[["render",_]]);export{M as __pageData,P as default};
diff --git a/assets/fe-utils_tool.md.91c28681.lean.js b/assets/fe-utils_tool.md.3087f19e.lean.js
similarity index 93%
rename from assets/fe-utils_tool.md.91c28681.lean.js
rename to assets/fe-utils_tool.md.3087f19e.lean.js
index bf85b883..5589e761 100644
--- a/assets/fe-utils_tool.md.91c28681.lean.js
+++ b/assets/fe-utils_tool.md.3087f19e.lean.js
@@ -1 +1 @@
-import{_ as e,o,c as a,b as t,d as s}from"./app.679ab08c.js";const M=JSON.parse('{"title":"涌现出来的新tools","description":"","frontmatter":{},"headers":[],"relativePath":"fe-utils/tool.md","lastUpdated":1710679304000}'),n={name:"fe-utils/tool.md"},i=t("h1",{id:"涌现出来的新tools",tabindex:"-1"},[s("涌现出来的新tools "),t("a",{class:"header-anchor",href:"#涌现出来的新tools","aria-hidden":"true"},"#")],-1),r=t("p",null,"apifox:一款国产的 API 管理神器",-1),c=t("p",null,"集成了 Postman + Swagger + Mock + JMeter 众多功能",-1),l=t("p",null,"可以让生成 api 文档、api调试、api Mock、api 自动化测试 变的十分高效,简单。",-1),d=t("p",null,"eolink | 国内第一个集Swagger+Postman+Mock+Jmeter单点工具于一身的 api 管理平台 | 以 api 为中心的前后端开发流程",-1),p=[i,r,c,l,d];function _(h,u,f,m,k,x){return o(),a("div",null,p)}const P=e(n,[["render",_]]);export{M as __pageData,P as default};
+import{_ as e,o,c as a,b as t,d as s}from"./app.815d1813.js";const M=JSON.parse('{"title":"涌现出来的新tools","description":"","frontmatter":{},"headers":[],"relativePath":"fe-utils/tool.md","lastUpdated":1710679304000}'),n={name:"fe-utils/tool.md"},i=t("h1",{id:"涌现出来的新tools",tabindex:"-1"},[s("涌现出来的新tools "),t("a",{class:"header-anchor",href:"#涌现出来的新tools","aria-hidden":"true"},"#")],-1),r=t("p",null,"apifox:一款国产的 API 管理神器",-1),c=t("p",null,"集成了 Postman + Swagger + Mock + JMeter 众多功能",-1),l=t("p",null,"可以让生成 api 文档、api调试、api Mock、api 自动化测试 变的十分高效,简单。",-1),d=t("p",null,"eolink | 国内第一个集Swagger+Postman+Mock+Jmeter单点工具于一身的 api 管理平台 | 以 api 为中心的前后端开发流程",-1),p=[i,r,c,l,d];function _(h,u,f,m,k,x){return o(),a("div",null,p)}const P=e(n,[["render",_]]);export{M as __pageData,P as default};
diff --git a/assets/fragment_Monorepo.md.1135a208.js b/assets/fragment_Monorepo.md.1135a208.js
new file mode 100644
index 00000000..f3abe13d
--- /dev/null
+++ b/assets/fragment_Monorepo.md.1135a208.js
@@ -0,0 +1 @@
+import{_ as o,o as e,c as a,a as n}from"./app.815d1813.js";const l="/blog/assets/2024-04-09-20-41-59.c3e21810.png",u=JSON.parse('{"title":"monorepo","description":"","frontmatter":{},"headers":[{"level":2,"title":"npm 的 install 流程","slug":"npm-的-install-流程","link":"#npm-的-install-流程","children":[]},{"level":2,"title":"package-lock.json","slug":"package-lock-json","link":"#package-lock-json","children":[]},{"level":2,"title":"npm 和 yarn","slug":"npm-和-yarn","link":"#npm-和-yarn","children":[]},{"level":2,"title":"monorepo 方案的优势","slug":"monorepo-方案的优势","link":"#monorepo-方案的优势","children":[]},{"level":2,"title":"monorepo 方案的劣势","slug":"monorepo-方案的劣势","link":"#monorepo-方案的劣势","children":[]},{"level":2,"title":"如何取舍?","slug":"如何取舍","link":"#如何取舍","children":[]}],"relativePath":"fragment/Monorepo.md","lastUpdated":null}'),r={name:"fragment/Monorepo.md"},p=n('

monorepo

npm 的 install 流程

package-lock.json

package-lock.json 的作用是进行锁版本号,保证整个开发团队的版本号统一,使用 monorepo 的项目有可能会提到一个最外层进行一个管理。

  • 为什么需要这个 package-lock.json package.json 的 semantic versioning(语意化版本控制),在不同的时间会安装不同的版本,如果没有 package-lock.json,不同的开发者可能就会得到不同版本的依赖,如果因为这个出现了一个 bug 的话,那排查起来也许会非常困难。
  • 更新规则 Npm v 5.4.2 以上:当 package.json 声明的版本依赖规范和 package-lock.json 安装版本兼容,则根据 package-lock json 安装依赖:如果两者不兼容,那么按照 package.json 安装依赖,并更新 package- lock.json 跟随 package.json 的语意化版本控制来进行更新,如果 package.json 中的依赖 a 的版本是^1.0.0,在这个时候如果 package-lock.json 中的版本锁定为 1.12.1 就是符合要求的不必重写,但如果是 0.12.11 即第一个数字变了,那就需要重写 package-lock.json 了。
  • 版本规则
    • ^: 只会执行不更改最左边非零数字的更新。 如果写入的是 ^0.13.0,则当运行 npm update 时,可以更新到 0.13.1、0.13.2 等,但不能更新到 0.14.0 或更高版本。 如果写入的是 ^1.13.0,则当运行 npm update 时,可以更新到 1.13.1、1.14.0 等,但不能更新到 2.0.0 或更高版本。
    • ~: 如果写入的是 〜0.13.0,则当运行 npm update 时,会更新到补丁版本:即 0.13.1 可以,但 0.14.0 不可以。
    • : 接受高于指定版本的任何版本。

    • =: 接受等于或高于指定版本的任何版本。

    • =: 接受确切的版本。
    • -: 接受一定范围的版本。例如:2.1.0 - 2.6.2。
    • ||: 组合集合。例如 < 2.1 || > 2.6。

npm 和 yarn

缺点。下面将两者进行比较

  1. 性能

每当 Yarn 或 npm 需要安装包时,它们都会执行一系列任务。在 npm 中,这些任务是按包顺序执行的,这意味着它会等待一个包完全安装,然后再继续下一个。相比之下,Yarn 并行执行这些任务,从而提高了性能。 虽然这两个管理器都提供缓存机制,但 Yarn 似乎做得更好一些。 尽管 Yarn 有一些优势,但 Yarn 和 npm 在它们的最新版本中的速度相当。所以我们不能评判孰优孰劣。

  1. 依赖版本

早期的时候 yarn 有 yarn.lock 来锁定版本,这一点上比 package.json 要强很多,而后面 npm 也推出了 package-lock.json,所以这一点上已经没太多差异了。

  1. 安全性

从版本 6 开始,npm 会在安装过程中审核软件包并告诉您是否发现了任何漏洞。我们可以通过 npm audit 针对已安装的软件包运行来手动执行此检查。如果发现任何漏洞,npm 会给我们安全建议。 Yarn 和 npm 都使用加密哈希算法来确保包的完整性。

  1. 工作区

工作区允许您拥有一个 monorepo 来管理跨多个项目的依赖项。这意味着您有一个单一的顶级根包,其中包含多个称为工作区的子包。

  1. 用哪个?

目前 2021 年,yarn 的安装速度还是比 npm 快,其他地方的差异并不大,基本上可以忽略,用哪个都行。

monorepo

monorepo 方案的优势

  1. 代码重用将变得非常容易:由于所有的项目代码都集中于一个代码仓库,我们将很容易抽离出各个项目共用的业务组件或工具,并通过 TypeScript,Lerna 或其他工具进行代码内引用;
  2. 依赖管理将变得非常简单:同理,由于项目之间的引用路径内化在同一个仓库之中,我们很容易追踪当某个项目的代码修改后,会影响到其他哪些项目。通过使用一些工具,我们将很容易地做到版本依赖管理和版本号自动升级;
  3. 代码重构将变得非常便捷:想想究竟是什么在阻止您进行代码重构,很多时候,原因来自于「不确定性」,您不确定对某个项目的修改是否对于其他项目而言是「致命的」,出于对未知的恐惧,您会倾向于不重构代码,这将导致整个项目代码的腐烂度会以惊人的速度增长。而在 monorepo 策略的指导下,您能够明确知道您的代码的影响范围,并且能够对被影响的项目可以进行统一的测试,这会鼓励您不断优化代码;
  4. 它倡导了一种开放,透明,共享的组织文化,这有利于开发者成长,代码质量的提升:在 monorepo 策略下,每个开发者都被鼓励去查看,修改他人的代码(只要有必要),同时,也会激起开发者维护代码,和编写单元测试的责任心(毕竟朋友来访之前,我们从不介意自己的房子究竟有多乱),这将会形成一种良性的技术氛围,从而保障整个组织的代码质量。

monorepo 方案的劣势

  1. 项目粒度的权限管理变得非常复杂:无论是 Git 还是其他 VCS 系统,在支持 monorepo 策略中项目粒度的权限管理上都没有令人满意的方案,这意味着 A 部门的 a 项目若是不想被 B 部门的开发者看到就很难了。(好在我们可以将 monorepo 策略实践在「项目级」这个层次上,这才是我们这篇文章的主题,我们后面会再次明确它);
  2. 新员工的学习成本变高:不同于一个项目一个代码仓库这种模式下,组织新人只要熟悉特定代码仓库下的代码逻辑,在 monorepo 策略下,新人可能不得不花更多精力来理清各个代码仓库之间的相互逻辑,当然这个成本可以通过新人文档的方式来解决,但维护文档的新鲜又需要消耗额外的人力;
  3. 对于公司级别的 monorepo 策略而言,需要专门的 VFS 系统,自动重构工具的支持:设想一下 Google 这样的企业是如何将十亿行的代码存储在一个仓库之中的?开发人员每次拉取代码需要等待多久?各个项目代码之间又如何实现权限管理,敏捷发布?任何简单的策略乘以足够的规模量级都会产生一个奇迹(不管是好是坏),对于中小企业而言,如果没有像 Google,Facebook 这样雄厚的人力资源,把所有项目代码放在同一个仓库里这个美好的愿望就只能是个空中楼阁。

如何取舍?

没错,软件开发领域从来没有「银弹」。monorepo 策略也并不完美,并且,我在实践中发现,要想完美在组织中运用 monorepo 策略,所需要的不仅是出色的编程技巧和耐心。团队日程,组织文化和个人影响力相互碰撞的最终结果才决定了想法最终是否能被实现。

但是请别灰心的太早,因为虽然让组织作出改变,统一施行 monorepo 策略困难重重,但这却并不意味着我们需要彻底跟 monorepo 策略说再见。我们还可以把 monorepo 策略实践在「项目」这个级别,即从逻辑上确定项目与项目之间的关联性,然后把相关联的项目整合在同一个仓库下,通常情况下,我们不会有太多相互关联的项目,这意味着我们能够免费得到 monorepo 策略的所有好处,并且可以拒绝支付大型 monorepo 架构的利息。

',26),i=[p];function t(c,s,m,d,h,k){return e(),a("div",null,i)}const _=o(r,[["render",t]]);export{u as __pageData,_ as default}; diff --git a/assets/fragment_Monorepo.md.1135a208.lean.js b/assets/fragment_Monorepo.md.1135a208.lean.js new file mode 100644 index 00000000..d969c589 --- /dev/null +++ b/assets/fragment_Monorepo.md.1135a208.lean.js @@ -0,0 +1 @@ +import{_ as o,o as e,c as a,a as n}from"./app.815d1813.js";const l="/blog/assets/2024-04-09-20-41-59.c3e21810.png",u=JSON.parse('{"title":"monorepo","description":"","frontmatter":{},"headers":[{"level":2,"title":"npm 的 install 流程","slug":"npm-的-install-流程","link":"#npm-的-install-流程","children":[]},{"level":2,"title":"package-lock.json","slug":"package-lock-json","link":"#package-lock-json","children":[]},{"level":2,"title":"npm 和 yarn","slug":"npm-和-yarn","link":"#npm-和-yarn","children":[]},{"level":2,"title":"monorepo 方案的优势","slug":"monorepo-方案的优势","link":"#monorepo-方案的优势","children":[]},{"level":2,"title":"monorepo 方案的劣势","slug":"monorepo-方案的劣势","link":"#monorepo-方案的劣势","children":[]},{"level":2,"title":"如何取舍?","slug":"如何取舍","link":"#如何取舍","children":[]}],"relativePath":"fragment/Monorepo.md","lastUpdated":null}'),r={name:"fragment/Monorepo.md"},p=n("",26),i=[p];function t(c,s,m,d,h,k){return e(),a("div",null,i)}const _=o(r,[["render",t]]);export{u as __pageData,_ as default}; diff --git a/assets/fragment_babel-console.md.9e4fa1c1.js b/assets/fragment_babel-console.md.9e4fa1c1.js new file mode 100644 index 00000000..4055eb02 --- /dev/null +++ b/assets/fragment_babel-console.md.9e4fa1c1.js @@ -0,0 +1 @@ +import{_ as a,o,c as t,b as e,d as s}from"./app.815d1813.js";const m=JSON.parse('{"title":"🔥 手撕 babel 插件-消灭 console!","description":"","frontmatter":{},"headers":[],"relativePath":"fragment/babel-console.md","lastUpdated":null}'),n={name:"fragment/babel-console.md"},c=e("h1",{id:"🔥-手撕-babel-插件-消灭-console",tabindex:"-1"},[s("🔥 手撕 babel 插件-消灭 console! "),e("a",{class:"header-anchor",href:"#🔥-手撕-babel-插件-消灭-console","aria-hidden":"true"},"#")],-1),r=[c];function l(d,b,i,_,p,f){return o(),t("div",null,r)}const u=a(n,[["render",l]]);export{m as __pageData,u as default}; diff --git a/assets/fragment_babel-console.md.9e4fa1c1.lean.js b/assets/fragment_babel-console.md.9e4fa1c1.lean.js new file mode 100644 index 00000000..4055eb02 --- /dev/null +++ b/assets/fragment_babel-console.md.9e4fa1c1.lean.js @@ -0,0 +1 @@ +import{_ as a,o,c as t,b as e,d as s}from"./app.815d1813.js";const m=JSON.parse('{"title":"🔥 手撕 babel 插件-消灭 console!","description":"","frontmatter":{},"headers":[],"relativePath":"fragment/babel-console.md","lastUpdated":null}'),n={name:"fragment/babel-console.md"},c=e("h1",{id:"🔥-手撕-babel-插件-消灭-console",tabindex:"-1"},[s("🔥 手撕 babel 插件-消灭 console! "),e("a",{class:"header-anchor",href:"#🔥-手撕-babel-插件-消灭-console","aria-hidden":"true"},"#")],-1),r=[c];function l(d,b,i,_,p,f){return o(),t("div",null,r)}const u=a(n,[["render",l]]);export{m as __pageData,u as default}; diff --git a/assets/fe-utils_setTimeout.md.1c93d132.js b/assets/fragment_setTimeout.md.9ceec1c9.js similarity index 99% rename from assets/fe-utils_setTimeout.md.1c93d132.js rename to assets/fragment_setTimeout.md.9ceec1c9.js index cf1b82be..48170eac 100644 --- a/assets/fe-utils_setTimeout.md.1c93d132.js +++ b/assets/fragment_setTimeout.md.9ceec1c9.js @@ -1,4 +1,4 @@ -import{_ as s,o as n,c as a,a as l}from"./app.679ab08c.js";const p="/blog/assets/2024-04-09-16-41-09.9e776372.png",o="/blog/assets/2024-04-09-16-41-29.01db7dfd.png",d=JSON.parse('{"title":"如何实现准时的 setTimeout","description":"","frontmatter":{},"headers":[{"level":2,"title":"为什么不准?","slug":"为什么不准","link":"#为什么不准","children":[]},{"level":2,"title":"如何实现准时的 “setTimeout”","slug":"如何实现准时的-settimeout-1","link":"#如何实现准时的-settimeout-1","children":[{"level":3,"title":"requestAnimationFrame","slug":"requestanimationframe","link":"#requestanimationframe","children":[]},{"level":3,"title":"while","slug":"while","link":"#while","children":[]},{"level":3,"title":"setTimeout 系统时间补偿","slug":"settimeout-系统时间补偿","link":"#settimeout-系统时间补偿","children":[]}]}],"relativePath":"fe-utils/setTimeout.md","lastUpdated":1712652445000}'),e={name:"fe-utils/setTimeout.md"},t=l(`

如何实现准时的 setTimeout

为什么不准?

因为 setTimeout 是一个宏任务,它的指定时间指的是: 进入主线程的时间。

js
setTimeout(callback, 进入主线程的时间);
+import{_ as s,o as n,c as a,a as l}from"./app.815d1813.js";const p="/blog/assets/2024-04-09-16-41-09.9e776372.png",o="/blog/assets/2024-04-09-16-41-29.01db7dfd.png",d=JSON.parse('{"title":"如何实现准时的 setTimeout","description":"","frontmatter":{},"headers":[{"level":2,"title":"为什么不准?","slug":"为什么不准","link":"#为什么不准","children":[]},{"level":2,"title":"如何实现准时的 “setTimeout”","slug":"如何实现准时的-settimeout-1","link":"#如何实现准时的-settimeout-1","children":[{"level":3,"title":"requestAnimationFrame","slug":"requestanimationframe","link":"#requestanimationframe","children":[]},{"level":3,"title":"while","slug":"while","link":"#while","children":[]},{"level":3,"title":"setTimeout 系统时间补偿","slug":"settimeout-系统时间补偿","link":"#settimeout-系统时间补偿","children":[]}]}],"relativePath":"fragment/setTimeout.md","lastUpdated":1712654359000}'),e={name:"fragment/setTimeout.md"},t=l(`

如何实现准时的 setTimeout

为什么不准?

因为 setTimeout 是一个宏任务,它的指定时间指的是: 进入主线程的时间。

js
setTimeout(callback, 进入主线程的时间);
 
setTimeout(callback, 进入主线程的时间);
 

所以什么时候可以执行 callback,需要看 主线程前面还有多少任务待执行 。

如何实现准时的 “setTimeout”

requestAnimationFrame

window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。

该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行,回调函数执行次数通常是每秒 60 次,也就是每 16.7ms 执行一次,但是并不一定保证为 16.7 ms。

js
// 模拟代码
 function setTimeout(cb, delay) {
diff --git a/assets/fe-utils_setTimeout.md.1c93d132.lean.js b/assets/fragment_setTimeout.md.9ceec1c9.lean.js
similarity index 77%
rename from assets/fe-utils_setTimeout.md.1c93d132.lean.js
rename to assets/fragment_setTimeout.md.9ceec1c9.lean.js
index e37bcebc..623fc164 100644
--- a/assets/fe-utils_setTimeout.md.1c93d132.lean.js
+++ b/assets/fragment_setTimeout.md.9ceec1c9.lean.js
@@ -1 +1 @@
-import{_ as s,o as n,c as a,a as l}from"./app.679ab08c.js";const p="/blog/assets/2024-04-09-16-41-09.9e776372.png",o="/blog/assets/2024-04-09-16-41-29.01db7dfd.png",d=JSON.parse('{"title":"如何实现准时的 setTimeout","description":"","frontmatter":{},"headers":[{"level":2,"title":"为什么不准?","slug":"为什么不准","link":"#为什么不准","children":[]},{"level":2,"title":"如何实现准时的 “setTimeout”","slug":"如何实现准时的-settimeout-1","link":"#如何实现准时的-settimeout-1","children":[{"level":3,"title":"requestAnimationFrame","slug":"requestanimationframe","link":"#requestanimationframe","children":[]},{"level":3,"title":"while","slug":"while","link":"#while","children":[]},{"level":3,"title":"setTimeout 系统时间补偿","slug":"settimeout-系统时间补偿","link":"#settimeout-系统时间补偿","children":[]}]}],"relativePath":"fe-utils/setTimeout.md","lastUpdated":1712652445000}'),e={name:"fe-utils/setTimeout.md"},t=l("",23),c=[t];function r(B,y,i,F,A,u){return n(),a("div",null,c)}const b=s(e,[["render",r]]);export{d as __pageData,b as default};
+import{_ as s,o as n,c as a,a as l}from"./app.815d1813.js";const p="/blog/assets/2024-04-09-16-41-09.9e776372.png",o="/blog/assets/2024-04-09-16-41-29.01db7dfd.png",d=JSON.parse('{"title":"如何实现准时的 setTimeout","description":"","frontmatter":{},"headers":[{"level":2,"title":"为什么不准?","slug":"为什么不准","link":"#为什么不准","children":[]},{"level":2,"title":"如何实现准时的 “setTimeout”","slug":"如何实现准时的-settimeout-1","link":"#如何实现准时的-settimeout-1","children":[{"level":3,"title":"requestAnimationFrame","slug":"requestanimationframe","link":"#requestanimationframe","children":[]},{"level":3,"title":"while","slug":"while","link":"#while","children":[]},{"level":3,"title":"setTimeout 系统时间补偿","slug":"settimeout-系统时间补偿","link":"#settimeout-系统时间补偿","children":[]}]}],"relativePath":"fragment/setTimeout.md","lastUpdated":1712654359000}'),e={name:"fragment/setTimeout.md"},t=l("",23),c=[t];function r(B,y,i,F,A,u){return n(),a("div",null,c)}const b=s(e,[["render",r]]);export{d as __pageData,b as default};
diff --git "a/assets/fragment_\345\276\256\345\206\205\346\240\270\346\236\266\346\236\204.md.28241a86.js" "b/assets/fragment_\345\276\256\345\206\205\346\240\270\346\236\266\346\236\204.md.28241a86.js"
new file mode 100644
index 00000000..a0a7eb11
--- /dev/null
+++ "b/assets/fragment_\345\276\256\345\206\205\346\240\270\346\236\266\346\236\204.md.28241a86.js"
@@ -0,0 +1,273 @@
+import{_ as s,o as n,c as a,a as l}from"./app.815d1813.js";const p="/blog/assets/2024-04-09-16-52-36.f7ed461a.png",b=JSON.parse('{"title":"🔥 微内核架构在前端的实现及其应用","description":"","frontmatter":{},"headers":[{"level":2,"title":"前置知识","slug":"前置知识","link":"#前置知识","children":[]},{"level":2,"title":"如何实现","slug":"如何实现","link":"#如何实现","children":[{"level":3,"title":"如何调用","slug":"如何调用","link":"#如何调用","children":[]},{"level":3,"title":"Core 和 Plugin 的实现","slug":"core-和-plugin-的实现","link":"#core-和-plugin-的实现","children":[]},{"level":3,"title":"设计思想","slug":"设计思想","link":"#设计思想","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":"presets 预设","slug":"presets-预设","link":"#presets-预设","children":[]}]},{"level":2,"title":"应用场景","slug":"应用场景","link":"#应用场景","children":[{"level":3,"title":"webpack","slug":"webpack","link":"#webpack","children":[]},{"level":3,"title":"babel","slug":"babel","link":"#babel","children":[]},{"level":3,"title":"Vue","slug":"vue","link":"#vue","children":[]}]},{"level":2,"title":"小结","slug":"小结","link":"#小结","children":[]}],"relativePath":"fragment/微内核架构.md","lastUpdated":1712654359000}'),o={name:"fragment/微内核架构.md"},e=l('

🔥 微内核架构在前端的实现及其应用

前置知识

微内核架构,大白话讲就是插件系统,就像下面这张图:

从上图中可以看出,微内核架构主要由两部分组成:Core + plugin,也就是一个内核和多个插件。通常来说:

  • 内核主要负责一些基础功能、核心功能以及插件的管理;
  • 插件则是一个独立的功能模块,用来丰富和加强内核的能力。

内核和插件通常是解耦的,在不使用插件的情况下,内核也能够独立运行。看得出来这种架构拓展性极强,在前端主流框架中都能看到它的影子:

  • 你在用 vue 的时候,可以用 Vue.use()
  • webpack 用的一些 plugins
  • babel 用的一些 plugins
  • 浏览器插件
  • vscode 插件
  • koa 中间件(中间件也可以理解为插件)
  • 甚至是 jQuery 插件
  • ...

如何实现

话不多说,接下来就直接开撸,实现一个微内核系统。不过在做之前,我们要先明确要做什么东西,这里就以文档编辑器为例子吧,每篇文档由多个 Block 区块组成,每个 Block 又可以展示不同的内容(列表、图片、图表等),这种情况下,我们要想扩展文档没有的功能(比如脑图),就很适合用插件系统做啦!

如何调用

通常在开发之前,我们会先确定一下期望的调用方式,大抵应该是下面这个样子 👇🏻:

js
// 内核初始化
+const core = new Core();
+// 使用插件
+core.use(new Plugin1());
+core.use(new Plugin2());
+// 开始运行
+core.run();
+
// 内核初始化
+const core = new Core();
+// 使用插件
+core.use(new Plugin1());
+core.use(new Plugin2());
+// 开始运行
+core.run();
+

Core 和 Plugin 的实现

插件是围绕内核而生的,所以我们先来实现内核的部分吧,也就是 Core 类。通常内核有两个用途:一个是实现基础功能;一个是管理插件。基础功能要看具体使用场景,它是基于业务的,这里就略过了。我们的重点在插件,要想能够在内核中使用插件肯定是要在内核中开个口子的,最基本的问题就是如何注册、如何执行。一般来说插件的格式会长下面这个样子:

ts
interface IPlugin {
+  /** 插件名字 */
+  name: string;
+  /** 插件能力,通常是个函数,也可以叫 apply、exec、handle */
+  fn: Function;
+}
+
interface IPlugin {
+  /** 插件名字 */
+  name: string;
+  /** 插件能力,通常是个函数,也可以叫 apply、exec、handle */
+  fn: Function;
+}
+

再看看前面定义的调用方式, core.use() 就是向内核中注册插件了,而 core.run() 则是执行,也就是说 Core 中需要有 use 和 run 这两个方法,于是我们就能简单写出如下代码 👇🏻:

ts
/** 内核 Core :基础功能 + 插件调度器 */
+class Core {
+  pluginMap: Map<string, IPlugin> = new Map();
+  constructor() {
+    console.log("实现内核基础功能:文档初始化");
+  }
+  /** 插件注册,也可以叫 register,通常以注册表的形式实现,其实就是个对象映射 */
+  use(plugin: IPlugin): Core {
+    this.pluginMap.set(plugin.name, plugin);
+    return this; // 方便链式调用
+  }
+  /** 插件执行,也可以叫 start */
+  run() {
+    this.pluginMap.forEach((plugin) => {
+      plugin.fn();
+    });
+  }
+}
+class Plugin1 implements IPlugin {
+  name = "Block1";
+  fn() {
+    console.log("扩展文档功能:Block1");
+  }
+}
+class Plugin2 implements IPlugin {
+  name = "Block2";
+  fn() {
+    console.log("扩展文档功能:Block2");
+  }
+}
+// 运行结果如下:
+// 实现内核基础功能:文档初始化
+// 扩展文档功能:Block1
+// 扩展文档功能:Block2
+
/** 内核 Core :基础功能 + 插件调度器 */
+class Core {
+  pluginMap: Map<string, IPlugin> = new Map();
+  constructor() {
+    console.log("实现内核基础功能:文档初始化");
+  }
+  /** 插件注册,也可以叫 register,通常以注册表的形式实现,其实就是个对象映射 */
+  use(plugin: IPlugin): Core {
+    this.pluginMap.set(plugin.name, plugin);
+    return this; // 方便链式调用
+  }
+  /** 插件执行,也可以叫 start */
+  run() {
+    this.pluginMap.forEach((plugin) => {
+      plugin.fn();
+    });
+  }
+}
+class Plugin1 implements IPlugin {
+  name = "Block1";
+  fn() {
+    console.log("扩展文档功能:Block1");
+  }
+}
+class Plugin2 implements IPlugin {
+  name = "Block2";
+  fn() {
+    console.log("扩展文档功能:Block2");
+  }
+}
+// 运行结果如下:
+// 实现内核基础功能:文档初始化
+// 扩展文档功能:Block1
+// 扩展文档功能:Block2
+

设计思想

一个好的架构肯定是能够体现一些设计模式思想的:

  • 单一职责:每个插件相互独立,只负责其对应的功能模块,也方便进行单独测试。如果把功能点都写在内核里,就容易造成高耦合。
  • 开放封闭:也就是我们扩展某个功能,不会去修改老代码,每个插件的接入成本是一样的,我们也不用关心内核是怎么实现的,只要知道它暴露了什么 api,会用就行。
  • 策略模式:比如我们在渲染文档的某个 Block 区块时,需要根据不同 Block 类型(插件名、策略名)去执行不同的渲染方法(插件方法、策略),当然了,当前的代码体现的可能不是很明显。
  • 还有一个就是控制反转:这个暂时还没体现,但是下文会提到,简单来说就是通过依赖注入实现控制权的反转

重点问题

通讯问题

前面我们说道,内核和插件是解耦的,那如果我需要在插件中用到一些内核的功能,该怎么和内核通信呢?插件与插件之间又该怎么通信呢?别急,先来解决第一个问题,这个很简单,既然插件要用内核的东西,那我把内核暴露给插件不就好了,就像下面这样 👇🏻:

ts
interface IPlugin {
+  /** 插件名字 */
+  name: string;
+  /** 插件能力:这个 ctx 就是内核 */
+  fn(ctx: Core): void;
+}
+class Core {
+  run() {
+    this.pluginMap.forEach((plugin) => {
+      plugin.fn(this); // 注意这里,我们把 this 传递了进去
+    });
+  }
+}
+
interface IPlugin {
+  /** 插件名字 */
+  name: string;
+  /** 插件能力:这个 ctx 就是内核 */
+  fn(ctx: Core): void;
+}
+class Core {
+  run() {
+    this.pluginMap.forEach((plugin) => {
+      plugin.fn(this); // 注意这里,我们把 this 传递了进去
+    });
+  }
+}
+

这样一来我们就能在插件中获取 Core 实例了(也可以叫做上下文),相应的就能够用内核的一些东西了。比如内核中通常会有一些系统基础配置信息(isDev、platform、version 等),那我们在插件中就能根据不同环境、不同平台、不同版本进行进一步处理,甚至是更改内核的内容。 这个暴露内核的过程其实就是前文提到的控制反转,什么意思呢?比如通常情况下我们要在一个类中要使用另外一个类,你会怎么做呢?是不是会直接在这个类中直接实例化另外一个类,这个就是正常的控制权。现在呢,我们虽然需要在内核中使用插件,但是我们把内核实例暴露给了插件,变成了在插件中使用内核,注意,此时主动权已经在插件这里了,这就是通过依赖注入实现了控制反转,可以好仔体会一下 🤯。 这种方式虽然我们能够引用和修改到内核的一些信息,但是好像不能干预内核初始化和执行的过程,要是想在内核初始化和执行过程中做一些处理该怎么办呢?我们可以引入事件机制,也就是发布订阅模式,在内核执行过程中抛出一些事件,然后由插件去监听相应的事件,我们可以叫 events,也可以叫 hooks(webpack 里面就是 hooks),具体实现就像下面这样:

js
import { EventEmitter } from "events"; // webpack 中使用 tapable,很强大的一个发布订阅库
+
+class Core {
+  events: EventEmitter = new EventEmitter(); // 也可以叫 hooks,就是发布订阅
+  constructor() {
+    this.events.emit("beforeInit");
+    console.log("实现内核基础功能:文档初始化");
+    this.events.emit("afterInit");
+  }
+  run() {
+    this.events.emit("before all plugins");
+    this.pluginMap.forEach((plugin) => {
+      plugin.fn(this);
+    });
+    this.events.emit("after all plugins");
+  }
+}
+// 插件中可以这样调用:this.events.on('xxxx');
+
import { EventEmitter } from "events"; // webpack 中使用 tapable,很强大的一个发布订阅库
+
+class Core {
+  events: EventEmitter = new EventEmitter(); // 也可以叫 hooks,就是发布订阅
+  constructor() {
+    this.events.emit("beforeInit");
+    console.log("实现内核基础功能:文档初始化");
+    this.events.emit("afterInit");
+  }
+  run() {
+    this.events.emit("before all plugins");
+    this.pluginMap.forEach((plugin) => {
+      plugin.fn(this);
+    });
+    this.events.emit("after all plugins");
+  }
+}
+// 插件中可以这样调用:this.events.on('xxxx');
+

我们也可以改成生命周期的形式,比如:

ts
class Core {
+  constructor(opts?: IOption) {
+    opts?.beforeCreate();
+    console.log("实现内核基础功能:文档初始化");
+    opts?.afterCreate();
+  }
+}
+
class Core {
+  constructor(opts?: IOption) {
+    opts?.beforeCreate();
+    console.log("实现内核基础功能:文档初始化");
+    opts?.afterCreate();
+  }
+}
+

这就是生命周期钩子。除了内核本身的生命周期之外,插件也可以有,一般分为加载、运行和卸载三部分,道理类似。当然这里要注意上面两种通信方式的区别, hooks 侧重的是时机,它会在特定阶段触发,而 ctx 则侧重于共享信息的传递。 至于插件和插件的通信,通常来说在这个模式中使用相互影响的插件是不友好的,也不建议这么做,但是如果一定需要的话可以通过内核和一些 hooks 来做桥接。

插件的调度

我们思考一个问题,插件的加载顺序对内核有影响吗?多个插件之间应该如何运行,它们是怎样的关系?看看我们的代码是直接 forEach 遍历执行的,这样的对吗?此处可以停下来思考几秒种 🤔。

通常来说多个插件有以下几种运行方式:

具体采用哪种方式就要看我们的具体业务场景了,比如我们的业务是文档编辑器,插件主要用于扩展文档的 Block 功能,所以顺序不是很重要,相当于上面的第三种模式,主要就是加强功能。那管道式呢,管道式的特点就是上一步的输入是下一步的输出,也就是管道流,这种就是对顺序有要求的,改变插件的顺序,结果也是不一样的,比如我们的业务核心是处理数据,input 就是原始数据,中间的 plugin 就是各种数据处理步骤(比如采样、均化、归一等),output 就是最终处理后的结果;又比如构建工具 gulp 也是如此,有兴趣的可以自行了解下。最后是洋葱式,这个最典型的应用场景就是 koa 中间件了,每个路由都会经过层层中间件,又层层返回;还有 babel 遍历过程中访问器 vistor 的进出过程也是如此。了解上图的三种模式你就大概能知道何时何地加载、运行和销毁插件了。 那我们除了用 use 来使用插件外,还可以用什么方式来注册插件呢?可以用声明式注入,也就是通过配置文件来告诉系统应该去哪里去取什么插件,系统运行时会按照约定的配置去加载对应的插件。比如 babel 就可以通过在配置文件中填写插件名称,运行时就会去自行查找对应的插件并加载(可以想想我们平时开发时的各种 config.js 配置文件)。而编程式的就是系统提供某种注册 api,开发者通过将插件传入该 api 中来完成注册,也就是本文使用的方式。 另外我们再来说个小问题,就是插件的管理,也就是插件池,有时我们会用 map,有时我们会用数组,就像这样:

js
// map: {'pluginName1': plugin1, 'pluginName2': plugin2, ...}
+// 数组:[new Plugin1(), new Plugin2(), ...]
+
// map: {'pluginName1': plugin1, 'pluginName2': plugin2, ...}
+// 数组:[new Plugin1(), new Plugin2(), ...]
+

这两种用法在日常开发中也很常见,那它们有什么区别呢?这就要看你要求了,用 map 一般都是要具名的,目的是为了存取方便,尤其是取,就像缓存的感觉;而如果我们只需要单纯的遍历执行插件用数组就可以了,当然如果复杂度较高的情况下,可以两者一起使用,就像下面这样 👇🏻:

ts
class Core {
+  plugins: IPlugin[] = [];
+  pluginMap: Map<string, IPlugin> = new Map();
+  use(plugin: IPlugin): Core {
+    this.plugins.push(plugin);
+    this.pluginMap.set(plugin.name, plugin);
+    return this;
+  }
+}
+
class Core {
+  plugins: IPlugin[] = [];
+  pluginMap: Map<string, IPlugin> = new Map();
+  use(plugin: IPlugin): Core {
+    this.plugins.push(plugin);
+    this.pluginMap.set(plugin.name, plugin);
+    return this;
+  }
+}
+

安全性和稳定性

因为这种架构的扩展性相当强,并且插件也有能力去修改内核,所以安全性和稳定性的问题也是必须要考虑的问题 😬。 为了保障稳定性,在插件运行异常时,我们应当进行相应的容错处理,由内核来捕获并继续往下执行,不能让系统轻易崩掉,可以给个警告或错误的提示,或者通过系统级事件通知内核 重启 或 关闭 该插件。 关于安全性,我们可以只暴露必要的信息给插件,而不是全部(整个内核),这是什么意思呢,其实就是要搞个简单的沙箱,在前端这边具体点就是用 with+proxy+白名单 来处理,node 用 vm 模块来处理,当然在浏览器中要想完全隔绝是很难的,不过已经有个提案的 api 在路上了,可以期待一下。

presets 预设

presets 这个字眼在前端中应该还是挺常见的,它其实是什么意思呢,中文我们叫预设,本质就是一些插件的集合,亦即 Core + presets(几个插件) ,内核可以有不同的预设,presets1、presets2 等等,每个 presets 就是几个不同插件的组合,主要用途就是方便,不用我们一个一个去引入去设置,比如 babel 的预设、vue 脚手架中的预设。当然除了预设之外,我们也可以将一些必备的插件固化为内置插件,这个度由开发者自己去把握。

应用场景

webpack

js
module.exports = {
+  plugins: [
+    // 用配置的方式注册插件
+    new webpack.ProgressPlugin(),
+    new HtmlWebpackPlugin({ template: "./src/index.html" }),
+  ],
+};
+
module.exports = {
+  plugins: [
+    // 用配置的方式注册插件
+    new webpack.ProgressPlugin(),
+    new HtmlWebpackPlugin({ template: "./src/index.html" }),
+  ],
+};
+
js
const pluginName = "ConsoleLogOnBuildWebpackPlugin";
+
+class ConsoleLogOnBuildWebpackPlugin {
+  apply(compiler) {
+    // apply就是插件的fn,compiler就是内核上下文
+    compiler.hooks.run.tap(pluginName, (compilation) => {
+      // hooks就是发布订阅
+      console.log("The webpack build process is starting!");
+    });
+  }
+}
+
+module.exports = ConsoleLogOnBuildWebpackPlugin;
+
const pluginName = "ConsoleLogOnBuildWebpackPlugin";
+
+class ConsoleLogOnBuildWebpackPlugin {
+  apply(compiler) {
+    // apply就是插件的fn,compiler就是内核上下文
+    compiler.hooks.run.tap(pluginName, (compilation) => {
+      // hooks就是发布订阅
+      console.log("The webpack build process is starting!");
+    });
+  }
+}
+
+module.exports = ConsoleLogOnBuildWebpackPlugin;
+

babel

配置文件方式注册插件

json
{
+  "plugins": ["babel-plugin-myPlugin", "@babel/plugin-transform-runtime"]
+}
+
{
+  "plugins": ["babel-plugin-myPlugin", "@babel/plugin-transform-runtime"]
+}
+

插件开发

js
export default function () {
+  return {
+    visitor: {
+      Identifier(path) {
+        // 插件的执行内容,看起来特殊一些,不过path可以理解为内核上下文
+        const name = path.node.name;
+        path.node.name = name.split("").reverse().join("");
+      },
+    },
+  };
+}
+
export default function () {
+  return {
+    visitor: {
+      Identifier(path) {
+        // 插件的执行内容,看起来特殊一些,不过path可以理解为内核上下文
+        const name = path.node.name;
+        path.node.name = name.split("").reverse().join("");
+      },
+    },
+  };
+}
+

Vue

js
const app = createApp();
+
+app.use(myPlugin, {});
+
const app = createApp();
+
+app.use(myPlugin, {});
+
js
const myPlugin = {
+  install(app, options) {},
+};
+
const myPlugin = {
+  install(app, options) {},
+};
+

类似 jQuery、window 从某种角度上来说也可以看做是内核,通过 $.fn() 或者在 prototype 中扩展方法也是一种插件的形式。

小结

最后我们再来巩固一下这篇文章的核心思想,微内核架构主要由两部分组成: Core + Plugin,其中: Core 需要具备的能力有:

  • 基础功能,视业务情况而定
  • 一些配置信息(环境变量、全局变量)
  • 插件管理:通信、调度、稳定性
  • 生命周期钩子 hooks:约束一些特定阶段,用较少的钩子尽可能覆盖大部分场景 plugin 应该具备的能力有:
  • 能被内核调用
  • 可以更改内核的一些东西
  • 相互独立
  • 插件一般先注册后干预执行,有需要再提供卸载功能 其实微内核架构的核心思想就是在系统内部预留一些入口,系统本身功能不变,但是通过这个入口可以集百家之长以丰富系统自身 🍺。

🔗 原文链接: https://juejin.cn/post/716307803160...

`,58),c=[e];function r(t,B,y,i,F,u){return n(),a("div",null,c)}const d=s(o,[["render",r]]);export{b as __pageData,d as default}; diff --git "a/assets/fragment_\345\276\256\345\206\205\346\240\270\346\236\266\346\236\204.md.28241a86.lean.js" "b/assets/fragment_\345\276\256\345\206\205\346\240\270\346\236\266\346\236\204.md.28241a86.lean.js" new file mode 100644 index 00000000..2af8c07b --- /dev/null +++ "b/assets/fragment_\345\276\256\345\206\205\346\240\270\346\236\266\346\236\204.md.28241a86.lean.js" @@ -0,0 +1 @@ +import{_ as s,o as n,c as a,a as l}from"./app.815d1813.js";const p="/blog/assets/2024-04-09-16-52-36.f7ed461a.png",b=JSON.parse('{"title":"🔥 微内核架构在前端的实现及其应用","description":"","frontmatter":{},"headers":[{"level":2,"title":"前置知识","slug":"前置知识","link":"#前置知识","children":[]},{"level":2,"title":"如何实现","slug":"如何实现","link":"#如何实现","children":[{"level":3,"title":"如何调用","slug":"如何调用","link":"#如何调用","children":[]},{"level":3,"title":"Core 和 Plugin 的实现","slug":"core-和-plugin-的实现","link":"#core-和-plugin-的实现","children":[]},{"level":3,"title":"设计思想","slug":"设计思想","link":"#设计思想","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":"presets 预设","slug":"presets-预设","link":"#presets-预设","children":[]}]},{"level":2,"title":"应用场景","slug":"应用场景","link":"#应用场景","children":[{"level":3,"title":"webpack","slug":"webpack","link":"#webpack","children":[]},{"level":3,"title":"babel","slug":"babel","link":"#babel","children":[]},{"level":3,"title":"Vue","slug":"vue","link":"#vue","children":[]}]},{"level":2,"title":"小结","slug":"小结","link":"#小结","children":[]}],"relativePath":"fragment/微内核架构.md","lastUpdated":1712654359000}'),o={name:"fragment/微内核架构.md"},e=l("",58),c=[e];function r(t,B,y,i,F,u){return n(),a("div",null,c)}const d=s(o,[["render",r]]);export{b as __pageData,d as default}; diff --git "a/assets/front-end-engineering_CSS\345\267\245\347\250\213\345\214\226.md.1eeb0199.js" "b/assets/front-end-engineering_CSS\345\267\245\347\250\213\345\214\226.md.e50e4c60.js" similarity index 99% rename from "assets/front-end-engineering_CSS\345\267\245\347\250\213\345\214\226.md.1eeb0199.js" rename to "assets/front-end-engineering_CSS\345\267\245\347\250\213\345\214\226.md.e50e4c60.js" index 754deecb..245bc248 100644 --- "a/assets/front-end-engineering_CSS\345\267\245\347\250\213\345\214\226.md.1eeb0199.js" +++ "b/assets/front-end-engineering_CSS\345\267\245\347\250\213\345\214\226.md.e50e4c60.js" @@ -1,4 +1,4 @@ -import{_ as s,o as n,c as a,a as l}from"./app.679ab08c.js";const p="/blog/assets/2023-01-06-16-14-44.9c23195b.png",o="/blog/assets/2023-01-06-16-16-45.8cacfb52.png",e="/blog/assets/2023-01-06-16-16-57.792b0ec0.png",c="/blog/assets/2023-01-06-16-17-35.bce22d59.png",r="/blog/assets/2023-01-06-16-23-08.16c5b4ae.png",t="/blog/assets/2023-01-06-16-23-16.12a8f778.png",B="/blog/assets/2023-01-06-16-37-27.347ac1c6.png",i="/blog/assets/2023-01-06-16-37-43.6a6a7a3c.png",y="/blog/assets/2023-01-06-16-37-56.802983a9.png",F="/blog/assets/2023-01-06-16-38-33.30911d0e.png",E=JSON.parse('{"title":"CSS工程化","description":"","frontmatter":{},"headers":[{"level":2,"title":"css的问题","slug":"css的问题","link":"#css的问题","children":[{"level":3,"title":"类名冲突的问题","slug":"类名冲突的问题","link":"#类名冲突的问题","children":[]},{"level":3,"title":"重复样式","slug":"重复样式","link":"#重复样式","children":[]},{"level":3,"title":"css文件细分问题","slug":"css文件细分问题","link":"#css文件细分问题","children":[]}]},{"level":2,"title":"如何解决","slug":"如何解决","link":"#如何解决","children":[{"level":3,"title":"解决类名冲突","slug":"解决类名冲突","link":"#解决类名冲突","children":[]},{"level":3,"title":"解决重复样式的问题","slug":"解决重复样式的问题","link":"#解决重复样式的问题","children":[]},{"level":3,"title":"解决css文件细分问题","slug":"解决css文件细分问题","link":"#解决css文件细分问题","children":[]}]},{"level":2,"title":"利用webpack拆分css","slug":"利用webpack拆分css","link":"#利用webpack拆分css","children":[{"level":3,"title":"css-loader","slug":"css-loader","link":"#css-loader","children":[]},{"level":3,"title":"style-loader","slug":"style-loader","link":"#style-loader","children":[]}]},{"level":2,"title":"BEM","slug":"bem","link":"#bem","children":[]},{"level":2,"title":"css in js","slug":"css-in-js","link":"#css-in-js","children":[]},{"level":2,"title":"css module","slug":"css-module","link":"#css-module","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":[]},{"level":3,"title":"全局类名","slug":"全局类名","link":"#全局类名","children":[]},{"level":3,"title":"如何控制最终的类名","slug":"如何控制最终的类名","link":"#如何控制最终的类名","children":[]},{"level":3,"title":"其他注意事项","slug":"其他注意事项","link":"#其他注意事项","children":[]}]},{"level":2,"title":"CSS预编译器","slug":"css预编译器","link":"#css预编译器","children":[{"level":3,"title":"基本原理","slug":"基本原理","link":"#基本原理","children":[]},{"level":3,"title":"LESS的安装和使用","slug":"less的安装和使用","link":"#less的安装和使用","children":[]},{"level":3,"title":"LESS的基本使用","slug":"less的基本使用","link":"#less的基本使用","children":[]},{"level":3,"title":"webpack中使用less","slug":"webpack中使用less","link":"#webpack中使用less","children":[]}]},{"level":2,"title":"PostCss","slug":"postcss","link":"#postcss","children":[{"level":3,"title":"什么是PostCss","slug":"什么是postcss","link":"#什么是postcss","children":[]},{"level":3,"title":"安装","slug":"安装","link":"#安装","children":[]},{"level":3,"title":"配置文件","slug":"配置文件","link":"#配置文件","children":[]},{"level":3,"title":"插件","slug":"插件","link":"#插件","children":[]},{"level":3,"title":"postcss-preset-env","slug":"postcss-preset-env","link":"#postcss-preset-env","children":[]},{"level":3,"title":"自动的厂商前缀","slug":"自动的厂商前缀","link":"#自动的厂商前缀","children":[]},{"level":3,"title":"未来的CSS语法","slug":"未来的css语法","link":"#未来的css语法","children":[]},{"level":3,"title":"postcss-apply","slug":"postcss-apply","link":"#postcss-apply","children":[]},{"level":3,"title":"postcss-color-function","slug":"postcss-color-function","link":"#postcss-color-function","children":[]},{"level":3,"title":"postcss-import","slug":"postcss-import","link":"#postcss-import","children":[]}]},{"level":2,"title":"stylelint","slug":"stylelint","link":"#stylelint","children":[]},{"level":2,"title":"抽离css文件","slug":"抽离css文件","link":"#抽离css文件","children":[]}],"relativePath":"front-end-engineering/CSS工程化.md","lastUpdated":1672994388000}'),d={name:"front-end-engineering/CSS工程化.md"},u=l(`

CSS工程化

css的问题

类名冲突的问题

当你写一个css类的时候,你是写全局的类呢,还是写多个层级选择后的类呢?

你会发现,怎么都不好

  • 过深的层级不利于编写、阅读、压缩、复用
  • 过浅的层级容易导致类名冲突

一旦样式多起来,这个问题就会变得越发严重,其实归根结底,就是类名冲突不好解决的问题

重复样式

这种问题就更普遍了,一些重复的样式值总是不断的出现在css代码中,维护起来极其困难

比如,一个网站的颜色一般就那么几种:

  • primary
  • info
  • warn
  • error
  • success

如果有更多的颜色,都是从这些色调中自然变化得来,可以想象,这些颜色会到处充斥到诸如背景、文字、边框中,一旦要做颜色调整,是一个非常大的工程

css文件细分问题

在大型项目中,css也需要更细的拆分,这样有利于css代码的维护。

比如,有一个做轮播图的模块,它不仅需要依赖js功能,还需要依赖css样式,既然依赖的js功能仅关心轮播图,那css样式也应该仅关心轮播图,由此类推,不同的功能依赖不同的css样式、公共样式可以单独抽离,这样就形成了不同于过去的css文件结构:文件更多、拆分的更细

而同时,在真实的运行环境下,我们却希望文件越少越好,这种情况和JS遇到的情况是一致的

因此,对于css,也需要工程化管理

从另一个角度来说,css的工程化会遇到更多的挑战,因为css不像JS,它的语法本身经过这么多年并没有发生多少的变化(css3也仅仅是多了一些属性而已),对于css语法本身的改变也是一个工程化的课题

如何解决

这么多年来,官方一直没有提出方案来解决上述问题

一些第三方机构针对不同的问题,提出了自己的解决方案

解决类名冲突

一些第三方机构提出了一些方案来解决该问题,常见的解决方案如下:

命名约定

即提供一种命名的标准,来解决冲突,常见的标准有:

  • BEM
  • OOCSS
  • AMCSS
  • SMACSS
  • 其他

css in js

这种方案非常大胆,它觉得,css语言本身几乎无可救药了,干脆直接用js对象来表示样式,然后把样式直接应用到元素的style中

这样一来,css变成了一个一个的对象,就可以完全利用到js语言的优势,你可以:

  • 通过一个函数返回一个样式对象
  • 把公共的样式提取到公共模块中返回
  • 应用js的各种特性操作对象,比如:混合、提取、拆分
  • 更多的花样

这种方案在手机端的React Native中大行其道

css module

非常有趣和好用的css模块化方案,编写简单,绝对不重名

具体的课程中详细介绍

解决重复样式的问题

css in js

这种方案虽然可以利用js语言解决重复样式值的问题,但由于太过激进,很多习惯写css的开发者编写起来并不是很适应

预编译器

有些第三方搞出一套css语言的进化版来解决这个问题,它支持变量、函数等高级语法,然后经过编译器将其编译成为正常的css

这种方案特别像构建工具,不过它仅针对css

常见的预编译器支持的语言有:

  • less
  • sass

解决css文件细分问题

这一部分,就要依靠构建工具,例如webpack来解决了

利用一些loader或plugin来打包、合并、压缩css文件

利用webpack拆分css

要拆分css,就必须把css当成像js那样的模块;要把css当成模块,就必须有一个构建工具(webpack),它具备合并代码的能力

而webpack本身只能读取css文件的内容、将其当作JS代码进行分析,因此,会导致错误

于是,就必须有一个loader,能够将css代码转换为js代码

css-loader

css-loader的作用,就是将css代码转换为js代码

它的处理原理极其简单:将css代码作为字符串导出

例如:

css
.red{
+import{_ as s,o as n,c as a,a as l}from"./app.815d1813.js";const p="/blog/assets/2023-01-06-16-14-44.9c23195b.png",o="/blog/assets/2023-01-06-16-16-45.8cacfb52.png",e="/blog/assets/2023-01-06-16-16-57.792b0ec0.png",c="/blog/assets/2023-01-06-16-17-35.bce22d59.png",r="/blog/assets/2023-01-06-16-23-08.16c5b4ae.png",t="/blog/assets/2023-01-06-16-23-16.12a8f778.png",B="/blog/assets/2023-01-06-16-37-27.347ac1c6.png",i="/blog/assets/2023-01-06-16-37-43.6a6a7a3c.png",y="/blog/assets/2023-01-06-16-37-56.802983a9.png",F="/blog/assets/2023-01-06-16-38-33.30911d0e.png",E=JSON.parse('{"title":"CSS工程化","description":"","frontmatter":{},"headers":[{"level":2,"title":"css的问题","slug":"css的问题","link":"#css的问题","children":[{"level":3,"title":"类名冲突的问题","slug":"类名冲突的问题","link":"#类名冲突的问题","children":[]},{"level":3,"title":"重复样式","slug":"重复样式","link":"#重复样式","children":[]},{"level":3,"title":"css文件细分问题","slug":"css文件细分问题","link":"#css文件细分问题","children":[]}]},{"level":2,"title":"如何解决","slug":"如何解决","link":"#如何解决","children":[{"level":3,"title":"解决类名冲突","slug":"解决类名冲突","link":"#解决类名冲突","children":[]},{"level":3,"title":"解决重复样式的问题","slug":"解决重复样式的问题","link":"#解决重复样式的问题","children":[]},{"level":3,"title":"解决css文件细分问题","slug":"解决css文件细分问题","link":"#解决css文件细分问题","children":[]}]},{"level":2,"title":"利用webpack拆分css","slug":"利用webpack拆分css","link":"#利用webpack拆分css","children":[{"level":3,"title":"css-loader","slug":"css-loader","link":"#css-loader","children":[]},{"level":3,"title":"style-loader","slug":"style-loader","link":"#style-loader","children":[]}]},{"level":2,"title":"BEM","slug":"bem","link":"#bem","children":[]},{"level":2,"title":"css in js","slug":"css-in-js","link":"#css-in-js","children":[]},{"level":2,"title":"css module","slug":"css-module","link":"#css-module","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":[]},{"level":3,"title":"全局类名","slug":"全局类名","link":"#全局类名","children":[]},{"level":3,"title":"如何控制最终的类名","slug":"如何控制最终的类名","link":"#如何控制最终的类名","children":[]},{"level":3,"title":"其他注意事项","slug":"其他注意事项","link":"#其他注意事项","children":[]}]},{"level":2,"title":"CSS预编译器","slug":"css预编译器","link":"#css预编译器","children":[{"level":3,"title":"基本原理","slug":"基本原理","link":"#基本原理","children":[]},{"level":3,"title":"LESS的安装和使用","slug":"less的安装和使用","link":"#less的安装和使用","children":[]},{"level":3,"title":"LESS的基本使用","slug":"less的基本使用","link":"#less的基本使用","children":[]},{"level":3,"title":"webpack中使用less","slug":"webpack中使用less","link":"#webpack中使用less","children":[]}]},{"level":2,"title":"PostCss","slug":"postcss","link":"#postcss","children":[{"level":3,"title":"什么是PostCss","slug":"什么是postcss","link":"#什么是postcss","children":[]},{"level":3,"title":"安装","slug":"安装","link":"#安装","children":[]},{"level":3,"title":"配置文件","slug":"配置文件","link":"#配置文件","children":[]},{"level":3,"title":"插件","slug":"插件","link":"#插件","children":[]},{"level":3,"title":"postcss-preset-env","slug":"postcss-preset-env","link":"#postcss-preset-env","children":[]},{"level":3,"title":"自动的厂商前缀","slug":"自动的厂商前缀","link":"#自动的厂商前缀","children":[]},{"level":3,"title":"未来的CSS语法","slug":"未来的css语法","link":"#未来的css语法","children":[]},{"level":3,"title":"postcss-apply","slug":"postcss-apply","link":"#postcss-apply","children":[]},{"level":3,"title":"postcss-color-function","slug":"postcss-color-function","link":"#postcss-color-function","children":[]},{"level":3,"title":"postcss-import","slug":"postcss-import","link":"#postcss-import","children":[]}]},{"level":2,"title":"stylelint","slug":"stylelint","link":"#stylelint","children":[]},{"level":2,"title":"抽离css文件","slug":"抽离css文件","link":"#抽离css文件","children":[]}],"relativePath":"front-end-engineering/CSS工程化.md","lastUpdated":1672994388000}'),d={name:"front-end-engineering/CSS工程化.md"},u=l(`

CSS工程化

css的问题

类名冲突的问题

当你写一个css类的时候,你是写全局的类呢,还是写多个层级选择后的类呢?

你会发现,怎么都不好

  • 过深的层级不利于编写、阅读、压缩、复用
  • 过浅的层级容易导致类名冲突

一旦样式多起来,这个问题就会变得越发严重,其实归根结底,就是类名冲突不好解决的问题

重复样式

这种问题就更普遍了,一些重复的样式值总是不断的出现在css代码中,维护起来极其困难

比如,一个网站的颜色一般就那么几种:

  • primary
  • info
  • warn
  • error
  • success

如果有更多的颜色,都是从这些色调中自然变化得来,可以想象,这些颜色会到处充斥到诸如背景、文字、边框中,一旦要做颜色调整,是一个非常大的工程

css文件细分问题

在大型项目中,css也需要更细的拆分,这样有利于css代码的维护。

比如,有一个做轮播图的模块,它不仅需要依赖js功能,还需要依赖css样式,既然依赖的js功能仅关心轮播图,那css样式也应该仅关心轮播图,由此类推,不同的功能依赖不同的css样式、公共样式可以单独抽离,这样就形成了不同于过去的css文件结构:文件更多、拆分的更细

而同时,在真实的运行环境下,我们却希望文件越少越好,这种情况和JS遇到的情况是一致的

因此,对于css,也需要工程化管理

从另一个角度来说,css的工程化会遇到更多的挑战,因为css不像JS,它的语法本身经过这么多年并没有发生多少的变化(css3也仅仅是多了一些属性而已),对于css语法本身的改变也是一个工程化的课题

如何解决

这么多年来,官方一直没有提出方案来解决上述问题

一些第三方机构针对不同的问题,提出了自己的解决方案

解决类名冲突

一些第三方机构提出了一些方案来解决该问题,常见的解决方案如下:

命名约定

即提供一种命名的标准,来解决冲突,常见的标准有:

  • BEM
  • OOCSS
  • AMCSS
  • SMACSS
  • 其他

css in js

这种方案非常大胆,它觉得,css语言本身几乎无可救药了,干脆直接用js对象来表示样式,然后把样式直接应用到元素的style中

这样一来,css变成了一个一个的对象,就可以完全利用到js语言的优势,你可以:

  • 通过一个函数返回一个样式对象
  • 把公共的样式提取到公共模块中返回
  • 应用js的各种特性操作对象,比如:混合、提取、拆分
  • 更多的花样

这种方案在手机端的React Native中大行其道

css module

非常有趣和好用的css模块化方案,编写简单,绝对不重名

具体的课程中详细介绍

解决重复样式的问题

css in js

这种方案虽然可以利用js语言解决重复样式值的问题,但由于太过激进,很多习惯写css的开发者编写起来并不是很适应

预编译器

有些第三方搞出一套css语言的进化版来解决这个问题,它支持变量、函数等高级语法,然后经过编译器将其编译成为正常的css

这种方案特别像构建工具,不过它仅针对css

常见的预编译器支持的语言有:

  • less
  • sass

解决css文件细分问题

这一部分,就要依靠构建工具,例如webpack来解决了

利用一些loader或plugin来打包、合并、压缩css文件

利用webpack拆分css

要拆分css,就必须把css当成像js那样的模块;要把css当成模块,就必须有一个构建工具(webpack),它具备合并代码的能力

而webpack本身只能读取css文件的内容、将其当作JS代码进行分析,因此,会导致错误

于是,就必须有一个loader,能够将css代码转换为js代码

css-loader

css-loader的作用,就是将css代码转换为js代码

它的处理原理极其简单:将css代码作为字符串导出

例如:

css
.red{
     color:"#f40";
 }
 
.red{
diff --git "a/assets/front-end-engineering_CSS\345\267\245\347\250\213\345\214\226.md.1eeb0199.lean.js" "b/assets/front-end-engineering_CSS\345\267\245\347\250\213\345\214\226.md.e50e4c60.lean.js"
similarity index 98%
rename from "assets/front-end-engineering_CSS\345\267\245\347\250\213\345\214\226.md.1eeb0199.lean.js"
rename to "assets/front-end-engineering_CSS\345\267\245\347\250\213\345\214\226.md.e50e4c60.lean.js"
index 6eee427f..196d0ce4 100644
--- "a/assets/front-end-engineering_CSS\345\267\245\347\250\213\345\214\226.md.1eeb0199.lean.js"
+++ "b/assets/front-end-engineering_CSS\345\267\245\347\250\213\345\214\226.md.e50e4c60.lean.js"
@@ -1 +1 @@
-import{_ as s,o as n,c as a,a as l}from"./app.679ab08c.js";const p="/blog/assets/2023-01-06-16-14-44.9c23195b.png",o="/blog/assets/2023-01-06-16-16-45.8cacfb52.png",e="/blog/assets/2023-01-06-16-16-57.792b0ec0.png",c="/blog/assets/2023-01-06-16-17-35.bce22d59.png",r="/blog/assets/2023-01-06-16-23-08.16c5b4ae.png",t="/blog/assets/2023-01-06-16-23-16.12a8f778.png",B="/blog/assets/2023-01-06-16-37-27.347ac1c6.png",i="/blog/assets/2023-01-06-16-37-43.6a6a7a3c.png",y="/blog/assets/2023-01-06-16-37-56.802983a9.png",F="/blog/assets/2023-01-06-16-38-33.30911d0e.png",E=JSON.parse('{"title":"CSS工程化","description":"","frontmatter":{},"headers":[{"level":2,"title":"css的问题","slug":"css的问题","link":"#css的问题","children":[{"level":3,"title":"类名冲突的问题","slug":"类名冲突的问题","link":"#类名冲突的问题","children":[]},{"level":3,"title":"重复样式","slug":"重复样式","link":"#重复样式","children":[]},{"level":3,"title":"css文件细分问题","slug":"css文件细分问题","link":"#css文件细分问题","children":[]}]},{"level":2,"title":"如何解决","slug":"如何解决","link":"#如何解决","children":[{"level":3,"title":"解决类名冲突","slug":"解决类名冲突","link":"#解决类名冲突","children":[]},{"level":3,"title":"解决重复样式的问题","slug":"解决重复样式的问题","link":"#解决重复样式的问题","children":[]},{"level":3,"title":"解决css文件细分问题","slug":"解决css文件细分问题","link":"#解决css文件细分问题","children":[]}]},{"level":2,"title":"利用webpack拆分css","slug":"利用webpack拆分css","link":"#利用webpack拆分css","children":[{"level":3,"title":"css-loader","slug":"css-loader","link":"#css-loader","children":[]},{"level":3,"title":"style-loader","slug":"style-loader","link":"#style-loader","children":[]}]},{"level":2,"title":"BEM","slug":"bem","link":"#bem","children":[]},{"level":2,"title":"css in js","slug":"css-in-js","link":"#css-in-js","children":[]},{"level":2,"title":"css module","slug":"css-module","link":"#css-module","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":[]},{"level":3,"title":"全局类名","slug":"全局类名","link":"#全局类名","children":[]},{"level":3,"title":"如何控制最终的类名","slug":"如何控制最终的类名","link":"#如何控制最终的类名","children":[]},{"level":3,"title":"其他注意事项","slug":"其他注意事项","link":"#其他注意事项","children":[]}]},{"level":2,"title":"CSS预编译器","slug":"css预编译器","link":"#css预编译器","children":[{"level":3,"title":"基本原理","slug":"基本原理","link":"#基本原理","children":[]},{"level":3,"title":"LESS的安装和使用","slug":"less的安装和使用","link":"#less的安装和使用","children":[]},{"level":3,"title":"LESS的基本使用","slug":"less的基本使用","link":"#less的基本使用","children":[]},{"level":3,"title":"webpack中使用less","slug":"webpack中使用less","link":"#webpack中使用less","children":[]}]},{"level":2,"title":"PostCss","slug":"postcss","link":"#postcss","children":[{"level":3,"title":"什么是PostCss","slug":"什么是postcss","link":"#什么是postcss","children":[]},{"level":3,"title":"安装","slug":"安装","link":"#安装","children":[]},{"level":3,"title":"配置文件","slug":"配置文件","link":"#配置文件","children":[]},{"level":3,"title":"插件","slug":"插件","link":"#插件","children":[]},{"level":3,"title":"postcss-preset-env","slug":"postcss-preset-env","link":"#postcss-preset-env","children":[]},{"level":3,"title":"自动的厂商前缀","slug":"自动的厂商前缀","link":"#自动的厂商前缀","children":[]},{"level":3,"title":"未来的CSS语法","slug":"未来的css语法","link":"#未来的css语法","children":[]},{"level":3,"title":"postcss-apply","slug":"postcss-apply","link":"#postcss-apply","children":[]},{"level":3,"title":"postcss-color-function","slug":"postcss-color-function","link":"#postcss-color-function","children":[]},{"level":3,"title":"postcss-import","slug":"postcss-import","link":"#postcss-import","children":[]}]},{"level":2,"title":"stylelint","slug":"stylelint","link":"#stylelint","children":[]},{"level":2,"title":"抽离css文件","slug":"抽离css文件","link":"#抽离css文件","children":[]}],"relativePath":"front-end-engineering/CSS工程化.md","lastUpdated":1672994388000}'),d={name:"front-end-engineering/CSS工程化.md"},u=l("",307),b=[u];function A(m,h,C,g,v,k){return n(),a("div",null,b)}const f=s(d,[["render",A]]);export{E as __pageData,f as default};
+import{_ as s,o as n,c as a,a as l}from"./app.815d1813.js";const p="/blog/assets/2023-01-06-16-14-44.9c23195b.png",o="/blog/assets/2023-01-06-16-16-45.8cacfb52.png",e="/blog/assets/2023-01-06-16-16-57.792b0ec0.png",c="/blog/assets/2023-01-06-16-17-35.bce22d59.png",r="/blog/assets/2023-01-06-16-23-08.16c5b4ae.png",t="/blog/assets/2023-01-06-16-23-16.12a8f778.png",B="/blog/assets/2023-01-06-16-37-27.347ac1c6.png",i="/blog/assets/2023-01-06-16-37-43.6a6a7a3c.png",y="/blog/assets/2023-01-06-16-37-56.802983a9.png",F="/blog/assets/2023-01-06-16-38-33.30911d0e.png",E=JSON.parse('{"title":"CSS工程化","description":"","frontmatter":{},"headers":[{"level":2,"title":"css的问题","slug":"css的问题","link":"#css的问题","children":[{"level":3,"title":"类名冲突的问题","slug":"类名冲突的问题","link":"#类名冲突的问题","children":[]},{"level":3,"title":"重复样式","slug":"重复样式","link":"#重复样式","children":[]},{"level":3,"title":"css文件细分问题","slug":"css文件细分问题","link":"#css文件细分问题","children":[]}]},{"level":2,"title":"如何解决","slug":"如何解决","link":"#如何解决","children":[{"level":3,"title":"解决类名冲突","slug":"解决类名冲突","link":"#解决类名冲突","children":[]},{"level":3,"title":"解决重复样式的问题","slug":"解决重复样式的问题","link":"#解决重复样式的问题","children":[]},{"level":3,"title":"解决css文件细分问题","slug":"解决css文件细分问题","link":"#解决css文件细分问题","children":[]}]},{"level":2,"title":"利用webpack拆分css","slug":"利用webpack拆分css","link":"#利用webpack拆分css","children":[{"level":3,"title":"css-loader","slug":"css-loader","link":"#css-loader","children":[]},{"level":3,"title":"style-loader","slug":"style-loader","link":"#style-loader","children":[]}]},{"level":2,"title":"BEM","slug":"bem","link":"#bem","children":[]},{"level":2,"title":"css in js","slug":"css-in-js","link":"#css-in-js","children":[]},{"level":2,"title":"css module","slug":"css-module","link":"#css-module","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":[]},{"level":3,"title":"全局类名","slug":"全局类名","link":"#全局类名","children":[]},{"level":3,"title":"如何控制最终的类名","slug":"如何控制最终的类名","link":"#如何控制最终的类名","children":[]},{"level":3,"title":"其他注意事项","slug":"其他注意事项","link":"#其他注意事项","children":[]}]},{"level":2,"title":"CSS预编译器","slug":"css预编译器","link":"#css预编译器","children":[{"level":3,"title":"基本原理","slug":"基本原理","link":"#基本原理","children":[]},{"level":3,"title":"LESS的安装和使用","slug":"less的安装和使用","link":"#less的安装和使用","children":[]},{"level":3,"title":"LESS的基本使用","slug":"less的基本使用","link":"#less的基本使用","children":[]},{"level":3,"title":"webpack中使用less","slug":"webpack中使用less","link":"#webpack中使用less","children":[]}]},{"level":2,"title":"PostCss","slug":"postcss","link":"#postcss","children":[{"level":3,"title":"什么是PostCss","slug":"什么是postcss","link":"#什么是postcss","children":[]},{"level":3,"title":"安装","slug":"安装","link":"#安装","children":[]},{"level":3,"title":"配置文件","slug":"配置文件","link":"#配置文件","children":[]},{"level":3,"title":"插件","slug":"插件","link":"#插件","children":[]},{"level":3,"title":"postcss-preset-env","slug":"postcss-preset-env","link":"#postcss-preset-env","children":[]},{"level":3,"title":"自动的厂商前缀","slug":"自动的厂商前缀","link":"#自动的厂商前缀","children":[]},{"level":3,"title":"未来的CSS语法","slug":"未来的css语法","link":"#未来的css语法","children":[]},{"level":3,"title":"postcss-apply","slug":"postcss-apply","link":"#postcss-apply","children":[]},{"level":3,"title":"postcss-color-function","slug":"postcss-color-function","link":"#postcss-color-function","children":[]},{"level":3,"title":"postcss-import","slug":"postcss-import","link":"#postcss-import","children":[]}]},{"level":2,"title":"stylelint","slug":"stylelint","link":"#stylelint","children":[]},{"level":2,"title":"抽离css文件","slug":"抽离css文件","link":"#抽离css文件","children":[]}],"relativePath":"front-end-engineering/CSS工程化.md","lastUpdated":1672994388000}'),d={name:"front-end-engineering/CSS工程化.md"},u=l("",307),b=[u];function A(m,h,C,g,v,k){return n(),a("div",null,b)}const f=s(d,[["render",A]]);export{E as __pageData,f as default};
diff --git a/assets/front-end-engineering_PackageManager.md.870a6421.js b/assets/front-end-engineering_PackageManager.md.168ce36b.js
similarity index 99%
rename from assets/front-end-engineering_PackageManager.md.870a6421.js
rename to assets/front-end-engineering_PackageManager.md.168ce36b.js
index 7832791f..0bc64783 100644
--- a/assets/front-end-engineering_PackageManager.md.870a6421.js
+++ b/assets/front-end-engineering_PackageManager.md.168ce36b.js
@@ -1,4 +1,4 @@
-import{_ as s,o as n,c as a,a as l}from"./app.679ab08c.js";const p="/blog/assets/2023-02-01-22-03-34.729a63f6.png",e="/blog/assets/2023-02-01-22-03-43.d520fd1e.png",o="/blog/assets/2023-02-01-22-03-59.a0646341.png",r="/blog/assets/2023-02-01-22-04-06.095f01dd.png",c="/blog/assets/2023-02-01-22-05-00.cd9ee97d.png",C=JSON.parse('{"title":"包管理器","description":"","frontmatter":{},"headers":[{"level":2,"title":"包管理工具概述","slug":"包管理工具概述","link":"#包管理工具概述","children":[{"level":3,"title":"1.概念","slug":"_1-概念","link":"#_1-概念","children":[]},{"level":3,"title":"2.背景","slug":"_2-背景","link":"#_2-背景","children":[]},{"level":3,"title":"3.前端包管理器","slug":"_3-前端包管理器","link":"#_3-前端包管理器","children":[]}]},{"level":2,"title":"npm","slug":"npm","link":"#npm","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":[]}]},{"level":2,"title":"语义版本","slug":"语义版本","link":"#语义版本","children":[{"level":3,"title":"1.避免还原的差异","slug":"_1-避免还原的差异","link":"#_1-避免还原的差异","children":[]},{"level":3,"title":"2.[扩展]npm 的差异版本处理","slug":"_2-扩展-npm-的差异版本处理","link":"#_2-扩展-npm-的差异版本处理","children":[]}]},{"level":2,"title":"npm 脚本 (npm scripts)","slug":"npm-脚本-npm-scripts","link":"#npm-脚本-npm-scripts","children":[]},{"level":2,"title":"运行环境配置","slug":"运行环境配置","link":"#运行环境配置","children":[{"level":3,"title":"在 node 中读取 package.json","slug":"在-node-中读取-package-json","link":"#在-node-中读取-package-json","children":[]}]},{"level":2,"title":"其他 npm 命令","slug":"其他-npm-命令","link":"#其他-npm-命令","children":[{"level":3,"title":"1.安装","slug":"_1-安装","link":"#_1-安装","children":[]},{"level":3,"title":"2.查询","slug":"_2-查询","link":"#_2-查询","children":[]},{"level":3,"title":"3.更新","slug":"_3-更新","link":"#_3-更新","children":[]},{"level":3,"title":"4.卸载包","slug":"_4-卸载包","link":"#_4-卸载包","children":[]},{"level":3,"title":"5.npm 配置","slug":"_5-npm-配置","link":"#_5-npm-配置","children":[]}]},{"level":2,"title":"发布包","slug":"发布包","link":"#发布包","children":[{"level":3,"title":"1.准备工作","slug":"_1-准备工作","link":"#_1-准备工作","children":[]},{"level":3,"title":"2.发布","slug":"_2-发布","link":"#_2-发布","children":[]},{"level":3,"title":"开源协议","slug":"开源协议","link":"#开源协议","children":[]}]},{"level":2,"title":"yarn 简介","slug":"yarn-简介","link":"#yarn-简介","children":[{"level":3,"title":"yarn 的核心命令","slug":"yarn-的核心命令","link":"#yarn-的核心命令","children":[]},{"level":3,"title":"yarn 的特别礼物","slug":"yarn-的特别礼物","link":"#yarn-的特别礼物","children":[]}]},{"level":2,"title":"其他包管理器","slug":"其他包管理器","link":"#其他包管理器","children":[{"level":3,"title":"cnpm","slug":"cnpm","link":"#cnpm","children":[]},{"level":3,"title":"nvm","slug":"nvm","link":"#nvm","children":[]},{"level":3,"title":"pnpm","slug":"pnpm","link":"#pnpm","children":[]},{"level":3,"title":"bower","slug":"bower","link":"#bower","children":[]}]}],"relativePath":"front-end-engineering/PackageManager.md","lastUpdated":1675736874000}'),t={name:"front-end-engineering/PackageManager.md"},i=l('

包管理器

包管理工具概述

1.概念

模块(module)

通常以单个文件形式存在的功能片段,入口文件通常称之为入口模块主模块

库(library,简称 lib)

以一个或多个模块组成的完整功能块,为开发中某一方面的问题提供完整的解决方案

包(package)

包含元数据的库,这些元数据包括:名称、描述、git 主页、许可证协议、作者、依赖等等

2.背景

CommonJS 的出现,使 node 环境下的 JS 代码可以用模块更加细粒度的划分。一个类、一个函数、一个对象、一个配置等等均可以作为模块,这种细粒度的划分,是开发大型应用的基石。 为了解决在开发过程中遇到的常见问题,比如加密、提供常见的工具方法、模拟数据等等,一时间,在前端社区涌现了大量的第三方库。这些库使用 CommonJS 标准书写而成,非常容易使用。 然而,在下载使用这些第三方库的时候,遇到难以处理的问题:

  • 下载过程繁琐
    • 进入官网或 github 主页
    • 找到并下载相应的版本
    • 拷贝到工程的目录中
    • 如果遇到有同名的库,需要更改名称
  • 如果该库需要依赖其他库,还需要按照要求先下载其他库
  • 开发环境中安装的大量的库如何在生产环境中还原,又如何区分
  • 更新一个库极度麻烦
  • 自己开发的库,如何在下一次开发使用

以上问题,就是包管理工具要解决的问题

3.前端包管理器

几乎可以这样认为,前端所有的包管理器都是基于 npm 的,目前,npm 即是一个包管理器,也是其他包管理的基石 npm 全称为 node package manager,即 node 包管理器,它运行在 node 环境中,让开发者可以用简单的方式完成包的查找、安装、更新、卸载、上传等操作

npm 之所以要运行在 node 环境,而不是浏览器环境,根本原因是因为浏览器环境无法提供下载、删除、读取本地文件的功能。而 node 属于服务器环境,没有浏览器的种种限制,理论上可以完全掌控运行 node 的计算机。

npm 的出现,弥补了 node 没有包管理器的缺陷,于是很快,node 在安装文件中内置了 npm,当开发者安装好 node 之后,就自动安装了 npm,不仅如此,node 环境还专门为 npm 提供了良好的支持,使用 npm 下载的包更加方便了。

npm 由三部分组成:

  • registry:入口
    • 可以把它想象成一个庞大的数据库
    • 第三方库的开发者,将自己的库按照 npm 的规范,打包上传到数据库中
    • 使用者通过统一的地址下载第三方包
  • 官网:https://www.npmjs.com/
    • 查询包
    • 注册、登录、管理个人信息
  • CLI:command-line interface 命令行接口
    • 这一部分是本门课讲解的重点
    • 安装好 npm 后,通过 CLI 来使用 npm 的各种功能
    • vscode 里面 Ctrl+j

cmd 常用命令 换到 e 盘:e: 查看目录:dir vscode 查看 npm 的当前版本 输入 npm -v 或者 npm --version 注意空格

node 和 npm 是互相成就的,node 的出现让 npm 火了,npm 的火爆带动了大量的第三方库的发展,很多优秀的第三方库打包上传到了 npm,这些第三方库又为 node 带来了大量的用户

npm

包的安装

安装(install)即下载包 由于 npm 的官方 registry 服务器位于国外,可能受网速影响导致下载缓慢或失败。因此,安装好 npm 之后,需要重新设置 registry 的地址为国内地址。目前,淘宝 https://registry.npm.taobao.org 提供了国内的 registry 地址,先设置到该地址。设置方式为npm config set registry [https://registry.npm.taobao.org](https://registry.npm.taobao.org)。设置好后,通过命令npm config get registry进行检查

npm 安装一个包,分为两种安装方式:

  1. 本地安装
  2. 全局安装

1.本地安装

使用命令npm install 包名npm i 包名即可完成本地安装 终端 clear 是清除命令 本地安装的包出现在当前目录下的node_modules目录中

随着开发的进展,node_modules目录会变得异常庞大,目录下的内容不适合直接传输到生产环境,因此通常使用.gitignore文件忽略该目录中的内容 本地安装适用于绝大部分的包,它会在当前目录及其子目录中发挥作用 通常在项目的根目录中使用本地安装 安装一个包的时候,npm 会自动管理依赖,它会下载该包的依赖包到node_modules目录中 例如:react 如果本地安装的包带有 CLI,npm 会将它的 CLI 脚本文件放置到node_modules/.bin下,使用命令npx 命令名即可调用(npx mocha) 例如:mocha

演示 安装 jQuery:命令 npm install jquery

2.全局安装

全局安装的包放置在一个特殊的全局目录,该目录可以通过命令npm config get prefix查看 使用命令npm install --global 包名npm i -g 包名 重要:全局安装的包并非所有工程可用,它仅提供全局的 CLI 工具 大部分情况下,都不需要全局安装包,除非:

  1. 包的版本非常稳定,很少有大的更新
  2. 提供的 CLI 工具在各个工程中使用的非常频繁
  3. CLI 工具仅为开发环境提供支持,而非部署环境

包配置

前节补充:同时下载两个包:命令 npm i jquery loadsh

目前遇到的问题:

  1. 拷贝工程后如何还原?
  2. 如何区分开发依赖和生产依赖?
  3. 如果自身的项目也是一个包,如何描述包的信息

以上这些问题都需要通过包的配置文件解决

1.配置文件

npm 将每个使用 npm 的工程本身都看作是一个包,包的信息需要通过一个名称固定的配置文件来描述 配置文件的名称固定为:package.json 可以手动创建该文件,而更多的时候,是通过命令npm init创建的 配置文件中可以描述大量的信息,包括:

  • name:包的名称,该名称必须是英文单词字符,支持连接符
  • version:版本
    • 版本规范:主版本号.次版本号.补丁版本号
    • 主版本号:仅当程序发生了重大变化时才会增长,如新增了重要功能、新增了大量的 API、技术架构发生了重大变化
    • 次版本号:仅当程序发生了一些小变化时才会增长,如新增了一些小功能、新增了一些辅助型的 API
    • 补丁版本号:仅当解决了一些 bug 或 进行了一些局部优化时更新,如修复了某个函数的 bug、提升了某个函数的运行效率
  • description:包的描述
  • homepage:官网地址
  • author:包的作者,必须是有效的 npm 账户名,书写规范是 account <mail>,例如:zhangsan <zhangsan@gmail.com>,不正确的账号和邮箱可能导致发布包时失败
  • repository:包的仓储地址,通常指 git 或 svn 的地址,它是一个对象
    • type:仓储类型,git 或 svn
    • url:地址

命令:get remote -v

  • main:包的入口文件,使用包的人默认从该入口文件导入包的内容
  • keywords: 搜索关键字,发布包后,可以通过该数组中的关键字搜索到包

    一步到位简化配置 json 文件

使用npm init --yesnpm init -y可以在生成配置文件时自动填充默认配置 操作:创建一个英文文件夹,右键终端打开,输入npm init --yesnpm init -y

2.保存依赖关系

大部分时候,我们仅仅是开发项目,并不会把它打包发布出去,尽管如此,我们仍然需要 package.json 文件 package.json 文件最重要的作用,是记录当前工程的依赖

  • dependencies:生产环境的依赖包
  • devDependencies:仅开发环境的依赖包
json
"dependencies": {
+import{_ as s,o as n,c as a,a as l}from"./app.815d1813.js";const p="/blog/assets/2023-02-01-22-03-34.729a63f6.png",e="/blog/assets/2023-02-01-22-03-43.d520fd1e.png",o="/blog/assets/2023-02-01-22-03-59.a0646341.png",r="/blog/assets/2023-02-01-22-04-06.095f01dd.png",c="/blog/assets/2023-02-01-22-05-00.cd9ee97d.png",C=JSON.parse('{"title":"包管理器","description":"","frontmatter":{},"headers":[{"level":2,"title":"包管理工具概述","slug":"包管理工具概述","link":"#包管理工具概述","children":[{"level":3,"title":"1.概念","slug":"_1-概念","link":"#_1-概念","children":[]},{"level":3,"title":"2.背景","slug":"_2-背景","link":"#_2-背景","children":[]},{"level":3,"title":"3.前端包管理器","slug":"_3-前端包管理器","link":"#_3-前端包管理器","children":[]}]},{"level":2,"title":"npm","slug":"npm","link":"#npm","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":[]}]},{"level":2,"title":"语义版本","slug":"语义版本","link":"#语义版本","children":[{"level":3,"title":"1.避免还原的差异","slug":"_1-避免还原的差异","link":"#_1-避免还原的差异","children":[]},{"level":3,"title":"2.[扩展]npm 的差异版本处理","slug":"_2-扩展-npm-的差异版本处理","link":"#_2-扩展-npm-的差异版本处理","children":[]}]},{"level":2,"title":"npm 脚本 (npm scripts)","slug":"npm-脚本-npm-scripts","link":"#npm-脚本-npm-scripts","children":[]},{"level":2,"title":"运行环境配置","slug":"运行环境配置","link":"#运行环境配置","children":[{"level":3,"title":"在 node 中读取 package.json","slug":"在-node-中读取-package-json","link":"#在-node-中读取-package-json","children":[]}]},{"level":2,"title":"其他 npm 命令","slug":"其他-npm-命令","link":"#其他-npm-命令","children":[{"level":3,"title":"1.安装","slug":"_1-安装","link":"#_1-安装","children":[]},{"level":3,"title":"2.查询","slug":"_2-查询","link":"#_2-查询","children":[]},{"level":3,"title":"3.更新","slug":"_3-更新","link":"#_3-更新","children":[]},{"level":3,"title":"4.卸载包","slug":"_4-卸载包","link":"#_4-卸载包","children":[]},{"level":3,"title":"5.npm 配置","slug":"_5-npm-配置","link":"#_5-npm-配置","children":[]}]},{"level":2,"title":"发布包","slug":"发布包","link":"#发布包","children":[{"level":3,"title":"1.准备工作","slug":"_1-准备工作","link":"#_1-准备工作","children":[]},{"level":3,"title":"2.发布","slug":"_2-发布","link":"#_2-发布","children":[]},{"level":3,"title":"开源协议","slug":"开源协议","link":"#开源协议","children":[]}]},{"level":2,"title":"yarn 简介","slug":"yarn-简介","link":"#yarn-简介","children":[{"level":3,"title":"yarn 的核心命令","slug":"yarn-的核心命令","link":"#yarn-的核心命令","children":[]},{"level":3,"title":"yarn 的特别礼物","slug":"yarn-的特别礼物","link":"#yarn-的特别礼物","children":[]}]},{"level":2,"title":"其他包管理器","slug":"其他包管理器","link":"#其他包管理器","children":[{"level":3,"title":"cnpm","slug":"cnpm","link":"#cnpm","children":[]},{"level":3,"title":"nvm","slug":"nvm","link":"#nvm","children":[]},{"level":3,"title":"pnpm","slug":"pnpm","link":"#pnpm","children":[]},{"level":3,"title":"bower","slug":"bower","link":"#bower","children":[]}]}],"relativePath":"front-end-engineering/PackageManager.md","lastUpdated":1675736874000}'),t={name:"front-end-engineering/PackageManager.md"},i=l('

包管理器

包管理工具概述

1.概念

模块(module)

通常以单个文件形式存在的功能片段,入口文件通常称之为入口模块主模块

库(library,简称 lib)

以一个或多个模块组成的完整功能块,为开发中某一方面的问题提供完整的解决方案

包(package)

包含元数据的库,这些元数据包括:名称、描述、git 主页、许可证协议、作者、依赖等等

2.背景

CommonJS 的出现,使 node 环境下的 JS 代码可以用模块更加细粒度的划分。一个类、一个函数、一个对象、一个配置等等均可以作为模块,这种细粒度的划分,是开发大型应用的基石。 为了解决在开发过程中遇到的常见问题,比如加密、提供常见的工具方法、模拟数据等等,一时间,在前端社区涌现了大量的第三方库。这些库使用 CommonJS 标准书写而成,非常容易使用。 然而,在下载使用这些第三方库的时候,遇到难以处理的问题:

  • 下载过程繁琐
    • 进入官网或 github 主页
    • 找到并下载相应的版本
    • 拷贝到工程的目录中
    • 如果遇到有同名的库,需要更改名称
  • 如果该库需要依赖其他库,还需要按照要求先下载其他库
  • 开发环境中安装的大量的库如何在生产环境中还原,又如何区分
  • 更新一个库极度麻烦
  • 自己开发的库,如何在下一次开发使用

以上问题,就是包管理工具要解决的问题

3.前端包管理器

几乎可以这样认为,前端所有的包管理器都是基于 npm 的,目前,npm 即是一个包管理器,也是其他包管理的基石 npm 全称为 node package manager,即 node 包管理器,它运行在 node 环境中,让开发者可以用简单的方式完成包的查找、安装、更新、卸载、上传等操作

npm 之所以要运行在 node 环境,而不是浏览器环境,根本原因是因为浏览器环境无法提供下载、删除、读取本地文件的功能。而 node 属于服务器环境,没有浏览器的种种限制,理论上可以完全掌控运行 node 的计算机。

npm 的出现,弥补了 node 没有包管理器的缺陷,于是很快,node 在安装文件中内置了 npm,当开发者安装好 node 之后,就自动安装了 npm,不仅如此,node 环境还专门为 npm 提供了良好的支持,使用 npm 下载的包更加方便了。

npm 由三部分组成:

  • registry:入口
    • 可以把它想象成一个庞大的数据库
    • 第三方库的开发者,将自己的库按照 npm 的规范,打包上传到数据库中
    • 使用者通过统一的地址下载第三方包
  • 官网:https://www.npmjs.com/
    • 查询包
    • 注册、登录、管理个人信息
  • CLI:command-line interface 命令行接口
    • 这一部分是本门课讲解的重点
    • 安装好 npm 后,通过 CLI 来使用 npm 的各种功能
    • vscode 里面 Ctrl+j

cmd 常用命令 换到 e 盘:e: 查看目录:dir vscode 查看 npm 的当前版本 输入 npm -v 或者 npm --version 注意空格

node 和 npm 是互相成就的,node 的出现让 npm 火了,npm 的火爆带动了大量的第三方库的发展,很多优秀的第三方库打包上传到了 npm,这些第三方库又为 node 带来了大量的用户

npm

包的安装

安装(install)即下载包 由于 npm 的官方 registry 服务器位于国外,可能受网速影响导致下载缓慢或失败。因此,安装好 npm 之后,需要重新设置 registry 的地址为国内地址。目前,淘宝 https://registry.npm.taobao.org 提供了国内的 registry 地址,先设置到该地址。设置方式为npm config set registry [https://registry.npm.taobao.org](https://registry.npm.taobao.org)。设置好后,通过命令npm config get registry进行检查

npm 安装一个包,分为两种安装方式:

  1. 本地安装
  2. 全局安装

1.本地安装

使用命令npm install 包名npm i 包名即可完成本地安装 终端 clear 是清除命令 本地安装的包出现在当前目录下的node_modules目录中

随着开发的进展,node_modules目录会变得异常庞大,目录下的内容不适合直接传输到生产环境,因此通常使用.gitignore文件忽略该目录中的内容 本地安装适用于绝大部分的包,它会在当前目录及其子目录中发挥作用 通常在项目的根目录中使用本地安装 安装一个包的时候,npm 会自动管理依赖,它会下载该包的依赖包到node_modules目录中 例如:react 如果本地安装的包带有 CLI,npm 会将它的 CLI 脚本文件放置到node_modules/.bin下,使用命令npx 命令名即可调用(npx mocha) 例如:mocha

演示 安装 jQuery:命令 npm install jquery

2.全局安装

全局安装的包放置在一个特殊的全局目录,该目录可以通过命令npm config get prefix查看 使用命令npm install --global 包名npm i -g 包名 重要:全局安装的包并非所有工程可用,它仅提供全局的 CLI 工具 大部分情况下,都不需要全局安装包,除非:

  1. 包的版本非常稳定,很少有大的更新
  2. 提供的 CLI 工具在各个工程中使用的非常频繁
  3. CLI 工具仅为开发环境提供支持,而非部署环境

包配置

前节补充:同时下载两个包:命令 npm i jquery loadsh

目前遇到的问题:

  1. 拷贝工程后如何还原?
  2. 如何区分开发依赖和生产依赖?
  3. 如果自身的项目也是一个包,如何描述包的信息

以上这些问题都需要通过包的配置文件解决

1.配置文件

npm 将每个使用 npm 的工程本身都看作是一个包,包的信息需要通过一个名称固定的配置文件来描述 配置文件的名称固定为:package.json 可以手动创建该文件,而更多的时候,是通过命令npm init创建的 配置文件中可以描述大量的信息,包括:

  • name:包的名称,该名称必须是英文单词字符,支持连接符
  • version:版本
    • 版本规范:主版本号.次版本号.补丁版本号
    • 主版本号:仅当程序发生了重大变化时才会增长,如新增了重要功能、新增了大量的 API、技术架构发生了重大变化
    • 次版本号:仅当程序发生了一些小变化时才会增长,如新增了一些小功能、新增了一些辅助型的 API
    • 补丁版本号:仅当解决了一些 bug 或 进行了一些局部优化时更新,如修复了某个函数的 bug、提升了某个函数的运行效率
  • description:包的描述
  • homepage:官网地址
  • author:包的作者,必须是有效的 npm 账户名,书写规范是 account <mail>,例如:zhangsan <zhangsan@gmail.com>,不正确的账号和邮箱可能导致发布包时失败
  • repository:包的仓储地址,通常指 git 或 svn 的地址,它是一个对象
    • type:仓储类型,git 或 svn
    • url:地址

命令:get remote -v

  • main:包的入口文件,使用包的人默认从该入口文件导入包的内容
  • keywords: 搜索关键字,发布包后,可以通过该数组中的关键字搜索到包

    一步到位简化配置 json 文件

使用npm init --yesnpm init -y可以在生成配置文件时自动填充默认配置 操作:创建一个英文文件夹,右键终端打开,输入npm init --yesnpm init -y

2.保存依赖关系

大部分时候,我们仅仅是开发项目,并不会把它打包发布出去,尽管如此,我们仍然需要 package.json 文件 package.json 文件最重要的作用,是记录当前工程的依赖

  • dependencies:生产环境的依赖包
  • devDependencies:仅开发环境的依赖包
json
"dependencies": {
     "jquery": "latest",//不推荐最新,防止开发冲突
     "lodash": "4.17.15"
 },
diff --git a/assets/front-end-engineering_PackageManager.md.870a6421.lean.js b/assets/front-end-engineering_PackageManager.md.168ce36b.lean.js
similarity index 98%
rename from assets/front-end-engineering_PackageManager.md.870a6421.lean.js
rename to assets/front-end-engineering_PackageManager.md.168ce36b.lean.js
index 0531a3c1..d5606ef9 100644
--- a/assets/front-end-engineering_PackageManager.md.870a6421.lean.js
+++ b/assets/front-end-engineering_PackageManager.md.168ce36b.lean.js
@@ -1 +1 @@
-import{_ as s,o as n,c as a,a as l}from"./app.679ab08c.js";const p="/blog/assets/2023-02-01-22-03-34.729a63f6.png",e="/blog/assets/2023-02-01-22-03-43.d520fd1e.png",o="/blog/assets/2023-02-01-22-03-59.a0646341.png",r="/blog/assets/2023-02-01-22-04-06.095f01dd.png",c="/blog/assets/2023-02-01-22-05-00.cd9ee97d.png",C=JSON.parse('{"title":"包管理器","description":"","frontmatter":{},"headers":[{"level":2,"title":"包管理工具概述","slug":"包管理工具概述","link":"#包管理工具概述","children":[{"level":3,"title":"1.概念","slug":"_1-概念","link":"#_1-概念","children":[]},{"level":3,"title":"2.背景","slug":"_2-背景","link":"#_2-背景","children":[]},{"level":3,"title":"3.前端包管理器","slug":"_3-前端包管理器","link":"#_3-前端包管理器","children":[]}]},{"level":2,"title":"npm","slug":"npm","link":"#npm","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":[]}]},{"level":2,"title":"语义版本","slug":"语义版本","link":"#语义版本","children":[{"level":3,"title":"1.避免还原的差异","slug":"_1-避免还原的差异","link":"#_1-避免还原的差异","children":[]},{"level":3,"title":"2.[扩展]npm 的差异版本处理","slug":"_2-扩展-npm-的差异版本处理","link":"#_2-扩展-npm-的差异版本处理","children":[]}]},{"level":2,"title":"npm 脚本 (npm scripts)","slug":"npm-脚本-npm-scripts","link":"#npm-脚本-npm-scripts","children":[]},{"level":2,"title":"运行环境配置","slug":"运行环境配置","link":"#运行环境配置","children":[{"level":3,"title":"在 node 中读取 package.json","slug":"在-node-中读取-package-json","link":"#在-node-中读取-package-json","children":[]}]},{"level":2,"title":"其他 npm 命令","slug":"其他-npm-命令","link":"#其他-npm-命令","children":[{"level":3,"title":"1.安装","slug":"_1-安装","link":"#_1-安装","children":[]},{"level":3,"title":"2.查询","slug":"_2-查询","link":"#_2-查询","children":[]},{"level":3,"title":"3.更新","slug":"_3-更新","link":"#_3-更新","children":[]},{"level":3,"title":"4.卸载包","slug":"_4-卸载包","link":"#_4-卸载包","children":[]},{"level":3,"title":"5.npm 配置","slug":"_5-npm-配置","link":"#_5-npm-配置","children":[]}]},{"level":2,"title":"发布包","slug":"发布包","link":"#发布包","children":[{"level":3,"title":"1.准备工作","slug":"_1-准备工作","link":"#_1-准备工作","children":[]},{"level":3,"title":"2.发布","slug":"_2-发布","link":"#_2-发布","children":[]},{"level":3,"title":"开源协议","slug":"开源协议","link":"#开源协议","children":[]}]},{"level":2,"title":"yarn 简介","slug":"yarn-简介","link":"#yarn-简介","children":[{"level":3,"title":"yarn 的核心命令","slug":"yarn-的核心命令","link":"#yarn-的核心命令","children":[]},{"level":3,"title":"yarn 的特别礼物","slug":"yarn-的特别礼物","link":"#yarn-的特别礼物","children":[]}]},{"level":2,"title":"其他包管理器","slug":"其他包管理器","link":"#其他包管理器","children":[{"level":3,"title":"cnpm","slug":"cnpm","link":"#cnpm","children":[]},{"level":3,"title":"nvm","slug":"nvm","link":"#nvm","children":[]},{"level":3,"title":"pnpm","slug":"pnpm","link":"#pnpm","children":[]},{"level":3,"title":"bower","slug":"bower","link":"#bower","children":[]}]}],"relativePath":"front-end-engineering/PackageManager.md","lastUpdated":1675736874000}'),t={name:"front-end-engineering/PackageManager.md"},i=l("",255),d=[i];function B(y,u,F,m,h,b){return n(),a("div",null,d)}const v=s(t,[["render",B]]);export{C as __pageData,v as default};
+import{_ as s,o as n,c as a,a as l}from"./app.815d1813.js";const p="/blog/assets/2023-02-01-22-03-34.729a63f6.png",e="/blog/assets/2023-02-01-22-03-43.d520fd1e.png",o="/blog/assets/2023-02-01-22-03-59.a0646341.png",r="/blog/assets/2023-02-01-22-04-06.095f01dd.png",c="/blog/assets/2023-02-01-22-05-00.cd9ee97d.png",C=JSON.parse('{"title":"包管理器","description":"","frontmatter":{},"headers":[{"level":2,"title":"包管理工具概述","slug":"包管理工具概述","link":"#包管理工具概述","children":[{"level":3,"title":"1.概念","slug":"_1-概念","link":"#_1-概念","children":[]},{"level":3,"title":"2.背景","slug":"_2-背景","link":"#_2-背景","children":[]},{"level":3,"title":"3.前端包管理器","slug":"_3-前端包管理器","link":"#_3-前端包管理器","children":[]}]},{"level":2,"title":"npm","slug":"npm","link":"#npm","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":[]}]},{"level":2,"title":"语义版本","slug":"语义版本","link":"#语义版本","children":[{"level":3,"title":"1.避免还原的差异","slug":"_1-避免还原的差异","link":"#_1-避免还原的差异","children":[]},{"level":3,"title":"2.[扩展]npm 的差异版本处理","slug":"_2-扩展-npm-的差异版本处理","link":"#_2-扩展-npm-的差异版本处理","children":[]}]},{"level":2,"title":"npm 脚本 (npm scripts)","slug":"npm-脚本-npm-scripts","link":"#npm-脚本-npm-scripts","children":[]},{"level":2,"title":"运行环境配置","slug":"运行环境配置","link":"#运行环境配置","children":[{"level":3,"title":"在 node 中读取 package.json","slug":"在-node-中读取-package-json","link":"#在-node-中读取-package-json","children":[]}]},{"level":2,"title":"其他 npm 命令","slug":"其他-npm-命令","link":"#其他-npm-命令","children":[{"level":3,"title":"1.安装","slug":"_1-安装","link":"#_1-安装","children":[]},{"level":3,"title":"2.查询","slug":"_2-查询","link":"#_2-查询","children":[]},{"level":3,"title":"3.更新","slug":"_3-更新","link":"#_3-更新","children":[]},{"level":3,"title":"4.卸载包","slug":"_4-卸载包","link":"#_4-卸载包","children":[]},{"level":3,"title":"5.npm 配置","slug":"_5-npm-配置","link":"#_5-npm-配置","children":[]}]},{"level":2,"title":"发布包","slug":"发布包","link":"#发布包","children":[{"level":3,"title":"1.准备工作","slug":"_1-准备工作","link":"#_1-准备工作","children":[]},{"level":3,"title":"2.发布","slug":"_2-发布","link":"#_2-发布","children":[]},{"level":3,"title":"开源协议","slug":"开源协议","link":"#开源协议","children":[]}]},{"level":2,"title":"yarn 简介","slug":"yarn-简介","link":"#yarn-简介","children":[{"level":3,"title":"yarn 的核心命令","slug":"yarn-的核心命令","link":"#yarn-的核心命令","children":[]},{"level":3,"title":"yarn 的特别礼物","slug":"yarn-的特别礼物","link":"#yarn-的特别礼物","children":[]}]},{"level":2,"title":"其他包管理器","slug":"其他包管理器","link":"#其他包管理器","children":[{"level":3,"title":"cnpm","slug":"cnpm","link":"#cnpm","children":[]},{"level":3,"title":"nvm","slug":"nvm","link":"#nvm","children":[]},{"level":3,"title":"pnpm","slug":"pnpm","link":"#pnpm","children":[]},{"level":3,"title":"bower","slug":"bower","link":"#bower","children":[]}]}],"relativePath":"front-end-engineering/PackageManager.md","lastUpdated":1675736874000}'),t={name:"front-end-engineering/PackageManager.md"},i=l("",255),d=[i];function B(y,u,F,m,h,b){return n(),a("div",null,d)}const v=s(t,[["render",B]]);export{C as __pageData,v as default};
diff --git a/assets/front-end-engineering_engineering-onepage.md.2b741042.js b/assets/front-end-engineering_engineering-onepage.md.dbe37a23.js
similarity index 99%
rename from assets/front-end-engineering_engineering-onepage.md.2b741042.js
rename to assets/front-end-engineering_engineering-onepage.md.dbe37a23.js
index 38e5e89d..3c917fdc 100644
--- a/assets/front-end-engineering_engineering-onepage.md.2b741042.js
+++ b/assets/front-end-engineering_engineering-onepage.md.dbe37a23.js
@@ -1,4 +1,4 @@
-import{_ as s,o as n,c as a,a as l}from"./app.679ab08c.js";const u=JSON.parse('{"title":"前端工程化 OnePage","description":"","frontmatter":{},"headers":[{"level":2,"title":"为什么会出现前端工程化","slug":"为什么会出现前端工程化","link":"#为什么会出现前端工程化","children":[]},{"level":2,"title":"nodejs对CommonJS的实现","slug":"nodejs对commonjs的实现","link":"#nodejs对commonjs的实现","children":[]},{"level":2,"title":"nodejs","slug":"nodejs","link":"#nodejs","children":[]},{"level":2,"title":"CommonJS标准和使用","slug":"commonjs标准和使用","link":"#commonjs标准和使用","children":[]},{"level":2,"title":"下面的代码执行结果是什么?","slug":"下面的代码执行结果是什么","link":"#下面的代码执行结果是什么","children":[]},{"level":2,"title":"ES6 module","slug":"es6-module","link":"#es6-module","children":[]},{"level":2,"title":"请对比一下CommonJS和ES Module","slug":"请对比一下commonjs和es-module","link":"#请对比一下commonjs和es-module","children":[]},{"level":2,"title":"对前端工程化,模块化,组件化的理解?","slug":"对前端工程化-模块化-组件化的理解","link":"#对前端工程化-模块化-组件化的理解","children":[]},{"level":2,"title":"webpack 中的 loader 属性和 plugins 属性的区别是什么?","slug":"webpack-中的-loader-属性和-plugins-属性的区别是什么","link":"#webpack-中的-loader-属性和-plugins-属性的区别是什么","children":[]},{"level":2,"title":"webpack 的核心概念都有哪些?","slug":"webpack-的核心概念都有哪些","link":"#webpack-的核心概念都有哪些","children":[]},{"level":2,"title":"ES6 中如何实现模块化的异步加载?","slug":"es6-中如何实现模块化的异步加载","link":"#es6-中如何实现模块化的异步加载","children":[]},{"level":2,"title":"说一下 webpack 中的几种 hash 的实现原理是什么?","slug":"说一下-webpack-中的几种-hash-的实现原理是什么","link":"#说一下-webpack-中的几种-hash-的实现原理是什么","children":[]},{"level":2,"title":"webpack 如果使用了 hash 命名,那是每次都会重新生成 hash 吗?","slug":"webpack-如果使用了-hash-命名-那是每次都会重新生成-hash-吗","link":"#webpack-如果使用了-hash-命名-那是每次都会重新生成-hash-吗","children":[]},{"level":2,"title":"webpack 中是如何处理图片的?","slug":"webpack-中是如何处理图片的","link":"#webpack-中是如何处理图片的","children":[]},{"level":2,"title":"webpack 打包出来的 html 为什么 style 放在头部 script 放在底部?","slug":"webpack-打包出来的-html-为什么-style-放在头部-script-放在底部","link":"#webpack-打包出来的-html-为什么-style-放在头部-script-放在底部","children":[]},{"level":2,"title":"webpack 配置如何实现开发环境不使用 cdn、生产环境使用 cdn?","slug":"webpack-配置如何实现开发环境不使用-cdn、生产环境使用-cdn","link":"#webpack-配置如何实现开发环境不使用-cdn、生产环境使用-cdn","children":[]},{"level":2,"title":"介绍一下 webpack4 中的 tree-shaking 的工作流程?","slug":"介绍一下-webpack4-中的-tree-shaking-的工作流程","link":"#介绍一下-webpack4-中的-tree-shaking-的工作流程","children":[]},{"level":2,"title":"说一下 webpack loader 的作用是什么?","slug":"说一下-webpack-loader-的作用是什么","link":"#说一下-webpack-loader-的作用是什么","children":[]},{"level":2,"title":"在开发过程中如果需要对已有模块进行扩展,如何进行开发保证调用方不受影响?","slug":"在开发过程中如果需要对已有模块进行扩展-如何进行开发保证调用方不受影响","link":"#在开发过程中如果需要对已有模块进行扩展-如何进行开发保证调用方不受影响","children":[]},{"level":2,"title":"export 和 export default 的区别是什么?","slug":"export-和-export-default-的区别是什么","link":"#export-和-export-default-的区别是什么","children":[]},{"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":[]},{"level":2,"title":"webpack 如何实现动态导入?","slug":"webpack-如何实现动态导入","link":"#webpack-如何实现动态导入","children":[]},{"level":2,"title":"说一下 webpack 有哪几种文件指纹","slug":"说一下-webpack-有哪几种文件指纹","link":"#说一下-webpack-有哪几种文件指纹","children":[]},{"level":2,"title":"常用的 webpack Loader 都有哪些?","slug":"常用的-webpack-loader-都有哪些","link":"#常用的-webpack-loader-都有哪些","children":[]},{"level":2,"title":"说一下 webpack 常用插件都有哪些?","slug":"说一下-webpack-常用插件都有哪些","link":"#说一下-webpack-常用插件都有哪些","children":[]},{"level":2,"title":"使用 babel-loader 会有哪些问题,可以怎样优化?","slug":"使用-babel-loader-会有哪些问题-可以怎样优化","link":"#使用-babel-loader-会有哪些问题-可以怎样优化","children":[]},{"level":2,"title":"babel 是如何对 class 进行编译的?","slug":"babel-是如何对-class-进行编译的","link":"#babel-是如何对-class-进行编译的","children":[]},{"level":2,"title":"释一下 babel-polyfill 的作用是什么?","slug":"释一下-babel-polyfill-的作用是什么","link":"#释一下-babel-polyfill-的作用是什么","children":[]},{"level":2,"title":"解释一下 less 的&的操作符是做什么用的?","slug":"解释一下-less-的-的操作符是做什么用的","link":"#解释一下-less-的-的操作符是做什么用的","children":[]},{"level":2,"title":"webpack proxy 工作原理,为什么能解决跨域?","slug":"webpack-proxy-工作原理-为什么能解决跨域","link":"#webpack-proxy-工作原理-为什么能解决跨域","children":[]},{"level":2,"title":"组件发布的是不是所有依赖这个组件库的项目都需要升级?","slug":"组件发布的是不是所有依赖这个组件库的项目都需要升级","link":"#组件发布的是不是所有依赖这个组件库的项目都需要升级","children":[]},{"level":2,"title":"具体说一下 splitchunksplugin 的使用场景及使用方法。(字节跳动)","slug":"具体说一下-splitchunksplugin-的使用场景及使用方法。-字节跳动","link":"#具体说一下-splitchunksplugin-的使用场景及使用方法。-字节跳动","children":[]},{"level":2,"title":"描述一下 webpack 的构建流程?(CVTE)","slug":"描述一下-webpack-的构建流程-cvte","link":"#描述一下-webpack-的构建流程-cvte","children":[]},{"level":2,"title":"解释一下 webpack 插件的实现原理?(CVTE)","slug":"解释一下-webpack-插件的实现原理-cvte","link":"#解释一下-webpack-插件的实现原理-cvte","children":[]},{"level":2,"title":"有用过哪些插件做项目的分析吗?(CVTE)","slug":"有用过哪些插件做项目的分析吗-cvte","link":"#有用过哪些插件做项目的分析吗-cvte","children":[]},{"level":2,"title":"什么是 babel,有什么作用?","slug":"什么是-babel-有什么作用","link":"#什么是-babel-有什么作用","children":[]},{"level":2,"title":"解释一下 npm 模块安装机制是什么?","slug":"解释一下-npm-模块安装机制是什么","link":"#解释一下-npm-模块安装机制是什么","children":[]},{"level":2,"title":"webpack与grunt、gulp的不同?","slug":"webpack与grunt、gulp的不同","link":"#webpack与grunt、gulp的不同","children":[]},{"level":2,"title":"2. 与webpack类似的工具还有哪些?谈谈你为什么最终选择(或放弃)使用webpack?","slug":"_2-与webpack类似的工具还有哪些-谈谈你为什么最终选择-或放弃-使用webpack","link":"#_2-与webpack类似的工具还有哪些-谈谈你为什么最终选择-或放弃-使用webpack","children":[]},{"level":2,"title":"3.有哪些常见的Loader?他们是解决什么问题的?","slug":"_3-有哪些常见的loader-他们是解决什么问题的","link":"#_3-有哪些常见的loader-他们是解决什么问题的","children":[]},{"level":2,"title":"4.有哪些常见的Plugin?他们是解决什么问题的?","slug":"_4-有哪些常见的plugin-他们是解决什么问题的","link":"#_4-有哪些常见的plugin-他们是解决什么问题的","children":[]},{"level":2,"title":"5.Loader和Plugin的不同?","slug":"_5-loader和plugin的不同","link":"#_5-loader和plugin的不同","children":[]},{"level":2,"title":"6.webpack的构建流程是什么?从读取配置到输出文件这个过程尽量说全","slug":"_6-webpack的构建流程是什么-从读取配置到输出文件这个过程尽量说全","link":"#_6-webpack的构建流程是什么-从读取配置到输出文件这个过程尽量说全","children":[]},{"level":2,"title":"7.是否写过Loader和Plugin?描述一下编写loader或plugin的思路?","slug":"_7-是否写过loader和plugin-描述一下编写loader或plugin的思路","link":"#_7-是否写过loader和plugin-描述一下编写loader或plugin的思路","children":[]},{"level":2,"title":"11.怎么配置单页应用?怎么配置多页应用?","slug":"_11-怎么配置单页应用-怎么配置多页应用","link":"#_11-怎么配置单页应用-怎么配置多页应用","children":[]},{"level":2,"title":"12.npm打包时需要注意哪些?如何利用webpack来更好的构建?","slug":"_12-npm打包时需要注意哪些-如何利用webpack来更好的构建","link":"#_12-npm打包时需要注意哪些-如何利用webpack来更好的构建","children":[]},{"level":2,"title":"13.如何在vue项目中实现按需加载?","slug":"_13-如何在vue项目中实现按需加载","link":"#_13-如何在vue项目中实现按需加载","children":[]},{"level":2,"title":"能说说webpack的作用吗?","slug":"能说说webpack的作用吗","link":"#能说说webpack的作用吗","children":[]},{"level":2,"title":"为什么要打包呢?","slug":"为什么要打包呢","link":"#为什么要打包呢","children":[]},{"level":2,"title":"能说说模块化的好处吗?","slug":"能说说模块化的好处吗","link":"#能说说模块化的好处吗","children":[]},{"level":2,"title":"模块化打包方案知道啥,可以说说吗?","slug":"模块化打包方案知道啥-可以说说吗","link":"#模块化打包方案知道啥-可以说说吗","children":[]},{"level":2,"title":"那ES6 模块与 CommonJS 模块的差异有哪些呢?","slug":"那es6-模块与-commonjs-模块的差异有哪些呢","link":"#那es6-模块与-commonjs-模块的差异有哪些呢","children":[]},{"level":2,"title":"webpack的编译(打包)流程说说","slug":"webpack的编译-打包-流程说说","link":"#webpack的编译-打包-流程说说","children":[]},{"level":2,"title":"说一下 Webpack 的热更新原理吧?","slug":"说一下-webpack-的热更新原理吧","link":"#说一下-webpack-的热更新原理吧","children":[]},{"level":2,"title":"路由懒加载的原理","slug":"路由懒加载的原理","link":"#路由懒加载的原理","children":[]},{"level":2,"title":"路由懒加载?","slug":"路由懒加载","link":"#路由懒加载","children":[]},{"level":2,"title":"git 指令","slug":"git-指令","link":"#git-指令","children":[]},{"level":2,"title":"知道git的原理吗?说说他的原理吧","slug":"知道git的原理吗-说说他的原理吧","link":"#知道git的原理吗-说说他的原理吧","children":[]},{"level":2,"title":"package.json 字段","slug":"package-json-字段","link":"#package-json-字段","children":[]},{"level":2,"title":"npm安装和查找机制","slug":"npm安装和查找机制","link":"#npm安装和查找机制","children":[]}],"relativePath":"front-end-engineering/engineering-onepage.md","lastUpdated":1710671080000}'),p={name:"front-end-engineering/engineering-onepage.md"},e=l(`

前端工程化 OnePage

为什么会出现前端工程化

模块化:意味着 JS 总算可以开发大型项目(解决 JS 的复用问题)

组件化:解决 HTML、CSS、JS 的复用问题

工程化:前端项目越来越复杂、前端项目模块(组件)越来越多

为什么前端项目越来越复杂?

在书写前端项目的时候,可能会涉及到使用其他的语言,typescript、less/sass、coffeescript,涉及到编译

在部署的时候,还需要对代码进行丑化、压缩

代码优化:为 CSS 代码添加兼容性前缀

前端项目模块(组件)越来越多?

专门有一个 node_modules 来管理这些可以服用的代码。

前端工程化的出现,归根结底,其实就是要解决开发环境和生产环境不一致的问题。

nodejs对CommonJS的实现

为了实现CommonJS规范,nodejs对模块做出了以下处理

  1. 为了保证高效的执行,仅加载必要的模块。nodejs只有执行到require函数时才会加载并执行模块

nodejs中导入模块,使用相对路径,并且必须以./或../开头,浏览器可以省略./,nodejs不行

  1. 为了隐藏模块中的代码,nodejs执行模块时,会将模块中的所有代码放置到一个函数中执行,以保证不污染全局变量。
javascript
 (function(){
+import{_ as s,o as n,c as a,a as l}from"./app.815d1813.js";const u=JSON.parse('{"title":"前端工程化 OnePage","description":"","frontmatter":{},"headers":[{"level":2,"title":"为什么会出现前端工程化","slug":"为什么会出现前端工程化","link":"#为什么会出现前端工程化","children":[]},{"level":2,"title":"nodejs对CommonJS的实现","slug":"nodejs对commonjs的实现","link":"#nodejs对commonjs的实现","children":[]},{"level":2,"title":"nodejs","slug":"nodejs","link":"#nodejs","children":[]},{"level":2,"title":"CommonJS标准和使用","slug":"commonjs标准和使用","link":"#commonjs标准和使用","children":[]},{"level":2,"title":"下面的代码执行结果是什么?","slug":"下面的代码执行结果是什么","link":"#下面的代码执行结果是什么","children":[]},{"level":2,"title":"ES6 module","slug":"es6-module","link":"#es6-module","children":[]},{"level":2,"title":"请对比一下CommonJS和ES Module","slug":"请对比一下commonjs和es-module","link":"#请对比一下commonjs和es-module","children":[]},{"level":2,"title":"对前端工程化,模块化,组件化的理解?","slug":"对前端工程化-模块化-组件化的理解","link":"#对前端工程化-模块化-组件化的理解","children":[]},{"level":2,"title":"webpack 中的 loader 属性和 plugins 属性的区别是什么?","slug":"webpack-中的-loader-属性和-plugins-属性的区别是什么","link":"#webpack-中的-loader-属性和-plugins-属性的区别是什么","children":[]},{"level":2,"title":"webpack 的核心概念都有哪些?","slug":"webpack-的核心概念都有哪些","link":"#webpack-的核心概念都有哪些","children":[]},{"level":2,"title":"ES6 中如何实现模块化的异步加载?","slug":"es6-中如何实现模块化的异步加载","link":"#es6-中如何实现模块化的异步加载","children":[]},{"level":2,"title":"说一下 webpack 中的几种 hash 的实现原理是什么?","slug":"说一下-webpack-中的几种-hash-的实现原理是什么","link":"#说一下-webpack-中的几种-hash-的实现原理是什么","children":[]},{"level":2,"title":"webpack 如果使用了 hash 命名,那是每次都会重新生成 hash 吗?","slug":"webpack-如果使用了-hash-命名-那是每次都会重新生成-hash-吗","link":"#webpack-如果使用了-hash-命名-那是每次都会重新生成-hash-吗","children":[]},{"level":2,"title":"webpack 中是如何处理图片的?","slug":"webpack-中是如何处理图片的","link":"#webpack-中是如何处理图片的","children":[]},{"level":2,"title":"webpack 打包出来的 html 为什么 style 放在头部 script 放在底部?","slug":"webpack-打包出来的-html-为什么-style-放在头部-script-放在底部","link":"#webpack-打包出来的-html-为什么-style-放在头部-script-放在底部","children":[]},{"level":2,"title":"webpack 配置如何实现开发环境不使用 cdn、生产环境使用 cdn?","slug":"webpack-配置如何实现开发环境不使用-cdn、生产环境使用-cdn","link":"#webpack-配置如何实现开发环境不使用-cdn、生产环境使用-cdn","children":[]},{"level":2,"title":"介绍一下 webpack4 中的 tree-shaking 的工作流程?","slug":"介绍一下-webpack4-中的-tree-shaking-的工作流程","link":"#介绍一下-webpack4-中的-tree-shaking-的工作流程","children":[]},{"level":2,"title":"说一下 webpack loader 的作用是什么?","slug":"说一下-webpack-loader-的作用是什么","link":"#说一下-webpack-loader-的作用是什么","children":[]},{"level":2,"title":"在开发过程中如果需要对已有模块进行扩展,如何进行开发保证调用方不受影响?","slug":"在开发过程中如果需要对已有模块进行扩展-如何进行开发保证调用方不受影响","link":"#在开发过程中如果需要对已有模块进行扩展-如何进行开发保证调用方不受影响","children":[]},{"level":2,"title":"export 和 export default 的区别是什么?","slug":"export-和-export-default-的区别是什么","link":"#export-和-export-default-的区别是什么","children":[]},{"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":[]},{"level":2,"title":"webpack 如何实现动态导入?","slug":"webpack-如何实现动态导入","link":"#webpack-如何实现动态导入","children":[]},{"level":2,"title":"说一下 webpack 有哪几种文件指纹","slug":"说一下-webpack-有哪几种文件指纹","link":"#说一下-webpack-有哪几种文件指纹","children":[]},{"level":2,"title":"常用的 webpack Loader 都有哪些?","slug":"常用的-webpack-loader-都有哪些","link":"#常用的-webpack-loader-都有哪些","children":[]},{"level":2,"title":"说一下 webpack 常用插件都有哪些?","slug":"说一下-webpack-常用插件都有哪些","link":"#说一下-webpack-常用插件都有哪些","children":[]},{"level":2,"title":"使用 babel-loader 会有哪些问题,可以怎样优化?","slug":"使用-babel-loader-会有哪些问题-可以怎样优化","link":"#使用-babel-loader-会有哪些问题-可以怎样优化","children":[]},{"level":2,"title":"babel 是如何对 class 进行编译的?","slug":"babel-是如何对-class-进行编译的","link":"#babel-是如何对-class-进行编译的","children":[]},{"level":2,"title":"释一下 babel-polyfill 的作用是什么?","slug":"释一下-babel-polyfill-的作用是什么","link":"#释一下-babel-polyfill-的作用是什么","children":[]},{"level":2,"title":"解释一下 less 的&的操作符是做什么用的?","slug":"解释一下-less-的-的操作符是做什么用的","link":"#解释一下-less-的-的操作符是做什么用的","children":[]},{"level":2,"title":"webpack proxy 工作原理,为什么能解决跨域?","slug":"webpack-proxy-工作原理-为什么能解决跨域","link":"#webpack-proxy-工作原理-为什么能解决跨域","children":[]},{"level":2,"title":"组件发布的是不是所有依赖这个组件库的项目都需要升级?","slug":"组件发布的是不是所有依赖这个组件库的项目都需要升级","link":"#组件发布的是不是所有依赖这个组件库的项目都需要升级","children":[]},{"level":2,"title":"具体说一下 splitchunksplugin 的使用场景及使用方法。(字节跳动)","slug":"具体说一下-splitchunksplugin-的使用场景及使用方法。-字节跳动","link":"#具体说一下-splitchunksplugin-的使用场景及使用方法。-字节跳动","children":[]},{"level":2,"title":"描述一下 webpack 的构建流程?(CVTE)","slug":"描述一下-webpack-的构建流程-cvte","link":"#描述一下-webpack-的构建流程-cvte","children":[]},{"level":2,"title":"解释一下 webpack 插件的实现原理?(CVTE)","slug":"解释一下-webpack-插件的实现原理-cvte","link":"#解释一下-webpack-插件的实现原理-cvte","children":[]},{"level":2,"title":"有用过哪些插件做项目的分析吗?(CVTE)","slug":"有用过哪些插件做项目的分析吗-cvte","link":"#有用过哪些插件做项目的分析吗-cvte","children":[]},{"level":2,"title":"什么是 babel,有什么作用?","slug":"什么是-babel-有什么作用","link":"#什么是-babel-有什么作用","children":[]},{"level":2,"title":"解释一下 npm 模块安装机制是什么?","slug":"解释一下-npm-模块安装机制是什么","link":"#解释一下-npm-模块安装机制是什么","children":[]},{"level":2,"title":"webpack与grunt、gulp的不同?","slug":"webpack与grunt、gulp的不同","link":"#webpack与grunt、gulp的不同","children":[]},{"level":2,"title":"2. 与webpack类似的工具还有哪些?谈谈你为什么最终选择(或放弃)使用webpack?","slug":"_2-与webpack类似的工具还有哪些-谈谈你为什么最终选择-或放弃-使用webpack","link":"#_2-与webpack类似的工具还有哪些-谈谈你为什么最终选择-或放弃-使用webpack","children":[]},{"level":2,"title":"3.有哪些常见的Loader?他们是解决什么问题的?","slug":"_3-有哪些常见的loader-他们是解决什么问题的","link":"#_3-有哪些常见的loader-他们是解决什么问题的","children":[]},{"level":2,"title":"4.有哪些常见的Plugin?他们是解决什么问题的?","slug":"_4-有哪些常见的plugin-他们是解决什么问题的","link":"#_4-有哪些常见的plugin-他们是解决什么问题的","children":[]},{"level":2,"title":"5.Loader和Plugin的不同?","slug":"_5-loader和plugin的不同","link":"#_5-loader和plugin的不同","children":[]},{"level":2,"title":"6.webpack的构建流程是什么?从读取配置到输出文件这个过程尽量说全","slug":"_6-webpack的构建流程是什么-从读取配置到输出文件这个过程尽量说全","link":"#_6-webpack的构建流程是什么-从读取配置到输出文件这个过程尽量说全","children":[]},{"level":2,"title":"7.是否写过Loader和Plugin?描述一下编写loader或plugin的思路?","slug":"_7-是否写过loader和plugin-描述一下编写loader或plugin的思路","link":"#_7-是否写过loader和plugin-描述一下编写loader或plugin的思路","children":[]},{"level":2,"title":"11.怎么配置单页应用?怎么配置多页应用?","slug":"_11-怎么配置单页应用-怎么配置多页应用","link":"#_11-怎么配置单页应用-怎么配置多页应用","children":[]},{"level":2,"title":"12.npm打包时需要注意哪些?如何利用webpack来更好的构建?","slug":"_12-npm打包时需要注意哪些-如何利用webpack来更好的构建","link":"#_12-npm打包时需要注意哪些-如何利用webpack来更好的构建","children":[]},{"level":2,"title":"13.如何在vue项目中实现按需加载?","slug":"_13-如何在vue项目中实现按需加载","link":"#_13-如何在vue项目中实现按需加载","children":[]},{"level":2,"title":"能说说webpack的作用吗?","slug":"能说说webpack的作用吗","link":"#能说说webpack的作用吗","children":[]},{"level":2,"title":"为什么要打包呢?","slug":"为什么要打包呢","link":"#为什么要打包呢","children":[]},{"level":2,"title":"能说说模块化的好处吗?","slug":"能说说模块化的好处吗","link":"#能说说模块化的好处吗","children":[]},{"level":2,"title":"模块化打包方案知道啥,可以说说吗?","slug":"模块化打包方案知道啥-可以说说吗","link":"#模块化打包方案知道啥-可以说说吗","children":[]},{"level":2,"title":"那ES6 模块与 CommonJS 模块的差异有哪些呢?","slug":"那es6-模块与-commonjs-模块的差异有哪些呢","link":"#那es6-模块与-commonjs-模块的差异有哪些呢","children":[]},{"level":2,"title":"webpack的编译(打包)流程说说","slug":"webpack的编译-打包-流程说说","link":"#webpack的编译-打包-流程说说","children":[]},{"level":2,"title":"说一下 Webpack 的热更新原理吧?","slug":"说一下-webpack-的热更新原理吧","link":"#说一下-webpack-的热更新原理吧","children":[]},{"level":2,"title":"路由懒加载的原理","slug":"路由懒加载的原理","link":"#路由懒加载的原理","children":[]},{"level":2,"title":"路由懒加载?","slug":"路由懒加载","link":"#路由懒加载","children":[]},{"level":2,"title":"git 指令","slug":"git-指令","link":"#git-指令","children":[]},{"level":2,"title":"知道git的原理吗?说说他的原理吧","slug":"知道git的原理吗-说说他的原理吧","link":"#知道git的原理吗-说说他的原理吧","children":[]},{"level":2,"title":"package.json 字段","slug":"package-json-字段","link":"#package-json-字段","children":[]},{"level":2,"title":"npm安装和查找机制","slug":"npm安装和查找机制","link":"#npm安装和查找机制","children":[]}],"relativePath":"front-end-engineering/engineering-onepage.md","lastUpdated":1710671080000}'),p={name:"front-end-engineering/engineering-onepage.md"},e=l(`

前端工程化 OnePage

为什么会出现前端工程化

模块化:意味着 JS 总算可以开发大型项目(解决 JS 的复用问题)

组件化:解决 HTML、CSS、JS 的复用问题

工程化:前端项目越来越复杂、前端项目模块(组件)越来越多

为什么前端项目越来越复杂?

在书写前端项目的时候,可能会涉及到使用其他的语言,typescript、less/sass、coffeescript,涉及到编译

在部署的时候,还需要对代码进行丑化、压缩

代码优化:为 CSS 代码添加兼容性前缀

前端项目模块(组件)越来越多?

专门有一个 node_modules 来管理这些可以服用的代码。

前端工程化的出现,归根结底,其实就是要解决开发环境和生产环境不一致的问题。

nodejs对CommonJS的实现

为了实现CommonJS规范,nodejs对模块做出了以下处理

  1. 为了保证高效的执行,仅加载必要的模块。nodejs只有执行到require函数时才会加载并执行模块

nodejs中导入模块,使用相对路径,并且必须以./或../开头,浏览器可以省略./,nodejs不行

  1. 为了隐藏模块中的代码,nodejs执行模块时,会将模块中的所有代码放置到一个函数中执行,以保证不污染全局变量。
javascript
 (function(){
      //模块中的代码
  })()
 
 (function(){
diff --git a/assets/front-end-engineering_engineering-onepage.md.2b741042.lean.js b/assets/front-end-engineering_engineering-onepage.md.dbe37a23.lean.js
similarity index 99%
rename from assets/front-end-engineering_engineering-onepage.md.2b741042.lean.js
rename to assets/front-end-engineering_engineering-onepage.md.dbe37a23.lean.js
index 38e5e89d..3c917fdc 100644
--- a/assets/front-end-engineering_engineering-onepage.md.2b741042.lean.js
+++ b/assets/front-end-engineering_engineering-onepage.md.dbe37a23.lean.js
@@ -1,4 +1,4 @@
-import{_ as s,o as n,c as a,a as l}from"./app.679ab08c.js";const u=JSON.parse('{"title":"前端工程化 OnePage","description":"","frontmatter":{},"headers":[{"level":2,"title":"为什么会出现前端工程化","slug":"为什么会出现前端工程化","link":"#为什么会出现前端工程化","children":[]},{"level":2,"title":"nodejs对CommonJS的实现","slug":"nodejs对commonjs的实现","link":"#nodejs对commonjs的实现","children":[]},{"level":2,"title":"nodejs","slug":"nodejs","link":"#nodejs","children":[]},{"level":2,"title":"CommonJS标准和使用","slug":"commonjs标准和使用","link":"#commonjs标准和使用","children":[]},{"level":2,"title":"下面的代码执行结果是什么?","slug":"下面的代码执行结果是什么","link":"#下面的代码执行结果是什么","children":[]},{"level":2,"title":"ES6 module","slug":"es6-module","link":"#es6-module","children":[]},{"level":2,"title":"请对比一下CommonJS和ES Module","slug":"请对比一下commonjs和es-module","link":"#请对比一下commonjs和es-module","children":[]},{"level":2,"title":"对前端工程化,模块化,组件化的理解?","slug":"对前端工程化-模块化-组件化的理解","link":"#对前端工程化-模块化-组件化的理解","children":[]},{"level":2,"title":"webpack 中的 loader 属性和 plugins 属性的区别是什么?","slug":"webpack-中的-loader-属性和-plugins-属性的区别是什么","link":"#webpack-中的-loader-属性和-plugins-属性的区别是什么","children":[]},{"level":2,"title":"webpack 的核心概念都有哪些?","slug":"webpack-的核心概念都有哪些","link":"#webpack-的核心概念都有哪些","children":[]},{"level":2,"title":"ES6 中如何实现模块化的异步加载?","slug":"es6-中如何实现模块化的异步加载","link":"#es6-中如何实现模块化的异步加载","children":[]},{"level":2,"title":"说一下 webpack 中的几种 hash 的实现原理是什么?","slug":"说一下-webpack-中的几种-hash-的实现原理是什么","link":"#说一下-webpack-中的几种-hash-的实现原理是什么","children":[]},{"level":2,"title":"webpack 如果使用了 hash 命名,那是每次都会重新生成 hash 吗?","slug":"webpack-如果使用了-hash-命名-那是每次都会重新生成-hash-吗","link":"#webpack-如果使用了-hash-命名-那是每次都会重新生成-hash-吗","children":[]},{"level":2,"title":"webpack 中是如何处理图片的?","slug":"webpack-中是如何处理图片的","link":"#webpack-中是如何处理图片的","children":[]},{"level":2,"title":"webpack 打包出来的 html 为什么 style 放在头部 script 放在底部?","slug":"webpack-打包出来的-html-为什么-style-放在头部-script-放在底部","link":"#webpack-打包出来的-html-为什么-style-放在头部-script-放在底部","children":[]},{"level":2,"title":"webpack 配置如何实现开发环境不使用 cdn、生产环境使用 cdn?","slug":"webpack-配置如何实现开发环境不使用-cdn、生产环境使用-cdn","link":"#webpack-配置如何实现开发环境不使用-cdn、生产环境使用-cdn","children":[]},{"level":2,"title":"介绍一下 webpack4 中的 tree-shaking 的工作流程?","slug":"介绍一下-webpack4-中的-tree-shaking-的工作流程","link":"#介绍一下-webpack4-中的-tree-shaking-的工作流程","children":[]},{"level":2,"title":"说一下 webpack loader 的作用是什么?","slug":"说一下-webpack-loader-的作用是什么","link":"#说一下-webpack-loader-的作用是什么","children":[]},{"level":2,"title":"在开发过程中如果需要对已有模块进行扩展,如何进行开发保证调用方不受影响?","slug":"在开发过程中如果需要对已有模块进行扩展-如何进行开发保证调用方不受影响","link":"#在开发过程中如果需要对已有模块进行扩展-如何进行开发保证调用方不受影响","children":[]},{"level":2,"title":"export 和 export default 的区别是什么?","slug":"export-和-export-default-的区别是什么","link":"#export-和-export-default-的区别是什么","children":[]},{"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":[]},{"level":2,"title":"webpack 如何实现动态导入?","slug":"webpack-如何实现动态导入","link":"#webpack-如何实现动态导入","children":[]},{"level":2,"title":"说一下 webpack 有哪几种文件指纹","slug":"说一下-webpack-有哪几种文件指纹","link":"#说一下-webpack-有哪几种文件指纹","children":[]},{"level":2,"title":"常用的 webpack Loader 都有哪些?","slug":"常用的-webpack-loader-都有哪些","link":"#常用的-webpack-loader-都有哪些","children":[]},{"level":2,"title":"说一下 webpack 常用插件都有哪些?","slug":"说一下-webpack-常用插件都有哪些","link":"#说一下-webpack-常用插件都有哪些","children":[]},{"level":2,"title":"使用 babel-loader 会有哪些问题,可以怎样优化?","slug":"使用-babel-loader-会有哪些问题-可以怎样优化","link":"#使用-babel-loader-会有哪些问题-可以怎样优化","children":[]},{"level":2,"title":"babel 是如何对 class 进行编译的?","slug":"babel-是如何对-class-进行编译的","link":"#babel-是如何对-class-进行编译的","children":[]},{"level":2,"title":"释一下 babel-polyfill 的作用是什么?","slug":"释一下-babel-polyfill-的作用是什么","link":"#释一下-babel-polyfill-的作用是什么","children":[]},{"level":2,"title":"解释一下 less 的&的操作符是做什么用的?","slug":"解释一下-less-的-的操作符是做什么用的","link":"#解释一下-less-的-的操作符是做什么用的","children":[]},{"level":2,"title":"webpack proxy 工作原理,为什么能解决跨域?","slug":"webpack-proxy-工作原理-为什么能解决跨域","link":"#webpack-proxy-工作原理-为什么能解决跨域","children":[]},{"level":2,"title":"组件发布的是不是所有依赖这个组件库的项目都需要升级?","slug":"组件发布的是不是所有依赖这个组件库的项目都需要升级","link":"#组件发布的是不是所有依赖这个组件库的项目都需要升级","children":[]},{"level":2,"title":"具体说一下 splitchunksplugin 的使用场景及使用方法。(字节跳动)","slug":"具体说一下-splitchunksplugin-的使用场景及使用方法。-字节跳动","link":"#具体说一下-splitchunksplugin-的使用场景及使用方法。-字节跳动","children":[]},{"level":2,"title":"描述一下 webpack 的构建流程?(CVTE)","slug":"描述一下-webpack-的构建流程-cvte","link":"#描述一下-webpack-的构建流程-cvte","children":[]},{"level":2,"title":"解释一下 webpack 插件的实现原理?(CVTE)","slug":"解释一下-webpack-插件的实现原理-cvte","link":"#解释一下-webpack-插件的实现原理-cvte","children":[]},{"level":2,"title":"有用过哪些插件做项目的分析吗?(CVTE)","slug":"有用过哪些插件做项目的分析吗-cvte","link":"#有用过哪些插件做项目的分析吗-cvte","children":[]},{"level":2,"title":"什么是 babel,有什么作用?","slug":"什么是-babel-有什么作用","link":"#什么是-babel-有什么作用","children":[]},{"level":2,"title":"解释一下 npm 模块安装机制是什么?","slug":"解释一下-npm-模块安装机制是什么","link":"#解释一下-npm-模块安装机制是什么","children":[]},{"level":2,"title":"webpack与grunt、gulp的不同?","slug":"webpack与grunt、gulp的不同","link":"#webpack与grunt、gulp的不同","children":[]},{"level":2,"title":"2. 与webpack类似的工具还有哪些?谈谈你为什么最终选择(或放弃)使用webpack?","slug":"_2-与webpack类似的工具还有哪些-谈谈你为什么最终选择-或放弃-使用webpack","link":"#_2-与webpack类似的工具还有哪些-谈谈你为什么最终选择-或放弃-使用webpack","children":[]},{"level":2,"title":"3.有哪些常见的Loader?他们是解决什么问题的?","slug":"_3-有哪些常见的loader-他们是解决什么问题的","link":"#_3-有哪些常见的loader-他们是解决什么问题的","children":[]},{"level":2,"title":"4.有哪些常见的Plugin?他们是解决什么问题的?","slug":"_4-有哪些常见的plugin-他们是解决什么问题的","link":"#_4-有哪些常见的plugin-他们是解决什么问题的","children":[]},{"level":2,"title":"5.Loader和Plugin的不同?","slug":"_5-loader和plugin的不同","link":"#_5-loader和plugin的不同","children":[]},{"level":2,"title":"6.webpack的构建流程是什么?从读取配置到输出文件这个过程尽量说全","slug":"_6-webpack的构建流程是什么-从读取配置到输出文件这个过程尽量说全","link":"#_6-webpack的构建流程是什么-从读取配置到输出文件这个过程尽量说全","children":[]},{"level":2,"title":"7.是否写过Loader和Plugin?描述一下编写loader或plugin的思路?","slug":"_7-是否写过loader和plugin-描述一下编写loader或plugin的思路","link":"#_7-是否写过loader和plugin-描述一下编写loader或plugin的思路","children":[]},{"level":2,"title":"11.怎么配置单页应用?怎么配置多页应用?","slug":"_11-怎么配置单页应用-怎么配置多页应用","link":"#_11-怎么配置单页应用-怎么配置多页应用","children":[]},{"level":2,"title":"12.npm打包时需要注意哪些?如何利用webpack来更好的构建?","slug":"_12-npm打包时需要注意哪些-如何利用webpack来更好的构建","link":"#_12-npm打包时需要注意哪些-如何利用webpack来更好的构建","children":[]},{"level":2,"title":"13.如何在vue项目中实现按需加载?","slug":"_13-如何在vue项目中实现按需加载","link":"#_13-如何在vue项目中实现按需加载","children":[]},{"level":2,"title":"能说说webpack的作用吗?","slug":"能说说webpack的作用吗","link":"#能说说webpack的作用吗","children":[]},{"level":2,"title":"为什么要打包呢?","slug":"为什么要打包呢","link":"#为什么要打包呢","children":[]},{"level":2,"title":"能说说模块化的好处吗?","slug":"能说说模块化的好处吗","link":"#能说说模块化的好处吗","children":[]},{"level":2,"title":"模块化打包方案知道啥,可以说说吗?","slug":"模块化打包方案知道啥-可以说说吗","link":"#模块化打包方案知道啥-可以说说吗","children":[]},{"level":2,"title":"那ES6 模块与 CommonJS 模块的差异有哪些呢?","slug":"那es6-模块与-commonjs-模块的差异有哪些呢","link":"#那es6-模块与-commonjs-模块的差异有哪些呢","children":[]},{"level":2,"title":"webpack的编译(打包)流程说说","slug":"webpack的编译-打包-流程说说","link":"#webpack的编译-打包-流程说说","children":[]},{"level":2,"title":"说一下 Webpack 的热更新原理吧?","slug":"说一下-webpack-的热更新原理吧","link":"#说一下-webpack-的热更新原理吧","children":[]},{"level":2,"title":"路由懒加载的原理","slug":"路由懒加载的原理","link":"#路由懒加载的原理","children":[]},{"level":2,"title":"路由懒加载?","slug":"路由懒加载","link":"#路由懒加载","children":[]},{"level":2,"title":"git 指令","slug":"git-指令","link":"#git-指令","children":[]},{"level":2,"title":"知道git的原理吗?说说他的原理吧","slug":"知道git的原理吗-说说他的原理吧","link":"#知道git的原理吗-说说他的原理吧","children":[]},{"level":2,"title":"package.json 字段","slug":"package-json-字段","link":"#package-json-字段","children":[]},{"level":2,"title":"npm安装和查找机制","slug":"npm安装和查找机制","link":"#npm安装和查找机制","children":[]}],"relativePath":"front-end-engineering/engineering-onepage.md","lastUpdated":1710671080000}'),p={name:"front-end-engineering/engineering-onepage.md"},e=l(`

前端工程化 OnePage

为什么会出现前端工程化

模块化:意味着 JS 总算可以开发大型项目(解决 JS 的复用问题)

组件化:解决 HTML、CSS、JS 的复用问题

工程化:前端项目越来越复杂、前端项目模块(组件)越来越多

为什么前端项目越来越复杂?

在书写前端项目的时候,可能会涉及到使用其他的语言,typescript、less/sass、coffeescript,涉及到编译

在部署的时候,还需要对代码进行丑化、压缩

代码优化:为 CSS 代码添加兼容性前缀

前端项目模块(组件)越来越多?

专门有一个 node_modules 来管理这些可以服用的代码。

前端工程化的出现,归根结底,其实就是要解决开发环境和生产环境不一致的问题。

nodejs对CommonJS的实现

为了实现CommonJS规范,nodejs对模块做出了以下处理

  1. 为了保证高效的执行,仅加载必要的模块。nodejs只有执行到require函数时才会加载并执行模块

nodejs中导入模块,使用相对路径,并且必须以./或../开头,浏览器可以省略./,nodejs不行

  1. 为了隐藏模块中的代码,nodejs执行模块时,会将模块中的所有代码放置到一个函数中执行,以保证不污染全局变量。
javascript
 (function(){
+import{_ as s,o as n,c as a,a as l}from"./app.815d1813.js";const u=JSON.parse('{"title":"前端工程化 OnePage","description":"","frontmatter":{},"headers":[{"level":2,"title":"为什么会出现前端工程化","slug":"为什么会出现前端工程化","link":"#为什么会出现前端工程化","children":[]},{"level":2,"title":"nodejs对CommonJS的实现","slug":"nodejs对commonjs的实现","link":"#nodejs对commonjs的实现","children":[]},{"level":2,"title":"nodejs","slug":"nodejs","link":"#nodejs","children":[]},{"level":2,"title":"CommonJS标准和使用","slug":"commonjs标准和使用","link":"#commonjs标准和使用","children":[]},{"level":2,"title":"下面的代码执行结果是什么?","slug":"下面的代码执行结果是什么","link":"#下面的代码执行结果是什么","children":[]},{"level":2,"title":"ES6 module","slug":"es6-module","link":"#es6-module","children":[]},{"level":2,"title":"请对比一下CommonJS和ES Module","slug":"请对比一下commonjs和es-module","link":"#请对比一下commonjs和es-module","children":[]},{"level":2,"title":"对前端工程化,模块化,组件化的理解?","slug":"对前端工程化-模块化-组件化的理解","link":"#对前端工程化-模块化-组件化的理解","children":[]},{"level":2,"title":"webpack 中的 loader 属性和 plugins 属性的区别是什么?","slug":"webpack-中的-loader-属性和-plugins-属性的区别是什么","link":"#webpack-中的-loader-属性和-plugins-属性的区别是什么","children":[]},{"level":2,"title":"webpack 的核心概念都有哪些?","slug":"webpack-的核心概念都有哪些","link":"#webpack-的核心概念都有哪些","children":[]},{"level":2,"title":"ES6 中如何实现模块化的异步加载?","slug":"es6-中如何实现模块化的异步加载","link":"#es6-中如何实现模块化的异步加载","children":[]},{"level":2,"title":"说一下 webpack 中的几种 hash 的实现原理是什么?","slug":"说一下-webpack-中的几种-hash-的实现原理是什么","link":"#说一下-webpack-中的几种-hash-的实现原理是什么","children":[]},{"level":2,"title":"webpack 如果使用了 hash 命名,那是每次都会重新生成 hash 吗?","slug":"webpack-如果使用了-hash-命名-那是每次都会重新生成-hash-吗","link":"#webpack-如果使用了-hash-命名-那是每次都会重新生成-hash-吗","children":[]},{"level":2,"title":"webpack 中是如何处理图片的?","slug":"webpack-中是如何处理图片的","link":"#webpack-中是如何处理图片的","children":[]},{"level":2,"title":"webpack 打包出来的 html 为什么 style 放在头部 script 放在底部?","slug":"webpack-打包出来的-html-为什么-style-放在头部-script-放在底部","link":"#webpack-打包出来的-html-为什么-style-放在头部-script-放在底部","children":[]},{"level":2,"title":"webpack 配置如何实现开发环境不使用 cdn、生产环境使用 cdn?","slug":"webpack-配置如何实现开发环境不使用-cdn、生产环境使用-cdn","link":"#webpack-配置如何实现开发环境不使用-cdn、生产环境使用-cdn","children":[]},{"level":2,"title":"介绍一下 webpack4 中的 tree-shaking 的工作流程?","slug":"介绍一下-webpack4-中的-tree-shaking-的工作流程","link":"#介绍一下-webpack4-中的-tree-shaking-的工作流程","children":[]},{"level":2,"title":"说一下 webpack loader 的作用是什么?","slug":"说一下-webpack-loader-的作用是什么","link":"#说一下-webpack-loader-的作用是什么","children":[]},{"level":2,"title":"在开发过程中如果需要对已有模块进行扩展,如何进行开发保证调用方不受影响?","slug":"在开发过程中如果需要对已有模块进行扩展-如何进行开发保证调用方不受影响","link":"#在开发过程中如果需要对已有模块进行扩展-如何进行开发保证调用方不受影响","children":[]},{"level":2,"title":"export 和 export default 的区别是什么?","slug":"export-和-export-default-的区别是什么","link":"#export-和-export-default-的区别是什么","children":[]},{"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":[]},{"level":2,"title":"webpack 如何实现动态导入?","slug":"webpack-如何实现动态导入","link":"#webpack-如何实现动态导入","children":[]},{"level":2,"title":"说一下 webpack 有哪几种文件指纹","slug":"说一下-webpack-有哪几种文件指纹","link":"#说一下-webpack-有哪几种文件指纹","children":[]},{"level":2,"title":"常用的 webpack Loader 都有哪些?","slug":"常用的-webpack-loader-都有哪些","link":"#常用的-webpack-loader-都有哪些","children":[]},{"level":2,"title":"说一下 webpack 常用插件都有哪些?","slug":"说一下-webpack-常用插件都有哪些","link":"#说一下-webpack-常用插件都有哪些","children":[]},{"level":2,"title":"使用 babel-loader 会有哪些问题,可以怎样优化?","slug":"使用-babel-loader-会有哪些问题-可以怎样优化","link":"#使用-babel-loader-会有哪些问题-可以怎样优化","children":[]},{"level":2,"title":"babel 是如何对 class 进行编译的?","slug":"babel-是如何对-class-进行编译的","link":"#babel-是如何对-class-进行编译的","children":[]},{"level":2,"title":"释一下 babel-polyfill 的作用是什么?","slug":"释一下-babel-polyfill-的作用是什么","link":"#释一下-babel-polyfill-的作用是什么","children":[]},{"level":2,"title":"解释一下 less 的&的操作符是做什么用的?","slug":"解释一下-less-的-的操作符是做什么用的","link":"#解释一下-less-的-的操作符是做什么用的","children":[]},{"level":2,"title":"webpack proxy 工作原理,为什么能解决跨域?","slug":"webpack-proxy-工作原理-为什么能解决跨域","link":"#webpack-proxy-工作原理-为什么能解决跨域","children":[]},{"level":2,"title":"组件发布的是不是所有依赖这个组件库的项目都需要升级?","slug":"组件发布的是不是所有依赖这个组件库的项目都需要升级","link":"#组件发布的是不是所有依赖这个组件库的项目都需要升级","children":[]},{"level":2,"title":"具体说一下 splitchunksplugin 的使用场景及使用方法。(字节跳动)","slug":"具体说一下-splitchunksplugin-的使用场景及使用方法。-字节跳动","link":"#具体说一下-splitchunksplugin-的使用场景及使用方法。-字节跳动","children":[]},{"level":2,"title":"描述一下 webpack 的构建流程?(CVTE)","slug":"描述一下-webpack-的构建流程-cvte","link":"#描述一下-webpack-的构建流程-cvte","children":[]},{"level":2,"title":"解释一下 webpack 插件的实现原理?(CVTE)","slug":"解释一下-webpack-插件的实现原理-cvte","link":"#解释一下-webpack-插件的实现原理-cvte","children":[]},{"level":2,"title":"有用过哪些插件做项目的分析吗?(CVTE)","slug":"有用过哪些插件做项目的分析吗-cvte","link":"#有用过哪些插件做项目的分析吗-cvte","children":[]},{"level":2,"title":"什么是 babel,有什么作用?","slug":"什么是-babel-有什么作用","link":"#什么是-babel-有什么作用","children":[]},{"level":2,"title":"解释一下 npm 模块安装机制是什么?","slug":"解释一下-npm-模块安装机制是什么","link":"#解释一下-npm-模块安装机制是什么","children":[]},{"level":2,"title":"webpack与grunt、gulp的不同?","slug":"webpack与grunt、gulp的不同","link":"#webpack与grunt、gulp的不同","children":[]},{"level":2,"title":"2. 与webpack类似的工具还有哪些?谈谈你为什么最终选择(或放弃)使用webpack?","slug":"_2-与webpack类似的工具还有哪些-谈谈你为什么最终选择-或放弃-使用webpack","link":"#_2-与webpack类似的工具还有哪些-谈谈你为什么最终选择-或放弃-使用webpack","children":[]},{"level":2,"title":"3.有哪些常见的Loader?他们是解决什么问题的?","slug":"_3-有哪些常见的loader-他们是解决什么问题的","link":"#_3-有哪些常见的loader-他们是解决什么问题的","children":[]},{"level":2,"title":"4.有哪些常见的Plugin?他们是解决什么问题的?","slug":"_4-有哪些常见的plugin-他们是解决什么问题的","link":"#_4-有哪些常见的plugin-他们是解决什么问题的","children":[]},{"level":2,"title":"5.Loader和Plugin的不同?","slug":"_5-loader和plugin的不同","link":"#_5-loader和plugin的不同","children":[]},{"level":2,"title":"6.webpack的构建流程是什么?从读取配置到输出文件这个过程尽量说全","slug":"_6-webpack的构建流程是什么-从读取配置到输出文件这个过程尽量说全","link":"#_6-webpack的构建流程是什么-从读取配置到输出文件这个过程尽量说全","children":[]},{"level":2,"title":"7.是否写过Loader和Plugin?描述一下编写loader或plugin的思路?","slug":"_7-是否写过loader和plugin-描述一下编写loader或plugin的思路","link":"#_7-是否写过loader和plugin-描述一下编写loader或plugin的思路","children":[]},{"level":2,"title":"11.怎么配置单页应用?怎么配置多页应用?","slug":"_11-怎么配置单页应用-怎么配置多页应用","link":"#_11-怎么配置单页应用-怎么配置多页应用","children":[]},{"level":2,"title":"12.npm打包时需要注意哪些?如何利用webpack来更好的构建?","slug":"_12-npm打包时需要注意哪些-如何利用webpack来更好的构建","link":"#_12-npm打包时需要注意哪些-如何利用webpack来更好的构建","children":[]},{"level":2,"title":"13.如何在vue项目中实现按需加载?","slug":"_13-如何在vue项目中实现按需加载","link":"#_13-如何在vue项目中实现按需加载","children":[]},{"level":2,"title":"能说说webpack的作用吗?","slug":"能说说webpack的作用吗","link":"#能说说webpack的作用吗","children":[]},{"level":2,"title":"为什么要打包呢?","slug":"为什么要打包呢","link":"#为什么要打包呢","children":[]},{"level":2,"title":"能说说模块化的好处吗?","slug":"能说说模块化的好处吗","link":"#能说说模块化的好处吗","children":[]},{"level":2,"title":"模块化打包方案知道啥,可以说说吗?","slug":"模块化打包方案知道啥-可以说说吗","link":"#模块化打包方案知道啥-可以说说吗","children":[]},{"level":2,"title":"那ES6 模块与 CommonJS 模块的差异有哪些呢?","slug":"那es6-模块与-commonjs-模块的差异有哪些呢","link":"#那es6-模块与-commonjs-模块的差异有哪些呢","children":[]},{"level":2,"title":"webpack的编译(打包)流程说说","slug":"webpack的编译-打包-流程说说","link":"#webpack的编译-打包-流程说说","children":[]},{"level":2,"title":"说一下 Webpack 的热更新原理吧?","slug":"说一下-webpack-的热更新原理吧","link":"#说一下-webpack-的热更新原理吧","children":[]},{"level":2,"title":"路由懒加载的原理","slug":"路由懒加载的原理","link":"#路由懒加载的原理","children":[]},{"level":2,"title":"路由懒加载?","slug":"路由懒加载","link":"#路由懒加载","children":[]},{"level":2,"title":"git 指令","slug":"git-指令","link":"#git-指令","children":[]},{"level":2,"title":"知道git的原理吗?说说他的原理吧","slug":"知道git的原理吗-说说他的原理吧","link":"#知道git的原理吗-说说他的原理吧","children":[]},{"level":2,"title":"package.json 字段","slug":"package-json-字段","link":"#package-json-字段","children":[]},{"level":2,"title":"npm安装和查找机制","slug":"npm安装和查找机制","link":"#npm安装和查找机制","children":[]}],"relativePath":"front-end-engineering/engineering-onepage.md","lastUpdated":1710671080000}'),p={name:"front-end-engineering/engineering-onepage.md"},e=l(`

前端工程化 OnePage

为什么会出现前端工程化

模块化:意味着 JS 总算可以开发大型项目(解决 JS 的复用问题)

组件化:解决 HTML、CSS、JS 的复用问题

工程化:前端项目越来越复杂、前端项目模块(组件)越来越多

为什么前端项目越来越复杂?

在书写前端项目的时候,可能会涉及到使用其他的语言,typescript、less/sass、coffeescript,涉及到编译

在部署的时候,还需要对代码进行丑化、压缩

代码优化:为 CSS 代码添加兼容性前缀

前端项目模块(组件)越来越多?

专门有一个 node_modules 来管理这些可以服用的代码。

前端工程化的出现,归根结底,其实就是要解决开发环境和生产环境不一致的问题。

nodejs对CommonJS的实现

为了实现CommonJS规范,nodejs对模块做出了以下处理

  1. 为了保证高效的执行,仅加载必要的模块。nodejs只有执行到require函数时才会加载并执行模块

nodejs中导入模块,使用相对路径,并且必须以./或../开头,浏览器可以省略./,nodejs不行

  1. 为了隐藏模块中的代码,nodejs执行模块时,会将模块中的所有代码放置到一个函数中执行,以保证不污染全局变量。
javascript
 (function(){
      //模块中的代码
  })()
 
 (function(){
diff --git a/assets/front-end-engineering_jscompatibility.md.3de66593.js b/assets/front-end-engineering_jscompatibility.md.4f967061.js
similarity index 99%
rename from assets/front-end-engineering_jscompatibility.md.3de66593.js
rename to assets/front-end-engineering_jscompatibility.md.4f967061.js
index caf30f39..6604f086 100644
--- a/assets/front-end-engineering_jscompatibility.md.3de66593.js
+++ b/assets/front-end-engineering_jscompatibility.md.4f967061.js
@@ -1,4 +1,4 @@
-import{_ as s,o as n,c as a,a as l}from"./app.679ab08c.js";const p="/blog/assets/2023-01-06-16-46-05.c830893b.png",e="/blog/assets/2023-01-06-16-46-13.ea302e87.png",o="/blog/assets/2023-01-06-16-49-52.3f0e9aff.png",r="/blog/assets/2023-01-06-16-50-09.1857bf9e.png",A=JSON.parse('{"title":"JS兼容性","description":"","frontmatter":{},"headers":[{"level":2,"title":"babel","slug":"babel","link":"#babel","children":[{"level":3,"title":"babel简介","slug":"babel简介","link":"#babel简介","children":[]},{"level":3,"title":"babel的安装","slug":"babel的安装","link":"#babel的安装","children":[]},{"level":3,"title":"babel的使用","slug":"babel的使用","link":"#babel的使用","children":[]},{"level":3,"title":"babel的配置","slug":"babel的配置","link":"#babel的配置","children":[]},{"level":3,"title":"babel预设","slug":"babel预设","link":"#babel预设","children":[]},{"level":3,"title":"babel插件","slug":"babel插件","link":"#babel插件","children":[]}]},{"level":2,"title":"ESLint","slug":"eslint","link":"#eslint","children":[{"level":3,"title":"使用","slug":"使用","link":"#使用","children":[]},{"level":3,"title":"配置","slug":"配置","link":"#配置","children":[]},{"level":3,"title":"env","slug":"env","link":"#env","children":[]},{"level":3,"title":"parserOptions","slug":"parseroptions","link":"#parseroptions","children":[]},{"level":3,"title":"parser","slug":"parser","link":"#parser","children":[]},{"level":3,"title":"globals","slug":"globals","link":"#globals","children":[]},{"level":3,"title":"extends","slug":"extends","link":"#extends","children":[]},{"level":3,"title":"ignoreFiles","slug":"ignorefiles","link":"#ignorefiles","children":[]},{"level":3,"title":"rules","slug":"rules","link":"#rules","children":[]},{"level":3,"title":"ESLint的由来","slug":"eslint的由来","link":"#eslint的由来","children":[]},{"level":3,"title":"如何验证","slug":"如何验证","link":"#如何验证","children":[]},{"level":3,"title":"配置规则","slug":"配置规则","link":"#配置规则","children":[]},{"level":3,"title":"在VSCode中及时发现问题","slug":"在vscode中及时发现问题","link":"#在vscode中及时发现问题","children":[]},{"level":3,"title":"使用继承","slug":"使用继承","link":"#使用继承","children":[]}]},{"level":2,"title":"企业开发的实际情况","slug":"企业开发的实际情况","link":"#企业开发的实际情况","children":[]}],"relativePath":"front-end-engineering/jscompatibility.md","lastUpdated":1672995138000}'),c={name:"front-end-engineering/jscompatibility.md"},t=l('

JS兼容性

babel

官网:https://babeljs.io/

民间中文网:https://www.babeljs.cn/

babel简介

babel一词来自于希伯来语,直译为巴别塔

巴别塔象征的统一的国度、统一的语言

而今天的JS世界缺少一座巴别塔,不同版本的浏览器能识别的ES标准并不相同,就导致了开发者面对不同版本的浏览器要使用不同的语言,和古巴比伦一样,前端开发也面临着这样的困境。

babel的出现,就是用于解决这样的问题,它是一个编译器,可以把不同标准书写的语言,编译为统一的、能被各种浏览器识别的语言

由于语言的转换工作灵活多样,babel的做法和postcss、webpack差不多,它本身仅提供一些分析功能,真正的转换需要依托于插件完成

babel的安装

babel可以和构建工具联合使用,也可以独立使用

如果要独立的使用babel,需要安装下面两个库:

  • @babel/core:babel核心库,提供了编译所需的所有api
  • @babel/cli:提供一个命令行工具,调用核心库的api完成编译
shell
npm i -D @babel/core @babel/cli
+import{_ as s,o as n,c as a,a as l}from"./app.815d1813.js";const p="/blog/assets/2023-01-06-16-46-05.c830893b.png",e="/blog/assets/2023-01-06-16-46-13.ea302e87.png",o="/blog/assets/2023-01-06-16-49-52.3f0e9aff.png",r="/blog/assets/2023-01-06-16-50-09.1857bf9e.png",A=JSON.parse('{"title":"JS兼容性","description":"","frontmatter":{},"headers":[{"level":2,"title":"babel","slug":"babel","link":"#babel","children":[{"level":3,"title":"babel简介","slug":"babel简介","link":"#babel简介","children":[]},{"level":3,"title":"babel的安装","slug":"babel的安装","link":"#babel的安装","children":[]},{"level":3,"title":"babel的使用","slug":"babel的使用","link":"#babel的使用","children":[]},{"level":3,"title":"babel的配置","slug":"babel的配置","link":"#babel的配置","children":[]},{"level":3,"title":"babel预设","slug":"babel预设","link":"#babel预设","children":[]},{"level":3,"title":"babel插件","slug":"babel插件","link":"#babel插件","children":[]}]},{"level":2,"title":"ESLint","slug":"eslint","link":"#eslint","children":[{"level":3,"title":"使用","slug":"使用","link":"#使用","children":[]},{"level":3,"title":"配置","slug":"配置","link":"#配置","children":[]},{"level":3,"title":"env","slug":"env","link":"#env","children":[]},{"level":3,"title":"parserOptions","slug":"parseroptions","link":"#parseroptions","children":[]},{"level":3,"title":"parser","slug":"parser","link":"#parser","children":[]},{"level":3,"title":"globals","slug":"globals","link":"#globals","children":[]},{"level":3,"title":"extends","slug":"extends","link":"#extends","children":[]},{"level":3,"title":"ignoreFiles","slug":"ignorefiles","link":"#ignorefiles","children":[]},{"level":3,"title":"rules","slug":"rules","link":"#rules","children":[]},{"level":3,"title":"ESLint的由来","slug":"eslint的由来","link":"#eslint的由来","children":[]},{"level":3,"title":"如何验证","slug":"如何验证","link":"#如何验证","children":[]},{"level":3,"title":"配置规则","slug":"配置规则","link":"#配置规则","children":[]},{"level":3,"title":"在VSCode中及时发现问题","slug":"在vscode中及时发现问题","link":"#在vscode中及时发现问题","children":[]},{"level":3,"title":"使用继承","slug":"使用继承","link":"#使用继承","children":[]}]},{"level":2,"title":"企业开发的实际情况","slug":"企业开发的实际情况","link":"#企业开发的实际情况","children":[]}],"relativePath":"front-end-engineering/jscompatibility.md","lastUpdated":1672995138000}'),c={name:"front-end-engineering/jscompatibility.md"},t=l('

JS兼容性

babel

官网:https://babeljs.io/

民间中文网:https://www.babeljs.cn/

babel简介

babel一词来自于希伯来语,直译为巴别塔

巴别塔象征的统一的国度、统一的语言

而今天的JS世界缺少一座巴别塔,不同版本的浏览器能识别的ES标准并不相同,就导致了开发者面对不同版本的浏览器要使用不同的语言,和古巴比伦一样,前端开发也面临着这样的困境。

babel的出现,就是用于解决这样的问题,它是一个编译器,可以把不同标准书写的语言,编译为统一的、能被各种浏览器识别的语言

由于语言的转换工作灵活多样,babel的做法和postcss、webpack差不多,它本身仅提供一些分析功能,真正的转换需要依托于插件完成

babel的安装

babel可以和构建工具联合使用,也可以独立使用

如果要独立的使用babel,需要安装下面两个库:

  • @babel/core:babel核心库,提供了编译所需的所有api
  • @babel/cli:提供一个命令行工具,调用核心库的api完成编译
shell
npm i -D @babel/core @babel/cli
 
npm i -D @babel/core @babel/cli
 

babel的使用

@babel/cli的使用极其简单

它提供了一个命令babel

shell
# 按文件编译
 babel 要编译的文件 -o 编辑结果文件
diff --git a/assets/front-end-engineering_jscompatibility.md.3de66593.lean.js b/assets/front-end-engineering_jscompatibility.md.4f967061.lean.js
similarity index 97%
rename from assets/front-end-engineering_jscompatibility.md.3de66593.lean.js
rename to assets/front-end-engineering_jscompatibility.md.4f967061.lean.js
index 6e04cbf4..54948a79 100644
--- a/assets/front-end-engineering_jscompatibility.md.3de66593.lean.js
+++ b/assets/front-end-engineering_jscompatibility.md.4f967061.lean.js
@@ -1 +1 @@
-import{_ as s,o as n,c as a,a as l}from"./app.679ab08c.js";const p="/blog/assets/2023-01-06-16-46-05.c830893b.png",e="/blog/assets/2023-01-06-16-46-13.ea302e87.png",o="/blog/assets/2023-01-06-16-49-52.3f0e9aff.png",r="/blog/assets/2023-01-06-16-50-09.1857bf9e.png",A=JSON.parse('{"title":"JS兼容性","description":"","frontmatter":{},"headers":[{"level":2,"title":"babel","slug":"babel","link":"#babel","children":[{"level":3,"title":"babel简介","slug":"babel简介","link":"#babel简介","children":[]},{"level":3,"title":"babel的安装","slug":"babel的安装","link":"#babel的安装","children":[]},{"level":3,"title":"babel的使用","slug":"babel的使用","link":"#babel的使用","children":[]},{"level":3,"title":"babel的配置","slug":"babel的配置","link":"#babel的配置","children":[]},{"level":3,"title":"babel预设","slug":"babel预设","link":"#babel预设","children":[]},{"level":3,"title":"babel插件","slug":"babel插件","link":"#babel插件","children":[]}]},{"level":2,"title":"ESLint","slug":"eslint","link":"#eslint","children":[{"level":3,"title":"使用","slug":"使用","link":"#使用","children":[]},{"level":3,"title":"配置","slug":"配置","link":"#配置","children":[]},{"level":3,"title":"env","slug":"env","link":"#env","children":[]},{"level":3,"title":"parserOptions","slug":"parseroptions","link":"#parseroptions","children":[]},{"level":3,"title":"parser","slug":"parser","link":"#parser","children":[]},{"level":3,"title":"globals","slug":"globals","link":"#globals","children":[]},{"level":3,"title":"extends","slug":"extends","link":"#extends","children":[]},{"level":3,"title":"ignoreFiles","slug":"ignorefiles","link":"#ignorefiles","children":[]},{"level":3,"title":"rules","slug":"rules","link":"#rules","children":[]},{"level":3,"title":"ESLint的由来","slug":"eslint的由来","link":"#eslint的由来","children":[]},{"level":3,"title":"如何验证","slug":"如何验证","link":"#如何验证","children":[]},{"level":3,"title":"配置规则","slug":"配置规则","link":"#配置规则","children":[]},{"level":3,"title":"在VSCode中及时发现问题","slug":"在vscode中及时发现问题","link":"#在vscode中及时发现问题","children":[]},{"level":3,"title":"使用继承","slug":"使用继承","link":"#使用继承","children":[]}]},{"level":2,"title":"企业开发的实际情况","slug":"企业开发的实际情况","link":"#企业开发的实际情况","children":[]}],"relativePath":"front-end-engineering/jscompatibility.md","lastUpdated":1672995138000}'),c={name:"front-end-engineering/jscompatibility.md"},t=l("",157),i=[t];function B(d,y,b,u,F,h){return n(),a("div",null,i)}const g=s(c,[["render",B]]);export{A as __pageData,g as default};
+import{_ as s,o as n,c as a,a as l}from"./app.815d1813.js";const p="/blog/assets/2023-01-06-16-46-05.c830893b.png",e="/blog/assets/2023-01-06-16-46-13.ea302e87.png",o="/blog/assets/2023-01-06-16-49-52.3f0e9aff.png",r="/blog/assets/2023-01-06-16-50-09.1857bf9e.png",A=JSON.parse('{"title":"JS兼容性","description":"","frontmatter":{},"headers":[{"level":2,"title":"babel","slug":"babel","link":"#babel","children":[{"level":3,"title":"babel简介","slug":"babel简介","link":"#babel简介","children":[]},{"level":3,"title":"babel的安装","slug":"babel的安装","link":"#babel的安装","children":[]},{"level":3,"title":"babel的使用","slug":"babel的使用","link":"#babel的使用","children":[]},{"level":3,"title":"babel的配置","slug":"babel的配置","link":"#babel的配置","children":[]},{"level":3,"title":"babel预设","slug":"babel预设","link":"#babel预设","children":[]},{"level":3,"title":"babel插件","slug":"babel插件","link":"#babel插件","children":[]}]},{"level":2,"title":"ESLint","slug":"eslint","link":"#eslint","children":[{"level":3,"title":"使用","slug":"使用","link":"#使用","children":[]},{"level":3,"title":"配置","slug":"配置","link":"#配置","children":[]},{"level":3,"title":"env","slug":"env","link":"#env","children":[]},{"level":3,"title":"parserOptions","slug":"parseroptions","link":"#parseroptions","children":[]},{"level":3,"title":"parser","slug":"parser","link":"#parser","children":[]},{"level":3,"title":"globals","slug":"globals","link":"#globals","children":[]},{"level":3,"title":"extends","slug":"extends","link":"#extends","children":[]},{"level":3,"title":"ignoreFiles","slug":"ignorefiles","link":"#ignorefiles","children":[]},{"level":3,"title":"rules","slug":"rules","link":"#rules","children":[]},{"level":3,"title":"ESLint的由来","slug":"eslint的由来","link":"#eslint的由来","children":[]},{"level":3,"title":"如何验证","slug":"如何验证","link":"#如何验证","children":[]},{"level":3,"title":"配置规则","slug":"配置规则","link":"#配置规则","children":[]},{"level":3,"title":"在VSCode中及时发现问题","slug":"在vscode中及时发现问题","link":"#在vscode中及时发现问题","children":[]},{"level":3,"title":"使用继承","slug":"使用继承","link":"#使用继承","children":[]}]},{"level":2,"title":"企业开发的实际情况","slug":"企业开发的实际情况","link":"#企业开发的实际情况","children":[]}],"relativePath":"front-end-engineering/jscompatibility.md","lastUpdated":1672995138000}'),c={name:"front-end-engineering/jscompatibility.md"},t=l("",157),i=[t];function B(d,y,b,u,F,h){return n(),a("div",null,i)}const g=s(c,[["render",B]]);export{A as __pageData,g as default};
diff --git a/assets/front-end-engineering_modularization.md.18e98187.js b/assets/front-end-engineering_modularization.md.3f1f0912.js
similarity index 99%
rename from assets/front-end-engineering_modularization.md.18e98187.js
rename to assets/front-end-engineering_modularization.md.3f1f0912.js
index 61716309..9e03cf11 100644
--- a/assets/front-end-engineering_modularization.md.18e98187.js
+++ b/assets/front-end-engineering_modularization.md.3f1f0912.js
@@ -1,4 +1,4 @@
-import{_ as s,o as a,c as n,a as l}from"./app.679ab08c.js";const p="/blog/assets/2023-02-01-21-59-20.1fa5b4ed.png",o="/blog/assets/2023-02-01-21-59-43.f3f27dad.png",e="/blog/assets/2023-02-01-22-00-22.5f64024d.png",r="/blog/assets/2023-02-01-22-00-43.9fbe4838.png",c="/blog/assets/2023-02-01-22-01-01.8aaf55c5.png",t="/blog/assets/2023-02-01-22-01-19.d349110a.png",v=JSON.parse('{"title":"模块化","description":"","frontmatter":{},"headers":[{"level":2,"title":"1.JavaScript 模块化发展史","slug":"_1-javascript-模块化发展史","link":"#_1-javascript-模块化发展史","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":[]}]},{"level":2,"title":"2. CommonJS","slug":"_2-commonjs","link":"#_2-commonjs","children":[{"level":3,"title":"2-2. CommonJS","slug":"_2-2-commonjs","link":"#_2-2-commonjs","children":[]},{"level":3,"title":"模块的导出","slug":"模块的导出","link":"#模块的导出","children":[]},{"level":3,"title":"模块的导入","slug":"模块的导入","link":"#模块的导入","children":[]},{"level":3,"title":"CommonJS 规范","slug":"commonjs-规范","link":"#commonjs-规范","children":[]}]},{"level":2,"title":"3.AMD 和 CMD","slug":"_3-amd-和-cmd","link":"#_3-amd-和-cmd","children":[{"level":3,"title":"3-1 浏览器端模块化的难题","slug":"_3-1-浏览器端模块化的难题","link":"#_3-1-浏览器端模块化的难题","children":[]},{"level":3,"title":"3-2AMD","slug":"_3-2amd","link":"#_3-2amd","children":[]},{"level":3,"title":"3-3CMD","slug":"_3-3cmd","link":"#_3-3cmd","children":[]}]},{"level":2,"title":"4.es6 模块化","slug":"_4-es6-模块化","link":"#_4-es6-模块化","children":[{"level":3,"title":"4-1.ES6 模块化简介","slug":"_4-1-es6-模块化简介","link":"#_4-1-es6-模块化简介","children":[]},{"level":3,"title":"4-2.基本导入导出","slug":"_4-2-基本导入导出","link":"#_4-2-基本导入导出","children":[]},{"level":3,"title":"模块的引入","slug":"模块的引入","link":"#模块的引入","children":[]},{"level":3,"title":"4-3. 默认导入导出","slug":"_4-3-默认导入导出","link":"#_4-3-默认导入导出","children":[]},{"level":3,"title":"4-4.ES6 模块化的其他细节","slug":"_4-4-es6-模块化的其他细节","link":"#_4-4-es6-模块化的其他细节","children":[]}]}],"relativePath":"front-end-engineering/modularization.md","lastUpdated":1675736874000}'),i={name:"front-end-engineering/modularization.md"},B=l(`

模块化

1.JavaScript 模块化发展史

第一阶段

在 JavaScript 语言刚刚诞生的时候,它仅仅用于实现页面中的一些小效果 那个时候,一个页面所用到的 JS 可能只有区区几百行的代码 在这种情况下,语言本身所存在的一些缺陷往往被大家有意的忽略,因为程序的规模实在太小,只要开发人员小心谨慎,往往不会造成什么问题 在这个阶段,也不存在专业的前端工程师,由于前端要做的事情实在太少,因此这一部分工作往往由后端工程师顺带完成 第一阶段发生的大事件:

  • 1996 年,NetScape 将 JavaScript 语言提交给欧洲的一个标准制定组织 ECMA(欧洲计算机制造商协会)
  • 1998 年,NetScape 在与微软浏览器 IE 的竞争中失利,宣布破产

第二阶段

ajax 的出现,逐渐改变了 JavaScript 在浏览器中扮演的角色。现在,它不仅可以实现小的效果,还可以和服务器之间进行交互,以更好的体验来改变数据 JS 代码的数量开始逐渐增长,从最初的几百行,到后来的几万行,前端程序逐渐变得复杂 后端开发者压力逐渐增加,致使一些公司开始招募专业的前端开发者 但此时,前端开发者的待遇远不及后端开发者,因为前端开发者承担的开发任务相对于后端开发来说,还是比较简单的,通过短短一个月的时间集训,就可以成为满足前端开发的需要 究其根本原因,是因为前端开发还有几个大的问题没有解决,这些问题都严重的制约了前端程序的规模进一步扩大:

  1. 浏览器解释执行 JS 的速度太慢
  2. 用户端的电脑配置不足
  3. 更多的代码带来了全局变量污染、依赖关系混乱等问题

上面三个问题,就像是阿喀琉斯之踵,成为前端开发挥之不去的阴影和原罪。 在这个阶段,前端开发处在一个非常尴尬的境地,它在传统的开发模式和前后端分离之间无助的徘徊 第二阶段的大事件:

  1. IE 浏览器制霸市场后,几乎不再更新
  2. ES4.0 流产,导致 JS 语言 10 年间几乎毫无变化
  3. 2008 年 ES5 发布,仅解决了一些 JS API 不足的糟糕局面

第三阶段

时间继续向前推移,到了 2008 年,谷歌的 V8 引擎发布(面试),将 JS 的执行速度推上了一个新的台阶,甚至可以和后端语言媲美。 摩尔定律持续发酵,个人电脑的配置开始飞跃 突然间,制约前端发展的两大问题得以解决,此时,只剩下最后一个问题还在负隅顽抗,即全局变量污染和依赖混乱的问题,解决了它,前端便可以突破一切障碍,未来无可限量。 于是,全世界的前端开发者在社区中激烈的讨论,想要为这个问题寻求解决之道...... 2008 年,有一个名叫 Ryan Dahl 小伙子正在为一件事焦头烂额,它需要在服务器端手写一个高性能的 web 服务,该服务对于性能要求之高,以至于目前市面上已有的 web 服务产品都满足不了需求。

服务器开发

新浪的服务器(电脑)收到请求 其中一个应用程序在做以下的事情 web 服务

  1. 监听 80 端口
  2. 将请求进行分析
  3. 将分析的结果交给相应的程序(php,Java)进行处理
  4. 把程序处理的结果返还给客户端

经过分析,它确定,如果要实现高性能,那么必须要尽可能的减少线程,而要减少线程,避免不了要实用异步的处理方案。 一开始,他打算自己实用 C/C++语言来编写,可是这一过程实在太痛苦。 就在他一筹莫展的时候,谷歌 V8 引擎的发布引起了他的注意,他突然发现,JS 不就是最好的实现 web 服务的语言吗?它天生就是单线程,并且是基于异步的!有了 V8 引擎的支撑,它的执行速度完全可以撑起一个服务器。而且 V8 是鼎鼎大名的谷歌公司发布的,谷歌一定会不断的优化 V8,有这种又省钱又省力的好事,我干嘛还要自己去写呢? 典型异步场景

javascript
setTimeout(function () {
+import{_ as s,o as a,c as n,a as l}from"./app.815d1813.js";const p="/blog/assets/2023-02-01-21-59-20.1fa5b4ed.png",o="/blog/assets/2023-02-01-21-59-43.f3f27dad.png",e="/blog/assets/2023-02-01-22-00-22.5f64024d.png",r="/blog/assets/2023-02-01-22-00-43.9fbe4838.png",c="/blog/assets/2023-02-01-22-01-01.8aaf55c5.png",t="/blog/assets/2023-02-01-22-01-19.d349110a.png",v=JSON.parse('{"title":"模块化","description":"","frontmatter":{},"headers":[{"level":2,"title":"1.JavaScript 模块化发展史","slug":"_1-javascript-模块化发展史","link":"#_1-javascript-模块化发展史","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":[]}]},{"level":2,"title":"2. CommonJS","slug":"_2-commonjs","link":"#_2-commonjs","children":[{"level":3,"title":"2-2. CommonJS","slug":"_2-2-commonjs","link":"#_2-2-commonjs","children":[]},{"level":3,"title":"模块的导出","slug":"模块的导出","link":"#模块的导出","children":[]},{"level":3,"title":"模块的导入","slug":"模块的导入","link":"#模块的导入","children":[]},{"level":3,"title":"CommonJS 规范","slug":"commonjs-规范","link":"#commonjs-规范","children":[]}]},{"level":2,"title":"3.AMD 和 CMD","slug":"_3-amd-和-cmd","link":"#_3-amd-和-cmd","children":[{"level":3,"title":"3-1 浏览器端模块化的难题","slug":"_3-1-浏览器端模块化的难题","link":"#_3-1-浏览器端模块化的难题","children":[]},{"level":3,"title":"3-2AMD","slug":"_3-2amd","link":"#_3-2amd","children":[]},{"level":3,"title":"3-3CMD","slug":"_3-3cmd","link":"#_3-3cmd","children":[]}]},{"level":2,"title":"4.es6 模块化","slug":"_4-es6-模块化","link":"#_4-es6-模块化","children":[{"level":3,"title":"4-1.ES6 模块化简介","slug":"_4-1-es6-模块化简介","link":"#_4-1-es6-模块化简介","children":[]},{"level":3,"title":"4-2.基本导入导出","slug":"_4-2-基本导入导出","link":"#_4-2-基本导入导出","children":[]},{"level":3,"title":"模块的引入","slug":"模块的引入","link":"#模块的引入","children":[]},{"level":3,"title":"4-3. 默认导入导出","slug":"_4-3-默认导入导出","link":"#_4-3-默认导入导出","children":[]},{"level":3,"title":"4-4.ES6 模块化的其他细节","slug":"_4-4-es6-模块化的其他细节","link":"#_4-4-es6-模块化的其他细节","children":[]}]}],"relativePath":"front-end-engineering/modularization.md","lastUpdated":1675736874000}'),i={name:"front-end-engineering/modularization.md"},B=l(`

模块化

1.JavaScript 模块化发展史

第一阶段

在 JavaScript 语言刚刚诞生的时候,它仅仅用于实现页面中的一些小效果 那个时候,一个页面所用到的 JS 可能只有区区几百行的代码 在这种情况下,语言本身所存在的一些缺陷往往被大家有意的忽略,因为程序的规模实在太小,只要开发人员小心谨慎,往往不会造成什么问题 在这个阶段,也不存在专业的前端工程师,由于前端要做的事情实在太少,因此这一部分工作往往由后端工程师顺带完成 第一阶段发生的大事件:

  • 1996 年,NetScape 将 JavaScript 语言提交给欧洲的一个标准制定组织 ECMA(欧洲计算机制造商协会)
  • 1998 年,NetScape 在与微软浏览器 IE 的竞争中失利,宣布破产

第二阶段

ajax 的出现,逐渐改变了 JavaScript 在浏览器中扮演的角色。现在,它不仅可以实现小的效果,还可以和服务器之间进行交互,以更好的体验来改变数据 JS 代码的数量开始逐渐增长,从最初的几百行,到后来的几万行,前端程序逐渐变得复杂 后端开发者压力逐渐增加,致使一些公司开始招募专业的前端开发者 但此时,前端开发者的待遇远不及后端开发者,因为前端开发者承担的开发任务相对于后端开发来说,还是比较简单的,通过短短一个月的时间集训,就可以成为满足前端开发的需要 究其根本原因,是因为前端开发还有几个大的问题没有解决,这些问题都严重的制约了前端程序的规模进一步扩大:

  1. 浏览器解释执行 JS 的速度太慢
  2. 用户端的电脑配置不足
  3. 更多的代码带来了全局变量污染、依赖关系混乱等问题

上面三个问题,就像是阿喀琉斯之踵,成为前端开发挥之不去的阴影和原罪。 在这个阶段,前端开发处在一个非常尴尬的境地,它在传统的开发模式和前后端分离之间无助的徘徊 第二阶段的大事件:

  1. IE 浏览器制霸市场后,几乎不再更新
  2. ES4.0 流产,导致 JS 语言 10 年间几乎毫无变化
  3. 2008 年 ES5 发布,仅解决了一些 JS API 不足的糟糕局面

第三阶段

时间继续向前推移,到了 2008 年,谷歌的 V8 引擎发布(面试),将 JS 的执行速度推上了一个新的台阶,甚至可以和后端语言媲美。 摩尔定律持续发酵,个人电脑的配置开始飞跃 突然间,制约前端发展的两大问题得以解决,此时,只剩下最后一个问题还在负隅顽抗,即全局变量污染和依赖混乱的问题,解决了它,前端便可以突破一切障碍,未来无可限量。 于是,全世界的前端开发者在社区中激烈的讨论,想要为这个问题寻求解决之道...... 2008 年,有一个名叫 Ryan Dahl 小伙子正在为一件事焦头烂额,它需要在服务器端手写一个高性能的 web 服务,该服务对于性能要求之高,以至于目前市面上已有的 web 服务产品都满足不了需求。

服务器开发

新浪的服务器(电脑)收到请求 其中一个应用程序在做以下的事情 web 服务

  1. 监听 80 端口
  2. 将请求进行分析
  3. 将分析的结果交给相应的程序(php,Java)进行处理
  4. 把程序处理的结果返还给客户端

经过分析,它确定,如果要实现高性能,那么必须要尽可能的减少线程,而要减少线程,避免不了要实用异步的处理方案。 一开始,他打算自己实用 C/C++语言来编写,可是这一过程实在太痛苦。 就在他一筹莫展的时候,谷歌 V8 引擎的发布引起了他的注意,他突然发现,JS 不就是最好的实现 web 服务的语言吗?它天生就是单线程,并且是基于异步的!有了 V8 引擎的支撑,它的执行速度完全可以撑起一个服务器。而且 V8 是鼎鼎大名的谷歌公司发布的,谷歌一定会不断的优化 V8,有这种又省钱又省力的好事,我干嘛还要自己去写呢? 典型异步场景

javascript
setTimeout(function () {
   console.log("a");
 }, 1000);
 //后面不会阻塞
diff --git a/assets/front-end-engineering_modularization.md.18e98187.lean.js b/assets/front-end-engineering_modularization.md.3f1f0912.lean.js
similarity index 97%
rename from assets/front-end-engineering_modularization.md.18e98187.lean.js
rename to assets/front-end-engineering_modularization.md.3f1f0912.lean.js
index 031bfe64..2fc180c4 100644
--- a/assets/front-end-engineering_modularization.md.18e98187.lean.js
+++ b/assets/front-end-engineering_modularization.md.3f1f0912.lean.js
@@ -1 +1 @@
-import{_ as s,o as a,c as n,a as l}from"./app.679ab08c.js";const p="/blog/assets/2023-02-01-21-59-20.1fa5b4ed.png",o="/blog/assets/2023-02-01-21-59-43.f3f27dad.png",e="/blog/assets/2023-02-01-22-00-22.5f64024d.png",r="/blog/assets/2023-02-01-22-00-43.9fbe4838.png",c="/blog/assets/2023-02-01-22-01-01.8aaf55c5.png",t="/blog/assets/2023-02-01-22-01-19.d349110a.png",v=JSON.parse('{"title":"模块化","description":"","frontmatter":{},"headers":[{"level":2,"title":"1.JavaScript 模块化发展史","slug":"_1-javascript-模块化发展史","link":"#_1-javascript-模块化发展史","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":[]}]},{"level":2,"title":"2. CommonJS","slug":"_2-commonjs","link":"#_2-commonjs","children":[{"level":3,"title":"2-2. CommonJS","slug":"_2-2-commonjs","link":"#_2-2-commonjs","children":[]},{"level":3,"title":"模块的导出","slug":"模块的导出","link":"#模块的导出","children":[]},{"level":3,"title":"模块的导入","slug":"模块的导入","link":"#模块的导入","children":[]},{"level":3,"title":"CommonJS 规范","slug":"commonjs-规范","link":"#commonjs-规范","children":[]}]},{"level":2,"title":"3.AMD 和 CMD","slug":"_3-amd-和-cmd","link":"#_3-amd-和-cmd","children":[{"level":3,"title":"3-1 浏览器端模块化的难题","slug":"_3-1-浏览器端模块化的难题","link":"#_3-1-浏览器端模块化的难题","children":[]},{"level":3,"title":"3-2AMD","slug":"_3-2amd","link":"#_3-2amd","children":[]},{"level":3,"title":"3-3CMD","slug":"_3-3cmd","link":"#_3-3cmd","children":[]}]},{"level":2,"title":"4.es6 模块化","slug":"_4-es6-模块化","link":"#_4-es6-模块化","children":[{"level":3,"title":"4-1.ES6 模块化简介","slug":"_4-1-es6-模块化简介","link":"#_4-1-es6-模块化简介","children":[]},{"level":3,"title":"4-2.基本导入导出","slug":"_4-2-基本导入导出","link":"#_4-2-基本导入导出","children":[]},{"level":3,"title":"模块的引入","slug":"模块的引入","link":"#模块的引入","children":[]},{"level":3,"title":"4-3. 默认导入导出","slug":"_4-3-默认导入导出","link":"#_4-3-默认导入导出","children":[]},{"level":3,"title":"4-4.ES6 模块化的其他细节","slug":"_4-4-es6-模块化的其他细节","link":"#_4-4-es6-模块化的其他细节","children":[]}]}],"relativePath":"front-end-engineering/modularization.md","lastUpdated":1675736874000}'),i={name:"front-end-engineering/modularization.md"},B=l("",151),y=[B];function F(d,u,m,b,A,C){return a(),n("div",null,y)}const g=s(i,[["render",F]]);export{v as __pageData,g as default};
+import{_ as s,o as a,c as n,a as l}from"./app.815d1813.js";const p="/blog/assets/2023-02-01-21-59-20.1fa5b4ed.png",o="/blog/assets/2023-02-01-21-59-43.f3f27dad.png",e="/blog/assets/2023-02-01-22-00-22.5f64024d.png",r="/blog/assets/2023-02-01-22-00-43.9fbe4838.png",c="/blog/assets/2023-02-01-22-01-01.8aaf55c5.png",t="/blog/assets/2023-02-01-22-01-19.d349110a.png",v=JSON.parse('{"title":"模块化","description":"","frontmatter":{},"headers":[{"level":2,"title":"1.JavaScript 模块化发展史","slug":"_1-javascript-模块化发展史","link":"#_1-javascript-模块化发展史","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":[]}]},{"level":2,"title":"2. CommonJS","slug":"_2-commonjs","link":"#_2-commonjs","children":[{"level":3,"title":"2-2. CommonJS","slug":"_2-2-commonjs","link":"#_2-2-commonjs","children":[]},{"level":3,"title":"模块的导出","slug":"模块的导出","link":"#模块的导出","children":[]},{"level":3,"title":"模块的导入","slug":"模块的导入","link":"#模块的导入","children":[]},{"level":3,"title":"CommonJS 规范","slug":"commonjs-规范","link":"#commonjs-规范","children":[]}]},{"level":2,"title":"3.AMD 和 CMD","slug":"_3-amd-和-cmd","link":"#_3-amd-和-cmd","children":[{"level":3,"title":"3-1 浏览器端模块化的难题","slug":"_3-1-浏览器端模块化的难题","link":"#_3-1-浏览器端模块化的难题","children":[]},{"level":3,"title":"3-2AMD","slug":"_3-2amd","link":"#_3-2amd","children":[]},{"level":3,"title":"3-3CMD","slug":"_3-3cmd","link":"#_3-3cmd","children":[]}]},{"level":2,"title":"4.es6 模块化","slug":"_4-es6-模块化","link":"#_4-es6-模块化","children":[{"level":3,"title":"4-1.ES6 模块化简介","slug":"_4-1-es6-模块化简介","link":"#_4-1-es6-模块化简介","children":[]},{"level":3,"title":"4-2.基本导入导出","slug":"_4-2-基本导入导出","link":"#_4-2-基本导入导出","children":[]},{"level":3,"title":"模块的引入","slug":"模块的引入","link":"#模块的引入","children":[]},{"level":3,"title":"4-3. 默认导入导出","slug":"_4-3-默认导入导出","link":"#_4-3-默认导入导出","children":[]},{"level":3,"title":"4-4.ES6 模块化的其他细节","slug":"_4-4-es6-模块化的其他细节","link":"#_4-4-es6-模块化的其他细节","children":[]}]}],"relativePath":"front-end-engineering/modularization.md","lastUpdated":1675736874000}'),i={name:"front-end-engineering/modularization.md"},B=l("",151),y=[B];function F(d,u,m,b,A,C){return a(),n("div",null,y)}const g=s(i,[["render",F]]);export{v as __pageData,g as default};
diff --git a/assets/front-end-engineering_performance.md.af2c1e0b.js b/assets/front-end-engineering_performance.md.7538e83b.js
similarity index 99%
rename from assets/front-end-engineering_performance.md.af2c1e0b.js
rename to assets/front-end-engineering_performance.md.7538e83b.js
index 7301116e..2d5a388d 100644
--- a/assets/front-end-engineering_performance.md.af2c1e0b.js
+++ b/assets/front-end-engineering_performance.md.7538e83b.js
@@ -1,4 +1,4 @@
-import{_ as s,o as n,c as a,a as l}from"./app.679ab08c.js";const p="/blog/assets/2024-01-27-11-37-14.86d37b7f.png",o="/blog/assets/2024-01-27-11-42-53.5bf914cc.png",e="/blog/assets/2024-01-27-11-43-39.3f42a6fd.png",c="/blog/assets/2024-01-27-11-43-48.dc786c83.png",r="/blog/assets/2024-01-27-11-45-28.86477da9.png",t="/blog/assets/2024-01-28-16-38-33.db813e08.png",B="/blog/assets/2024-01-28-16-43-50.943cbfac.png",y="/blog/assets/2024-01-28-16-45-11.6c1597c5.png",i="/blog/assets/2024-01-28-16-52-03.61de8fe4.png",F="/blog/assets/2024-01-28-16-52-40.1ee5db60.png",u="/blog/assets/2024-01-28-17-09-16.389ef971.png",d="/blog/assets/2024-01-28-17-09-22.8a8ef8fe.png",b="/blog/assets/2024-01-28-17-18-12.4f54b024.png",A="/blog/assets/2024-01-28-17-28-28.709acd8d.png",m="/blog/assets/2024-01-28-17-29-13.fbcf0c26.png",C="/blog/assets/2024-01-28-17-30-23.3db19902.png",_=JSON.parse('{"title":"前端性能优化方法论","description":"","frontmatter":{},"headers":[{"level":2,"title":"Webpack 优化","slug":"webpack-优化","link":"#webpack-优化","children":[{"level":3,"title":"1. 构建性能","slug":"_1-构建性能","link":"#_1-构建性能","children":[]},{"level":3,"title":"2. 传输性能","slug":"_2-传输性能","link":"#_2-传输性能","children":[]},{"level":3,"title":"3. 运行性能","slug":"_3-运行性能","link":"#_3-运行性能","children":[]},{"level":3,"title":"4. webpack5 内置优化","slug":"_4-webpack5-内置优化","link":"#_4-webpack5-内置优化","children":[]},{"level":3,"title":"字节跳动面试题:说一下项目里有做过哪些 webpack 上的优化","slug":"字节跳动面试题-说一下项目里有做过哪些-webpack-上的优化","link":"#字节跳动面试题-说一下项目里有做过哪些-webpack-上的优化","children":[]}]},{"level":2,"title":"CSS","slug":"css","link":"#css","children":[]},{"level":2,"title":"网络层面","slug":"网络层面","link":"#网络层面","children":[{"level":3,"title":"总结","slug":"总结","link":"#总结","children":[]},{"level":3,"title":"CDN","slug":"cdn","link":"#cdn","children":[]},{"level":3,"title":"增加带宽","slug":"增加带宽","link":"#增加带宽","children":[]},{"level":3,"title":"http内置优化","slug":"http内置优化","link":"#http内置优化","children":[]},{"level":3,"title":"数据传输层面","slug":"数据传输层面","link":"#数据传输层面","children":[]}]},{"level":2,"title":"Vue","slug":"vue","link":"#vue","children":[{"level":3,"title":"Vue 开发优化","slug":"vue-开发优化","link":"#vue-开发优化","children":[]},{"level":3,"title":"Vue3 内置优化","slug":"vue3-内置优化","link":"#vue3-内置优化","children":[]},{"level":3,"title":"问题梳理","slug":"问题梳理","link":"#问题梳理","children":[]}]},{"level":2,"title":"React","slug":"react","link":"#react","children":[{"level":3,"title":"总结","slug":"总结-1","link":"#总结-1","children":[]}]},{"level":2,"title":"高性能JavaScript","slug":"高性能javascript","link":"#高性能javascript","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":[]}]},{"level":2,"title":"优化首屏响应","slug":"优化首屏响应","link":"#优化首屏响应","children":[{"level":3,"title":"觉得快","slug":"觉得快","link":"#觉得快","children":[]},{"level":3,"title":"真实快","slug":"真实快","link":"#真实快","children":[]}]}],"relativePath":"front-end-engineering/performance.md","lastUpdated":1710671080000}'),h={name:"front-end-engineering/performance.md"},g=l('

前端性能优化方法论

我们可以从两个方面来看性能优化的意义:

  1. 用户角度:网站优化能够让页面加载得更快,响应更加及时,极大提升用户体验。
  2. 服务商角度:优化会减少页面资源请求数,减小请求资源所占带宽大小,从而节省可观的带宽资源。

网站优化的目标就是减少网站加载时间,提高响应速度。 Google 和亚马逊的研究表明,Google 页面加载的时间从 0.4 秒提升到 0.9 秒导致丢失了 20% 流量和广告收入,对于亚马逊,页面加载时间每增加 100ms 就意味着 1% 的销售额损失。 可见,页面的加载速度对于用户有着至关重要的影响。

Webpack 优化

如何分析打包结果?webpack-bundle-analyzer

1. 构建性能

TIP

这里所说的构建性能,是指在开发阶段的构建性能,而不是生产环境的构建性能

优化的目标,是降低从打包开始,到代码效果呈现所经过的时间

构建性能会影响开发效率。构建性能越高,开发过程中时间的浪费越少

1.1 减少模块解析

模块解析包括:抽象语法树分析、依赖分析、模块语法替换

如果某个模块不做解析,该模块经过loader处理后的代码就是最终代码。

如果没有loader对该模块进行处理,该模块的源码就是最终打包结果的代码。

如果不对某个模块进行解析,可以缩短构建时间,那么哪些模块不需要解析呢?

模块中无其他依赖:一些已经打包好的第三方库,比如jquery,所以可以配置module.noParse,它是一个正则,被正则匹配到的模块不会解析

js
module.exports = {
+import{_ as s,o as n,c as a,a as l}from"./app.815d1813.js";const p="/blog/assets/2024-01-27-11-37-14.86d37b7f.png",o="/blog/assets/2024-01-27-11-42-53.5bf914cc.png",e="/blog/assets/2024-01-27-11-43-39.3f42a6fd.png",c="/blog/assets/2024-01-27-11-43-48.dc786c83.png",r="/blog/assets/2024-01-27-11-45-28.86477da9.png",t="/blog/assets/2024-01-28-16-38-33.db813e08.png",B="/blog/assets/2024-01-28-16-43-50.943cbfac.png",y="/blog/assets/2024-01-28-16-45-11.6c1597c5.png",i="/blog/assets/2024-01-28-16-52-03.61de8fe4.png",F="/blog/assets/2024-01-28-16-52-40.1ee5db60.png",u="/blog/assets/2024-01-28-17-09-16.389ef971.png",d="/blog/assets/2024-01-28-17-09-22.8a8ef8fe.png",b="/blog/assets/2024-01-28-17-18-12.4f54b024.png",A="/blog/assets/2024-01-28-17-28-28.709acd8d.png",m="/blog/assets/2024-01-28-17-29-13.fbcf0c26.png",C="/blog/assets/2024-01-28-17-30-23.3db19902.png",_=JSON.parse('{"title":"前端性能优化方法论","description":"","frontmatter":{},"headers":[{"level":2,"title":"Webpack 优化","slug":"webpack-优化","link":"#webpack-优化","children":[{"level":3,"title":"1. 构建性能","slug":"_1-构建性能","link":"#_1-构建性能","children":[]},{"level":3,"title":"2. 传输性能","slug":"_2-传输性能","link":"#_2-传输性能","children":[]},{"level":3,"title":"3. 运行性能","slug":"_3-运行性能","link":"#_3-运行性能","children":[]},{"level":3,"title":"4. webpack5 内置优化","slug":"_4-webpack5-内置优化","link":"#_4-webpack5-内置优化","children":[]},{"level":3,"title":"字节跳动面试题:说一下项目里有做过哪些 webpack 上的优化","slug":"字节跳动面试题-说一下项目里有做过哪些-webpack-上的优化","link":"#字节跳动面试题-说一下项目里有做过哪些-webpack-上的优化","children":[]}]},{"level":2,"title":"CSS","slug":"css","link":"#css","children":[]},{"level":2,"title":"网络层面","slug":"网络层面","link":"#网络层面","children":[{"level":3,"title":"总结","slug":"总结","link":"#总结","children":[]},{"level":3,"title":"CDN","slug":"cdn","link":"#cdn","children":[]},{"level":3,"title":"增加带宽","slug":"增加带宽","link":"#增加带宽","children":[]},{"level":3,"title":"http内置优化","slug":"http内置优化","link":"#http内置优化","children":[]},{"level":3,"title":"数据传输层面","slug":"数据传输层面","link":"#数据传输层面","children":[]}]},{"level":2,"title":"Vue","slug":"vue","link":"#vue","children":[{"level":3,"title":"Vue 开发优化","slug":"vue-开发优化","link":"#vue-开发优化","children":[]},{"level":3,"title":"Vue3 内置优化","slug":"vue3-内置优化","link":"#vue3-内置优化","children":[]},{"level":3,"title":"问题梳理","slug":"问题梳理","link":"#问题梳理","children":[]}]},{"level":2,"title":"React","slug":"react","link":"#react","children":[{"level":3,"title":"总结","slug":"总结-1","link":"#总结-1","children":[]}]},{"level":2,"title":"高性能JavaScript","slug":"高性能javascript","link":"#高性能javascript","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":[]}]},{"level":2,"title":"优化首屏响应","slug":"优化首屏响应","link":"#优化首屏响应","children":[{"level":3,"title":"觉得快","slug":"觉得快","link":"#觉得快","children":[]},{"level":3,"title":"真实快","slug":"真实快","link":"#真实快","children":[]}]}],"relativePath":"front-end-engineering/performance.md","lastUpdated":1710671080000}'),h={name:"front-end-engineering/performance.md"},g=l('

前端性能优化方法论

我们可以从两个方面来看性能优化的意义:

  1. 用户角度:网站优化能够让页面加载得更快,响应更加及时,极大提升用户体验。
  2. 服务商角度:优化会减少页面资源请求数,减小请求资源所占带宽大小,从而节省可观的带宽资源。

网站优化的目标就是减少网站加载时间,提高响应速度。 Google 和亚马逊的研究表明,Google 页面加载的时间从 0.4 秒提升到 0.9 秒导致丢失了 20% 流量和广告收入,对于亚马逊,页面加载时间每增加 100ms 就意味着 1% 的销售额损失。 可见,页面的加载速度对于用户有着至关重要的影响。

Webpack 优化

如何分析打包结果?webpack-bundle-analyzer

1. 构建性能

TIP

这里所说的构建性能,是指在开发阶段的构建性能,而不是生产环境的构建性能

优化的目标,是降低从打包开始,到代码效果呈现所经过的时间

构建性能会影响开发效率。构建性能越高,开发过程中时间的浪费越少

1.1 减少模块解析

模块解析包括:抽象语法树分析、依赖分析、模块语法替换

如果某个模块不做解析,该模块经过loader处理后的代码就是最终代码。

如果没有loader对该模块进行处理,该模块的源码就是最终打包结果的代码。

如果不对某个模块进行解析,可以缩短构建时间,那么哪些模块不需要解析呢?

模块中无其他依赖:一些已经打包好的第三方库,比如jquery,所以可以配置module.noParse,它是一个正则,被正则匹配到的模块不会解析

js
module.exports = {
   mode: 'development',
   module: {
     noParse: /jquery/
diff --git a/assets/front-end-engineering_performance.md.af2c1e0b.lean.js b/assets/front-end-engineering_performance.md.7538e83b.lean.js
similarity index 98%
rename from assets/front-end-engineering_performance.md.af2c1e0b.lean.js
rename to assets/front-end-engineering_performance.md.7538e83b.lean.js
index cbddf7bb..d7ffd413 100644
--- a/assets/front-end-engineering_performance.md.af2c1e0b.lean.js
+++ b/assets/front-end-engineering_performance.md.7538e83b.lean.js
@@ -1 +1 @@
-import{_ as s,o as n,c as a,a as l}from"./app.679ab08c.js";const p="/blog/assets/2024-01-27-11-37-14.86d37b7f.png",o="/blog/assets/2024-01-27-11-42-53.5bf914cc.png",e="/blog/assets/2024-01-27-11-43-39.3f42a6fd.png",c="/blog/assets/2024-01-27-11-43-48.dc786c83.png",r="/blog/assets/2024-01-27-11-45-28.86477da9.png",t="/blog/assets/2024-01-28-16-38-33.db813e08.png",B="/blog/assets/2024-01-28-16-43-50.943cbfac.png",y="/blog/assets/2024-01-28-16-45-11.6c1597c5.png",i="/blog/assets/2024-01-28-16-52-03.61de8fe4.png",F="/blog/assets/2024-01-28-16-52-40.1ee5db60.png",u="/blog/assets/2024-01-28-17-09-16.389ef971.png",d="/blog/assets/2024-01-28-17-09-22.8a8ef8fe.png",b="/blog/assets/2024-01-28-17-18-12.4f54b024.png",A="/blog/assets/2024-01-28-17-28-28.709acd8d.png",m="/blog/assets/2024-01-28-17-29-13.fbcf0c26.png",C="/blog/assets/2024-01-28-17-30-23.3db19902.png",_=JSON.parse('{"title":"前端性能优化方法论","description":"","frontmatter":{},"headers":[{"level":2,"title":"Webpack 优化","slug":"webpack-优化","link":"#webpack-优化","children":[{"level":3,"title":"1. 构建性能","slug":"_1-构建性能","link":"#_1-构建性能","children":[]},{"level":3,"title":"2. 传输性能","slug":"_2-传输性能","link":"#_2-传输性能","children":[]},{"level":3,"title":"3. 运行性能","slug":"_3-运行性能","link":"#_3-运行性能","children":[]},{"level":3,"title":"4. webpack5 内置优化","slug":"_4-webpack5-内置优化","link":"#_4-webpack5-内置优化","children":[]},{"level":3,"title":"字节跳动面试题:说一下项目里有做过哪些 webpack 上的优化","slug":"字节跳动面试题-说一下项目里有做过哪些-webpack-上的优化","link":"#字节跳动面试题-说一下项目里有做过哪些-webpack-上的优化","children":[]}]},{"level":2,"title":"CSS","slug":"css","link":"#css","children":[]},{"level":2,"title":"网络层面","slug":"网络层面","link":"#网络层面","children":[{"level":3,"title":"总结","slug":"总结","link":"#总结","children":[]},{"level":3,"title":"CDN","slug":"cdn","link":"#cdn","children":[]},{"level":3,"title":"增加带宽","slug":"增加带宽","link":"#增加带宽","children":[]},{"level":3,"title":"http内置优化","slug":"http内置优化","link":"#http内置优化","children":[]},{"level":3,"title":"数据传输层面","slug":"数据传输层面","link":"#数据传输层面","children":[]}]},{"level":2,"title":"Vue","slug":"vue","link":"#vue","children":[{"level":3,"title":"Vue 开发优化","slug":"vue-开发优化","link":"#vue-开发优化","children":[]},{"level":3,"title":"Vue3 内置优化","slug":"vue3-内置优化","link":"#vue3-内置优化","children":[]},{"level":3,"title":"问题梳理","slug":"问题梳理","link":"#问题梳理","children":[]}]},{"level":2,"title":"React","slug":"react","link":"#react","children":[{"level":3,"title":"总结","slug":"总结-1","link":"#总结-1","children":[]}]},{"level":2,"title":"高性能JavaScript","slug":"高性能javascript","link":"#高性能javascript","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":[]}]},{"level":2,"title":"优化首屏响应","slug":"优化首屏响应","link":"#优化首屏响应","children":[{"level":3,"title":"觉得快","slug":"觉得快","link":"#觉得快","children":[]},{"level":3,"title":"真实快","slug":"真实快","link":"#真实快","children":[]}]}],"relativePath":"front-end-engineering/performance.md","lastUpdated":1710671080000}'),h={name:"front-end-engineering/performance.md"},g=l("",592),E=[g];function k(v,f,q,D,w,x){return n(),a("div",null,E)}const P=s(h,[["render",k]]);export{_ as __pageData,P as default};
+import{_ as s,o as n,c as a,a as l}from"./app.815d1813.js";const p="/blog/assets/2024-01-27-11-37-14.86d37b7f.png",o="/blog/assets/2024-01-27-11-42-53.5bf914cc.png",e="/blog/assets/2024-01-27-11-43-39.3f42a6fd.png",c="/blog/assets/2024-01-27-11-43-48.dc786c83.png",r="/blog/assets/2024-01-27-11-45-28.86477da9.png",t="/blog/assets/2024-01-28-16-38-33.db813e08.png",B="/blog/assets/2024-01-28-16-43-50.943cbfac.png",y="/blog/assets/2024-01-28-16-45-11.6c1597c5.png",i="/blog/assets/2024-01-28-16-52-03.61de8fe4.png",F="/blog/assets/2024-01-28-16-52-40.1ee5db60.png",u="/blog/assets/2024-01-28-17-09-16.389ef971.png",d="/blog/assets/2024-01-28-17-09-22.8a8ef8fe.png",b="/blog/assets/2024-01-28-17-18-12.4f54b024.png",A="/blog/assets/2024-01-28-17-28-28.709acd8d.png",m="/blog/assets/2024-01-28-17-29-13.fbcf0c26.png",C="/blog/assets/2024-01-28-17-30-23.3db19902.png",_=JSON.parse('{"title":"前端性能优化方法论","description":"","frontmatter":{},"headers":[{"level":2,"title":"Webpack 优化","slug":"webpack-优化","link":"#webpack-优化","children":[{"level":3,"title":"1. 构建性能","slug":"_1-构建性能","link":"#_1-构建性能","children":[]},{"level":3,"title":"2. 传输性能","slug":"_2-传输性能","link":"#_2-传输性能","children":[]},{"level":3,"title":"3. 运行性能","slug":"_3-运行性能","link":"#_3-运行性能","children":[]},{"level":3,"title":"4. webpack5 内置优化","slug":"_4-webpack5-内置优化","link":"#_4-webpack5-内置优化","children":[]},{"level":3,"title":"字节跳动面试题:说一下项目里有做过哪些 webpack 上的优化","slug":"字节跳动面试题-说一下项目里有做过哪些-webpack-上的优化","link":"#字节跳动面试题-说一下项目里有做过哪些-webpack-上的优化","children":[]}]},{"level":2,"title":"CSS","slug":"css","link":"#css","children":[]},{"level":2,"title":"网络层面","slug":"网络层面","link":"#网络层面","children":[{"level":3,"title":"总结","slug":"总结","link":"#总结","children":[]},{"level":3,"title":"CDN","slug":"cdn","link":"#cdn","children":[]},{"level":3,"title":"增加带宽","slug":"增加带宽","link":"#增加带宽","children":[]},{"level":3,"title":"http内置优化","slug":"http内置优化","link":"#http内置优化","children":[]},{"level":3,"title":"数据传输层面","slug":"数据传输层面","link":"#数据传输层面","children":[]}]},{"level":2,"title":"Vue","slug":"vue","link":"#vue","children":[{"level":3,"title":"Vue 开发优化","slug":"vue-开发优化","link":"#vue-开发优化","children":[]},{"level":3,"title":"Vue3 内置优化","slug":"vue3-内置优化","link":"#vue3-内置优化","children":[]},{"level":3,"title":"问题梳理","slug":"问题梳理","link":"#问题梳理","children":[]}]},{"level":2,"title":"React","slug":"react","link":"#react","children":[{"level":3,"title":"总结","slug":"总结-1","link":"#总结-1","children":[]}]},{"level":2,"title":"高性能JavaScript","slug":"高性能javascript","link":"#高性能javascript","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":[]}]},{"level":2,"title":"优化首屏响应","slug":"优化首屏响应","link":"#优化首屏响应","children":[{"level":3,"title":"觉得快","slug":"觉得快","link":"#觉得快","children":[]},{"level":3,"title":"真实快","slug":"真实快","link":"#真实快","children":[]}]}],"relativePath":"front-end-engineering/performance.md","lastUpdated":1710671080000}'),h={name:"front-end-engineering/performance.md"},g=l("",592),E=[g];function k(v,f,q,D,w,x){return n(),a("div",null,E)}const P=s(h,[["render",k]]);export{_ as __pageData,P as default};
diff --git "a/assets/front-end-engineering_pnpm\345\216\237\347\220\206.md.49360206.js" "b/assets/front-end-engineering_pnpm\345\216\237\347\220\206.md.b67c2dab.js"
similarity index 99%
rename from "assets/front-end-engineering_pnpm\345\216\237\347\220\206.md.49360206.js"
rename to "assets/front-end-engineering_pnpm\345\216\237\347\220\206.md.b67c2dab.js"
index 257a2db6..41d40dea 100644
--- "a/assets/front-end-engineering_pnpm\345\216\237\347\220\206.md.49360206.js"
+++ "b/assets/front-end-engineering_pnpm\345\216\237\347\220\206.md.b67c2dab.js"
@@ -1,4 +1,4 @@
-import{_ as p,o as a,c as e,a as n}from"./app.679ab08c.js";const h=JSON.parse('{"title":"pnpm 原理","description":"","frontmatter":{},"headers":[{"level":3,"title":"1、文件的本质","slug":"_1、文件的本质","link":"#_1、文件的本质","children":[]},{"level":3,"title":"2、文件的拷贝","slug":"_2、文件的拷贝","link":"#_2、文件的拷贝","children":[]},{"level":3,"title":"3、硬链接 hard link","slug":"_3、硬链接-hard-link","link":"#_3、硬链接-hard-link","children":[]},{"level":3,"title":"4、符号链接 symbol link","slug":"_4、符号链接-symbol-link","link":"#_4、符号链接-symbol-link","children":[]},{"level":3,"title":"5、符号链接和硬链接的区别","slug":"_5、符号链接和硬链接的区别","link":"#_5、符号链接和硬链接的区别","children":[]},{"level":3,"title":"6、快捷方式","slug":"_6、快捷方式","link":"#_6、快捷方式","children":[]},{"level":3,"title":"7、node 环境对硬链接和符号链接的处理","slug":"_7、node-环境对硬链接和符号链接的处理","link":"#_7、node-环境对硬链接和符号链接的处理","children":[]},{"level":3,"title":"8、pnpm 原理","slug":"_8、pnpm-原理","link":"#_8、pnpm-原理","children":[]}],"relativePath":"front-end-engineering/pnpm原理.md","lastUpdated":1675248660000}'),s={name:"front-end-engineering/pnpm原理.md"},i=n(`

pnpm 原理

想要理解 pnpm 是怎么做的,需要一些操作系统的知识

1、文件的本质

在操作系统中,文件实际上是一个指针,只不过它指向的不是内存地址,而是一个外部存储地址(这里的外部存储可以是硬盘、U 盘、甚至是网络)

当我们删除文件时,删除的实际上是指针,因此,无论删除多么大的文件,速度都非常快。像我们的 U 盘、硬盘里的文件虽然说看起来已经删除了,但是其实数据恢复公司是可以恢复的,因为数据还是存在的,只要删除文件后再没有存储其它文件就可以恢复,所以真正删除一个文件就是可劲存可劲删

2、文件的拷贝

如果你复制一个文件,是将该文件指针指向的内容进行复制,然后产生一个新文件指向新的内容。

硬链接的概念来自于 Unix 操作系统,它是指将一个文件 A 指针复制到另一个文件 B 指针中,文件 B 就是文件 A 的硬链接。

通过硬链接,不会产生额外的磁盘占用,并且,两个文件都能找到相同的磁盘内容。

硬链接的数量没有限制,可以为同一个文件产生多个硬链接。

windows Vista 操作系统开始,支持了创建硬链接的操作,在 cmd 中使用下面的命令可以创建硬链接。

mklink /h  链接名称  目标文件
+import{_ as p,o as a,c as e,a as n}from"./app.815d1813.js";const h=JSON.parse('{"title":"pnpm 原理","description":"","frontmatter":{},"headers":[{"level":3,"title":"1、文件的本质","slug":"_1、文件的本质","link":"#_1、文件的本质","children":[]},{"level":3,"title":"2、文件的拷贝","slug":"_2、文件的拷贝","link":"#_2、文件的拷贝","children":[]},{"level":3,"title":"3、硬链接 hard link","slug":"_3、硬链接-hard-link","link":"#_3、硬链接-hard-link","children":[]},{"level":3,"title":"4、符号链接 symbol link","slug":"_4、符号链接-symbol-link","link":"#_4、符号链接-symbol-link","children":[]},{"level":3,"title":"5、符号链接和硬链接的区别","slug":"_5、符号链接和硬链接的区别","link":"#_5、符号链接和硬链接的区别","children":[]},{"level":3,"title":"6、快捷方式","slug":"_6、快捷方式","link":"#_6、快捷方式","children":[]},{"level":3,"title":"7、node 环境对硬链接和符号链接的处理","slug":"_7、node-环境对硬链接和符号链接的处理","link":"#_7、node-环境对硬链接和符号链接的处理","children":[]},{"level":3,"title":"8、pnpm 原理","slug":"_8、pnpm-原理","link":"#_8、pnpm-原理","children":[]}],"relativePath":"front-end-engineering/pnpm原理.md","lastUpdated":1675248660000}'),s={name:"front-end-engineering/pnpm原理.md"},i=n(`

pnpm 原理

想要理解 pnpm 是怎么做的,需要一些操作系统的知识

1、文件的本质

在操作系统中,文件实际上是一个指针,只不过它指向的不是内存地址,而是一个外部存储地址(这里的外部存储可以是硬盘、U 盘、甚至是网络)

当我们删除文件时,删除的实际上是指针,因此,无论删除多么大的文件,速度都非常快。像我们的 U 盘、硬盘里的文件虽然说看起来已经删除了,但是其实数据恢复公司是可以恢复的,因为数据还是存在的,只要删除文件后再没有存储其它文件就可以恢复,所以真正删除一个文件就是可劲存可劲删

2、文件的拷贝

如果你复制一个文件,是将该文件指针指向的内容进行复制,然后产生一个新文件指向新的内容。

硬链接的概念来自于 Unix 操作系统,它是指将一个文件 A 指针复制到另一个文件 B 指针中,文件 B 就是文件 A 的硬链接。

通过硬链接,不会产生额外的磁盘占用,并且,两个文件都能找到相同的磁盘内容。

硬链接的数量没有限制,可以为同一个文件产生多个硬链接。

windows Vista 操作系统开始,支持了创建硬链接的操作,在 cmd 中使用下面的命令可以创建硬链接。

mklink /h  链接名称  目标文件
 
 
mklink /h  链接名称  目标文件
 
diff --git "a/assets/front-end-engineering_pnpm\345\216\237\347\220\206.md.49360206.lean.js" "b/assets/front-end-engineering_pnpm\345\216\237\347\220\206.md.b67c2dab.lean.js"
similarity index 95%
rename from "assets/front-end-engineering_pnpm\345\216\237\347\220\206.md.49360206.lean.js"
rename to "assets/front-end-engineering_pnpm\345\216\237\347\220\206.md.b67c2dab.lean.js"
index 6f6c978d..0e740db1 100644
--- "a/assets/front-end-engineering_pnpm\345\216\237\347\220\206.md.49360206.lean.js"
+++ "b/assets/front-end-engineering_pnpm\345\216\237\347\220\206.md.b67c2dab.lean.js"
@@ -1 +1 @@
-import{_ as p,o as a,c as e,a as n}from"./app.679ab08c.js";const h=JSON.parse('{"title":"pnpm 原理","description":"","frontmatter":{},"headers":[{"level":3,"title":"1、文件的本质","slug":"_1、文件的本质","link":"#_1、文件的本质","children":[]},{"level":3,"title":"2、文件的拷贝","slug":"_2、文件的拷贝","link":"#_2、文件的拷贝","children":[]},{"level":3,"title":"3、硬链接 hard link","slug":"_3、硬链接-hard-link","link":"#_3、硬链接-hard-link","children":[]},{"level":3,"title":"4、符号链接 symbol link","slug":"_4、符号链接-symbol-link","link":"#_4、符号链接-symbol-link","children":[]},{"level":3,"title":"5、符号链接和硬链接的区别","slug":"_5、符号链接和硬链接的区别","link":"#_5、符号链接和硬链接的区别","children":[]},{"level":3,"title":"6、快捷方式","slug":"_6、快捷方式","link":"#_6、快捷方式","children":[]},{"level":3,"title":"7、node 环境对硬链接和符号链接的处理","slug":"_7、node-环境对硬链接和符号链接的处理","link":"#_7、node-环境对硬链接和符号链接的处理","children":[]},{"level":3,"title":"8、pnpm 原理","slug":"_8、pnpm-原理","link":"#_8、pnpm-原理","children":[]}],"relativePath":"front-end-engineering/pnpm原理.md","lastUpdated":1675248660000}'),s={name:"front-end-engineering/pnpm原理.md"},i=n("",40),l=[i];function c(t,o,r,d,b,m){return a(),e("div",null,l)}const k=p(s,[["render",c]]);export{h as __pageData,k as default};
+import{_ as p,o as a,c as e,a as n}from"./app.815d1813.js";const h=JSON.parse('{"title":"pnpm 原理","description":"","frontmatter":{},"headers":[{"level":3,"title":"1、文件的本质","slug":"_1、文件的本质","link":"#_1、文件的本质","children":[]},{"level":3,"title":"2、文件的拷贝","slug":"_2、文件的拷贝","link":"#_2、文件的拷贝","children":[]},{"level":3,"title":"3、硬链接 hard link","slug":"_3、硬链接-hard-link","link":"#_3、硬链接-hard-link","children":[]},{"level":3,"title":"4、符号链接 symbol link","slug":"_4、符号链接-symbol-link","link":"#_4、符号链接-symbol-link","children":[]},{"level":3,"title":"5、符号链接和硬链接的区别","slug":"_5、符号链接和硬链接的区别","link":"#_5、符号链接和硬链接的区别","children":[]},{"level":3,"title":"6、快捷方式","slug":"_6、快捷方式","link":"#_6、快捷方式","children":[]},{"level":3,"title":"7、node 环境对硬链接和符号链接的处理","slug":"_7、node-环境对硬链接和符号链接的处理","link":"#_7、node-环境对硬链接和符号链接的处理","children":[]},{"level":3,"title":"8、pnpm 原理","slug":"_8、pnpm-原理","link":"#_8、pnpm-原理","children":[]}],"relativePath":"front-end-engineering/pnpm原理.md","lastUpdated":1675248660000}'),s={name:"front-end-engineering/pnpm原理.md"},i=n("",40),l=[i];function c(t,o,r,d,b,m){return a(),e("div",null,l)}const k=p(s,[["render",c]]);export{h as __pageData,k as default};
diff --git a/assets/front-end-engineering_webpack5-mf.md.91ebbb9a.js b/assets/front-end-engineering_webpack5-mf.md.17c9d3f6.js
similarity index 99%
rename from assets/front-end-engineering_webpack5-mf.md.91ebbb9a.js
rename to assets/front-end-engineering_webpack5-mf.md.17c9d3f6.js
index 3ac4edbe..eb94cb58 100644
--- a/assets/front-end-engineering_webpack5-mf.md.91ebbb9a.js
+++ b/assets/front-end-engineering_webpack5-mf.md.17c9d3f6.js
@@ -1,4 +1,4 @@
-import{_ as s,o as n,c as a,a as l}from"./app.679ab08c.js";const p="/blog/assets/2023-01-06-16-57-09.02129cc3.png",e="/blog/assets/2023-01-06-16-58-00.54f4410f.png",m=JSON.parse('{"title":"webpack5 模块联邦","description":"","frontmatter":{},"headers":[{"level":3,"title":"示例","slug":"示例","link":"#示例","children":[]},{"level":3,"title":"暴露自身模块","slug":"暴露自身模块","link":"#暴露自身模块","children":[]},{"level":3,"title":"使用对方暴露的模块","slug":"使用对方暴露的模块","link":"#使用对方暴露的模块","children":[]},{"level":3,"title":"共享模块","slug":"共享模块","link":"#共享模块","children":[]}],"relativePath":"front-end-engineering/webpack5-mf.md","lastUpdated":1706438015000}'),o={name:"front-end-engineering/webpack5-mf.md"},c=l('

webpack5 模块联邦

在大型项目中,往往会把项目中的某个区域或功能模块作为单独的项目开发,最终形成「微前端」架构

在微前端架构中,不同的工程可能出现下面的场景

这涉及到很多非常棘手的问题:

  • 如何避免公共模块重复打包
  • 如何将某个项目中一部分模块分享出去,同时还要避免重复打包
  • 如何管理依赖的不同版本
  • 如何更新模块
  • ......

webpack5尝试着通过模块联邦来解决此类问题

示例

现有两个微前端工程,它们各自独立开发、测试、部署,但它们有一些相同的公共模块,并有一些自己的模块需要分享给其他工程使用,同时又要引入其他工程的模块。

暴露自身模块

如果一个项目需要把一部分模块暴露给其他项目使用,可以使用webpack5的模块联邦将这些模块暴露出去

javascript
// webpack.config.js
+import{_ as s,o as n,c as a,a as l}from"./app.815d1813.js";const p="/blog/assets/2023-01-06-16-57-09.02129cc3.png",e="/blog/assets/2023-01-06-16-58-00.54f4410f.png",m=JSON.parse('{"title":"webpack5 模块联邦","description":"","frontmatter":{},"headers":[{"level":3,"title":"示例","slug":"示例","link":"#示例","children":[]},{"level":3,"title":"暴露自身模块","slug":"暴露自身模块","link":"#暴露自身模块","children":[]},{"level":3,"title":"使用对方暴露的模块","slug":"使用对方暴露的模块","link":"#使用对方暴露的模块","children":[]},{"level":3,"title":"共享模块","slug":"共享模块","link":"#共享模块","children":[]}],"relativePath":"front-end-engineering/webpack5-mf.md","lastUpdated":1706438015000}'),o={name:"front-end-engineering/webpack5-mf.md"},c=l('

webpack5 模块联邦

在大型项目中,往往会把项目中的某个区域或功能模块作为单独的项目开发,最终形成「微前端」架构

在微前端架构中,不同的工程可能出现下面的场景

这涉及到很多非常棘手的问题:

  • 如何避免公共模块重复打包
  • 如何将某个项目中一部分模块分享出去,同时还要避免重复打包
  • 如何管理依赖的不同版本
  • 如何更新模块
  • ......

webpack5尝试着通过模块联邦来解决此类问题

示例

现有两个微前端工程,它们各自独立开发、测试、部署,但它们有一些相同的公共模块,并有一些自己的模块需要分享给其他工程使用,同时又要引入其他工程的模块。

暴露自身模块

如果一个项目需要把一部分模块暴露给其他项目使用,可以使用webpack5的模块联邦将这些模块暴露出去

javascript
// webpack.config.js
 const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
 
 module.exports = {
diff --git a/assets/front-end-engineering_webpack5-mf.md.91ebbb9a.lean.js b/assets/front-end-engineering_webpack5-mf.md.17c9d3f6.lean.js
similarity index 93%
rename from assets/front-end-engineering_webpack5-mf.md.91ebbb9a.lean.js
rename to assets/front-end-engineering_webpack5-mf.md.17c9d3f6.lean.js
index 486a3c5e..bb0ac847 100644
--- a/assets/front-end-engineering_webpack5-mf.md.91ebbb9a.lean.js
+++ b/assets/front-end-engineering_webpack5-mf.md.17c9d3f6.lean.js
@@ -1 +1 @@
-import{_ as s,o as n,c as a,a as l}from"./app.679ab08c.js";const p="/blog/assets/2023-01-06-16-57-09.02129cc3.png",e="/blog/assets/2023-01-06-16-58-00.54f4410f.png",m=JSON.parse('{"title":"webpack5 模块联邦","description":"","frontmatter":{},"headers":[{"level":3,"title":"示例","slug":"示例","link":"#示例","children":[]},{"level":3,"title":"暴露自身模块","slug":"暴露自身模块","link":"#暴露自身模块","children":[]},{"level":3,"title":"使用对方暴露的模块","slug":"使用对方暴露的模块","link":"#使用对方暴露的模块","children":[]},{"level":3,"title":"共享模块","slug":"共享模块","link":"#共享模块","children":[]}],"relativePath":"front-end-engineering/webpack5-mf.md","lastUpdated":1706438015000}'),o={name:"front-end-engineering/webpack5-mf.md"},c=l("",20),r=[c];function t(B,i,y,F,u,b){return n(),a("div",null,r)}const A=s(o,[["render",t]]);export{m as __pageData,A as default};
+import{_ as s,o as n,c as a,a as l}from"./app.815d1813.js";const p="/blog/assets/2023-01-06-16-57-09.02129cc3.png",e="/blog/assets/2023-01-06-16-58-00.54f4410f.png",m=JSON.parse('{"title":"webpack5 模块联邦","description":"","frontmatter":{},"headers":[{"level":3,"title":"示例","slug":"示例","link":"#示例","children":[]},{"level":3,"title":"暴露自身模块","slug":"暴露自身模块","link":"#暴露自身模块","children":[]},{"level":3,"title":"使用对方暴露的模块","slug":"使用对方暴露的模块","link":"#使用对方暴露的模块","children":[]},{"level":3,"title":"共享模块","slug":"共享模块","link":"#共享模块","children":[]}],"relativePath":"front-end-engineering/webpack5-mf.md","lastUpdated":1706438015000}'),o={name:"front-end-engineering/webpack5-mf.md"},c=l("",20),r=[c];function t(B,i,y,F,u,b){return n(),a("div",null,r)}const A=s(o,[["render",t]]);export{m as __pageData,A as default};
diff --git "a/assets/front-end-engineering_webpack\345\270\270\347\224\250\346\213\223\345\261\225.md.bb19598b.js" "b/assets/front-end-engineering_webpack\345\270\270\347\224\250\346\213\223\345\261\225.md.6be37d04.js"
similarity index 99%
rename from "assets/front-end-engineering_webpack\345\270\270\347\224\250\346\213\223\345\261\225.md.bb19598b.js"
rename to "assets/front-end-engineering_webpack\345\270\270\347\224\250\346\213\223\345\261\225.md.6be37d04.js"
index 407ccbca..8340cf00 100644
--- "a/assets/front-end-engineering_webpack\345\270\270\347\224\250\346\213\223\345\261\225.md.bb19598b.js"
+++ "b/assets/front-end-engineering_webpack\345\270\270\347\224\250\346\213\223\345\261\225.md.6be37d04.js"
@@ -1,4 +1,4 @@
-import{_ as s,o as n,c as a,a as l}from"./app.679ab08c.js";const p="/blog/assets/2023-01-06-16-01-47.0a0026be.png",b=JSON.parse('{"title":"webpack常用拓展","description":"","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":[]},{"level":2,"title":"webpack内置插件","slug":"webpack内置插件","link":"#webpack内置插件","children":[{"level":3,"title":"DefinePlugin","slug":"defineplugin","link":"#defineplugin","children":[]},{"level":3,"title":"BannerPlugin","slug":"bannerplugin","link":"#bannerplugin","children":[]},{"level":3,"title":"ProvidePlugin","slug":"provideplugin","link":"#provideplugin","children":[]}]}],"relativePath":"front-end-engineering/webpack常用拓展.md","lastUpdated":1706436523000}'),e={name:"front-end-engineering/webpack常用拓展.md"},o=l(`

webpack常用拓展

清除输出目录

clean-webpack-plugin 当文件内容变化,重新打包,会自动删除原来打包的文件

js
module.exports = {
+import{_ as s,o as n,c as a,a as l}from"./app.815d1813.js";const p="/blog/assets/2023-01-06-16-01-47.0a0026be.png",b=JSON.parse('{"title":"webpack常用拓展","description":"","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":[]},{"level":2,"title":"webpack内置插件","slug":"webpack内置插件","link":"#webpack内置插件","children":[{"level":3,"title":"DefinePlugin","slug":"defineplugin","link":"#defineplugin","children":[]},{"level":3,"title":"BannerPlugin","slug":"bannerplugin","link":"#bannerplugin","children":[]},{"level":3,"title":"ProvidePlugin","slug":"provideplugin","link":"#provideplugin","children":[]}]}],"relativePath":"front-end-engineering/webpack常用拓展.md","lastUpdated":1706436523000}'),e={name:"front-end-engineering/webpack常用拓展.md"},o=l(`

webpack常用拓展

清除输出目录

clean-webpack-plugin 当文件内容变化,重新打包,会自动删除原来打包的文件

js
module.exports = {
     plugins: [
         new CleanWebpackPlugin()
     ],
diff --git "a/assets/front-end-engineering_webpack\345\270\270\347\224\250\346\213\223\345\261\225.md.bb19598b.lean.js" "b/assets/front-end-engineering_webpack\345\270\270\347\224\250\346\213\223\345\261\225.md.6be37d04.lean.js"
similarity index 95%
rename from "assets/front-end-engineering_webpack\345\270\270\347\224\250\346\213\223\345\261\225.md.bb19598b.lean.js"
rename to "assets/front-end-engineering_webpack\345\270\270\347\224\250\346\213\223\345\261\225.md.6be37d04.lean.js"
index 51c7614e..f95d9971 100644
--- "a/assets/front-end-engineering_webpack\345\270\270\347\224\250\346\213\223\345\261\225.md.bb19598b.lean.js"
+++ "b/assets/front-end-engineering_webpack\345\270\270\347\224\250\346\213\223\345\261\225.md.6be37d04.lean.js"
@@ -1 +1 @@
-import{_ as s,o as n,c as a,a as l}from"./app.679ab08c.js";const p="/blog/assets/2023-01-06-16-01-47.0a0026be.png",b=JSON.parse('{"title":"webpack常用拓展","description":"","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":[]},{"level":2,"title":"webpack内置插件","slug":"webpack内置插件","link":"#webpack内置插件","children":[{"level":3,"title":"DefinePlugin","slug":"defineplugin","link":"#defineplugin","children":[]},{"level":3,"title":"BannerPlugin","slug":"bannerplugin","link":"#bannerplugin","children":[]},{"level":3,"title":"ProvidePlugin","slug":"provideplugin","link":"#provideplugin","children":[]}]}],"relativePath":"front-end-engineering/webpack常用拓展.md","lastUpdated":1706436523000}'),e={name:"front-end-engineering/webpack常用拓展.md"},o=l("",62),c=[o];function r(t,B,i,y,F,d){return n(),a("div",null,c)}const m=s(e,[["render",r]]);export{b as __pageData,m as default};
+import{_ as s,o as n,c as a,a as l}from"./app.815d1813.js";const p="/blog/assets/2023-01-06-16-01-47.0a0026be.png",b=JSON.parse('{"title":"webpack常用拓展","description":"","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":[]},{"level":2,"title":"webpack内置插件","slug":"webpack内置插件","link":"#webpack内置插件","children":[{"level":3,"title":"DefinePlugin","slug":"defineplugin","link":"#defineplugin","children":[]},{"level":3,"title":"BannerPlugin","slug":"bannerplugin","link":"#bannerplugin","children":[]},{"level":3,"title":"ProvidePlugin","slug":"provideplugin","link":"#provideplugin","children":[]}]}],"relativePath":"front-end-engineering/webpack常用拓展.md","lastUpdated":1706436523000}'),e={name:"front-end-engineering/webpack常用拓展.md"},o=l("",62),c=[o];function r(t,B,i,y,F,d){return n(),a("div",null,c)}const m=s(e,[["render",r]]);export{b as __pageData,m as default};
diff --git "a/assets/front-end-engineering_\343\200\220\345\256\214\347\273\223\343\200\221\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226.md.a3ffd0ed.js" "b/assets/front-end-engineering_\343\200\220\345\256\214\347\273\223\343\200\221\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226.md.403363ad.js"
similarity index 99%
rename from "assets/front-end-engineering_\343\200\220\345\256\214\347\273\223\343\200\221\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226.md.a3ffd0ed.js"
rename to "assets/front-end-engineering_\343\200\220\345\256\214\347\273\223\343\200\221\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226.md.403363ad.js"
index 6c0a54ea..bbb390b3 100644
--- "a/assets/front-end-engineering_\343\200\220\345\256\214\347\273\223\343\200\221\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226.md.a3ffd0ed.js"
+++ "b/assets/front-end-engineering_\343\200\220\345\256\214\347\273\223\343\200\221\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226.md.403363ad.js"
@@ -1,4 +1,4 @@
-import{_ as s,o as a,c as n,a as l}from"./app.679ab08c.js";const F=JSON.parse('{"title":"npx","description":"","frontmatter":{},"headers":[{"level":2,"title":"为什么会出现前端工程化","slug":"为什么会出现前端工程化","link":"#为什么会出现前端工程化","children":[]},{"level":2,"title":"nodejs对CommonJS的实现(面试)","slug":"nodejs对commonjs的实现-面试","link":"#nodejs对commonjs的实现-面试","children":[]},{"level":2,"title":"CommonJS","slug":"commonjs","link":"#commonjs","children":[]},{"level":2,"title":"node","slug":"node","link":"#node","children":[]},{"level":2,"title":"CommonJS标准和使用","slug":"commonjs标准和使用","link":"#commonjs标准和使用","children":[]},{"level":2,"title":"下面的代码执行结果是什么?","slug":"下面的代码执行结果是什么","link":"#下面的代码执行结果是什么","children":[]},{"level":2,"title":"ES6 module","slug":"es6-module","link":"#es6-module","children":[]},{"level":2,"title":"模块的引入","slug":"模块的引入","link":"#模块的引入","children":[]},{"level":2,"title":"标准和使用","slug":"标准和使用","link":"#标准和使用","children":[]},{"level":2,"title":"请对比一下CommonJS和ES Module","slug":"请对比一下commonjs和es-module","link":"#请对比一下commonjs和es-module","children":[]},{"level":2,"title":"说一下你对前端工程化,模块化,组件化的理解?","slug":"说一下你对前端工程化-模块化-组件化的理解","link":"#说一下你对前端工程化-模块化-组件化的理解","children":[]},{"level":2,"title":"webpack 中的 loader 属性和 plugins 属性的区别是什么?","slug":"webpack-中的-loader-属性和-plugins-属性的区别是什么","link":"#webpack-中的-loader-属性和-plugins-属性的区别是什么","children":[]},{"level":2,"title":"webpack 的核心概念都有哪些?","slug":"webpack-的核心概念都有哪些","link":"#webpack-的核心概念都有哪些","children":[]},{"level":2,"title":"ES6 中如何实现模块化的异步加载?","slug":"es6-中如何实现模块化的异步加载","link":"#es6-中如何实现模块化的异步加载","children":[]},{"level":2,"title":"说一下 webpack 中的几种 hash 的实现原理是什么?","slug":"说一下-webpack-中的几种-hash-的实现原理是什么","link":"#说一下-webpack-中的几种-hash-的实现原理是什么","children":[]},{"level":2,"title":"webpack 如果使用了 hash 命名,那是每次都会重新生成 hash 吗?","slug":"webpack-如果使用了-hash-命名-那是每次都会重新生成-hash-吗","link":"#webpack-如果使用了-hash-命名-那是每次都会重新生成-hash-吗","children":[]},{"level":2,"title":"webpack 中是如何处理图片的? (抖音直播)","slug":"webpack-中是如何处理图片的-抖音直播","link":"#webpack-中是如何处理图片的-抖音直播","children":[]},{"level":2,"title":"webpack 打包出来的 html 为什么 style 放在头部 script 放在底部?","slug":"webpack-打包出来的-html-为什么-style-放在头部-script-放在底部","link":"#webpack-打包出来的-html-为什么-style-放在头部-script-放在底部","children":[]},{"level":2,"title":"webpack 配置如何实现开发环境不使用 cdn、生产环境使用 cdn?","slug":"webpack-配置如何实现开发环境不使用-cdn、生产环境使用-cdn","link":"#webpack-配置如何实现开发环境不使用-cdn、生产环境使用-cdn","children":[]},{"level":2,"title":"介绍一下 webpack4 中的 tree-shaking 的工作流程?","slug":"介绍一下-webpack4-中的-tree-shaking-的工作流程","link":"#介绍一下-webpack4-中的-tree-shaking-的工作流程","children":[]},{"level":2,"title":"说一下 webpack loader 的作用是什么?","slug":"说一下-webpack-loader-的作用是什么","link":"#说一下-webpack-loader-的作用是什么","children":[]},{"level":2,"title":"在开发过程中如果需要对已有模块进行扩展,如何进行开发保证调用方不受影响?","slug":"在开发过程中如果需要对已有模块进行扩展-如何进行开发保证调用方不受影响","link":"#在开发过程中如果需要对已有模块进行扩展-如何进行开发保证调用方不受影响","children":[]},{"level":2,"title":"export 和 export default 的区别是什么?","slug":"export-和-export-default-的区别是什么","link":"#export-和-export-default-的区别是什么","children":[]},{"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":[]},{"level":2,"title":"webpack 如何实现动态导入?","slug":"webpack-如何实现动态导入","link":"#webpack-如何实现动态导入","children":[]},{"level":2,"title":"说一下 webpack 有哪几种文件指纹","slug":"说一下-webpack-有哪几种文件指纹","link":"#说一下-webpack-有哪几种文件指纹","children":[]},{"level":2,"title":"常用的 webpack Loader 都有哪些?","slug":"常用的-webpack-loader-都有哪些","link":"#常用的-webpack-loader-都有哪些","children":[]},{"level":2,"title":"说一下 webpack 常用插件都有哪些?","slug":"说一下-webpack-常用插件都有哪些","link":"#说一下-webpack-常用插件都有哪些","children":[]},{"level":2,"title":"使用 babel-loader 会有哪些问题,可以怎样优化?","slug":"使用-babel-loader-会有哪些问题-可以怎样优化","link":"#使用-babel-loader-会有哪些问题-可以怎样优化","children":[]},{"level":2,"title":"babel 是如何对 class 进行编译的?","slug":"babel-是如何对-class-进行编译的","link":"#babel-是如何对-class-进行编译的","children":[]},{"level":2,"title":"释一下 babel-polyfill 的作用是什么?","slug":"释一下-babel-polyfill-的作用是什么","link":"#释一下-babel-polyfill-的作用是什么","children":[]},{"level":2,"title":"解释一下 less 的&的操作符是做什么用的?","slug":"解释一下-less-的-的操作符是做什么用的","link":"#解释一下-less-的-的操作符是做什么用的","children":[]},{"level":2,"title":"webpack proxy 工作原理,为什么能解决跨域?","slug":"webpack-proxy-工作原理-为什么能解决跨域","link":"#webpack-proxy-工作原理-为什么能解决跨域","children":[]},{"level":2,"title":"组件发布的是不是所有依赖这个组件库的项目都需要升级?","slug":"组件发布的是不是所有依赖这个组件库的项目都需要升级","link":"#组件发布的是不是所有依赖这个组件库的项目都需要升级","children":[]},{"level":2,"title":"开发过程中,如何进行公共组件的设计?(字节跳动)","slug":"开发过程中-如何进行公共组件的设计-字节跳动","link":"#开发过程中-如何进行公共组件的设计-字节跳动","children":[]},{"level":2,"title":"具体说一下 splitchunksplugin 的使用场景及使用方法。(字节跳动)","slug":"具体说一下-splitchunksplugin-的使用场景及使用方法。-字节跳动","link":"#具体说一下-splitchunksplugin-的使用场景及使用方法。-字节跳动","children":[]},{"level":2,"title":"描述一下 webpack 的构建流程?(CVTE)","slug":"描述一下-webpack-的构建流程-cvte","link":"#描述一下-webpack-的构建流程-cvte","children":[]},{"level":2,"title":"解释一下 webpack 插件的实现原理?(CVTE)","slug":"解释一下-webpack-插件的实现原理-cvte","link":"#解释一下-webpack-插件的实现原理-cvte","children":[]},{"level":2,"title":"有用过哪些插件做项目的分析吗?(CVTE)","slug":"有用过哪些插件做项目的分析吗-cvte","link":"#有用过哪些插件做项目的分析吗-cvte","children":[]},{"level":2,"title":"什么是 babel,有什么作用?","slug":"什么是-babel-有什么作用","link":"#什么是-babel-有什么作用","children":[]},{"level":2,"title":"解释一下 npm 模块安装机制是什么?","slug":"解释一下-npm-模块安装机制是什么","link":"#解释一下-npm-模块安装机制是什么","children":[]},{"level":2,"title":"webpack与grunt、gulp的不同?","slug":"webpack与grunt、gulp的不同","link":"#webpack与grunt、gulp的不同","children":[]},{"level":2,"title":"2. 与webpack类似的工具还有哪些?谈谈你为什么最终选择(或放弃)使用webpack?","slug":"_2-与webpack类似的工具还有哪些-谈谈你为什么最终选择-或放弃-使用webpack","link":"#_2-与webpack类似的工具还有哪些-谈谈你为什么最终选择-或放弃-使用webpack","children":[]},{"level":2,"title":"3.有哪些常见的Loader?他们是解决什么问题的?","slug":"_3-有哪些常见的loader-他们是解决什么问题的","link":"#_3-有哪些常见的loader-他们是解决什么问题的","children":[]},{"level":2,"title":"4.有哪些常见的Plugin?他们是解决什么问题的?","slug":"_4-有哪些常见的plugin-他们是解决什么问题的","link":"#_4-有哪些常见的plugin-他们是解决什么问题的","children":[]},{"level":2,"title":"5.Loader和Plugin的不同?","slug":"_5-loader和plugin的不同","link":"#_5-loader和plugin的不同","children":[]},{"level":2,"title":"6.webpack的构建流程是什么?从读取配置到输出文件这个过程尽量说全","slug":"_6-webpack的构建流程是什么-从读取配置到输出文件这个过程尽量说全","link":"#_6-webpack的构建流程是什么-从读取配置到输出文件这个过程尽量说全","children":[]},{"level":2,"title":"7.是否写过Loader和Plugin?描述一下编写loader或plugin的思路?","slug":"_7-是否写过loader和plugin-描述一下编写loader或plugin的思路","link":"#_7-是否写过loader和plugin-描述一下编写loader或plugin的思路","children":[]},{"level":2,"title":"8.webpack的热更新是如何做到的?说明其原理?","slug":"_8-webpack的热更新是如何做到的-说明其原理","link":"#_8-webpack的热更新是如何做到的-说明其原理","children":[]},{"level":2,"title":"9.如何利用webpack来优化前端性能?(提高性能和体验)","slug":"_9-如何利用webpack来优化前端性能-提高性能和体验","link":"#_9-如何利用webpack来优化前端性能-提高性能和体验","children":[]},{"level":2,"title":"10.如何提高webpack的构建速度?","slug":"_10-如何提高webpack的构建速度","link":"#_10-如何提高webpack的构建速度","children":[]},{"level":2,"title":"11.怎么配置单页应用?怎么配置多页应用?","slug":"_11-怎么配置单页应用-怎么配置多页应用","link":"#_11-怎么配置单页应用-怎么配置多页应用","children":[]},{"level":2,"title":"12.npm打包时需要注意哪些?如何利用webpack来更好的构建?","slug":"_12-npm打包时需要注意哪些-如何利用webpack来更好的构建","link":"#_12-npm打包时需要注意哪些-如何利用webpack来更好的构建","children":[]},{"level":2,"title":"13.如何在vue项目中实现按需加载?","slug":"_13-如何在vue项目中实现按需加载","link":"#_13-如何在vue项目中实现按需加载","children":[]},{"level":2,"title":"能说说webpack的作用吗?","slug":"能说说webpack的作用吗","link":"#能说说webpack的作用吗","children":[]},{"level":2,"title":"为什么要打包呢?","slug":"为什么要打包呢","link":"#为什么要打包呢","children":[]},{"level":2,"title":"能说说模块化的好处吗?","slug":"能说说模块化的好处吗","link":"#能说说模块化的好处吗","children":[]},{"level":2,"title":"模块化打包方案知道啥,可以说说吗?","slug":"模块化打包方案知道啥-可以说说吗","link":"#模块化打包方案知道啥-可以说说吗","children":[]},{"level":2,"title":"那ES6 模块与 CommonJS 模块的差异有哪些呢?","slug":"那es6-模块与-commonjs-模块的差异有哪些呢","link":"#那es6-模块与-commonjs-模块的差异有哪些呢","children":[]},{"level":2,"title":"webpack的编译(打包)流程说说","slug":"webpack的编译-打包-流程说说","link":"#webpack的编译-打包-流程说说","children":[]},{"level":2,"title":"说一下 Webpack 的热更新原理吧?","slug":"说一下-webpack-的热更新原理吧","link":"#说一下-webpack-的热更新原理吧","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":[]},{"level":2,"title":"git","slug":"git","link":"#git","children":[{"level":3,"title":"打造前后端分离的开发环境,一般需要从哪几个方面进行设计","slug":"打造前后端分离的开发环境-一般需要从哪几个方面进行设计","link":"#打造前后端分离的开发环境-一般需要从哪几个方面进行设计","children":[]}]}],"relativePath":"front-end-engineering/【完结】前端工程化.md","lastUpdated":1710671080000}'),p={name:"front-end-engineering/【完结】前端工程化.md"},e=l(`

为什么会出现前端工程化

模块化:意味着咱们的 JS 总算可以开发大型项目(解决 JS 的复用问题)

组件化:解决 HTML、CSS、JS 的复用问题

工程化:因为咱们的前端项目越来越复杂、咱们的前端项目模块(组件)越来越多

前端项目越来越复杂?

在书写前端项目的时候,可能会涉及到使用其他的语言,typescript、less/sass、coffeescript,涉及到编译

在部署的时候,还需要对代码进行丑化、压缩

代码优化:为 CSS 代码添加兼容性前缀

前端项目模块(组件)越来越多?

专门有一个 node_modules 来管理这些可以服用的代码。

前端工程化的出现,归根结底,其实就是要解决开发环境和生产环境不一致的问题。

nodejs对CommonJS的实现(面试)

为了实现CommonJS规范,nodejs对模块做出了以下处理

  1. 为了保证高效的执行,仅加载必要的模块。nodejs只有执行到require函数时才会加载并执行模块

nodejs中导入模块,使用相对路径,并且必须以./或../开头,浏览器可以省略./,nodejs不行

  1. 为了隐藏模块中的代码,nodejs执行模块时,会将模块中的所有代码放置到一个函数中执行,以保证不污染全局变量。
javascript
 (function(){
+import{_ as s,o as a,c as n,a as l}from"./app.815d1813.js";const F=JSON.parse('{"title":"npx","description":"","frontmatter":{},"headers":[{"level":2,"title":"为什么会出现前端工程化","slug":"为什么会出现前端工程化","link":"#为什么会出现前端工程化","children":[]},{"level":2,"title":"nodejs对CommonJS的实现(面试)","slug":"nodejs对commonjs的实现-面试","link":"#nodejs对commonjs的实现-面试","children":[]},{"level":2,"title":"CommonJS","slug":"commonjs","link":"#commonjs","children":[]},{"level":2,"title":"node","slug":"node","link":"#node","children":[]},{"level":2,"title":"CommonJS标准和使用","slug":"commonjs标准和使用","link":"#commonjs标准和使用","children":[]},{"level":2,"title":"下面的代码执行结果是什么?","slug":"下面的代码执行结果是什么","link":"#下面的代码执行结果是什么","children":[]},{"level":2,"title":"ES6 module","slug":"es6-module","link":"#es6-module","children":[]},{"level":2,"title":"模块的引入","slug":"模块的引入","link":"#模块的引入","children":[]},{"level":2,"title":"标准和使用","slug":"标准和使用","link":"#标准和使用","children":[]},{"level":2,"title":"请对比一下CommonJS和ES Module","slug":"请对比一下commonjs和es-module","link":"#请对比一下commonjs和es-module","children":[]},{"level":2,"title":"说一下你对前端工程化,模块化,组件化的理解?","slug":"说一下你对前端工程化-模块化-组件化的理解","link":"#说一下你对前端工程化-模块化-组件化的理解","children":[]},{"level":2,"title":"webpack 中的 loader 属性和 plugins 属性的区别是什么?","slug":"webpack-中的-loader-属性和-plugins-属性的区别是什么","link":"#webpack-中的-loader-属性和-plugins-属性的区别是什么","children":[]},{"level":2,"title":"webpack 的核心概念都有哪些?","slug":"webpack-的核心概念都有哪些","link":"#webpack-的核心概念都有哪些","children":[]},{"level":2,"title":"ES6 中如何实现模块化的异步加载?","slug":"es6-中如何实现模块化的异步加载","link":"#es6-中如何实现模块化的异步加载","children":[]},{"level":2,"title":"说一下 webpack 中的几种 hash 的实现原理是什么?","slug":"说一下-webpack-中的几种-hash-的实现原理是什么","link":"#说一下-webpack-中的几种-hash-的实现原理是什么","children":[]},{"level":2,"title":"webpack 如果使用了 hash 命名,那是每次都会重新生成 hash 吗?","slug":"webpack-如果使用了-hash-命名-那是每次都会重新生成-hash-吗","link":"#webpack-如果使用了-hash-命名-那是每次都会重新生成-hash-吗","children":[]},{"level":2,"title":"webpack 中是如何处理图片的? (抖音直播)","slug":"webpack-中是如何处理图片的-抖音直播","link":"#webpack-中是如何处理图片的-抖音直播","children":[]},{"level":2,"title":"webpack 打包出来的 html 为什么 style 放在头部 script 放在底部?","slug":"webpack-打包出来的-html-为什么-style-放在头部-script-放在底部","link":"#webpack-打包出来的-html-为什么-style-放在头部-script-放在底部","children":[]},{"level":2,"title":"webpack 配置如何实现开发环境不使用 cdn、生产环境使用 cdn?","slug":"webpack-配置如何实现开发环境不使用-cdn、生产环境使用-cdn","link":"#webpack-配置如何实现开发环境不使用-cdn、生产环境使用-cdn","children":[]},{"level":2,"title":"介绍一下 webpack4 中的 tree-shaking 的工作流程?","slug":"介绍一下-webpack4-中的-tree-shaking-的工作流程","link":"#介绍一下-webpack4-中的-tree-shaking-的工作流程","children":[]},{"level":2,"title":"说一下 webpack loader 的作用是什么?","slug":"说一下-webpack-loader-的作用是什么","link":"#说一下-webpack-loader-的作用是什么","children":[]},{"level":2,"title":"在开发过程中如果需要对已有模块进行扩展,如何进行开发保证调用方不受影响?","slug":"在开发过程中如果需要对已有模块进行扩展-如何进行开发保证调用方不受影响","link":"#在开发过程中如果需要对已有模块进行扩展-如何进行开发保证调用方不受影响","children":[]},{"level":2,"title":"export 和 export default 的区别是什么?","slug":"export-和-export-default-的区别是什么","link":"#export-和-export-default-的区别是什么","children":[]},{"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":[]},{"level":2,"title":"webpack 如何实现动态导入?","slug":"webpack-如何实现动态导入","link":"#webpack-如何实现动态导入","children":[]},{"level":2,"title":"说一下 webpack 有哪几种文件指纹","slug":"说一下-webpack-有哪几种文件指纹","link":"#说一下-webpack-有哪几种文件指纹","children":[]},{"level":2,"title":"常用的 webpack Loader 都有哪些?","slug":"常用的-webpack-loader-都有哪些","link":"#常用的-webpack-loader-都有哪些","children":[]},{"level":2,"title":"说一下 webpack 常用插件都有哪些?","slug":"说一下-webpack-常用插件都有哪些","link":"#说一下-webpack-常用插件都有哪些","children":[]},{"level":2,"title":"使用 babel-loader 会有哪些问题,可以怎样优化?","slug":"使用-babel-loader-会有哪些问题-可以怎样优化","link":"#使用-babel-loader-会有哪些问题-可以怎样优化","children":[]},{"level":2,"title":"babel 是如何对 class 进行编译的?","slug":"babel-是如何对-class-进行编译的","link":"#babel-是如何对-class-进行编译的","children":[]},{"level":2,"title":"释一下 babel-polyfill 的作用是什么?","slug":"释一下-babel-polyfill-的作用是什么","link":"#释一下-babel-polyfill-的作用是什么","children":[]},{"level":2,"title":"解释一下 less 的&的操作符是做什么用的?","slug":"解释一下-less-的-的操作符是做什么用的","link":"#解释一下-less-的-的操作符是做什么用的","children":[]},{"level":2,"title":"webpack proxy 工作原理,为什么能解决跨域?","slug":"webpack-proxy-工作原理-为什么能解决跨域","link":"#webpack-proxy-工作原理-为什么能解决跨域","children":[]},{"level":2,"title":"组件发布的是不是所有依赖这个组件库的项目都需要升级?","slug":"组件发布的是不是所有依赖这个组件库的项目都需要升级","link":"#组件发布的是不是所有依赖这个组件库的项目都需要升级","children":[]},{"level":2,"title":"开发过程中,如何进行公共组件的设计?(字节跳动)","slug":"开发过程中-如何进行公共组件的设计-字节跳动","link":"#开发过程中-如何进行公共组件的设计-字节跳动","children":[]},{"level":2,"title":"具体说一下 splitchunksplugin 的使用场景及使用方法。(字节跳动)","slug":"具体说一下-splitchunksplugin-的使用场景及使用方法。-字节跳动","link":"#具体说一下-splitchunksplugin-的使用场景及使用方法。-字节跳动","children":[]},{"level":2,"title":"描述一下 webpack 的构建流程?(CVTE)","slug":"描述一下-webpack-的构建流程-cvte","link":"#描述一下-webpack-的构建流程-cvte","children":[]},{"level":2,"title":"解释一下 webpack 插件的实现原理?(CVTE)","slug":"解释一下-webpack-插件的实现原理-cvte","link":"#解释一下-webpack-插件的实现原理-cvte","children":[]},{"level":2,"title":"有用过哪些插件做项目的分析吗?(CVTE)","slug":"有用过哪些插件做项目的分析吗-cvte","link":"#有用过哪些插件做项目的分析吗-cvte","children":[]},{"level":2,"title":"什么是 babel,有什么作用?","slug":"什么是-babel-有什么作用","link":"#什么是-babel-有什么作用","children":[]},{"level":2,"title":"解释一下 npm 模块安装机制是什么?","slug":"解释一下-npm-模块安装机制是什么","link":"#解释一下-npm-模块安装机制是什么","children":[]},{"level":2,"title":"webpack与grunt、gulp的不同?","slug":"webpack与grunt、gulp的不同","link":"#webpack与grunt、gulp的不同","children":[]},{"level":2,"title":"2. 与webpack类似的工具还有哪些?谈谈你为什么最终选择(或放弃)使用webpack?","slug":"_2-与webpack类似的工具还有哪些-谈谈你为什么最终选择-或放弃-使用webpack","link":"#_2-与webpack类似的工具还有哪些-谈谈你为什么最终选择-或放弃-使用webpack","children":[]},{"level":2,"title":"3.有哪些常见的Loader?他们是解决什么问题的?","slug":"_3-有哪些常见的loader-他们是解决什么问题的","link":"#_3-有哪些常见的loader-他们是解决什么问题的","children":[]},{"level":2,"title":"4.有哪些常见的Plugin?他们是解决什么问题的?","slug":"_4-有哪些常见的plugin-他们是解决什么问题的","link":"#_4-有哪些常见的plugin-他们是解决什么问题的","children":[]},{"level":2,"title":"5.Loader和Plugin的不同?","slug":"_5-loader和plugin的不同","link":"#_5-loader和plugin的不同","children":[]},{"level":2,"title":"6.webpack的构建流程是什么?从读取配置到输出文件这个过程尽量说全","slug":"_6-webpack的构建流程是什么-从读取配置到输出文件这个过程尽量说全","link":"#_6-webpack的构建流程是什么-从读取配置到输出文件这个过程尽量说全","children":[]},{"level":2,"title":"7.是否写过Loader和Plugin?描述一下编写loader或plugin的思路?","slug":"_7-是否写过loader和plugin-描述一下编写loader或plugin的思路","link":"#_7-是否写过loader和plugin-描述一下编写loader或plugin的思路","children":[]},{"level":2,"title":"8.webpack的热更新是如何做到的?说明其原理?","slug":"_8-webpack的热更新是如何做到的-说明其原理","link":"#_8-webpack的热更新是如何做到的-说明其原理","children":[]},{"level":2,"title":"9.如何利用webpack来优化前端性能?(提高性能和体验)","slug":"_9-如何利用webpack来优化前端性能-提高性能和体验","link":"#_9-如何利用webpack来优化前端性能-提高性能和体验","children":[]},{"level":2,"title":"10.如何提高webpack的构建速度?","slug":"_10-如何提高webpack的构建速度","link":"#_10-如何提高webpack的构建速度","children":[]},{"level":2,"title":"11.怎么配置单页应用?怎么配置多页应用?","slug":"_11-怎么配置单页应用-怎么配置多页应用","link":"#_11-怎么配置单页应用-怎么配置多页应用","children":[]},{"level":2,"title":"12.npm打包时需要注意哪些?如何利用webpack来更好的构建?","slug":"_12-npm打包时需要注意哪些-如何利用webpack来更好的构建","link":"#_12-npm打包时需要注意哪些-如何利用webpack来更好的构建","children":[]},{"level":2,"title":"13.如何在vue项目中实现按需加载?","slug":"_13-如何在vue项目中实现按需加载","link":"#_13-如何在vue项目中实现按需加载","children":[]},{"level":2,"title":"能说说webpack的作用吗?","slug":"能说说webpack的作用吗","link":"#能说说webpack的作用吗","children":[]},{"level":2,"title":"为什么要打包呢?","slug":"为什么要打包呢","link":"#为什么要打包呢","children":[]},{"level":2,"title":"能说说模块化的好处吗?","slug":"能说说模块化的好处吗","link":"#能说说模块化的好处吗","children":[]},{"level":2,"title":"模块化打包方案知道啥,可以说说吗?","slug":"模块化打包方案知道啥-可以说说吗","link":"#模块化打包方案知道啥-可以说说吗","children":[]},{"level":2,"title":"那ES6 模块与 CommonJS 模块的差异有哪些呢?","slug":"那es6-模块与-commonjs-模块的差异有哪些呢","link":"#那es6-模块与-commonjs-模块的差异有哪些呢","children":[]},{"level":2,"title":"webpack的编译(打包)流程说说","slug":"webpack的编译-打包-流程说说","link":"#webpack的编译-打包-流程说说","children":[]},{"level":2,"title":"说一下 Webpack 的热更新原理吧?","slug":"说一下-webpack-的热更新原理吧","link":"#说一下-webpack-的热更新原理吧","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":[]},{"level":2,"title":"git","slug":"git","link":"#git","children":[{"level":3,"title":"打造前后端分离的开发环境,一般需要从哪几个方面进行设计","slug":"打造前后端分离的开发环境-一般需要从哪几个方面进行设计","link":"#打造前后端分离的开发环境-一般需要从哪几个方面进行设计","children":[]}]}],"relativePath":"front-end-engineering/【完结】前端工程化.md","lastUpdated":1710671080000}'),p={name:"front-end-engineering/【完结】前端工程化.md"},e=l(`

为什么会出现前端工程化

模块化:意味着咱们的 JS 总算可以开发大型项目(解决 JS 的复用问题)

组件化:解决 HTML、CSS、JS 的复用问题

工程化:因为咱们的前端项目越来越复杂、咱们的前端项目模块(组件)越来越多

前端项目越来越复杂?

在书写前端项目的时候,可能会涉及到使用其他的语言,typescript、less/sass、coffeescript,涉及到编译

在部署的时候,还需要对代码进行丑化、压缩

代码优化:为 CSS 代码添加兼容性前缀

前端项目模块(组件)越来越多?

专门有一个 node_modules 来管理这些可以服用的代码。

前端工程化的出现,归根结底,其实就是要解决开发环境和生产环境不一致的问题。

nodejs对CommonJS的实现(面试)

为了实现CommonJS规范,nodejs对模块做出了以下处理

  1. 为了保证高效的执行,仅加载必要的模块。nodejs只有执行到require函数时才会加载并执行模块

nodejs中导入模块,使用相对路径,并且必须以./或../开头,浏览器可以省略./,nodejs不行

  1. 为了隐藏模块中的代码,nodejs执行模块时,会将模块中的所有代码放置到一个函数中执行,以保证不污染全局变量。
javascript
 (function(){
      //模块中的代码
  })()
 
 (function(){
diff --git "a/assets/front-end-engineering_\343\200\220\345\256\214\347\273\223\343\200\221\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226.md.a3ffd0ed.lean.js" "b/assets/front-end-engineering_\343\200\220\345\256\214\347\273\223\343\200\221\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226.md.403363ad.lean.js"
similarity index 99%
rename from "assets/front-end-engineering_\343\200\220\345\256\214\347\273\223\343\200\221\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226.md.a3ffd0ed.lean.js"
rename to "assets/front-end-engineering_\343\200\220\345\256\214\347\273\223\343\200\221\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226.md.403363ad.lean.js"
index 6c0a54ea..bbb390b3 100644
--- "a/assets/front-end-engineering_\343\200\220\345\256\214\347\273\223\343\200\221\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226.md.a3ffd0ed.lean.js"
+++ "b/assets/front-end-engineering_\343\200\220\345\256\214\347\273\223\343\200\221\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226.md.403363ad.lean.js"
@@ -1,4 +1,4 @@
-import{_ as s,o as a,c as n,a as l}from"./app.679ab08c.js";const F=JSON.parse('{"title":"npx","description":"","frontmatter":{},"headers":[{"level":2,"title":"为什么会出现前端工程化","slug":"为什么会出现前端工程化","link":"#为什么会出现前端工程化","children":[]},{"level":2,"title":"nodejs对CommonJS的实现(面试)","slug":"nodejs对commonjs的实现-面试","link":"#nodejs对commonjs的实现-面试","children":[]},{"level":2,"title":"CommonJS","slug":"commonjs","link":"#commonjs","children":[]},{"level":2,"title":"node","slug":"node","link":"#node","children":[]},{"level":2,"title":"CommonJS标准和使用","slug":"commonjs标准和使用","link":"#commonjs标准和使用","children":[]},{"level":2,"title":"下面的代码执行结果是什么?","slug":"下面的代码执行结果是什么","link":"#下面的代码执行结果是什么","children":[]},{"level":2,"title":"ES6 module","slug":"es6-module","link":"#es6-module","children":[]},{"level":2,"title":"模块的引入","slug":"模块的引入","link":"#模块的引入","children":[]},{"level":2,"title":"标准和使用","slug":"标准和使用","link":"#标准和使用","children":[]},{"level":2,"title":"请对比一下CommonJS和ES Module","slug":"请对比一下commonjs和es-module","link":"#请对比一下commonjs和es-module","children":[]},{"level":2,"title":"说一下你对前端工程化,模块化,组件化的理解?","slug":"说一下你对前端工程化-模块化-组件化的理解","link":"#说一下你对前端工程化-模块化-组件化的理解","children":[]},{"level":2,"title":"webpack 中的 loader 属性和 plugins 属性的区别是什么?","slug":"webpack-中的-loader-属性和-plugins-属性的区别是什么","link":"#webpack-中的-loader-属性和-plugins-属性的区别是什么","children":[]},{"level":2,"title":"webpack 的核心概念都有哪些?","slug":"webpack-的核心概念都有哪些","link":"#webpack-的核心概念都有哪些","children":[]},{"level":2,"title":"ES6 中如何实现模块化的异步加载?","slug":"es6-中如何实现模块化的异步加载","link":"#es6-中如何实现模块化的异步加载","children":[]},{"level":2,"title":"说一下 webpack 中的几种 hash 的实现原理是什么?","slug":"说一下-webpack-中的几种-hash-的实现原理是什么","link":"#说一下-webpack-中的几种-hash-的实现原理是什么","children":[]},{"level":2,"title":"webpack 如果使用了 hash 命名,那是每次都会重新生成 hash 吗?","slug":"webpack-如果使用了-hash-命名-那是每次都会重新生成-hash-吗","link":"#webpack-如果使用了-hash-命名-那是每次都会重新生成-hash-吗","children":[]},{"level":2,"title":"webpack 中是如何处理图片的? (抖音直播)","slug":"webpack-中是如何处理图片的-抖音直播","link":"#webpack-中是如何处理图片的-抖音直播","children":[]},{"level":2,"title":"webpack 打包出来的 html 为什么 style 放在头部 script 放在底部?","slug":"webpack-打包出来的-html-为什么-style-放在头部-script-放在底部","link":"#webpack-打包出来的-html-为什么-style-放在头部-script-放在底部","children":[]},{"level":2,"title":"webpack 配置如何实现开发环境不使用 cdn、生产环境使用 cdn?","slug":"webpack-配置如何实现开发环境不使用-cdn、生产环境使用-cdn","link":"#webpack-配置如何实现开发环境不使用-cdn、生产环境使用-cdn","children":[]},{"level":2,"title":"介绍一下 webpack4 中的 tree-shaking 的工作流程?","slug":"介绍一下-webpack4-中的-tree-shaking-的工作流程","link":"#介绍一下-webpack4-中的-tree-shaking-的工作流程","children":[]},{"level":2,"title":"说一下 webpack loader 的作用是什么?","slug":"说一下-webpack-loader-的作用是什么","link":"#说一下-webpack-loader-的作用是什么","children":[]},{"level":2,"title":"在开发过程中如果需要对已有模块进行扩展,如何进行开发保证调用方不受影响?","slug":"在开发过程中如果需要对已有模块进行扩展-如何进行开发保证调用方不受影响","link":"#在开发过程中如果需要对已有模块进行扩展-如何进行开发保证调用方不受影响","children":[]},{"level":2,"title":"export 和 export default 的区别是什么?","slug":"export-和-export-default-的区别是什么","link":"#export-和-export-default-的区别是什么","children":[]},{"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":[]},{"level":2,"title":"webpack 如何实现动态导入?","slug":"webpack-如何实现动态导入","link":"#webpack-如何实现动态导入","children":[]},{"level":2,"title":"说一下 webpack 有哪几种文件指纹","slug":"说一下-webpack-有哪几种文件指纹","link":"#说一下-webpack-有哪几种文件指纹","children":[]},{"level":2,"title":"常用的 webpack Loader 都有哪些?","slug":"常用的-webpack-loader-都有哪些","link":"#常用的-webpack-loader-都有哪些","children":[]},{"level":2,"title":"说一下 webpack 常用插件都有哪些?","slug":"说一下-webpack-常用插件都有哪些","link":"#说一下-webpack-常用插件都有哪些","children":[]},{"level":2,"title":"使用 babel-loader 会有哪些问题,可以怎样优化?","slug":"使用-babel-loader-会有哪些问题-可以怎样优化","link":"#使用-babel-loader-会有哪些问题-可以怎样优化","children":[]},{"level":2,"title":"babel 是如何对 class 进行编译的?","slug":"babel-是如何对-class-进行编译的","link":"#babel-是如何对-class-进行编译的","children":[]},{"level":2,"title":"释一下 babel-polyfill 的作用是什么?","slug":"释一下-babel-polyfill-的作用是什么","link":"#释一下-babel-polyfill-的作用是什么","children":[]},{"level":2,"title":"解释一下 less 的&的操作符是做什么用的?","slug":"解释一下-less-的-的操作符是做什么用的","link":"#解释一下-less-的-的操作符是做什么用的","children":[]},{"level":2,"title":"webpack proxy 工作原理,为什么能解决跨域?","slug":"webpack-proxy-工作原理-为什么能解决跨域","link":"#webpack-proxy-工作原理-为什么能解决跨域","children":[]},{"level":2,"title":"组件发布的是不是所有依赖这个组件库的项目都需要升级?","slug":"组件发布的是不是所有依赖这个组件库的项目都需要升级","link":"#组件发布的是不是所有依赖这个组件库的项目都需要升级","children":[]},{"level":2,"title":"开发过程中,如何进行公共组件的设计?(字节跳动)","slug":"开发过程中-如何进行公共组件的设计-字节跳动","link":"#开发过程中-如何进行公共组件的设计-字节跳动","children":[]},{"level":2,"title":"具体说一下 splitchunksplugin 的使用场景及使用方法。(字节跳动)","slug":"具体说一下-splitchunksplugin-的使用场景及使用方法。-字节跳动","link":"#具体说一下-splitchunksplugin-的使用场景及使用方法。-字节跳动","children":[]},{"level":2,"title":"描述一下 webpack 的构建流程?(CVTE)","slug":"描述一下-webpack-的构建流程-cvte","link":"#描述一下-webpack-的构建流程-cvte","children":[]},{"level":2,"title":"解释一下 webpack 插件的实现原理?(CVTE)","slug":"解释一下-webpack-插件的实现原理-cvte","link":"#解释一下-webpack-插件的实现原理-cvte","children":[]},{"level":2,"title":"有用过哪些插件做项目的分析吗?(CVTE)","slug":"有用过哪些插件做项目的分析吗-cvte","link":"#有用过哪些插件做项目的分析吗-cvte","children":[]},{"level":2,"title":"什么是 babel,有什么作用?","slug":"什么是-babel-有什么作用","link":"#什么是-babel-有什么作用","children":[]},{"level":2,"title":"解释一下 npm 模块安装机制是什么?","slug":"解释一下-npm-模块安装机制是什么","link":"#解释一下-npm-模块安装机制是什么","children":[]},{"level":2,"title":"webpack与grunt、gulp的不同?","slug":"webpack与grunt、gulp的不同","link":"#webpack与grunt、gulp的不同","children":[]},{"level":2,"title":"2. 与webpack类似的工具还有哪些?谈谈你为什么最终选择(或放弃)使用webpack?","slug":"_2-与webpack类似的工具还有哪些-谈谈你为什么最终选择-或放弃-使用webpack","link":"#_2-与webpack类似的工具还有哪些-谈谈你为什么最终选择-或放弃-使用webpack","children":[]},{"level":2,"title":"3.有哪些常见的Loader?他们是解决什么问题的?","slug":"_3-有哪些常见的loader-他们是解决什么问题的","link":"#_3-有哪些常见的loader-他们是解决什么问题的","children":[]},{"level":2,"title":"4.有哪些常见的Plugin?他们是解决什么问题的?","slug":"_4-有哪些常见的plugin-他们是解决什么问题的","link":"#_4-有哪些常见的plugin-他们是解决什么问题的","children":[]},{"level":2,"title":"5.Loader和Plugin的不同?","slug":"_5-loader和plugin的不同","link":"#_5-loader和plugin的不同","children":[]},{"level":2,"title":"6.webpack的构建流程是什么?从读取配置到输出文件这个过程尽量说全","slug":"_6-webpack的构建流程是什么-从读取配置到输出文件这个过程尽量说全","link":"#_6-webpack的构建流程是什么-从读取配置到输出文件这个过程尽量说全","children":[]},{"level":2,"title":"7.是否写过Loader和Plugin?描述一下编写loader或plugin的思路?","slug":"_7-是否写过loader和plugin-描述一下编写loader或plugin的思路","link":"#_7-是否写过loader和plugin-描述一下编写loader或plugin的思路","children":[]},{"level":2,"title":"8.webpack的热更新是如何做到的?说明其原理?","slug":"_8-webpack的热更新是如何做到的-说明其原理","link":"#_8-webpack的热更新是如何做到的-说明其原理","children":[]},{"level":2,"title":"9.如何利用webpack来优化前端性能?(提高性能和体验)","slug":"_9-如何利用webpack来优化前端性能-提高性能和体验","link":"#_9-如何利用webpack来优化前端性能-提高性能和体验","children":[]},{"level":2,"title":"10.如何提高webpack的构建速度?","slug":"_10-如何提高webpack的构建速度","link":"#_10-如何提高webpack的构建速度","children":[]},{"level":2,"title":"11.怎么配置单页应用?怎么配置多页应用?","slug":"_11-怎么配置单页应用-怎么配置多页应用","link":"#_11-怎么配置单页应用-怎么配置多页应用","children":[]},{"level":2,"title":"12.npm打包时需要注意哪些?如何利用webpack来更好的构建?","slug":"_12-npm打包时需要注意哪些-如何利用webpack来更好的构建","link":"#_12-npm打包时需要注意哪些-如何利用webpack来更好的构建","children":[]},{"level":2,"title":"13.如何在vue项目中实现按需加载?","slug":"_13-如何在vue项目中实现按需加载","link":"#_13-如何在vue项目中实现按需加载","children":[]},{"level":2,"title":"能说说webpack的作用吗?","slug":"能说说webpack的作用吗","link":"#能说说webpack的作用吗","children":[]},{"level":2,"title":"为什么要打包呢?","slug":"为什么要打包呢","link":"#为什么要打包呢","children":[]},{"level":2,"title":"能说说模块化的好处吗?","slug":"能说说模块化的好处吗","link":"#能说说模块化的好处吗","children":[]},{"level":2,"title":"模块化打包方案知道啥,可以说说吗?","slug":"模块化打包方案知道啥-可以说说吗","link":"#模块化打包方案知道啥-可以说说吗","children":[]},{"level":2,"title":"那ES6 模块与 CommonJS 模块的差异有哪些呢?","slug":"那es6-模块与-commonjs-模块的差异有哪些呢","link":"#那es6-模块与-commonjs-模块的差异有哪些呢","children":[]},{"level":2,"title":"webpack的编译(打包)流程说说","slug":"webpack的编译-打包-流程说说","link":"#webpack的编译-打包-流程说说","children":[]},{"level":2,"title":"说一下 Webpack 的热更新原理吧?","slug":"说一下-webpack-的热更新原理吧","link":"#说一下-webpack-的热更新原理吧","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":[]},{"level":2,"title":"git","slug":"git","link":"#git","children":[{"level":3,"title":"打造前后端分离的开发环境,一般需要从哪几个方面进行设计","slug":"打造前后端分离的开发环境-一般需要从哪几个方面进行设计","link":"#打造前后端分离的开发环境-一般需要从哪几个方面进行设计","children":[]}]}],"relativePath":"front-end-engineering/【完结】前端工程化.md","lastUpdated":1710671080000}'),p={name:"front-end-engineering/【完结】前端工程化.md"},e=l(`

为什么会出现前端工程化

模块化:意味着咱们的 JS 总算可以开发大型项目(解决 JS 的复用问题)

组件化:解决 HTML、CSS、JS 的复用问题

工程化:因为咱们的前端项目越来越复杂、咱们的前端项目模块(组件)越来越多

前端项目越来越复杂?

在书写前端项目的时候,可能会涉及到使用其他的语言,typescript、less/sass、coffeescript,涉及到编译

在部署的时候,还需要对代码进行丑化、压缩

代码优化:为 CSS 代码添加兼容性前缀

前端项目模块(组件)越来越多?

专门有一个 node_modules 来管理这些可以服用的代码。

前端工程化的出现,归根结底,其实就是要解决开发环境和生产环境不一致的问题。

nodejs对CommonJS的实现(面试)

为了实现CommonJS规范,nodejs对模块做出了以下处理

  1. 为了保证高效的执行,仅加载必要的模块。nodejs只有执行到require函数时才会加载并执行模块

nodejs中导入模块,使用相对路径,并且必须以./或../开头,浏览器可以省略./,nodejs不行

  1. 为了隐藏模块中的代码,nodejs执行模块时,会将模块中的所有代码放置到一个函数中执行,以保证不污染全局变量。
javascript
 (function(){
+import{_ as s,o as a,c as n,a as l}from"./app.815d1813.js";const F=JSON.parse('{"title":"npx","description":"","frontmatter":{},"headers":[{"level":2,"title":"为什么会出现前端工程化","slug":"为什么会出现前端工程化","link":"#为什么会出现前端工程化","children":[]},{"level":2,"title":"nodejs对CommonJS的实现(面试)","slug":"nodejs对commonjs的实现-面试","link":"#nodejs对commonjs的实现-面试","children":[]},{"level":2,"title":"CommonJS","slug":"commonjs","link":"#commonjs","children":[]},{"level":2,"title":"node","slug":"node","link":"#node","children":[]},{"level":2,"title":"CommonJS标准和使用","slug":"commonjs标准和使用","link":"#commonjs标准和使用","children":[]},{"level":2,"title":"下面的代码执行结果是什么?","slug":"下面的代码执行结果是什么","link":"#下面的代码执行结果是什么","children":[]},{"level":2,"title":"ES6 module","slug":"es6-module","link":"#es6-module","children":[]},{"level":2,"title":"模块的引入","slug":"模块的引入","link":"#模块的引入","children":[]},{"level":2,"title":"标准和使用","slug":"标准和使用","link":"#标准和使用","children":[]},{"level":2,"title":"请对比一下CommonJS和ES Module","slug":"请对比一下commonjs和es-module","link":"#请对比一下commonjs和es-module","children":[]},{"level":2,"title":"说一下你对前端工程化,模块化,组件化的理解?","slug":"说一下你对前端工程化-模块化-组件化的理解","link":"#说一下你对前端工程化-模块化-组件化的理解","children":[]},{"level":2,"title":"webpack 中的 loader 属性和 plugins 属性的区别是什么?","slug":"webpack-中的-loader-属性和-plugins-属性的区别是什么","link":"#webpack-中的-loader-属性和-plugins-属性的区别是什么","children":[]},{"level":2,"title":"webpack 的核心概念都有哪些?","slug":"webpack-的核心概念都有哪些","link":"#webpack-的核心概念都有哪些","children":[]},{"level":2,"title":"ES6 中如何实现模块化的异步加载?","slug":"es6-中如何实现模块化的异步加载","link":"#es6-中如何实现模块化的异步加载","children":[]},{"level":2,"title":"说一下 webpack 中的几种 hash 的实现原理是什么?","slug":"说一下-webpack-中的几种-hash-的实现原理是什么","link":"#说一下-webpack-中的几种-hash-的实现原理是什么","children":[]},{"level":2,"title":"webpack 如果使用了 hash 命名,那是每次都会重新生成 hash 吗?","slug":"webpack-如果使用了-hash-命名-那是每次都会重新生成-hash-吗","link":"#webpack-如果使用了-hash-命名-那是每次都会重新生成-hash-吗","children":[]},{"level":2,"title":"webpack 中是如何处理图片的? (抖音直播)","slug":"webpack-中是如何处理图片的-抖音直播","link":"#webpack-中是如何处理图片的-抖音直播","children":[]},{"level":2,"title":"webpack 打包出来的 html 为什么 style 放在头部 script 放在底部?","slug":"webpack-打包出来的-html-为什么-style-放在头部-script-放在底部","link":"#webpack-打包出来的-html-为什么-style-放在头部-script-放在底部","children":[]},{"level":2,"title":"webpack 配置如何实现开发环境不使用 cdn、生产环境使用 cdn?","slug":"webpack-配置如何实现开发环境不使用-cdn、生产环境使用-cdn","link":"#webpack-配置如何实现开发环境不使用-cdn、生产环境使用-cdn","children":[]},{"level":2,"title":"介绍一下 webpack4 中的 tree-shaking 的工作流程?","slug":"介绍一下-webpack4-中的-tree-shaking-的工作流程","link":"#介绍一下-webpack4-中的-tree-shaking-的工作流程","children":[]},{"level":2,"title":"说一下 webpack loader 的作用是什么?","slug":"说一下-webpack-loader-的作用是什么","link":"#说一下-webpack-loader-的作用是什么","children":[]},{"level":2,"title":"在开发过程中如果需要对已有模块进行扩展,如何进行开发保证调用方不受影响?","slug":"在开发过程中如果需要对已有模块进行扩展-如何进行开发保证调用方不受影响","link":"#在开发过程中如果需要对已有模块进行扩展-如何进行开发保证调用方不受影响","children":[]},{"level":2,"title":"export 和 export default 的区别是什么?","slug":"export-和-export-default-的区别是什么","link":"#export-和-export-default-的区别是什么","children":[]},{"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":[]},{"level":2,"title":"webpack 如何实现动态导入?","slug":"webpack-如何实现动态导入","link":"#webpack-如何实现动态导入","children":[]},{"level":2,"title":"说一下 webpack 有哪几种文件指纹","slug":"说一下-webpack-有哪几种文件指纹","link":"#说一下-webpack-有哪几种文件指纹","children":[]},{"level":2,"title":"常用的 webpack Loader 都有哪些?","slug":"常用的-webpack-loader-都有哪些","link":"#常用的-webpack-loader-都有哪些","children":[]},{"level":2,"title":"说一下 webpack 常用插件都有哪些?","slug":"说一下-webpack-常用插件都有哪些","link":"#说一下-webpack-常用插件都有哪些","children":[]},{"level":2,"title":"使用 babel-loader 会有哪些问题,可以怎样优化?","slug":"使用-babel-loader-会有哪些问题-可以怎样优化","link":"#使用-babel-loader-会有哪些问题-可以怎样优化","children":[]},{"level":2,"title":"babel 是如何对 class 进行编译的?","slug":"babel-是如何对-class-进行编译的","link":"#babel-是如何对-class-进行编译的","children":[]},{"level":2,"title":"释一下 babel-polyfill 的作用是什么?","slug":"释一下-babel-polyfill-的作用是什么","link":"#释一下-babel-polyfill-的作用是什么","children":[]},{"level":2,"title":"解释一下 less 的&的操作符是做什么用的?","slug":"解释一下-less-的-的操作符是做什么用的","link":"#解释一下-less-的-的操作符是做什么用的","children":[]},{"level":2,"title":"webpack proxy 工作原理,为什么能解决跨域?","slug":"webpack-proxy-工作原理-为什么能解决跨域","link":"#webpack-proxy-工作原理-为什么能解决跨域","children":[]},{"level":2,"title":"组件发布的是不是所有依赖这个组件库的项目都需要升级?","slug":"组件发布的是不是所有依赖这个组件库的项目都需要升级","link":"#组件发布的是不是所有依赖这个组件库的项目都需要升级","children":[]},{"level":2,"title":"开发过程中,如何进行公共组件的设计?(字节跳动)","slug":"开发过程中-如何进行公共组件的设计-字节跳动","link":"#开发过程中-如何进行公共组件的设计-字节跳动","children":[]},{"level":2,"title":"具体说一下 splitchunksplugin 的使用场景及使用方法。(字节跳动)","slug":"具体说一下-splitchunksplugin-的使用场景及使用方法。-字节跳动","link":"#具体说一下-splitchunksplugin-的使用场景及使用方法。-字节跳动","children":[]},{"level":2,"title":"描述一下 webpack 的构建流程?(CVTE)","slug":"描述一下-webpack-的构建流程-cvte","link":"#描述一下-webpack-的构建流程-cvte","children":[]},{"level":2,"title":"解释一下 webpack 插件的实现原理?(CVTE)","slug":"解释一下-webpack-插件的实现原理-cvte","link":"#解释一下-webpack-插件的实现原理-cvte","children":[]},{"level":2,"title":"有用过哪些插件做项目的分析吗?(CVTE)","slug":"有用过哪些插件做项目的分析吗-cvte","link":"#有用过哪些插件做项目的分析吗-cvte","children":[]},{"level":2,"title":"什么是 babel,有什么作用?","slug":"什么是-babel-有什么作用","link":"#什么是-babel-有什么作用","children":[]},{"level":2,"title":"解释一下 npm 模块安装机制是什么?","slug":"解释一下-npm-模块安装机制是什么","link":"#解释一下-npm-模块安装机制是什么","children":[]},{"level":2,"title":"webpack与grunt、gulp的不同?","slug":"webpack与grunt、gulp的不同","link":"#webpack与grunt、gulp的不同","children":[]},{"level":2,"title":"2. 与webpack类似的工具还有哪些?谈谈你为什么最终选择(或放弃)使用webpack?","slug":"_2-与webpack类似的工具还有哪些-谈谈你为什么最终选择-或放弃-使用webpack","link":"#_2-与webpack类似的工具还有哪些-谈谈你为什么最终选择-或放弃-使用webpack","children":[]},{"level":2,"title":"3.有哪些常见的Loader?他们是解决什么问题的?","slug":"_3-有哪些常见的loader-他们是解决什么问题的","link":"#_3-有哪些常见的loader-他们是解决什么问题的","children":[]},{"level":2,"title":"4.有哪些常见的Plugin?他们是解决什么问题的?","slug":"_4-有哪些常见的plugin-他们是解决什么问题的","link":"#_4-有哪些常见的plugin-他们是解决什么问题的","children":[]},{"level":2,"title":"5.Loader和Plugin的不同?","slug":"_5-loader和plugin的不同","link":"#_5-loader和plugin的不同","children":[]},{"level":2,"title":"6.webpack的构建流程是什么?从读取配置到输出文件这个过程尽量说全","slug":"_6-webpack的构建流程是什么-从读取配置到输出文件这个过程尽量说全","link":"#_6-webpack的构建流程是什么-从读取配置到输出文件这个过程尽量说全","children":[]},{"level":2,"title":"7.是否写过Loader和Plugin?描述一下编写loader或plugin的思路?","slug":"_7-是否写过loader和plugin-描述一下编写loader或plugin的思路","link":"#_7-是否写过loader和plugin-描述一下编写loader或plugin的思路","children":[]},{"level":2,"title":"8.webpack的热更新是如何做到的?说明其原理?","slug":"_8-webpack的热更新是如何做到的-说明其原理","link":"#_8-webpack的热更新是如何做到的-说明其原理","children":[]},{"level":2,"title":"9.如何利用webpack来优化前端性能?(提高性能和体验)","slug":"_9-如何利用webpack来优化前端性能-提高性能和体验","link":"#_9-如何利用webpack来优化前端性能-提高性能和体验","children":[]},{"level":2,"title":"10.如何提高webpack的构建速度?","slug":"_10-如何提高webpack的构建速度","link":"#_10-如何提高webpack的构建速度","children":[]},{"level":2,"title":"11.怎么配置单页应用?怎么配置多页应用?","slug":"_11-怎么配置单页应用-怎么配置多页应用","link":"#_11-怎么配置单页应用-怎么配置多页应用","children":[]},{"level":2,"title":"12.npm打包时需要注意哪些?如何利用webpack来更好的构建?","slug":"_12-npm打包时需要注意哪些-如何利用webpack来更好的构建","link":"#_12-npm打包时需要注意哪些-如何利用webpack来更好的构建","children":[]},{"level":2,"title":"13.如何在vue项目中实现按需加载?","slug":"_13-如何在vue项目中实现按需加载","link":"#_13-如何在vue项目中实现按需加载","children":[]},{"level":2,"title":"能说说webpack的作用吗?","slug":"能说说webpack的作用吗","link":"#能说说webpack的作用吗","children":[]},{"level":2,"title":"为什么要打包呢?","slug":"为什么要打包呢","link":"#为什么要打包呢","children":[]},{"level":2,"title":"能说说模块化的好处吗?","slug":"能说说模块化的好处吗","link":"#能说说模块化的好处吗","children":[]},{"level":2,"title":"模块化打包方案知道啥,可以说说吗?","slug":"模块化打包方案知道啥-可以说说吗","link":"#模块化打包方案知道啥-可以说说吗","children":[]},{"level":2,"title":"那ES6 模块与 CommonJS 模块的差异有哪些呢?","slug":"那es6-模块与-commonjs-模块的差异有哪些呢","link":"#那es6-模块与-commonjs-模块的差异有哪些呢","children":[]},{"level":2,"title":"webpack的编译(打包)流程说说","slug":"webpack的编译-打包-流程说说","link":"#webpack的编译-打包-流程说说","children":[]},{"level":2,"title":"说一下 Webpack 的热更新原理吧?","slug":"说一下-webpack-的热更新原理吧","link":"#说一下-webpack-的热更新原理吧","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":[]},{"level":2,"title":"git","slug":"git","link":"#git","children":[{"level":3,"title":"打造前后端分离的开发环境,一般需要从哪几个方面进行设计","slug":"打造前后端分离的开发环境-一般需要从哪几个方面进行设计","link":"#打造前后端分离的开发环境-一般需要从哪几个方面进行设计","children":[]}]}],"relativePath":"front-end-engineering/【完结】前端工程化.md","lastUpdated":1710671080000}'),p={name:"front-end-engineering/【完结】前端工程化.md"},e=l(`

为什么会出现前端工程化

模块化:意味着咱们的 JS 总算可以开发大型项目(解决 JS 的复用问题)

组件化:解决 HTML、CSS、JS 的复用问题

工程化:因为咱们的前端项目越来越复杂、咱们的前端项目模块(组件)越来越多

前端项目越来越复杂?

在书写前端项目的时候,可能会涉及到使用其他的语言,typescript、less/sass、coffeescript,涉及到编译

在部署的时候,还需要对代码进行丑化、压缩

代码优化:为 CSS 代码添加兼容性前缀

前端项目模块(组件)越来越多?

专门有一个 node_modules 来管理这些可以服用的代码。

前端工程化的出现,归根结底,其实就是要解决开发环境和生产环境不一致的问题。

nodejs对CommonJS的实现(面试)

为了实现CommonJS规范,nodejs对模块做出了以下处理

  1. 为了保证高效的执行,仅加载必要的模块。nodejs只有执行到require函数时才会加载并执行模块

nodejs中导入模块,使用相对路径,并且必须以./或../开头,浏览器可以省略./,nodejs不行

  1. 为了隐藏模块中的代码,nodejs执行模块时,会将模块中的所有代码放置到一个函数中执行,以保证不污染全局变量。
javascript
 (function(){
      //模块中的代码
  })()
 
 (function(){
diff --git a/assets/getting-started.md.595f0ebe.js b/assets/getting-started.md.30398209.js
similarity index 99%
rename from assets/getting-started.md.595f0ebe.js
rename to assets/getting-started.md.30398209.js
index 0b389605..2b7805b4 100644
--- a/assets/getting-started.md.595f0ebe.js
+++ b/assets/getting-started.md.30398209.js
@@ -1 +1 @@
-import{_ as e,o as t,c as i,a as n}from"./app.679ab08c.js";const a="/blog/assets/js-c.40a46866.png",r="/blog/assets/mini-any.2d945c6b.png",y=JSON.parse('{"title":"My Projects","description":"","frontmatter":{},"headers":[{"level":3,"title":"js-challenges","slug":"js-challenges","link":"#js-challenges","children":[]},{"level":3,"title":"mini-anythings","slug":"mini-anythings","link":"#mini-anythings","children":[]},{"level":3,"title":"BOSScript","slug":"bosscript","link":"#bosscript","children":[]},{"level":3,"title":"rc-design","slug":"rc-design","link":"#rc-design","children":[]},{"level":3,"title":"cherry","slug":"cherry","link":"#cherry","children":[]},{"level":3,"title":"rollup-plugin-alias","slug":"rollup-plugin-alias","link":"#rollup-plugin-alias","children":[]},{"level":3,"title":"commencer","slug":"commencer","link":"#commencer","children":[]},{"level":3,"title":"tiny-react","slug":"tiny-react","link":"#tiny-react","children":[]},{"level":3,"title":"treejs","slug":"treejs","link":"#treejs","children":[]},{"level":3,"title":"lodash-ts","slug":"lodash-ts","link":"#lodash-ts","children":[]},{"level":3,"title":"tiny-vue","slug":"tiny-vue","link":"#tiny-vue","children":[]},{"level":3,"title":"Native-project","slug":"native-project","link":"#native-project","children":[]},{"level":3,"title":"shooks","slug":"shooks","link":"#shooks","children":[]},{"level":3,"title":"mini-webpack","slug":"mini-webpack","link":"#mini-webpack","children":[]},{"level":3,"title":"ts-lib-vite","slug":"ts-lib-vite","link":"#ts-lib-vite","children":[]},{"level":3,"title":"esbuild-plugins","slug":"esbuild-plugins","link":"#esbuild-plugins","children":[]},{"level":3,"title":"tiny-vite","slug":"tiny-vite","link":"#tiny-vite","children":[]},{"level":3,"title":"vite-plugins","slug":"vite-plugins","link":"#vite-plugins","children":[]},{"level":3,"title":"tiny-complier","slug":"tiny-complier","link":"#tiny-complier","children":[]},{"level":3,"title":"keep-everyday","slug":"keep-everyday","link":"#keep-everyday","children":[]},{"level":3,"title":"text-image","slug":"text-image","link":"#text-image","children":[]},{"level":3,"title":"jsx-compilation","slug":"jsx-compilation","link":"#jsx-compilation","children":[]},{"level":3,"title":"awesome-native","slug":"awesome-native","link":"#awesome-native","children":[]},{"level":3,"title":"vsc-delete-func","slug":"vsc-delete-func","link":"#vsc-delete-func","children":[]},{"level":3,"title":"eslint-plugin-reviewget","slug":"eslint-plugin-reviewget","link":"#eslint-plugin-reviewget","children":[]},{"level":3,"title":"babel-plugin-dev-debug","slug":"babel-plugin-dev-debug","link":"#babel-plugin-dev-debug","children":[]},{"level":3,"title":"webpack-expand-lib","slug":"webpack-expand-lib","link":"#webpack-expand-lib","children":[]},{"level":3,"title":"network-speed-js","slug":"network-speed-js","link":"#network-speed-js","children":[]},{"level":2,"title":"TODO ...","slug":"todo","link":"#todo","children":[]}],"relativePath":"getting-started.md","lastUpdated":1710671456000}'),l={name:"getting-started.md"},s=n('

My Projects

TODO:分类

js-challenges

https://github.com/Sunny-117/js-challenges

✨✨✨ Challenge your JavaScript programming limits step by step

mini-anythings

https://github.com/Sunny-117/mini-anything

🚀 Explore the source code of the front-end library and implement a super mini version

BOSScript

https://github.com/Sunny-117/BOSScript

Boss's direct recruitment and delivery, shutdown, one-stop service of the oil monkey script, allowing you to submit resumes overseas in just 2 minutes

rc-design

https://github.com/Sunny-117/rc-design

🗃️ rc-design is a component library developed for react, providing developers with a more lightweight and concise component library choice. Use tsx to write logic, less to write styles, dumi2 to write documentation sites, and jest+ts-jest+react-testing-library for unit testing.

cherry

https://github.com/Sunny-117/cherry

✨ A lightweight JavaScript packaging library based on magic-string and acorn, supporting tree-shaking

rollup-plugin-alias

https://github.com/Sunny-117/rollup-plugin-alias

🍣 A Rollup plugin for defining aliases when bundling packages.

commencer

https://github.com/Sunny-117/commencer

Starter template for xxx

tiny-react

https://github.com/Sunny-117/tiny-react

🌱 The closest implementation to the React source code

treejs

https://github.com/Sunny-117/treejs

🌱 Easy to learn, high-performance, and highly scalable Tree components, supporting Vuejs and React simultaneously

lodash-ts

https://github.com/Sunny-117/lodash-ts

tiny-vue

https://github.com/Sunny-117/tiny-vue

Native-project

https://github.com/Sunny-117/Native-project

Native JavaScript project collection, Github China latest version

shooks

https://github.com/Sunny-117/shooks

📦️ A high-quality & reliable React Hooks library.

mini-webpack

https://github.com/Sunny-117/mini-webpack

手写一个简易版的webpack

ts-lib-vite

https://github.com/Sunny-117/ts-lib-vite

A foundation for developing front-end utility libraries using Vite and TypeScript.

esbuild-plugins

https://github.com/Sunny-117/esbuild-plugins

packages of esbuild plugins

tiny-vite

https://github.com/Sunny-117/tiny-vite

⚡️ a lightweight frontend build tool designed to deliver swift development experiences and efficient build processes

vite-plugins

https://github.com/Sunny-117/vite-plugins

tiny-complier

实现超级 mini 的编译器 | codegen&compiler 生成代码 | 只需要 200 行代码 | 前端编译原理

https://github.com/Sunny-117/tiny-complier

keep-everyday

https://github.com/Sunny-117/keep-everyday

使用 Github Actions 来完成自动创建 issues 任务

text-image

https://github.com/Sunny-117/text-image

🐛🐛🐛 text-image 可以将文字、图片、视频进行「文本化」,只需要通过简单的配置即可使用

jsx-compilation

https://github.com/Sunny-117/jsx-compilation

🍻 实现 JSX 语法转成 JS 语法的编译器

awesome-native

https://github.com/Sunny-117/awesome-native

🔧 Collection of native JavaScript projects

vsc-delete-func

https://github.com/Sunny-117/vsc-delete-func

🍻🍻🍻 vscode plugins

eslint-plugin-reviewget

https://github.com/Sunny-117/eslint-plugin-reviewget

🚀当用户使用 getXXX get开头的函数的时候 如果不返回值的话 那么就会报错 🐛可以 fix 🎉用户可以自行配置是否 fix

babel-plugin-dev-debug

https://github.com/Sunny-117/babel-plugin-dev-debug

an babel plugin that for dev debug

webpack-expand-lib

https://github.com/Sunny-117/webpack-expand-lib

🚀 some expansion libs of webpack

network-speed-js

https://github.com/Sunny-117/network-speed-js

A small tool for testing network speed. It also has the ability to test internal and external networks.

TODO ...

',86),h=[s];function p(o,c,d,u,g,b){return t(),i("div",null,h)}const v=e(l,[["render",p]]);export{y as __pageData,v as default}; +import{_ as e,o as t,c as i,a as n}from"./app.815d1813.js";const a="/blog/assets/js-c.40a46866.png",r="/blog/assets/mini-any.2d945c6b.png",y=JSON.parse('{"title":"My Projects","description":"","frontmatter":{},"headers":[{"level":3,"title":"js-challenges","slug":"js-challenges","link":"#js-challenges","children":[]},{"level":3,"title":"mini-anythings","slug":"mini-anythings","link":"#mini-anythings","children":[]},{"level":3,"title":"BOSScript","slug":"bosscript","link":"#bosscript","children":[]},{"level":3,"title":"rc-design","slug":"rc-design","link":"#rc-design","children":[]},{"level":3,"title":"cherry","slug":"cherry","link":"#cherry","children":[]},{"level":3,"title":"rollup-plugin-alias","slug":"rollup-plugin-alias","link":"#rollup-plugin-alias","children":[]},{"level":3,"title":"commencer","slug":"commencer","link":"#commencer","children":[]},{"level":3,"title":"tiny-react","slug":"tiny-react","link":"#tiny-react","children":[]},{"level":3,"title":"treejs","slug":"treejs","link":"#treejs","children":[]},{"level":3,"title":"lodash-ts","slug":"lodash-ts","link":"#lodash-ts","children":[]},{"level":3,"title":"tiny-vue","slug":"tiny-vue","link":"#tiny-vue","children":[]},{"level":3,"title":"Native-project","slug":"native-project","link":"#native-project","children":[]},{"level":3,"title":"shooks","slug":"shooks","link":"#shooks","children":[]},{"level":3,"title":"mini-webpack","slug":"mini-webpack","link":"#mini-webpack","children":[]},{"level":3,"title":"ts-lib-vite","slug":"ts-lib-vite","link":"#ts-lib-vite","children":[]},{"level":3,"title":"esbuild-plugins","slug":"esbuild-plugins","link":"#esbuild-plugins","children":[]},{"level":3,"title":"tiny-vite","slug":"tiny-vite","link":"#tiny-vite","children":[]},{"level":3,"title":"vite-plugins","slug":"vite-plugins","link":"#vite-plugins","children":[]},{"level":3,"title":"tiny-complier","slug":"tiny-complier","link":"#tiny-complier","children":[]},{"level":3,"title":"keep-everyday","slug":"keep-everyday","link":"#keep-everyday","children":[]},{"level":3,"title":"text-image","slug":"text-image","link":"#text-image","children":[]},{"level":3,"title":"jsx-compilation","slug":"jsx-compilation","link":"#jsx-compilation","children":[]},{"level":3,"title":"awesome-native","slug":"awesome-native","link":"#awesome-native","children":[]},{"level":3,"title":"vsc-delete-func","slug":"vsc-delete-func","link":"#vsc-delete-func","children":[]},{"level":3,"title":"eslint-plugin-reviewget","slug":"eslint-plugin-reviewget","link":"#eslint-plugin-reviewget","children":[]},{"level":3,"title":"babel-plugin-dev-debug","slug":"babel-plugin-dev-debug","link":"#babel-plugin-dev-debug","children":[]},{"level":3,"title":"webpack-expand-lib","slug":"webpack-expand-lib","link":"#webpack-expand-lib","children":[]},{"level":3,"title":"network-speed-js","slug":"network-speed-js","link":"#network-speed-js","children":[]},{"level":2,"title":"TODO ...","slug":"todo","link":"#todo","children":[]}],"relativePath":"getting-started.md","lastUpdated":1710671456000}'),l={name:"getting-started.md"},s=n('

My Projects

TODO:分类

js-challenges

https://github.com/Sunny-117/js-challenges

✨✨✨ Challenge your JavaScript programming limits step by step

mini-anythings

https://github.com/Sunny-117/mini-anything

🚀 Explore the source code of the front-end library and implement a super mini version

BOSScript

https://github.com/Sunny-117/BOSScript

Boss's direct recruitment and delivery, shutdown, one-stop service of the oil monkey script, allowing you to submit resumes overseas in just 2 minutes

rc-design

https://github.com/Sunny-117/rc-design

🗃️ rc-design is a component library developed for react, providing developers with a more lightweight and concise component library choice. Use tsx to write logic, less to write styles, dumi2 to write documentation sites, and jest+ts-jest+react-testing-library for unit testing.

cherry

https://github.com/Sunny-117/cherry

✨ A lightweight JavaScript packaging library based on magic-string and acorn, supporting tree-shaking

rollup-plugin-alias

https://github.com/Sunny-117/rollup-plugin-alias

🍣 A Rollup plugin for defining aliases when bundling packages.

commencer

https://github.com/Sunny-117/commencer

Starter template for xxx

tiny-react

https://github.com/Sunny-117/tiny-react

🌱 The closest implementation to the React source code

treejs

https://github.com/Sunny-117/treejs

🌱 Easy to learn, high-performance, and highly scalable Tree components, supporting Vuejs and React simultaneously

lodash-ts

https://github.com/Sunny-117/lodash-ts

tiny-vue

https://github.com/Sunny-117/tiny-vue

Native-project

https://github.com/Sunny-117/Native-project

Native JavaScript project collection, Github China latest version

shooks

https://github.com/Sunny-117/shooks

📦️ A high-quality & reliable React Hooks library.

mini-webpack

https://github.com/Sunny-117/mini-webpack

手写一个简易版的webpack

ts-lib-vite

https://github.com/Sunny-117/ts-lib-vite

A foundation for developing front-end utility libraries using Vite and TypeScript.

esbuild-plugins

https://github.com/Sunny-117/esbuild-plugins

packages of esbuild plugins

tiny-vite

https://github.com/Sunny-117/tiny-vite

⚡️ a lightweight frontend build tool designed to deliver swift development experiences and efficient build processes

vite-plugins

https://github.com/Sunny-117/vite-plugins

tiny-complier

实现超级 mini 的编译器 | codegen&compiler 生成代码 | 只需要 200 行代码 | 前端编译原理

https://github.com/Sunny-117/tiny-complier

keep-everyday

https://github.com/Sunny-117/keep-everyday

使用 Github Actions 来完成自动创建 issues 任务

text-image

https://github.com/Sunny-117/text-image

🐛🐛🐛 text-image 可以将文字、图片、视频进行「文本化」,只需要通过简单的配置即可使用

jsx-compilation

https://github.com/Sunny-117/jsx-compilation

🍻 实现 JSX 语法转成 JS 语法的编译器

awesome-native

https://github.com/Sunny-117/awesome-native

🔧 Collection of native JavaScript projects

vsc-delete-func

https://github.com/Sunny-117/vsc-delete-func

🍻🍻🍻 vscode plugins

eslint-plugin-reviewget

https://github.com/Sunny-117/eslint-plugin-reviewget

🚀当用户使用 getXXX get开头的函数的时候 如果不返回值的话 那么就会报错 🐛可以 fix 🎉用户可以自行配置是否 fix

babel-plugin-dev-debug

https://github.com/Sunny-117/babel-plugin-dev-debug

an babel plugin that for dev debug

webpack-expand-lib

https://github.com/Sunny-117/webpack-expand-lib

🚀 some expansion libs of webpack

network-speed-js

https://github.com/Sunny-117/network-speed-js

A small tool for testing network speed. It also has the ability to test internal and external networks.

TODO ...

',86),h=[s];function p(o,c,d,u,g,b){return t(),i("div",null,h)}const v=e(l,[["render",p]]);export{y as __pageData,v as default}; diff --git a/assets/getting-started.md.595f0ebe.lean.js b/assets/getting-started.md.30398209.lean.js similarity index 98% rename from assets/getting-started.md.595f0ebe.lean.js rename to assets/getting-started.md.30398209.lean.js index 534801e7..3a8c89a1 100644 --- a/assets/getting-started.md.595f0ebe.lean.js +++ b/assets/getting-started.md.30398209.lean.js @@ -1 +1 @@ -import{_ as e,o as t,c as i,a as n}from"./app.679ab08c.js";const a="/blog/assets/js-c.40a46866.png",r="/blog/assets/mini-any.2d945c6b.png",y=JSON.parse('{"title":"My Projects","description":"","frontmatter":{},"headers":[{"level":3,"title":"js-challenges","slug":"js-challenges","link":"#js-challenges","children":[]},{"level":3,"title":"mini-anythings","slug":"mini-anythings","link":"#mini-anythings","children":[]},{"level":3,"title":"BOSScript","slug":"bosscript","link":"#bosscript","children":[]},{"level":3,"title":"rc-design","slug":"rc-design","link":"#rc-design","children":[]},{"level":3,"title":"cherry","slug":"cherry","link":"#cherry","children":[]},{"level":3,"title":"rollup-plugin-alias","slug":"rollup-plugin-alias","link":"#rollup-plugin-alias","children":[]},{"level":3,"title":"commencer","slug":"commencer","link":"#commencer","children":[]},{"level":3,"title":"tiny-react","slug":"tiny-react","link":"#tiny-react","children":[]},{"level":3,"title":"treejs","slug":"treejs","link":"#treejs","children":[]},{"level":3,"title":"lodash-ts","slug":"lodash-ts","link":"#lodash-ts","children":[]},{"level":3,"title":"tiny-vue","slug":"tiny-vue","link":"#tiny-vue","children":[]},{"level":3,"title":"Native-project","slug":"native-project","link":"#native-project","children":[]},{"level":3,"title":"shooks","slug":"shooks","link":"#shooks","children":[]},{"level":3,"title":"mini-webpack","slug":"mini-webpack","link":"#mini-webpack","children":[]},{"level":3,"title":"ts-lib-vite","slug":"ts-lib-vite","link":"#ts-lib-vite","children":[]},{"level":3,"title":"esbuild-plugins","slug":"esbuild-plugins","link":"#esbuild-plugins","children":[]},{"level":3,"title":"tiny-vite","slug":"tiny-vite","link":"#tiny-vite","children":[]},{"level":3,"title":"vite-plugins","slug":"vite-plugins","link":"#vite-plugins","children":[]},{"level":3,"title":"tiny-complier","slug":"tiny-complier","link":"#tiny-complier","children":[]},{"level":3,"title":"keep-everyday","slug":"keep-everyday","link":"#keep-everyday","children":[]},{"level":3,"title":"text-image","slug":"text-image","link":"#text-image","children":[]},{"level":3,"title":"jsx-compilation","slug":"jsx-compilation","link":"#jsx-compilation","children":[]},{"level":3,"title":"awesome-native","slug":"awesome-native","link":"#awesome-native","children":[]},{"level":3,"title":"vsc-delete-func","slug":"vsc-delete-func","link":"#vsc-delete-func","children":[]},{"level":3,"title":"eslint-plugin-reviewget","slug":"eslint-plugin-reviewget","link":"#eslint-plugin-reviewget","children":[]},{"level":3,"title":"babel-plugin-dev-debug","slug":"babel-plugin-dev-debug","link":"#babel-plugin-dev-debug","children":[]},{"level":3,"title":"webpack-expand-lib","slug":"webpack-expand-lib","link":"#webpack-expand-lib","children":[]},{"level":3,"title":"network-speed-js","slug":"network-speed-js","link":"#network-speed-js","children":[]},{"level":2,"title":"TODO ...","slug":"todo","link":"#todo","children":[]}],"relativePath":"getting-started.md","lastUpdated":1710671456000}'),l={name:"getting-started.md"},s=n("",86),h=[s];function p(o,c,d,u,g,b){return t(),i("div",null,h)}const v=e(l,[["render",p]]);export{y as __pageData,v as default}; +import{_ as e,o as t,c as i,a as n}from"./app.815d1813.js";const a="/blog/assets/js-c.40a46866.png",r="/blog/assets/mini-any.2d945c6b.png",y=JSON.parse('{"title":"My Projects","description":"","frontmatter":{},"headers":[{"level":3,"title":"js-challenges","slug":"js-challenges","link":"#js-challenges","children":[]},{"level":3,"title":"mini-anythings","slug":"mini-anythings","link":"#mini-anythings","children":[]},{"level":3,"title":"BOSScript","slug":"bosscript","link":"#bosscript","children":[]},{"level":3,"title":"rc-design","slug":"rc-design","link":"#rc-design","children":[]},{"level":3,"title":"cherry","slug":"cherry","link":"#cherry","children":[]},{"level":3,"title":"rollup-plugin-alias","slug":"rollup-plugin-alias","link":"#rollup-plugin-alias","children":[]},{"level":3,"title":"commencer","slug":"commencer","link":"#commencer","children":[]},{"level":3,"title":"tiny-react","slug":"tiny-react","link":"#tiny-react","children":[]},{"level":3,"title":"treejs","slug":"treejs","link":"#treejs","children":[]},{"level":3,"title":"lodash-ts","slug":"lodash-ts","link":"#lodash-ts","children":[]},{"level":3,"title":"tiny-vue","slug":"tiny-vue","link":"#tiny-vue","children":[]},{"level":3,"title":"Native-project","slug":"native-project","link":"#native-project","children":[]},{"level":3,"title":"shooks","slug":"shooks","link":"#shooks","children":[]},{"level":3,"title":"mini-webpack","slug":"mini-webpack","link":"#mini-webpack","children":[]},{"level":3,"title":"ts-lib-vite","slug":"ts-lib-vite","link":"#ts-lib-vite","children":[]},{"level":3,"title":"esbuild-plugins","slug":"esbuild-plugins","link":"#esbuild-plugins","children":[]},{"level":3,"title":"tiny-vite","slug":"tiny-vite","link":"#tiny-vite","children":[]},{"level":3,"title":"vite-plugins","slug":"vite-plugins","link":"#vite-plugins","children":[]},{"level":3,"title":"tiny-complier","slug":"tiny-complier","link":"#tiny-complier","children":[]},{"level":3,"title":"keep-everyday","slug":"keep-everyday","link":"#keep-everyday","children":[]},{"level":3,"title":"text-image","slug":"text-image","link":"#text-image","children":[]},{"level":3,"title":"jsx-compilation","slug":"jsx-compilation","link":"#jsx-compilation","children":[]},{"level":3,"title":"awesome-native","slug":"awesome-native","link":"#awesome-native","children":[]},{"level":3,"title":"vsc-delete-func","slug":"vsc-delete-func","link":"#vsc-delete-func","children":[]},{"level":3,"title":"eslint-plugin-reviewget","slug":"eslint-plugin-reviewget","link":"#eslint-plugin-reviewget","children":[]},{"level":3,"title":"babel-plugin-dev-debug","slug":"babel-plugin-dev-debug","link":"#babel-plugin-dev-debug","children":[]},{"level":3,"title":"webpack-expand-lib","slug":"webpack-expand-lib","link":"#webpack-expand-lib","children":[]},{"level":3,"title":"network-speed-js","slug":"network-speed-js","link":"#network-speed-js","children":[]},{"level":2,"title":"TODO ...","slug":"todo","link":"#todo","children":[]}],"relativePath":"getting-started.md","lastUpdated":1710671456000}'),l={name:"getting-started.md"},s=n("",86),h=[s];function p(o,c,d,u,g,b){return t(),i("div",null,h)}const v=e(l,[["render",p]]);export{y as __pageData,v as default}; diff --git a/assets/html-css_CSS.md.ddd23058.js b/assets/html-css_CSS.md.b086bf50.js similarity index 99% rename from assets/html-css_CSS.md.ddd23058.js rename to assets/html-css_CSS.md.b086bf50.js index fb8a166f..0256efec 100644 --- a/assets/html-css_CSS.md.ddd23058.js +++ b/assets/html-css_CSS.md.b086bf50.js @@ -1,4 +1,4 @@ -import{_ as s,o as n,c as a,a as l}from"./app.679ab08c.js";const p="/blog/assets/2023-02-01-21-02-59.47222bd8.png",o="/blog/assets/2023-02-01-20-57-45.daee2fcd.png",u=JSON.parse('{"title":"CSS3","description":"","frontmatter":{},"headers":[{"level":2,"title":"introduction","slug":"introduction","link":"#introduction","children":[{"level":3,"title":"1.历史","slug":"_1-历史","link":"#_1-历史","children":[]},{"level":3,"title":"2.处理器","slug":"_2-处理器","link":"#_2-处理器","children":[]},{"level":3,"title":"3.怎么用","slug":"_3-怎么用","link":"#_3-怎么用","children":[]},{"level":3,"title":"4.CSS3 进化到编程化:cssNext","slug":"_4-css3-进化到编程化-cssnext","link":"#_4-css3-进化到编程化-cssnext","children":[]}]},{"level":2,"title":"border","slug":"border","link":"#border","children":[{"level":3,"title":"1.border","slug":"_1-border","link":"#_1-border","children":[]},{"level":3,"title":"2.box-shallow","slug":"_2-box-shallow","link":"#_2-box-shallow","children":[]},{"level":3,"title":"3.border-image","slug":"_3-border-image","link":"#_3-border-image","children":[]}]},{"level":2,"title":"background","slug":"background","link":"#background","children":[{"level":3,"title":"1.background-origin:","slug":"_1-background-origin","link":"#_1-background-origin","children":[]},{"level":3,"title":"2.background-clip","slug":"_2-background-clip","link":"#_2-background-clip","children":[]},{"level":3,"title":"3.background-repeat","slug":"_3-background-repeat","link":"#_3-background-repeat","children":[]},{"level":3,"title":"4.background-attachment","slug":"_4-background-attachment","link":"#_4-background-attachment","children":[]},{"level":3,"title":"5.background-size","slug":"_5-background-size","link":"#_5-background-size","children":[]}]},{"level":2,"title":"text","slug":"text","link":"#text","children":[{"level":3,"title":"1.text-shadow","slug":"_1-text-shadow","link":"#_1-text-shadow","children":[]},{"level":3,"title":"2.   text 系列","slug":"_2-text-系列","link":"#_2-text-系列","children":[]}]},{"level":2,"title":"box","slug":"box","link":"#box","children":[{"level":3,"title":"1.IE6 混杂模式盒子","slug":"_1-ie6-混杂模式盒子","link":"#_1-ie6-混杂模式盒子","children":[]},{"level":3,"title":"2.flex 弹性盒子","slug":"_2-flex-弹性盒子","link":"#_2-flex-弹性盒子","children":[]}]},{"level":2,"title":"响应式网站开发","slug":"响应式网站开发","link":"#响应式网站开发","children":[{"level":3,"title":"2.响应式网页设计","slug":"_2-响应式网页设计","link":"#_2-响应式网页设计","children":[]},{"level":3,"title":"3.设置视口","slug":"_3-设置视口","link":"#_3-设置视口","children":[]},{"level":3,"title":"4.响应式网页开发方法","slug":"_4-响应式网页开发方法","link":"#_4-响应式网页开发方法","children":[]},{"level":3,"title":"5.媒体查询","slug":"_5-媒体查询","link":"#_5-媒体查询","children":[]},{"level":3,"title":"6.媒体类型","slug":"_6-媒体类型","link":"#_6-媒体类型","children":[]},{"level":3,"title":"7.单位值","slug":"_7-单位值","link":"#_7-单位值","children":[]}]}],"relativePath":"html-css/CSS.md","lastUpdated":1675259332000}'),e={name:"html-css/CSS.md"},c=l(`

CSS3

introduction

兼容性前缀

prefix(前缀)browser
-webkitchrome/safari
-mozfirefox
-msIE
-oopera

1.历史

更新迭代,兼容性 ---- 加不加前缀

css
div {
+import{_ as s,o as n,c as a,a as l}from"./app.815d1813.js";const p="/blog/assets/2023-02-01-21-02-59.47222bd8.png",o="/blog/assets/2023-02-01-20-57-45.daee2fcd.png",u=JSON.parse('{"title":"CSS3","description":"","frontmatter":{},"headers":[{"level":2,"title":"introduction","slug":"introduction","link":"#introduction","children":[{"level":3,"title":"1.历史","slug":"_1-历史","link":"#_1-历史","children":[]},{"level":3,"title":"2.处理器","slug":"_2-处理器","link":"#_2-处理器","children":[]},{"level":3,"title":"3.怎么用","slug":"_3-怎么用","link":"#_3-怎么用","children":[]},{"level":3,"title":"4.CSS3 进化到编程化:cssNext","slug":"_4-css3-进化到编程化-cssnext","link":"#_4-css3-进化到编程化-cssnext","children":[]}]},{"level":2,"title":"border","slug":"border","link":"#border","children":[{"level":3,"title":"1.border","slug":"_1-border","link":"#_1-border","children":[]},{"level":3,"title":"2.box-shallow","slug":"_2-box-shallow","link":"#_2-box-shallow","children":[]},{"level":3,"title":"3.border-image","slug":"_3-border-image","link":"#_3-border-image","children":[]}]},{"level":2,"title":"background","slug":"background","link":"#background","children":[{"level":3,"title":"1.background-origin:","slug":"_1-background-origin","link":"#_1-background-origin","children":[]},{"level":3,"title":"2.background-clip","slug":"_2-background-clip","link":"#_2-background-clip","children":[]},{"level":3,"title":"3.background-repeat","slug":"_3-background-repeat","link":"#_3-background-repeat","children":[]},{"level":3,"title":"4.background-attachment","slug":"_4-background-attachment","link":"#_4-background-attachment","children":[]},{"level":3,"title":"5.background-size","slug":"_5-background-size","link":"#_5-background-size","children":[]}]},{"level":2,"title":"text","slug":"text","link":"#text","children":[{"level":3,"title":"1.text-shadow","slug":"_1-text-shadow","link":"#_1-text-shadow","children":[]},{"level":3,"title":"2.   text 系列","slug":"_2-text-系列","link":"#_2-text-系列","children":[]}]},{"level":2,"title":"box","slug":"box","link":"#box","children":[{"level":3,"title":"1.IE6 混杂模式盒子","slug":"_1-ie6-混杂模式盒子","link":"#_1-ie6-混杂模式盒子","children":[]},{"level":3,"title":"2.flex 弹性盒子","slug":"_2-flex-弹性盒子","link":"#_2-flex-弹性盒子","children":[]}]},{"level":2,"title":"响应式网站开发","slug":"响应式网站开发","link":"#响应式网站开发","children":[{"level":3,"title":"2.响应式网页设计","slug":"_2-响应式网页设计","link":"#_2-响应式网页设计","children":[]},{"level":3,"title":"3.设置视口","slug":"_3-设置视口","link":"#_3-设置视口","children":[]},{"level":3,"title":"4.响应式网页开发方法","slug":"_4-响应式网页开发方法","link":"#_4-响应式网页开发方法","children":[]},{"level":3,"title":"5.媒体查询","slug":"_5-媒体查询","link":"#_5-媒体查询","children":[]},{"level":3,"title":"6.媒体类型","slug":"_6-媒体类型","link":"#_6-媒体类型","children":[]},{"level":3,"title":"7.单位值","slug":"_7-单位值","link":"#_7-单位值","children":[]}]}],"relativePath":"html-css/CSS.md","lastUpdated":1675259332000}'),e={name:"html-css/CSS.md"},c=l(`

CSS3

introduction

兼容性前缀

prefix(前缀)browser
-webkitchrome/safari
-mozfirefox
-msIE
-oopera

1.历史

更新迭代,兼容性 ---- 加不加前缀

css
div {
   border-radius: ;
   -webkit-border-radius: ;
   -o-border-radius: ;
diff --git a/assets/html-css_CSS.md.ddd23058.lean.js b/assets/html-css_CSS.md.b086bf50.lean.js
similarity index 98%
rename from assets/html-css_CSS.md.ddd23058.lean.js
rename to assets/html-css_CSS.md.b086bf50.lean.js
index 03568a12..02fa170e 100644
--- a/assets/html-css_CSS.md.ddd23058.lean.js
+++ b/assets/html-css_CSS.md.b086bf50.lean.js
@@ -1 +1 @@
-import{_ as s,o as n,c as a,a as l}from"./app.679ab08c.js";const p="/blog/assets/2023-02-01-21-02-59.47222bd8.png",o="/blog/assets/2023-02-01-20-57-45.daee2fcd.png",u=JSON.parse('{"title":"CSS3","description":"","frontmatter":{},"headers":[{"level":2,"title":"introduction","slug":"introduction","link":"#introduction","children":[{"level":3,"title":"1.历史","slug":"_1-历史","link":"#_1-历史","children":[]},{"level":3,"title":"2.处理器","slug":"_2-处理器","link":"#_2-处理器","children":[]},{"level":3,"title":"3.怎么用","slug":"_3-怎么用","link":"#_3-怎么用","children":[]},{"level":3,"title":"4.CSS3 进化到编程化:cssNext","slug":"_4-css3-进化到编程化-cssnext","link":"#_4-css3-进化到编程化-cssnext","children":[]}]},{"level":2,"title":"border","slug":"border","link":"#border","children":[{"level":3,"title":"1.border","slug":"_1-border","link":"#_1-border","children":[]},{"level":3,"title":"2.box-shallow","slug":"_2-box-shallow","link":"#_2-box-shallow","children":[]},{"level":3,"title":"3.border-image","slug":"_3-border-image","link":"#_3-border-image","children":[]}]},{"level":2,"title":"background","slug":"background","link":"#background","children":[{"level":3,"title":"1.background-origin:","slug":"_1-background-origin","link":"#_1-background-origin","children":[]},{"level":3,"title":"2.background-clip","slug":"_2-background-clip","link":"#_2-background-clip","children":[]},{"level":3,"title":"3.background-repeat","slug":"_3-background-repeat","link":"#_3-background-repeat","children":[]},{"level":3,"title":"4.background-attachment","slug":"_4-background-attachment","link":"#_4-background-attachment","children":[]},{"level":3,"title":"5.background-size","slug":"_5-background-size","link":"#_5-background-size","children":[]}]},{"level":2,"title":"text","slug":"text","link":"#text","children":[{"level":3,"title":"1.text-shadow","slug":"_1-text-shadow","link":"#_1-text-shadow","children":[]},{"level":3,"title":"2.   text 系列","slug":"_2-text-系列","link":"#_2-text-系列","children":[]}]},{"level":2,"title":"box","slug":"box","link":"#box","children":[{"level":3,"title":"1.IE6 混杂模式盒子","slug":"_1-ie6-混杂模式盒子","link":"#_1-ie6-混杂模式盒子","children":[]},{"level":3,"title":"2.flex 弹性盒子","slug":"_2-flex-弹性盒子","link":"#_2-flex-弹性盒子","children":[]}]},{"level":2,"title":"响应式网站开发","slug":"响应式网站开发","link":"#响应式网站开发","children":[{"level":3,"title":"2.响应式网页设计","slug":"_2-响应式网页设计","link":"#_2-响应式网页设计","children":[]},{"level":3,"title":"3.设置视口","slug":"_3-设置视口","link":"#_3-设置视口","children":[]},{"level":3,"title":"4.响应式网页开发方法","slug":"_4-响应式网页开发方法","link":"#_4-响应式网页开发方法","children":[]},{"level":3,"title":"5.媒体查询","slug":"_5-媒体查询","link":"#_5-媒体查询","children":[]},{"level":3,"title":"6.媒体类型","slug":"_6-媒体类型","link":"#_6-媒体类型","children":[]},{"level":3,"title":"7.单位值","slug":"_7-单位值","link":"#_7-单位值","children":[]}]}],"relativePath":"html-css/CSS.md","lastUpdated":1675259332000}'),e={name:"html-css/CSS.md"},c=l("",278),r=[c];function t(B,y,F,i,A,b){return n(),a("div",null,r)}const m=s(e,[["render",t]]);export{u as __pageData,m as default};
+import{_ as s,o as n,c as a,a as l}from"./app.815d1813.js";const p="/blog/assets/2023-02-01-21-02-59.47222bd8.png",o="/blog/assets/2023-02-01-20-57-45.daee2fcd.png",u=JSON.parse('{"title":"CSS3","description":"","frontmatter":{},"headers":[{"level":2,"title":"introduction","slug":"introduction","link":"#introduction","children":[{"level":3,"title":"1.历史","slug":"_1-历史","link":"#_1-历史","children":[]},{"level":3,"title":"2.处理器","slug":"_2-处理器","link":"#_2-处理器","children":[]},{"level":3,"title":"3.怎么用","slug":"_3-怎么用","link":"#_3-怎么用","children":[]},{"level":3,"title":"4.CSS3 进化到编程化:cssNext","slug":"_4-css3-进化到编程化-cssnext","link":"#_4-css3-进化到编程化-cssnext","children":[]}]},{"level":2,"title":"border","slug":"border","link":"#border","children":[{"level":3,"title":"1.border","slug":"_1-border","link":"#_1-border","children":[]},{"level":3,"title":"2.box-shallow","slug":"_2-box-shallow","link":"#_2-box-shallow","children":[]},{"level":3,"title":"3.border-image","slug":"_3-border-image","link":"#_3-border-image","children":[]}]},{"level":2,"title":"background","slug":"background","link":"#background","children":[{"level":3,"title":"1.background-origin:","slug":"_1-background-origin","link":"#_1-background-origin","children":[]},{"level":3,"title":"2.background-clip","slug":"_2-background-clip","link":"#_2-background-clip","children":[]},{"level":3,"title":"3.background-repeat","slug":"_3-background-repeat","link":"#_3-background-repeat","children":[]},{"level":3,"title":"4.background-attachment","slug":"_4-background-attachment","link":"#_4-background-attachment","children":[]},{"level":3,"title":"5.background-size","slug":"_5-background-size","link":"#_5-background-size","children":[]}]},{"level":2,"title":"text","slug":"text","link":"#text","children":[{"level":3,"title":"1.text-shadow","slug":"_1-text-shadow","link":"#_1-text-shadow","children":[]},{"level":3,"title":"2.   text 系列","slug":"_2-text-系列","link":"#_2-text-系列","children":[]}]},{"level":2,"title":"box","slug":"box","link":"#box","children":[{"level":3,"title":"1.IE6 混杂模式盒子","slug":"_1-ie6-混杂模式盒子","link":"#_1-ie6-混杂模式盒子","children":[]},{"level":3,"title":"2.flex 弹性盒子","slug":"_2-flex-弹性盒子","link":"#_2-flex-弹性盒子","children":[]}]},{"level":2,"title":"响应式网站开发","slug":"响应式网站开发","link":"#响应式网站开发","children":[{"level":3,"title":"2.响应式网页设计","slug":"_2-响应式网页设计","link":"#_2-响应式网页设计","children":[]},{"level":3,"title":"3.设置视口","slug":"_3-设置视口","link":"#_3-设置视口","children":[]},{"level":3,"title":"4.响应式网页开发方法","slug":"_4-响应式网页开发方法","link":"#_4-响应式网页开发方法","children":[]},{"level":3,"title":"5.媒体查询","slug":"_5-媒体查询","link":"#_5-媒体查询","children":[]},{"level":3,"title":"6.媒体类型","slug":"_6-媒体类型","link":"#_6-媒体类型","children":[]},{"level":3,"title":"7.单位值","slug":"_7-单位值","link":"#_7-单位值","children":[]}]}],"relativePath":"html-css/CSS.md","lastUpdated":1675259332000}'),e={name:"html-css/CSS.md"},c=l("",278),r=[c];function t(B,y,F,i,A,b){return n(),a("div",null,r)}const m=s(e,[["render",t]]);export{u as __pageData,m as default};
diff --git a/assets/html-css_HTML.md.d9aa3d72.js b/assets/html-css_HTML.md.a59c2cd4.js
similarity index 99%
rename from assets/html-css_HTML.md.d9aa3d72.js
rename to assets/html-css_HTML.md.a59c2cd4.js
index 981c8bef..a9682bf9 100644
--- a/assets/html-css_HTML.md.d9aa3d72.js
+++ b/assets/html-css_HTML.md.a59c2cd4.js
@@ -1,4 +1,4 @@
-import{_ as s,o as n,c as a,a as l}from"./app.679ab08c.js";const u=JSON.parse('{"title":"HTML5","description":"","frontmatter":{},"headers":[{"level":2,"title":"大纲","slug":"大纲","link":"#大纲","children":[{"level":3,"title":"新增的属性","slug":"新增的属性","link":"#新增的属性","children":[]},{"level":3,"title":"新增的标签","slug":"新增的标签","link":"#新增的标签","children":[]},{"level":3,"title":"API","slug":"api","link":"#api","children":[]}]},{"level":2,"title":"属性篇_input 新增 type","slug":"属性篇-input-新增-type","link":"#属性篇-input-新增-type","children":[{"level":3,"title":"1.placeholder","slug":"_1-placeholder","link":"#_1-placeholder","children":[]},{"level":3,"title":"2.input 新增 type","slug":"_2-input-新增-type","link":"#_2-input-新增-type","children":[]}]},{"level":2,"title":"ContentEditable","slug":"contenteditable","link":"#contenteditable","children":[]},{"level":2,"title":"标签篇_语义化标签","slug":"标签篇-语义化标签","link":"#标签篇-语义化标签","children":[]},{"level":2,"title":"audio 与 video 播放器","slug":"audio-与-video-播放器","link":"#audio-与-video-播放器","children":[]},{"level":2,"title":"视频播放器","slug":"视频播放器","link":"#视频播放器","children":[]},{"level":2,"title":"geolocation","slug":"geolocation","link":"#geolocation","children":[]},{"level":2,"title":"四行写个服务器","slug":"四行写个服务器","link":"#四行写个服务器","children":[]},{"level":2,"title":"deviceorientation","slug":"deviceorientation","link":"#deviceorientation","children":[]},{"level":2,"title":"手机访问电脑","slug":"手机访问电脑","link":"#手机访问电脑","children":[]},{"level":2,"title":"devicemotion","slug":"devicemotion","link":"#devicemotion","children":[]},{"level":2,"title":"requestAnimationFrame","slug":"requestanimationframe","link":"#requestanimationframe","children":[]},{"level":2,"title":"localStrorage","slug":"localstrorage","link":"#localstrorage","children":[]},{"level":2,"title":"history","slug":"history","link":"#history","children":[]},{"level":2,"title":"worker","slug":"worker","link":"#worker","children":[]}],"relativePath":"html-css/HTML.md","lastUpdated":1675256330000}'),p={name:"html-css/HTML.md"},o=l(`

HTML5

大纲

新增的属性

  • placeholder
  • Calendar, date, time, email, url, search
  • ContentEditable
  • Draggable
  • Hidden
  • Content-menu
  • Data-Val(自定义属性)

新增的标签

  • 语义化标签
  • canvas
  • svg
  • Audio(声音播放)
  • Video(视频播放)

API

  • 移动端网页开发一般指的是 h5
  • 定位(需要地理位置的功能)
  • 重力感应(手机里面的陀螺仪(微信摇一摇,赛车转弯))
  • request-animation-frame(动画优化)
  • History 历史界面(控制当前页面的历史记录)
  • LocalStorage(本地存储,电脑/浏览器关闭都会保留);SessionStorage,(会话存储:窗口关闭就消失)。 都是存储信息(比如历史最高记录)
  • WebSocket(在线聊天,聊天室)
  • FileReader(文件读取,预览图)
  • WebWoker(文件的异步,提升性能,提升交互体验)
  • Fetch(传说中要替代 AJAX 的东西)

属性篇_input 新增 type

1.placeholder

html
<input type="text" placeholder="用户名/手机/邮箱" />
+import{_ as s,o as n,c as a,a as l}from"./app.815d1813.js";const u=JSON.parse('{"title":"HTML5","description":"","frontmatter":{},"headers":[{"level":2,"title":"大纲","slug":"大纲","link":"#大纲","children":[{"level":3,"title":"新增的属性","slug":"新增的属性","link":"#新增的属性","children":[]},{"level":3,"title":"新增的标签","slug":"新增的标签","link":"#新增的标签","children":[]},{"level":3,"title":"API","slug":"api","link":"#api","children":[]}]},{"level":2,"title":"属性篇_input 新增 type","slug":"属性篇-input-新增-type","link":"#属性篇-input-新增-type","children":[{"level":3,"title":"1.placeholder","slug":"_1-placeholder","link":"#_1-placeholder","children":[]},{"level":3,"title":"2.input 新增 type","slug":"_2-input-新增-type","link":"#_2-input-新增-type","children":[]}]},{"level":2,"title":"ContentEditable","slug":"contenteditable","link":"#contenteditable","children":[]},{"level":2,"title":"标签篇_语义化标签","slug":"标签篇-语义化标签","link":"#标签篇-语义化标签","children":[]},{"level":2,"title":"audio 与 video 播放器","slug":"audio-与-video-播放器","link":"#audio-与-video-播放器","children":[]},{"level":2,"title":"视频播放器","slug":"视频播放器","link":"#视频播放器","children":[]},{"level":2,"title":"geolocation","slug":"geolocation","link":"#geolocation","children":[]},{"level":2,"title":"四行写个服务器","slug":"四行写个服务器","link":"#四行写个服务器","children":[]},{"level":2,"title":"deviceorientation","slug":"deviceorientation","link":"#deviceorientation","children":[]},{"level":2,"title":"手机访问电脑","slug":"手机访问电脑","link":"#手机访问电脑","children":[]},{"level":2,"title":"devicemotion","slug":"devicemotion","link":"#devicemotion","children":[]},{"level":2,"title":"requestAnimationFrame","slug":"requestanimationframe","link":"#requestanimationframe","children":[]},{"level":2,"title":"localStrorage","slug":"localstrorage","link":"#localstrorage","children":[]},{"level":2,"title":"history","slug":"history","link":"#history","children":[]},{"level":2,"title":"worker","slug":"worker","link":"#worker","children":[]}],"relativePath":"html-css/HTML.md","lastUpdated":1675256330000}'),p={name:"html-css/HTML.md"},o=l(`

HTML5

大纲

新增的属性

  • placeholder
  • Calendar, date, time, email, url, search
  • ContentEditable
  • Draggable
  • Hidden
  • Content-menu
  • Data-Val(自定义属性)

新增的标签

  • 语义化标签
  • canvas
  • svg
  • Audio(声音播放)
  • Video(视频播放)

API

  • 移动端网页开发一般指的是 h5
  • 定位(需要地理位置的功能)
  • 重力感应(手机里面的陀螺仪(微信摇一摇,赛车转弯))
  • request-animation-frame(动画优化)
  • History 历史界面(控制当前页面的历史记录)
  • LocalStorage(本地存储,电脑/浏览器关闭都会保留);SessionStorage,(会话存储:窗口关闭就消失)。 都是存储信息(比如历史最高记录)
  • WebSocket(在线聊天,聊天室)
  • FileReader(文件读取,预览图)
  • WebWoker(文件的异步,提升性能,提升交互体验)
  • Fetch(传说中要替代 AJAX 的东西)

属性篇_input 新增 type

1.placeholder

html
<input type="text" placeholder="用户名/手机/邮箱" />
 <input type="password" placeholder="请输入密码" />
 
<input type="text" placeholder="用户名/手机/邮箱" />
 <input type="password" placeholder="请输入密码" />
diff --git a/assets/html-css_HTML.md.d9aa3d72.lean.js b/assets/html-css_HTML.md.a59c2cd4.lean.js
similarity index 97%
rename from assets/html-css_HTML.md.d9aa3d72.lean.js
rename to assets/html-css_HTML.md.a59c2cd4.lean.js
index 4cf2b9ee..30b92287 100644
--- a/assets/html-css_HTML.md.d9aa3d72.lean.js
+++ b/assets/html-css_HTML.md.a59c2cd4.lean.js
@@ -1 +1 @@
-import{_ as s,o as n,c as a,a as l}from"./app.679ab08c.js";const u=JSON.parse('{"title":"HTML5","description":"","frontmatter":{},"headers":[{"level":2,"title":"大纲","slug":"大纲","link":"#大纲","children":[{"level":3,"title":"新增的属性","slug":"新增的属性","link":"#新增的属性","children":[]},{"level":3,"title":"新增的标签","slug":"新增的标签","link":"#新增的标签","children":[]},{"level":3,"title":"API","slug":"api","link":"#api","children":[]}]},{"level":2,"title":"属性篇_input 新增 type","slug":"属性篇-input-新增-type","link":"#属性篇-input-新增-type","children":[{"level":3,"title":"1.placeholder","slug":"_1-placeholder","link":"#_1-placeholder","children":[]},{"level":3,"title":"2.input 新增 type","slug":"_2-input-新增-type","link":"#_2-input-新增-type","children":[]}]},{"level":2,"title":"ContentEditable","slug":"contenteditable","link":"#contenteditable","children":[]},{"level":2,"title":"标签篇_语义化标签","slug":"标签篇-语义化标签","link":"#标签篇-语义化标签","children":[]},{"level":2,"title":"audio 与 video 播放器","slug":"audio-与-video-播放器","link":"#audio-与-video-播放器","children":[]},{"level":2,"title":"视频播放器","slug":"视频播放器","link":"#视频播放器","children":[]},{"level":2,"title":"geolocation","slug":"geolocation","link":"#geolocation","children":[]},{"level":2,"title":"四行写个服务器","slug":"四行写个服务器","link":"#四行写个服务器","children":[]},{"level":2,"title":"deviceorientation","slug":"deviceorientation","link":"#deviceorientation","children":[]},{"level":2,"title":"手机访问电脑","slug":"手机访问电脑","link":"#手机访问电脑","children":[]},{"level":2,"title":"devicemotion","slug":"devicemotion","link":"#devicemotion","children":[]},{"level":2,"title":"requestAnimationFrame","slug":"requestanimationframe","link":"#requestanimationframe","children":[]},{"level":2,"title":"localStrorage","slug":"localstrorage","link":"#localstrorage","children":[]},{"level":2,"title":"history","slug":"history","link":"#history","children":[]},{"level":2,"title":"worker","slug":"worker","link":"#worker","children":[]}],"relativePath":"html-css/HTML.md","lastUpdated":1675256330000}'),p={name:"html-css/HTML.md"},o=l("",70),e=[o];function t(c,r,B,y,F,i){return n(),a("div",null,e)}const b=s(p,[["render",t]]);export{u as __pageData,b as default};
+import{_ as s,o as n,c as a,a as l}from"./app.815d1813.js";const u=JSON.parse('{"title":"HTML5","description":"","frontmatter":{},"headers":[{"level":2,"title":"大纲","slug":"大纲","link":"#大纲","children":[{"level":3,"title":"新增的属性","slug":"新增的属性","link":"#新增的属性","children":[]},{"level":3,"title":"新增的标签","slug":"新增的标签","link":"#新增的标签","children":[]},{"level":3,"title":"API","slug":"api","link":"#api","children":[]}]},{"level":2,"title":"属性篇_input 新增 type","slug":"属性篇-input-新增-type","link":"#属性篇-input-新增-type","children":[{"level":3,"title":"1.placeholder","slug":"_1-placeholder","link":"#_1-placeholder","children":[]},{"level":3,"title":"2.input 新增 type","slug":"_2-input-新增-type","link":"#_2-input-新增-type","children":[]}]},{"level":2,"title":"ContentEditable","slug":"contenteditable","link":"#contenteditable","children":[]},{"level":2,"title":"标签篇_语义化标签","slug":"标签篇-语义化标签","link":"#标签篇-语义化标签","children":[]},{"level":2,"title":"audio 与 video 播放器","slug":"audio-与-video-播放器","link":"#audio-与-video-播放器","children":[]},{"level":2,"title":"视频播放器","slug":"视频播放器","link":"#视频播放器","children":[]},{"level":2,"title":"geolocation","slug":"geolocation","link":"#geolocation","children":[]},{"level":2,"title":"四行写个服务器","slug":"四行写个服务器","link":"#四行写个服务器","children":[]},{"level":2,"title":"deviceorientation","slug":"deviceorientation","link":"#deviceorientation","children":[]},{"level":2,"title":"手机访问电脑","slug":"手机访问电脑","link":"#手机访问电脑","children":[]},{"level":2,"title":"devicemotion","slug":"devicemotion","link":"#devicemotion","children":[]},{"level":2,"title":"requestAnimationFrame","slug":"requestanimationframe","link":"#requestanimationframe","children":[]},{"level":2,"title":"localStrorage","slug":"localstrorage","link":"#localstrorage","children":[]},{"level":2,"title":"history","slug":"history","link":"#history","children":[]},{"level":2,"title":"worker","slug":"worker","link":"#worker","children":[]}],"relativePath":"html-css/HTML.md","lastUpdated":1675256330000}'),p={name:"html-css/HTML.md"},o=l("",70),e=[o];function t(c,r,B,y,F,i){return n(),a("div",null,e)}const b=s(p,[["render",t]]);export{u as __pageData,b as default};
diff --git a/assets/html-css_animation.md.b2ff8169.js b/assets/html-css_animation.md.7ae88207.js
similarity index 99%
rename from assets/html-css_animation.md.b2ff8169.js
rename to assets/html-css_animation.md.7ae88207.js
index f7ad8456..6300ad55 100644
--- a/assets/html-css_animation.md.b2ff8169.js
+++ b/assets/html-css_animation.md.7ae88207.js
@@ -1,4 +1,4 @@
-import{_ as s,o as n,c as a,a as l}from"./app.679ab08c.js";const p="/blog/assets/2023-02-01-21-04-28.4121323f.png",o="/blog/assets/2023-02-01-21-04-38.6dfd96aa.png",m=JSON.parse('{"title":"动画","description":"","frontmatter":{},"headers":[{"level":2,"title":"1.transition 过渡动画","slug":"_1-transition-过渡动画","link":"#_1-transition-过渡动画","children":[]},{"level":2,"title":"2.cubic-bezier","slug":"_2-cubic-bezier","link":"#_2-cubic-bezier","children":[]},{"level":2,"title":"3.animation","slug":"_3-animation","link":"#_3-animation","children":[]},{"level":2,"title":"4.step 跳转动画","slug":"_4-step-跳转动画","link":"#_4-step-跳转动画","children":[]},{"level":2,"title":"5.rotate3D 变换","slug":"_5-rotate3d-变换","link":"#_5-rotate3d-变换","children":[]},{"level":2,"title":"6.scale 伸缩","slug":"_6-scale-伸缩","link":"#_6-scale-伸缩","children":[]},{"level":2,"title":"7.skew 倾斜","slug":"_7-skew-倾斜","link":"#_7-skew-倾斜","children":[]},{"level":2,"title":"8.translate+perspective","slug":"_8-translate-perspective","link":"#_8-translate-perspective","children":[]},{"level":2,"title":"9.matrix","slug":"_9-matrix","link":"#_9-matrix","children":[]}],"relativePath":"html-css/animation.md","lastUpdated":1675259332000}'),e={name:"html-css/animation.md"},c=l(`

动画

1.transition 过渡动画

css
div {
+import{_ as s,o as n,c as a,a as l}from"./app.815d1813.js";const p="/blog/assets/2023-02-01-21-04-28.4121323f.png",o="/blog/assets/2023-02-01-21-04-38.6dfd96aa.png",m=JSON.parse('{"title":"动画","description":"","frontmatter":{},"headers":[{"level":2,"title":"1.transition 过渡动画","slug":"_1-transition-过渡动画","link":"#_1-transition-过渡动画","children":[]},{"level":2,"title":"2.cubic-bezier","slug":"_2-cubic-bezier","link":"#_2-cubic-bezier","children":[]},{"level":2,"title":"3.animation","slug":"_3-animation","link":"#_3-animation","children":[]},{"level":2,"title":"4.step 跳转动画","slug":"_4-step-跳转动画","link":"#_4-step-跳转动画","children":[]},{"level":2,"title":"5.rotate3D 变换","slug":"_5-rotate3d-变换","link":"#_5-rotate3d-变换","children":[]},{"level":2,"title":"6.scale 伸缩","slug":"_6-scale-伸缩","link":"#_6-scale-伸缩","children":[]},{"level":2,"title":"7.skew 倾斜","slug":"_7-skew-倾斜","link":"#_7-skew-倾斜","children":[]},{"level":2,"title":"8.translate+perspective","slug":"_8-translate-perspective","link":"#_8-translate-perspective","children":[]},{"level":2,"title":"9.matrix","slug":"_9-matrix","link":"#_9-matrix","children":[]}],"relativePath":"html-css/animation.md","lastUpdated":1675259332000}'),e={name:"html-css/animation.md"},c=l(`

动画

1.transition 过渡动画

css
div {
   width: 100px;
   height: 100px;
   background-color: red;
diff --git a/assets/html-css_animation.md.b2ff8169.lean.js b/assets/html-css_animation.md.7ae88207.lean.js
similarity index 95%
rename from assets/html-css_animation.md.b2ff8169.lean.js
rename to assets/html-css_animation.md.7ae88207.lean.js
index 16f537b1..57b275c2 100644
--- a/assets/html-css_animation.md.b2ff8169.lean.js
+++ b/assets/html-css_animation.md.7ae88207.lean.js
@@ -1 +1 @@
-import{_ as s,o as n,c as a,a as l}from"./app.679ab08c.js";const p="/blog/assets/2023-02-01-21-04-28.4121323f.png",o="/blog/assets/2023-02-01-21-04-38.6dfd96aa.png",m=JSON.parse('{"title":"动画","description":"","frontmatter":{},"headers":[{"level":2,"title":"1.transition 过渡动画","slug":"_1-transition-过渡动画","link":"#_1-transition-过渡动画","children":[]},{"level":2,"title":"2.cubic-bezier","slug":"_2-cubic-bezier","link":"#_2-cubic-bezier","children":[]},{"level":2,"title":"3.animation","slug":"_3-animation","link":"#_3-animation","children":[]},{"level":2,"title":"4.step 跳转动画","slug":"_4-step-跳转动画","link":"#_4-step-跳转动画","children":[]},{"level":2,"title":"5.rotate3D 变换","slug":"_5-rotate3d-变换","link":"#_5-rotate3d-变换","children":[]},{"level":2,"title":"6.scale 伸缩","slug":"_6-scale-伸缩","link":"#_6-scale-伸缩","children":[]},{"level":2,"title":"7.skew 倾斜","slug":"_7-skew-倾斜","link":"#_7-skew-倾斜","children":[]},{"level":2,"title":"8.translate+perspective","slug":"_8-translate-perspective","link":"#_8-translate-perspective","children":[]},{"level":2,"title":"9.matrix","slug":"_9-matrix","link":"#_9-matrix","children":[]}],"relativePath":"html-css/animation.md","lastUpdated":1675259332000}'),e={name:"html-css/animation.md"},c=l("",70),r=[c];function t(B,y,F,i,A,b){return n(),a("div",null,r)}const d=s(e,[["render",t]]);export{m as __pageData,d as default};
+import{_ as s,o as n,c as a,a as l}from"./app.815d1813.js";const p="/blog/assets/2023-02-01-21-04-28.4121323f.png",o="/blog/assets/2023-02-01-21-04-38.6dfd96aa.png",m=JSON.parse('{"title":"动画","description":"","frontmatter":{},"headers":[{"level":2,"title":"1.transition 过渡动画","slug":"_1-transition-过渡动画","link":"#_1-transition-过渡动画","children":[]},{"level":2,"title":"2.cubic-bezier","slug":"_2-cubic-bezier","link":"#_2-cubic-bezier","children":[]},{"level":2,"title":"3.animation","slug":"_3-animation","link":"#_3-animation","children":[]},{"level":2,"title":"4.step 跳转动画","slug":"_4-step-跳转动画","link":"#_4-step-跳转动画","children":[]},{"level":2,"title":"5.rotate3D 变换","slug":"_5-rotate3d-变换","link":"#_5-rotate3d-变换","children":[]},{"level":2,"title":"6.scale 伸缩","slug":"_6-scale-伸缩","link":"#_6-scale-伸缩","children":[]},{"level":2,"title":"7.skew 倾斜","slug":"_7-skew-倾斜","link":"#_7-skew-倾斜","children":[]},{"level":2,"title":"8.translate+perspective","slug":"_8-translate-perspective","link":"#_8-translate-perspective","children":[]},{"level":2,"title":"9.matrix","slug":"_9-matrix","link":"#_9-matrix","children":[]}],"relativePath":"html-css/animation.md","lastUpdated":1675259332000}'),e={name:"html-css/animation.md"},c=l("",70),r=[c];function t(B,y,F,i,A,b){return n(),a("div",null,r)}const d=s(e,[["render",t]]);export{m as __pageData,d as default};
diff --git a/assets/html-css_canvas-svg.md.bfb80c2d.js b/assets/html-css_canvas-svg.md.b70ac3e1.js
similarity index 99%
rename from assets/html-css_canvas-svg.md.bfb80c2d.js
rename to assets/html-css_canvas-svg.md.b70ac3e1.js
index 124ad891..ff3fdeb1 100644
--- a/assets/html-css_canvas-svg.md.bfb80c2d.js
+++ b/assets/html-css_canvas-svg.md.b70ac3e1.js
@@ -1,4 +1,4 @@
-import{_ as s,o as n,c as a,a as l}from"./app.679ab08c.js";const u=JSON.parse('{"title":"canvas 和 svg","description":"","frontmatter":{},"headers":[{"level":2,"title":"canvas 画线","slug":"canvas-画线","link":"#canvas-画线","children":[]},{"level":2,"title":"canvas 画矩形","slug":"canvas-画矩形","link":"#canvas-画矩形","children":[]},{"level":2,"title":"小方块下落","slug":"小方块下落","link":"#小方块下落","children":[]},{"level":2,"title":"canvas 画圆","slug":"canvas-画圆","link":"#canvas-画圆","children":[]},{"level":2,"title":"画圆角矩形","slug":"画圆角矩形","link":"#画圆角矩形","children":[]},{"level":2,"title":"canvas 贝塞尔曲线","slug":"canvas-贝塞尔曲线","link":"#canvas-贝塞尔曲线","children":[]},{"level":2,"title":"坐标平移旋转与缩放","slug":"坐标平移旋转与缩放","link":"#坐标平移旋转与缩放","children":[]},{"level":2,"title":"canvas 的 save 和 restore","slug":"canvas-的-save-和-restore","link":"#canvas-的-save-和-restore","children":[]},{"level":2,"title":"canvas 背景填充","slug":"canvas-背景填充","link":"#canvas-背景填充","children":[]},{"level":2,"title":"线性渐变","slug":"线性渐变","link":"#线性渐变","children":[]},{"level":2,"title":"canvas 辐射渐变","slug":"canvas-辐射渐变","link":"#canvas-辐射渐变","children":[]},{"level":2,"title":"canvas 阴影","slug":"canvas-阴影","link":"#canvas-阴影","children":[]},{"level":2,"title":"canvas 渲染文字","slug":"canvas-渲染文字","link":"#canvas-渲染文字","children":[]},{"level":2,"title":"canvas 线端样式","slug":"canvas-线端样式","link":"#canvas-线端样式","children":[]},{"level":2,"title":"SVG 画线与矩形","slug":"svg-画线与矩形","link":"#svg-画线与矩形","children":[]},{"level":2,"title":"svg 画圈,椭圆,直线","slug":"svg-画圈-椭圆-直线","link":"#svg-画圈-椭圆-直线","children":[]},{"level":2,"title":"svg 画多边形和文本","slug":"svg-画多边形和文本","link":"#svg-画多边形和文本","children":[]},{"level":2,"title":"SVG 透明度与线条样式","slug":"svg-透明度与线条样式","link":"#svg-透明度与线条样式","children":[]},{"level":2,"title":"SVG 的 path 标签","slug":"svg-的-path-标签","link":"#svg-的-path-标签","children":[]},{"level":2,"title":"path 画弧","slug":"path-画弧","link":"#path-画弧","children":[]},{"level":2,"title":"svg 线性渐变","slug":"svg-线性渐变","link":"#svg-线性渐变","children":[]},{"level":2,"title":"svg 高斯模糊","slug":"svg-高斯模糊","link":"#svg-高斯模糊","children":[]},{"level":2,"title":"SVG 虚线及简单动画","slug":"svg-虚线及简单动画","link":"#svg-虚线及简单动画","children":[]},{"level":2,"title":"svg 的 viewbox(比例尺)","slug":"svg-的-viewbox-比例尺","link":"#svg-的-viewbox-比例尺","children":[]}],"relativePath":"html-css/canvas-svg.md","lastUpdated":1675256330000}'),p={name:"html-css/canvas-svg.md"},o=l(`

canvas 和 svg

canvas 画线

html
<canvas id="can" width="500px" height="300px"></canvas>
+import{_ as s,o as n,c as a,a as l}from"./app.815d1813.js";const u=JSON.parse('{"title":"canvas 和 svg","description":"","frontmatter":{},"headers":[{"level":2,"title":"canvas 画线","slug":"canvas-画线","link":"#canvas-画线","children":[]},{"level":2,"title":"canvas 画矩形","slug":"canvas-画矩形","link":"#canvas-画矩形","children":[]},{"level":2,"title":"小方块下落","slug":"小方块下落","link":"#小方块下落","children":[]},{"level":2,"title":"canvas 画圆","slug":"canvas-画圆","link":"#canvas-画圆","children":[]},{"level":2,"title":"画圆角矩形","slug":"画圆角矩形","link":"#画圆角矩形","children":[]},{"level":2,"title":"canvas 贝塞尔曲线","slug":"canvas-贝塞尔曲线","link":"#canvas-贝塞尔曲线","children":[]},{"level":2,"title":"坐标平移旋转与缩放","slug":"坐标平移旋转与缩放","link":"#坐标平移旋转与缩放","children":[]},{"level":2,"title":"canvas 的 save 和 restore","slug":"canvas-的-save-和-restore","link":"#canvas-的-save-和-restore","children":[]},{"level":2,"title":"canvas 背景填充","slug":"canvas-背景填充","link":"#canvas-背景填充","children":[]},{"level":2,"title":"线性渐变","slug":"线性渐变","link":"#线性渐变","children":[]},{"level":2,"title":"canvas 辐射渐变","slug":"canvas-辐射渐变","link":"#canvas-辐射渐变","children":[]},{"level":2,"title":"canvas 阴影","slug":"canvas-阴影","link":"#canvas-阴影","children":[]},{"level":2,"title":"canvas 渲染文字","slug":"canvas-渲染文字","link":"#canvas-渲染文字","children":[]},{"level":2,"title":"canvas 线端样式","slug":"canvas-线端样式","link":"#canvas-线端样式","children":[]},{"level":2,"title":"SVG 画线与矩形","slug":"svg-画线与矩形","link":"#svg-画线与矩形","children":[]},{"level":2,"title":"svg 画圈,椭圆,直线","slug":"svg-画圈-椭圆-直线","link":"#svg-画圈-椭圆-直线","children":[]},{"level":2,"title":"svg 画多边形和文本","slug":"svg-画多边形和文本","link":"#svg-画多边形和文本","children":[]},{"level":2,"title":"SVG 透明度与线条样式","slug":"svg-透明度与线条样式","link":"#svg-透明度与线条样式","children":[]},{"level":2,"title":"SVG 的 path 标签","slug":"svg-的-path-标签","link":"#svg-的-path-标签","children":[]},{"level":2,"title":"path 画弧","slug":"path-画弧","link":"#path-画弧","children":[]},{"level":2,"title":"svg 线性渐变","slug":"svg-线性渐变","link":"#svg-线性渐变","children":[]},{"level":2,"title":"svg 高斯模糊","slug":"svg-高斯模糊","link":"#svg-高斯模糊","children":[]},{"level":2,"title":"SVG 虚线及简单动画","slug":"svg-虚线及简单动画","link":"#svg-虚线及简单动画","children":[]},{"level":2,"title":"svg 的 viewbox(比例尺)","slug":"svg-的-viewbox-比例尺","link":"#svg-的-viewbox-比例尺","children":[]}],"relativePath":"html-css/canvas-svg.md","lastUpdated":1675256330000}'),p={name:"html-css/canvas-svg.md"},o=l(`

canvas 和 svg

canvas 画线

html
<canvas id="can" width="500px" height="300px"></canvas>
 
<canvas id="can" width="500px" height="300px"></canvas>
 

注意:只能在行间样式设置大小,不能通过 css

javascript
var canvas = document.getElementById("can"); //画布
 var ctx = canvas.getContext("2d"); //画笔
diff --git a/assets/html-css_canvas-svg.md.bfb80c2d.lean.js b/assets/html-css_canvas-svg.md.b70ac3e1.lean.js
similarity index 97%
rename from assets/html-css_canvas-svg.md.bfb80c2d.lean.js
rename to assets/html-css_canvas-svg.md.b70ac3e1.lean.js
index 2335fcfd..f9590089 100644
--- a/assets/html-css_canvas-svg.md.bfb80c2d.lean.js
+++ b/assets/html-css_canvas-svg.md.b70ac3e1.lean.js
@@ -1 +1 @@
-import{_ as s,o as n,c as a,a as l}from"./app.679ab08c.js";const u=JSON.parse('{"title":"canvas 和 svg","description":"","frontmatter":{},"headers":[{"level":2,"title":"canvas 画线","slug":"canvas-画线","link":"#canvas-画线","children":[]},{"level":2,"title":"canvas 画矩形","slug":"canvas-画矩形","link":"#canvas-画矩形","children":[]},{"level":2,"title":"小方块下落","slug":"小方块下落","link":"#小方块下落","children":[]},{"level":2,"title":"canvas 画圆","slug":"canvas-画圆","link":"#canvas-画圆","children":[]},{"level":2,"title":"画圆角矩形","slug":"画圆角矩形","link":"#画圆角矩形","children":[]},{"level":2,"title":"canvas 贝塞尔曲线","slug":"canvas-贝塞尔曲线","link":"#canvas-贝塞尔曲线","children":[]},{"level":2,"title":"坐标平移旋转与缩放","slug":"坐标平移旋转与缩放","link":"#坐标平移旋转与缩放","children":[]},{"level":2,"title":"canvas 的 save 和 restore","slug":"canvas-的-save-和-restore","link":"#canvas-的-save-和-restore","children":[]},{"level":2,"title":"canvas 背景填充","slug":"canvas-背景填充","link":"#canvas-背景填充","children":[]},{"level":2,"title":"线性渐变","slug":"线性渐变","link":"#线性渐变","children":[]},{"level":2,"title":"canvas 辐射渐变","slug":"canvas-辐射渐变","link":"#canvas-辐射渐变","children":[]},{"level":2,"title":"canvas 阴影","slug":"canvas-阴影","link":"#canvas-阴影","children":[]},{"level":2,"title":"canvas 渲染文字","slug":"canvas-渲染文字","link":"#canvas-渲染文字","children":[]},{"level":2,"title":"canvas 线端样式","slug":"canvas-线端样式","link":"#canvas-线端样式","children":[]},{"level":2,"title":"SVG 画线与矩形","slug":"svg-画线与矩形","link":"#svg-画线与矩形","children":[]},{"level":2,"title":"svg 画圈,椭圆,直线","slug":"svg-画圈-椭圆-直线","link":"#svg-画圈-椭圆-直线","children":[]},{"level":2,"title":"svg 画多边形和文本","slug":"svg-画多边形和文本","link":"#svg-画多边形和文本","children":[]},{"level":2,"title":"SVG 透明度与线条样式","slug":"svg-透明度与线条样式","link":"#svg-透明度与线条样式","children":[]},{"level":2,"title":"SVG 的 path 标签","slug":"svg-的-path-标签","link":"#svg-的-path-标签","children":[]},{"level":2,"title":"path 画弧","slug":"path-画弧","link":"#path-画弧","children":[]},{"level":2,"title":"svg 线性渐变","slug":"svg-线性渐变","link":"#svg-线性渐变","children":[]},{"level":2,"title":"svg 高斯模糊","slug":"svg-高斯模糊","link":"#svg-高斯模糊","children":[]},{"level":2,"title":"SVG 虚线及简单动画","slug":"svg-虚线及简单动画","link":"#svg-虚线及简单动画","children":[]},{"level":2,"title":"svg 的 viewbox(比例尺)","slug":"svg-的-viewbox-比例尺","link":"#svg-的-viewbox-比例尺","children":[]}],"relativePath":"html-css/canvas-svg.md","lastUpdated":1675256330000}'),p={name:"html-css/canvas-svg.md"},o=l("",82),e=[o];function t(c,B,r,y,F,i){return n(),a("div",null,e)}const d=s(p,[["render",t]]);export{u as __pageData,d as default};
+import{_ as s,o as n,c as a,a as l}from"./app.815d1813.js";const u=JSON.parse('{"title":"canvas 和 svg","description":"","frontmatter":{},"headers":[{"level":2,"title":"canvas 画线","slug":"canvas-画线","link":"#canvas-画线","children":[]},{"level":2,"title":"canvas 画矩形","slug":"canvas-画矩形","link":"#canvas-画矩形","children":[]},{"level":2,"title":"小方块下落","slug":"小方块下落","link":"#小方块下落","children":[]},{"level":2,"title":"canvas 画圆","slug":"canvas-画圆","link":"#canvas-画圆","children":[]},{"level":2,"title":"画圆角矩形","slug":"画圆角矩形","link":"#画圆角矩形","children":[]},{"level":2,"title":"canvas 贝塞尔曲线","slug":"canvas-贝塞尔曲线","link":"#canvas-贝塞尔曲线","children":[]},{"level":2,"title":"坐标平移旋转与缩放","slug":"坐标平移旋转与缩放","link":"#坐标平移旋转与缩放","children":[]},{"level":2,"title":"canvas 的 save 和 restore","slug":"canvas-的-save-和-restore","link":"#canvas-的-save-和-restore","children":[]},{"level":2,"title":"canvas 背景填充","slug":"canvas-背景填充","link":"#canvas-背景填充","children":[]},{"level":2,"title":"线性渐变","slug":"线性渐变","link":"#线性渐变","children":[]},{"level":2,"title":"canvas 辐射渐变","slug":"canvas-辐射渐变","link":"#canvas-辐射渐变","children":[]},{"level":2,"title":"canvas 阴影","slug":"canvas-阴影","link":"#canvas-阴影","children":[]},{"level":2,"title":"canvas 渲染文字","slug":"canvas-渲染文字","link":"#canvas-渲染文字","children":[]},{"level":2,"title":"canvas 线端样式","slug":"canvas-线端样式","link":"#canvas-线端样式","children":[]},{"level":2,"title":"SVG 画线与矩形","slug":"svg-画线与矩形","link":"#svg-画线与矩形","children":[]},{"level":2,"title":"svg 画圈,椭圆,直线","slug":"svg-画圈-椭圆-直线","link":"#svg-画圈-椭圆-直线","children":[]},{"level":2,"title":"svg 画多边形和文本","slug":"svg-画多边形和文本","link":"#svg-画多边形和文本","children":[]},{"level":2,"title":"SVG 透明度与线条样式","slug":"svg-透明度与线条样式","link":"#svg-透明度与线条样式","children":[]},{"level":2,"title":"SVG 的 path 标签","slug":"svg-的-path-标签","link":"#svg-的-path-标签","children":[]},{"level":2,"title":"path 画弧","slug":"path-画弧","link":"#path-画弧","children":[]},{"level":2,"title":"svg 线性渐变","slug":"svg-线性渐变","link":"#svg-线性渐变","children":[]},{"level":2,"title":"svg 高斯模糊","slug":"svg-高斯模糊","link":"#svg-高斯模糊","children":[]},{"level":2,"title":"SVG 虚线及简单动画","slug":"svg-虚线及简单动画","link":"#svg-虚线及简单动画","children":[]},{"level":2,"title":"svg 的 viewbox(比例尺)","slug":"svg-的-viewbox-比例尺","link":"#svg-的-viewbox-比例尺","children":[]}],"relativePath":"html-css/canvas-svg.md","lastUpdated":1675256330000}'),p={name:"html-css/canvas-svg.md"},o=l("",82),e=[o];function t(c,B,r,y,F,i){return n(),a("div",null,e)}const d=s(p,[["render",t]]);export{u as __pageData,d as default};
diff --git a/assets/html-css_drag.md.b170a897.js b/assets/html-css_drag.md.7fd332a5.js
similarity index 99%
rename from assets/html-css_drag.md.b170a897.js
rename to assets/html-css_drag.md.7fd332a5.js
index 66d3c08d..4b173988 100644
--- a/assets/html-css_drag.md.b170a897.js
+++ b/assets/html-css_drag.md.7fd332a5.js
@@ -1,4 +1,4 @@
-import{_ as n,o as a,c as l,a as s,b as p}from"./app.679ab08c.js";const g=JSON.parse('{"title":"拖拽 API","description":"","frontmatter":{},"headers":[{"level":2,"title":"Drag 被拖拽元素","slug":"drag-被拖拽元素","link":"#drag-被拖拽元素","children":[]},{"level":2,"title":"Drag 目标元素(目标区域)","slug":"drag-目标元素-目标区域","link":"#drag-目标元素-目标区域","children":[]},{"level":2,"title":"拖拽 demo","slug":"拖拽-demo","link":"#拖拽-demo","children":[]},{"level":2,"title":"dataTransfer 补充属性","slug":"datatransfer-补充属性","link":"#datatransfer-补充属性","children":[]}],"relativePath":"html-css/drag.md","lastUpdated":1675256330000}'),o={name:"html-css/drag.md"},e=s(`

拖拽 API

Drag 被拖拽元素

html
<div class="a" draggable="true"></div>
+import{_ as n,o as a,c as l,a as s,b as p}from"./app.815d1813.js";const g=JSON.parse('{"title":"拖拽 API","description":"","frontmatter":{},"headers":[{"level":2,"title":"Drag 被拖拽元素","slug":"drag-被拖拽元素","link":"#drag-被拖拽元素","children":[]},{"level":2,"title":"Drag 目标元素(目标区域)","slug":"drag-目标元素-目标区域","link":"#drag-目标元素-目标区域","children":[]},{"level":2,"title":"拖拽 demo","slug":"拖拽-demo","link":"#拖拽-demo","children":[]},{"level":2,"title":"dataTransfer 补充属性","slug":"datatransfer-补充属性","link":"#datatransfer-补充属性","children":[]}],"relativePath":"html-css/drag.md","lastUpdated":1675256330000}'),o={name:"html-css/drag.md"},e=s(`

拖拽 API

Drag 被拖拽元素

html
<div class="a" draggable="true"></div>
 <!-- 谷歌,safari可以,火狐,Ie不支持 -->
 
<div class="a" draggable="true"></div>
 <!-- 谷歌,safari可以,火狐,Ie不支持 -->
diff --git a/assets/html-css_drag.md.b170a897.lean.js b/assets/html-css_drag.md.7fd332a5.lean.js
similarity index 93%
rename from assets/html-css_drag.md.b170a897.lean.js
rename to assets/html-css_drag.md.7fd332a5.lean.js
index c5995ac3..a58f7c10 100644
--- a/assets/html-css_drag.md.b170a897.lean.js
+++ b/assets/html-css_drag.md.7fd332a5.lean.js
@@ -1 +1 @@
-import{_ as n,o as a,c as l,a as s,b as p}from"./app.679ab08c.js";const g=JSON.parse('{"title":"拖拽 API","description":"","frontmatter":{},"headers":[{"level":2,"title":"Drag 被拖拽元素","slug":"drag-被拖拽元素","link":"#drag-被拖拽元素","children":[]},{"level":2,"title":"Drag 目标元素(目标区域)","slug":"drag-目标元素-目标区域","link":"#drag-目标元素-目标区域","children":[]},{"level":2,"title":"拖拽 demo","slug":"拖拽-demo","link":"#拖拽-demo","children":[]},{"level":2,"title":"dataTransfer 补充属性","slug":"datatransfer-补充属性","link":"#datatransfer-补充属性","children":[]}],"relativePath":"html-css/drag.md","lastUpdated":1675256330000}'),o={name:"html-css/drag.md"},e=s("",8),t=p("p",{"position:absolute":""},"小功能:.a",-1),c=s("",12),r=[e,t,c];function B(y,F,i,A,b,u){return a(),l("div",null,r)}const C=n(o,[["render",B]]);export{g as __pageData,C as default};
+import{_ as n,o as a,c as l,a as s,b as p}from"./app.815d1813.js";const g=JSON.parse('{"title":"拖拽 API","description":"","frontmatter":{},"headers":[{"level":2,"title":"Drag 被拖拽元素","slug":"drag-被拖拽元素","link":"#drag-被拖拽元素","children":[]},{"level":2,"title":"Drag 目标元素(目标区域)","slug":"drag-目标元素-目标区域","link":"#drag-目标元素-目标区域","children":[]},{"level":2,"title":"拖拽 demo","slug":"拖拽-demo","link":"#拖拽-demo","children":[]},{"level":2,"title":"dataTransfer 补充属性","slug":"datatransfer-补充属性","link":"#datatransfer-补充属性","children":[]}],"relativePath":"html-css/drag.md","lastUpdated":1675256330000}'),o={name:"html-css/drag.md"},e=s("",8),t=p("p",{"position:absolute":""},"小功能:.a",-1),c=s("",12),r=[e,t,c];function B(y,F,i,A,b,u){return a(),l("div",null,r)}const C=n(o,[["render",B]]);export{g as __pageData,C as default};
diff --git a/assets/html-css_interview.md.fda9f4cc.js b/assets/html-css_interview.md.f0810157.js
similarity index 99%
rename from assets/html-css_interview.md.fda9f4cc.js
rename to assets/html-css_interview.md.f0810157.js
index 3c128f90..245284ce 100644
--- a/assets/html-css_interview.md.fda9f4cc.js
+++ b/assets/html-css_interview.md.f0810157.js
@@ -1,4 +1,4 @@
-import{_ as s,o as n,c as a,a as l}from"./app.679ab08c.js";const p="/blog/assets/2023-02-01-21-38-25.04ce3663.png",A=JSON.parse('{"title":"HTML_CSS 面试题","description":"","frontmatter":{},"headers":[{"level":2,"title":"1. 什么是 ?是否需要在 HTML5 中使用?","slug":"_1-什么是-doctype-是否需要在-html5-中使用","link":"#_1-什么是-doctype-是否需要在-html5-中使用","children":[]},{"level":2,"title":"2. 什么是可替换元素,什么是非可替换元素,它们各自有什么特点?","slug":"_2-什么是可替换元素-什么是非可替换元素-它们各自有什么特点","link":"#_2-什么是可替换元素-什么是非可替换元素-它们各自有什么特点","children":[]},{"level":2,"title":"3. src 和 href 的区别","slug":"_3-src-和-href-的区别","link":"#_3-src-和-href-的区别","children":[]},{"level":2,"title":"4. 说说常用的 meta 标签","slug":"_4-说说常用的-meta-标签","link":"#_4-说说常用的-meta-标签","children":[]},{"level":2,"title":"5. 说说对 html 语义化的理解","slug":"_5-说说对-html-语义化的理解","link":"#_5-说说对-html-语义化的理解","children":[]},{"level":2,"title":"6. label 的作用是什么?是怎么用的?","slug":"_6-label-的作用是什么-是怎么用的","link":"#_6-label-的作用是什么-是怎么用的","children":[]},{"level":2,"title":"7. iframe 框架有那些优缺点?","slug":"_7-iframe-框架有那些优缺点","link":"#_7-iframe-框架有那些优缺点","children":[]},{"level":2,"title":"8. HTML 与 XHTML 二者有什么区别,你觉得应该使用哪一个并说出理由。 不同于 XML","slug":"_8-html-与-xhtml-二者有什么区别-你觉得应该使用哪一个并说出理由。-不同于-xml","link":"#_8-html-与-xhtml-二者有什么区别-你觉得应该使用哪一个并说出理由。-不同于-xml","children":[]},{"level":2,"title":"9. HTML5 的 form 如何关闭自动完成功能?","slug":"_9-html5-的-form-如何关闭自动完成功能","link":"#_9-html5-的-form-如何关闭自动完成功能","children":[]},{"level":2,"title":"10. title 与 h1 的区别、b 与 strong 的区别、i 与 em 的区别?","slug":"_10-title-与-h1-的区别、b-与-strong-的区别、i-与-em-的区别","link":"#_10-title-与-h1-的区别、b-与-strong-的区别、i-与-em-的区别","children":[]},{"level":2,"title":"11. 请描述下 SEO 中的 TDK","slug":"_11-请描述下-seo-中的-tdk","link":"#_11-请描述下-seo-中的-tdk","children":[]},{"level":2,"title":"13. 什么是严格模式与混杂模式?","slug":"_13-什么是严格模式与混杂模式","link":"#_13-什么是严格模式与混杂模式","children":[]},{"level":2,"title":"14. 对于 WEB 标准以及 W3C 的理解与认识问题","slug":"_14-对于-web-标准以及-w3c-的理解与认识问题","link":"#_14-对于-web-标准以及-w3c-的理解与认识问题","children":[]},{"level":2,"title":"15. 列举 IE 与其他浏览器不一样的特性?","slug":"_15-列举-ie-与其他浏览器不一样的特性","link":"#_15-列举-ie-与其他浏览器不一样的特性","children":[]},{"level":2,"title":"17. 页面可见性(Page Visibility)API 可以有哪些用途?","slug":"_17-页面可见性-page-visibility-api-可以有哪些用途","link":"#_17-页面可见性-page-visibility-api-可以有哪些用途","children":[]},{"level":2,"title":"19. div+css 的布局较 table 布局有什么优点?","slug":"_19-div-css-的布局较-table-布局有什么优点","link":"#_19-div-css-的布局较-table-布局有什么优点","children":[]},{"level":2,"title":"24. HTML 全局属性(global attribute)有哪些","slug":"_24-html-全局属性-global-attribute-有哪些","link":"#_24-html-全局属性-global-attribute-有哪些","children":[]},{"level":2,"title":"28.  iframe 的作用","slug":"_28-iframe-的作用","link":"#_28-iframe-的作用","children":[]},{"level":2,"title":"29. img 上 title 与 alt","slug":"_29-img-上-title-与-alt","link":"#_29-img-上-title-与-alt","children":[]},{"level":2,"title":"31. 行内元素和块级元素区别,有哪些,怎样转换?(顶呱呱)","slug":"_31-行内元素和块级元素区别-有哪些-怎样转换-顶呱呱","link":"#_31-行内元素和块级元素区别-有哪些-怎样转换-顶呱呱","children":[]},{"level":2,"title":"34.  h5 和 html5 区别","slug":"_34-h5-和-html5-区别","link":"#_34-h5-和-html5-区别","children":[]},{"level":2,"title":"35. form 表单上传文件时需要进行什么样的声明","slug":"_35-form-表单上传文件时需要进行什么样的声明","link":"#_35-form-表单上传文件时需要进行什么样的声明","children":[]},{"level":2,"title":"37. 如何在一张图片上的某一个区域做到点击事件","slug":"_37-如何在一张图片上的某一个区域做到点击事件","link":"#_37-如何在一张图片上的某一个区域做到点击事件","children":[]},{"level":2,"title":"39. 什么是锚点?","slug":"_39-什么是锚点","link":"#_39-什么是锚点","children":[]},{"level":2,"title":"40. 图片与 span 元素混排图像下方会出现几像素的空隙的原因是什么?","slug":"_40-图片与-span-元素混排图像下方会出现几像素的空隙的原因是什么","link":"#_40-图片与-span-元素混排图像下方会出现几像素的空隙的原因是什么","children":[]},{"level":2,"title":"41. a 元素除了用于导航外,还可以有什么作用?","slug":"_41-a-元素除了用于导航外-还可以有什么作用","link":"#_41-a-元素除了用于导航外-还可以有什么作用","children":[{"level":3,"title":"1.   介绍下 BFC 及其应用","slug":"_1-介绍下-bfc-及其应用","link":"#_1-介绍下-bfc-及其应用","children":[]},{"level":3,"title":"2. 介绍下 BFC、IFC、GFC 和 FFC","slug":"_2-介绍下-bfc、ifc、gfc-和-ffc","link":"#_2-介绍下-bfc、ifc、gfc-和-ffc","children":[]},{"level":3,"title":"3. flex 骰子","slug":"_3-flex-骰子","link":"#_3-flex-骰子","children":[]},{"level":3,"title":"5. 分析比较 opacity: 0、visibility: hidden、display: none 优劣和适用场景。","slug":"_5-分析比较-opacity-0、visibility-hidden、display-none-优劣和适用场景。","link":"#_5-分析比较-opacity-0、visibility-hidden、display-none-优劣和适用场景。","children":[]},{"level":3,"title":"6. 已知如下代码,如何修改才能让图片宽度为 300px ?注意下面代码不可修改。","slug":"_6-已知如下代码-如何修改才能让图片宽度为-300px-注意下面代码不可修改。","link":"#_6-已知如下代码-如何修改才能让图片宽度为-300px-注意下面代码不可修改。","children":[]},{"level":3,"title":"7. 如何用 css 或 js 实现多行文本溢出省略效果,考虑兼容性","slug":"_7-如何用-css-或-js-实现多行文本溢出省略效果-考虑兼容性","link":"#_7-如何用-css-或-js-实现多行文本溢出省略效果-考虑兼容性","children":[]},{"level":3,"title":"8. 居中为什么要使用 transform(为什么不使用 marginLeft/Top)(阿里)","slug":"_8-居中为什么要使用-transform-为什么不使用-marginleft-top-阿里","link":"#_8-居中为什么要使用-transform-为什么不使用-marginleft-top-阿里","children":[]},{"level":3,"title":"10. 说出 space-between 和 space-around 的区别?(携程)","slug":"_10-说出-space-between-和-space-around-的区别-携程","link":"#_10-说出-space-between-和-space-around-的区别-携程","children":[]},{"level":3,"title":"11. CSS3 中 transition 和 animation 的属性分别有哪些","slug":"_11-css3-中-transition-和-animation-的属性分别有哪些","link":"#_11-css3-中-transition-和-animation-的属性分别有哪些","children":[]},{"level":3,"title":"12. 隐藏页面中的某个元素的方法有哪些?","slug":"_12-隐藏页面中的某个元素的方法有哪些","link":"#_12-隐藏页面中的某个元素的方法有哪些","children":[]},{"level":3,"title":"13. 层叠上下文","slug":"_13-层叠上下文","link":"#_13-层叠上下文","children":[]},{"level":3,"title":"15. 讲一下png8、png16、png32的区别,并简单讲讲 png 的压缩原理","slug":"_15-讲一下png8、png16、png32的区别-并简单讲讲-png-的压缩原理","link":"#_15-讲一下png8、png16、png32的区别-并简单讲讲-png-的压缩原理","children":[]},{"level":3,"title":"16. 说说渐进增强和优雅降级","slug":"_16-说说渐进增强和优雅降级","link":"#_16-说说渐进增强和优雅降级","children":[]},{"level":3,"title":"17. 介绍下 positon 属性","slug":"_17-介绍下-positon-属性","link":"#_17-介绍下-positon-属性","children":[]},{"level":3,"title":"19. 如何实现一个自适应的正方形","slug":"_19-如何实现一个自适应的正方形","link":"#_19-如何实现一个自适应的正方形","children":[]},{"level":3,"title":"20. 如何实现三栏布局","slug":"_20-如何实现三栏布局","link":"#_20-如何实现三栏布局","children":[]},{"level":3,"title":"21. import 和 link 区别","slug":"_21-import-和-link-区别","link":"#_21-import-和-link-区别","children":[]},{"level":3,"title":"23. 清除浮动的方法","slug":"_23-清除浮动的方法","link":"#_23-清除浮动的方法","children":[]},{"level":3,"title":"使用 clear 属性清除浮动的原理?","slug":"使用-clear-属性清除浮动的原理","link":"#使用-clear-属性清除浮动的原理","children":[]},{"level":3,"title":"对 requestAnimationframe 的理解","slug":"对-requestanimationframe-的理解","link":"#对-requestanimationframe-的理解","children":[]},{"level":3,"title":"伪元素和伪类的区别和作用?","slug":"伪元素和伪类的区别和作用","link":"#伪元素和伪类的区别和作用","children":[]},{"level":3,"title":"25. css reset 和 normalize.css 有什么区别?","slug":"_25-css-reset-和-normalize-css-有什么区别","link":"#_25-css-reset-和-normalize-css-有什么区别","children":[]},{"level":3,"title":"27. 如何避免重绘或者重排?","slug":"_27-如何避免重绘或者重排","link":"#_27-如何避免重绘或者重排","children":[]},{"level":3,"title":"28. 如何触发重排和重绘?","slug":"_28-如何触发重排和重绘","link":"#_28-如何触发重排和重绘","children":[]},{"level":3,"title":"29. 重绘与重排的区别?","slug":"_29-重绘与重排的区别","link":"#_29-重绘与重排的区别","children":[]},{"level":3,"title":"30. 如何优化图片","slug":"_30-如何优化图片","link":"#_30-如何优化图片","children":[]},{"level":3,"title":"34. 什么是渐进式渲染(progressive rendering)?","slug":"_34-什么是渐进式渲染-progressive-rendering","link":"#_34-什么是渐进式渲染-progressive-rendering","children":[]},{"level":3,"title":"39. img 标签在页面中有 1px 的边框,怎么处理","slug":"_39-img-标签在页面中有-1px-的边框-怎么处理","link":"#_39-img-标签在页面中有-1px-的边框-怎么处理","children":[]},{"level":3,"title":"40. 如何绝对居中(不用定位)","slug":"_40-如何绝对居中-不用定位","link":"#_40-如何绝对居中-不用定位","children":[]},{"level":3,"title":"42. 我有 5 个 div 在一行,我要让 div 与 div 直接间距 10px 且最左最右两边的 div 据边框 10px,同时在我改变窗口大小时,这个 10px 不能变,div 根据窗口改变大小(长天星斗)","slug":"_42-我有-5-个-div-在一行-我要让-div-与-div-直接间距-10px-且最左最右两边的-div-据边框-10px-同时在我改变窗口大小时-这个-10px-不能变-div-根据窗口改变大小-长天星斗","link":"#_42-我有-5-个-div-在一行-我要让-div-与-div-直接间距-10px-且最左最右两边的-div-据边框-10px-同时在我改变窗口大小时-这个-10px-不能变-div-根据窗口改变大小-长天星斗","children":[]},{"level":3,"title":"48. px 和 em 的区别","slug":"_48-px-和-em-的区别","link":"#_48-px-和-em-的区别","children":[]},{"level":3,"title":"49. div 之间的间隙是怎么产生的,应该怎么消除?","slug":"_49-div-之间的间隙是怎么产生的-应该怎么消除","link":"#_49-div-之间的间隙是怎么产生的-应该怎么消除","children":[]},{"level":3,"title":"24. display:inline-block 什么时候会显示间隙?","slug":"_24-display-inline-block-什么时候会显示间隙","link":"#_24-display-inline-block-什么时候会显示间隙","children":[]},{"level":3,"title":"57. 你怎么处理页面兼容性问题?","slug":"_57-你怎么处理页面兼容性问题","link":"#_57-你怎么处理页面兼容性问题","children":[]},{"level":3,"title":"63. 什么是继承?CSS 中哪些属性可以继承?哪些不可以继承?","slug":"_63-什么是继承-css-中哪些属性可以继承-哪些不可以继承","link":"#_63-什么是继承-css-中哪些属性可以继承-哪些不可以继承","children":[]},{"level":3,"title":"65. CSS 的计算属性知道吗","slug":"_65-css-的计算属性知道吗","link":"#_65-css-的计算属性知道吗","children":[]},{"level":3,"title":"66. 为何 CSS 放在 HTML 头部?","slug":"_66-为何-css-放在-html-头部","link":"#_66-为何-css-放在-html-头部","children":[]},{"level":3,"title":"67. background-size 有哪 4 种值类型?","slug":"_67-background-size-有哪-4-种值类型","link":"#_67-background-size-有哪-4-种值类型","children":[]},{"level":3,"title":"74. 有一个高度自适应的 div,里面有 2 个 div,一个高度 100px,希望另一个填满剩下的高度?(CSS 实现)","slug":"_74-有一个高度自适应的-div-里面有-2-个-div-一个高度-100px-希望另一个填满剩下的高度-css-实现","link":"#_74-有一个高度自适应的-div-里面有-2-个-div-一个高度-100px-希望另一个填满剩下的高度-css-实现","children":[]},{"level":3,"title":"inline 和 block 区别(长宽,padding,margin)","slug":"inline-和-block-区别-长宽-padding-margin","link":"#inline-和-block-区别-长宽-padding-margin","children":[]},{"level":3,"title":"77. 当 margin-top、padding-top 的值是百分比时,分别是如何计算的?","slug":"_77-当-margin-top、padding-top-的值是百分比时-分别是如何计算的","link":"#_77-当-margin-top、padding-top-的值是百分比时-分别是如何计算的","children":[]},{"level":3,"title":"81. 在 rem 自适应页面中使用 sprite 会出现背景图不随元素放大缩小的情况,如何解决?","slug":"_81-在-rem-自适应页面中使用-sprite-会出现背景图不随元素放大缩小的情况-如何解决","link":"#_81-在-rem-自适应页面中使用-sprite-会出现背景图不随元素放大缩小的情况-如何解决","children":[]},{"level":3,"title":"98. 如何将大量可变化的图片显示到页面并不影响浏览器性能?","slug":"_98-如何将大量可变化的图片显示到页面并不影响浏览器性能","link":"#_98-如何将大量可变化的图片显示到页面并不影响浏览器性能","children":[]},{"level":3,"title":"99. 图片与图片之间默认存在的间距如何去除?","slug":"_99-图片与图片之间默认存在的间距如何去除","link":"#_99-图片与图片之间默认存在的间距如何去除","children":[]},{"level":3,"title":"102. 不使用 border 属性,使用其他属性模拟边框。","slug":"_102-不使用-border-属性-使用其他属性模拟边框。","link":"#_102-不使用-border-属性-使用其他属性模拟边框。","children":[]},{"level":3,"title":"103. 阅读代码,计算 ul 的高度。","slug":"_103-阅读代码-计算-ul-的高度。","link":"#_103-阅读代码-计算-ul-的高度。","children":[]},{"level":3,"title":"105. 如何使用媒体查询实现视口宽度大于 320px 小于 640px 时 div 元素宽度变成 30%?","slug":"_105-如何使用媒体查询实现视口宽度大于-320px-小于-640px-时-div-元素宽度变成-30","link":"#_105-如何使用媒体查询实现视口宽度大于-320px-小于-640px-时-div-元素宽度变成-30","children":[]},{"level":3,"title":"106. 什么是 HTML 实体?","slug":"_106-什么是-html-实体","link":"#_106-什么是-html-实体","children":[]},{"level":3,"title":"109. 在定位属性 position 中,哪个值会脱离文档流?","slug":"_109-在定位属性-position-中-哪个值会脱离文档流","link":"#_109-在定位属性-position-中-哪个值会脱离文档流","children":[]},{"level":3,"title":"画一条 0.5px 的线","slug":"画一条-0-5px-的线","link":"#画一条-0-5px-的线","children":[]},{"level":3,"title":"6. 如何解决 1px 问题?","slug":"_6-如何解决-1px-问题","link":"#_6-如何解决-1px-问题","children":[]},{"level":3,"title":"118. 绝对定位和浮动有什么异同?","slug":"_118-绝对定位和浮动有什么异同","link":"#_118-绝对定位和浮动有什么异同","children":[]},{"level":3,"title":"120. 文本“强制换行”的属性是什么?","slug":"_120-文本-强制换行-的属性是什么","link":"#_120-文本-强制换行-的属性是什么","children":[]},{"level":3,"title":"121. 阅读代码,分析最终执行结果 div 的水平、垂直位置距离。","slug":"_121-阅读代码-分析最终执行结果-div-的水平、垂直位置距离。","link":"#_121-阅读代码-分析最终执行结果-div-的水平、垂直位置距离。","children":[]},{"level":3,"title":"122. 设置了元素的过渡后需要有触发条件才能看到效果,列举出可用的触发条件。","slug":"_122-设置了元素的过渡后需要有触发条件才能看到效果-列举出可用的触发条件。","link":"#_122-设置了元素的过渡后需要有触发条件才能看到效果-列举出可用的触发条件。","children":[]},{"level":3,"title":"123. 怎样把背景图附着在内容上?","slug":"_123-怎样把背景图附着在内容上","link":"#_123-怎样把背景图附着在内容上","children":[]},{"level":3,"title":"124. CSS 优化、提高性能的方法有哪些?","slug":"_124-css-优化、提高性能的方法有哪些","link":"#_124-css-优化、提高性能的方法有哪些","children":[]},{"level":3,"title":"125. 网页中应该使用奇数还是偶数的字体?","slug":"_125-网页中应该使用奇数还是偶数的字体","link":"#_125-网页中应该使用奇数还是偶数的字体","children":[]},{"level":3,"title":"126. margin 和 padding 分别适合什么场景使用?","slug":"_126-margin-和-padding-分别适合什么场景使用","link":"#_126-margin-和-padding-分别适合什么场景使用","children":[]},{"level":3,"title":"127. 对于 line-height 是如何理解的?","slug":"_127-对于-line-height-是如何理解的","link":"#_127-对于-line-height-是如何理解的","children":[]},{"level":3,"title":"128. 如何让 Chrome 支持小于 12px 的文字?","slug":"_128-如何让-chrome-支持小于-12px-的文字","link":"#_128-如何让-chrome-支持小于-12px-的文字","children":[]},{"level":3,"title":"129. 如果需要手动写动画,你认为最小时间间隔是多久?","slug":"_129-如果需要手动写动画-你认为最小时间间隔是多久","link":"#_129-如果需要手动写动画-你认为最小时间间隔是多久","children":[]},{"level":3,"title":"131. style 标签写在 body 后面与 body 前面有什么区别?","slug":"_131-style-标签写在-body-后面与-body-前面有什么区别","link":"#_131-style-标签写在-body-后面与-body-前面有什么区别","children":[]},{"level":3,"title":"132. 阐述一下 CSS Sprites","slug":"_132-阐述一下-css-sprites","link":"#_132-阐述一下-css-sprites","children":[]},{"level":3,"title":"133. 写出背景色渐变的 CSS 代码","slug":"_133-写出背景色渐变的-css-代码","link":"#_133-写出背景色渐变的-css-代码","children":[]},{"level":3,"title":"134. 写出添加下划线的 CSS 代码","slug":"_134-写出添加下划线的-css-代码","link":"#_134-写出添加下划线的-css-代码","children":[]},{"level":3,"title":"135. 写出让对象顺时针旋转 90 度的 CSS 代码(最好附带动画效果)","slug":"_135-写出让对象顺时针旋转-90-度的-css-代码-最好附带动画效果","link":"#_135-写出让对象顺时针旋转-90-度的-css-代码-最好附带动画效果","children":[]},{"level":3,"title":"136. CSS 优先级顺序正确的是( )","slug":"_136-css-优先级顺序正确的是","link":"#_136-css-优先级顺序正确的是","children":[]},{"level":3,"title":"137. 如何产生带有正方形项目的列表","slug":"_137-如何产生带有正方形项目的列表","link":"#_137-如何产生带有正方形项目的列表","children":[]},{"level":3,"title":"138. 手写一个三栏布局,要求:垂直三栏布局,所有两栏宽度固定,中间自适应","slug":"_138-手写一个三栏布局-要求-垂直三栏布局-所有两栏宽度固定-中间自适应","link":"#_138-手写一个三栏布局-要求-垂直三栏布局-所有两栏宽度固定-中间自适应","children":[]},{"level":3,"title":"css 如何做一个 loading 动画(关键帧、动画循环)","slug":"css-如何做一个-loading-动画-关键帧、动画循环","link":"#css-如何做一个-loading-动画-关键帧、动画循环","children":[]},{"level":3,"title":"left 和 margin-left 哪性能好","slug":"left-和-margin-left-哪性能好","link":"#left-和-margin-left-哪性能好","children":[]},{"level":3,"title":"心跳","slug":"心跳","link":"#心跳","children":[]},{"level":3,"title":"如何写一个移动端 html 抖音界面和刷视频的功能实现 【手撕代码】","slug":"如何写一个移动端-html-抖音界面和刷视频的功能实现-【手撕代码】","link":"#如何写一个移动端-html-抖音界面和刷视频的功能实现-【手撕代码】","children":[]},{"level":3,"title":"flex 骰子","slug":"flex-骰子","link":"#flex-骰子","children":[]},{"level":3,"title":"常见的 CSS 布局单位","slug":"常见的-css-布局单位","link":"#常见的-css-布局单位","children":[]},{"level":3,"title":"img 的 style 里面有 important 怎么覆盖掉","slug":"img-的-style-里面有-important-怎么覆盖掉","link":"#img-的-style-里面有-important-怎么覆盖掉","children":[]},{"level":3,"title":"实现一个布局 1+(2,3,4(4 是在 3 的右上角)flex","slug":"实现一个布局-1-2-3-4-4-是在-3-的右上角-flex","link":"#实现一个布局-1-2-3-4-4-是在-3-的右上角-flex","children":[]},{"level":3,"title":"移动端的点击事件的有延迟,时间是多久,为什么会有? 怎么解决这个 延时?","slug":"移动端的点击事件的有延迟-时间是多久-为什么会有-怎么解决这个-延时","link":"#移动端的点击事件的有延迟-时间是多久-为什么会有-怎么解决这个-延时","children":[]},{"level":3,"title":"一个 span 能不能改变长宽高。 --行元素不能改变宽高 假如 span 已经被 position 设置好了,能不能改变长宽高","slug":"一个-span-能不能改变长宽高。-行元素不能改变宽高-假如-span-已经被-position-设置好了-能不能改变长宽高","link":"#一个-span-能不能改变长宽高。-行元素不能改变宽高-假如-span-已经被-position-设置好了-能不能改变长宽高","children":[]},{"level":3,"title":"实现内容多时页面滑动,内容少时保持占据一页","slug":"实现内容多时页面滑动-内容少时保持占据一页","link":"#实现内容多时页面滑动-内容少时保持占据一页","children":[]},{"level":3,"title":"base64 优缺点","slug":"base64-优缺点","link":"#base64-优缺点","children":[]},{"level":3,"title":"用 font-weight 和 b 标签有什么区别","slug":"用-font-weight-和-b-标签有什么区别","link":"#用-font-weight-和-b-标签有什么区别","children":[]},{"level":3,"title":"移动端 fastclick 原理是什么","slug":"移动端-fastclick-原理是什么","link":"#移动端-fastclick-原理是什么","children":[]},{"level":3,"title":"grid 布局","slug":"grid-布局","link":"#grid-布局","children":[]},{"level":3,"title":"判断元素是否到达可视区域","slug":"判断元素是否到达可视区域","link":"#判断元素是否到达可视区域","children":[]},{"level":3,"title":"富文本编辑器,文本设置大小不同,怎么设置 line-height","slug":"富文本编辑器-文本设置大小不同-怎么设置-line-height","link":"#富文本编辑器-文本设置大小不同-怎么设置-line-height","children":[]},{"level":3,"title":"translate 可以设置 x y,如果右边也有一个元素,右边元素会不会被挤掉。","slug":"translate-可以设置-x-y-如果右边也有一个元素-右边元素会不会被挤掉。","link":"#translate-可以设置-x-y-如果右边也有一个元素-右边元素会不会被挤掉。","children":[]},{"level":3,"title":"图片尺寸不固定(可能比盒子大,小),盒子长宽固定,图片比盒子大,希望长宽达到 100%(等比缩放),比盒子小,希望垂直居中。两个 class,一个针对图片大,一个针对图片小,js 获取图片大小,更改 class","slug":"图片尺寸不固定-可能比盒子大-小-盒子长宽固定-图片比盒子大-希望长宽达到-100-等比缩放-比盒子小-希望垂直居中。两个-class-一个针对图片大-一个针对图片小-js-获取图片大小-更改-class","link":"#图片尺寸不固定-可能比盒子大-小-盒子长宽固定-图片比盒子大-希望长宽达到-100-等比缩放-比盒子小-希望垂直居中。两个-class-一个针对图片大-一个针对图片小-js-获取图片大小-更改-class","children":[]},{"level":3,"title":"rem 原理 ,rem 怎么计算出来的","slug":"rem-原理-rem-怎么计算出来的","link":"#rem-原理-rem-怎么计算出来的","children":[]},{"level":3,"title":"清除图片下方出现像素空白间隙","slug":"清除图片下方出现像素空白间隙","link":"#清除图片下方出现像素空白间隙","children":[]},{"level":3,"title":"说一下 SEO 优化","slug":"说一下-seo-优化","link":"#说一下-seo-优化","children":[]},{"level":3,"title":"浏览器乱码的原因是什么?如何解决?","slug":"浏览器乱码的原因是什么-如何解决","link":"#浏览器乱码的原因是什么-如何解决","children":[]},{"level":3,"title":"head 标签有什么作用,其中什么标签必不可少?","slug":"head-标签有什么作用-其中什么标签必不可少","link":"#head-标签有什么作用-其中什么标签必不可少","children":[]},{"level":3,"title":"对 line-height 的理解及其赋值方式","slug":"对-line-height-的理解及其赋值方式","link":"#对-line-height-的理解及其赋值方式","children":[]},{"level":3,"title":"z-index 属性在什么情况下会失效","slug":"z-index-属性在什么情况下会失效","link":"#z-index-属性在什么情况下会失效","children":[]},{"level":3,"title":"z-index 工作原理和适用范围","slug":"z-index-工作原理和适用范围","link":"#z-index-工作原理和适用范围","children":[]},{"level":3,"title":"CSS 环形进度条","slug":"css-环形进度条","link":"#css-环形进度条","children":[]},{"level":3,"title":"不考虑其它因素,下面哪种的渲染性能比较高?","slug":"不考虑其它因素-下面哪种的渲染性能比较高","link":"#不考虑其它因素-下面哪种的渲染性能比较高","children":[]},{"level":3,"title":"script 标签中 defer 和 async 的区别","slug":"script-标签中-defer-和-async-的区别","link":"#script-标签中-defer-和-async-的区别","children":[]},{"level":3,"title":"10. HTML5 的离线储存怎么使用,它的工作原理是什么","slug":"_10-html5-的离线储存怎么使用-它的工作原理是什么","link":"#_10-html5-的离线储存怎么使用-它的工作原理是什么","children":[]},{"level":3,"title":"浏览器是如何对 HTML5 的离线储存资源进行管理和加载?","slug":"浏览器是如何对-html5-的离线储存资源进行管理和加载","link":"#浏览器是如何对-html5-的离线储存资源进行管理和加载","children":[]}]},{"level":2,"title":"CSS 自适应正方形","slug":"css-自适应正方形","link":"#css-自适应正方形","children":[]},{"level":2,"title":"css3 transform 变换顺序考察","slug":"css3-transform-变换顺序考察","link":"#css3-transform-变换顺序考察","children":[]},{"level":2,"title":"css 单位的百分比","slug":"css-单位的百分比","link":"#css-单位的百分比","children":[]},{"level":2,"title":"CSS3 新增伪类有那些?","slug":"css3-新增伪类有那些","link":"#css3-新增伪类有那些","children":[]},{"level":2,"title":"请问 span 标签设置宽高有效吗?设置 margin、padding 有效吗?","slug":"请问-span-标签设置宽高有效吗-设置-margin、padding-有效吗","link":"#请问-span-标签设置宽高有效吗-设置-margin、padding-有效吗","children":[]},{"level":2,"title":"弹性盒子中 flex: 0 1 auto 表示什么意思?","slug":"弹性盒子中-flex-0-1-auto-表示什么意思","link":"#弹性盒子中-flex-0-1-auto-表示什么意思","children":[]},{"level":2,"title":"请简述响应式设计解决方案以及其应用","slug":"请简述响应式设计解决方案以及其应用","link":"#请简述响应式设计解决方案以及其应用","children":[]},{"level":2,"title":"inline-block 元素间隙处理","slug":"inline-block-元素间隙处理","link":"#inline-block-元素间隙处理","children":[]},{"level":2,"title":"line-height 取值","slug":"line-height-取值","link":"#line-height-取值","children":[]},{"level":2,"title":"【CSS】1rem、1em、1vh、1px 这几个值的实际意义","slug":"【css】1rem、1em、1vh、1px-这几个值的实际意义","link":"#【css】1rem、1em、1vh、1px-这几个值的实际意义","children":[]},{"level":2,"title":"css 百分比","slug":"css-百分比","link":"#css-百分比","children":[]},{"level":2,"title":"CSS 中 px、em、rem 的区别","slug":"css-中-px、em、rem-的区别","link":"#css-中-px、em、rem-的区别","children":[]},{"level":2,"title":"div 设置成 inline-block,有空隙是什么原因呢?如何解决","slug":"div-设置成-inline-block-有空隙是什么原因呢-如何解决","link":"#div-设置成-inline-block-有空隙是什么原因呢-如何解决","children":[]},{"level":2,"title":"css 中 z-index 属性考察","slug":"css-中-z-index-属性考察","link":"#css-中-z-index-属性考察","children":[]},{"level":2,"title":"css3 GPU 加速","slug":"css3-gpu-加速","link":"#css3-gpu-加速","children":[]},{"level":2,"title":"请问 HTML 中如何通过
   
   
-    
Skip to content
On this page

常用命令

git merge 和 git rebase

git pull 和 git fetch

创建 SSH Key

shell
$ ssh-keygen -t rsa -C "youremail@example.com"
+    
Skip to content
On this page

常用命令

git merge 和 git rebase

git pull 和 git fetch

创建 SSH Key

shell
$ ssh-keygen -t rsa -C "youremail@example.com"
 
$ ssh-keygen -t rsa -C "youremail@example.com"
 

测试 key 是否配置成功

shell
$ ssh -T git@gitee.com
 
$ ssh -T git@gitee.com
@@ -197,9 +197,9 @@
 6. git commit -m '内容提示'
 7. git push origin first
 切回主分支:git checkout master
-

参考资料

local(每一个人的计算机上面也有一个项目仓库)

配置用户名

git config --global user.name 你的名字

配置邮箱

git config --global user.emial 你的邮箱

查看配置

  • git config --list

  • git config user.name

  • git config user.email

- - +

参考资料

local(每一个人的计算机上面也有一个项目仓库)

配置用户名

git config --global user.name 你的名字

配置邮箱

git config --global user.emial 你的邮箱

查看配置

  • git config --list

  • git config user.name

  • git config user.email

+ + \ No newline at end of file diff --git "a/fe-utils/js\345\267\245\345\205\267\345\272\223.html" "b/fe-utils/js\345\267\245\345\205\267\345\272\223.html" index 4bd6d5fc..ef4d14a0 100644 --- "a/fe-utils/js\345\267\245\345\205\267\345\272\223.html" +++ "b/fe-utils/js\345\267\245\345\205\267\345\272\223.html" @@ -6,13 +6,13 @@ 前端 JavaScript 必会工具库合集 | Sunny's blog - - + + -
Skip to content
On this page

前端 JavaScript 必会工具库合集

工具库集合

jQuery: 让操作DOM变得更容易

官网:https://jquery.com/

中文网:https://jquery.cuishifeng.cn/

Lodash: 你能想到的工具函数它都帮你写了

官网:https://lodash.com/docs

中文网:https://www.lodashjs.com/

Animate.css: 常见的CSS动画效果都帮你写好了

官网:https://animate.style/

Axios:让网络请求变得更简单

官网:https://axios-http.com/zh/

MockJS:ajax拦截和模拟数据生成

官网:http://mockjs.com/

Moment:让日期处理更容易

官网:https://momentjs.com/

中文网:http://momentjs.cn/

ECharts:搞定所有你能想到的图表📈

官网:https://echarts.apache.org/zh

animejs:简单好用的JS动画库

官网:https://animejs.com/

editormd:markdown编辑器

官网:https://pandao.github.io/editor.md | |

validate:简单好用的JS对象验证库

官网:http://validatejs.org/

date-fns:功能和Moment几乎相同

官网:https://date-fns.org/

支持tree shaking

zepto:功能和jQuery几乎相同

官网:https://zeptojs.com/

对移动端支持更好,包体积更小

nprogress:简单好用的进度条插件YouTube就使用的是它

官网:https://github.com/rstacruz/nprogress

qs:一个用于解析url的小工具

官网:https://github.com/ljharb/qs

使用方式

对于第三方库,除了下载使用,还可以通过CDN在线使用

科普知识:CDN

CDN称之为内容分发网络(Content Delivery Network)。

简单来说,就是提供很多的服务器,用户访问时,自动就近选择服务器给用户提供资源

国内使用广泛的免费CDN站点:https://www.bootcdn.cn/

JQuery

官网:https://jquery.com/

中文网:https://jquery.cuishifeng.cn/

CDN:https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js

针对DOM的操作无非以下几种:

  • 获取它
  • 创建它
  • 监听它
  • 改变它

JQuery可以让上面整个过程更加轻松

jQuery函数

jQuery提供了一个函数,名称为jQuery,也可以写作$

该函数提供了强大的DOM控制能力

通过下面的示例,可以快速理解jQuery的核心功能

javascript
// 获取类样式为container的所有DOM
+    
Skip to content
On this page

前端 JavaScript 必会工具库合集

工具库集合

jQuery: 让操作DOM变得更容易

官网:https://jquery.com/

中文网:https://jquery.cuishifeng.cn/

Lodash: 你能想到的工具函数它都帮你写了

官网:https://lodash.com/docs

中文网:https://www.lodashjs.com/

Animate.css: 常见的CSS动画效果都帮你写好了

官网:https://animate.style/

Axios:让网络请求变得更简单

官网:https://axios-http.com/zh/

MockJS:ajax拦截和模拟数据生成

官网:http://mockjs.com/

Moment:让日期处理更容易

官网:https://momentjs.com/

中文网:http://momentjs.cn/

ECharts:搞定所有你能想到的图表📈

官网:https://echarts.apache.org/zh

animejs:简单好用的JS动画库

官网:https://animejs.com/

editormd:markdown编辑器

官网:https://pandao.github.io/editor.md | |

validate:简单好用的JS对象验证库

官网:http://validatejs.org/

date-fns:功能和Moment几乎相同

官网:https://date-fns.org/

支持tree shaking

zepto:功能和jQuery几乎相同

官网:https://zeptojs.com/

对移动端支持更好,包体积更小

nprogress:简单好用的进度条插件YouTube就使用的是它

官网:https://github.com/rstacruz/nprogress

qs:一个用于解析url的小工具

官网:https://github.com/ljharb/qs

使用方式

对于第三方库,除了下载使用,还可以通过CDN在线使用

科普知识:CDN

CDN称之为内容分发网络(Content Delivery Network)。

简单来说,就是提供很多的服务器,用户访问时,自动就近选择服务器给用户提供资源

国内使用广泛的免费CDN站点:https://www.bootcdn.cn/

JQuery

官网:https://jquery.com/

中文网:https://jquery.cuishifeng.cn/

CDN:https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js

针对DOM的操作无非以下几种:

  • 获取它
  • 创建它
  • 监听它
  • 改变它

JQuery可以让上面整个过程更加轻松

jQuery函数

jQuery提供了一个函数,名称为jQuery,也可以写作$

该函数提供了强大的DOM控制能力

通过下面的示例,可以快速理解jQuery的核心功能

javascript
// 获取类样式为container的所有DOM
 const container = $(".container")
 
 // 获取container后面的兄弟元素,元素类样式必须包含list
@@ -291,8 +291,8 @@
 例如:
 2020-08-27T08:01:44.000Z
 

GMT、UTC、ISO 8601都表示的是零时区的时间

Unix 时间戳

Unix 时间戳(Unix Timestamp)是Unix系统最早提出的概念

它将UTC时间1970年1月1日凌晨作为起始时间,到指定时间经过的秒数(毫秒数)

程序中的时间处理

程序对时间的计算、存储务必使用UTC时间,或者时间戳

在和用户交互时,将UTC时间或时间戳转换为更加友好的文本

思考下面的问题:

  1. 用户的生日是本地时间还是UTC时间?
  2. 如果要比较两个日期的大小,是比较本地时间还是比较UTC时间?
  3. 如果要显示文章的发布日期,是显示本地时间还是显示UTC时间?
  4. 北京时间2020-8-28 10:00:00格林威治2020-8-28 02:00:00,两个时间哪个大,哪个小?
  5. 北京的时间戳为0格林威治的时间戳为0,它们的时间一样吗?
  6. 一个中国用户注册时填写的生日是1970-1-1,它出生的UTC时间是多少?时间戳是多少?

Moment的核心用法

Moment的使用分为两个部分:

  1. 获得Moment对象
  2. 针对Moment对象做各种操作
- - + + \ No newline at end of file diff --git a/fe-utils/setTimeout.html b/fe-utils/setTimeout.html deleted file mode 100644 index 7aa35807..00000000 --- a/fe-utils/setTimeout.html +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - 如何实现准时的 setTimeout | Sunny's blog - - - - - - - - -
Skip to content
On this page

如何实现准时的 setTimeout

为什么不准?

因为 setTimeout 是一个宏任务,它的指定时间指的是: 进入主线程的时间。

js
setTimeout(callback, 进入主线程的时间);
-
setTimeout(callback, 进入主线程的时间);
-

所以什么时候可以执行 callback,需要看 主线程前面还有多少任务待执行 。

如何实现准时的 “setTimeout”

requestAnimationFrame

window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。

该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行,回调函数执行次数通常是每秒 60 次,也就是每 16.7ms 执行一次,但是并不一定保证为 16.7 ms。

js
// 模拟代码
-function setTimeout(cb, delay) {
-  let startTime = Date.now();
-  loop();
-
-  function loop() {
-    const now = Date.now();
-    if (now - startTime >= delay) {
-      cb();
-      return;
-    }
-    requestAnimationFrame(loop);
-  }
-}
-
// 模拟代码
-function setTimeout(cb, delay) {
-  let startTime = Date.now();
-  loop();
-
-  function loop() {
-    const now = Date.now();
-    if (now - startTime >= delay) {
-      cb();
-      return;
-    }
-    requestAnimationFrame(loop);
-  }
-}
-

由于 16.7 ms 间隔执行,在使用间隔很小的定时器,很容易导致时间的不准确。因此这种方案仍然不是一种好的方案。

while

想得到准确的,我们第一反应就是如果我们能够主动去触发,获取到最开始的时间,以及不断去轮询当前时间,如果差值是预期的时间,那么这个定时器肯定是准确的,那么用 while 可以实现这个功能。

js
function timer(time) {
-  const startTime = Date.now();
-  while (true) {
-    const now = Date.now();
-    if (now - startTime >= time) {
-      console.log("误差", now - startTime - time);
-      return;
-    }
-  }
-}
-timer(5000);
-
function timer(time) {
-  const startTime = Date.now();
-  while (true) {
-    const now = Date.now();
-    if (now - startTime >= time) {
-      console.log("误差", now - startTime - time);
-      return;
-    }
-  }
-}
-timer(5000);
-

这样的方式很精确,但是我们知道 js 是单线程运行,使用这样的方式强行霸占线程会使得页面进入卡死状态,这样的结果显然是不合适的。

setTimeout 系统时间补偿

我们来看看此方案和原方案的区别

原方案:

setTimeout 系统时间补偿:

当每一次定时器执行时后,都去获取系统的时间来进行修正,虽然每次运行可能会有误差,但是通过系统时间对每次运行的修复,能够让后面每一次时间都得到一个补偿。

js
function timer() {
-  let speed = 500,
-    counter = 1,
-    start = new Date().getTime();
-
-  function instance() {
-    let real = counter * speed,
-      ideal = new Date().getTime() - start;
-
-    counter++;
-    let diff = ideal - real;
-    setTimeout(function () {
-      instance();
-    }, speed - diff); // 通过系统时间进行修复
-  }
-
-  setTimeout(function () {
-    instance();
-  }, speed);
-}
-
function timer() {
-  let speed = 500,
-    counter = 1,
-    start = new Date().getTime();
-
-  function instance() {
-    let real = counter * speed,
-      ideal = new Date().getTime() - start;
-
-    counter++;
-    let diff = ideal - real;
-    setTimeout(function () {
-      instance();
-    }, speed - diff); // 通过系统时间进行修复
-  }
-
-  setTimeout(function () {
-    instance();
-  }, speed);
-}
-
- - - - - \ No newline at end of file diff --git a/fe-utils/tool.html b/fe-utils/tool.html index ddfbc73b..ad8e5903 100644 --- a/fe-utils/tool.html +++ b/fe-utils/tool.html @@ -6,15 +6,15 @@ 涌现出来的新tools | Sunny's blog - - + + -
Skip to content
On this page

涌现出来的新tools

apifox:一款国产的 API 管理神器

集成了 Postman + Swagger + Mock + JMeter 众多功能

可以让生成 api 文档、api调试、api Mock、api 自动化测试 变的十分高效,简单。

eolink | 国内第一个集Swagger+Postman+Mock+Jmeter单点工具于一身的 api 管理平台 | 以 api 为中心的前后端开发流程

- - +
Skip to content
On this page

涌现出来的新tools

apifox:一款国产的 API 管理神器

集成了 Postman + Swagger + Mock + JMeter 众多功能

可以让生成 api 文档、api调试、api Mock、api 自动化测试 变的十分高效,简单。

eolink | 国内第一个集Swagger+Postman+Mock+Jmeter单点工具于一身的 api 管理平台 | 以 api 为中心的前后端开发流程

+ + \ No newline at end of file diff --git a/fragment/Monorepo.html b/fragment/Monorepo.html new file mode 100644 index 00000000..4083515e --- /dev/null +++ b/fragment/Monorepo.html @@ -0,0 +1,20 @@ + + + + + + monorepo | Sunny's blog + + + + + + + + +
Skip to content
On this page

monorepo

npm 的 install 流程

package-lock.json

package-lock.json 的作用是进行锁版本号,保证整个开发团队的版本号统一,使用 monorepo 的项目有可能会提到一个最外层进行一个管理。

  • 为什么需要这个 package-lock.json package.json 的 semantic versioning(语意化版本控制),在不同的时间会安装不同的版本,如果没有 package-lock.json,不同的开发者可能就会得到不同版本的依赖,如果因为这个出现了一个 bug 的话,那排查起来也许会非常困难。
  • 更新规则 Npm v 5.4.2 以上:当 package.json 声明的版本依赖规范和 package-lock.json 安装版本兼容,则根据 package-lock json 安装依赖:如果两者不兼容,那么按照 package.json 安装依赖,并更新 package- lock.json 跟随 package.json 的语意化版本控制来进行更新,如果 package.json 中的依赖 a 的版本是^1.0.0,在这个时候如果 package-lock.json 中的版本锁定为 1.12.1 就是符合要求的不必重写,但如果是 0.12.11 即第一个数字变了,那就需要重写 package-lock.json 了。
  • 版本规则
    • ^: 只会执行不更改最左边非零数字的更新。 如果写入的是 ^0.13.0,则当运行 npm update 时,可以更新到 0.13.1、0.13.2 等,但不能更新到 0.14.0 或更高版本。 如果写入的是 ^1.13.0,则当运行 npm update 时,可以更新到 1.13.1、1.14.0 等,但不能更新到 2.0.0 或更高版本。
    • ~: 如果写入的是 〜0.13.0,则当运行 npm update 时,会更新到补丁版本:即 0.13.1 可以,但 0.14.0 不可以。
    • : 接受高于指定版本的任何版本。

    • =: 接受等于或高于指定版本的任何版本。

    • =: 接受确切的版本。
    • -: 接受一定范围的版本。例如:2.1.0 - 2.6.2。
    • ||: 组合集合。例如 < 2.1 || > 2.6。

npm 和 yarn

缺点。下面将两者进行比较

  1. 性能

每当 Yarn 或 npm 需要安装包时,它们都会执行一系列任务。在 npm 中,这些任务是按包顺序执行的,这意味着它会等待一个包完全安装,然后再继续下一个。相比之下,Yarn 并行执行这些任务,从而提高了性能。 虽然这两个管理器都提供缓存机制,但 Yarn 似乎做得更好一些。 尽管 Yarn 有一些优势,但 Yarn 和 npm 在它们的最新版本中的速度相当。所以我们不能评判孰优孰劣。

  1. 依赖版本

早期的时候 yarn 有 yarn.lock 来锁定版本,这一点上比 package.json 要强很多,而后面 npm 也推出了 package-lock.json,所以这一点上已经没太多差异了。

  1. 安全性

从版本 6 开始,npm 会在安装过程中审核软件包并告诉您是否发现了任何漏洞。我们可以通过 npm audit 针对已安装的软件包运行来手动执行此检查。如果发现任何漏洞,npm 会给我们安全建议。 Yarn 和 npm 都使用加密哈希算法来确保包的完整性。

  1. 工作区

工作区允许您拥有一个 monorepo 来管理跨多个项目的依赖项。这意味着您有一个单一的顶级根包,其中包含多个称为工作区的子包。

  1. 用哪个?

目前 2021 年,yarn 的安装速度还是比 npm 快,其他地方的差异并不大,基本上可以忽略,用哪个都行。

monorepo

monorepo 方案的优势

  1. 代码重用将变得非常容易:由于所有的项目代码都集中于一个代码仓库,我们将很容易抽离出各个项目共用的业务组件或工具,并通过 TypeScript,Lerna 或其他工具进行代码内引用;
  2. 依赖管理将变得非常简单:同理,由于项目之间的引用路径内化在同一个仓库之中,我们很容易追踪当某个项目的代码修改后,会影响到其他哪些项目。通过使用一些工具,我们将很容易地做到版本依赖管理和版本号自动升级;
  3. 代码重构将变得非常便捷:想想究竟是什么在阻止您进行代码重构,很多时候,原因来自于「不确定性」,您不确定对某个项目的修改是否对于其他项目而言是「致命的」,出于对未知的恐惧,您会倾向于不重构代码,这将导致整个项目代码的腐烂度会以惊人的速度增长。而在 monorepo 策略的指导下,您能够明确知道您的代码的影响范围,并且能够对被影响的项目可以进行统一的测试,这会鼓励您不断优化代码;
  4. 它倡导了一种开放,透明,共享的组织文化,这有利于开发者成长,代码质量的提升:在 monorepo 策略下,每个开发者都被鼓励去查看,修改他人的代码(只要有必要),同时,也会激起开发者维护代码,和编写单元测试的责任心(毕竟朋友来访之前,我们从不介意自己的房子究竟有多乱),这将会形成一种良性的技术氛围,从而保障整个组织的代码质量。

monorepo 方案的劣势

  1. 项目粒度的权限管理变得非常复杂:无论是 Git 还是其他 VCS 系统,在支持 monorepo 策略中项目粒度的权限管理上都没有令人满意的方案,这意味着 A 部门的 a 项目若是不想被 B 部门的开发者看到就很难了。(好在我们可以将 monorepo 策略实践在「项目级」这个层次上,这才是我们这篇文章的主题,我们后面会再次明确它);
  2. 新员工的学习成本变高:不同于一个项目一个代码仓库这种模式下,组织新人只要熟悉特定代码仓库下的代码逻辑,在 monorepo 策略下,新人可能不得不花更多精力来理清各个代码仓库之间的相互逻辑,当然这个成本可以通过新人文档的方式来解决,但维护文档的新鲜又需要消耗额外的人力;
  3. 对于公司级别的 monorepo 策略而言,需要专门的 VFS 系统,自动重构工具的支持:设想一下 Google 这样的企业是如何将十亿行的代码存储在一个仓库之中的?开发人员每次拉取代码需要等待多久?各个项目代码之间又如何实现权限管理,敏捷发布?任何简单的策略乘以足够的规模量级都会产生一个奇迹(不管是好是坏),对于中小企业而言,如果没有像 Google,Facebook 这样雄厚的人力资源,把所有项目代码放在同一个仓库里这个美好的愿望就只能是个空中楼阁。

如何取舍?

没错,软件开发领域从来没有「银弹」。monorepo 策略也并不完美,并且,我在实践中发现,要想完美在组织中运用 monorepo 策略,所需要的不仅是出色的编程技巧和耐心。团队日程,组织文化和个人影响力相互碰撞的最终结果才决定了想法最终是否能被实现。

但是请别灰心的太早,因为虽然让组织作出改变,统一施行 monorepo 策略困难重重,但这却并不意味着我们需要彻底跟 monorepo 策略说再见。我们还可以把 monorepo 策略实践在「项目」这个级别,即从逻辑上确定项目与项目之间的关联性,然后把相关联的项目整合在同一个仓库下,通常情况下,我们不会有太多相互关联的项目,这意味着我们能够免费得到 monorepo 策略的所有好处,并且可以拒绝支付大型 monorepo 架构的利息。

+ + + + + \ No newline at end of file diff --git a/fragment/babel-console.html b/fragment/babel-console.html new file mode 100644 index 00000000..7018dd87 --- /dev/null +++ b/fragment/babel-console.html @@ -0,0 +1,20 @@ + + + + + + 🔥 手撕 babel 插件-消灭 console! | Sunny's blog + + + + + + + + +
Skip to content
On this page
+ + + + + \ No newline at end of file diff --git a/fragment/setTimeout.html b/fragment/setTimeout.html new file mode 100644 index 00000000..8ba79d84 --- /dev/null +++ b/fragment/setTimeout.html @@ -0,0 +1,112 @@ + + + + + + 如何实现准时的 setTimeout | Sunny's blog + + + + + + + + +
Skip to content
On this page

如何实现准时的 setTimeout

为什么不准?

因为 setTimeout 是一个宏任务,它的指定时间指的是: 进入主线程的时间。

js
setTimeout(callback, 进入主线程的时间);
+
setTimeout(callback, 进入主线程的时间);
+

所以什么时候可以执行 callback,需要看 主线程前面还有多少任务待执行 。

如何实现准时的 “setTimeout”

requestAnimationFrame

window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。

该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行,回调函数执行次数通常是每秒 60 次,也就是每 16.7ms 执行一次,但是并不一定保证为 16.7 ms。

js
// 模拟代码
+function setTimeout(cb, delay) {
+  let startTime = Date.now();
+  loop();
+
+  function loop() {
+    const now = Date.now();
+    if (now - startTime >= delay) {
+      cb();
+      return;
+    }
+    requestAnimationFrame(loop);
+  }
+}
+
// 模拟代码
+function setTimeout(cb, delay) {
+  let startTime = Date.now();
+  loop();
+
+  function loop() {
+    const now = Date.now();
+    if (now - startTime >= delay) {
+      cb();
+      return;
+    }
+    requestAnimationFrame(loop);
+  }
+}
+

由于 16.7 ms 间隔执行,在使用间隔很小的定时器,很容易导致时间的不准确。因此这种方案仍然不是一种好的方案。

while

想得到准确的,我们第一反应就是如果我们能够主动去触发,获取到最开始的时间,以及不断去轮询当前时间,如果差值是预期的时间,那么这个定时器肯定是准确的,那么用 while 可以实现这个功能。

js
function timer(time) {
+  const startTime = Date.now();
+  while (true) {
+    const now = Date.now();
+    if (now - startTime >= time) {
+      console.log("误差", now - startTime - time);
+      return;
+    }
+  }
+}
+timer(5000);
+
function timer(time) {
+  const startTime = Date.now();
+  while (true) {
+    const now = Date.now();
+    if (now - startTime >= time) {
+      console.log("误差", now - startTime - time);
+      return;
+    }
+  }
+}
+timer(5000);
+

这样的方式很精确,但是我们知道 js 是单线程运行,使用这样的方式强行霸占线程会使得页面进入卡死状态,这样的结果显然是不合适的。

setTimeout 系统时间补偿

我们来看看此方案和原方案的区别

原方案:

setTimeout 系统时间补偿:

当每一次定时器执行时后,都去获取系统的时间来进行修正,虽然每次运行可能会有误差,但是通过系统时间对每次运行的修复,能够让后面每一次时间都得到一个补偿。

js
function timer() {
+  let speed = 500,
+    counter = 1,
+    start = new Date().getTime();
+
+  function instance() {
+    let real = counter * speed,
+      ideal = new Date().getTime() - start;
+
+    counter++;
+    let diff = ideal - real;
+    setTimeout(function () {
+      instance();
+    }, speed - diff); // 通过系统时间进行修复
+  }
+
+  setTimeout(function () {
+    instance();
+  }, speed);
+}
+
function timer() {
+  let speed = 500,
+    counter = 1,
+    start = new Date().getTime();
+
+  function instance() {
+    let real = counter * speed,
+      ideal = new Date().getTime() - start;
+
+    counter++;
+    let diff = ideal - real;
+    setTimeout(function () {
+      instance();
+    }, speed - diff); // 通过系统时间进行修复
+  }
+
+  setTimeout(function () {
+    instance();
+  }, speed);
+}
+
+ + + + + \ No newline at end of file diff --git "a/fragment/\345\276\256\345\206\205\346\240\270\346\236\266\346\236\204.html" "b/fragment/\345\276\256\345\206\205\346\240\270\346\236\266\346\236\204.html" new file mode 100644 index 00000000..df8c5f47 --- /dev/null +++ "b/fragment/\345\276\256\345\206\205\346\240\270\346\236\266\346\236\204.html" @@ -0,0 +1,292 @@ + + + + + + 🔥 微内核架构在前端的实现及其应用 | Sunny's blog + + + + + + + + +
Skip to content
On this page

🔥 微内核架构在前端的实现及其应用

前置知识

微内核架构,大白话讲就是插件系统,就像下面这张图:

从上图中可以看出,微内核架构主要由两部分组成:Core + plugin,也就是一个内核和多个插件。通常来说:

  • 内核主要负责一些基础功能、核心功能以及插件的管理;
  • 插件则是一个独立的功能模块,用来丰富和加强内核的能力。

内核和插件通常是解耦的,在不使用插件的情况下,内核也能够独立运行。看得出来这种架构拓展性极强,在前端主流框架中都能看到它的影子:

  • 你在用 vue 的时候,可以用 Vue.use()
  • webpack 用的一些 plugins
  • babel 用的一些 plugins
  • 浏览器插件
  • vscode 插件
  • koa 中间件(中间件也可以理解为插件)
  • 甚至是 jQuery 插件
  • ...

如何实现

话不多说,接下来就直接开撸,实现一个微内核系统。不过在做之前,我们要先明确要做什么东西,这里就以文档编辑器为例子吧,每篇文档由多个 Block 区块组成,每个 Block 又可以展示不同的内容(列表、图片、图表等),这种情况下,我们要想扩展文档没有的功能(比如脑图),就很适合用插件系统做啦!

如何调用

通常在开发之前,我们会先确定一下期望的调用方式,大抵应该是下面这个样子 👇🏻:

js
// 内核初始化
+const core = new Core();
+// 使用插件
+core.use(new Plugin1());
+core.use(new Plugin2());
+// 开始运行
+core.run();
+
// 内核初始化
+const core = new Core();
+// 使用插件
+core.use(new Plugin1());
+core.use(new Plugin2());
+// 开始运行
+core.run();
+

Core 和 Plugin 的实现

插件是围绕内核而生的,所以我们先来实现内核的部分吧,也就是 Core 类。通常内核有两个用途:一个是实现基础功能;一个是管理插件。基础功能要看具体使用场景,它是基于业务的,这里就略过了。我们的重点在插件,要想能够在内核中使用插件肯定是要在内核中开个口子的,最基本的问题就是如何注册、如何执行。一般来说插件的格式会长下面这个样子:

ts
interface IPlugin {
+  /** 插件名字 */
+  name: string;
+  /** 插件能力,通常是个函数,也可以叫 apply、exec、handle */
+  fn: Function;
+}
+
interface IPlugin {
+  /** 插件名字 */
+  name: string;
+  /** 插件能力,通常是个函数,也可以叫 apply、exec、handle */
+  fn: Function;
+}
+

再看看前面定义的调用方式, core.use() 就是向内核中注册插件了,而 core.run() 则是执行,也就是说 Core 中需要有 use 和 run 这两个方法,于是我们就能简单写出如下代码 👇🏻:

ts
/** 内核 Core :基础功能 + 插件调度器 */
+class Core {
+  pluginMap: Map<string, IPlugin> = new Map();
+  constructor() {
+    console.log("实现内核基础功能:文档初始化");
+  }
+  /** 插件注册,也可以叫 register,通常以注册表的形式实现,其实就是个对象映射 */
+  use(plugin: IPlugin): Core {
+    this.pluginMap.set(plugin.name, plugin);
+    return this; // 方便链式调用
+  }
+  /** 插件执行,也可以叫 start */
+  run() {
+    this.pluginMap.forEach((plugin) => {
+      plugin.fn();
+    });
+  }
+}
+class Plugin1 implements IPlugin {
+  name = "Block1";
+  fn() {
+    console.log("扩展文档功能:Block1");
+  }
+}
+class Plugin2 implements IPlugin {
+  name = "Block2";
+  fn() {
+    console.log("扩展文档功能:Block2");
+  }
+}
+// 运行结果如下:
+// 实现内核基础功能:文档初始化
+// 扩展文档功能:Block1
+// 扩展文档功能:Block2
+
/** 内核 Core :基础功能 + 插件调度器 */
+class Core {
+  pluginMap: Map<string, IPlugin> = new Map();
+  constructor() {
+    console.log("实现内核基础功能:文档初始化");
+  }
+  /** 插件注册,也可以叫 register,通常以注册表的形式实现,其实就是个对象映射 */
+  use(plugin: IPlugin): Core {
+    this.pluginMap.set(plugin.name, plugin);
+    return this; // 方便链式调用
+  }
+  /** 插件执行,也可以叫 start */
+  run() {
+    this.pluginMap.forEach((plugin) => {
+      plugin.fn();
+    });
+  }
+}
+class Plugin1 implements IPlugin {
+  name = "Block1";
+  fn() {
+    console.log("扩展文档功能:Block1");
+  }
+}
+class Plugin2 implements IPlugin {
+  name = "Block2";
+  fn() {
+    console.log("扩展文档功能:Block2");
+  }
+}
+// 运行结果如下:
+// 实现内核基础功能:文档初始化
+// 扩展文档功能:Block1
+// 扩展文档功能:Block2
+

设计思想

一个好的架构肯定是能够体现一些设计模式思想的:

  • 单一职责:每个插件相互独立,只负责其对应的功能模块,也方便进行单独测试。如果把功能点都写在内核里,就容易造成高耦合。
  • 开放封闭:也就是我们扩展某个功能,不会去修改老代码,每个插件的接入成本是一样的,我们也不用关心内核是怎么实现的,只要知道它暴露了什么 api,会用就行。
  • 策略模式:比如我们在渲染文档的某个 Block 区块时,需要根据不同 Block 类型(插件名、策略名)去执行不同的渲染方法(插件方法、策略),当然了,当前的代码体现的可能不是很明显。
  • 还有一个就是控制反转:这个暂时还没体现,但是下文会提到,简单来说就是通过依赖注入实现控制权的反转

重点问题

通讯问题

前面我们说道,内核和插件是解耦的,那如果我需要在插件中用到一些内核的功能,该怎么和内核通信呢?插件与插件之间又该怎么通信呢?别急,先来解决第一个问题,这个很简单,既然插件要用内核的东西,那我把内核暴露给插件不就好了,就像下面这样 👇🏻:

ts
interface IPlugin {
+  /** 插件名字 */
+  name: string;
+  /** 插件能力:这个 ctx 就是内核 */
+  fn(ctx: Core): void;
+}
+class Core {
+  run() {
+    this.pluginMap.forEach((plugin) => {
+      plugin.fn(this); // 注意这里,我们把 this 传递了进去
+    });
+  }
+}
+
interface IPlugin {
+  /** 插件名字 */
+  name: string;
+  /** 插件能力:这个 ctx 就是内核 */
+  fn(ctx: Core): void;
+}
+class Core {
+  run() {
+    this.pluginMap.forEach((plugin) => {
+      plugin.fn(this); // 注意这里,我们把 this 传递了进去
+    });
+  }
+}
+

这样一来我们就能在插件中获取 Core 实例了(也可以叫做上下文),相应的就能够用内核的一些东西了。比如内核中通常会有一些系统基础配置信息(isDev、platform、version 等),那我们在插件中就能根据不同环境、不同平台、不同版本进行进一步处理,甚至是更改内核的内容。 这个暴露内核的过程其实就是前文提到的控制反转,什么意思呢?比如通常情况下我们要在一个类中要使用另外一个类,你会怎么做呢?是不是会直接在这个类中直接实例化另外一个类,这个就是正常的控制权。现在呢,我们虽然需要在内核中使用插件,但是我们把内核实例暴露给了插件,变成了在插件中使用内核,注意,此时主动权已经在插件这里了,这就是通过依赖注入实现了控制反转,可以好仔体会一下 🤯。 这种方式虽然我们能够引用和修改到内核的一些信息,但是好像不能干预内核初始化和执行的过程,要是想在内核初始化和执行过程中做一些处理该怎么办呢?我们可以引入事件机制,也就是发布订阅模式,在内核执行过程中抛出一些事件,然后由插件去监听相应的事件,我们可以叫 events,也可以叫 hooks(webpack 里面就是 hooks),具体实现就像下面这样:

js
import { EventEmitter } from "events"; // webpack 中使用 tapable,很强大的一个发布订阅库
+
+class Core {
+  events: EventEmitter = new EventEmitter(); // 也可以叫 hooks,就是发布订阅
+  constructor() {
+    this.events.emit("beforeInit");
+    console.log("实现内核基础功能:文档初始化");
+    this.events.emit("afterInit");
+  }
+  run() {
+    this.events.emit("before all plugins");
+    this.pluginMap.forEach((plugin) => {
+      plugin.fn(this);
+    });
+    this.events.emit("after all plugins");
+  }
+}
+// 插件中可以这样调用:this.events.on('xxxx');
+
import { EventEmitter } from "events"; // webpack 中使用 tapable,很强大的一个发布订阅库
+
+class Core {
+  events: EventEmitter = new EventEmitter(); // 也可以叫 hooks,就是发布订阅
+  constructor() {
+    this.events.emit("beforeInit");
+    console.log("实现内核基础功能:文档初始化");
+    this.events.emit("afterInit");
+  }
+  run() {
+    this.events.emit("before all plugins");
+    this.pluginMap.forEach((plugin) => {
+      plugin.fn(this);
+    });
+    this.events.emit("after all plugins");
+  }
+}
+// 插件中可以这样调用:this.events.on('xxxx');
+

我们也可以改成生命周期的形式,比如:

ts
class Core {
+  constructor(opts?: IOption) {
+    opts?.beforeCreate();
+    console.log("实现内核基础功能:文档初始化");
+    opts?.afterCreate();
+  }
+}
+
class Core {
+  constructor(opts?: IOption) {
+    opts?.beforeCreate();
+    console.log("实现内核基础功能:文档初始化");
+    opts?.afterCreate();
+  }
+}
+

这就是生命周期钩子。除了内核本身的生命周期之外,插件也可以有,一般分为加载、运行和卸载三部分,道理类似。当然这里要注意上面两种通信方式的区别, hooks 侧重的是时机,它会在特定阶段触发,而 ctx 则侧重于共享信息的传递。 至于插件和插件的通信,通常来说在这个模式中使用相互影响的插件是不友好的,也不建议这么做,但是如果一定需要的话可以通过内核和一些 hooks 来做桥接。

插件的调度

我们思考一个问题,插件的加载顺序对内核有影响吗?多个插件之间应该如何运行,它们是怎样的关系?看看我们的代码是直接 forEach 遍历执行的,这样的对吗?此处可以停下来思考几秒种 🤔。

通常来说多个插件有以下几种运行方式:

具体采用哪种方式就要看我们的具体业务场景了,比如我们的业务是文档编辑器,插件主要用于扩展文档的 Block 功能,所以顺序不是很重要,相当于上面的第三种模式,主要就是加强功能。那管道式呢,管道式的特点就是上一步的输入是下一步的输出,也就是管道流,这种就是对顺序有要求的,改变插件的顺序,结果也是不一样的,比如我们的业务核心是处理数据,input 就是原始数据,中间的 plugin 就是各种数据处理步骤(比如采样、均化、归一等),output 就是最终处理后的结果;又比如构建工具 gulp 也是如此,有兴趣的可以自行了解下。最后是洋葱式,这个最典型的应用场景就是 koa 中间件了,每个路由都会经过层层中间件,又层层返回;还有 babel 遍历过程中访问器 vistor 的进出过程也是如此。了解上图的三种模式你就大概能知道何时何地加载、运行和销毁插件了。 那我们除了用 use 来使用插件外,还可以用什么方式来注册插件呢?可以用声明式注入,也就是通过配置文件来告诉系统应该去哪里去取什么插件,系统运行时会按照约定的配置去加载对应的插件。比如 babel 就可以通过在配置文件中填写插件名称,运行时就会去自行查找对应的插件并加载(可以想想我们平时开发时的各种 config.js 配置文件)。而编程式的就是系统提供某种注册 api,开发者通过将插件传入该 api 中来完成注册,也就是本文使用的方式。 另外我们再来说个小问题,就是插件的管理,也就是插件池,有时我们会用 map,有时我们会用数组,就像这样:

js
// map: {'pluginName1': plugin1, 'pluginName2': plugin2, ...}
+// 数组:[new Plugin1(), new Plugin2(), ...]
+
// map: {'pluginName1': plugin1, 'pluginName2': plugin2, ...}
+// 数组:[new Plugin1(), new Plugin2(), ...]
+

这两种用法在日常开发中也很常见,那它们有什么区别呢?这就要看你要求了,用 map 一般都是要具名的,目的是为了存取方便,尤其是取,就像缓存的感觉;而如果我们只需要单纯的遍历执行插件用数组就可以了,当然如果复杂度较高的情况下,可以两者一起使用,就像下面这样 👇🏻:

ts
class Core {
+  plugins: IPlugin[] = [];
+  pluginMap: Map<string, IPlugin> = new Map();
+  use(plugin: IPlugin): Core {
+    this.plugins.push(plugin);
+    this.pluginMap.set(plugin.name, plugin);
+    return this;
+  }
+}
+
class Core {
+  plugins: IPlugin[] = [];
+  pluginMap: Map<string, IPlugin> = new Map();
+  use(plugin: IPlugin): Core {
+    this.plugins.push(plugin);
+    this.pluginMap.set(plugin.name, plugin);
+    return this;
+  }
+}
+

安全性和稳定性

因为这种架构的扩展性相当强,并且插件也有能力去修改内核,所以安全性和稳定性的问题也是必须要考虑的问题 😬。 为了保障稳定性,在插件运行异常时,我们应当进行相应的容错处理,由内核来捕获并继续往下执行,不能让系统轻易崩掉,可以给个警告或错误的提示,或者通过系统级事件通知内核 重启 或 关闭 该插件。 关于安全性,我们可以只暴露必要的信息给插件,而不是全部(整个内核),这是什么意思呢,其实就是要搞个简单的沙箱,在前端这边具体点就是用 with+proxy+白名单 来处理,node 用 vm 模块来处理,当然在浏览器中要想完全隔绝是很难的,不过已经有个提案的 api 在路上了,可以期待一下。

presets 预设

presets 这个字眼在前端中应该还是挺常见的,它其实是什么意思呢,中文我们叫预设,本质就是一些插件的集合,亦即 Core + presets(几个插件) ,内核可以有不同的预设,presets1、presets2 等等,每个 presets 就是几个不同插件的组合,主要用途就是方便,不用我们一个一个去引入去设置,比如 babel 的预设、vue 脚手架中的预设。当然除了预设之外,我们也可以将一些必备的插件固化为内置插件,这个度由开发者自己去把握。

应用场景

webpack

js
module.exports = {
+  plugins: [
+    // 用配置的方式注册插件
+    new webpack.ProgressPlugin(),
+    new HtmlWebpackPlugin({ template: "./src/index.html" }),
+  ],
+};
+
module.exports = {
+  plugins: [
+    // 用配置的方式注册插件
+    new webpack.ProgressPlugin(),
+    new HtmlWebpackPlugin({ template: "./src/index.html" }),
+  ],
+};
+
js
const pluginName = "ConsoleLogOnBuildWebpackPlugin";
+
+class ConsoleLogOnBuildWebpackPlugin {
+  apply(compiler) {
+    // apply就是插件的fn,compiler就是内核上下文
+    compiler.hooks.run.tap(pluginName, (compilation) => {
+      // hooks就是发布订阅
+      console.log("The webpack build process is starting!");
+    });
+  }
+}
+
+module.exports = ConsoleLogOnBuildWebpackPlugin;
+
const pluginName = "ConsoleLogOnBuildWebpackPlugin";
+
+class ConsoleLogOnBuildWebpackPlugin {
+  apply(compiler) {
+    // apply就是插件的fn,compiler就是内核上下文
+    compiler.hooks.run.tap(pluginName, (compilation) => {
+      // hooks就是发布订阅
+      console.log("The webpack build process is starting!");
+    });
+  }
+}
+
+module.exports = ConsoleLogOnBuildWebpackPlugin;
+

babel

配置文件方式注册插件

json
{
+  "plugins": ["babel-plugin-myPlugin", "@babel/plugin-transform-runtime"]
+}
+
{
+  "plugins": ["babel-plugin-myPlugin", "@babel/plugin-transform-runtime"]
+}
+

插件开发

js
export default function () {
+  return {
+    visitor: {
+      Identifier(path) {
+        // 插件的执行内容,看起来特殊一些,不过path可以理解为内核上下文
+        const name = path.node.name;
+        path.node.name = name.split("").reverse().join("");
+      },
+    },
+  };
+}
+
export default function () {
+  return {
+    visitor: {
+      Identifier(path) {
+        // 插件的执行内容,看起来特殊一些,不过path可以理解为内核上下文
+        const name = path.node.name;
+        path.node.name = name.split("").reverse().join("");
+      },
+    },
+  };
+}
+

Vue

js
const app = createApp();
+
+app.use(myPlugin, {});
+
const app = createApp();
+
+app.use(myPlugin, {});
+
js
const myPlugin = {
+  install(app, options) {},
+};
+
const myPlugin = {
+  install(app, options) {},
+};
+

类似 jQuery、window 从某种角度上来说也可以看做是内核,通过 $.fn() 或者在 prototype 中扩展方法也是一种插件的形式。

小结

最后我们再来巩固一下这篇文章的核心思想,微内核架构主要由两部分组成: Core + Plugin,其中: Core 需要具备的能力有:

  • 基础功能,视业务情况而定
  • 一些配置信息(环境变量、全局变量)
  • 插件管理:通信、调度、稳定性
  • 生命周期钩子 hooks:约束一些特定阶段,用较少的钩子尽可能覆盖大部分场景 plugin 应该具备的能力有:
  • 能被内核调用
  • 可以更改内核的一些东西
  • 相互独立
  • 插件一般先注册后干预执行,有需要再提供卸载功能 其实微内核架构的核心思想就是在系统内部预留一些入口,系统本身功能不变,但是通过这个入口可以集百家之长以丰富系统自身 🍺。

🔗 原文链接: https://juejin.cn/post/716307803160...

+ + + + + \ No newline at end of file diff --git a/front-end-engineering/2024-04-09-16-52-36.png b/front-end-engineering/2024-04-09-16-52-36.png new file mode 100644 index 00000000..3a2b1c8e Binary files /dev/null and b/front-end-engineering/2024-04-09-16-52-36.png differ diff --git a/front-end-engineering/2024-04-09-20-41-59.png b/front-end-engineering/2024-04-09-20-41-59.png new file mode 100644 index 00000000..58ca8600 Binary files /dev/null and b/front-end-engineering/2024-04-09-20-41-59.png differ diff --git "a/front-end-engineering/CSS\345\267\245\347\250\213\345\214\226.html" "b/front-end-engineering/CSS\345\267\245\347\250\213\345\214\226.html" index 1695ff81..48e3c57d 100644 --- "a/front-end-engineering/CSS\345\267\245\347\250\213\345\214\226.html" +++ "b/front-end-engineering/CSS\345\267\245\347\250\213\345\214\226.html" @@ -6,13 +6,13 @@ CSS工程化 | Sunny's blog - - + + -
Skip to content
On this page

CSS工程化

css的问题

类名冲突的问题

当你写一个css类的时候,你是写全局的类呢,还是写多个层级选择后的类呢?

你会发现,怎么都不好

  • 过深的层级不利于编写、阅读、压缩、复用
  • 过浅的层级容易导致类名冲突

一旦样式多起来,这个问题就会变得越发严重,其实归根结底,就是类名冲突不好解决的问题

重复样式

这种问题就更普遍了,一些重复的样式值总是不断的出现在css代码中,维护起来极其困难

比如,一个网站的颜色一般就那么几种:

  • primary
  • info
  • warn
  • error
  • success

如果有更多的颜色,都是从这些色调中自然变化得来,可以想象,这些颜色会到处充斥到诸如背景、文字、边框中,一旦要做颜色调整,是一个非常大的工程

css文件细分问题

在大型项目中,css也需要更细的拆分,这样有利于css代码的维护。

比如,有一个做轮播图的模块,它不仅需要依赖js功能,还需要依赖css样式,既然依赖的js功能仅关心轮播图,那css样式也应该仅关心轮播图,由此类推,不同的功能依赖不同的css样式、公共样式可以单独抽离,这样就形成了不同于过去的css文件结构:文件更多、拆分的更细

而同时,在真实的运行环境下,我们却希望文件越少越好,这种情况和JS遇到的情况是一致的

因此,对于css,也需要工程化管理

从另一个角度来说,css的工程化会遇到更多的挑战,因为css不像JS,它的语法本身经过这么多年并没有发生多少的变化(css3也仅仅是多了一些属性而已),对于css语法本身的改变也是一个工程化的课题

如何解决

这么多年来,官方一直没有提出方案来解决上述问题

一些第三方机构针对不同的问题,提出了自己的解决方案

解决类名冲突

一些第三方机构提出了一些方案来解决该问题,常见的解决方案如下:

命名约定

即提供一种命名的标准,来解决冲突,常见的标准有:

  • BEM
  • OOCSS
  • AMCSS
  • SMACSS
  • 其他

css in js

这种方案非常大胆,它觉得,css语言本身几乎无可救药了,干脆直接用js对象来表示样式,然后把样式直接应用到元素的style中

这样一来,css变成了一个一个的对象,就可以完全利用到js语言的优势,你可以:

  • 通过一个函数返回一个样式对象
  • 把公共的样式提取到公共模块中返回
  • 应用js的各种特性操作对象,比如:混合、提取、拆分
  • 更多的花样

这种方案在手机端的React Native中大行其道

css module

非常有趣和好用的css模块化方案,编写简单,绝对不重名

具体的课程中详细介绍

解决重复样式的问题

css in js

这种方案虽然可以利用js语言解决重复样式值的问题,但由于太过激进,很多习惯写css的开发者编写起来并不是很适应

预编译器

有些第三方搞出一套css语言的进化版来解决这个问题,它支持变量、函数等高级语法,然后经过编译器将其编译成为正常的css

这种方案特别像构建工具,不过它仅针对css

常见的预编译器支持的语言有:

  • less
  • sass

解决css文件细分问题

这一部分,就要依靠构建工具,例如webpack来解决了

利用一些loader或plugin来打包、合并、压缩css文件

利用webpack拆分css

要拆分css,就必须把css当成像js那样的模块;要把css当成模块,就必须有一个构建工具(webpack),它具备合并代码的能力

而webpack本身只能读取css文件的内容、将其当作JS代码进行分析,因此,会导致错误

于是,就必须有一个loader,能够将css代码转换为js代码

css-loader

css-loader的作用,就是将css代码转换为js代码

它的处理原理极其简单:将css代码作为字符串导出

例如:

css
.red{
+    
Skip to content
On this page

CSS工程化

css的问题

类名冲突的问题

当你写一个css类的时候,你是写全局的类呢,还是写多个层级选择后的类呢?

你会发现,怎么都不好

  • 过深的层级不利于编写、阅读、压缩、复用
  • 过浅的层级容易导致类名冲突

一旦样式多起来,这个问题就会变得越发严重,其实归根结底,就是类名冲突不好解决的问题

重复样式

这种问题就更普遍了,一些重复的样式值总是不断的出现在css代码中,维护起来极其困难

比如,一个网站的颜色一般就那么几种:

  • primary
  • info
  • warn
  • error
  • success

如果有更多的颜色,都是从这些色调中自然变化得来,可以想象,这些颜色会到处充斥到诸如背景、文字、边框中,一旦要做颜色调整,是一个非常大的工程

css文件细分问题

在大型项目中,css也需要更细的拆分,这样有利于css代码的维护。

比如,有一个做轮播图的模块,它不仅需要依赖js功能,还需要依赖css样式,既然依赖的js功能仅关心轮播图,那css样式也应该仅关心轮播图,由此类推,不同的功能依赖不同的css样式、公共样式可以单独抽离,这样就形成了不同于过去的css文件结构:文件更多、拆分的更细

而同时,在真实的运行环境下,我们却希望文件越少越好,这种情况和JS遇到的情况是一致的

因此,对于css,也需要工程化管理

从另一个角度来说,css的工程化会遇到更多的挑战,因为css不像JS,它的语法本身经过这么多年并没有发生多少的变化(css3也仅仅是多了一些属性而已),对于css语法本身的改变也是一个工程化的课题

如何解决

这么多年来,官方一直没有提出方案来解决上述问题

一些第三方机构针对不同的问题,提出了自己的解决方案

解决类名冲突

一些第三方机构提出了一些方案来解决该问题,常见的解决方案如下:

命名约定

即提供一种命名的标准,来解决冲突,常见的标准有:

  • BEM
  • OOCSS
  • AMCSS
  • SMACSS
  • 其他

css in js

这种方案非常大胆,它觉得,css语言本身几乎无可救药了,干脆直接用js对象来表示样式,然后把样式直接应用到元素的style中

这样一来,css变成了一个一个的对象,就可以完全利用到js语言的优势,你可以:

  • 通过一个函数返回一个样式对象
  • 把公共的样式提取到公共模块中返回
  • 应用js的各种特性操作对象,比如:混合、提取、拆分
  • 更多的花样

这种方案在手机端的React Native中大行其道

css module

非常有趣和好用的css模块化方案,编写简单,绝对不重名

具体的课程中详细介绍

解决重复样式的问题

css in js

这种方案虽然可以利用js语言解决重复样式值的问题,但由于太过激进,很多习惯写css的开发者编写起来并不是很适应

预编译器

有些第三方搞出一套css语言的进化版来解决这个问题,它支持变量、函数等高级语法,然后经过编译器将其编译成为正常的css

这种方案特别像构建工具,不过它仅针对css

常见的预编译器支持的语言有:

  • less
  • sass

解决css文件细分问题

这一部分,就要依靠构建工具,例如webpack来解决了

利用一些loader或plugin来打包、合并、压缩css文件

利用webpack拆分css

要拆分css,就必须把css当成像js那样的模块;要把css当成模块,就必须有一个构建工具(webpack),它具备合并代码的能力

而webpack本身只能读取css文件的内容、将其当作JS代码进行分析,因此,会导致错误

于是,就必须有一个loader,能够将css代码转换为js代码

css-loader

css-loader的作用,就是将css代码转换为js代码

它的处理原理极其简单:将css代码作为字符串导出

例如:

css
.red{
     color:"#f40";
 }
 
.red{
@@ -645,8 +645,8 @@
     ]
 }
 

配置生成的文件名

output.filename的含义一样,即根据chunk生成的样式文件名

配置生成的文件名,例如[name].[contenthash:5].css

默认情况下,每个chunk对应一个css文件

- - + + \ No newline at end of file diff --git a/front-end-engineering/PackageManager.html b/front-end-engineering/PackageManager.html index 21863ccf..6cc3e75c 100644 --- a/front-end-engineering/PackageManager.html +++ b/front-end-engineering/PackageManager.html @@ -6,13 +6,13 @@ 包管理器 | Sunny's blog - - + + -
Skip to content
On this page

包管理器

包管理工具概述

1.概念

模块(module)

通常以单个文件形式存在的功能片段,入口文件通常称之为入口模块主模块

库(library,简称 lib)

以一个或多个模块组成的完整功能块,为开发中某一方面的问题提供完整的解决方案

包(package)

包含元数据的库,这些元数据包括:名称、描述、git 主页、许可证协议、作者、依赖等等

2.背景

CommonJS 的出现,使 node 环境下的 JS 代码可以用模块更加细粒度的划分。一个类、一个函数、一个对象、一个配置等等均可以作为模块,这种细粒度的划分,是开发大型应用的基石。 为了解决在开发过程中遇到的常见问题,比如加密、提供常见的工具方法、模拟数据等等,一时间,在前端社区涌现了大量的第三方库。这些库使用 CommonJS 标准书写而成,非常容易使用。 然而,在下载使用这些第三方库的时候,遇到难以处理的问题:

  • 下载过程繁琐
    • 进入官网或 github 主页
    • 找到并下载相应的版本
    • 拷贝到工程的目录中
    • 如果遇到有同名的库,需要更改名称
  • 如果该库需要依赖其他库,还需要按照要求先下载其他库
  • 开发环境中安装的大量的库如何在生产环境中还原,又如何区分
  • 更新一个库极度麻烦
  • 自己开发的库,如何在下一次开发使用

以上问题,就是包管理工具要解决的问题

3.前端包管理器

几乎可以这样认为,前端所有的包管理器都是基于 npm 的,目前,npm 即是一个包管理器,也是其他包管理的基石 npm 全称为 node package manager,即 node 包管理器,它运行在 node 环境中,让开发者可以用简单的方式完成包的查找、安装、更新、卸载、上传等操作

npm 之所以要运行在 node 环境,而不是浏览器环境,根本原因是因为浏览器环境无法提供下载、删除、读取本地文件的功能。而 node 属于服务器环境,没有浏览器的种种限制,理论上可以完全掌控运行 node 的计算机。

npm 的出现,弥补了 node 没有包管理器的缺陷,于是很快,node 在安装文件中内置了 npm,当开发者安装好 node 之后,就自动安装了 npm,不仅如此,node 环境还专门为 npm 提供了良好的支持,使用 npm 下载的包更加方便了。

npm 由三部分组成:

  • registry:入口
    • 可以把它想象成一个庞大的数据库
    • 第三方库的开发者,将自己的库按照 npm 的规范,打包上传到数据库中
    • 使用者通过统一的地址下载第三方包
  • 官网:https://www.npmjs.com/
    • 查询包
    • 注册、登录、管理个人信息
  • CLI:command-line interface 命令行接口
    • 这一部分是本门课讲解的重点
    • 安装好 npm 后,通过 CLI 来使用 npm 的各种功能
    • vscode 里面 Ctrl+j

cmd 常用命令 换到 e 盘:e: 查看目录:dir vscode 查看 npm 的当前版本 输入 npm -v 或者 npm --version 注意空格

node 和 npm 是互相成就的,node 的出现让 npm 火了,npm 的火爆带动了大量的第三方库的发展,很多优秀的第三方库打包上传到了 npm,这些第三方库又为 node 带来了大量的用户

npm

包的安装

安装(install)即下载包 由于 npm 的官方 registry 服务器位于国外,可能受网速影响导致下载缓慢或失败。因此,安装好 npm 之后,需要重新设置 registry 的地址为国内地址。目前,淘宝 https://registry.npm.taobao.org 提供了国内的 registry 地址,先设置到该地址。设置方式为npm config set registry [https://registry.npm.taobao.org](https://registry.npm.taobao.org)。设置好后,通过命令npm config get registry进行检查

npm 安装一个包,分为两种安装方式:

  1. 本地安装
  2. 全局安装

1.本地安装

使用命令npm install 包名npm i 包名即可完成本地安装 终端 clear 是清除命令 本地安装的包出现在当前目录下的node_modules目录中

随着开发的进展,node_modules目录会变得异常庞大,目录下的内容不适合直接传输到生产环境,因此通常使用.gitignore文件忽略该目录中的内容 本地安装适用于绝大部分的包,它会在当前目录及其子目录中发挥作用 通常在项目的根目录中使用本地安装 安装一个包的时候,npm 会自动管理依赖,它会下载该包的依赖包到node_modules目录中 例如:react 如果本地安装的包带有 CLI,npm 会将它的 CLI 脚本文件放置到node_modules/.bin下,使用命令npx 命令名即可调用(npx mocha) 例如:mocha

演示 安装 jQuery:命令 npm install jquery

2.全局安装

全局安装的包放置在一个特殊的全局目录,该目录可以通过命令npm config get prefix查看 使用命令npm install --global 包名npm i -g 包名 重要:全局安装的包并非所有工程可用,它仅提供全局的 CLI 工具 大部分情况下,都不需要全局安装包,除非:

  1. 包的版本非常稳定,很少有大的更新
  2. 提供的 CLI 工具在各个工程中使用的非常频繁
  3. CLI 工具仅为开发环境提供支持,而非部署环境

包配置

前节补充:同时下载两个包:命令 npm i jquery loadsh

目前遇到的问题:

  1. 拷贝工程后如何还原?
  2. 如何区分开发依赖和生产依赖?
  3. 如果自身的项目也是一个包,如何描述包的信息

以上这些问题都需要通过包的配置文件解决

1.配置文件

npm 将每个使用 npm 的工程本身都看作是一个包,包的信息需要通过一个名称固定的配置文件来描述 配置文件的名称固定为:package.json 可以手动创建该文件,而更多的时候,是通过命令npm init创建的 配置文件中可以描述大量的信息,包括:

  • name:包的名称,该名称必须是英文单词字符,支持连接符
  • version:版本
    • 版本规范:主版本号.次版本号.补丁版本号
    • 主版本号:仅当程序发生了重大变化时才会增长,如新增了重要功能、新增了大量的 API、技术架构发生了重大变化
    • 次版本号:仅当程序发生了一些小变化时才会增长,如新增了一些小功能、新增了一些辅助型的 API
    • 补丁版本号:仅当解决了一些 bug 或 进行了一些局部优化时更新,如修复了某个函数的 bug、提升了某个函数的运行效率
  • description:包的描述
  • homepage:官网地址
  • author:包的作者,必须是有效的 npm 账户名,书写规范是 account <mail>,例如:zhangsan <zhangsan@gmail.com>,不正确的账号和邮箱可能导致发布包时失败
  • repository:包的仓储地址,通常指 git 或 svn 的地址,它是一个对象
    • type:仓储类型,git 或 svn
    • url:地址

命令:get remote -v

  • main:包的入口文件,使用包的人默认从该入口文件导入包的内容
  • keywords: 搜索关键字,发布包后,可以通过该数组中的关键字搜索到包

    一步到位简化配置 json 文件

使用npm init --yesnpm init -y可以在生成配置文件时自动填充默认配置 操作:创建一个英文文件夹,右键终端打开,输入npm init --yesnpm init -y

2.保存依赖关系

大部分时候,我们仅仅是开发项目,并不会把它打包发布出去,尽管如此,我们仍然需要 package.json 文件 package.json 文件最重要的作用,是记录当前工程的依赖

  • dependencies:生产环境的依赖包
  • devDependencies:仅开发环境的依赖包
json
"dependencies": {
+    
Skip to content
On this page

包管理器

包管理工具概述

1.概念

模块(module)

通常以单个文件形式存在的功能片段,入口文件通常称之为入口模块主模块

库(library,简称 lib)

以一个或多个模块组成的完整功能块,为开发中某一方面的问题提供完整的解决方案

包(package)

包含元数据的库,这些元数据包括:名称、描述、git 主页、许可证协议、作者、依赖等等

2.背景

CommonJS 的出现,使 node 环境下的 JS 代码可以用模块更加细粒度的划分。一个类、一个函数、一个对象、一个配置等等均可以作为模块,这种细粒度的划分,是开发大型应用的基石。 为了解决在开发过程中遇到的常见问题,比如加密、提供常见的工具方法、模拟数据等等,一时间,在前端社区涌现了大量的第三方库。这些库使用 CommonJS 标准书写而成,非常容易使用。 然而,在下载使用这些第三方库的时候,遇到难以处理的问题:

  • 下载过程繁琐
    • 进入官网或 github 主页
    • 找到并下载相应的版本
    • 拷贝到工程的目录中
    • 如果遇到有同名的库,需要更改名称
  • 如果该库需要依赖其他库,还需要按照要求先下载其他库
  • 开发环境中安装的大量的库如何在生产环境中还原,又如何区分
  • 更新一个库极度麻烦
  • 自己开发的库,如何在下一次开发使用

以上问题,就是包管理工具要解决的问题

3.前端包管理器

几乎可以这样认为,前端所有的包管理器都是基于 npm 的,目前,npm 即是一个包管理器,也是其他包管理的基石 npm 全称为 node package manager,即 node 包管理器,它运行在 node 环境中,让开发者可以用简单的方式完成包的查找、安装、更新、卸载、上传等操作

npm 之所以要运行在 node 环境,而不是浏览器环境,根本原因是因为浏览器环境无法提供下载、删除、读取本地文件的功能。而 node 属于服务器环境,没有浏览器的种种限制,理论上可以完全掌控运行 node 的计算机。

npm 的出现,弥补了 node 没有包管理器的缺陷,于是很快,node 在安装文件中内置了 npm,当开发者安装好 node 之后,就自动安装了 npm,不仅如此,node 环境还专门为 npm 提供了良好的支持,使用 npm 下载的包更加方便了。

npm 由三部分组成:

  • registry:入口
    • 可以把它想象成一个庞大的数据库
    • 第三方库的开发者,将自己的库按照 npm 的规范,打包上传到数据库中
    • 使用者通过统一的地址下载第三方包
  • 官网:https://www.npmjs.com/
    • 查询包
    • 注册、登录、管理个人信息
  • CLI:command-line interface 命令行接口
    • 这一部分是本门课讲解的重点
    • 安装好 npm 后,通过 CLI 来使用 npm 的各种功能
    • vscode 里面 Ctrl+j

cmd 常用命令 换到 e 盘:e: 查看目录:dir vscode 查看 npm 的当前版本 输入 npm -v 或者 npm --version 注意空格

node 和 npm 是互相成就的,node 的出现让 npm 火了,npm 的火爆带动了大量的第三方库的发展,很多优秀的第三方库打包上传到了 npm,这些第三方库又为 node 带来了大量的用户

npm

包的安装

安装(install)即下载包 由于 npm 的官方 registry 服务器位于国外,可能受网速影响导致下载缓慢或失败。因此,安装好 npm 之后,需要重新设置 registry 的地址为国内地址。目前,淘宝 https://registry.npm.taobao.org 提供了国内的 registry 地址,先设置到该地址。设置方式为npm config set registry [https://registry.npm.taobao.org](https://registry.npm.taobao.org)。设置好后,通过命令npm config get registry进行检查

npm 安装一个包,分为两种安装方式:

  1. 本地安装
  2. 全局安装

1.本地安装

使用命令npm install 包名npm i 包名即可完成本地安装 终端 clear 是清除命令 本地安装的包出现在当前目录下的node_modules目录中

随着开发的进展,node_modules目录会变得异常庞大,目录下的内容不适合直接传输到生产环境,因此通常使用.gitignore文件忽略该目录中的内容 本地安装适用于绝大部分的包,它会在当前目录及其子目录中发挥作用 通常在项目的根目录中使用本地安装 安装一个包的时候,npm 会自动管理依赖,它会下载该包的依赖包到node_modules目录中 例如:react 如果本地安装的包带有 CLI,npm 会将它的 CLI 脚本文件放置到node_modules/.bin下,使用命令npx 命令名即可调用(npx mocha) 例如:mocha

演示 安装 jQuery:命令 npm install jquery

2.全局安装

全局安装的包放置在一个特殊的全局目录,该目录可以通过命令npm config get prefix查看 使用命令npm install --global 包名npm i -g 包名 重要:全局安装的包并非所有工程可用,它仅提供全局的 CLI 工具 大部分情况下,都不需要全局安装包,除非:

  1. 包的版本非常稳定,很少有大的更新
  2. 提供的 CLI 工具在各个工程中使用的非常频繁
  3. CLI 工具仅为开发环境提供支持,而非部署环境

包配置

前节补充:同时下载两个包:命令 npm i jquery loadsh

目前遇到的问题:

  1. 拷贝工程后如何还原?
  2. 如何区分开发依赖和生产依赖?
  3. 如果自身的项目也是一个包,如何描述包的信息

以上这些问题都需要通过包的配置文件解决

1.配置文件

npm 将每个使用 npm 的工程本身都看作是一个包,包的信息需要通过一个名称固定的配置文件来描述 配置文件的名称固定为:package.json 可以手动创建该文件,而更多的时候,是通过命令npm init创建的 配置文件中可以描述大量的信息,包括:

  • name:包的名称,该名称必须是英文单词字符,支持连接符
  • version:版本
    • 版本规范:主版本号.次版本号.补丁版本号
    • 主版本号:仅当程序发生了重大变化时才会增长,如新增了重要功能、新增了大量的 API、技术架构发生了重大变化
    • 次版本号:仅当程序发生了一些小变化时才会增长,如新增了一些小功能、新增了一些辅助型的 API
    • 补丁版本号:仅当解决了一些 bug 或 进行了一些局部优化时更新,如修复了某个函数的 bug、提升了某个函数的运行效率
  • description:包的描述
  • homepage:官网地址
  • author:包的作者,必须是有效的 npm 账户名,书写规范是 account <mail>,例如:zhangsan <zhangsan@gmail.com>,不正确的账号和邮箱可能导致发布包时失败
  • repository:包的仓储地址,通常指 git 或 svn 的地址,它是一个对象
    • type:仓储类型,git 或 svn
    • url:地址

命令:get remote -v

  • main:包的入口文件,使用包的人默认从该入口文件导入包的内容
  • keywords: 搜索关键字,发布包后,可以通过该数组中的关键字搜索到包

    一步到位简化配置 json 文件

使用npm init --yesnpm init -y可以在生成配置文件时自动填充默认配置 操作:创建一个英文文件夹,右键终端打开,输入npm init --yesnpm init -y

2.保存依赖关系

大部分时候,我们仅仅是开发项目,并不会把它打包发布出去,尽管如此,我们仍然需要 package.json 文件 package.json 文件最重要的作用,是记录当前工程的依赖

  • dependencies:生产环境的依赖包
  • devDependencies:仅开发环境的依赖包
json
"dependencies": {
     "jquery": "latest",//不推荐最新,防止开发冲突
     "lodash": "4.17.15"
 },
@@ -247,8 +247,8 @@
 

nvm

nvm 并非包管理器,它是用于管理多个 node 版本的工具

在实际的开发中,可能会出现多个项目分别使用的是不同的 node 版本,在这种场景下,管理不同的 node 版本就显得尤为重要

nvm 就是用于切换版本的一个工具

1.下载和安装

最新版下载地址:https://github.com/coreybutler/nvm-windows/releases

下载 nvm-setup.zip 后,直接安装 一路下一步,不要改动安装路径(开发类工具尽量不该动)

2.使用 nvm

nvm 提供了 CLI 工具,用于管理 node 版本

管理员运行输入 nvm,以查看各种可用命令 nvm arch:打印系统版本和默认 node 架构类型 nvm install 8.5.4:nvm 安装指定的 node 版本 nvm list :列出目前电脑上使用的以及已经安装过的那些 node 版本 npm list available:node 版本

为了加快下载速度,建议设置淘宝镜像 node 淘宝镜像:https://npm.taobao.org/mirrors/node/ npm 淘宝镜像:https://npm.taobao.org/mirrors/npm/

nvm node _mirror https://npm.taobao.org/mirrors/node/ nvm npm_mirror https://npm.taobao.org/mirrors/npm/ 查看全局安装包:npm -g list --depth=0 切换 node 版本:nvm use 10.18.0 看 node 版本:node -v 卸载:nvm uninstall 10.18.0

pnpm

pnpm 是一种新起的包管理器,从 npm 的下载量看,目前还没有超过 yarn,但它的实现方式值得主流包管理器学习,某些开发者极力推荐使用 pnpm

从结果上来看,它具有以下优势:

  1. 目前,安装效率高于 npm 和 yarn 的最新版
  2. 极其简洁的 node_modules 目录
  3. 避免了开发时使用间接依赖的问题(之前的包管理器可以使用间接依赖不报错)pnpm 没有把间接依赖直接放入 node_modules 里面
  4. 能极大的降低磁盘空间的占用
  5. 使用缓存

1.安装和使用

全局安装 pnpm

shell
npm install -g pnpm
 
npm install -g pnpm
 

之后在使用时,只需要把 npm 替换为 pnpm 即可

如果要执行安装在本地的 CLI,可以使用 pnpx,它和 npx 的功能完全一样,唯一不同的是,在使用 pnpx 执行一个需要安装的命令时,会使用 pnpm 进行安装

比如npx mocha执行本地的mocha命令时,如果mocha没有安装,则 npx 会自动的、临时的安装 mocha,安装好后,自动运行 mocha 命令 类似:npx create-react-app my-app 临时下载 create-react-app,然后自动运行命令

2.pnpm 原理

  1. 同 yarn 和 npm 一样,pnpm 仍然使用缓存来保存已经安装过的包,以及使用 pnpm-lock.yaml 来记录详细的依赖版本

缓存位于工程所在盘的根目录,所以位置不固定

  1. 不同于 yarn 和 npm, pnpm 使用符号链接和硬链接(可将它们想象成快捷方式)的做法来放置依赖,从而规避了从缓存中拷贝文件的时间,使得安装和卸载的速度更快
  2. 由于使用了符号链接和硬链接,pnpm 可以规避 windows 操作系统路径过长的问题,因此,它选择使用树形的依赖结果,有着几乎完美的依赖管理。也因为如此,项目中只能使用直接依赖,而不能使用间接依赖

3.注意事项

由于 pnpm 会改动 node_modules 目录结构,使得每个包只能使用直接依赖,而不能使用间接依赖,因此,如果使用 pnpm 安装的包中包含间接依赖,则会出现问题(现在不会了,除非使用了绝对路径)

由于 pnpm 超高的安装卸载效率,越来越多的包开始修正之前的间接依赖代码

bower

浏览器端

过时了

- - + + \ No newline at end of file diff --git a/front-end-engineering/engineering-onepage.html b/front-end-engineering/engineering-onepage.html index 30a6f93f..928200a4 100644 --- a/front-end-engineering/engineering-onepage.html +++ b/front-end-engineering/engineering-onepage.html @@ -6,13 +6,13 @@ 前端工程化 OnePage | Sunny's blog - - + + -
Skip to content
On this page

前端工程化 OnePage

为什么会出现前端工程化

模块化:意味着 JS 总算可以开发大型项目(解决 JS 的复用问题)

组件化:解决 HTML、CSS、JS 的复用问题

工程化:前端项目越来越复杂、前端项目模块(组件)越来越多

为什么前端项目越来越复杂?

在书写前端项目的时候,可能会涉及到使用其他的语言,typescript、less/sass、coffeescript,涉及到编译

在部署的时候,还需要对代码进行丑化、压缩

代码优化:为 CSS 代码添加兼容性前缀

前端项目模块(组件)越来越多?

专门有一个 node_modules 来管理这些可以服用的代码。

前端工程化的出现,归根结底,其实就是要解决开发环境和生产环境不一致的问题。

nodejs对CommonJS的实现

为了实现CommonJS规范,nodejs对模块做出了以下处理

  1. 为了保证高效的执行,仅加载必要的模块。nodejs只有执行到require函数时才会加载并执行模块

nodejs中导入模块,使用相对路径,并且必须以./或../开头,浏览器可以省略./,nodejs不行

  1. 为了隐藏模块中的代码,nodejs执行模块时,会将模块中的所有代码放置到一个函数中执行,以保证不污染全局变量。
javascript
 (function(){
+    
Skip to content
On this page

前端工程化 OnePage

为什么会出现前端工程化

模块化:意味着 JS 总算可以开发大型项目(解决 JS 的复用问题)

组件化:解决 HTML、CSS、JS 的复用问题

工程化:前端项目越来越复杂、前端项目模块(组件)越来越多

为什么前端项目越来越复杂?

在书写前端项目的时候,可能会涉及到使用其他的语言,typescript、less/sass、coffeescript,涉及到编译

在部署的时候,还需要对代码进行丑化、压缩

代码优化:为 CSS 代码添加兼容性前缀

前端项目模块(组件)越来越多?

专门有一个 node_modules 来管理这些可以服用的代码。

前端工程化的出现,归根结底,其实就是要解决开发环境和生产环境不一致的问题。

nodejs对CommonJS的实现

为了实现CommonJS规范,nodejs对模块做出了以下处理

  1. 为了保证高效的执行,仅加载必要的模块。nodejs只有执行到require函数时才会加载并执行模块

nodejs中导入模块,使用相对路径,并且必须以./或../开头,浏览器可以省略./,nodejs不行

  1. 为了隐藏模块中的代码,nodejs执行模块时,会将模块中的所有代码放置到一个函数中执行,以保证不污染全局变量。
javascript
 (function(){
      //模块中的代码
  })()
 
 (function(){
@@ -474,9 +474,9 @@
 
 # 设置缓存位置
 npm config set cache "新的缓存路径"
-
- - +
+ + \ No newline at end of file diff --git a/front-end-engineering/jscompatibility.html b/front-end-engineering/jscompatibility.html index fa07980d..80bf49b0 100644 --- a/front-end-engineering/jscompatibility.html +++ b/front-end-engineering/jscompatibility.html @@ -6,13 +6,13 @@ JS兼容性 | Sunny's blog - - + + -
Skip to content
On this page

JS兼容性

babel

官网:https://babeljs.io/

民间中文网:https://www.babeljs.cn/

babel简介

babel一词来自于希伯来语,直译为巴别塔

巴别塔象征的统一的国度、统一的语言

而今天的JS世界缺少一座巴别塔,不同版本的浏览器能识别的ES标准并不相同,就导致了开发者面对不同版本的浏览器要使用不同的语言,和古巴比伦一样,前端开发也面临着这样的困境。

babel的出现,就是用于解决这样的问题,它是一个编译器,可以把不同标准书写的语言,编译为统一的、能被各种浏览器识别的语言

由于语言的转换工作灵活多样,babel的做法和postcss、webpack差不多,它本身仅提供一些分析功能,真正的转换需要依托于插件完成

babel的安装

babel可以和构建工具联合使用,也可以独立使用

如果要独立的使用babel,需要安装下面两个库:

  • @babel/core:babel核心库,提供了编译所需的所有api
  • @babel/cli:提供一个命令行工具,调用核心库的api完成编译
shell
npm i -D @babel/core @babel/cli
+    
Skip to content
On this page

JS兼容性

babel

官网:https://babeljs.io/

民间中文网:https://www.babeljs.cn/

babel简介

babel一词来自于希伯来语,直译为巴别塔

巴别塔象征的统一的国度、统一的语言

而今天的JS世界缺少一座巴别塔,不同版本的浏览器能识别的ES标准并不相同,就导致了开发者面对不同版本的浏览器要使用不同的语言,和古巴比伦一样,前端开发也面临着这样的困境。

babel的出现,就是用于解决这样的问题,它是一个编译器,可以把不同标准书写的语言,编译为统一的、能被各种浏览器识别的语言

由于语言的转换工作灵活多样,babel的做法和postcss、webpack差不多,它本身仅提供一些分析功能,真正的转换需要依托于插件完成

babel的安装

babel可以和构建工具联合使用,也可以独立使用

如果要独立的使用babel,需要安装下面两个库:

  • @babel/core:babel核心库,提供了编译所需的所有api
  • @babel/cli:提供一个命令行工具,调用核心库的api完成编译
shell
npm i -D @babel/core @babel/cli
 
npm i -D @babel/core @babel/cli
 

babel的使用

@babel/cli的使用极其简单

它提供了一个命令babel

shell
# 按文件编译
 babel 要编译的文件 -o 编辑结果文件
@@ -225,8 +225,8 @@
 	extends: 'airbnb' # 配置继承自 airbnb
 }
 

企业开发的实际情况

我们要做什么?

  • 安装好VSCode的ESLint插件
  • 学会查看ESLint错误提示
- - + + \ No newline at end of file diff --git a/front-end-engineering/modularization.html b/front-end-engineering/modularization.html index fb155a0c..37baceab 100644 --- a/front-end-engineering/modularization.html +++ b/front-end-engineering/modularization.html @@ -6,13 +6,13 @@ 模块化 | Sunny's blog - - + + -
Skip to content
On this page

模块化

1.JavaScript 模块化发展史

第一阶段

在 JavaScript 语言刚刚诞生的时候,它仅仅用于实现页面中的一些小效果 那个时候,一个页面所用到的 JS 可能只有区区几百行的代码 在这种情况下,语言本身所存在的一些缺陷往往被大家有意的忽略,因为程序的规模实在太小,只要开发人员小心谨慎,往往不会造成什么问题 在这个阶段,也不存在专业的前端工程师,由于前端要做的事情实在太少,因此这一部分工作往往由后端工程师顺带完成 第一阶段发生的大事件:

  • 1996 年,NetScape 将 JavaScript 语言提交给欧洲的一个标准制定组织 ECMA(欧洲计算机制造商协会)
  • 1998 年,NetScape 在与微软浏览器 IE 的竞争中失利,宣布破产

第二阶段

ajax 的出现,逐渐改变了 JavaScript 在浏览器中扮演的角色。现在,它不仅可以实现小的效果,还可以和服务器之间进行交互,以更好的体验来改变数据 JS 代码的数量开始逐渐增长,从最初的几百行,到后来的几万行,前端程序逐渐变得复杂 后端开发者压力逐渐增加,致使一些公司开始招募专业的前端开发者 但此时,前端开发者的待遇远不及后端开发者,因为前端开发者承担的开发任务相对于后端开发来说,还是比较简单的,通过短短一个月的时间集训,就可以成为满足前端开发的需要 究其根本原因,是因为前端开发还有几个大的问题没有解决,这些问题都严重的制约了前端程序的规模进一步扩大:

  1. 浏览器解释执行 JS 的速度太慢
  2. 用户端的电脑配置不足
  3. 更多的代码带来了全局变量污染、依赖关系混乱等问题

上面三个问题,就像是阿喀琉斯之踵,成为前端开发挥之不去的阴影和原罪。 在这个阶段,前端开发处在一个非常尴尬的境地,它在传统的开发模式和前后端分离之间无助的徘徊 第二阶段的大事件:

  1. IE 浏览器制霸市场后,几乎不再更新
  2. ES4.0 流产,导致 JS 语言 10 年间几乎毫无变化
  3. 2008 年 ES5 发布,仅解决了一些 JS API 不足的糟糕局面

第三阶段

时间继续向前推移,到了 2008 年,谷歌的 V8 引擎发布(面试),将 JS 的执行速度推上了一个新的台阶,甚至可以和后端语言媲美。 摩尔定律持续发酵,个人电脑的配置开始飞跃 突然间,制约前端发展的两大问题得以解决,此时,只剩下最后一个问题还在负隅顽抗,即全局变量污染和依赖混乱的问题,解决了它,前端便可以突破一切障碍,未来无可限量。 于是,全世界的前端开发者在社区中激烈的讨论,想要为这个问题寻求解决之道...... 2008 年,有一个名叫 Ryan Dahl 小伙子正在为一件事焦头烂额,它需要在服务器端手写一个高性能的 web 服务,该服务对于性能要求之高,以至于目前市面上已有的 web 服务产品都满足不了需求。

服务器开发

新浪的服务器(电脑)收到请求 其中一个应用程序在做以下的事情 web 服务

  1. 监听 80 端口
  2. 将请求进行分析
  3. 将分析的结果交给相应的程序(php,Java)进行处理
  4. 把程序处理的结果返还给客户端

经过分析,它确定,如果要实现高性能,那么必须要尽可能的减少线程,而要减少线程,避免不了要实用异步的处理方案。 一开始,他打算自己实用 C/C++语言来编写,可是这一过程实在太痛苦。 就在他一筹莫展的时候,谷歌 V8 引擎的发布引起了他的注意,他突然发现,JS 不就是最好的实现 web 服务的语言吗?它天生就是单线程,并且是基于异步的!有了 V8 引擎的支撑,它的执行速度完全可以撑起一个服务器。而且 V8 是鼎鼎大名的谷歌公司发布的,谷歌一定会不断的优化 V8,有这种又省钱又省力的好事,我干嘛还要自己去写呢? 典型异步场景

javascript
setTimeout(function () {
+    
Skip to content
On this page

模块化

1.JavaScript 模块化发展史

第一阶段

在 JavaScript 语言刚刚诞生的时候,它仅仅用于实现页面中的一些小效果 那个时候,一个页面所用到的 JS 可能只有区区几百行的代码 在这种情况下,语言本身所存在的一些缺陷往往被大家有意的忽略,因为程序的规模实在太小,只要开发人员小心谨慎,往往不会造成什么问题 在这个阶段,也不存在专业的前端工程师,由于前端要做的事情实在太少,因此这一部分工作往往由后端工程师顺带完成 第一阶段发生的大事件:

  • 1996 年,NetScape 将 JavaScript 语言提交给欧洲的一个标准制定组织 ECMA(欧洲计算机制造商协会)
  • 1998 年,NetScape 在与微软浏览器 IE 的竞争中失利,宣布破产

第二阶段

ajax 的出现,逐渐改变了 JavaScript 在浏览器中扮演的角色。现在,它不仅可以实现小的效果,还可以和服务器之间进行交互,以更好的体验来改变数据 JS 代码的数量开始逐渐增长,从最初的几百行,到后来的几万行,前端程序逐渐变得复杂 后端开发者压力逐渐增加,致使一些公司开始招募专业的前端开发者 但此时,前端开发者的待遇远不及后端开发者,因为前端开发者承担的开发任务相对于后端开发来说,还是比较简单的,通过短短一个月的时间集训,就可以成为满足前端开发的需要 究其根本原因,是因为前端开发还有几个大的问题没有解决,这些问题都严重的制约了前端程序的规模进一步扩大:

  1. 浏览器解释执行 JS 的速度太慢
  2. 用户端的电脑配置不足
  3. 更多的代码带来了全局变量污染、依赖关系混乱等问题

上面三个问题,就像是阿喀琉斯之踵,成为前端开发挥之不去的阴影和原罪。 在这个阶段,前端开发处在一个非常尴尬的境地,它在传统的开发模式和前后端分离之间无助的徘徊 第二阶段的大事件:

  1. IE 浏览器制霸市场后,几乎不再更新
  2. ES4.0 流产,导致 JS 语言 10 年间几乎毫无变化
  3. 2008 年 ES5 发布,仅解决了一些 JS API 不足的糟糕局面

第三阶段

时间继续向前推移,到了 2008 年,谷歌的 V8 引擎发布(面试),将 JS 的执行速度推上了一个新的台阶,甚至可以和后端语言媲美。 摩尔定律持续发酵,个人电脑的配置开始飞跃 突然间,制约前端发展的两大问题得以解决,此时,只剩下最后一个问题还在负隅顽抗,即全局变量污染和依赖混乱的问题,解决了它,前端便可以突破一切障碍,未来无可限量。 于是,全世界的前端开发者在社区中激烈的讨论,想要为这个问题寻求解决之道...... 2008 年,有一个名叫 Ryan Dahl 小伙子正在为一件事焦头烂额,它需要在服务器端手写一个高性能的 web 服务,该服务对于性能要求之高,以至于目前市面上已有的 web 服务产品都满足不了需求。

服务器开发

新浪的服务器(电脑)收到请求 其中一个应用程序在做以下的事情 web 服务

  1. 监听 80 端口
  2. 将请求进行分析
  3. 将分析的结果交给相应的程序(php,Java)进行处理
  4. 把程序处理的结果返还给客户端

经过分析,它确定,如果要实现高性能,那么必须要尽可能的减少线程,而要减少线程,避免不了要实用异步的处理方案。 一开始,他打算自己实用 C/C++语言来编写,可是这一过程实在太痛苦。 就在他一筹莫展的时候,谷歌 V8 引擎的发布引起了他的注意,他突然发现,JS 不就是最好的实现 web 服务的语言吗?它天生就是单线程,并且是基于异步的!有了 V8 引擎的支撑,它的执行速度完全可以撑起一个服务器。而且 V8 是鼎鼎大名的谷歌公司发布的,谷歌一定会不断的优化 V8,有这种又省钱又省力的好事,我干嘛还要自己去写呢? 典型异步场景

javascript
setTimeout(function () {
   console.log("a");
 }, 1000);
 //后面不会阻塞
@@ -245,8 +245,8 @@
 export { k, default, a as m2a } from "./m2.js";
 export const r = "m-r";
 
- - + + \ No newline at end of file diff --git a/front-end-engineering/performance.html b/front-end-engineering/performance.html index 9edda893..3e519ea0 100644 --- a/front-end-engineering/performance.html +++ b/front-end-engineering/performance.html @@ -6,13 +6,13 @@ 前端性能优化方法论 | Sunny's blog - - + + -
Skip to content
On this page

前端性能优化方法论

我们可以从两个方面来看性能优化的意义:

  1. 用户角度:网站优化能够让页面加载得更快,响应更加及时,极大提升用户体验。
  2. 服务商角度:优化会减少页面资源请求数,减小请求资源所占带宽大小,从而节省可观的带宽资源。

网站优化的目标就是减少网站加载时间,提高响应速度。 Google 和亚马逊的研究表明,Google 页面加载的时间从 0.4 秒提升到 0.9 秒导致丢失了 20% 流量和广告收入,对于亚马逊,页面加载时间每增加 100ms 就意味着 1% 的销售额损失。 可见,页面的加载速度对于用户有着至关重要的影响。

Webpack 优化

如何分析打包结果?webpack-bundle-analyzer

1. 构建性能

TIP

这里所说的构建性能,是指在开发阶段的构建性能,而不是生产环境的构建性能

优化的目标,是降低从打包开始,到代码效果呈现所经过的时间

构建性能会影响开发效率。构建性能越高,开发过程中时间的浪费越少

1.1 减少模块解析

模块解析包括:抽象语法树分析、依赖分析、模块语法替换

如果某个模块不做解析,该模块经过loader处理后的代码就是最终代码。

如果没有loader对该模块进行处理,该模块的源码就是最终打包结果的代码。

如果不对某个模块进行解析,可以缩短构建时间,那么哪些模块不需要解析呢?

模块中无其他依赖:一些已经打包好的第三方库,比如jquery,所以可以配置module.noParse,它是一个正则,被正则匹配到的模块不会解析

js
module.exports = {
+    
Skip to content
On this page

前端性能优化方法论

我们可以从两个方面来看性能优化的意义:

  1. 用户角度:网站优化能够让页面加载得更快,响应更加及时,极大提升用户体验。
  2. 服务商角度:优化会减少页面资源请求数,减小请求资源所占带宽大小,从而节省可观的带宽资源。

网站优化的目标就是减少网站加载时间,提高响应速度。 Google 和亚马逊的研究表明,Google 页面加载的时间从 0.4 秒提升到 0.9 秒导致丢失了 20% 流量和广告收入,对于亚马逊,页面加载时间每增加 100ms 就意味着 1% 的销售额损失。 可见,页面的加载速度对于用户有着至关重要的影响。

Webpack 优化

如何分析打包结果?webpack-bundle-analyzer

1. 构建性能

TIP

这里所说的构建性能,是指在开发阶段的构建性能,而不是生产环境的构建性能

优化的目标,是降低从打包开始,到代码效果呈现所经过的时间

构建性能会影响开发效率。构建性能越高,开发过程中时间的浪费越少

1.1 减少模块解析

模块解析包括:抽象语法树分析、依赖分析、模块语法替换

如果某个模块不做解析,该模块经过loader处理后的代码就是最终代码。

如果没有loader对该模块进行处理,该模块的源码就是最终打包结果的代码。

如果不对某个模块进行解析,可以缩短构建时间,那么哪些模块不需要解析呢?

模块中无其他依赖:一些已经打包好的第三方库,比如jquery,所以可以配置module.noParse,它是一个正则,被正则匹配到的模块不会解析

js
module.exports = {
   mode: 'development',
   module: {
     noParse: /jquery/
@@ -1319,8 +1319,8 @@
 }
 
 

浏览器刷新页面的频率1s 60s

每16.7mm刷新一次

gpu 可以再一帧里渲染好页面,那么当你改动页面的元素或者实现动画的时候,将会非常流畅

documentFragment 是什么?用它跟直接操作 DOM 的区别是什么?

MDN中对documentFragment的解释:

DocumentFragment,文档片段接口,一个没有父对象的最小文档对象。它被作为一个轻量版的 Document使用,就像标准的document一样,存储由节点(nodes)组成的文档结构。与document相比,最大的区别是DocumentFragment不是真实 DOM 树的一部分,它的变化不会触发 DOM 树的重新渲染,且不会导致性能等问题。

当我们把一个 DocumentFragment 节点插入文档树时,插入的不是 DocumentFragment 自身,而是它的所有子孙节点。在频繁的DOM操作时,我们就可以将DOM元素插入DocumentFragment,之后一次性的将所有的子孙节点插入文档中。和直接操作DOM相比,将DocumentFragment 节点插入DOM树时,不会触发页面的重绘,这样就大大提高了页面的性能。

防抖函数

  • 按钮提交场景:防⽌多次提交按钮,只执⾏最后提交的⼀次
  • 服务端验证场景:表单验证需要服务端配合,只执⾏⼀段连续的输⼊事件的最后⼀次,还有搜索联想词功能类似⽣存环境请⽤lodash.debounce 节流函数的适⽤场景:
  • 拖拽场景:固定时间内只执⾏⼀次,防⽌超⾼频次触发位置变动
  • 缩放场景:监控浏览器resize
  • 动画场景:避免短时间内多次触发动画引起性能问题

如何对项目中的图片进行优化?

  1. 不用图片。很多时候会使用到很多修饰类图片,其实这类修饰图片完全可以用 CSS 去代替。
  2. 对于移动端来说,屏幕宽度就那么点,完全没有必要去加载原图浪费带宽。一般图片都用 CDN 加载,可以计算出适配屏幕的宽度,然后去请求相应裁剪好的图片。
  3. 小图使用 base64 格式
  4. 将多个图标文件整合到一张图片中(雪碧图)
  5. 选择正确的图片格式:
  • 对于能够显示 WebP 格式的浏览器尽量使用 WebP 格式。因为 WebP 格式具有更好的图像数据压缩算法,能带来更小的图片体积,而且拥有肉眼识别无差异的图像质量,缺点就是兼容性并不好
  • 小图使用 PNG,其实对于大部分图标这类图片,完全可以使用 SVG 代替
  • 照片使用 JPEG

常见的图片格式及使用场景

(1)BMP,是无损的、既支持索引色也支持直接色的点阵图。这种图片格式几乎没有对数据进行压缩,所以BMP格式的图片通常是较大的文件。

(2)GIF是无损的、采用索引色的点阵图。采用LZW压缩算法进行编码。文件小,是GIF格式的优点,同时,GIF格式还具有支持动画以及透明的优点。但是GIF格式仅支持8bit的索引色,所以GIF格式适用于对色彩要求不高同时需要文件体积较小的场景。

(3)JPEG是有损的、采用直接色的点阵图。JPEG的图片的优点是采用了直接色,得益于更丰富的色彩,JPEG非常适合用来存储照片,与GIF相比,JPEG不适合用来存储企业Logo、线框类的图。因为有损压缩会导致图片模糊,而直接色的选用,又会导致图片文件较GIF更大。

(4)PNG-8是无损的、使用索引色的点阵图。PNG是一种比较新的图片格式,PNG-8是非常好的GIF格式替代者,在可能的情况下,应该尽可能的使用PNG-8而不是GIF,因为在相同的图片效果下,PNG-8具有更小的文件体积。除此之外,PNG-8还支持透明度的调节,而GIF并不支持。除非需要动画的支持,否则没有理由使用GIF而不是PNG-8。

(5)PNG-24是无损的、使用直接色的点阵图。PNG-24的优点在于它压缩了图片的数据,使得同样效果的图片,PNG-24格式的文件大小要比BMP小得多。当然,PNG24的图片还是要比JPEG、GIF、PNG-8大得多。

(6)SVG是无损的矢量图。SVG是矢量图意味着SVG图片由直线和曲线以及绘制它们的方法组成。当放大SVG图片时,看到的还是线和曲线,而不会出现像素点。这意味着SVG图片在放大时,不会失真,所以它非常适合用来绘制Logo、Icon等。

(7)WebP是谷歌开发的一种新图片格式,WebP是同时支持有损和无损压缩的、使用直接色的点阵图。从名字就可以看出来它是为Web而生的,什么叫为Web而生呢?就是说相同质量的图片,WebP具有更小的文件体积。现在网站上充满了大量的图片,如果能够降低每一个图片的文件大小,那么将大大减少浏览器和服务器之间的数据传输量,进而降低访问延迟,提升访问体验。目前只有Chrome浏览器和Opera浏览器支持WebP格式,兼容性不太好。WebP图片格式支持图片透明度,一个无损压缩的WebP图片,如果要支持透明度只需要22%的格外文件大小。

优化首屏响应

觉得快

loading

vue页面需要通过js构建,因此在js下载到本地之前,页面上什么也没有 一个非常简单有效的办法,即在页面中先渲染一个小的加载中效果,等到js下载到本地并运行后,即会自动替换

nprogress

源码分析地址:https://blog.csdn.net/qq_31968791/article/details/106790179 使用到的库是什么 nprogress 进度条的实现原理知道吗 Nprogress的原理非常简单,就是页面启动的时候,构建一个方法,创建一个div,然后这个div靠近最顶部,用fixed定位住,至于样式就是按照自个或者默认走了。 怎么使用这个库的 主要采用的两个方法是nprogress.start和nprogress.done 如何使用: 在请求拦截器中调用nprogress.start 在响应拦截器中调用nprogress.done

betterScroll

https://blog.csdn.net/weixin_37719279/article/details/82084342 使用的库是什么:better-scroll

骨架屏

骨架屏的原理:https://blog.csdn.net/csdn_yudong/article/details/103909178

你能说说为啥使用骨架屏吗?

现在的前端开发领域,都是前后端分离,前端框架主流的都是 SPA,MPA;这就意味着,页面渲染以及等待的白屏时间,成为我们需要解决的问题点;而且大项目,这个问题尤为突出。 webpack 可以实现按需加载,减小我们首屏需要加载的代码体积;再配合上 CDN 以及一些静态代码(框架,组件库等等…)缓存技术,可以很好的缓解这个加载渲染的时间过长的问题。 但即便如此,首屏的加载依然还是存在这个加载以及渲染的等待时间问题; 现在的前端开发领域,都是前后端分离,前端框架主流的都是 SPA,MPA;这就意味着,页面渲染以及等待的白屏时间,成为我们需要解决的问题点;而且大项目,这个问题尤为突出。 webpack 可以实现按需加载,减小我们首屏需要加载的代码体积;再配合上 CDN 以及一些静态代码(框架,组件库等等…)缓存技术,可以很好的缓解这个加载渲染的时间过长的问题。 目前主流,常见的解决方案是使用骨架屏技术,包括很多原生的APP,在页面渲染时,也会使用骨架屏。(下图中,红圈中的部分,即为骨架屏在内容还没有出现之前的页面骨架填充,以免留白)

骨架屏的要怎么使用呢?骨架屏的原理知道吗?

  1. 在 index.html 中的 div#app 中来实现骨架屏,程序渲染后就会替换掉 index.html 里面的 div#app 骨架屏内容;
  2. 使用一个Base64的图片来作为骨架屏

使用图片作为骨架屏; 简单暴力,让UI同学花点功夫吧;小米商城的移动端页面采用的就是这个方法,它是使用了一个Base64的图片来作为骨架屏。 按照方案一的方案,将这个 Base64 的图片写在我们的 index.html 模块中的 div#app 里面。

  1. 使用 .vue 文件来完成骨架屏

真实快

webpack 怎么进行首屏加载的优化?

  1. CDN 如果工程中使用了一些知名的第三方库,可以考虑使用 CDN,而不进行打包
  2. 抽离公共模块 如果工程中用到了一些大的公共库,可以考虑将其分割出来单独打包
  3. 异步加载 对于那些不需要在一开始就执行的模块,可以考虑使用动态导入的方式异步加载它们,以尽量减少主包的体积
  4. 压缩、混淆
  5. tree shaking 尽量使用 ESM 语法进行导入导出,充分利用 tree shaking 去除无用代码
  6. gzip 开启 gzip 压缩,进一步减少包体积
  7. 环境适配 有些打包结果中包含了大量兼容性处理的代码,但在新版本浏览器中这些代码毫无意义。因此,可以把浏览器分为多个层次,为不同层次的浏览器给予不同的打包结果。
- - + + \ No newline at end of file diff --git "a/front-end-engineering/pnpm\345\216\237\347\220\206.html" "b/front-end-engineering/pnpm\345\216\237\347\220\206.html" index a28caba3..bdba97a4 100644 --- "a/front-end-engineering/pnpm\345\216\237\347\220\206.html" +++ "b/front-end-engineering/pnpm\345\216\237\347\220\206.html" @@ -6,13 +6,13 @@ pnpm 原理 | Sunny's blog - - + + -
Skip to content
On this page

pnpm 原理

想要理解 pnpm 是怎么做的,需要一些操作系统的知识

1、文件的本质

在操作系统中,文件实际上是一个指针,只不过它指向的不是内存地址,而是一个外部存储地址(这里的外部存储可以是硬盘、U 盘、甚至是网络)

当我们删除文件时,删除的实际上是指针,因此,无论删除多么大的文件,速度都非常快。像我们的 U 盘、硬盘里的文件虽然说看起来已经删除了,但是其实数据恢复公司是可以恢复的,因为数据还是存在的,只要删除文件后再没有存储其它文件就可以恢复,所以真正删除一个文件就是可劲存可劲删

2、文件的拷贝

如果你复制一个文件,是将该文件指针指向的内容进行复制,然后产生一个新文件指向新的内容。

硬链接的概念来自于 Unix 操作系统,它是指将一个文件 A 指针复制到另一个文件 B 指针中,文件 B 就是文件 A 的硬链接。

通过硬链接,不会产生额外的磁盘占用,并且,两个文件都能找到相同的磁盘内容。

硬链接的数量没有限制,可以为同一个文件产生多个硬链接。

windows Vista 操作系统开始,支持了创建硬链接的操作,在 cmd 中使用下面的命令可以创建硬链接。

mklink /h  链接名称  目标文件
+    
Skip to content
On this page

pnpm 原理

想要理解 pnpm 是怎么做的,需要一些操作系统的知识

1、文件的本质

在操作系统中,文件实际上是一个指针,只不过它指向的不是内存地址,而是一个外部存储地址(这里的外部存储可以是硬盘、U 盘、甚至是网络)

当我们删除文件时,删除的实际上是指针,因此,无论删除多么大的文件,速度都非常快。像我们的 U 盘、硬盘里的文件虽然说看起来已经删除了,但是其实数据恢复公司是可以恢复的,因为数据还是存在的,只要删除文件后再没有存储其它文件就可以恢复,所以真正删除一个文件就是可劲存可劲删

2、文件的拷贝

如果你复制一个文件,是将该文件指针指向的内容进行复制,然后产生一个新文件指向新的内容。

硬链接的概念来自于 Unix 操作系统,它是指将一个文件 A 指针复制到另一个文件 B 指针中,文件 B 就是文件 A 的硬链接。

通过硬链接,不会产生额外的磁盘占用,并且,两个文件都能找到相同的磁盘内容。

硬链接的数量没有限制,可以为同一个文件产生多个硬链接。

windows Vista 操作系统开始,支持了创建硬链接的操作,在 cmd 中使用下面的命令可以创建硬链接。

mklink /h  链接名称  目标文件
 
 
mklink /h  链接名称  目标文件
 
@@ -23,8 +23,8 @@
 # /d表示创建的是目录的符号链接,不写则是文件的符号链接
 
 

早期的 windows 系统不支持符号链接,但它提供了一个工具 junction 来达到类似的功能。

5、符号链接和硬链接的区别

  1. 硬链接仅能链接文件,而符号链接可以链接目录
  2. 硬链接在链接完成后仅和文件内容关联,和之前链接的文件没有任何关系。而符号链接始终和之前链接的文件关联,和文件内容不直接相关。

6、快捷方式

快捷方式类似于符号链接,是 windows 系统早期就支持的链接方式。它不仅仅是一个指向其他文件或目录的指针,其中还包含了各种信息:如权限、兼容性启动方式等其他各种属性,由于快捷方式是 windows 系统独有的,在跨平台的应用中一般不会使用。

7、node 环境对硬链接和符号链接的处理

硬链接:

硬链接是一个实实在在的文件,node 不对其做任何特殊处理,也无法区别对待,实际上,node 根本无从知晓该文件是不是一个硬链接

符号链接:

由于符号链接指向的是另一个文件或目录,当 node 执行符号链接下的 JS 文件时,会使用原始路径。比方说:我在 D 盘装了 LOL,在桌面创建了 LOL 快捷方式,相当于是符号链接,双击快捷方式运行游戏,在运行游戏的时候是按照 LOL 原始路径(D 盘路径)运行的。

8、pnpm 原理

pnpm 使用符号链接和硬链接来构建 node_modules 目录

下面用一个例子来说明它的构建方式

假设两个包 a 和 b,a 依赖 b:

假设我们的工程为 proj,直接依赖 a,则安装时,pnpm 会做下面的处理:

  1. 通过 package.json 查询依赖关系,得到最终要安装的包:a 和 b

  2. 在工程 proj 根目录中查看 a 和 b 是否已经有缓存,如果没有,下载到缓存中,如果有,则进入下一步

  3. 在 proj 中创建 node_modules 目录,并对目录进行结构初始化

  4. 从缓存的对应包中使用硬链接放置文件到相应包代码目录中

  5. 使用符号链接,将每个包的直接依赖放置到自己的目录中

    这样做的目的,是为了保证 a 的代码在执行过程中,可以读取到它们的直接依赖

  6. 新版本的 pnpm 为了解决一些书写不规范的包(读取间接依赖)的问题,又将所有的工程非直接依赖,使用符号链接加入到了 .pnpm/node_modules 中。如果 b 依赖 c,a 又要直接用 c,这种不规范的用法现在 pnpm 通过这种方式支持了。但对于那些使用绝对路径的奇葩写法,可能没有办法支持。

  7. 在工程的 node_modules 目录中使用符号链接,放置直接依赖

转载自 https://juejin.cn/post/6916101419703468045

- - + + \ No newline at end of file diff --git a/front-end-engineering/webpack5-mf.html b/front-end-engineering/webpack5-mf.html index 5eac4d68..ecc1724f 100644 --- a/front-end-engineering/webpack5-mf.html +++ b/front-end-engineering/webpack5-mf.html @@ -6,13 +6,13 @@ webpack5 模块联邦 | Sunny's blog - - + + -
Skip to content
On this page

webpack5 模块联邦

在大型项目中,往往会把项目中的某个区域或功能模块作为单独的项目开发,最终形成「微前端」架构

在微前端架构中,不同的工程可能出现下面的场景

这涉及到很多非常棘手的问题:

  • 如何避免公共模块重复打包
  • 如何将某个项目中一部分模块分享出去,同时还要避免重复打包
  • 如何管理依赖的不同版本
  • 如何更新模块
  • ......

webpack5尝试着通过模块联邦来解决此类问题

示例

现有两个微前端工程,它们各自独立开发、测试、部署,但它们有一些相同的公共模块,并有一些自己的模块需要分享给其他工程使用,同时又要引入其他工程的模块。

暴露自身模块

如果一个项目需要把一部分模块暴露给其他项目使用,可以使用webpack5的模块联邦将这些模块暴露出去

javascript
// webpack.config.js
+    
Skip to content
On this page

webpack5 模块联邦

在大型项目中,往往会把项目中的某个区域或功能模块作为单独的项目开发,最终形成「微前端」架构

在微前端架构中,不同的工程可能出现下面的场景

这涉及到很多非常棘手的问题:

  • 如何避免公共模块重复打包
  • 如何将某个项目中一部分模块分享出去,同时还要避免重复打包
  • 如何管理依赖的不同版本
  • 如何更新模块
  • ......

webpack5尝试着通过模块联邦来解决此类问题

示例

现有两个微前端工程,它们各自独立开发、测试、部署,但它们有一些相同的公共模块,并有一些自己的模块需要分享给其他工程使用,同时又要引入其他工程的模块。

暴露自身模块

如果一个项目需要把一部分模块暴露给其他项目使用,可以使用webpack5的模块联邦将这些模块暴露出去

javascript
// webpack.config.js
 const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
 
 module.exports = {
@@ -115,8 +115,8 @@
   ]
 }
 

webpack会根据需要从合适的位置引入合适的版本

- - + + \ No newline at end of file diff --git "a/front-end-engineering/webpack\345\270\270\347\224\250\346\213\223\345\261\225.html" "b/front-end-engineering/webpack\345\270\270\347\224\250\346\213\223\345\261\225.html" index 901604c3..ae6299a0 100644 --- "a/front-end-engineering/webpack\345\270\270\347\224\250\346\213\223\345\261\225.html" +++ "b/front-end-engineering/webpack\345\270\270\347\224\250\346\213\223\345\261\225.html" @@ -6,13 +6,13 @@ webpack常用拓展 | Sunny's blog - - + + -
Skip to content
On this page

webpack常用拓展

清除输出目录

clean-webpack-plugin 当文件内容变化,重新打包,会自动删除原来打包的文件

js
module.exports = {
+    
Skip to content
On this page

webpack常用拓展

清除输出目录

clean-webpack-plugin 当文件内容变化,重新打包,会自动删除原来打包的文件

js
module.exports = {
     plugins: [
         new CleanWebpackPlugin()
     ],
@@ -217,8 +217,8 @@
 
$('#item'); // <= 起作用
 _.drop([1, 2, 3], 2); // <= 起作用
 
- - + + \ No newline at end of file diff --git "a/front-end-engineering/\343\200\220\345\256\214\347\273\223\343\200\221\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226.html" "b/front-end-engineering/\343\200\220\345\256\214\347\273\223\343\200\221\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226.html" index 35b91ef6..47305b8a 100644 --- "a/front-end-engineering/\343\200\220\345\256\214\347\273\223\343\200\221\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226.html" +++ "b/front-end-engineering/\343\200\220\345\256\214\347\273\223\343\200\221\345\211\215\347\253\257\345\267\245\347\250\213\345\214\226.html" @@ -6,13 +6,13 @@ npx | Sunny's blog - - + + -
Skip to content
On this page

为什么会出现前端工程化

模块化:意味着咱们的 JS 总算可以开发大型项目(解决 JS 的复用问题)

组件化:解决 HTML、CSS、JS 的复用问题

工程化:因为咱们的前端项目越来越复杂、咱们的前端项目模块(组件)越来越多

前端项目越来越复杂?

在书写前端项目的时候,可能会涉及到使用其他的语言,typescript、less/sass、coffeescript,涉及到编译

在部署的时候,还需要对代码进行丑化、压缩

代码优化:为 CSS 代码添加兼容性前缀

前端项目模块(组件)越来越多?

专门有一个 node_modules 来管理这些可以服用的代码。

前端工程化的出现,归根结底,其实就是要解决开发环境和生产环境不一致的问题。

nodejs对CommonJS的实现(面试)

为了实现CommonJS规范,nodejs对模块做出了以下处理

  1. 为了保证高效的执行,仅加载必要的模块。nodejs只有执行到require函数时才会加载并执行模块

nodejs中导入模块,使用相对路径,并且必须以./或../开头,浏览器可以省略./,nodejs不行

  1. 为了隐藏模块中的代码,nodejs执行模块时,会将模块中的所有代码放置到一个函数中执行,以保证不污染全局变量。
javascript
 (function(){
+    
Skip to content
On this page

为什么会出现前端工程化

模块化:意味着咱们的 JS 总算可以开发大型项目(解决 JS 的复用问题)

组件化:解决 HTML、CSS、JS 的复用问题

工程化:因为咱们的前端项目越来越复杂、咱们的前端项目模块(组件)越来越多

前端项目越来越复杂?

在书写前端项目的时候,可能会涉及到使用其他的语言,typescript、less/sass、coffeescript,涉及到编译

在部署的时候,还需要对代码进行丑化、压缩

代码优化:为 CSS 代码添加兼容性前缀

前端项目模块(组件)越来越多?

专门有一个 node_modules 来管理这些可以服用的代码。

前端工程化的出现,归根结底,其实就是要解决开发环境和生产环境不一致的问题。

nodejs对CommonJS的实现(面试)

为了实现CommonJS规范,nodejs对模块做出了以下处理

  1. 为了保证高效的执行,仅加载必要的模块。nodejs只有执行到require函数时才会加载并执行模块

nodejs中导入模块,使用相对路径,并且必须以./或../开头,浏览器可以省略./,nodejs不行

  1. 为了隐藏模块中的代码,nodejs执行模块时,会将模块中的所有代码放置到一个函数中执行,以保证不污染全局变量。
javascript
 (function(){
      //模块中的代码
  })()
 
 (function(){
@@ -477,8 +477,8 @@
 # 设置缓存位置
 npm config set cache "新的缓存路径"
 
- - + + \ No newline at end of file diff --git a/getting-started.html b/getting-started.html index f8494bce..e77ee2df 100644 --- a/getting-started.html +++ b/getting-started.html @@ -6,15 +6,15 @@ My Projects | Sunny's blog - - + + -
Skip to content
On this page

My Projects

TODO:分类

js-challenges

https://github.com/Sunny-117/js-challenges

✨✨✨ Challenge your JavaScript programming limits step by step

mini-anythings

https://github.com/Sunny-117/mini-anything

🚀 Explore the source code of the front-end library and implement a super mini version

BOSScript

https://github.com/Sunny-117/BOSScript

Boss's direct recruitment and delivery, shutdown, one-stop service of the oil monkey script, allowing you to submit resumes overseas in just 2 minutes

rc-design

https://github.com/Sunny-117/rc-design

🗃️ rc-design is a component library developed for react, providing developers with a more lightweight and concise component library choice. Use tsx to write logic, less to write styles, dumi2 to write documentation sites, and jest+ts-jest+react-testing-library for unit testing.

cherry

https://github.com/Sunny-117/cherry

✨ A lightweight JavaScript packaging library based on magic-string and acorn, supporting tree-shaking

rollup-plugin-alias

https://github.com/Sunny-117/rollup-plugin-alias

🍣 A Rollup plugin for defining aliases when bundling packages.

commencer

https://github.com/Sunny-117/commencer

Starter template for xxx

tiny-react

https://github.com/Sunny-117/tiny-react

🌱 The closest implementation to the React source code

treejs

https://github.com/Sunny-117/treejs

🌱 Easy to learn, high-performance, and highly scalable Tree components, supporting Vuejs and React simultaneously

lodash-ts

https://github.com/Sunny-117/lodash-ts

tiny-vue

https://github.com/Sunny-117/tiny-vue

Native-project

https://github.com/Sunny-117/Native-project

Native JavaScript project collection, Github China latest version

shooks

https://github.com/Sunny-117/shooks

📦️ A high-quality & reliable React Hooks library.

mini-webpack

https://github.com/Sunny-117/mini-webpack

手写一个简易版的webpack

ts-lib-vite

https://github.com/Sunny-117/ts-lib-vite

A foundation for developing front-end utility libraries using Vite and TypeScript.

esbuild-plugins

https://github.com/Sunny-117/esbuild-plugins

packages of esbuild plugins

tiny-vite

https://github.com/Sunny-117/tiny-vite

⚡️ a lightweight frontend build tool designed to deliver swift development experiences and efficient build processes

vite-plugins

https://github.com/Sunny-117/vite-plugins

tiny-complier

实现超级 mini 的编译器 | codegen&compiler 生成代码 | 只需要 200 行代码 | 前端编译原理

https://github.com/Sunny-117/tiny-complier

keep-everyday

https://github.com/Sunny-117/keep-everyday

使用 Github Actions 来完成自动创建 issues 任务

text-image

https://github.com/Sunny-117/text-image

🐛🐛🐛 text-image 可以将文字、图片、视频进行「文本化」,只需要通过简单的配置即可使用

jsx-compilation

https://github.com/Sunny-117/jsx-compilation

🍻 实现 JSX 语法转成 JS 语法的编译器

awesome-native

https://github.com/Sunny-117/awesome-native

🔧 Collection of native JavaScript projects

vsc-delete-func

https://github.com/Sunny-117/vsc-delete-func

🍻🍻🍻 vscode plugins

eslint-plugin-reviewget

https://github.com/Sunny-117/eslint-plugin-reviewget

🚀当用户使用 getXXX get开头的函数的时候 如果不返回值的话 那么就会报错 🐛可以 fix 🎉用户可以自行配置是否 fix

babel-plugin-dev-debug

https://github.com/Sunny-117/babel-plugin-dev-debug

an babel plugin that for dev debug

webpack-expand-lib

https://github.com/Sunny-117/webpack-expand-lib

🚀 some expansion libs of webpack

network-speed-js

https://github.com/Sunny-117/network-speed-js

A small tool for testing network speed. It also has the ability to test internal and external networks.

TODO ...

- - +
Skip to content
On this page

My Projects

TODO:分类

js-challenges

https://github.com/Sunny-117/js-challenges

✨✨✨ Challenge your JavaScript programming limits step by step

mini-anythings

https://github.com/Sunny-117/mini-anything

🚀 Explore the source code of the front-end library and implement a super mini version

BOSScript

https://github.com/Sunny-117/BOSScript

Boss's direct recruitment and delivery, shutdown, one-stop service of the oil monkey script, allowing you to submit resumes overseas in just 2 minutes

rc-design

https://github.com/Sunny-117/rc-design

🗃️ rc-design is a component library developed for react, providing developers with a more lightweight and concise component library choice. Use tsx to write logic, less to write styles, dumi2 to write documentation sites, and jest+ts-jest+react-testing-library for unit testing.

cherry

https://github.com/Sunny-117/cherry

✨ A lightweight JavaScript packaging library based on magic-string and acorn, supporting tree-shaking

rollup-plugin-alias

https://github.com/Sunny-117/rollup-plugin-alias

🍣 A Rollup plugin for defining aliases when bundling packages.

commencer

https://github.com/Sunny-117/commencer

Starter template for xxx

tiny-react

https://github.com/Sunny-117/tiny-react

🌱 The closest implementation to the React source code

treejs

https://github.com/Sunny-117/treejs

🌱 Easy to learn, high-performance, and highly scalable Tree components, supporting Vuejs and React simultaneously

lodash-ts

https://github.com/Sunny-117/lodash-ts

tiny-vue

https://github.com/Sunny-117/tiny-vue

Native-project

https://github.com/Sunny-117/Native-project

Native JavaScript project collection, Github China latest version

shooks

https://github.com/Sunny-117/shooks

📦️ A high-quality & reliable React Hooks library.

mini-webpack

https://github.com/Sunny-117/mini-webpack

手写一个简易版的webpack

ts-lib-vite

https://github.com/Sunny-117/ts-lib-vite

A foundation for developing front-end utility libraries using Vite and TypeScript.

esbuild-plugins

https://github.com/Sunny-117/esbuild-plugins

packages of esbuild plugins

tiny-vite

https://github.com/Sunny-117/tiny-vite

⚡️ a lightweight frontend build tool designed to deliver swift development experiences and efficient build processes

vite-plugins

https://github.com/Sunny-117/vite-plugins

tiny-complier

实现超级 mini 的编译器 | codegen&compiler 生成代码 | 只需要 200 行代码 | 前端编译原理

https://github.com/Sunny-117/tiny-complier

keep-everyday

https://github.com/Sunny-117/keep-everyday

使用 Github Actions 来完成自动创建 issues 任务

text-image

https://github.com/Sunny-117/text-image

🐛🐛🐛 text-image 可以将文字、图片、视频进行「文本化」,只需要通过简单的配置即可使用

jsx-compilation

https://github.com/Sunny-117/jsx-compilation

🍻 实现 JSX 语法转成 JS 语法的编译器

awesome-native

https://github.com/Sunny-117/awesome-native

🔧 Collection of native JavaScript projects

vsc-delete-func

https://github.com/Sunny-117/vsc-delete-func

🍻🍻🍻 vscode plugins

eslint-plugin-reviewget

https://github.com/Sunny-117/eslint-plugin-reviewget

🚀当用户使用 getXXX get开头的函数的时候 如果不返回值的话 那么就会报错 🐛可以 fix 🎉用户可以自行配置是否 fix

babel-plugin-dev-debug

https://github.com/Sunny-117/babel-plugin-dev-debug

an babel plugin that for dev debug

webpack-expand-lib

https://github.com/Sunny-117/webpack-expand-lib

🚀 some expansion libs of webpack

network-speed-js

https://github.com/Sunny-117/network-speed-js

A small tool for testing network speed. It also has the ability to test internal and external networks.

TODO ...

+ + \ No newline at end of file diff --git a/hashmap.json b/hashmap.json index b25c6f75..774b6814 100644 --- a/hashmap.json +++ b/hashmap.json @@ -1 +1 @@ -{"article_cms.md":"c1044b71","fe-utils_tool.md":"91c28681","algorithm_🔥刷题之探索最优解.md":"fb0aa636","fe-utils_settimeout.md":"1c93d132","fe-utils_git.md":"1d44e18f","fe-utils_js工具库.md":"505fefb0","front-end-engineering_packagemanager.md":"870a6421","front-end-engineering_jscompatibility.md":"3de66593","front-end-engineering_css工程化.md":"1eeb0199","front-end-engineering_engineering-onepage.md":"2b741042","front-end-engineering_pnpm原理.md":"49360206","front-end-engineering_webpack5-mf.md":"91ebbb9a","front-end-engineering_modularization.md":"18e98187","front-end-engineering_webpack常用拓展.md":"bb19598b","getting-started.md":"595f0ebe","front-end-engineering_【完结】前端工程化.md":"a3ffd0ed","front-end-engineering_performance.md":"af2c1e0b","html-css_html.md":"d9aa3d72","html-css_drag.md":"b170a897","html-css_canvas-svg.md":"bfb80c2d","html-css_animation.md":"b2ff8169","html-css_css.md":"ddd23058","html-css_principle.md":"93000e5c","index.md":"019913d4","html-css_temop.md":"b228f630","interview_面试官:你还有问题要问我吗.md":"79bc0d6a","interview_算法笔试.md":"f1b65128","html-css_selector.md":"0d903f22","html-css_interview.md":"fda9f4cc","js_代理与反射.md":"febab8fb","react_fiber.md":"49ea3546","react_component-communication.md":"fbeef8d0","react_redux.md":"24126c0a","react_reactrouter.md":"b1e17836","react_dva.md":"b65ab2ae","react_event.md":"98256d41","js_迭代器和生成器.md":"b8ed63ac","react_hooks.md":"83649c16","react_context.md":"6c0ec1af","js_异步处理.md":"4b508803","react_react-redux-router.md":"9cdd99c2","react_lifecycle.md":"7cd8bdcf","react_transition.md":"2604ecdd","react_utils.md":"d8346e6a","react_umi.md":"d84db75a","react_render.md":"52622f4a","vue_ssr.md":"164a10c4","react_index.md":"cb2125b7","vue_challages.md":"84116bdf","vue_computed.md":"f33f359c","vue_directive.md":"f024212d","vue_diff.md":"164b8c74","vue_interviewer.md":"bde6357d","vue_component-communication.md":"279c54b3","react_react-interview.md":"388149ef","vue_lifecycle.md":"18e284d8","vue_keep-alive-lru.md":"d8134eaf","vue_nexttick.md":"d5f5dba7","vue_slot.md":"23c4c747","vue_v-model.md":"690f9ced","vue_vdom.md":"37d8ee68","vue_vue-cli.md":"989a2ed2","vue_vue-compile.md":"1a122d04","vue_vs.md":"b3e13812","vue_reactive.md":"d6595cba","ts_typescript-onepage.md":"4be35370","vue_vuex.md":"513169f6","vue_vue-router.md":"dfffe352","vue_vue-interview.md":"5ba58bf4","vue_vue3-onepage.md":"634bfdd6"} +{"algorithm_🔥刷题之探索最优解.md":"4f0a301b","article_cms.md":"62e92ffa","fe-utils_git.md":"6864052b","fragment_monorepo.md":"1135a208","fragment_babel-console.md":"9e4fa1c1","fe-utils_tool.md":"3087f19e","fe-utils_js工具库.md":"7df3c3c8","fragment_settimeout.md":"9ceec1c9","fragment_微内核架构.md":"28241a86","front-end-engineering_css工程化.md":"e50e4c60","front-end-engineering_packagemanager.md":"168ce36b","front-end-engineering_jscompatibility.md":"4f967061","front-end-engineering_engineering-onepage.md":"dbe37a23","front-end-engineering_modularization.md":"3f1f0912","front-end-engineering_pnpm原理.md":"b67c2dab","front-end-engineering_webpack5-mf.md":"17c9d3f6","getting-started.md":"30398209","front-end-engineering_webpack常用拓展.md":"6be37d04","front-end-engineering_【完结】前端工程化.md":"403363ad","front-end-engineering_performance.md":"7538e83b","html-css_html.md":"a59c2cd4","html-css_drag.md":"7fd332a5","html-css_animation.md":"7ae88207","html-css_canvas-svg.md":"b70ac3e1","html-css_principle.md":"e312152e","html-css_css.md":"b086bf50","html-css_temop.md":"5c8470c0","index.md":"a4868c83","interview_面试官:你还有问题要问我吗.md":"5cefe069","interview_算法笔试.md":"15e21765","js_代理与反射.md":"b257f2e5","html-css_selector.md":"dafcea22","html-css_interview.md":"f0810157","react_component-communication.md":"34050d2e","react_fiber.md":"edf56281","react_redux.md":"f5834d30","react_dva.md":"e46002fe","react_umi.md":"da0fa5cb","react_react-redux-router.md":"7331b91a","vue_slot.md":"6ca727af","react_transition.md":"4c0f0217","vue_directive.md":"1624070a","vue_keep-alive-lru.md":"eefc03a3","react_render.md":"be801c68","vue_interviewer.md":"68dbd613","vue_diff.md":"8edb2706","vue_component-communication.md":"db614df9","react_index.md":"8af8a7e5","react_lifecycle.md":"4bbc97a2","react_context.md":"d4981bd7","react_event.md":"9dcb76f3","react_hooks.md":"c549d3ab","vue_vue-compile.md":"e8335437","vue_vue-cli.md":"2d1efa87","vue_vs.md":"34734a24","vue_vuex.md":"66b86b1e","vue_vue-router.md":"d0e94995","react_utils.md":"403f4e09","vue_v-model.md":"01ef104c","vue_lifecycle.md":"ac98bf07","react_reactrouter.md":"bf5ace8d","vue_computed.md":"5aca8e09","vue_nexttick.md":"0e739ee7","vue_vdom.md":"edff2ad0","vue_challages.md":"92f7ed91","vue_vue-interview.md":"b9ab28ae","vue_ssr.md":"81ea6172","vue_reactive.md":"0d5b6606","vue_vue3-onepage.md":"200402a7","js_迭代器和生成器.md":"f1e890de","react_react-interview.md":"fac0dfec","js_异步处理.md":"529d4807","ts_typescript-onepage.md":"98d9abfd"} diff --git a/html-css/CSS.html b/html-css/CSS.html index 0c9e200f..c1a6bed6 100644 --- a/html-css/CSS.html +++ b/html-css/CSS.html @@ -6,13 +6,13 @@ CSS3 | Sunny's blog - - + + -
Skip to content
On this page

CSS3

introduction

兼容性前缀

prefix(前缀)browser
-webkitchrome/safari
-mozfirefox
-msIE
-oopera

1.历史

更新迭代,兼容性 ---- 加不加前缀

css
div {
+    
Skip to content
On this page

CSS3

introduction

兼容性前缀

prefix(前缀)browser
-webkitchrome/safari
-mozfirefox
-msIE
-oopera

1.历史

更新迭代,兼容性 ---- 加不加前缀

css
div {
   border-radius: ;
   -webkit-border-radius: ;
   -o-border-radius: ;
@@ -3047,8 +3047,8 @@
 区视口宽高中最大的一边分成100份 vmin 区视口宽高中最小的一边分成100份 css样式引入
 媒体查询不占用权重
 
- - + + \ No newline at end of file diff --git a/html-css/HTML.html b/html-css/HTML.html index d82b2540..3e3af896 100644 --- a/html-css/HTML.html +++ b/html-css/HTML.html @@ -6,13 +6,13 @@ HTML5 | Sunny's blog - - + + -
Skip to content
On this page

HTML5

大纲

新增的属性

  • placeholder
  • Calendar, date, time, email, url, search
  • ContentEditable
  • Draggable
  • Hidden
  • Content-menu
  • Data-Val(自定义属性)

新增的标签

  • 语义化标签
  • canvas
  • svg
  • Audio(声音播放)
  • Video(视频播放)

API

  • 移动端网页开发一般指的是 h5
  • 定位(需要地理位置的功能)
  • 重力感应(手机里面的陀螺仪(微信摇一摇,赛车转弯))
  • request-animation-frame(动画优化)
  • History 历史界面(控制当前页面的历史记录)
  • LocalStorage(本地存储,电脑/浏览器关闭都会保留);SessionStorage,(会话存储:窗口关闭就消失)。 都是存储信息(比如历史最高记录)
  • WebSocket(在线聊天,聊天室)
  • FileReader(文件读取,预览图)
  • WebWoker(文件的异步,提升性能,提升交互体验)
  • Fetch(传说中要替代 AJAX 的东西)

属性篇_input 新增 type

1.placeholder

html
<input type="text" placeholder="用户名/手机/邮箱" />
+    
Skip to content
On this page

HTML5

大纲

新增的属性

  • placeholder
  • Calendar, date, time, email, url, search
  • ContentEditable
  • Draggable
  • Hidden
  • Content-menu
  • Data-Val(自定义属性)

新增的标签

  • 语义化标签
  • canvas
  • svg
  • Audio(声音播放)
  • Video(视频播放)

API

  • 移动端网页开发一般指的是 h5
  • 定位(需要地理位置的功能)
  • 重力感应(手机里面的陀螺仪(微信摇一摇,赛车转弯))
  • request-animation-frame(动画优化)
  • History 历史界面(控制当前页面的历史记录)
  • LocalStorage(本地存储,电脑/浏览器关闭都会保留);SessionStorage,(会话存储:窗口关闭就消失)。 都是存储信息(比如历史最高记录)
  • WebSocket(在线聊天,聊天室)
  • FileReader(文件读取,预览图)
  • WebWoker(文件的异步,提升性能,提升交互体验)
  • Fetch(传说中要替代 AJAX 的东西)

属性篇_input 新增 type

1.placeholder

html
<input type="text" placeholder="用户名/手机/邮箱" />
 <input type="password" placeholder="请输入密码" />
 
<input type="text" placeholder="用户名/手机/邮箱" />
 <input type="password" placeholder="请输入密码" />
@@ -663,8 +663,8 @@
   this.postMessage(result);
 };
 

worker.js 里面可以通过 importScripts("./index.js")引入外部 js 文件

- - + + \ No newline at end of file diff --git a/html-css/animation.html b/html-css/animation.html index c62b6f83..a10fc213 100644 --- a/html-css/animation.html +++ b/html-css/animation.html @@ -6,13 +6,13 @@ 动画 | Sunny's blog - - + + -
Skip to content
On this page

动画

1.transition 过渡动画

css
div {
+    
Skip to content
On this page

动画

1.transition 过渡动画

css
div {
   width: 100px;
   height: 100px;
   background-color: red;
@@ -1573,8 +1573,8 @@
 ---- > paint喷色 (reflow重构) (repaint) 逻辑图(多层矢量图) ----->
 实际绘制(栅格化) 不设置就用cpu绘制 google chrome 自动调用 gpu
 
- - + + \ No newline at end of file diff --git a/html-css/canvas-svg.html b/html-css/canvas-svg.html index b923510b..3f566944 100644 --- a/html-css/canvas-svg.html +++ b/html-css/canvas-svg.html @@ -6,13 +6,13 @@ canvas 和 svg | Sunny's blog - - + + -
Skip to content
On this page

canvas 和 svg

canvas 画线

html
<canvas id="can" width="500px" height="300px"></canvas>
+    
Skip to content
On this page

canvas 和 svg

canvas 画线

html
<canvas id="can" width="500px" height="300px"></canvas>
 
<canvas id="can" width="500px" height="300px"></canvas>
 

注意:只能在行间样式设置大小,不能通过 css

javascript
var canvas = document.getElementById("can"); //画布
 var ctx = canvas.getContext("2d"); //画笔
@@ -809,8 +809,8 @@
   <line x1="100" y1="100" x2="200" y2="100" class="line1"></line>
 </svg>
 

总结:SVG 开发中不太用

- - + + \ No newline at end of file diff --git a/html-css/drag.html b/html-css/drag.html index 4d935b58..6b33f622 100644 --- a/html-css/drag.html +++ b/html-css/drag.html @@ -6,13 +6,13 @@ 拖拽 API | Sunny's blog - - + + -
Skip to content
On this page

拖拽 API

Drag 被拖拽元素

html
<div class="a" draggable="true"></div>
+    
Skip to content
On this page

拖拽 API

Drag 被拖拽元素

html
<div class="a" draggable="true"></div>
 <!-- 谷歌,safari可以,火狐,Ie不支持 -->
 
<div class="a" draggable="true"></div>
 <!-- 谷歌,safari可以,火狐,Ie不支持 -->
@@ -342,9 +342,9 @@
 "link";//放下时候的效果,只在drop里面设置 }
 
oDragTarget.ondrop = function (e) { e.dataTransfer.dropEffect =
 "link";//放下时候的效果,只在drop里面设置 }
-

试验不通过??

- - +

试验不通过??

+ + \ No newline at end of file diff --git a/html-css/interview.html b/html-css/interview.html index 3e889cb8..9468935a 100644 --- a/html-css/interview.html +++ b/html-css/interview.html @@ -6,13 +6,13 @@ HTML_CSS 面试题 | Sunny's blog - - + + -
Skip to content
On this page

HTML_CSS 面试题

1. 什么是 <!DOCTYPE>?是否需要在 HTML5 中使用?

它是 HTML 的文档声明,通过它告诉浏览器,使用哪一个 HTML 版本标准解析文档。

而文档声明有多种书写格式,对应不同的 HTML 版本,<!DOCTYPE> 这种书写是告诉浏览器,HTML5 的标准进行解析。

2. 什么是可替换元素,什么是非可替换元素,它们各自有什么特点?

可替换元素是指这样一种元素,它在页面中的大部分展现效果不由 CSS 决定。

比如 img 元素就是一个可替换元素,它在页面中显示出的效果主要取决于你连接的是什么图片,图片是什么它就展示什么,CSS** 虽然可以控制图片的尺寸位置,但永远无法控制图片本身**。

再比如,select** 元素也是一个典型的可替换元素**,它在页面上呈现的是用户操作系统上的下拉列表样式,因此,它的展现效果是由操作系统决定的。所以,同一个 select 元素,放到不同操作系统的电脑上会呈现不同的外观。

img、video、audio、大部分表单元素都属于可替换元素。 > 非可替换元素就是指的普通元素,它具体在页面上呈现什么,完全由 CSS 来决定。

3. srchref 的区别

srcsource 的缩写,它通常用于 img、video、audio、script 元素,通过 src 属性,可以指定外部资源的来源地址

hrefhyper reference 的缩写,意味「超引用」,它通常用于 a、link 元素,通过 href 属性,可以标识文档中引用的其他超文本。

4. 说说常用的 meta 标签

能够答出 meta 标签的意义,并且举出常规的 meta 标签使用方式

  1. meta 标签俗称描述网页数据的数据,比如网页本身的编码;
  2. 网页本身的作者,描述;
  3. 还有一些更高级的功能性特性,比如 dns 预解析,定义视口表现等
  4. 能结合 meta 提到一些 seo 相关的知识也可以有加分

    meta 标签提供关于 HTML 文档的元数据。元数据不会显示在页面上,但是对于机器是可读的。它可用于浏览器(如何显示内容或重新加载页面),搜索引擎(关键词),或其他 web 服务。

    1. content ,设置或返回 meta 元素的 content 属性的值 。
    2. http-equiv,把 content 属性连接到一个 HTTP 头部。
    3. name,把 content 属性连接到某个名称。
html
<meta charset="UTF-8" />
+    
Skip to content
On this page

HTML_CSS 面试题

1. 什么是 <!DOCTYPE>?是否需要在 HTML5 中使用?

它是 HTML 的文档声明,通过它告诉浏览器,使用哪一个 HTML 版本标准解析文档。

而文档声明有多种书写格式,对应不同的 HTML 版本,<!DOCTYPE> 这种书写是告诉浏览器,HTML5 的标准进行解析。

2. 什么是可替换元素,什么是非可替换元素,它们各自有什么特点?

可替换元素是指这样一种元素,它在页面中的大部分展现效果不由 CSS 决定。

比如 img 元素就是一个可替换元素,它在页面中显示出的效果主要取决于你连接的是什么图片,图片是什么它就展示什么,CSS** 虽然可以控制图片的尺寸位置,但永远无法控制图片本身**。

再比如,select** 元素也是一个典型的可替换元素**,它在页面上呈现的是用户操作系统上的下拉列表样式,因此,它的展现效果是由操作系统决定的。所以,同一个 select 元素,放到不同操作系统的电脑上会呈现不同的外观。

img、video、audio、大部分表单元素都属于可替换元素。 > 非可替换元素就是指的普通元素,它具体在页面上呈现什么,完全由 CSS 来决定。

3. srchref 的区别

srcsource 的缩写,它通常用于 img、video、audio、script 元素,通过 src 属性,可以指定外部资源的来源地址

hrefhyper reference 的缩写,意味「超引用」,它通常用于 a、link 元素,通过 href 属性,可以标识文档中引用的其他超文本。

4. 说说常用的 meta 标签

能够答出 meta 标签的意义,并且举出常规的 meta 标签使用方式

  1. meta 标签俗称描述网页数据的数据,比如网页本身的编码;
  2. 网页本身的作者,描述;
  3. 还有一些更高级的功能性特性,比如 dns 预解析,定义视口表现等
  4. 能结合 meta 提到一些 seo 相关的知识也可以有加分

    meta 标签提供关于 HTML 文档的元数据。元数据不会显示在页面上,但是对于机器是可读的。它可用于浏览器(如何显示内容或重新加载页面),搜索引擎(关键词),或其他 web 服务。

    1. content ,设置或返回 meta 元素的 content 属性的值 。
    2. http-equiv,把 content 属性连接到一个 HTTP 头部。
    3. name,把 content 属性连接到某个名称。
html
<meta charset="UTF-8" />
 <meta http-equiv="X-UA-Compatible" content="IE=edge" />
 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 
<meta charset="UTF-8" />
@@ -2507,8 +2507,8 @@
 margin: 150px
 top: 250px
 

CSS 中 px、em、rem 的区别

px:绝对单位,页面按精确像素显示; em:相对单位,基准点为父节点字体的大小; rem:相对单位,可理解为“root em”,相对根结点 html 的字体大小来计算,CSS3 新属性;

div 设置成 inline-block,有空隙是什么原因呢?如何解决

https://juejin.cn/post/6991762098111905806

css 中 z-index 属性考察

问:box1 在 box2 上面,还是 box2 在 box1 上面? 答案:box1 在 box2 上面 考察是否对z-index生效条件了解,概念: z-index 只对定位元素有效,这里的定位指:absolute、relative、fixed 和 inherit,其中 inherit 取决于父元素,如果父元素没有设置定位(absolute、relative、fixed)则 z-index 无效,注意低版本 IE 浏览器不支持这个值 除了给出答案,还了解 z-index 属性其他注意点,浏览器兼容性等 4. 4.0 分:

css3 GPU 加速

css3 中 2d 变化和 3d 变化有什么区别,性能对比如何,是否用到了 gpu 加速?

  1. 对于 translate 和 scale,2d 变换时有两条轴,对于 rotate,2d 变化时只有一条轴。3d 变化时,都存在相对 x、y、z 轴的变化。
  2. 2d 和 3d 变化都用到了 gpu,因为 gpu 擅长图像的变化操作
  3. 3d 变化性能更好,虽然 2d 和 3d 都用到了 gpu,但是 2d 变换的元素只有在动画过程中才会提到复合层(composite layer),而 3d 变换的元素一开始就被提到了符合层。

请问 HTML 中如何通过<script>加载 JAVAScript 脚本?如果同步阻塞加载,脚本过大影响页面渲染,如何优化?

默认情况下,浏览器是同步加载 JavaScript 脚本,即渲染引擎遇到

回答出浏览器同步加载,渲染引擎遇到 script 标签停止渲染,等待脚本加载执行完成,回答出脚本加载执行加载时机,通过(defer 或 async)异步加载方式优化防止渲染阻塞

如何 html 中开启和关闭 DNS 预读取

在文档中使用值为 http-equiv 的   标签:可以通过将 content 的参数设置为“on”来改变设置 然后通过使用 rel 属性值为 link type 中的 dns-prefetch 的   标签来对特定域名进行预读取: 总分 4 分,不知道不得分,知道通过 meta 值设置给 2 分, meta 写正确+1 分,link 写正确+1

要求:知道 dns 预读取和预读取的意义。如何开启 dns 预读取

请简述 transform 原理

transform 同时设置多个值,比如 translate(30px,0px) rotate(45deg)和 rotate(45deg) translate(30px,0px)效果是否相同,为什么?不相同 两个变换是有先后顺序的,是先转后移还是先移后转,比较容易比划出来 从原理上来说,变换最后是用矩阵乘向量来运算的,矩阵乘法不具有交换律,因此 M1_M2 和 M2_M1 的结果是不同的

答对了,原理也说出来了,为什么要用矩阵来实现变换也说得出来

如何解决 1px 问题

核心思路: 在 web 中,浏览器为我们提供了 window.devicePixelRatio 来帮助我们获取 dpr。在 css 中,可以使用媒体查询 min-device-pixel-ratio,区分 dpr 我们根据这个像素比,来算出他对应应该有的大小,但是暴露个非常大的兼容问题

解决过程会出现什么问题呢?

说说什么是视口吧(viewport)

viewport 即视窗、视口,用于显示网页部分的区域,在 PC 端视口即是浏览器窗口区域,在移动端,为了让页面展示更多的内容,视窗的宽度默认不为设备的宽度,在移动端视窗有三个概念:布局视窗、视觉视窗、理想视窗

  • 布局视窗:在浏览器窗口 css 的布局区域,布局视口的宽度限制 css 布局的宽。为了能在移动设备上正常显示那些为 pc 端浏览器设计的网站,移动设备上的浏览器都会把自己默认的 viewport 设为 980px 或其他值,一般都比移动端浏览器可视区域大很多,所以就会出现浏览器出现横向滚动条的情况
  • 视觉视窗:终端设备显示网页的区域
  • 理想视窗:针对当前设备最理想的展示页面的视窗,不会出现横向滚动条,页面刚好全部展现在视窗内,理想视窗也就是终端屏幕的宽度。

移动端视口配置怎么配的?

移动端适配有哪些方案知道吗?

1、rem 布局 2、vw、vh 布局 3、媒体查询响应式布局

说说媒体查询吧?

通过媒体查询,可以针对不同的屏幕进行单独设置,但是针对所有的屏幕尺寸做适配显然是不合理的,但是可以用来处理极端情况(例如 IPad 大屏设备)或做简单的适配(隐藏元素或改变元素位置)

说说 rem 适配吧

rem 是 CSS3 新增的一个相对单位,这个单位引起了广泛关注。这个单位与 em 有什么区别呢?区别在于使用 rem 为元素设定字体大小时,仍然是相对大小,但相对的只是 HTML 根元素。这个单位可谓集相对大小和绝对大小的优点于一身,通过它既可以做到只修改根元素就成比例地调整所有字体大小,又可以避免字体大小逐层复合的连锁反应。目前,除了 IE8 及更早版本外,所有浏览器均已支持 rem。对于不支持它的浏览器,应对方法也很简单,就是多写一个绝对单位的声明。这些浏览器会忽略用 rem 设定的字体大小

rem 的具体适配方案知道吗?

flexible.js 适配:阿里早期开源的一个移动端适配解决方案

因为当年 viewport 在低版本安卓设备上还有兼容问题,而 vw,vh 还没能实现所有浏览器兼容,所以 flexible 方案用 rem 来模拟 vmin 来实现在不同设备等比缩放的“通用”方案,之所以说是通用方案,是因为他这个方案是根据设备大小去判断页面的展示空间大小即屏幕大小,然后根据屏幕大小去百分百还原设计稿,从而让人看到的效果(展示范围)是一样的,这样一来,苹果 5 和苹果 6p 屏幕如果你按照设计稿还原的话,字体大小实际上不一样,而人们在一样的距离上希望看到的大小其实是一样的,本质上,用户使用更大的屏幕,是想看到更多的内容,而不是更大的字

rem 的弊端知道吗

弊端之一:和根元素 font-size 值强耦合,系统字体放大或缩小时,会导致布局错乱 弊端之二:html 文件头部需插入一段 js 代码

说说 vw/vh 适配

vh、vw 方案即将视觉视口宽度 window.innerWidth 和视觉视口高度 window.innerHeight 等分为 100 份

vw 和 vh 有啥不足吗?

vw 和 vh 的兼容性: Android 4.4 之下和 iOS 8 以下的版本有一定的兼容性问题(但是目前这两版本已经很少有人使用了) rem 的兼容性:

- - + + \ No newline at end of file diff --git a/html-css/principle.html b/html-css/principle.html index 0e9717cd..1c1e1fe7 100644 --- a/html-css/principle.html +++ b/html-css/principle.html @@ -6,13 +6,13 @@ 显示器的成像原理 | Sunny's blog - - + + -
Skip to content
On this page

显示器的成像原理

空间混色法 rgb 实质上并排排列(验证)

css
.wrapper {
+    
Skip to content
On this page

显示器的成像原理

空间混色法 rgb 实质上并排排列(验证)

css
.wrapper {
   display: flex;
 }
 .demo {
@@ -43,8 +43,8 @@
   /*红*/
 }
 

像素:--->红绿蓝像点----->空间混色

最小的单位:像点。像素由 3 个像点构成。

空间混色法应用

crt 显示屏

lcd 液晶屏

点距:crt 显示屏求点距的方法的意义,是几乎所有屏幕都通用的

像素的大小:点距

物理像素:设备出厂时,像素的大小

dpi:1 英寸所能容纳的像素点数

1 英寸= 2.54cm

dpi 打印机在一英寸屏幕里面可以打印多少墨点

ppi 一英寸所能容纳的像素点数(点距数)

参照像素

96dpi 一臂之遥的视角去看,显示出的具体大小

标杆 1/96*英寸

css 像素=逻辑像素

设备像素比 dpr = 物理像素/css 像素

衡量屏幕好不好:不看分辨率(分辨率:固定宽高下,展示的像素点数)

看的是 dpi

- - + + \ No newline at end of file diff --git a/html-css/selector.html b/html-css/selector.html index 35e2c22b..4489aeea 100644 --- a/html-css/selector.html +++ b/html-css/selector.html @@ -6,13 +6,13 @@ 选择器 | Sunny's blog - - + + -
Skip to content
On this page

选择器

1.关系型选择器模式(不常用)

E+F:下一个满足条件的兄弟元素节点

html
<div>div</div>
+    
Skip to content
On this page

选择器

1.关系型选择器模式(不常用)

E+F:下一个满足条件的兄弟元素节点

html
<div>div</div>
 <p>1</p>
 <p>2</p>
 <p>3</p>
@@ -1329,8 +1329,8 @@
   </body>
 </html>
 
- - + + \ No newline at end of file diff --git a/html-css/temop.html b/html-css/temop.html index 9a7b7a60..9dc43236 100644 --- a/html-css/temop.html +++ b/html-css/temop.html @@ -6,15 +6,15 @@ Sunny's blog | Sunny's blog - - + + -
Skip to content
On this page
- - +
Skip to content
On this page
+ + \ No newline at end of file diff --git a/index.html b/index.html index 8071bc35..f86aebd8 100644 --- a/index.html +++ b/index.html @@ -6,15 +6,15 @@ Sunny's blog | Sunny's blog - - + + - - - + + + \ No newline at end of file diff --git "a/interview/\347\256\227\346\263\225\347\254\224\350\257\225.html" "b/interview/\347\256\227\346\263\225\347\254\224\350\257\225.html" index 00c97f34..806352a9 100644 --- "a/interview/\347\256\227\346\263\225\347\254\224\350\257\225.html" +++ "b/interview/\347\256\227\346\263\225\347\254\224\350\257\225.html" @@ -6,13 +6,13 @@ 算法笔试 | Sunny's blog - - + + -
Skip to content
On this page

算法笔试

练习地址:

https://www.nowcoder.com/test/question/93bc96f6d19f4795a4b893ee16e97654?pid=27976983&tid=55388436

A+B

javascript
var line
+    
Skip to content
On this page

算法笔试

练习地址:

https://www.nowcoder.com/test/question/93bc96f6d19f4795a4b893ee16e97654?pid=27976983&tid=55388436

A+B

javascript
var line
 while (line = readline()) {
     var lines = line.split(' ');
     var a = parseInt(lines[0]);
@@ -299,8 +299,8 @@
 let line = gets(10000).trim();
 print(line.length);
 

trim()

  • 方法用于删除字符串的头尾空白符,空白符包括:空格、制表符 tab、回车、换行符 等其他空白符等。
  • 不适用于 null, undefined, Number 类型。
- - + + \ No newline at end of file diff --git "a/interview/\351\235\242\350\257\225\345\256\230\357\274\232\344\275\240\350\277\230\346\234\211\351\227\256\351\242\230\350\246\201\351\227\256\346\210\221\345\220\227.html" "b/interview/\351\235\242\350\257\225\345\256\230\357\274\232\344\275\240\350\277\230\346\234\211\351\227\256\351\242\230\350\246\201\351\227\256\346\210\221\345\220\227.html" index 0913dcaa..37ed86b0 100644 --- "a/interview/\351\235\242\350\257\225\345\256\230\357\274\232\344\275\240\350\277\230\346\234\211\351\227\256\351\242\230\350\246\201\351\227\256\346\210\221\345\220\227.html" +++ "b/interview/\351\235\242\350\257\225\345\256\230\357\274\232\344\275\240\350\277\230\346\234\211\351\227\256\351\242\230\350\246\201\351\227\256\346\210\221\345\220\227.html" @@ -6,15 +6,15 @@ 面试官:你还有问题要问我吗? | Sunny's blog - - + + -
Skip to content
On this page

面试官:你还有问题要问我吗?

下面列表里的问题对于参加技术面试的人来说可能有些用。

列表里的问题并不一定适用于某个特定的职位或者工作类型,也没有排序

最开始的时候这只是我自己的问题列表,但是慢慢地添加了一些我觉得可能让我对这家公司亮红牌的问题。

我也注意到被我面试的人提问我的问题太少了,感觉他们挺浪费机会的。

预期使用方式

  • 检查一下哪些问题你感兴趣
  • 检查一下哪些是你可以自己在网上找到答案的
  • 找不到的话就向面试官提问

绝对不要想把这个列表里的每个问题都问一遍。(尊重面试官的时间,而且你可以通过查找已经发布的答案来显示 你的主动性)

请记住事情总是灵活的,组织的结构调整也会经常发生。拥有一个 bug 追踪系统并不会保证高效处理 bug。 CI/CD (持续集成系统) 也不一定保证交付时间会很短。

职责

  • On-call (电话值班)的计划或者规定是什么?值班或者遇到问题加班时候有加班费吗?
  • 我的日常工作是什么?
  • 有给我设定的特定目标吗?
  • 团队里面初级和高级工程师的比例是多少?(有计划改变吗)
  • 入职培训 (onboarding) 会是什么样的?
  • 每个开发者有多大的自由来做出决定?
  • 在你看来,这个工作做到什么程度算成功?
  • 你期望我在最初的一个月 / 三个月能够完成什么?
  • 试用期结束的时候,你会怎么样衡量我的绩效?
  • 自己单独的开发活动和按部就班工作的比例大概是怎样的?
  • 一个典型的一天或者一周的工作是怎样安排的?
  • 对我的申请你有什么疑虑么?
  • 在这份工作上,我将会和谁紧密合作?
  • 我的直接上级他们的上级都是什么样的管理风格?(事无巨细还是着眼宏观)
  • 我在这个岗位上应该如何发展?会有哪些机会?
  • 每天预期 / 核心工作时间是多少小时?
  • 我入职的岗位是新增还是接替之前离职的同事?(是否有技术债需要还)?(zh)
  • 入职之后在哪个项目组,项目是新成立还是已有的?(zh)

技术

  • 公司常用的技术栈是什么?
  • 你们怎么使用源码控制系统?
  • 你们怎么测试代码?
  • 你们怎么追踪 bug?
  • 你们怎样监控项目?
  • 你们怎么集成和部署代码改动?是使用持续集成和持续部署吗 (CI/CD)?
  • 你们的基础设施搭建在版本管理系统里吗?或者是代码化的吗?
  • 从计划到完成一项任务的工作流是什么样的?
  • 你们如何准备故障恢复?
  • 有标准的开发环境吗?是强制的吗?
  • 你们需要花费多长时间来给产品搭建一个本地测试环境?(分钟 / 小时 / 天)
  • 你们需要花费多长时间来响应代码或者依赖中的安全问题?
  • 所有的开发者都可以使用他们电脑的本地管理员权限吗?
  • 介绍一下你们的技术原则或者展望。
  • 你们的代码有开发文档吗?有没有单独的供消费者阅读的文档?
  • 你们有更高层次的文档吗?比如说 ER 图,数据库范式
  • 你们使用静态代码分析吗?
  • 你们如何管理内部和外部的数字资产?
  • 你们如何管理依赖?
  • 公司是否有技术分享交流活动?有的话,多久一次呢?(zh)
  • 你们的数据库是怎么进行版本控制的?(zh)
  • 业务需求有没有文档记录?是如何记录的?(zh)

团队

  • 工作是怎么组织的?
  • 团队内 / 团队间的交流通常是怎样的?
  • 你们使用什么工具来做项目组织?你的实际体会是什么?
  • 如果遇到不同的意见怎样处理?
  • 谁来设定优先级 / 计划?
  • 如果团队没能赶上预期发布日期怎么办?
  • 每周都会开什么类型的会议?
  • 会有定期的和上级的一对一谈话吗?
  • 产品 / 服务的规划是什么样的?(n 周一发布 / 持续部署 / 多个发布流 / ...)
  • 生产环境发生事故了怎么办?是否有不批评人而分析问题的文化?
  • 有没有一些团队正在经历还尚待解决的挑战?
  • 你们如何跟踪进度?
  • 预期和目标是如何设定的?谁来设定?
  • Code Review 如何实施?
  • 给我介绍下团队里一个典型的 sprint
  • 你们如何平衡技术和商业目标?
  • 你们如何共享知识?
  • 团队有多大?
  • 公司技术团队的架构和人员组成?(zh)
  • 团队内开发、产品、运营哪一方是需求的主要提出方?哪一方更强势?(zh)

问未来的同事

  • 开发者倾向于从哪里学习?
  • 你对在这里工作最满意的地方是?
  • 最不满意的呢?
  • 如果可以的话,你想改变哪里?
  • 团队最老的成员在这里多久了?
  • 在小团队中,有没有出现成员性格互相冲突的情况?最后是如何解决的?

公司

  • 公司为什么在招人?(产品发展 / 新产品 / 波动...)
  • 有没有会议 / 旅行预算?使用的规定是什么?
  • 晋升流程是怎样的?要求 / 预期是怎样沟通的?
  • 绩效评估流程是怎样的?
  • 技术和管理两条职业路径是分开的吗?
  • 对于多元化招聘的现状或者观点是什么?
  • 有公司级别的学习资源吗?比如电子书订阅或者在线课程?
  • 有获取证书的预算吗?
  • 公司的成熟度如何?(早期寻找方向 / 有内容的工作 / 维护中 / ...)
  • 我可以为开源项目做贡献吗?是否需要审批?
  • 你认为公司未来五年或者十年会发展成什么样子?
  • 公司的大多数员工是如何看待整洁代码的?
  • 你上次注意到有人成长是什么时候?他们在哪方面成长了?
  • 在这里成功的定义是什么?如何衡量成功?
  • 有体育活动或者团建么?
  • 有内部的黑客马拉松活动吗?
  • 公司支持开源项目吗?
  • 有竞业限制或者保密协议需要签吗?
  • 你们认为公司文化中的空白是什么?
  • 能够跟我说一公司处于不良情况,以及如何处理的故事吗?
  • 您在这工作了多久了?您觉得体验如何?(zh)
  • 大家为什么会喜欢这里?(zh)
  • 公司的调薪制度是如何的?(zh)

社会问题

  • 你们关于多元化招聘什么看法?
  • 你们的公司文化如何?你认为有什么空白么?
  • 这里的工作生活平衡地怎么样?
  • 公司对气候变化有什么态度吗?

冲突

  • 不同的意见如何处理?
  • 如果被退回了会怎样?(“这个在预计的时间内做不完”)
  • 当团队有压力并且在超负荷工作的时候怎么处理?
  • 如果有人注意到了在流程或者技术等其他方面又改进的地方,怎么办?
  • 当管理层的预期和工程师的绩效之间有差距的时候如何处理?
  • 能给我讲一个公司深处有毒环境以及如何处理的故事吗?
  • 如果在公司内你的同事因涉嫌性侵犯他人而被调查,请问你会如何处理?
  • 假设我自己很不幸是在公司内被性侵的受害者,在公司内部有没有争取合法权益的渠道?

商业

  • 你们现在盈利吗?
  • 如果没有的话,还需要多久?
  • 公司的资金来源是什么?谁影响或者制定高层计划或方向?
  • 你们如何挣钱?
  • 什么阻止了你们挣更多的钱?
  • 公司未来一年的增长计划怎样?五年呢?
  • 你们认为什么是你们的竞争优势?
  • 你们的竞争优势是什么?
  • 公司未来的商业规划是怎样的?有上市的计划吗?(zh)

远程工作

  • 远程工作和办公室工作的比例是多少?
  • 公司提供硬件吗?更新计划如何?
  • 使用自己的硬件办公可以吗?现在有政策吗?
  • 额外的附件和家具可以通过公司购买吗?这方面是否有预算?
  • 有共享办公或者上网的预算吗?
  • 多久需要去一次办公室?
  • 公司的会议室是否一直是视频会议就绪的?

办公室布局

  • 办公室的布局如何?(开放的 / 小隔间 / 独立办公室)
  • 有没有支持 / 市场 / 或者其他需要大量打电话的团队在我的团队旁边办公?

终极问题

  • 该职位为何会空缺?
  • 公司如何保证人才不流失?
  • 这份工作 / 团队 / 公司最好和最坏的方面是?
  • 你最开始为什么选择了这家公司?
  • 你为什么留在这家公司?

待遇

  • 如果有奖金计划的话,奖金如何分配?
  • 如果有奖金计划的话,过去的几年里通常会发百分之多少的奖金?
  • 有五险一金(zh)/401k(us)或者其他退休养老金等福利吗?
  • 五险一金中,补充公积金一般交多少比例?/401k一般交多少比例?我可以自己选择这一比例吗?
  • 有什么医疗保险吗?如果有的话何时开始?
  • 有额外商业保险吗?例如人寿保险和额外的养老/医疗保险?
  • 更换工作地点,公司付费吗?

休假

  • 带薪休假时间有多久?
  • 病假和事假是分开的还是一起算?
  • 我可以提前使用假期时间吗?也就是说应休假期是负的?
  • 假期的更新策略是什么样的?也就是说未休的假期能否滚入下一周期
  • 照顾小孩的政策如何?
  • 无薪休假政策是什么样的?
  • 学术性休假政策是怎么样的?

参考链接

Find more inspiration for questions in:

https://github.com/viraptor/reverse-interview

翻译:

English

Korean

Portuguese

繁體中文

- - +
Skip to content
On this page

面试官:你还有问题要问我吗?

下面列表里的问题对于参加技术面试的人来说可能有些用。

列表里的问题并不一定适用于某个特定的职位或者工作类型,也没有排序

最开始的时候这只是我自己的问题列表,但是慢慢地添加了一些我觉得可能让我对这家公司亮红牌的问题。

我也注意到被我面试的人提问我的问题太少了,感觉他们挺浪费机会的。

预期使用方式

  • 检查一下哪些问题你感兴趣
  • 检查一下哪些是你可以自己在网上找到答案的
  • 找不到的话就向面试官提问

绝对不要想把这个列表里的每个问题都问一遍。(尊重面试官的时间,而且你可以通过查找已经发布的答案来显示 你的主动性)

请记住事情总是灵活的,组织的结构调整也会经常发生。拥有一个 bug 追踪系统并不会保证高效处理 bug。 CI/CD (持续集成系统) 也不一定保证交付时间会很短。

职责

  • On-call (电话值班)的计划或者规定是什么?值班或者遇到问题加班时候有加班费吗?
  • 我的日常工作是什么?
  • 有给我设定的特定目标吗?
  • 团队里面初级和高级工程师的比例是多少?(有计划改变吗)
  • 入职培训 (onboarding) 会是什么样的?
  • 每个开发者有多大的自由来做出决定?
  • 在你看来,这个工作做到什么程度算成功?
  • 你期望我在最初的一个月 / 三个月能够完成什么?
  • 试用期结束的时候,你会怎么样衡量我的绩效?
  • 自己单独的开发活动和按部就班工作的比例大概是怎样的?
  • 一个典型的一天或者一周的工作是怎样安排的?
  • 对我的申请你有什么疑虑么?
  • 在这份工作上,我将会和谁紧密合作?
  • 我的直接上级他们的上级都是什么样的管理风格?(事无巨细还是着眼宏观)
  • 我在这个岗位上应该如何发展?会有哪些机会?
  • 每天预期 / 核心工作时间是多少小时?
  • 我入职的岗位是新增还是接替之前离职的同事?(是否有技术债需要还)?(zh)
  • 入职之后在哪个项目组,项目是新成立还是已有的?(zh)

技术

  • 公司常用的技术栈是什么?
  • 你们怎么使用源码控制系统?
  • 你们怎么测试代码?
  • 你们怎么追踪 bug?
  • 你们怎样监控项目?
  • 你们怎么集成和部署代码改动?是使用持续集成和持续部署吗 (CI/CD)?
  • 你们的基础设施搭建在版本管理系统里吗?或者是代码化的吗?
  • 从计划到完成一项任务的工作流是什么样的?
  • 你们如何准备故障恢复?
  • 有标准的开发环境吗?是强制的吗?
  • 你们需要花费多长时间来给产品搭建一个本地测试环境?(分钟 / 小时 / 天)
  • 你们需要花费多长时间来响应代码或者依赖中的安全问题?
  • 所有的开发者都可以使用他们电脑的本地管理员权限吗?
  • 介绍一下你们的技术原则或者展望。
  • 你们的代码有开发文档吗?有没有单独的供消费者阅读的文档?
  • 你们有更高层次的文档吗?比如说 ER 图,数据库范式
  • 你们使用静态代码分析吗?
  • 你们如何管理内部和外部的数字资产?
  • 你们如何管理依赖?
  • 公司是否有技术分享交流活动?有的话,多久一次呢?(zh)
  • 你们的数据库是怎么进行版本控制的?(zh)
  • 业务需求有没有文档记录?是如何记录的?(zh)

团队

  • 工作是怎么组织的?
  • 团队内 / 团队间的交流通常是怎样的?
  • 你们使用什么工具来做项目组织?你的实际体会是什么?
  • 如果遇到不同的意见怎样处理?
  • 谁来设定优先级 / 计划?
  • 如果团队没能赶上预期发布日期怎么办?
  • 每周都会开什么类型的会议?
  • 会有定期的和上级的一对一谈话吗?
  • 产品 / 服务的规划是什么样的?(n 周一发布 / 持续部署 / 多个发布流 / ...)
  • 生产环境发生事故了怎么办?是否有不批评人而分析问题的文化?
  • 有没有一些团队正在经历还尚待解决的挑战?
  • 你们如何跟踪进度?
  • 预期和目标是如何设定的?谁来设定?
  • Code Review 如何实施?
  • 给我介绍下团队里一个典型的 sprint
  • 你们如何平衡技术和商业目标?
  • 你们如何共享知识?
  • 团队有多大?
  • 公司技术团队的架构和人员组成?(zh)
  • 团队内开发、产品、运营哪一方是需求的主要提出方?哪一方更强势?(zh)

问未来的同事

  • 开发者倾向于从哪里学习?
  • 你对在这里工作最满意的地方是?
  • 最不满意的呢?
  • 如果可以的话,你想改变哪里?
  • 团队最老的成员在这里多久了?
  • 在小团队中,有没有出现成员性格互相冲突的情况?最后是如何解决的?

公司

  • 公司为什么在招人?(产品发展 / 新产品 / 波动...)
  • 有没有会议 / 旅行预算?使用的规定是什么?
  • 晋升流程是怎样的?要求 / 预期是怎样沟通的?
  • 绩效评估流程是怎样的?
  • 技术和管理两条职业路径是分开的吗?
  • 对于多元化招聘的现状或者观点是什么?
  • 有公司级别的学习资源吗?比如电子书订阅或者在线课程?
  • 有获取证书的预算吗?
  • 公司的成熟度如何?(早期寻找方向 / 有内容的工作 / 维护中 / ...)
  • 我可以为开源项目做贡献吗?是否需要审批?
  • 你认为公司未来五年或者十年会发展成什么样子?
  • 公司的大多数员工是如何看待整洁代码的?
  • 你上次注意到有人成长是什么时候?他们在哪方面成长了?
  • 在这里成功的定义是什么?如何衡量成功?
  • 有体育活动或者团建么?
  • 有内部的黑客马拉松活动吗?
  • 公司支持开源项目吗?
  • 有竞业限制或者保密协议需要签吗?
  • 你们认为公司文化中的空白是什么?
  • 能够跟我说一公司处于不良情况,以及如何处理的故事吗?
  • 您在这工作了多久了?您觉得体验如何?(zh)
  • 大家为什么会喜欢这里?(zh)
  • 公司的调薪制度是如何的?(zh)

社会问题

  • 你们关于多元化招聘什么看法?
  • 你们的公司文化如何?你认为有什么空白么?
  • 这里的工作生活平衡地怎么样?
  • 公司对气候变化有什么态度吗?

冲突

  • 不同的意见如何处理?
  • 如果被退回了会怎样?(“这个在预计的时间内做不完”)
  • 当团队有压力并且在超负荷工作的时候怎么处理?
  • 如果有人注意到了在流程或者技术等其他方面又改进的地方,怎么办?
  • 当管理层的预期和工程师的绩效之间有差距的时候如何处理?
  • 能给我讲一个公司深处有毒环境以及如何处理的故事吗?
  • 如果在公司内你的同事因涉嫌性侵犯他人而被调查,请问你会如何处理?
  • 假设我自己很不幸是在公司内被性侵的受害者,在公司内部有没有争取合法权益的渠道?

商业

  • 你们现在盈利吗?
  • 如果没有的话,还需要多久?
  • 公司的资金来源是什么?谁影响或者制定高层计划或方向?
  • 你们如何挣钱?
  • 什么阻止了你们挣更多的钱?
  • 公司未来一年的增长计划怎样?五年呢?
  • 你们认为什么是你们的竞争优势?
  • 你们的竞争优势是什么?
  • 公司未来的商业规划是怎样的?有上市的计划吗?(zh)

远程工作

  • 远程工作和办公室工作的比例是多少?
  • 公司提供硬件吗?更新计划如何?
  • 使用自己的硬件办公可以吗?现在有政策吗?
  • 额外的附件和家具可以通过公司购买吗?这方面是否有预算?
  • 有共享办公或者上网的预算吗?
  • 多久需要去一次办公室?
  • 公司的会议室是否一直是视频会议就绪的?

办公室布局

  • 办公室的布局如何?(开放的 / 小隔间 / 独立办公室)
  • 有没有支持 / 市场 / 或者其他需要大量打电话的团队在我的团队旁边办公?

终极问题

  • 该职位为何会空缺?
  • 公司如何保证人才不流失?
  • 这份工作 / 团队 / 公司最好和最坏的方面是?
  • 你最开始为什么选择了这家公司?
  • 你为什么留在这家公司?

待遇

  • 如果有奖金计划的话,奖金如何分配?
  • 如果有奖金计划的话,过去的几年里通常会发百分之多少的奖金?
  • 有五险一金(zh)/401k(us)或者其他退休养老金等福利吗?
  • 五险一金中,补充公积金一般交多少比例?/401k一般交多少比例?我可以自己选择这一比例吗?
  • 有什么医疗保险吗?如果有的话何时开始?
  • 有额外商业保险吗?例如人寿保险和额外的养老/医疗保险?
  • 更换工作地点,公司付费吗?

休假

  • 带薪休假时间有多久?
  • 病假和事假是分开的还是一起算?
  • 我可以提前使用假期时间吗?也就是说应休假期是负的?
  • 假期的更新策略是什么样的?也就是说未休的假期能否滚入下一周期
  • 照顾小孩的政策如何?
  • 无薪休假政策是什么样的?
  • 学术性休假政策是怎么样的?

参考链接

Find more inspiration for questions in:

https://github.com/viraptor/reverse-interview

翻译:

English

Korean

Portuguese

繁體中文

+ + \ No newline at end of file diff --git "a/js/\344\273\243\347\220\206\344\270\216\345\217\215\345\260\204.html" "b/js/\344\273\243\347\220\206\344\270\216\345\217\215\345\260\204.html" index dc5b66a6..db9d3f56 100644 --- "a/js/\344\273\243\347\220\206\344\270\216\345\217\215\345\260\204.html" +++ "b/js/\344\273\243\347\220\206\344\270\216\345\217\215\345\260\204.html" @@ -6,13 +6,13 @@ 代理与反射 | Sunny's blog - - + + -
Skip to content
On this page

代理与反射

属性描述符

Property Descriptor

Property Descriptor 属性描述符 是一个普通对象,用于描述一个属性的相关信息 通过Object.getOwnPropertyDescriptor(对象, 属性名)可以得到一个对象的某个属性的属性描述符

javascript
const desc = Object.getOwnPropertyDescriptor(obj, "a")
+    
Skip to content
On this page

代理与反射

属性描述符

Property Descriptor

Property Descriptor 属性描述符 是一个普通对象,用于描述一个属性的相关信息 通过Object.getOwnPropertyDescriptor(对象, 属性名)可以得到一个对象的某个属性的属性描述符

javascript
const desc = Object.getOwnPropertyDescriptor(obj, "a")
 console.log(desc);
 
const desc = Object.getOwnPropertyDescriptor(obj, "a")
 console.log(desc);
@@ -959,8 +959,8 @@
 const sumProxy = validatorFunction(sum, "number", "number")
 console.log(sumProxy(1, 2))
 
- - + + \ No newline at end of file diff --git "a/js/\345\274\202\346\255\245\345\244\204\347\220\206.html" "b/js/\345\274\202\346\255\245\345\244\204\347\220\206.html" index ad8bdee8..d849557d 100644 --- "a/js/\345\274\202\346\255\245\345\244\204\347\220\206.html" +++ "b/js/\345\274\202\346\255\245\345\244\204\347\220\206.html" @@ -6,13 +6,13 @@ 异步处理 | Sunny's blog - - + + -
Skip to content
On this page

异步处理

事件循环 eventLoop

JS 运行的环境称之为宿主环境。JS语言不只运行在浏览器

执行栈:call stack(一个数据结构),用于存放各种函数的执行环境,每一个函数执行之前,它的相关信息会加入到执行栈。函数调用之前,创建执行环境,然后加入到执行栈;函数调用之后,销毁执行环境。 JS 引擎永远执行的是执行栈的最顶部。

异步函数:某些函数不会立即执行,需要等到某个时机到达后才会执行,这样的函数称之为异步函数。比如事件处理函数。异步函数的执行时机,会被宿主环境控制。 浏览器宿主环境中包含 5 个线程:

  • JS 引擎:负责执行执行栈的最顶部代码
  • GUI 线程:负责渲染页面
  • 事件监听线程:负责监听各种事件
  • 计时线程:负责计时
  • 网络线程:负责网络通信

当上面的线程发生了某些事请,如果该线程发现,这件事情有处理程序,它会将该处理程序加入一个叫做事件队列的内存。当 JS 引擎发现,执行栈中已经没有了任何内容后,会将事件队列中的第一个函数加入到执行栈中执行。 JS 引擎对事件队列的取出执行方式,以及与宿主环境的配合,称之为事件循环。

JavaScript 运行机制详解:再谈Event Loop

事件队列在不同的宿主环境中有所差异,大部分宿主环境会将事件队列进行细分。在浏览器中,事件队列分为两种:

  • 宏任务(队列):macroTask,计时器结束的回调、事件回调、http 回调等等绝大部分异步函数进入宏队列
  • 微任务(队列):MutationObserver,Promise 产生的回调进入微队列——vip

MutationObserver 用于监听某个 DOM 对象的变化 当执行栈清空时,JS 引擎首先会将微任务中的所有任务依次执行结束,如果没有微任务,则执行宏任务。

看下面一串代码

html
<script>
+    
Skip to content
On this page

异步处理

事件循环 eventLoop

JS 运行的环境称之为宿主环境。JS语言不只运行在浏览器

执行栈:call stack(一个数据结构),用于存放各种函数的执行环境,每一个函数执行之前,它的相关信息会加入到执行栈。函数调用之前,创建执行环境,然后加入到执行栈;函数调用之后,销毁执行环境。 JS 引擎永远执行的是执行栈的最顶部。

异步函数:某些函数不会立即执行,需要等到某个时机到达后才会执行,这样的函数称之为异步函数。比如事件处理函数。异步函数的执行时机,会被宿主环境控制。 浏览器宿主环境中包含 5 个线程:

  • JS 引擎:负责执行执行栈的最顶部代码
  • GUI 线程:负责渲染页面
  • 事件监听线程:负责监听各种事件
  • 计时线程:负责计时
  • 网络线程:负责网络通信

当上面的线程发生了某些事请,如果该线程发现,这件事情有处理程序,它会将该处理程序加入一个叫做事件队列的内存。当 JS 引擎发现,执行栈中已经没有了任何内容后,会将事件队列中的第一个函数加入到执行栈中执行。 JS 引擎对事件队列的取出执行方式,以及与宿主环境的配合,称之为事件循环。

JavaScript 运行机制详解:再谈Event Loop

事件队列在不同的宿主环境中有所差异,大部分宿主环境会将事件队列进行细分。在浏览器中,事件队列分为两种:

  • 宏任务(队列):macroTask,计时器结束的回调、事件回调、http 回调等等绝大部分异步函数进入宏队列
  • 微任务(队列):MutationObserver,Promise 产生的回调进入微队列——vip

MutationObserver 用于监听某个 DOM 对象的变化 当执行栈清空时,JS 引擎首先会将微任务中的所有任务依次执行结束,如果没有微任务,则执行宏任务。

看下面一串代码

html
<script>
   let count = 1;
   const ul = document.getElementById("container");
   document.getElementById("btn").onclick = function A() {
@@ -2129,8 +2129,8 @@
 });
 console.log('script end');
 
- - + + \ No newline at end of file diff --git "a/js/\350\277\255\344\273\243\345\231\250\345\222\214\347\224\237\346\210\220\345\231\250.html" "b/js/\350\277\255\344\273\243\345\231\250\345\222\214\347\224\237\346\210\220\345\231\250.html" index 9266381f..ff4202ca 100644 --- "a/js/\350\277\255\344\273\243\345\231\250\345\222\214\347\224\237\346\210\220\345\231\250.html" +++ "b/js/\350\277\255\344\273\243\345\231\250\345\222\214\347\224\237\346\210\220\345\231\250.html" @@ -6,13 +6,13 @@ 迭代器和生成器 | Sunny's blog - - + + -
Skip to content
On this page

迭代器和生成器


WARNING

大量 code 警告

迭代器

背景知识

  1. 什么是迭代?

从一个数据集合中按照一定的顺序,不断取出数据的过程

  1. 迭代和遍历的区别?

迭代强调的是依次取数据,并不保证取多少,也不保证把所有的数据取完 遍历强调的是要把整个数据依次全部取出

  1. 迭代器

对迭代过程的封装,在不同的语言中有不同的表现形式,通常为对象

  1. 迭代模式

一种设计模式,用于统一迭代过程,并规范了迭代器规格:

  • 迭代器应该具有得到下一个数据的能力
  • 迭代器应该具有判断是否还有后续数据的能力

JS中的迭代器

JS规定,如果一个对象具有next方法,并且该方法返回一个对象,该对象的格式如下:

javascript
{value: , done: 是否迭代完成}
+    
Skip to content
On this page

迭代器和生成器


WARNING

大量 code 警告

迭代器

背景知识

  1. 什么是迭代?

从一个数据集合中按照一定的顺序,不断取出数据的过程

  1. 迭代和遍历的区别?

迭代强调的是依次取数据,并不保证取多少,也不保证把所有的数据取完 遍历强调的是要把整个数据依次全部取出

  1. 迭代器

对迭代过程的封装,在不同的语言中有不同的表现形式,通常为对象

  1. 迭代模式

一种设计模式,用于统一迭代过程,并规范了迭代器规格:

  • 迭代器应该具有得到下一个数据的能力
  • 迭代器应该具有判断是否还有后续数据的能力

JS中的迭代器

JS规定,如果一个对象具有next方法,并且该方法返回一个对象,该对象的格式如下:

javascript
{value: , done: 是否迭代完成}
 
{value:, done: 是否迭代完成}
 

模板

javascript
const obj = {
   next() {
@@ -1241,8 +1241,8 @@
   }
 }
 
- - + + \ No newline at end of file diff --git a/react/Fiber.html b/react/Fiber.html index c115d6eb..a33e2834 100644 --- a/react/Fiber.html +++ b/react/Fiber.html @@ -6,15 +6,15 @@ Fiber | Sunny's blog - - + + -
Skip to content
On this page

Fiber

React V15 在渲染时,会递归比对 VirtualDOM 树,找出需要变动的节点,然后同步更新它们, 一气呵成。这个过程期间, React 会占据浏览器资源,这会导致用户触发的事件得不到响应,并且会导致掉帧,导致用户感觉到卡顿

为了给用户制造一种应用很快的“假象”,不能让一个任务长期霸占着资源。 可以将浏览器的渲染、布局、绘制、资源加载(例如 HTML 解析)、事件响应、脚本执行视作操作系统的“进程”,需要通过某些调度策略合理地分配 CPU 资源,从而提高浏览器的用户响应速率, 同时兼顾任务执行效率。

所以 React 通过 Fiber 架构,让这个执行过程变成可被中断。“适时”地让出 CPU 执行权,除了可以让浏览器及时地响应用户的交互,还有其他好处:

  • 分批延时对 DOM 进行操作,避免一次性操作大量 DOM 节点,可以得到更好的用户体验;
  • 给浏览器一点喘息的机会,它会对代码进行编译优化(JIT)及进行热代码优化,或者对 reflow 进行修正。

核心思想: Fiber 也称协程或者纤程。它和线程并不一样,协程本身是没有并发或者并行能力的(需要配合线程),它只是一种控制流程的让出机制。让出 CPU 的执行权,让 CPU 能在这段时间执行其他的操作。渲染的过程可以被中断,可以将控制权交回浏览器,让位给高优先级的任务,浏览器空闲后再恢复渲染。

- - +
Skip to content
On this page

Fiber

React V15 在渲染时,会递归比对 VirtualDOM 树,找出需要变动的节点,然后同步更新它们, 一气呵成。这个过程期间, React 会占据浏览器资源,这会导致用户触发的事件得不到响应,并且会导致掉帧,导致用户感觉到卡顿

为了给用户制造一种应用很快的“假象”,不能让一个任务长期霸占着资源。 可以将浏览器的渲染、布局、绘制、资源加载(例如 HTML 解析)、事件响应、脚本执行视作操作系统的“进程”,需要通过某些调度策略合理地分配 CPU 资源,从而提高浏览器的用户响应速率, 同时兼顾任务执行效率。

所以 React 通过 Fiber 架构,让这个执行过程变成可被中断。“适时”地让出 CPU 执行权,除了可以让浏览器及时地响应用户的交互,还有其他好处:

  • 分批延时对 DOM 进行操作,避免一次性操作大量 DOM 节点,可以得到更好的用户体验;
  • 给浏览器一点喘息的机会,它会对代码进行编译优化(JIT)及进行热代码优化,或者对 reflow 进行修正。

核心思想: Fiber 也称协程或者纤程。它和线程并不一样,协程本身是没有并发或者并行能力的(需要配合线程),它只是一种控制流程的让出机制。让出 CPU 的执行权,让 CPU 能在这段时间执行其他的操作。渲染的过程可以被中断,可以将控制权交回浏览器,让位给高优先级的任务,浏览器空闲后再恢复渲染。

+ + \ No newline at end of file diff --git a/react/ReactRouter.html b/react/ReactRouter.html index bd3a922b..6274ed2b 100644 --- a/react/ReactRouter.html +++ b/react/ReactRouter.html @@ -6,13 +6,13 @@ ReactRouter | Sunny's blog - - + + -
Skip to content
On this page

ReactRouter

React Router 概述

站点

无论是使用 Vue,还是 React,开发的单页应用程序,可能只是该站点的一部分(某一个功能块) 一个单页应用里,可能会划分为多个页面(几乎完全不同的页面效果)(组件)

如果要在单页应用中完成组件的切换,需要实现下面两个功能:

  1. 根据不同的页面地址,展示不同的组件(核心)
  2. 完成无刷新的地址切换

我们把实现了以上两个功能的插件,称之为路由

React Router

  1. react-router:路由核心库,包含诸多和路由功能相关的核心代码
  2. react-router-dom:利用路由核心库,结合实际的页面,实现跟页面路由密切相关的功能

如果是在页面中实现路由,需要安装 react-router-dom 库

两种模式

路由:根据不同的页面地址,展示不同的组件

url 地址组成

例:https://www.react.com:443/news/1-2-1.html?a=1&b=2#abcdefg

  1. 协议名(schema):https
  2. 主机名(host):www.react.com
    1. ip 地址
    2. 预设值:localhost
    3. 域名
    4. 局域网中电脑名称
  3. 端口号(port):443
    1. 如果协议是 http,端口号是 80,则可以省略端口号
    2. 如果协议是 https,端口号是 443,则可以省略端口号
  4. 路径(path):/news/1-2-1.html
  5. 地址参数(search、query):?a=1&b=2
    1. 附带的数据
    2. 格式:属性名=属性值&属性名=属性值....
  6. 哈希(hash、锚点)
    1. 附带的数据

Hash Router 哈希路由

根据 url 地址中的哈希值来确定显示的组件

原因:hash 的变化,不会导致页面刷新 这种模式的兼容性最好

Borswer History Router 浏览器历史记录路由

之前存在的 api:history.forward(), history.back(), history.go() HTML5 出现后,新增了 History Api,从此以后,浏览器拥有了改变路径而不刷新页面的方式

History 表示浏览器的历史记录,它使用栈的方式存储。

image.png

  1. history.length:获取栈中数据量
  2. history.pushState:向当前历史记录栈中加入一条新的记录
    1. 参数 1:附加的数据,自定义的数据,可以是任何类型
    2. 参数 2:页面标题,目前大部分浏览器不支持
    3. 参数 3:新的地址
  3. history.replaceState:将当前指针指向的历史记录,替换为某个记录
    1. 参数 1:附加的数据,自定义的数据,可以是任何类型
    2. 参数 2:页面标题,目前大部分浏览器不支持
    3. 参数 3:新的地址

根据页面的路径决定渲染哪个组件,不是根据哈希了

路由组件

React-Router 为我们提供了两个重要组件

Router 组件

它本身不做任何展示,仅提供路由模式配置,另外,该组件会产生一个上下文,上下文中会提供一些实用的对象和方法,供其他相关组件使用

  1. HashRouter:该组件,使用 hash 模式匹配
  2. BrowserRouter:该组件,使用 BrowserHistory 模式匹配

通常情况下,Router 组件只有一个,将该组件包裹整个页面

Route 组件

根据不同的地址,展示不同的组件

重要属性:

  1. path:匹配的路径
    1. 默认情况下,不区分大小写,可以设置 sensitive 属性为 true,来区分大小写
    2. 默认情况下,只匹配初始目录,如果要精确匹配,配置 exact 属性为 true
    3. 如果不写 path,则会匹配任意路径
  2. component:匹配成功后要显示的组件
  3. children:
    1. 传递 React 元素,无论是否匹配,一定会显示 children,并且会忽略 component 属性
    2. 传递一个函数,该函数有多个参数,这些参数来自于上下文,该函数返回 react 元素,则一定会显示返回的元素,并且忽略 component 属性.

Route 组件可以写到任意的地方,只要保证它是 Router 组件的后代元素

Switch 组件

写到 Switch 组件中的 Route 组件,当匹配到第一个 Route 后,会立即停止匹配

由于 Switch 组件会循环所有子元素,然后让每个子元素去完成匹配,若匹配到,则渲染对应的组件,然后停止循环。因此,不能在 Switch 的子元素中使用除 Route 外的其他组件。

路由信息

Router 组件会创建一个上下文,并且,向上下文中注入一些信息

该上下文对开发者是隐藏的,Route 组件若匹配到了地址,则会将这些上下文中的信息作为属性传入对应的组件

history

它并不是 window.history 对象,我们利用该对象无刷新跳转地址

为什么没有直接使用 history 对象

  1. React-Router 中有两种模式:Hash、History,如果直接使用 window.history,只能支持一种模式。为了适配两种模式
  2. 当使用 windows.history.pushState 方法时,没有办法收到任何通知,将导致 React 无法知晓地址发生了变化,结果导致无法重新渲染组件
  • push:将某个新的地址入栈(历史记录栈)
    • 参数 1:新的地址
    • 参数 2:可选,附带的状态数据
  • replace:将某个新的地址替换掉当前栈中的地址
  • go: 与 window.history 一致
  • forward: 与 window.history 一致
  • back: 与 window.history 一致

location

与 history.location 完全一致,是同一个对象,但是,与 window.location 不同

location 对象中记录了当前地址的相关信息

我们通常使用第三方库query-string,用于解析地址栏中的数据

match

该对象中保存了,路由匹配的相关信息

  • isExact:事实上,当前的路径和路由配置的路径是否是精确匹配的。和 exact 写没写无关
  • params:获取路径规则中对应的数据

实际上,在书写 Route 组件的 path 属性时,可以书写一个string pattern(字符串正则)

react-router 使用了第三方库:Path-to-RegExp,该库的作用是,将一个字符串正则转换成一个真正的正则表达式。

向某个页面传递数据的方式:

  1. 使用 state:在 push 页面时,加入 state
  2. 利用 search:把数据填写到地址栏中的?后
  3. 利用 hash:把数据填写到 hash 后
  4. params:把数据填写到路径中

非路由组件获取路由信息

某些组件,并没有直接放到 Route 中,而是嵌套在其他普通组件中,因此,它的 props 中没有路由信息,如果这些组件需要获取到路由信息,可以使用下面两种方式:

  1. 将路由信息从父组件一层一层传递到子组件
  2. 使用 react-router 提供的高阶组件 withRouter,包装要使用的组件,该高阶组件会返回一个新组件,新组件将向提供的组件注入路由信息。

其他组件

已学习:

  • Router:BrowswerRouter、HashRouter
  • Route
  • Switch
  • 高阶函数:withRouter

生成一个无刷新跳转的 a 元素

  • to
    • 字符串:跳转的目标地址
    • 对象:
      • pathname:url 路径
      • search
      • hash
      • state:附加的状态信息
  • replace:bool,表示是否是替换当前地址,默认是 false,是 push 跳转
  • innerRef:可以将内部的 a 元素的 ref 附着在传递的对象或函数参数上
    • 函数
    • ref 对象

是一种特殊的 Link,Link 组件具备的功能,它都有

它具备的额外功能是:根据当前地址和链接地址,来决定该链接的样式

  • activeClassName: 匹配时使用的类名
  • activeStyle: 匹配时使用的内联样式
  • exact: 是否精确匹配
  • sensitive:匹配时是否区分大小写
  • strict:是否严格匹配最后一个斜杠

Redirect

重定向组件,当加载到该组件时,会自动跳转(无刷新)到另外一个地址

  • to:跳转的地址
    • 字符串
    • 对象
  • push: 默认为 false,表示跳转使用替换的方式,设置为 true 后,则使用 push 的方式跳转
  • from:当匹配到 from 地址规则时才进行跳转
  • exact: 是否精确匹配 from
  • sensitive:from 匹配时是否区分大小写
  • strict:from 是否严格匹配最后一个斜杠

vue-router 和 React router

vue-router 是一个静态的配置 react-router v4 之前 静态的配置 现在 react-router 是动态的组件,灵活了

嵌套路由

导航守卫

导航守卫:当离开一个页面,进入另一个页面时,触发的事件

history 对象

  • listen: 添加一个监听器,监听地址的变化,当地址发生变化时,会调用传递的函数
    • 参数:函数,运行时间点:发生在即将跳转到新页面时
      • 参数 1:location 对象,记录当前的地址信息
      • 参数 2:action,一个字符串,表示进入该地址的方式
        • POP:出栈 (指针移动)
          • 通过点击浏览器后退、前进
          • 调用 history.go
          • 调用 history.goBack
          • 调用 history.goForward
        • PUSH:入栈 (指针移动)
          • history.push
        • REPLACE:替换
          • history.replace
    • 返回结果:函数,可以调用该函数取消监听
  • block:设置一个阻塞,并同时设置阻塞消息,当页面发生跳转时,会进入阻塞,并将阻塞消息传递到路由根组件的 getUserConfirmation 方法。
    • 返回一个回调函数,用于取消阻塞器

路由根组件

  • getUserConfirmation
    • 参数:函数
      • 参数 1:阻塞消息
        • 字符串消息
        • 函数,函数的返回结果是一个字符串,用于表示阻塞消息
          • 参数 1:location 对象
          • 参数 2:action 值
      • 参数 2:回调函数,调用该函数并传递 true,则表示进入到新页面,否则,不做任何操作

常见应用 - 路由切换动画

第三方动画库:react-transition-group

CSSTransition:用于为内部的 DOM 元素添加类样式,通过 in 属性决定内部的 DOM 处于退出还是进入阶段。

滚动条复位

高阶组件

使用 useEffect

使用自定义的导航守卫

路由

React-Router 的实现原理是什么?

客户端路由实现的思想:

  • 基于 hash 的路由:通过监听 事件,感知 hash 的变化 hashchange
    • 改变 hash 可以直接通过 location.hash=xxx
  • 基于 H5 history 路由:
    • 改变 url 可以通过 history.pushState 和 resplaceState 等,会将 URL 压入堆栈,同时能够应用 history.go() 等 API
    • 监听 url 的变化可以通过自定义事件触发实现

react-router 实现的思想:

  • 基于 history 库来实现上述不同的客户端路由实现思想,并且能够保存历史记录等,磨平浏览器差异,上层无感知
  • 通过维护的列表,在每次 URL 发生变化的回收,通过配置的 路由路径,匹配到对应的 Component,并且 render

如何配置 React-Router 实现路由切换

(1)使用<Route> 组件

路由匹配是通过比较 <Route> 的 path 属性和当前地址的 pathname 来实现的。当一个 <Route> 匹配成功时,它将渲染其内容,当它不匹配时就会渲染 null。没有路径的 <Route> 将始终被匹配。

javascript
// when location = { pathname: '/about' }
+    
Skip to content
On this page

ReactRouter

React Router 概述

站点

无论是使用 Vue,还是 React,开发的单页应用程序,可能只是该站点的一部分(某一个功能块) 一个单页应用里,可能会划分为多个页面(几乎完全不同的页面效果)(组件)

如果要在单页应用中完成组件的切换,需要实现下面两个功能:

  1. 根据不同的页面地址,展示不同的组件(核心)
  2. 完成无刷新的地址切换

我们把实现了以上两个功能的插件,称之为路由

React Router

  1. react-router:路由核心库,包含诸多和路由功能相关的核心代码
  2. react-router-dom:利用路由核心库,结合实际的页面,实现跟页面路由密切相关的功能

如果是在页面中实现路由,需要安装 react-router-dom 库

两种模式

路由:根据不同的页面地址,展示不同的组件

url 地址组成

例:https://www.react.com:443/news/1-2-1.html?a=1&b=2#abcdefg

  1. 协议名(schema):https
  2. 主机名(host):www.react.com
    1. ip 地址
    2. 预设值:localhost
    3. 域名
    4. 局域网中电脑名称
  3. 端口号(port):443
    1. 如果协议是 http,端口号是 80,则可以省略端口号
    2. 如果协议是 https,端口号是 443,则可以省略端口号
  4. 路径(path):/news/1-2-1.html
  5. 地址参数(search、query):?a=1&b=2
    1. 附带的数据
    2. 格式:属性名=属性值&属性名=属性值....
  6. 哈希(hash、锚点)
    1. 附带的数据

Hash Router 哈希路由

根据 url 地址中的哈希值来确定显示的组件

原因:hash 的变化,不会导致页面刷新 这种模式的兼容性最好

Borswer History Router 浏览器历史记录路由

之前存在的 api:history.forward(), history.back(), history.go() HTML5 出现后,新增了 History Api,从此以后,浏览器拥有了改变路径而不刷新页面的方式

History 表示浏览器的历史记录,它使用栈的方式存储。

image.png

  1. history.length:获取栈中数据量
  2. history.pushState:向当前历史记录栈中加入一条新的记录
    1. 参数 1:附加的数据,自定义的数据,可以是任何类型
    2. 参数 2:页面标题,目前大部分浏览器不支持
    3. 参数 3:新的地址
  3. history.replaceState:将当前指针指向的历史记录,替换为某个记录
    1. 参数 1:附加的数据,自定义的数据,可以是任何类型
    2. 参数 2:页面标题,目前大部分浏览器不支持
    3. 参数 3:新的地址

根据页面的路径决定渲染哪个组件,不是根据哈希了

路由组件

React-Router 为我们提供了两个重要组件

Router 组件

它本身不做任何展示,仅提供路由模式配置,另外,该组件会产生一个上下文,上下文中会提供一些实用的对象和方法,供其他相关组件使用

  1. HashRouter:该组件,使用 hash 模式匹配
  2. BrowserRouter:该组件,使用 BrowserHistory 模式匹配

通常情况下,Router 组件只有一个,将该组件包裹整个页面

Route 组件

根据不同的地址,展示不同的组件

重要属性:

  1. path:匹配的路径
    1. 默认情况下,不区分大小写,可以设置 sensitive 属性为 true,来区分大小写
    2. 默认情况下,只匹配初始目录,如果要精确匹配,配置 exact 属性为 true
    3. 如果不写 path,则会匹配任意路径
  2. component:匹配成功后要显示的组件
  3. children:
    1. 传递 React 元素,无论是否匹配,一定会显示 children,并且会忽略 component 属性
    2. 传递一个函数,该函数有多个参数,这些参数来自于上下文,该函数返回 react 元素,则一定会显示返回的元素,并且忽略 component 属性.

Route 组件可以写到任意的地方,只要保证它是 Router 组件的后代元素

Switch 组件

写到 Switch 组件中的 Route 组件,当匹配到第一个 Route 后,会立即停止匹配

由于 Switch 组件会循环所有子元素,然后让每个子元素去完成匹配,若匹配到,则渲染对应的组件,然后停止循环。因此,不能在 Switch 的子元素中使用除 Route 外的其他组件。

路由信息

Router 组件会创建一个上下文,并且,向上下文中注入一些信息

该上下文对开发者是隐藏的,Route 组件若匹配到了地址,则会将这些上下文中的信息作为属性传入对应的组件

history

它并不是 window.history 对象,我们利用该对象无刷新跳转地址

为什么没有直接使用 history 对象

  1. React-Router 中有两种模式:Hash、History,如果直接使用 window.history,只能支持一种模式。为了适配两种模式
  2. 当使用 windows.history.pushState 方法时,没有办法收到任何通知,将导致 React 无法知晓地址发生了变化,结果导致无法重新渲染组件
  • push:将某个新的地址入栈(历史记录栈)
    • 参数 1:新的地址
    • 参数 2:可选,附带的状态数据
  • replace:将某个新的地址替换掉当前栈中的地址
  • go: 与 window.history 一致
  • forward: 与 window.history 一致
  • back: 与 window.history 一致

location

与 history.location 完全一致,是同一个对象,但是,与 window.location 不同

location 对象中记录了当前地址的相关信息

我们通常使用第三方库query-string,用于解析地址栏中的数据

match

该对象中保存了,路由匹配的相关信息

  • isExact:事实上,当前的路径和路由配置的路径是否是精确匹配的。和 exact 写没写无关
  • params:获取路径规则中对应的数据

实际上,在书写 Route 组件的 path 属性时,可以书写一个string pattern(字符串正则)

react-router 使用了第三方库:Path-to-RegExp,该库的作用是,将一个字符串正则转换成一个真正的正则表达式。

向某个页面传递数据的方式:

  1. 使用 state:在 push 页面时,加入 state
  2. 利用 search:把数据填写到地址栏中的?后
  3. 利用 hash:把数据填写到 hash 后
  4. params:把数据填写到路径中

非路由组件获取路由信息

某些组件,并没有直接放到 Route 中,而是嵌套在其他普通组件中,因此,它的 props 中没有路由信息,如果这些组件需要获取到路由信息,可以使用下面两种方式:

  1. 将路由信息从父组件一层一层传递到子组件
  2. 使用 react-router 提供的高阶组件 withRouter,包装要使用的组件,该高阶组件会返回一个新组件,新组件将向提供的组件注入路由信息。

其他组件

已学习:

  • Router:BrowswerRouter、HashRouter
  • Route
  • Switch
  • 高阶函数:withRouter

生成一个无刷新跳转的 a 元素

  • to
    • 字符串:跳转的目标地址
    • 对象:
      • pathname:url 路径
      • search
      • hash
      • state:附加的状态信息
  • replace:bool,表示是否是替换当前地址,默认是 false,是 push 跳转
  • innerRef:可以将内部的 a 元素的 ref 附着在传递的对象或函数参数上
    • 函数
    • ref 对象

是一种特殊的 Link,Link 组件具备的功能,它都有

它具备的额外功能是:根据当前地址和链接地址,来决定该链接的样式

  • activeClassName: 匹配时使用的类名
  • activeStyle: 匹配时使用的内联样式
  • exact: 是否精确匹配
  • sensitive:匹配时是否区分大小写
  • strict:是否严格匹配最后一个斜杠

Redirect

重定向组件,当加载到该组件时,会自动跳转(无刷新)到另外一个地址

  • to:跳转的地址
    • 字符串
    • 对象
  • push: 默认为 false,表示跳转使用替换的方式,设置为 true 后,则使用 push 的方式跳转
  • from:当匹配到 from 地址规则时才进行跳转
  • exact: 是否精确匹配 from
  • sensitive:from 匹配时是否区分大小写
  • strict:from 是否严格匹配最后一个斜杠

vue-router 和 React router

vue-router 是一个静态的配置 react-router v4 之前 静态的配置 现在 react-router 是动态的组件,灵活了

嵌套路由

导航守卫

导航守卫:当离开一个页面,进入另一个页面时,触发的事件

history 对象

  • listen: 添加一个监听器,监听地址的变化,当地址发生变化时,会调用传递的函数
    • 参数:函数,运行时间点:发生在即将跳转到新页面时
      • 参数 1:location 对象,记录当前的地址信息
      • 参数 2:action,一个字符串,表示进入该地址的方式
        • POP:出栈 (指针移动)
          • 通过点击浏览器后退、前进
          • 调用 history.go
          • 调用 history.goBack
          • 调用 history.goForward
        • PUSH:入栈 (指针移动)
          • history.push
        • REPLACE:替换
          • history.replace
    • 返回结果:函数,可以调用该函数取消监听
  • block:设置一个阻塞,并同时设置阻塞消息,当页面发生跳转时,会进入阻塞,并将阻塞消息传递到路由根组件的 getUserConfirmation 方法。
    • 返回一个回调函数,用于取消阻塞器

路由根组件

  • getUserConfirmation
    • 参数:函数
      • 参数 1:阻塞消息
        • 字符串消息
        • 函数,函数的返回结果是一个字符串,用于表示阻塞消息
          • 参数 1:location 对象
          • 参数 2:action 值
      • 参数 2:回调函数,调用该函数并传递 true,则表示进入到新页面,否则,不做任何操作

常见应用 - 路由切换动画

第三方动画库:react-transition-group

CSSTransition:用于为内部的 DOM 元素添加类样式,通过 in 属性决定内部的 DOM 处于退出还是进入阶段。

滚动条复位

高阶组件

使用 useEffect

使用自定义的导航守卫

路由

React-Router 的实现原理是什么?

客户端路由实现的思想:

  • 基于 hash 的路由:通过监听 事件,感知 hash 的变化 hashchange
    • 改变 hash 可以直接通过 location.hash=xxx
  • 基于 H5 history 路由:
    • 改变 url 可以通过 history.pushState 和 resplaceState 等,会将 URL 压入堆栈,同时能够应用 history.go() 等 API
    • 监听 url 的变化可以通过自定义事件触发实现

react-router 实现的思想:

  • 基于 history 库来实现上述不同的客户端路由实现思想,并且能够保存历史记录等,磨平浏览器差异,上层无感知
  • 通过维护的列表,在每次 URL 发生变化的回收,通过配置的 路由路径,匹配到对应的 Component,并且 render

如何配置 React-Router 实现路由切换

(1)使用<Route> 组件

路由匹配是通过比较 <Route> 的 path 属性和当前地址的 pathname 来实现的。当一个 <Route> 匹配成功时,它将渲染其内容,当它不匹配时就会渲染 null。没有路径的 <Route> 将始终被匹配。

javascript
// when location = { pathname: '/about' }
 <Route path='/about' component={About}/> // renders <About/>
 <Route path='/contact' component={Contact}/> // renders null
 <Route component={Always}/> // renders <Always/>
@@ -621,8 +621,8 @@
 o 支持监听action的分发,更新状态(dispatch(action));
 o 支持订阅store的变更(subscribe(listener));
 
  • 异步流 ∶ 由于 Redux 所有对 store 状态的变更,都应该通过 action 触发,异步任务(通常都是业务或获取数据任务)也不例外,而为了不将业务或数据相关的任务混入 React 组件中,就需要使用其他框架配合管理异步任务流程,如 redux-thunk,redux-saga 等;

Mobx 是一个透明函数响应式编程的状态管理库,它使得状态管理简单可伸缩 ∶

  • Action∶ 定义改变状态的动作函数,包括如何变更状态;
  • Store∶ 集中管理模块状态(State)和动作(action)
  • Derivation(衍生)∶ 从应用状态中派生而出,且没有任何其他影响的数据

对比总结:

  • redux 将数据保存在单一的 store 中,mobx 将数据保存在分散的多个 store 中
  • redux 使用 plain object 保存数据,需要手动处理变化后的操作;mobx 适用 observable 保存数据,数据变化后自动处理响应的操作
  • redux 使用不可变状态,这意味着状态是只读的,不能直接去修改它,而是应该返回一个新的状态,同时使用纯函数;mobx 中的状态是可变的,可以直接对其进行修改
  • mobx 相对来说比较简单,在其中有很多的抽象,mobx 更多的使用面向对象的编程思维;redux 会比较复杂,因为其中的函数式编程思想掌握起来不是那么容易,同时需要借助一系列的中间件来处理异步和副作用
  • mobx 中有更多的抽象和封装,调试会比较困难,同时结果也难以预测;而 redux 提供能够进行时间回溯的开发工具,同时其纯函数以及更少的抽象,让调试变得更加的容易

Redux 和 Vuex 有什么区别,它们的共同思想

(1)Redux 和 Vuex 区别

  • Vuex 改进了 Redux 中的 Action 和 Reducer 函数,以 mutations 变化函数取代 Reducer,无需 switch,只需在对应的 mutation 函数里改变 state 值即可
  • Vuex 由于 Vue 自动重新渲染的特性,无需订阅重新渲染函数,只要生成新的 State 即可
  • Vuex 数据流的顺序是 ∶View 调用 store.commit 提交对应的请求到 Store 中对应的 mutation 函数->store 改变(vue 检测到数据变化自动渲染)

通俗点理解就是,vuex 弱化 dispatch,通过 commit 进行 store 状态的一次更变;取消了 action 概念,不必传入特定的 action 形式进行指定变更;弱化 reducer,基于 commit 参数直接对数据进行转变,使得框架更加简易;

(2)共同思想

  • 单—的数据源
  • 变化可以预测

本质上 ∶ redux 与 vuex 都是对 mvvm 思想的服务,将数据从视图中抽离的一种方案。

Redux 中间件是怎么拿到 store 和 action? 然后怎么处理?

redux 中间件本质就是一个函数柯里化。redux applyMiddleware Api 源码中每个 middleware 接受 2 个参数, Store 的 getState 函数和 dispatch 函数,分别获得 store 和 action,最终返回一个函数。该函数会被传入 next 的下一个 middleware 的 dispatch 方法,并返回一个接收 action 的新函数,这个函数可以直接调用 next(action),或者在其他需要的时刻调用,甚至根本不去调用它。调用链中最后一个 middleware 会接受真实的 store 的 dispatch 方法作为 next 参数,并借此结束调用链。所以,middleware 的函数签名是({ getState,dispatch })=> next => action。

Redux 中的 connect 有什么作用

connect 负责连接 React 和 Redux

(1)获取 state

connect 通过 context 获取 Provider 中的 store,通过store.getState() 获取整个 store tree 上所有 state

(2)包装原组件

将 state 和 action 通过 props 的方式传入到原组件内部 wrapWithConnect 返回—个 ReactComponent 对 象 Connect,Connect 重 新 render 外部传入的原组件 WrappedComponent ,并把 connect 中传入的 mapStateToProps,mapDispatchToProps 与组件上原有的 props 合并后,通过属性的方式传给 WrappedComponent

(3)监听 store tree 变化

connect 缓存了 store tree 中 state 的状态,通过当前 state 状态 和变更前 state 状态进行比较,从而确定是否调用 this.setState()方法触发 Connect 及其子组件的重新渲染

- - + + \ No newline at end of file diff --git a/react/Redux.html b/react/Redux.html index 2785b95f..ba9c2db6 100644 --- a/react/Redux.html +++ b/react/Redux.html @@ -6,13 +6,13 @@ Redux | Sunny's blog - - + + -
Skip to content
On this page

Redux

Redux 核心概念

action  reducer  store

MVC

它是一个 UI 的解决方案,用于降低 UI,以及 UI 关联的数据的复杂度。

传统的服务器端的 MVC

环境:

  1. 服务端需要响应一个完整的 HTML
  2. 该 HTML 中包含页面需要的数据
  3. 浏览器仅承担渲染页面的作用

以上的这种方式叫做服务端渲染,即服务器端将完整的页面组装好之后,一起发送给客户端。

服务器端需要处理 UI 中要用到的数据,并且要将数据嵌入到页面中,最终生成一个完整的 HTML 页面响应。

为了降低处理这个过程的复杂度,出现了 MVC 模式。

Controller: 处理请求,组装这次请求需要的数据 Model:需要用于 UI 渲染的数据模型 View:视图,用于将模型组装到界面中

前后端分离前端 MVC 模式的困难

React 解决了   数据 -> 视图   的问题

解决了 MVC 的 V

  1. 前端的 controller 要比服务器复杂很多,因为前端中的 controller 处理的是用户的操作,而用户的操作场景是复杂的。
  2. 对于那些组件化的框架(比如 vue、react),它们使用的是单向数据流。若需要共享数据,则必须将数据提升到顶层组件,然后数据再一层一层传递,极其繁琐。 虽然可以使用上下文来提供共享数据,但对数据的操作难以监控,容易导致调试错误的困难,以及数据还原的困难。并且,若开发一个大中型项目,共享的数据很多,会导致上下文中的数据变得非常复杂。

比如,上下文中有如下格式的数据:

javascript
value = {
+    
Skip to content
On this page

Redux

Redux 核心概念

action  reducer  store

MVC

它是一个 UI 的解决方案,用于降低 UI,以及 UI 关联的数据的复杂度。

传统的服务器端的 MVC

环境:

  1. 服务端需要响应一个完整的 HTML
  2. 该 HTML 中包含页面需要的数据
  3. 浏览器仅承担渲染页面的作用

以上的这种方式叫做服务端渲染,即服务器端将完整的页面组装好之后,一起发送给客户端。

服务器端需要处理 UI 中要用到的数据,并且要将数据嵌入到页面中,最终生成一个完整的 HTML 页面响应。

为了降低处理这个过程的复杂度,出现了 MVC 模式。

Controller: 处理请求,组装这次请求需要的数据 Model:需要用于 UI 渲染的数据模型 View:视图,用于将模型组装到界面中

前后端分离前端 MVC 模式的困难

React 解决了   数据 -> 视图   的问题

解决了 MVC 的 V

  1. 前端的 controller 要比服务器复杂很多,因为前端中的 controller 处理的是用户的操作,而用户的操作场景是复杂的。
  2. 对于那些组件化的框架(比如 vue、react),它们使用的是单向数据流。若需要共享数据,则必须将数据提升到顶层组件,然后数据再一层一层传递,极其繁琐。 虽然可以使用上下文来提供共享数据,但对数据的操作难以监控,容易导致调试错误的困难,以及数据还原的困难。并且,若开发一个大中型项目,共享的数据很多,会导致上下文中的数据变得非常复杂。

比如,上下文中有如下格式的数据:

javascript
value = {
   users: [{}, {}, {}],
   addUser: function (u) {},
   deleteUser: function (u) {},
@@ -51,8 +51,8 @@
   payload: 1, // 用户id为1
 };
 

Redux

在 Flux 基础上,引入了 reducer 的概念

reducer:处理器,用于根据 action 来处理数据,处理后的数据会被仓库重新保存。

Action

  1. action 是一个 plain-object(平面对象)
    1. 它的proto指向 Object.prototype
  2. 通常,使用 payload 属性表示附加数据(没有强制要求)
  3. action 中必须有 type 属性,该属性用于描述操作的类型
    1. 但是,没有对 type 的类型做出要求
  4. 在大型项目,由于操作类型非常多,为了避免硬编码(hard code),会将 action 的类型存放到一个或一些单独的文件中(样板代码)。

  1. 为了方面传递 action,通常会使用 action 创建函数(action creator)来创建 action

  2. action 创建函数应为无副作用的纯函数

    1. 不能以任何形式改动参数
    2. 不可以有异步
    3. 不可以对外部环境中的数据造成影响
  3. 为了方便利用 action 创建函数来分发(触发)action,redux 提供了一个函数bindActionCreators,该函数用于增强 action 创建函数的功能,使它不仅可以创建 action,并且创建后会自动完成分发。

Reducer

Reducer 是用于改变数据的函数

  1. 一个数据仓库,有且仅有一个 reducer,并且通常情况下,一个工程只有一个仓库,因此,一个系统,只有一个 reducer
  2. 为了方便管理,通常会将 reducer 放到单独的文件中。
  3. reducer 被调用的时机
    1. 通过 store.dispatch,分发了一个 action,此时,会调用 reducer
    2. 当创建一个 store 的时候,会调用一次 reducer
      1. 可以利用这一点,用 reducer 初始化状态
      2. 创建仓库时,不传递任何默认状态
      3. 将 reducer 的参数 state 设置一个默认值。创建仓库不写默认值,传递 reducer 的时候传递默认值
  4. reducer 内部通常使用 switch 来判断 type 值
  5. reducer 必须是一个没有副作用的纯函数
    1. 为什么需要纯函数
      1. 纯函数有利于测试和调式
      2. 有利于还原数据
      3. 有利于将来和 react 结合时的优化
    2. 具体要求
      1. 不能改变参数,因此若要让状态变化,必须得到一个新的状态
      2. 不能有异步
      3. 不能对外部环境造成影响
  6. 由于在大中型项目中,操作比较复杂,数据结构也比较复杂,因此,需要对 reducer 进行细分。
    1. redux 提供了方法,可以帮助我们更加方便的合并 reducer
    2. combineReducers: 合并 reducer,得到一个新的 reducer,该新的 reducer 管理一个对象,该对象中的每一个属性交给对应的 reducer 管理。

Store

Store:用于保存数据

通过 createStore 方法创建的对象。

该对象的成员:

  • dispatch:分发一个 action
  • getState:得到仓库中当前的状态
  • replaceReducer:替换掉当前的 reducer
  • subscribe:注册一个监听器,监听器是一个无参函数,该分发一个 action 之后,会运行注册的监听器。该函数会返回一个函数,用于取消监听。可以注册多个监听器

createStore

返回一个对象:

  • dispatch:分发一个 action
  • getState:得到仓库中当前的状态
  • subscribe:注册一个监听器,监听器是一个无参函数,该分发一个 action 之后,会运行注册的监听器。该函数会返回一个函数,用于取消监听

bindActionCreators

combineReducers

组装 reducers,返回一个 reducer,数据使用一个对象表示,对象的属性名与传递的参数对象保持一致

Redux 中间件(Middleware)

中间件:类似于插件,可以在不影响原本功能、并且不改动原本代码的基础上,对其功能进行增强。在 Redux 中,中间件主要用于增强 dispatch 函数。

实现 Redux 中间件的基本原理,是更改仓库中的 dispatch 函数。

Redux 中间件书写:

  • 中间件本身是一个函数,该函数接收一个 store 参数,表示创建的仓库,该仓库并非一个完整的仓库对象,仅包含 getState,dispatch。该函数运行的时间,是在仓库创建之后运行。
    • 由于创建仓库后需要自动运行设置的中间件函数,因此,需要在创建仓库时,告诉仓库有哪些中间件
    • 需要调用 applyMiddleware 函数,将函数的返回结果作为 createStore 的第二或第三个参数。
  • 中间件函数必须返回一个 dispatch 创建函数
  • applyMiddleware 函数,用于记录有哪些中间件,它会返回一个函数
    • 该函数用于记录创建仓库的方法,然后又返回一个函数

redux-actions

不维护了:https://github.com/redux-utilities/redux-actions#looking-for-maintainers

该库用于简化 action-types、action-creator 以及 reducer 官网文档:https://redux-actions.js.org/

createAction(s)

createAction

该函数用于帮助你创建一个 action 创建函数(action creator)

createActions

该函数用于帮助你创建多个 action 创建函数

handleAction(s)

handleAction

简化针对单个 action 类型的 reducer 处理,当它匹配到对应的 action 类型后,会执行对应的函数

handleActions

简化针对多个 action 类型的 reducre 处理

combineActions

配合 createActions 和 handleActions 两个函数,用于处理多个 action-type 对应同一个 reducer 处理函数。

- - + + \ No newline at end of file diff --git a/react/component-communication.html b/react/component-communication.html index 1a11c15b..41dfcece 100644 --- a/react/component-communication.html +++ b/react/component-communication.html @@ -6,15 +6,15 @@ React 组件通信 | Sunny's blog - - + + -
Skip to content
On this page

React 组件通信

1. 父子组件的通信方式?

父组件向子组件通信:父组件通过 props 向子组件传递需要的信息。

子组件向父组件通信:: props+回调的方式。

2. 跨级组件的通信方式?

父组件向子组件的子组件通信,向更深层子组件通信:

  • 使用 props,利用中间组件层层传递,但是如果父组件结构较深,那么中间每一层组件都要去传递 props,增加了复杂度,并且这些 props 并不是中间组件自己需要的。
  • 使用 context,context 相当于一个大容器,可以把要通信的内容放在这个容器中,这样不管嵌套多深,都可以随意取用,对于跨越多层的全局数据可以使用 context 实现。

3. 非嵌套关系组件的通信方式?

即没有任何包含关系的组件,包括兄弟组件以及不在同一个父级中的非兄弟组件。

  • 可以使用自定义事件通信(发布订阅模式)
  • 可以通过 redux 等进行全局状态管理
  • 如果是兄弟组件通信,可以找到这两个兄弟节点共同的父节点, 结合父子间通信方式进行通信。
- - +
Skip to content
On this page

React 组件通信

1. 父子组件的通信方式?

父组件向子组件通信:父组件通过 props 向子组件传递需要的信息。

子组件向父组件通信:: props+回调的方式。

2. 跨级组件的通信方式?

父组件向子组件的子组件通信,向更深层子组件通信:

  • 使用 props,利用中间组件层层传递,但是如果父组件结构较深,那么中间每一层组件都要去传递 props,增加了复杂度,并且这些 props 并不是中间组件自己需要的。
  • 使用 context,context 相当于一个大容器,可以把要通信的内容放在这个容器中,这样不管嵌套多深,都可以随意取用,对于跨越多层的全局数据可以使用 context 实现。

3. 非嵌套关系组件的通信方式?

即没有任何包含关系的组件,包括兄弟组件以及不在同一个父级中的非兄弟组件。

  • 可以使用自定义事件通信(发布订阅模式)
  • 可以通过 redux 等进行全局状态管理
  • 如果是兄弟组件通信,可以找到这两个兄弟节点共同的父节点, 结合父子间通信方式进行通信。
+ + \ No newline at end of file diff --git a/react/context.html b/react/context.html index a21e5b4c..607bed17 100644 --- a/react/context.html +++ b/react/context.html @@ -6,13 +6,13 @@ Context 上下文 | Sunny's blog - - + + -
Skip to content
On this page

Context 上下文

上下文:Context,表示做某一些事情的环境 React 中的上下文特点:

  1. 当某个组件创建了上下文后,上下文中的数据,会被所有后代组件共享
  2. 如果某个组件依赖了上下文,会导致该组件不再纯粹(纯粹指的是外部数据仅来源于属性 props)
  3. 一般情况下,用于第三方组件(通用组件)

旧的 API

创建上下文

只有类组件才可以创建上下文

  1. 给类组件书写静态属性 childContextTypes,使用该属性对上下文中的数据类型进行约束
  2. 添加实例方法 getChildContext,该方法返回的对象,即为上下文中的数据,该数据必须满足类型约束,该方法会在每次 render 之后运行。

使用上下文中的数据

要求:如果要使用上下文中的数据,组件必须有一个静态属性 contextTypes,该属性描述了需要获取的上下文中的数据类型。

  1. 可以在组件的构造函数中,通过第二个参数,获取上下文数据。但是由于构造函数只会运行一次,后面上下文数据改变了,不会更新
  2. 从组件的 context 属性中获取
  3. 在函数组件中,通过第二个参数,获取上下文数据。数据并不会流动异常,只是调用了父组件的函数而已

    创建上下文只能是类组件,获取上下文可以是类组件或函数组件

jsx
import React, { Component } from "react";
+    
Skip to content
On this page

Context 上下文

上下文:Context,表示做某一些事情的环境 React 中的上下文特点:

  1. 当某个组件创建了上下文后,上下文中的数据,会被所有后代组件共享
  2. 如果某个组件依赖了上下文,会导致该组件不再纯粹(纯粹指的是外部数据仅来源于属性 props)
  3. 一般情况下,用于第三方组件(通用组件)

旧的 API

创建上下文

只有类组件才可以创建上下文

  1. 给类组件书写静态属性 childContextTypes,使用该属性对上下文中的数据类型进行约束
  2. 添加实例方法 getChildContext,该方法返回的对象,即为上下文中的数据,该数据必须满足类型约束,该方法会在每次 render 之后运行。

使用上下文中的数据

要求:如果要使用上下文中的数据,组件必须有一个静态属性 contextTypes,该属性描述了需要获取的上下文中的数据类型。

  1. 可以在组件的构造函数中,通过第二个参数,获取上下文数据。但是由于构造函数只会运行一次,后面上下文数据改变了,不会更新
  2. 从组件的 context 属性中获取
  3. 在函数组件中,通过第二个参数,获取上下文数据。数据并不会流动异常,只是调用了父组件的函数而已

    创建上下文只能是类组件,获取上下文可以是类组件或函数组件

jsx
import React, { Component } from "react";
 import PropTypes from "prop-types";
 const types = {
   a: PropTypes.number,
@@ -811,8 +811,8 @@
   );
 }
 

对 React context 的理解

在 React 中,数据传递一般使用 props 传递数据,维持单向数据流,这样可以让组件之间的关系变得简单且可预测,但是单项数据流在某些场景中并不适用。单纯一对的父子组件传递并无问题,但要是组件之间层层依赖深入,props 就需要层层传递显然,这样做太繁琐了。

Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。

可以把 context 当做是特定一个组件树内共享的 store,用来做数据传递。简单说就是,当你不想在组件树中通过逐层传递 props 或者 state 的方式来传递数据时,可以使用 Context 来实现跨层级的组件数据传递。

JS 的代码块在执行期间,会创建一个相应的作用域链,这个作用域链记录着运行时 JS 代码块执行期间所能访问的活动对象,包括变量和函数,JS 程序通过作用域链访问到代码块内部或者外部的变量和函数。

假如以 JS 的作用域链作为类比,React 组件提供的 Context 对象其实就好比一个提供给子组件访问的作用域,而 Context 对象的属性可以看成作用域上的活动对象。由于组件 的 Context 由其父节点链上所有组件通 过 getChildContext()返回的 Context 对象组合而成,所以,组件通过 Context 是可以访问到其父组件链上所有节点组件提供的 Context 的属性。

为什么 React 并不推荐优先考虑使用 Context?

  • Context 目前还处于实验阶段,可能会在后面的发行版本中有很大的变化,事实上这种情况已经发生了,所以为了避免给今后升级带来大的影响和麻烦,不建议在 app 中使用 context。
  • 尽管不建议在 app 中使用 context,但是独有组件而言,由于影响范围小于 app,如果可以做到高内聚,不破坏组件树之间的依赖关系,可以考虑使用 context
  • 对于组件之间的数据通信或者状态管理,有效使用 props 或者 state 解决,然后再考虑使用第三方的成熟库进行解决,以上的方法都不是最佳的方案的时候,在考虑 context。
  • context 的更新需要通过 setState()触发,但是这并不是很可靠的,Context 支持跨组件的访问,但是如果中间的子组件通过一些方法不影响更新,比如 shouldComponentUpdate() 返回 false 那么不能保证 Context 的更新一定可以使用 Context 的子组件,因此,Context 的可靠性需要关注
- - + + \ No newline at end of file diff --git a/react/dva.html b/react/dva.html index 3eb0c2c8..95b00608 100644 --- a/react/dva.html +++ b/react/dva.html @@ -6,15 +6,15 @@ dva | Sunny's blog - - + + -
Skip to content
On this page

dva

DANGER

已经不维护了,仅供学习

官方网站:https://dvajs.com dva 不仅仅是一个第三方库,更是一个框架,它主要整合了 redux 的相关内容,让我们处理数据更加容易,实际上,dva 依赖了很多:react、react-router、redux、redux-saga、react-redux、connected-react-router 等。

dva 的使用

  1. dva 默认导出一个函数,通过调用该函数,可以得到一个 dva 对象
  2. dva 对象.router:路由方法,传入一个函数,该函数返回一个 React 节点,将来,应用程序启动后,会自动渲染该节点。
  3. dva 对象.start: 该方法用于启动 dva 应用程序,可以认为启动的就是 react 程序,该函数传入一个选择器,用于选中页面中的某个 dom 元素,react 会将内容渲染到该元素内部。
  4. dva 对象.model: 该方法用于定义一个模型,该模型可以理解为 redux 的 action、reducer、redux-saga 副作用处理的整合,整合成一个对象,将该对象传入 model 方法即可。
  5. namespace:命名空间,该属性是一个字符串,字符串的值,会被作为仓库中的属性保存
  6. state:该模型的默认状态
  7. reducers: 该属性配置为一个对象,对象中的每个方法就是一个 reducer,dva 约定,方法的名字,就是匹配的 action 类型
  8. effects: 处理副作用,底层是使用 redux-saga 实现的,该属性配置为一个对象,对象中的每隔方法均处理一个副作用,方法的名字,就是匹配的 action 类型。
    1. 函数的参数 1:action
    2. 参数 2:封装好的 saga/effects 对象
  9. subscriptions:配置为一个对象,该对象中可以写任意数量任意名称的属性,每个属性是一个函数,这些函数会在模型加入到仓库中后立即运行。
  10. 在 dva 中同步路由到仓库
  11. 在调用 dva 函数时,配置 history 对象
  12. 使用 ConnectedRouter 提供路由上下文
  13. 配置:
  14. history:同步到仓库的 history 对象
  15. initialState:创建 redux 仓库时,使用的默认状态
  16. onError: 当仓库的运行发生错误的时候,运行的函数
  17. onAction: 可以配置 redux 中间件
    1. 传入一个中间件对象
    2. 传入一个中间件数组
  18. onStateChange: 当仓库中的状态发生变化时运行的函数
  19. onReducer:对模型中的 reducer 的进一步封装
  20. onEffect:类似于对模型中的 effect 的进一步封装
  21. extraReducers:用于配置额外的 reducer,它是一个对象,对象的每一个属性是一个方法,每个方法就是一个需要合并的 reducer,方法名即属性名。
  22. extraEnhancers: 它是用于封装 createStore 函数的,dva 会将原来的仓库创建函数作为参数传递,返回一个新的用于创建仓库的函数。函数必须放置到数组中。

dva 插件

通过dva对象.use(插件),来使用插件,插件本质上就是一个对象,该对象与配置对象相同,dva 会在启动时,将传递的插件对象混合到配置中。

dva-loading

该插件会在仓库中加入一个状态,名称为 loading,它是一个对象,其中有以下属性

  • global:全局是否正在处理副作用(加载),只要有任何一个模型在处理副作用,则该属性为 true
  • models:一个对象,对象中的属性名以及属性的值,表示哪个对应的模型是否在处理副作用中(加载中)
  • effects:一个对象,对象中的属性名以及属性的值,表示是哪个 action 触发了副作用
- - +
Skip to content
On this page

dva

DANGER

已经不维护了,仅供学习

官方网站:https://dvajs.com dva 不仅仅是一个第三方库,更是一个框架,它主要整合了 redux 的相关内容,让我们处理数据更加容易,实际上,dva 依赖了很多:react、react-router、redux、redux-saga、react-redux、connected-react-router 等。

dva 的使用

  1. dva 默认导出一个函数,通过调用该函数,可以得到一个 dva 对象
  2. dva 对象.router:路由方法,传入一个函数,该函数返回一个 React 节点,将来,应用程序启动后,会自动渲染该节点。
  3. dva 对象.start: 该方法用于启动 dva 应用程序,可以认为启动的就是 react 程序,该函数传入一个选择器,用于选中页面中的某个 dom 元素,react 会将内容渲染到该元素内部。
  4. dva 对象.model: 该方法用于定义一个模型,该模型可以理解为 redux 的 action、reducer、redux-saga 副作用处理的整合,整合成一个对象,将该对象传入 model 方法即可。
  5. namespace:命名空间,该属性是一个字符串,字符串的值,会被作为仓库中的属性保存
  6. state:该模型的默认状态
  7. reducers: 该属性配置为一个对象,对象中的每个方法就是一个 reducer,dva 约定,方法的名字,就是匹配的 action 类型
  8. effects: 处理副作用,底层是使用 redux-saga 实现的,该属性配置为一个对象,对象中的每隔方法均处理一个副作用,方法的名字,就是匹配的 action 类型。
    1. 函数的参数 1:action
    2. 参数 2:封装好的 saga/effects 对象
  9. subscriptions:配置为一个对象,该对象中可以写任意数量任意名称的属性,每个属性是一个函数,这些函数会在模型加入到仓库中后立即运行。
  10. 在 dva 中同步路由到仓库
  11. 在调用 dva 函数时,配置 history 对象
  12. 使用 ConnectedRouter 提供路由上下文
  13. 配置:
  14. history:同步到仓库的 history 对象
  15. initialState:创建 redux 仓库时,使用的默认状态
  16. onError: 当仓库的运行发生错误的时候,运行的函数
  17. onAction: 可以配置 redux 中间件
    1. 传入一个中间件对象
    2. 传入一个中间件数组
  18. onStateChange: 当仓库中的状态发生变化时运行的函数
  19. onReducer:对模型中的 reducer 的进一步封装
  20. onEffect:类似于对模型中的 effect 的进一步封装
  21. extraReducers:用于配置额外的 reducer,它是一个对象,对象的每一个属性是一个方法,每个方法就是一个需要合并的 reducer,方法名即属性名。
  22. extraEnhancers: 它是用于封装 createStore 函数的,dva 会将原来的仓库创建函数作为参数传递,返回一个新的用于创建仓库的函数。函数必须放置到数组中。

dva 插件

通过dva对象.use(插件),来使用插件,插件本质上就是一个对象,该对象与配置对象相同,dva 会在启动时,将传递的插件对象混合到配置中。

dva-loading

该插件会在仓库中加入一个状态,名称为 loading,它是一个对象,其中有以下属性

  • global:全局是否正在处理副作用(加载),只要有任何一个模型在处理副作用,则该属性为 true
  • models:一个对象,对象中的属性名以及属性的值,表示哪个对应的模型是否在处理副作用中(加载中)
  • effects:一个对象,对象中的属性名以及属性的值,表示是哪个 action 触发了副作用
+ + \ No newline at end of file diff --git a/react/event.html b/react/event.html index 28b87dff..adf574b2 100644 --- a/react/event.html +++ b/react/event.html @@ -6,17 +6,17 @@ React 事件机制 | Sunny's blog - - + + -
Skip to content
On this page

React 事件机制

React 事件机制

jsx
<div onClick={this.handleClick.bind(this)}>点我</div>
+    
Skip to content
On this page

React 事件机制

React 事件机制

jsx
<div onClick={this.handleClick.bind(this)}>点我</div>
 
<div onClick={this.handleClick.bind(this)}>点我</div>
 

React 并不是将 click 事件绑定到了 div 的真实 DOM 上,而是在 document 处监听了所有的事件,当事件发生并且冒泡到 document 处的时候,React 将事件内容封装并交由真正的处理函数运行。这样的方式不仅仅减少了内存的消耗,还能在组件挂在销毁时统一订阅和移除事件。

除此之外,冒泡到 document 上的事件也不是原生的浏览器事件,而是由 react 自己实现的合成事件(SyntheticEvent)。因此如果不想要是事件冒泡的话应该调用 event.preventDefault()方法,而不是调用 event.stopProppagation()方法。

JSX 上写的事件并没有绑定在对应的真实 DOM 上,而是通过事件代理的方式,将所有的事件都统一绑定在了 document 上。这样的方式不仅减少了内存消耗,还能在组件挂载销毁时统一订阅和移除事件。

另外冒泡到 document 上的事件也不是原生浏览器事件,而是 React 自己实现的合成事件。因此我们如果不想要事件冒泡的话,调用 event.stopPropagation 是无效的,而应该调用 event.preventDefault

实现合成事件的目的如下:

  • 合成事件首先抹平了浏览器之间的兼容问题,另外这是一个跨浏览器原生事件包装器,赋予了跨浏览器开发的能力;
  • 对于原生浏览器事件来说,浏览器会给监听器创建一个事件对象。如果你有很多的事件监听,那么就需要分配很多的事件对象,造成高额的内存分配问题。但是对于合成事件来说,有一个事件池专门来管理它们的创建和销毁,当事件需要被使用时,就会从池子中复用对象,事件回调结束后,就会销毁事件对象上的属性,从而便于下次复用事件对象。

React 的事件和普通的 HTML 事件有什么不同?

区别:

  • 对于事件名称命名方式,原生事件为全小写,react 事件采用小驼峰;
  • 对于事件函数处理语法,原生事件为字符串,react 事件为函数;
  • react 事件不能采用 return false 的方式来阻止浏览器的默认行为,而必须要地明确地调用preventDefault()来阻止默认行为。

合成事件是 react 模拟原生 DOM 事件所有能力的一个事件对象,其优点如下:

  • 兼容所有浏览器,更好的跨平台;
  • 将事件统一存放在一个数组,避免频繁的新增与删除(垃圾回收)。
  • 方便 react 统一管理和事务机制。

事件的执行顺序为原生事件先执行,合成事件后执行,合成事件会冒泡绑定到 document 上,所以尽量避免原生事件与合成事件混用,如果原生事件阻止冒泡,可能会导致合成事件不执行,因为需要冒泡到 document 上合成事件才会执行。

React 组件中怎么做事件代理?它的原理是什么?

React 基于 Virtual DOM 实现了一个 SyntheticEvent 层(合成事件层),定义的事件处理器会接收到一个合成事件对象的实例,它符合 W3C 标准,且与原生的浏览器事件拥有同样的接口,支持冒泡机制,所有的事件都自动绑定在最外层上。

在 React 底层,主要对合成事件做了两件事:

  • 事件委派: React 会把所有的事件绑定到结构的最外层,使用统一的事件监听器,这个事件监听器上维持了一个映射来保存所有组件内部事件监听和处理函数。
  • 自动绑定: React 组件中,每个方法的上下文都会指向该组件的实例,即自动绑定 this 为当前组件。
- - + + \ No newline at end of file diff --git a/react/hooks.html b/react/hooks.html index 6460bbe9..8d05a8a0 100644 --- a/react/hooks.html +++ b/react/hooks.html @@ -6,13 +6,13 @@ Hooks | Sunny's blog - - + + -
Skip to content
On this page

HOOK 简介

组件:无状态组件(函数组件)、类组件 类组件中的麻烦:

  1. this 指向问题
  2. 繁琐的生命周期
  3. 其他问题

HOOK 专门用于增强函数组件的功能(HOOK 在类组件中是不能使用的),使之理论上可以成为类组件的替代品 官方强调:没有必要更改已经完成的类组件,官方目前没有计划取消类组件,只是鼓励使用函数组件 HOOK(钩子)本质上是一个函数(命名上总是以use开头),该函数可以挂载任何功能 HOOK 种类:

  1. useState 解决状态
  2. useEffect 解决生命周期函数
  3. 其他...

不同 HOOK 能解决某一方面的功能

State Hook

State Hook 是一个在函数组件中使用的函数(useState),用于在函数组件中使用状态

useState

  • 函数有一个参数,这个参数的值表示状态的默认值
  • 函数的返回值是一个数组,该数组一定包含两项
    • 第一项:当前状态的值
    • 第二项:改变状态的函数

一个函数组件中可以有多个状态,这种做法非常有利于横向切分关注点。 函数组件的写法

jsx
import React, { useState } from "react";
+    
Skip to content
On this page

HOOK 简介

组件:无状态组件(函数组件)、类组件 类组件中的麻烦:

  1. this 指向问题
  2. 繁琐的生命周期
  3. 其他问题

HOOK 专门用于增强函数组件的功能(HOOK 在类组件中是不能使用的),使之理论上可以成为类组件的替代品 官方强调:没有必要更改已经完成的类组件,官方目前没有计划取消类组件,只是鼓励使用函数组件 HOOK(钩子)本质上是一个函数(命名上总是以use开头),该函数可以挂载任何功能 HOOK 种类:

  1. useState 解决状态
  2. useEffect 解决生命周期函数
  3. 其他...

不同 HOOK 能解决某一方面的功能

State Hook

State Hook 是一个在函数组件中使用的函数(useState),用于在函数组件中使用状态

useState

  • 函数有一个参数,这个参数的值表示状态的默认值
  • 函数的返回值是一个数组,该数组一定包含两项
    • 第一项:当前状态的值
    • 第二项:改变状态的函数

一个函数组件中可以有多个状态,这种做法非常有利于横向切分关注点。 函数组件的写法

jsx
import React, { useState } from "react";
 // 函数组件的写法
 export default function App() {
   // const arr = useState(0); // 不填默认undefined。使用一个状态,该状态默认值是0
@@ -303,8 +303,8 @@
 

这里,首先假定 ExampleComponent 可见,然后再改变它的状态,让它不可见 。映射为真实的 DOM 操作是这样的,React 会创建一个 div 节点。

javascript
<div class="visible">visbile</div>
 
<div class="visible">visbile</div>
 

当把 visbile 的值变为 false 时,就会替换 class 属性为 hidden,并重写内部的 innerText 为 hidden。这样一个生成补丁、更新差异的过程统称为 diff 算法。

diff 算法可以总结为三个策略,分别从树、组件及元素三个层面进行复杂度的优化:

策略一:忽略节点跨层级操作场景,提升比对效率。(基于树进行对比)

这一策略需要进行树比对,即对树进行分层比较。树比对的处理手法是非常“暴力”的,即两棵树只对同一层次的节点进行比较,如果发现节点已经不存在了,则该节点及其子节点会被完全删除掉,不会用于进一步的比较,这就提升了比对效率。

策略二:如果组件的 class 一致,则默认为相似的树结构,否则默认为不同的树结构。(基于组件进行对比)

在组件比对的过程中:

  • 如果组件是同一类型则进行树比对;
  • 如果不是则直接放入补丁中。

只要父组件类型不同,就会被重新渲染。这也就是为什么 shouldComponentUpdate、PureComponent 及 React.memo 可以提高性能的原因。

策略三:同一层级的子节点,可以通过标记 key 的方式进行列表对比。(基于节点进行对比)

元素比对主要发生在同层级中,通过标记节点操作生成补丁。节点操作包含了插入、移动、删除等。其中节点重新排序同时涉及插入、移动、删除三个操作,所以效率消耗最大,此时策略三起到了至关重要的作用。通过标记 key 的方式,React 可以直接移动 DOM 节点,降低内耗。

React key

Keys 是 React 用于追踪哪些列表中元素被修改、被添加或者被移除的辅助标识。在开发过程中,我们需要保证某个元素的 key 在其同级元素中具有唯一性。

在 React Diff 算法中 React 会借助元素的 Key 值来判断该元素是新近创建的还是被移动而来的元素,从而减少不必要的元素重渲染此外,React 还需要借助 Key 值来判断元素与本地状态的关联关系。

注意事项:

  • key 值一定要和具体的元素—一对应;
  • 尽量不要用数组的 index 去作为 key;
  • 不要在 render 的时候用随机数或者其他操作给元素加上不稳定的 key,这样造成的性能开销比不加 key 的情况下更糟糕。

虚拟 DOM 的引入与直接操作原生 DOM 相比,哪一个效率更高,为什么

虚拟 DOM 相对原生的 DOM 不一定是效率更高,如果只修改一个按钮的文案,那么虚拟 DOM 的操作无论如何都不可能比真实的 DOM 操作更快。在首次渲染大量 DOM 时,由于多了一层虚拟 DOM 的计算,虚拟 DOM 也会比 innerHTML 插入慢。它能保证性能下限,在真实 DOM 操作的时候进行针对性的优化时,还是更快的。所以要根据具体的场景进行探讨。

在整个 DOM 操作的演化过程中,其实主要矛盾并不在于性能,而在于开发者写得爽不爽,在于研发体验/研发效率。虚拟 DOM 不是别的,正是前端开发们为了追求更好的研发体验和研发效率而创造出来的高阶产物。虚拟 DOM 并不一定会带来更好的性能,React 官方也从来没有把虚拟 DOM 作为性能层面的卖点对外输出过。虚拟 DOM 的优越之处在于,它能够在提供更爽、更高效的研发模式(也就是函数式的 UI 编程方式)的同时,仍然保持一个还不错的性能。

React 与 Vue 的 diff 算法

diff 算法是指生成更新补丁的方式,主要应用于虚拟 DOM 树变化后,更新真实 DOM。所以 diff 算法一定存在这样一个过程:触发更新 → 生成补丁 → 应用补丁。

React 的 diff 算法,触发更新的时机主要在 state 变化与 hooks 调用之后。此时触发虚拟 DOM 树变更遍历,采用了深度优先遍历算法。但传统的遍历方式,效率较低。为了优化效率,使用了分治的方式。将单一节点比对转化为了 3 种类型节点的比对,分别是树、组件及元素,以此提升效率。

  • 树比对:由于网页视图中较少有跨层级节点移动,两株虚拟 DOM 树只对同一层次的节点进行比较。
  • 组件比对:如果组件是同一类型,则进行树比对,如果不是,则直接放入到补丁中。
  • 元素比对:主要发生在同层级中,通过标记节点操作生成补丁,节点操作对应真实的 DOM 剪裁操作。

以上是经典的 React diff 算法内容。自 React 16 起,引入了 Fiber 架构。为了使整个更新过程可随时暂停恢复,节点与树分别采用了 FiberNode 与 FiberTree 进行重构。fiberNode 使用了双链表的结构,可以直接找到兄弟节点与子节点。整个更新过程由 current 与 workInProgress 两株树双缓冲完成。workInProgress 更新完成后,再通过修改 current 相关指针指向新节点。

Vue 的整体 diff 策略与 React 对齐,虽然缺乏时间切片能力,但这并不意味着 Vue 的性能更差,因为在 Vue 3 初期引入过,后期因为收益不高移除掉了。除了高帧率动画,在 Vue 中其他的场景几乎都可以使用防抖和节流去提高响应性能。

- - + + \ No newline at end of file diff --git a/react/index.html b/react/index.html index e2a626b6..83757edc 100644 --- a/react/index.html +++ b/react/index.html @@ -6,13 +6,13 @@ React OnePage | Sunny's blog - - + + -
Skip to content
On this page

React OnePage

React 是由Facebook研发的、用于解决 UI 复杂度的开源JavaScript 库,目前由 React 联合社区维护。

它不是框架,只是为了解决 UI 复杂度而诞生的一个库,并不是 MVVM MVC

React 的特点

  • 轻量:React 的开发版所有源码(包含注释)仅 3000 多行
  • 原生:所有的 React 的代码都是用原生 JS 书写而成的,不依赖其他任何库
  • 易扩展:React 对代码的封装程度较低,也没有过多的使用魔法,所以 React 中的很多功能都可以扩展。
  • 不依赖宿主环境:React 只依赖原生 JS 语言,不依赖任何其他东西,包括运行环境。因此,它可以被轻松的移植到浏览器、桌面应用、移动端。
  • 渐近式:React 并非框架,对整个工程没有强制约束力。这对与那些已存在的工程,可以逐步的将其改造为 React,而不需要全盘重写。
  • 单向数据流:所有的数据自顶而下的流动
  • 用 JS 代码声明界面
  • 组件化

Hello World

html
<!-- React的核心库,与宿主环境无关 -->
+    
Skip to content
On this page

React OnePage

React 是由Facebook研发的、用于解决 UI 复杂度的开源JavaScript 库,目前由 React 联合社区维护。

它不是框架,只是为了解决 UI 复杂度而诞生的一个库,并不是 MVVM MVC

React 的特点

  • 轻量:React 的开发版所有源码(包含注释)仅 3000 多行
  • 原生:所有的 React 的代码都是用原生 JS 书写而成的,不依赖其他任何库
  • 易扩展:React 对代码的封装程度较低,也没有过多的使用魔法,所以 React 中的很多功能都可以扩展。
  • 不依赖宿主环境:React 只依赖原生 JS 语言,不依赖任何其他东西,包括运行环境。因此,它可以被轻松的移植到浏览器、桌面应用、移动端。
  • 渐近式:React 并非框架,对整个工程没有强制约束力。这对与那些已存在的工程,可以逐步的将其改造为 React,而不需要全盘重写。
  • 单向数据流:所有的数据自顶而下的流动
  • 用 JS 代码声明界面
  • 组件化

Hello World

html
<!-- React的核心库,与宿主环境无关 -->
 <script
   crossorigin
   src="https://unpkg.com/react@16/umd/react.development.js"
@@ -929,8 +929,8 @@
   );
 }
 

错误边界

默认情况下,若一个组件在渲染期间(render)发生错误,会导致整个组件树全部被卸载

错误边界:是一个组件,该组件会捕获到渲染期间(render)子组件发生的错误,并有能力阻止错误继续传播

让某个组件捕获错误

  1. 编写生命周期函数 getDerivedStateFromError
    1. 静态函数
    2. 运行时间点:渲染子组件的过程中,发生错误之后,在更新页面之前
    3. 注意:只有子组件发生错误,才会运行该函数。自己发生错误处理不了
    4. 该函数返回一个对象,React 会将该对象的属性覆盖掉当前组件的 state
    5. 参数:错误对象
    6. 通常,该函数用于改变状态
  2. 编写生命周期函数 componentDidCatch
    1. 实例方法
    2. 运行时间点:渲染子组件的过程中,发生错误,更新页面之后,由于其运行时间点比较靠后,因此不太会在该函数中改变状态
    3. 通常,该函数用于记录错误消息

细节

某些错误,错误边界组件无法捕获

  1. 自身的错误
  2. 异步的错误
  3. 事件中的错误

这些错误,需要用 try catch 处理

总结:仅处理渲染子组件期间的同步错误

React 中的事件

这里的事件:React 内置的 DOM 组件中的事件

  1. 给 document 注册事件
  2. 几乎所有的元素的事件处理,均在 document 的事件中处理
    1. 一些不冒泡的事件,是直接在元素上监听
    2. 一些 document 上面没有的事件,直接在元素上监听
  3. 在 document 的事件处理,React 会根据虚拟 DOM 树的完成事件函数的调用
  4. React 的事件参数,并非真实的 DOM 事件参数,是 React 合成的一个对象,该对象类似于真实 DOM 的事件参数
    1. stopPropagation,阻止事件在虚拟 DOM 树中冒泡
    2. nativeEvent,可以得到真实的 DOM 事件对象
    3. 为了提高执行效率,React 使用事件对象池来处理事件对象

注意事项

  1. 如果给真实的 DOM 注册事件,阻止了事件冒泡,则会导致 react 的相应事件无法触发
  2. 如果给真实的 DOM 注册事件,事件会先于 React 事件运行
  3. 通过 React 的事件中阻止事件冒泡,无法阻止真实的 DOM 事件冒泡
  4. 可以通过 nativeEvent.stopImmediatePropagation(),阻止 document 上剩余事件的执行
  5. 在事件处理程序中,不要异步的使用事件对象,如果一定要使用,需要调用 persist 函数
- - + + \ No newline at end of file diff --git a/react/lifecycle.html b/react/lifecycle.html index 46779450..85117f18 100644 --- a/react/lifecycle.html +++ b/react/lifecycle.html @@ -6,13 +6,13 @@ 生命周期 | Sunny's blog - - + + -
Skip to content
On this page

生命周期

componentWillReceiveProps

该方法当props发生变化时执行,初始化render时不执行,在这个回调函数里面,你可以根据属性的变化,通过调用this.setState()来更新你的组件状态,旧的属性还是可以通过this.props来获取,这里调用更新状态是安全的,并不会触发额外的render调用。

使用好处: 在这个生命周期中,可以在子组件的 render 函数执行前获取新的 props,从而更新子组件自己的 state。 可以将数据请求放在这里进行执行,需要传的参数则从 componentWillReceiveProps(nextProps)中获取。而不必将所有的请求都放在父组件中。于是该请求只会在该组件渲染时才会发出,从而减轻请求负担。

componentWillReceiveProps 在初始化 render 的时候不会执行,它会在 Component 接受到新的状态(Props)时被触发,一般用于父组件状态更新时子组件的重新渲染。

React 通常将组件生命周期分为三个阶段:

  • 装载阶段(Mount),组件第一次在 DOM 树中被渲染的过程;
  • 更新过程(Update),组件状态发生变化,重新更新渲染的过程;
  • 卸载过程(Unmount),组件从 DOM 树中被移除的过程;

组件挂载阶段

挂载阶段组件被创建,然后组件实例插入到 DOM 中,完成组件的第一次渲染,该过程只会发生一次,在此阶段会依次调用以下这些方法:

  • constructor
  • getDerivedStateFromProps
  • render
  • componentDidMount
(1)constructor

组件的构造函数,第一个被执行,若没有显式定义它,会有一个默认的构造函数,但是若显式定义了构造函数,我们必须在构造函数中执行 super(props),否则无法在构造函数中拿到 this。

如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数Constructor

constructor 中通常只做两件事:

  • 初始化组件的 state
  • 给事件处理方法绑定 this
javascript
constructor(props) {
+    
Skip to content
On this page

生命周期

componentWillReceiveProps

该方法当props发生变化时执行,初始化render时不执行,在这个回调函数里面,你可以根据属性的变化,通过调用this.setState()来更新你的组件状态,旧的属性还是可以通过this.props来获取,这里调用更新状态是安全的,并不会触发额外的render调用。

使用好处: 在这个生命周期中,可以在子组件的 render 函数执行前获取新的 props,从而更新子组件自己的 state。 可以将数据请求放在这里进行执行,需要传的参数则从 componentWillReceiveProps(nextProps)中获取。而不必将所有的请求都放在父组件中。于是该请求只会在该组件渲染时才会发出,从而减轻请求负担。

componentWillReceiveProps 在初始化 render 的时候不会执行,它会在 Component 接受到新的状态(Props)时被触发,一般用于父组件状态更新时子组件的重新渲染。

React 通常将组件生命周期分为三个阶段:

  • 装载阶段(Mount),组件第一次在 DOM 树中被渲染的过程;
  • 更新过程(Update),组件状态发生变化,重新更新渲染的过程;
  • 卸载过程(Unmount),组件从 DOM 树中被移除的过程;

组件挂载阶段

挂载阶段组件被创建,然后组件实例插入到 DOM 中,完成组件的第一次渲染,该过程只会发生一次,在此阶段会依次调用以下这些方法:

  • constructor
  • getDerivedStateFromProps
  • render
  • componentDidMount
(1)constructor

组件的构造函数,第一个被执行,若没有显式定义它,会有一个默认的构造函数,但是若显式定义了构造函数,我们必须在构造函数中执行 super(props),否则无法在构造函数中拿到 this。

如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数Constructor

constructor 中通常只做两件事:

  • 初始化组件的 state
  • 给事件处理方法绑定 this
javascript
constructor(props) {
   super(props);
   // 不要在构造函数中调用 setState,可以直接给 state 设置初始值
   this.state = { counter: 0 }
@@ -279,8 +279,8 @@
 }
 
 

state 和 props 触发更新的生命周期分别有什么区别?

state 更新流程:

这个过程当中涉及的函数:

  1. shouldComponentUpdate: 当组件的 state 或 props 发生改变时,都会首先触发这个生命周期函数。它会接收两个参数:nextProps, nextState——它们分别代表传入的新 props 和新的 state 值。拿到这两个值之后,我们就可以通过一些对比逻辑来决定是否有 re-render(重渲染)的必要了。如果该函数的返回值为 false,则生命周期终止,反之继续;

注意:此方法仅作为性能优化的方式而存在。不要企图依靠此方法来“阻止”渲染,因为这可能会产生 bug。应该考虑使用内置的 PureComponent 组件,而不是手动编写 shouldComponentUpdate()

  1. componentWillUpdate:当组件的 state 或 props 发生改变时,会在渲染之前调用 componentWillUpdate。componentWillUpdate 是 React16 废弃的三个生命周期之一。过去,我们可能希望能在这个阶段去收集一些必要的信息(比如更新前的 DOM 信息等等),现在我们完全可以在 React16 的 getSnapshotBeforeUpdate 中去做这些事;
  2. componentDidUpdate:componentDidUpdate() 会在 UI 更新后会被立即调用。它接收 prevProps(上一次的 props 值)作为入参,也就是说在此处我们仍然可以进行 props 值对比(再次说明 componentWillUpdate 确实鸡肋哈)。

props 更新流程:

相对于 state 更新,props 更新后唯一的区别是增加了对 componentWillReceiveProps 的调用。关于 componentWillReceiveProps,需要知道这些事情:

  • componentWillReceiveProps:它在 Component 接受到新的 props 时被触发。componentWillReceiveProps 会接收一个名为 nextProps 的参数(对应新的 props 值)。该生命周期是 React16 废弃掉的三个生命周期之一。在它被废弃前,可以用它来比较 this.props 和 nextProps 来重新 setState。在 React16 中,用一个类似的新生命周期 getDerivedStateFromProps 来代替它。

React 中发起网络请求应该在哪个生命周期中进行?为什么?

对于异步请求,最好放在 componentDidMount 中去操作,对于同步的状态改变,可以放在 componentWillMount 中,一般用的比较少。

如果认为在 componentWillMount 里发起请求能提早获得结果,这种想法其实是错误的,通常 componentWillMount 比 componentDidMount 早不了多少微秒,网络上任何一点延迟,这一点差异都可忽略不计。

react 的生命周期: constructor() -> componentWillMount() -> render() -> componentDidMount()

上面这些方法的调用是有次序的,由上而下依次调用。

  • constructor 被调用是在组件准备要挂载的最开始,此时组件尚未挂载到网页上。
  • componentWillMount 方法的调用在 constructor 之后,在 render 之前,在这方法里的代码调用 setState 方法不会触发重新 render,所以它一般不会用来作加载数据之用。
  • componentDidMount 方法中的代码,是在组件已经完全挂载到网页上才会调用被执行,所以可以保证数据的加载。此外,在这方法中调用 setState 方法,会触发重新渲染。所以,官方设计这个方法就是用来加载外部数据用的,或处理其他的副作用代码。与组件上的数据无关的加载,也可以在 constructor 里做,但 constructor 是做组件 state 初绐化工作,并不是做加载数据这工作的,constructor 里也不能 setState,还有加载的时间太长或者出错,页面就无法加载出来。所以有副作用的代码都会集中在 componentDidMount 方法里。

总结:

  • 跟服务器端渲染(同构)有关系,如果在 componentWillMount 里面获取数据,fetch data 会执行两次,一次在服务器端一次在客户端。在 componentDidMount 中可以解决这个问题,componentWillMount 同样也会 render 两次。
  • 在 componentWillMount 中 fetch data,数据一定在 render 后才能到达,如果忘记了设置初始状态,用户体验不好。
  • react16.0 以后,componentWillMount 可能会被执行多次。

React 16 中新生命周期有哪些

关于 React16 开始应用的新生命周期:

可以看出,React16 自上而下地对生命周期做了另一种维度的解读:

  • Render 阶段:用于计算一些必要的状态信息。这个阶段可能会被 React 暂停,这一点和 React16 引入的 Fiber 架构(我们后面会重点讲解)是有关的;
  • Pre-commit 阶段:所谓“commit”,这里指的是“更新真正的 DOM 节点”这个动作。所谓 Pre-commit,就是说我在这个阶段其实还并没有去更新真实的 DOM,不过 DOM 信息已经是可以读取的了;
  • Commit 阶段:在这一步,React 会完成真实 DOM 的更新工作。Commit 阶段,我们可以拿到真实 DOM(包括 refs)。

与此同时,新的生命周期在流程方面,仍然遵循“挂载”、“更新”、“卸载”这三个广义的划分方式。它们分别对应到:

  • 挂载过程:
    • constructor
    • getDerivedStateFromProps
    • render
    • componentDidMount
  • 更新过程:
    • getDerivedStateFromProps
    • shouldComponentUpdate
    • render
    • getSnapshotBeforeUpdate
    • componentDidUpdate
  • 卸载过程:
    • componentWillUnmount
- - + + \ No newline at end of file diff --git a/react/react-interview.html b/react/react-interview.html index 231e822a..236de143 100644 --- a/react/react-interview.html +++ b/react/react-interview.html @@ -6,13 +6,13 @@ 那些年,被问烂了的 React 面试题 | Sunny's blog - - + + -
Skip to content
On this page

那些年,被问烂了的 React 面试题

React 组件命名推荐的方式是哪个?

通过引用而不是使用来命名组件 displayName。

使用 displayName 命名组件:

javascript
export default React.createClass({  displayName: 'TodoApp',  // ...})
+    
Skip to content
On this page

那些年,被问烂了的 React 面试题

React 组件命名推荐的方式是哪个?

通过引用而不是使用来命名组件 displayName。

使用 displayName 命名组件:

javascript
export default React.createClass({  displayName: 'TodoApp',  // ...})
 
export default React.createClass({  displayName: 'TodoApp',  // ...})
 

React 推荐的方法:

javascript
export default class TodoApp extends React.Component {  // ...}
 
export default class TodoApp extends React.Component {  // ...}
@@ -1443,8 +1443,8 @@
   name: PropTypes.string,
 };
 

当然,如果项目汇中使用了 TypeScript,那么就可以不用 PropTypes 来校验,而使用 TypeScript 定义接口来校验 props。

- - + + \ No newline at end of file diff --git a/react/react-redux-router.html b/react/react-redux-router.html index 012d9344..18ee40b3 100644 --- a/react/react-redux-router.html +++ b/react/react-redux-router.html @@ -6,15 +6,15 @@ react-redux | Sunny's blog - - + + -
Skip to content
On this page

react-redux

  • React: 组件化的 UI 界面处理方案
  • React-Router: 根据地址匹配路由,最终渲染不同的组件
  • Redux:处理数据以及数据变化的方案(主要用于处理共享数据)

如果一个组件,仅用于渲染一个 UI 界面,而没有状态(通常是一个函数组件),该组件叫做展示组件 如果一个组件,仅用于提供数据,没有任何属于自己的 UI 界面,则该组件叫做容器组件,容器组件纯粹是为了给其他组件提供数据。

react-redux 库:链接 redux 和 react

  • Provider 组件:没有任何 UI 界面,该组件的作用,是将 redux 的仓库放到一个上下文中。
  • connect:高阶组件,用于链接仓库和组件的
    • 细节一:如果对返回的容器组件加上额外的属性,则这些属性会直接传递到展示组件
    • 第一个参数:mapStateToProps:
      • 参数 1:整个仓库的状态
      • 参数 2:使用者传递的属性对象
    • 第二个参数:
      • 情况 1:传递一个函数 mapDispatchToProps
        • 参数 1:dispatch 函数
        • 参数 2:使用者传递的属性对象
        • 函数返回的对象会作为属性传递到展示组件中(作为事件处理函数存在)
      • 情况 2:传递一个对象,对象的每个属性是一个 action 创建函数,当事件触发时,会自动的 dispatch 函数返回的 action
    • 细节二:如果不传递第二个参数,通过 connect 连接的组件,会自动得到一个属性:dispatch,使得组件有能力自行触发 action,但是,不推荐这样做。

知识

  1. chrome 插件:redux-devtools
  2. 使用 npm 安装第三方库:redux-devtools-extension

redux 和 router 的结合(connected-react-router)

希望把路由信息放进仓库统一管理的时候才需要这个库

用于将 redux 和 react-router 进行结合

本质上,router 中的某些数据可能会跟数据仓库中的数据进行联动

该组件会将下面的路由数据和仓库保持同步

  1. action:它不是 redux 的 action,它表示当前路由跳转的方式(PUSH、POP、REPLACE)
  2. location:它记录了当前的地址信息

该库中的内容:

connectRouter

这是一个函数,调用它,会返回一个用于管理仓库中路由信息的 reducer,该函数需要传递一个参数,参数是一个 history 对象。该对象,可以使用第三方库 history 得到。

routerMiddleware

该函数会返回一个 redux 中间件,用于拦截一些特殊的 action

ConnectedRouter

这是一个组件,用于向上下文提供一个 history 对象和其他的路由信息(与 react-router 提供的信息一致)

之所以需要新制作一个组件,是因为该库必须保证整个过程使用的是同一个 history 对象

一些 action 创建函数

  • push
  • replace
- - +
Skip to content
On this page

react-redux

  • React: 组件化的 UI 界面处理方案
  • React-Router: 根据地址匹配路由,最终渲染不同的组件
  • Redux:处理数据以及数据变化的方案(主要用于处理共享数据)

如果一个组件,仅用于渲染一个 UI 界面,而没有状态(通常是一个函数组件),该组件叫做展示组件 如果一个组件,仅用于提供数据,没有任何属于自己的 UI 界面,则该组件叫做容器组件,容器组件纯粹是为了给其他组件提供数据。

react-redux 库:链接 redux 和 react

  • Provider 组件:没有任何 UI 界面,该组件的作用,是将 redux 的仓库放到一个上下文中。
  • connect:高阶组件,用于链接仓库和组件的
    • 细节一:如果对返回的容器组件加上额外的属性,则这些属性会直接传递到展示组件
    • 第一个参数:mapStateToProps:
      • 参数 1:整个仓库的状态
      • 参数 2:使用者传递的属性对象
    • 第二个参数:
      • 情况 1:传递一个函数 mapDispatchToProps
        • 参数 1:dispatch 函数
        • 参数 2:使用者传递的属性对象
        • 函数返回的对象会作为属性传递到展示组件中(作为事件处理函数存在)
      • 情况 2:传递一个对象,对象的每个属性是一个 action 创建函数,当事件触发时,会自动的 dispatch 函数返回的 action
    • 细节二:如果不传递第二个参数,通过 connect 连接的组件,会自动得到一个属性:dispatch,使得组件有能力自行触发 action,但是,不推荐这样做。

知识

  1. chrome 插件:redux-devtools
  2. 使用 npm 安装第三方库:redux-devtools-extension

redux 和 router 的结合(connected-react-router)

希望把路由信息放进仓库统一管理的时候才需要这个库

用于将 redux 和 react-router 进行结合

本质上,router 中的某些数据可能会跟数据仓库中的数据进行联动

该组件会将下面的路由数据和仓库保持同步

  1. action:它不是 redux 的 action,它表示当前路由跳转的方式(PUSH、POP、REPLACE)
  2. location:它记录了当前的地址信息

该库中的内容:

connectRouter

这是一个函数,调用它,会返回一个用于管理仓库中路由信息的 reducer,该函数需要传递一个参数,参数是一个 history 对象。该对象,可以使用第三方库 history 得到。

routerMiddleware

该函数会返回一个 redux 中间件,用于拦截一些特殊的 action

ConnectedRouter

这是一个组件,用于向上下文提供一个 history 对象和其他的路由信息(与 react-router 提供的信息一致)

之所以需要新制作一个组件,是因为该库必须保证整个过程使用的是同一个 history 对象

一些 action 创建函数

  • push
  • replace
+ + \ No newline at end of file diff --git a/react/render.html b/react/render.html index 24791af3..757bd3a0 100644 --- a/react/render.html +++ b/react/render.html @@ -6,13 +6,13 @@ 渲染原理 | Sunny's blog - - + + -
Skip to content
On this page

渲染原理

渲染:生成用于显示的对象,以及将这些对象形成真实的 DOM 对象

  • React 元素:React Element,通过 React.createElement 创建(语法糖:JSX)
    • 例如:
    • <div><h1>标题</h1></div>
    • <App />
  • React 节点:专门用于渲染到 UI 界面的对象,React 会通过 React 元素,创建 React 节点,ReactDOM 一定是通过 React 节点来进行渲染的
  • 节点类型:
    • React DOM 节点:创建该节点的 React 元素类型是一个字符串
    • React 组件节点:创建该节点的 React 元素类型是一个函数或是一个类
    • React 文本节点:由字符串、数字创建的
    • React 空节点:由 null、undefined、false、true
    • React 数组节点:该节点由一个数组创建
  • 真实 DOM:通过 document.createElement 创建的 dom 元素

首次渲染(新节点渲染)

  1. 通过参数的值创建节点
  2. 根据不同的节点,做不同的事情
    1. 文本节点:通过 document.createTextNode 创建真实的文本节点
    2. 空节点:什么都不做,但是存在会占位
    3. 数组节点:遍历数组,将数组每一项递归创建节点(回到第 1 步进行反复操作,直到遍历结束)
    4. DOM 节点:通过 document.createElement 创建真实的 DOM 对象,然后立即设置该真实 DOM 元素的各种属性,然后遍历对应 React 元素的 children 属性,递归操作(回到第 1 步进行反复操作,直到遍历结束)
    5. 组件节点
      1. 函数组件:调用函数(该函数必须返回一个可以生成节点的内容),将该函数的返回结果递归生成节点(回到第 1 步进行反复操作,直到遍历结束)
      2. 类组件:
        1. 创建该类的实例
        2. 立即调用对象的生命周期方法:static getDerivedStateFromProps
        3. 运行该对象的 render 方法,拿到节点对象(将该节点递归操作,回到第 1 步进行反复操作)
        4. 将该组件的 componentDidMount 加入到执行队列(先进先出,先进先执行),当整个虚拟 DOM 树全部构建完毕,并且将真实的 DOM 对象加入到容器中后,执行该队列
  3. 生成出虚拟 DOM 树之后,将该树保存起来,以便后续使用
  4. 将之前生成的真实的 DOM 对象,加入到容器中。
javascript
const app = (
+    
Skip to content
On this page

渲染原理

渲染:生成用于显示的对象,以及将这些对象形成真实的 DOM 对象

  • React 元素:React Element,通过 React.createElement 创建(语法糖:JSX)
    • 例如:
    • <div><h1>标题</h1></div>
    • <App />
  • React 节点:专门用于渲染到 UI 界面的对象,React 会通过 React 元素,创建 React 节点,ReactDOM 一定是通过 React 节点来进行渲染的
  • 节点类型:
    • React DOM 节点:创建该节点的 React 元素类型是一个字符串
    • React 组件节点:创建该节点的 React 元素类型是一个函数或是一个类
    • React 文本节点:由字符串、数字创建的
    • React 空节点:由 null、undefined、false、true
    • React 数组节点:该节点由一个数组创建
  • 真实 DOM:通过 document.createElement 创建的 dom 元素

首次渲染(新节点渲染)

  1. 通过参数的值创建节点
  2. 根据不同的节点,做不同的事情
    1. 文本节点:通过 document.createTextNode 创建真实的文本节点
    2. 空节点:什么都不做,但是存在会占位
    3. 数组节点:遍历数组,将数组每一项递归创建节点(回到第 1 步进行反复操作,直到遍历结束)
    4. DOM 节点:通过 document.createElement 创建真实的 DOM 对象,然后立即设置该真实 DOM 元素的各种属性,然后遍历对应 React 元素的 children 属性,递归操作(回到第 1 步进行反复操作,直到遍历结束)
    5. 组件节点
      1. 函数组件:调用函数(该函数必须返回一个可以生成节点的内容),将该函数的返回结果递归生成节点(回到第 1 步进行反复操作,直到遍历结束)
      2. 类组件:
        1. 创建该类的实例
        2. 立即调用对象的生命周期方法:static getDerivedStateFromProps
        3. 运行该对象的 render 方法,拿到节点对象(将该节点递归操作,回到第 1 步进行反复操作)
        4. 将该组件的 componentDidMount 加入到执行队列(先进先出,先进先执行),当整个虚拟 DOM 树全部构建完毕,并且将真实的 DOM 对象加入到容器中后,执行该队列
  3. 生成出虚拟 DOM 树之后,将该树保存起来,以便后续使用
  4. 将之前生成的真实的 DOM 对象,加入到容器中。
javascript
const app = (
   <div className="assaf">
     <h1>
       标题
@@ -241,8 +241,8 @@
   }
 }
 

面试题 3:div 包住两个 App,再问执行顺序

队列:Comp1 App Comp1 App 左边执行完在执行右边而已(递归)

为什么不能写对象

对象可以构成 React 元素,但是没法构建节点,节点需要渲染,{a:1,b:2} 没法渲染

更新节点

更新的场景:

  1. 重新调用 ReactDOM.render,触发根节点更新
  2. 在类组件的实例对象中调用 setState,会导致该实例所在的节点更新

节点的更新

  • 如果调用的是 ReactDOM.render,进入根节点的对比(diff)更新
  • 如果调用的是 setState
      1. 运行生命周期函数,static getDerivedStateFromProps
      1. 运行 shouldComponentUpdate,如果该函数返回 false,终止当前流程
      1. 运行 render,得到一个新的节点,进入该新的节点的对比更新
      1. 将生命周期函数 getSnapshotBeforeUpdate 加入执行队列,以待将来执行
      1. 将生命周期函数 componentDidUpdate 加入执行队列,以待将来执行

以上两点的后续步骤:

  1. 更新虚拟 DOM 树
  2. 完成真实的 DOM 更新
  3. 依次调用执行队列中的 componentDidMount
  4. 依次调用执行队列中的 getSnapshotBeforeUpdate
  5. 依次调用执行队列中的 componentDidUpdate

对比更新

将新产生的节点,对比之前虚拟 DOM 中的节点,发现差异,完成更新

问题:对比之前 DOM 树中哪个节点

React 为了提高对比效率,做出以下假设

  1. 假设节点不会出现层次的移动(对比时,直接找到旧树中对应位置的节点进行对比)
  2. 不同的节点类型会生成不同的结构
    1. 相同的节点类型:节点本身类型相同,如果是由 React 元素生成,type 值还必须一致
    2. 其他的,都属于不相同的节点类型
  3. 多个兄弟通过唯一标识(key)来确定对比的新节点

key 值的作用:用于通过旧节点,寻找对应的新节点,如果某个旧节点有 key 值,则其更新时,会寻找相同层级中的相同 key 值的节点,进行对比。

key 值应该在一个范围内唯一(兄弟节点中),并且应该保持稳定

找到了对比的目标

判断节点类型是否一致

  • 一致

根据不同的节点类型,做不同的事情

空节点:不做任何事情

DOM 节点

  1. 直接重用之前的真实 DOM 对象
  2. 将其属性的变化记录下来,以待将来统一完成更新(现在不会真正的变化)
  3. 遍历该新的 React 元素的子元素,递归对比更新

文本节点

  1. 直接重用之前的真实 DOM 对象
  2. 将新的文本变化记录下来,将来统一完成更新

组件节点

函数组件:重新调用函数,得到一个节点对象,进入递归对比更新

类组件

  1. 重用之前的实例
  2. 调用生命周期方法 getDerivedStateFromProps
  3. 调用生命周期方法 shouldComponentUpdate,若该方法返回 false,终止
  4. 运行 render,得到新的节点对象,进入递归对比更新
  5. 将该对象的 getSnapshotBeforeUpdate 加入队列
  6. 将该对象的 componentDidUpdate 加入队列

数组节点:遍历数组进行递归对比更新

  • 不一致

整体上,卸载旧的节点,全新创建新的节点

创建新节点

进入新节点的挂载流程

卸载旧节点

  1. 文本节点、DOM 节点、数组节点、空节点、函数组件节点:直接放弃该节点,如果节点有子节点,递归卸载节点
  2. 类组件节点
    1. 直接放弃该节点
    2. 调用该节点的 componentWillUnMount 函数
    3. 递归卸载子节点

没有找到对比的目标

新的 DOM 树中有节点被删除

新的 DOM 树中有节点添加

  • 创建新加入的节点
  • 卸载多余的旧节点
- - + + \ No newline at end of file diff --git a/react/transition.html b/react/transition.html index eb806bbe..ca02fc71 100644 --- a/react/transition.html +++ b/react/transition.html @@ -6,15 +6,15 @@ React 动画 | Sunny's blog - - + + -
Skip to content
On this page

React 动画

React 动画库:react-transition-group 文档在 npm 搜索https://reactcommunity.org/react-transition-group/

React 动画 - CSSTransition

当进入时,发生:

  1. 为 CSSTransition 内部的 DOM 根元素(后续统一称之为 DOM 元素)添加样式 enter
  2. 在一下帧(enter 样式已经完全应用到了元素),立即为该元素添加样式 enter-active
  3. 当 timeout 结束后,去掉之前的样式,添加样式 enter-done

当退出时,发生:

  1. 为 CSSTransition 内部的 DOM 根元素(后续统一称之为 DOM 元素)添加样式 exit
  2. 在一下帧(exit 样式已经完全应用到了元素),立即为该元素添加样式 exit-active
  3. 当 timeout 结束后,去掉之前的样式,添加样式 exit-done

设置classNames属性,可以指定类样式的名称

  1. 字符串:为类样式添加前缀
  2. 对象:为每个类样式指定具体的名称(非前缀)

关于首次渲染时的类样式,appear、apear-active、apear-done,它和 enter 的唯一区别在于完成时,会同时加入 apear-done 和 enter-done

还可以与 Animate.css 联用

React 动画 - SwitchTransition

和 CSSTransition 的区别:用于有秩序的切换内部组件

默认情况下:out-in 先退出后进入

  1. 当 key 值改变时,会将之前的 DOM 根元素添加退出样式(exit,exit-active)
  2. 退出完成后,将该 DOM 元素移除
  3. 重新渲染内部 DOM 元素
  4. 为新渲染的 DOM 根元素添加进入样式(enter, enter-active, enter-done)

in-out:

  1. 重新渲染内部 DOM 元素,保留之前的元素
  2. 为新渲染的 DOM 根元素添加进入样式(enter, enter-active, enter-done)
  3. 将之前的 DOM 根元素添加退出样式(exit,exit-active)
  4. 退出完成后,将该 DOM 元素移除

该库寻找 dom 元素的方式,是使用已经过时的 API:findDomNode,该方法可以找到某个组件下的 DOM 根元素,先保留,创建新的之后在删除

React 动画 - TransitionGroup

该组件的 children,接收多个 Transition 或 CSSTransition 组件,该组件用于根据这些子组件的 key 值,控制他们的进入和退出状态

- - +
Skip to content
On this page

React 动画

React 动画库:react-transition-group 文档在 npm 搜索https://reactcommunity.org/react-transition-group/

React 动画 - CSSTransition

当进入时,发生:

  1. 为 CSSTransition 内部的 DOM 根元素(后续统一称之为 DOM 元素)添加样式 enter
  2. 在一下帧(enter 样式已经完全应用到了元素),立即为该元素添加样式 enter-active
  3. 当 timeout 结束后,去掉之前的样式,添加样式 enter-done

当退出时,发生:

  1. 为 CSSTransition 内部的 DOM 根元素(后续统一称之为 DOM 元素)添加样式 exit
  2. 在一下帧(exit 样式已经完全应用到了元素),立即为该元素添加样式 exit-active
  3. 当 timeout 结束后,去掉之前的样式,添加样式 exit-done

设置classNames属性,可以指定类样式的名称

  1. 字符串:为类样式添加前缀
  2. 对象:为每个类样式指定具体的名称(非前缀)

关于首次渲染时的类样式,appear、apear-active、apear-done,它和 enter 的唯一区别在于完成时,会同时加入 apear-done 和 enter-done

还可以与 Animate.css 联用

React 动画 - SwitchTransition

和 CSSTransition 的区别:用于有秩序的切换内部组件

默认情况下:out-in 先退出后进入

  1. 当 key 值改变时,会将之前的 DOM 根元素添加退出样式(exit,exit-active)
  2. 退出完成后,将该 DOM 元素移除
  3. 重新渲染内部 DOM 元素
  4. 为新渲染的 DOM 根元素添加进入样式(enter, enter-active, enter-done)

in-out:

  1. 重新渲染内部 DOM 元素,保留之前的元素
  2. 为新渲染的 DOM 根元素添加进入样式(enter, enter-active, enter-done)
  3. 将之前的 DOM 根元素添加退出样式(exit,exit-active)
  4. 退出完成后,将该 DOM 元素移除

该库寻找 dom 元素的方式,是使用已经过时的 API:findDomNode,该方法可以找到某个组件下的 DOM 根元素,先保留,创建新的之后在删除

React 动画 - TransitionGroup

该组件的 children,接收多个 Transition 或 CSSTransition 组件,该组件用于根据这些子组件的 key 值,控制他们的进入和退出状态

+ + \ No newline at end of file diff --git a/react/umi.html b/react/umi.html index ca984b80..aa3b6f8b 100644 --- a/react/umi.html +++ b/react/umi.html @@ -6,15 +6,15 @@ umijs 简介 | Sunny's blog - - + + -
Skip to content
On this page

umijs 简介

官网:https://umijs.org/

umijs, nextjs(ssr 服务端渲染),antd,antd-pro(antd+umijs)

  • 插件化
  • 开箱即用
  • 约定式路由

全局安装 umi

提供了一个命令行工具:umi,通过该命令可以对 umi 工程进行操作

umi 还可以使用对应的脚手架

  • dev: 使用开发模式启动工程
  • build:打包

约定式路由

umi 对路由的处理,主要通过两种方式:

  1. 约定式:使用约定好的文件夹和文件,来代表页面,umi 会根据开发者书写的页面,生成路由配置。
  2. 配置式:直接书写路由配置文件

路由匹配

  • umi 约定,工程中的 pages 文件夹中存放的是页面。如果工程包含 src 目录,则 src/pages 是页面文件夹。

  • umi 约定,页面的文件名,以及页面的文件路径,是该页面匹配的路由

  • umi 约定,如果页面的文件名是 index(不写 index 才能访问,写了反而不能访问了),则可以省略文件名(首页)(注意避免文件名和当前目录中的文件夹名称相同)

  • umi 约定,如果 src/layout 目录存在,则该目录中的 index.js 表示的是全局的通用布局,布局中的 children 则会添加具体的页面。

  • umi 约定,如果 pages 文件夹中包含_layout.js,则 layout.js 所在的目录以及其所有的子目录中的页面,共用该布局。

  • 404 约定,umi 约定,pages/404.js,表示 404 页面,如果路由无匹配,则会渲染该页面。该约定在开发模式中无效,只有部署后生效。

  • 使用$名称,会产生动态路由

路由跳转

  • 跳转链接: 导入umi/linkumi/navlink
  • 代码跳转: 导入umi/router

导入模块时,@表示 src 目录

路由信息的获取

所有的页面、布局组件,都会通过属性 props,收到下面的属性

  • match:等同于 react-router 的 match
  • history:等同于 react-router 的 history(history.location.query 被封装成了一个对象,使用的是 query-string 库进行的封装)
  • location:等同于 react-router 的 location(location.query 被封装成了一个对象,使用的是 query-string 库进行的封装)
  • route:对应的是路由配置

如果需要在普通组件中获取路由信息,则需要使用 withRouter 封装,可以通过umi/withRouter导入

配置式路由

当使用了路由配置后,约定式路由全部失效。

两种方式书写 umi 配置:

  1. 使用根目录下的文件.umirc.js
  2. 使用根目录下的文件config/config.js

进行路由配置时,每个配置就是一个匹配规则,并且,每个配置是一个对象,对象中的某些属性,会直接形成 Route 组件的属性

注意:

  • component 配置项,需要填写页面组件的路径,路径相对于 pages 文件夹
  • 如果配置项没有 exact,则会自动添加 exact 为 true
  • 每一个路由配置,可以添加任何属性
  • Routes 属性是一个数组,数组的每一项是一个组件路径,路径相对于项目根目录,当匹配到路由后,会转而渲染该属性指定的组件,并会将 component 组件作为 children 放到匹配的组件中

路由配置中的信息,同样可以放到约定式路由中,方式是,为约定式路由添加第一个文档注释(注释的格式的 YAML),需要将注释放到最开始的位置

YAML 格式

  • 键值对,冒号后需要加上空格
  • 如果某个属性有多个键或多个值,需要进行缩进(空格,不能 tab)

使用 dva

官方插件集 umi-plugin-react 文档:https://umijs.org/zh/plugin/umi-plugin-react.html

dva 插件和 umi 整合后,将模型分为两种:

  1. 全局模型:所有页面通用,工程一开始启动后,模型就会挂载到仓库
  2. 局部模型:只能被某些页面使用,访问具体的页面时才会挂载到仓库

定义全局模型

src/models目录下定义的 js 文件都会被看作是全局模型,默认情况下,模型的命名空间和文件名一致。

定义局部模型

局部模型定义在 pages 文件夹或其子文件夹中,在哪个文件夹定义的模型,会被该文件夹中的所有页面以及子页面、以及该文件夹的祖先文件夹中的页面所共享。

局部模型的定义和全局模型的约定类似,需要创建一个 models 文件夹

使用样式

解决两个问题:

  1. 保证类样式名称的唯一性:css-module
  2. 样式代码的重复:less 或 sass

局部样式和全局样式

底层使用了 webpack 的加载器:css-loader(内部包含了 css-module 的功能)

css 文件 -> css-module -> 对象

  1. 某个组件特有的样式,不与其他组件共享,通常,将该样式文件与组件放置在同一个目录(非强制性)(要保证类样式名称唯一)
  2. 如果某些样式可能被某些组件共享,这样的样式,通常放到 assets/css 文件夹中。(要保证类样式名称唯一)
  3. 全局样式,名称一定唯一,不需要 css-module 处理。umijs 约定,src/global.css 样式,是全局样式,不会交给 css-module 处理。

less

less 代码 -> less-loader -> css 代码 -> css-module -> 对象

代理和数据模拟

代理

代理用于解决跨域问题

配置.umirc.js中的 proxy,配置方式和 devServer 中的 proxy 配置相同

数据模拟

用于解决前后端协同开发的问题

数据模拟可以让前端开发者在开发时,无视后端接口是否真正完成,因为使用的是模拟的数据

umijs 约定:

  1. mock 文件夹中的文件
  2. src/pages 文件夹中的_mock.js 文件

以上两种 JS 文件,均会被 umijs 读取,并作为数据模拟的配置

可以自行发挥,添加模拟数据,通常,我们会和 mockjs 配合。

配置

额外的约定文件

  • src/pages/document.ejs: 页面模板文件
  • src/global.js:在 umi 最开始启动时运行的 js 文件
  • src/app.js:作运行时配置的代码
    • patchRoutes: 函数,该函数会在 umi 读取完所有静态路由配置后执行
    • dva
      • config: 相当于 new dva(配置)
      • plugins: 相当于 dva.use(插件)
  • .env: 配置环境变量,这些变量会在 umi 编译期间发挥作用
    • UMI_ENV:umi 的环境变量值,可以是任意值,该值会影响到.umirc.js
    • PORT
    • MOCK

umirc 配置

umi 配置

书写在.umirc.js 文件中的配置

  • plugins:配置 umijs 的插件
  • routes:配置路由(会导致约定式路由失效)
  • history:history 对象模式(默认是 browser)
  • outputPath:使用 umi build 后,打包的目录名称,默认./dist
  • base: 相当于之前 BrowserRouter 中的 basename
  • publicPath: 指定静态资源所在的目录
  • exportStatic: 开启该配置后,会打包成多个静态页面,每个页面对应一个路由,开启多静态页面应用的前提条件是:没有动态路由

webpack 配置

umi 脚手架

create-umi

- - +
Skip to content
On this page

umijs 简介

官网:https://umijs.org/

umijs, nextjs(ssr 服务端渲染),antd,antd-pro(antd+umijs)

  • 插件化
  • 开箱即用
  • 约定式路由

全局安装 umi

提供了一个命令行工具:umi,通过该命令可以对 umi 工程进行操作

umi 还可以使用对应的脚手架

  • dev: 使用开发模式启动工程
  • build:打包

约定式路由

umi 对路由的处理,主要通过两种方式:

  1. 约定式:使用约定好的文件夹和文件,来代表页面,umi 会根据开发者书写的页面,生成路由配置。
  2. 配置式:直接书写路由配置文件

路由匹配

  • umi 约定,工程中的 pages 文件夹中存放的是页面。如果工程包含 src 目录,则 src/pages 是页面文件夹。

  • umi 约定,页面的文件名,以及页面的文件路径,是该页面匹配的路由

  • umi 约定,如果页面的文件名是 index(不写 index 才能访问,写了反而不能访问了),则可以省略文件名(首页)(注意避免文件名和当前目录中的文件夹名称相同)

  • umi 约定,如果 src/layout 目录存在,则该目录中的 index.js 表示的是全局的通用布局,布局中的 children 则会添加具体的页面。

  • umi 约定,如果 pages 文件夹中包含_layout.js,则 layout.js 所在的目录以及其所有的子目录中的页面,共用该布局。

  • 404 约定,umi 约定,pages/404.js,表示 404 页面,如果路由无匹配,则会渲染该页面。该约定在开发模式中无效,只有部署后生效。

  • 使用$名称,会产生动态路由

路由跳转

  • 跳转链接: 导入umi/linkumi/navlink
  • 代码跳转: 导入umi/router

导入模块时,@表示 src 目录

路由信息的获取

所有的页面、布局组件,都会通过属性 props,收到下面的属性

  • match:等同于 react-router 的 match
  • history:等同于 react-router 的 history(history.location.query 被封装成了一个对象,使用的是 query-string 库进行的封装)
  • location:等同于 react-router 的 location(location.query 被封装成了一个对象,使用的是 query-string 库进行的封装)
  • route:对应的是路由配置

如果需要在普通组件中获取路由信息,则需要使用 withRouter 封装,可以通过umi/withRouter导入

配置式路由

当使用了路由配置后,约定式路由全部失效。

两种方式书写 umi 配置:

  1. 使用根目录下的文件.umirc.js
  2. 使用根目录下的文件config/config.js

进行路由配置时,每个配置就是一个匹配规则,并且,每个配置是一个对象,对象中的某些属性,会直接形成 Route 组件的属性

注意:

  • component 配置项,需要填写页面组件的路径,路径相对于 pages 文件夹
  • 如果配置项没有 exact,则会自动添加 exact 为 true
  • 每一个路由配置,可以添加任何属性
  • Routes 属性是一个数组,数组的每一项是一个组件路径,路径相对于项目根目录,当匹配到路由后,会转而渲染该属性指定的组件,并会将 component 组件作为 children 放到匹配的组件中

路由配置中的信息,同样可以放到约定式路由中,方式是,为约定式路由添加第一个文档注释(注释的格式的 YAML),需要将注释放到最开始的位置

YAML 格式

  • 键值对,冒号后需要加上空格
  • 如果某个属性有多个键或多个值,需要进行缩进(空格,不能 tab)

使用 dva

官方插件集 umi-plugin-react 文档:https://umijs.org/zh/plugin/umi-plugin-react.html

dva 插件和 umi 整合后,将模型分为两种:

  1. 全局模型:所有页面通用,工程一开始启动后,模型就会挂载到仓库
  2. 局部模型:只能被某些页面使用,访问具体的页面时才会挂载到仓库

定义全局模型

src/models目录下定义的 js 文件都会被看作是全局模型,默认情况下,模型的命名空间和文件名一致。

定义局部模型

局部模型定义在 pages 文件夹或其子文件夹中,在哪个文件夹定义的模型,会被该文件夹中的所有页面以及子页面、以及该文件夹的祖先文件夹中的页面所共享。

局部模型的定义和全局模型的约定类似,需要创建一个 models 文件夹

使用样式

解决两个问题:

  1. 保证类样式名称的唯一性:css-module
  2. 样式代码的重复:less 或 sass

局部样式和全局样式

底层使用了 webpack 的加载器:css-loader(内部包含了 css-module 的功能)

css 文件 -> css-module -> 对象

  1. 某个组件特有的样式,不与其他组件共享,通常,将该样式文件与组件放置在同一个目录(非强制性)(要保证类样式名称唯一)
  2. 如果某些样式可能被某些组件共享,这样的样式,通常放到 assets/css 文件夹中。(要保证类样式名称唯一)
  3. 全局样式,名称一定唯一,不需要 css-module 处理。umijs 约定,src/global.css 样式,是全局样式,不会交给 css-module 处理。

less

less 代码 -> less-loader -> css 代码 -> css-module -> 对象

代理和数据模拟

代理

代理用于解决跨域问题

配置.umirc.js中的 proxy,配置方式和 devServer 中的 proxy 配置相同

数据模拟

用于解决前后端协同开发的问题

数据模拟可以让前端开发者在开发时,无视后端接口是否真正完成,因为使用的是模拟的数据

umijs 约定:

  1. mock 文件夹中的文件
  2. src/pages 文件夹中的_mock.js 文件

以上两种 JS 文件,均会被 umijs 读取,并作为数据模拟的配置

可以自行发挥,添加模拟数据,通常,我们会和 mockjs 配合。

配置

额外的约定文件

  • src/pages/document.ejs: 页面模板文件
  • src/global.js:在 umi 最开始启动时运行的 js 文件
  • src/app.js:作运行时配置的代码
    • patchRoutes: 函数,该函数会在 umi 读取完所有静态路由配置后执行
    • dva
      • config: 相当于 new dva(配置)
      • plugins: 相当于 dva.use(插件)
  • .env: 配置环境变量,这些变量会在 umi 编译期间发挥作用
    • UMI_ENV:umi 的环境变量值,可以是任意值,该值会影响到.umirc.js
    • PORT
    • MOCK

umirc 配置

umi 配置

书写在.umirc.js 文件中的配置

  • plugins:配置 umijs 的插件
  • routes:配置路由(会导致约定式路由失效)
  • history:history 对象模式(默认是 browser)
  • outputPath:使用 umi build 后,打包的目录名称,默认./dist
  • base: 相当于之前 BrowserRouter 中的 basename
  • publicPath: 指定静态资源所在的目录
  • exportStatic: 开启该配置后,会打包成多个静态页面,每个页面对应一个路由,开启多静态页面应用的前提条件是:没有动态路由

webpack 配置

umi 脚手架

create-umi

+ + \ No newline at end of file diff --git a/react/utils.html b/react/utils.html index 5790c703..d5458e6c 100644 --- a/react/utils.html +++ b/react/utils.html @@ -6,15 +6,15 @@ 工具 | Sunny's blog - - + + -
Skip to content
On this page

工具

严格模式

StrictMode(React.StrictMode),本质是一个组件,该组件不进行 UI 渲染(React.Fragment <> </>),它的作用是,在渲染内部组件时,发现不合适的代码。

  • 识别不安全的生命周期
  • 关于使用过时字符串 ref API 的警告
  • 关于使用废弃的 findDOMNode 方法的警告
  • 检测意外的副作用
    • React 要求,副作用代码仅出现在以下生命周期函数中
    • ComponentDidMount
    • ComponentDidUpdate
    • ComponentWillUnMount

副作用:一个函数中,做了一些会影响函数外部数据的事情,例如:

  1. 异步处理
  2. 改变参数值
  3. setState
  4. 本地存储
  5. 改变函数外部的变量

相反的,如果一个函数没有副作用,则可以认为该函数是一个纯函数

在严格模式下,虽然不能监控到具体的副作用代码,但它会将不能具有副作用的函数调用两遍,以便发现问题。(这种情况,仅在开发模式下有效)

  • 检测过时的 context API

Profiler

性能分析工具

分析某一次或多次提交(更新),涉及到的组件的渲染时间

火焰图:得到某一次提交,每个组件总的渲染时间以及自身的渲染时间

排序图:得到某一次提交,每个组件自身渲染时间的排序

组件图:某一个组件,在多次提交中,自身渲染花费的时间

- - +
Skip to content
On this page

工具

严格模式

StrictMode(React.StrictMode),本质是一个组件,该组件不进行 UI 渲染(React.Fragment <> </>),它的作用是,在渲染内部组件时,发现不合适的代码。

  • 识别不安全的生命周期
  • 关于使用过时字符串 ref API 的警告
  • 关于使用废弃的 findDOMNode 方法的警告
  • 检测意外的副作用
    • React 要求,副作用代码仅出现在以下生命周期函数中
    • ComponentDidMount
    • ComponentDidUpdate
    • ComponentWillUnMount

副作用:一个函数中,做了一些会影响函数外部数据的事情,例如:

  1. 异步处理
  2. 改变参数值
  3. setState
  4. 本地存储
  5. 改变函数外部的变量

相反的,如果一个函数没有副作用,则可以认为该函数是一个纯函数

在严格模式下,虽然不能监控到具体的副作用代码,但它会将不能具有副作用的函数调用两遍,以便发现问题。(这种情况,仅在开发模式下有效)

  • 检测过时的 context API

Profiler

性能分析工具

分析某一次或多次提交(更新),涉及到的组件的渲染时间

火焰图:得到某一次提交,每个组件总的渲染时间以及自身的渲染时间

排序图:得到某一次提交,每个组件自身渲染时间的排序

组件图:某一个组件,在多次提交中,自身渲染花费的时间

+ + \ No newline at end of file diff --git a/ts/TypeScript-onePage.html b/ts/TypeScript-onePage.html index 45c774f3..167d0777 100644 --- a/ts/TypeScript-onePage.html +++ b/ts/TypeScript-onePage.html @@ -6,13 +6,13 @@ TypeScript onePage | Sunny's blog - - + + -
Skip to content
On this page

TypeScript onePage

为什么学习 TS

  • 获得更好的开发体验
  • 解决 JS 中难以解决的问题
javascript
function getUserName() {
+    
Skip to content
On this page

TypeScript onePage

为什么学习 TS

  • 获得更好的开发体验
  • 解决 JS 中难以解决的问题
javascript
function getUserName() {
   if (Math.random() < 0.5) {
     return "sunny-117";
   }
@@ -2207,8 +2207,8 @@
 

声明文件

概述、编写、发布

概述

  1. 什么是声明文件?

.d.ts结尾的文件

  1. 声明文件有什么作用?

为 JS 代码提供类型声明

  1. 声明文件的位置
  • 放置到 tsconfig.json 配置中包含的目录中
  • 放置到 node_modules/@types 文件夹中
  • 手动配置
  • 与 JS 代码所在目录相同,并且文件名也相同的文件。用 ts 代码书写的工程发布之后的格式。

编写声明文件

手动编写   自动生成

  • 自动生成

工程是使用 ts 开发的,发布(编译)之后,是 js 文件,发布的是 js 文件。

如果发布的文件,需要其他开发者使用,可以使用声明文件,来描述发布结果中的类型。

配置tsconfig.json中的declaration:true即可

  • 手动编写
  1. 对已有库,它是使用 js 书写而成,并且更改该库的代码为 ts 成本较高,可以手动编写声明文件
  2. 对一些第三方库,它们使用 js 书写而成,并且这些第三方库没有提供声明文件,可以手动编写声明文件。

全局声明

声明一些全局的对象、属性、变量

namespace: 表示命名空间,可以将其认为是一个对象,命名空间中的内容,必须通过命名空间.成员名访问

模块声明

三斜线指令

在一个声明文件中,包含另一个声明文件

发布

  1. 当前工程使用 ts 开发

编译完成后,将编译结果所在文件夹直接发布到 npm 上即可

  1. 为其他第三方库开发的声明文件

发布到@types/**中。

1) 进入 github 的开源项目:https://github.com/DefinitelyTyped/DefinitelyTyped

2) fork 到自己的开源库中

3) 从自己的开源库中克隆到本地

4) 本地新建分支(例如:mylodash4.3),在新分支中进行声明文件的开发

在types目录中新建文件夹,在新的文件夹中开发声明文件
 
在types目录中新建文件夹,在新的文件夹中开发声明文件
 

5) push 分支到你的开源库

6) 到官方的开源库中,提交 pull request

7) 等待官方管理员审核(1 天)

审核通过之后,会将你的分支代码合并到主分支,然后发布到 npm。

之后,就可以通过命令npm install @types/你发布的库名

- - + + \ No newline at end of file diff --git a/vue/SSR.html b/vue/SSR.html index 803b4927..0bce9ae5 100644 --- a/vue/SSR.html +++ b/vue/SSR.html @@ -6,15 +6,15 @@ SSR | Sunny's blog - - + + -
Skip to content
On this page

SSR

SPA

SPA( single-page application )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载。

优点:

  • 用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染;
  • 基于上面一点,SPA 相对对服务器压力小;
  • 前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理;

缺点:

  • 初次加载耗时多:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面按需加载;
  • 前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理;
  • SEO 难度较大:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。

Vue SSR 的实现原理

  • app.js 作为客户端与服务端的公用入口,导出 Vue 根实例,供客户端 entry 与服务端 entry 使用。客户端 entry 主要作用挂载到 DOM 上,服务端 entry 除了创建和返回实例,还需要进行路由匹配与数据预获取。
  • webpack 为客服端打包一个 ClientBundle,为服务端打包一个 ServerBundle
  • 服务器接收请求时,会根据 url,加载相应组件,获取和解析异步数据,创建一个读取 Server BundleBundleRenderer,然后生成 html 发送给客户端。
  • 客户端混合,客户端收到从服务端传来的 DOM 与自己的生成的 DOM 进行对比,把不相同的 DOM 激活,使其可以能够响应后续变化,这个过程称为客户端激活(也就是转换为单页应用)。为确保混合成功,客户 端与服务器端需要共享同一套数据。在服务端,可以在渲染之前获取数据,填充到 store 里,这样,在客户端挂载到 DOM 之前,可以直接从 store 里取数据。首屏的动态数据通过 window.INITIAL_STATE 发送到客户端
  • VueSSR 的原理,主要就是通过 vue-server-rendererVue 的组件输出成一个完整 HTML,输出到客户端,到达客户端后重新展开为一个单页应用。
- - +
Skip to content
On this page

SSR

SPA

SPA( single-page application )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载。

优点:

  • 用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染;
  • 基于上面一点,SPA 相对对服务器压力小;
  • 前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理;

缺点:

  • 初次加载耗时多:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面按需加载;
  • 前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理;
  • SEO 难度较大:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。

Vue SSR 的实现原理

  • app.js 作为客户端与服务端的公用入口,导出 Vue 根实例,供客户端 entry 与服务端 entry 使用。客户端 entry 主要作用挂载到 DOM 上,服务端 entry 除了创建和返回实例,还需要进行路由匹配与数据预获取。
  • webpack 为客服端打包一个 ClientBundle,为服务端打包一个 ServerBundle
  • 服务器接收请求时,会根据 url,加载相应组件,获取和解析异步数据,创建一个读取 Server BundleBundleRenderer,然后生成 html 发送给客户端。
  • 客户端混合,客户端收到从服务端传来的 DOM 与自己的生成的 DOM 进行对比,把不相同的 DOM 激活,使其可以能够响应后续变化,这个过程称为客户端激活(也就是转换为单页应用)。为确保混合成功,客户 端与服务器端需要共享同一套数据。在服务端,可以在渲染之前获取数据,填充到 store 里,这样,在客户端挂载到 DOM 之前,可以直接从 store 里取数据。首屏的动态数据通过 window.INITIAL_STATE 发送到客户端
  • VueSSR 的原理,主要就是通过 vue-server-rendererVue 的组件输出成一个完整 HTML,输出到客户端,到达客户端后重新展开为一个单页应用。
+ + \ No newline at end of file diff --git a/vue/challages.html b/vue/challages.html index 802bcd81..1ff9fe7c 100644 --- a/vue/challages.html +++ b/vue/challages.html @@ -6,13 +6,13 @@ vue 手写篇 | Sunny's blog - - + + -
Skip to content
On this page

vue 手写篇

实现一个迷你版的reactivity

js
let activeEffect = undefined;
+    
Skip to content
On this page

vue 手写篇

实现一个迷你版的reactivity

js
let activeEffect = undefined;
 const targetMap = new WeakMap();
 
 const effect = function (fn) {
@@ -495,8 +495,8 @@
 
 // 设置没有的属性,删除属性监控不到,所以要用$set,$delete
 
- - + + \ No newline at end of file diff --git a/vue/component-communication.html b/vue/component-communication.html index 6591c96b..18e0e268 100644 --- a/vue/component-communication.html +++ b/vue/component-communication.html @@ -6,13 +6,13 @@ Vuejs 组件通信概览 | Sunny's blog - - + + -
Skip to content
On this page

Vuejs 组件通信概览

父子组件通信

绝大部分vue本身提供的通信方式,都是父子组件通信

prop

最常见的组件通信方式之一,由父组件传递到子组件

event

最常见的组件通信方式之一,当子组件发生了某些事,可以通过event通知父组件

styleclass

父组件可以向子组件传递styleclass,它们会合并到子组件的根元素中

示例

父组件

vue
<template>
+    
Skip to content
On this page

Vuejs 组件通信概览

父子组件通信

绝大部分vue本身提供的通信方式,都是父子组件通信

prop

最常见的组件通信方式之一,由父组件传递到子组件

event

最常见的组件通信方式之一,当子组件发生了某些事,可以通过event通知父组件

styleclass

父组件可以向子组件传递styleclass,它们会合并到子组件的根元素中

示例

父组件

vue
<template>
   <div id="app">
     <HelloWorld
       style="color:red"
@@ -399,8 +399,8 @@
   }
 }
 

缺点:无法跟踪数据的变化,如果组件数变得复杂,任何组件都有权改动他,仓库数据出了问题,难以判断那个步骤出现问题。

eventbus

组件通知事件总线发生了某件事,事件总线通知其他监听该事件的所有组件运行某个函数

$emit$listeners通信的异同

相同点:均可实现子组件向父组件传递消息

差异点:

  • $emit更加符合单向数据流,子组件仅发出通知,由父组件监听做出改变;而$listeners则是在子组件中直接使用了父组件的方法。
  • 调试工具可以监听到子组件$emit的事件,但无法监听到$listeners中的方法调用。(想想为什么)
  • 由于$listeners中可以获得传递过来的方法,因此调用方法可以得到其返回值。但$emit仅仅是向父组件发出通知,无法知晓父组件处理的结果
- - + + \ No newline at end of file diff --git a/vue/computed.html b/vue/computed.html index 6d7d68ba..5dd070cb 100644 --- a/vue/computed.html +++ b/vue/computed.html @@ -6,15 +6,15 @@ computed | Sunny's blog - - + + -
Skip to content
On this page

computed

Vue2 中 computed 源码解读

methods

vue 对 methods 的处理比较简单,只需要遍历 methods 配置中的每个属性,将其对应的函数使用 bind 绑定当前组件实例后复制其引用到组件实例中即可

computed

当组件实例触发生命周期函数beforeCreate后,它会做一系列事情,其中就包括对 computed 的处理

它会遍历 computed 配置中的所有属性,为每一个属性创建一个 Watcher 对象,并传入一个函数,该函数的本质其实就是 computed 配置中的 getter,这样一来,getter 运行过程中就会收集依赖

但是和渲染函数不同,为计算属性创建的 Watcher 不会立即执行,因为要考虑到该计算属性是否会被渲染函数使用,如果没有使用,就不会得到执行。因此,在创建 Watcher 的时候,它使用了 lazy 配置,lazy 配置可以让 Watcher 不会立即执行。

收到lazy的影响,Watcher 内部会保存两个关键属性来实现缓存,一个是value,一个是dirty

value属性用于保存 Watcher 运行的结果,受lazy的影响,该值在最开始是undefined

dirty属性用于指示当前的value是否已经过时了,即是否为脏值,受lazy的影响,该值在最开始是true

Watcher创建好后,vue 会使用代理模式,将计算属性挂载到组件实例中

当读取计算属性时,vue 检查其对应的 Watcher 是否是脏值,如果是,则运行函数,计算依赖,并得到对应的值,保存在 Watcher 的 value 中,然后设置 dirty 为 false,然后返回。

如果 dirty 为 false,则直接返回 watcher 的 value,即为缓存的原理 巧妙的是,在依赖收集时,被依赖的数据不仅会收集到计算属性的 Watcher,还会收集到组件的 Watcher

当计算属性的依赖变化时,会先触发计算属性的 Watcher执行,此时,它只需设置**dirty**为 true 即可,不做任何处理。

由于依赖同时会收集到组件的 Watcher,因此组件会重新渲染,而重新渲染时又读取到了计算属性,由于计算属性目前已为 dirty,因此会重新运行 getter 进行运算

而对于计算属性的 setter,则极其简单,当设置计算属性时,直接运行 setter 即可

Vue3 中 computed 源码解读

TODO

常见问题

面试官:computed 和 methods 有什么区别?

我:

  1. 在使用时,computed 当做属性使用,而 methods 则当做方法调用
  2. computed 可以具有 getter 和 setter,因此可以赋值,而 methods 不行
  3. computed 无法接收多个参数,而 methods 可以
  4. computed 具有缓存,而 methods 没有

面试官:回去等通知吧!

更深入的回答:↑

watch 与 computed 的区别是什么?

  1. 都是观察数据变化的(相同)
  2. 计算属性将会混入到 vue 的实例中,所以需要监听自定义变量;watch 监听 data 、props 里面数据的变化;
  3. computed 有缓存,它依赖的值变了才会重新计算,watch 没有;
  4. watch 支持异步,computed 不支持;
  5. watch 是一对多(监听某一个值变化,执行对应操作);computed 是多对一(监听属性依赖于其他属性)
  6. watch 监听函数接收两个参数,第一个是最新值,第二个是输入之前的值;
  7. computed 属性是函数时,都有 get 和 set 方法,默认走 get 方法,get 必须有返回值(return)

watch 的 参数:deep:深度监听;immediate :组件加载立即触发回调函数执行

对于 Computed:

  • 它支持缓存,只有依赖的数据发生了变化,才会重新计算
  • 不支持异步,当 Computed 中有异步操作时,无法监听数据的变化
  • computed 的值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于 data 声明过,或者父组件传递过来的 props 中的数据进行计算的。
  • 如果一个属性是由其他属性计算而来的,这个属性依赖其他的属性,一般会使用 computed
  • 如果 computed 属性的属性值是函数,那么默认使用 get 方法,函数的返回值就是属性的属性值;在 computed 中,属性有一个 get 方法和一个 set 方法,当数据发生变化时,会调用 set 方法。

对于 Watch:

  • 它不支持缓存,数据变化时,它就会触发相应的操作
  • 支持异步监听
  • 监听的函数接收两个参数,第一个参数是最新的值,第二个是变化之前的值
  • 当一个属性发生变化时,就需要执行相应的操作
  • 监听数据必须是 data 中声明的或者父组件传递过来的 props 中的数据,当发生变化时,会触发其他操作,函数有两个的参数:
    • immediate:组件加载立即触发回调函数
    • deep:深度监听,发现数据内部的变化,在复杂数据类型中使用,例如数组中的对象发生变化。需要注意的是,deep 无法监听到数组和对象内部的变化。

当想要执行异步或者昂贵的操作以响应不断的变化时,就需要使用 watch。 总结:

  • computed 计算属性 : 依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值。
  • watch 侦听器 : 更多的是观察的作用,无缓存性,类似于某些数据的监听回调,每当监听的数据变化时都会执行回调进行后续操作。

运用场景:

  • 当需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时都要重新计算。
  • 当需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许执行异步操作 ( 访问一个 API ),限制执行该操作的频率,并在得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

Vue 中要获取当前时间你会放到 computed 还是 methods 里?

放在 computed 里面。因为 computed 只有在它的相关依赖发生改变时才会重新求值。相比而言,方法只要发生重新渲染,methods 调用总会执行所有函数。

- - +
Skip to content
On this page

computed

Vue2 中 computed 源码解读

methods

vue 对 methods 的处理比较简单,只需要遍历 methods 配置中的每个属性,将其对应的函数使用 bind 绑定当前组件实例后复制其引用到组件实例中即可

computed

当组件实例触发生命周期函数beforeCreate后,它会做一系列事情,其中就包括对 computed 的处理

它会遍历 computed 配置中的所有属性,为每一个属性创建一个 Watcher 对象,并传入一个函数,该函数的本质其实就是 computed 配置中的 getter,这样一来,getter 运行过程中就会收集依赖

但是和渲染函数不同,为计算属性创建的 Watcher 不会立即执行,因为要考虑到该计算属性是否会被渲染函数使用,如果没有使用,就不会得到执行。因此,在创建 Watcher 的时候,它使用了 lazy 配置,lazy 配置可以让 Watcher 不会立即执行。

收到lazy的影响,Watcher 内部会保存两个关键属性来实现缓存,一个是value,一个是dirty

value属性用于保存 Watcher 运行的结果,受lazy的影响,该值在最开始是undefined

dirty属性用于指示当前的value是否已经过时了,即是否为脏值,受lazy的影响,该值在最开始是true

Watcher创建好后,vue 会使用代理模式,将计算属性挂载到组件实例中

当读取计算属性时,vue 检查其对应的 Watcher 是否是脏值,如果是,则运行函数,计算依赖,并得到对应的值,保存在 Watcher 的 value 中,然后设置 dirty 为 false,然后返回。

如果 dirty 为 false,则直接返回 watcher 的 value,即为缓存的原理 巧妙的是,在依赖收集时,被依赖的数据不仅会收集到计算属性的 Watcher,还会收集到组件的 Watcher

当计算属性的依赖变化时,会先触发计算属性的 Watcher执行,此时,它只需设置**dirty**为 true 即可,不做任何处理。

由于依赖同时会收集到组件的 Watcher,因此组件会重新渲染,而重新渲染时又读取到了计算属性,由于计算属性目前已为 dirty,因此会重新运行 getter 进行运算

而对于计算属性的 setter,则极其简单,当设置计算属性时,直接运行 setter 即可

Vue3 中 computed 源码解读

TODO

常见问题

面试官:computed 和 methods 有什么区别?

我:

  1. 在使用时,computed 当做属性使用,而 methods 则当做方法调用
  2. computed 可以具有 getter 和 setter,因此可以赋值,而 methods 不行
  3. computed 无法接收多个参数,而 methods 可以
  4. computed 具有缓存,而 methods 没有

面试官:回去等通知吧!

更深入的回答:↑

watch 与 computed 的区别是什么?

  1. 都是观察数据变化的(相同)
  2. 计算属性将会混入到 vue 的实例中,所以需要监听自定义变量;watch 监听 data 、props 里面数据的变化;
  3. computed 有缓存,它依赖的值变了才会重新计算,watch 没有;
  4. watch 支持异步,computed 不支持;
  5. watch 是一对多(监听某一个值变化,执行对应操作);computed 是多对一(监听属性依赖于其他属性)
  6. watch 监听函数接收两个参数,第一个是最新值,第二个是输入之前的值;
  7. computed 属性是函数时,都有 get 和 set 方法,默认走 get 方法,get 必须有返回值(return)

watch 的 参数:deep:深度监听;immediate :组件加载立即触发回调函数执行

对于 Computed:

  • 它支持缓存,只有依赖的数据发生了变化,才会重新计算
  • 不支持异步,当 Computed 中有异步操作时,无法监听数据的变化
  • computed 的值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于 data 声明过,或者父组件传递过来的 props 中的数据进行计算的。
  • 如果一个属性是由其他属性计算而来的,这个属性依赖其他的属性,一般会使用 computed
  • 如果 computed 属性的属性值是函数,那么默认使用 get 方法,函数的返回值就是属性的属性值;在 computed 中,属性有一个 get 方法和一个 set 方法,当数据发生变化时,会调用 set 方法。

对于 Watch:

  • 它不支持缓存,数据变化时,它就会触发相应的操作
  • 支持异步监听
  • 监听的函数接收两个参数,第一个参数是最新的值,第二个是变化之前的值
  • 当一个属性发生变化时,就需要执行相应的操作
  • 监听数据必须是 data 中声明的或者父组件传递过来的 props 中的数据,当发生变化时,会触发其他操作,函数有两个的参数:
    • immediate:组件加载立即触发回调函数
    • deep:深度监听,发现数据内部的变化,在复杂数据类型中使用,例如数组中的对象发生变化。需要注意的是,deep 无法监听到数组和对象内部的变化。

当想要执行异步或者昂贵的操作以响应不断的变化时,就需要使用 watch。 总结:

  • computed 计算属性 : 依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值。
  • watch 侦听器 : 更多的是观察的作用,无缓存性,类似于某些数据的监听回调,每当监听的数据变化时都会执行回调进行后续操作。

运用场景:

  • 当需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时都要重新计算。
  • 当需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许执行异步操作 ( 访问一个 API ),限制执行该操作的频率,并在得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

Vue 中要获取当前时间你会放到 computed 还是 methods 里?

放在 computed 里面。因为 computed 只有在它的相关依赖发生改变时才会重新求值。相比而言,方法只要发生重新渲染,methods 调用总会执行所有函数。

+ + \ No newline at end of file diff --git a/vue/diff.html b/vue/diff.html index f2c003da..c53b316a 100644 --- a/vue/diff.html +++ b/vue/diff.html @@ -6,13 +6,13 @@ Vuejs diff 算法 | Sunny's blog - - + + -
Skip to content
On this page

Vuejs diff 算法

diff的时机

当组件创建时,以及依赖的属性或数据变化时,会运行一个函数,该函数会做两件事:

  • 运行_render生成一棵新的虚拟 dom 树(vnode tree)
  • 运行_update,传入虚拟 dom 树的根节点,对新旧两棵树进行对比,最终完成对真实 dom 的更新

核心代码如下:

javascript
// vue构造函数
+    
Skip to content
On this page

Vuejs diff 算法

diff的时机

当组件创建时,以及依赖的属性或数据变化时,会运行一个函数,该函数会做两件事:

  • 运行_render生成一棵新的虚拟 dom 树(vnode tree)
  • 运行_update,传入虚拟 dom 树的根节点,对新旧两棵树进行对比,最终完成对真实 dom 的更新

核心代码如下:

javascript
// vue构造函数
 function Vue() {
   // ... 其他代码
   var updateComponent = () => {
@@ -169,8 +169,8 @@
   </div>
 </div>
 

总结

当组件创建和更新时,vue 均会执行内部的 update 函数,该函数使用 render 函数生成的虚拟 dom 树,将新旧两树进行对比,找到差异点,最终更新到真实 dom

对比差异的过程叫 diff,vue 在内部通过一个叫 patch 的函数完成该过程

在对比时,vue 采用深度优先、同层比较的方式进行比对。

在判断两个节点是否相同时,vue 是通过虚拟节点的 key 和 tag来进行判断的

具体来说

  • 首先对根节点进行对比,如果相同则将旧节点关联的真实 dom 的引用挂到新节点上,然后根据需要更新属性到真实 dom

  • 然后再对比其子节点数组;如果不相同,则按照新节点的信息递归创建所有真实 dom,同时挂到对应虚拟节点上,然后移除掉旧的 dom。

  • 在对比其子节点数组时,vue 对每个子节点数组使用了两个指针,分别指向头尾,然后不断向中间靠拢来进行对比,这样做的目的是尽量复用真实 dom,尽量少的销毁和创建真实 dom。如果发现相同,则进入和根节点一样的对比流程,如果发现不同,则移动真实 dom 到合适的位置。

这样一直递归的遍历下去,直到整棵树完成对比。

注意:

  • 在给 vue 中的元素设置 key 值时可以使用 Mathrandom 方法么?

random 是生成随机数,有一定概率多个 item 会生成相同的值,不能保证唯一。

如果是根据数据来生成 item,数据具有 id 属性,那么就可以使用 id 来作为 key

如果不是根据数据生成 item,那么最好的方式就是使用时间戳来作为 key。或者使用诸如 uuid 之类的库来生成唯一的 id

  • 为什么不建议用 index 作为 key?

使用 index 作为 key 和没写基本上没区别,因为不管数组的顺序怎么颠倒,index 都是 0, 1, 2...这样排列, 导致 Vue 会复用错误的旧子节点,做很多额外的工作。

对比 Vue3

简单来说,diff 算法有以下过程

  • 同级比较,再比较子节点
  • 先判断一方有子节点一方没有子节点的情况(如果新的 children 没有子节点,将旧的子节点移除)
  • 比较都有子节点的情况(核心 diff)
  • 递归比较子节点

正常 Diff 两个树的时间复杂度是 O(n^3),但实际情况下我们很少会进行跨层级的移动 DOM,所以 VueDiff 进行了优化,从O(n^3) -> O(n),只有当新旧 children 都为多个子节点时才需要用核心的 Diff 算法进行同层级比较。

Vue2 的核心 Diff 算法采用了双端比较的算法,同时从新旧 children 的两端开始进行比较,借助 key 值找到可复用的节点,再进行相关操作。相比 ReactDiff 算法,同样情况下可以减少移动节点次数,减少不必要的性能损耗,更加的优雅。

Vue3.x 借鉴了 ivi 算法和 inferno 算法

在创建 VNode 时就确定其类型,以及在 mount/patch 的过程中采用位运算来判断一个 VNode 的类型,在这个基础之上再配合核心的 Diff 算法,使得性能上较 Vue2.x 有了提升。该算法中还运用了动态规划的思想求解最长递归子序列。

- - + + \ No newline at end of file diff --git a/vue/directive.html b/vue/directive.html index 5eee8de5..2796f263 100644 --- a/vue/directive.html +++ b/vue/directive.html @@ -6,15 +6,15 @@ Vue 指令篇 | Sunny's blog - - + + -
Skip to content
On this page

Vue 指令篇

描述下 Vue 自定义指令

在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。 一般需要对 DOM 元素进行底层操作时使用,尽量只用来操作 DOM 展示,不修改内部的值。当使用自定义指令直接修改 value 值时绑定 v-model 的值也不会同步更新;如必须修改可以在自定义指令中使用 keydown 事件,在 vue 组件中使用 change 事件,回调中修改 vue 数据;

(1)自定义指令基本内容

  • 全局定义:Vue.directive("focus",{})

  • 局部定义:directives:{focus:

  • 钩子函数:指令定义对象提供钩子函数

    • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
    • inSerted:被绑定元素插入父节点时调用(仅保证父节点存在,但不一定已被插入文档中)。
    • update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前调用。指令的值可能发生了改变,也可能没有。但是可以通过比较更新前后的值来忽略不必要的模板更新。
    • ComponentUpdate:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
    • unbind:只调用一次,指令与元素解绑时调用。
  • 钩子函数参数

    • el:绑定元素
    • bing: 指令核心对象,描述指令全部信息属性
    • name
    • value
    • oldValue
    • expression
    • arg
    • modifers
    • vnode   虚拟节点
    • oldVnode:上一个虚拟节点(更新钩子函数中才有用)

(2)使用场景

  • 普通 DOM 元素进行底层操作的时候,可以使用自定义指令
  • 自定义指令是用来操作 DOM 的。尽管 Vue 推崇数据驱动视图的理念,但并非所有情况都适合数据驱动。自定义指令就是一种有效的补充和扩展,不仅可用于定义任何的 DOM 操作,并且是可复用的。

(3)使用案例 初级应用:

  • 鼠标聚焦
  • 下拉菜单
  • 相对时间转换
  • 滚动动画

高级应用:

  • 自定义指令实现图片懒加载
  • 自定义指令集成第三方插件
- - +
Skip to content
On this page

Vue 指令篇

描述下 Vue 自定义指令

在 Vue2.0 中,代码复用和抽象的主要形式是组件。然而,有的情况下,你仍然需要对普通 DOM 元素进行底层操作,这时候就会用到自定义指令。 一般需要对 DOM 元素进行底层操作时使用,尽量只用来操作 DOM 展示,不修改内部的值。当使用自定义指令直接修改 value 值时绑定 v-model 的值也不会同步更新;如必须修改可以在自定义指令中使用 keydown 事件,在 vue 组件中使用 change 事件,回调中修改 vue 数据;

(1)自定义指令基本内容

  • 全局定义:Vue.directive("focus",{})

  • 局部定义:directives:{focus:

  • 钩子函数:指令定义对象提供钩子函数

    • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
    • inSerted:被绑定元素插入父节点时调用(仅保证父节点存在,但不一定已被插入文档中)。
    • update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前调用。指令的值可能发生了改变,也可能没有。但是可以通过比较更新前后的值来忽略不必要的模板更新。
    • ComponentUpdate:指令所在组件的 VNode 及其子 VNode 全部更新后调用。
    • unbind:只调用一次,指令与元素解绑时调用。
  • 钩子函数参数

    • el:绑定元素
    • bing: 指令核心对象,描述指令全部信息属性
    • name
    • value
    • oldValue
    • expression
    • arg
    • modifers
    • vnode   虚拟节点
    • oldVnode:上一个虚拟节点(更新钩子函数中才有用)

(2)使用场景

  • 普通 DOM 元素进行底层操作的时候,可以使用自定义指令
  • 自定义指令是用来操作 DOM 的。尽管 Vue 推崇数据驱动视图的理念,但并非所有情况都适合数据驱动。自定义指令就是一种有效的补充和扩展,不仅可用于定义任何的 DOM 操作,并且是可复用的。

(3)使用案例 初级应用:

  • 鼠标聚焦
  • 下拉菜单
  • 相对时间转换
  • 滚动动画

高级应用:

  • 自定义指令实现图片懒加载
  • 自定义指令集成第三方插件
+ + \ No newline at end of file diff --git a/vue/interviewer.html b/vue/interviewer.html index 032a499b..671e9066 100644 --- a/vue/interviewer.html +++ b/vue/interviewer.html @@ -6,15 +6,15 @@ 今天我是面试官 | Sunny's blog - - + + -
Skip to content
On this page

今天我是面试官

vue 中 key 的功能是什么?

  • 用于虚拟节点树之间,diff 时更好地比较 如果不设置 key,或者用 index 作为 key 会有什么问题?

为什么用 vuex,不用 event bus? 这两者有什么区别?

vue router 的实现原理

  • Location   href, path
  • 知道多页面应用中,如何区分前端路由和后端路由?
  • 路由哈希模式和 history 模式的区别 -
  • 把 nextTick 中的回调放到事件循环的微任务队列中。
  1. vue 发送请求在 created 生命周期钩子中,尽量避免 DOM 渲染挂载后再发送请求,更新数据,更改 DOM;
  2. vue router 组件懒加载;
  3. index.html 文件控制在 14kb 之内,这是基于 TCP 的慢开始规则,第一个响应包的大小就是 14kb,应该包含浏览器开始渲染页面所需的所有内容,或者至少包含页面模板(第一次渲染所需的 CSS 和 HTML)

以头条为例,采用组件化方式设计一个信息流?

  • 组件功能抽象能力
  • 组件设计思路
  • 功能划分:多种文章显示格式,刷新提示,评论/媒体/时间等信息显示,refresh/loadmore 功能,dislike 功能,动态广告
  • 卡片形式抽象:单图、多图、无图、视频、ugc、刷新条等

要求:有清晰思路,良好的信息流组件设计模式,可扩展性强并给出核心功能的实现方式

请简述什么是双向数据绑定和单向数据流,以及它们的区别

双向数据绑定

双向数据绑定意味着 UI 动态地绑定到模型数据,这样当 UI 改变时,模型数据就随之变化,反之亦然。

单向数据流

单向数据流意味着 model 是唯一来源。UI 触发消息的变化,将用户行为标记为 model。只有 model 具有访问更改应用程序状态的权限。其效果是数据总是朝一个方向流动,这使得理解起来更容易。

二者有什么优缺点

单向数据流是确定性的,数据流动方向可以跟踪,流动单一,追查问题的时候可以跟快捷。缺点就是写起来不太方便。要使 UI 发生变更就必须创建各种 action 来维护对应的 state 双向绑定,优点是使用方便,值和 UI 双绑定,但是由于各种数据相互依赖相互绑定,导致数据问题的源头难以被跟踪到,子组件修改父组件,兄弟组件互相修改有有违设计原则

可以介绍一下模板引擎的原理,比如实现类似 html 这种模板将其中变量替换为对应值的方式。

介绍下你所理解的 MVVM 框架,如 Angular、React、Vue 都解决了什么问题?

要求:能够从每个框架的生态系统,甚至结合之前的项目及不同的业务特点,给出框架的优劣

Vue 框架中组件消息通信方式

父子之间层级过多时,当父子组件之间层级不多的时候,父组件可以一层层的向子组件传递数据或者子组件一层层向父组件发送消息,代码上没有太难维护的地方。可是,一旦父子组件之间层级变多后,传递一个数据或者发送一个消息就变得麻烦。这块如果了解开源的 Element 组件库,就会知道其实现方式:构造一个函数自动向上/向下查询父亲节点,以[组件名, 消息名, 参数]三元组进行消息传递,降低长链传播成本;

具体实现参考:https://github.com/ElemeFE/element/blob/dev/src/mixins/emitter.js

如何理解虚拟 DOM?

对虚拟 dom 和 diff 算法中的一些细节理解

https://github.com/livoras/blog/issues/13

要求:写出 diff 算法的核心部分

- - +
Skip to content
On this page

今天我是面试官

vue 中 key 的功能是什么?

  • 用于虚拟节点树之间,diff 时更好地比较 如果不设置 key,或者用 index 作为 key 会有什么问题?

为什么用 vuex,不用 event bus? 这两者有什么区别?

vue router 的实现原理

  • Location   href, path
  • 知道多页面应用中,如何区分前端路由和后端路由?
  • 路由哈希模式和 history 模式的区别 -
  • 把 nextTick 中的回调放到事件循环的微任务队列中。
  1. vue 发送请求在 created 生命周期钩子中,尽量避免 DOM 渲染挂载后再发送请求,更新数据,更改 DOM;
  2. vue router 组件懒加载;
  3. index.html 文件控制在 14kb 之内,这是基于 TCP 的慢开始规则,第一个响应包的大小就是 14kb,应该包含浏览器开始渲染页面所需的所有内容,或者至少包含页面模板(第一次渲染所需的 CSS 和 HTML)

以头条为例,采用组件化方式设计一个信息流?

  • 组件功能抽象能力
  • 组件设计思路
  • 功能划分:多种文章显示格式,刷新提示,评论/媒体/时间等信息显示,refresh/loadmore 功能,dislike 功能,动态广告
  • 卡片形式抽象:单图、多图、无图、视频、ugc、刷新条等

要求:有清晰思路,良好的信息流组件设计模式,可扩展性强并给出核心功能的实现方式

请简述什么是双向数据绑定和单向数据流,以及它们的区别

双向数据绑定

双向数据绑定意味着 UI 动态地绑定到模型数据,这样当 UI 改变时,模型数据就随之变化,反之亦然。

单向数据流

单向数据流意味着 model 是唯一来源。UI 触发消息的变化,将用户行为标记为 model。只有 model 具有访问更改应用程序状态的权限。其效果是数据总是朝一个方向流动,这使得理解起来更容易。

二者有什么优缺点

单向数据流是确定性的,数据流动方向可以跟踪,流动单一,追查问题的时候可以跟快捷。缺点就是写起来不太方便。要使 UI 发生变更就必须创建各种 action 来维护对应的 state 双向绑定,优点是使用方便,值和 UI 双绑定,但是由于各种数据相互依赖相互绑定,导致数据问题的源头难以被跟踪到,子组件修改父组件,兄弟组件互相修改有有违设计原则

可以介绍一下模板引擎的原理,比如实现类似 html 这种模板将其中变量替换为对应值的方式。

介绍下你所理解的 MVVM 框架,如 Angular、React、Vue 都解决了什么问题?

要求:能够从每个框架的生态系统,甚至结合之前的项目及不同的业务特点,给出框架的优劣

Vue 框架中组件消息通信方式

父子之间层级过多时,当父子组件之间层级不多的时候,父组件可以一层层的向子组件传递数据或者子组件一层层向父组件发送消息,代码上没有太难维护的地方。可是,一旦父子组件之间层级变多后,传递一个数据或者发送一个消息就变得麻烦。这块如果了解开源的 Element 组件库,就会知道其实现方式:构造一个函数自动向上/向下查询父亲节点,以[组件名, 消息名, 参数]三元组进行消息传递,降低长链传播成本;

具体实现参考:https://github.com/ElemeFE/element/blob/dev/src/mixins/emitter.js

如何理解虚拟 DOM?

对虚拟 dom 和 diff 算法中的一些细节理解

https://github.com/livoras/blog/issues/13

要求:写出 diff 算法的核心部分

+ + \ No newline at end of file diff --git a/vue/keep-alive-LRU.html b/vue/keep-alive-LRU.html index 00d81e82..c5f2993a 100644 --- a/vue/keep-alive-LRU.html +++ b/vue/keep-alive-LRU.html @@ -6,13 +6,13 @@ keep-alive 与 LRU 算法 | Sunny's blog - - + + -
Skip to content
On this page

keep-alive 与 LRU 算法

keep-alive 组件是 vue 的内置组件,用于缓存内部组件实例。这样做的目的在于,keep-alive 内部的组件切回时,不用重新创建组件实例,而直接使用缓存中的实例,一方面能够避免创建组件带来的开销,另一方面可以保留组件的状态。

keep-alive 具有 include 和 exclude 属性,通过它们可以控制哪些组件进入缓存。另外它还提供了 max 属性,通过它可以设置最大缓存数,当缓存的实例超过该数时,vue 会移除最久没有使用的组件缓存。

受 keep-alive 的影响,其内部所有嵌套的组件都具有两个生命周期钩子函数,分别是 activated 和 deactivated,它们分别在组件激活和失活时触发。第一次 activated 触发是在 mounted 之后

在具体的实现上,keep-alive 在内部维护了一个 key 数组和一个缓存对象

key 数组记录目前缓存的组件 key 值,如果组件没有指定 key 值,则会为其自动生成一个唯一的 key 值

cache 对象以 key 值为键,vnode 为值,用于缓存组件对应的虚拟 DOM

在 keep-alive 的渲染函数中,其基本逻辑是判断当前渲染的 vnode 是否有对应的缓存,如果有,从缓存中读取到对应的组件实例;如果没有则将其缓存。

当缓存数量超过 max 数值时,keep-alive 会移除掉 key 数组的第一个元素。

javascript
// keep-alive 内部的声明周期函数
+    
Skip to content
On this page

keep-alive 与 LRU 算法

keep-alive 组件是 vue 的内置组件,用于缓存内部组件实例。这样做的目的在于,keep-alive 内部的组件切回时,不用重新创建组件实例,而直接使用缓存中的实例,一方面能够避免创建组件带来的开销,另一方面可以保留组件的状态。

keep-alive 具有 include 和 exclude 属性,通过它们可以控制哪些组件进入缓存。另外它还提供了 max 属性,通过它可以设置最大缓存数,当缓存的实例超过该数时,vue 会移除最久没有使用的组件缓存。

受 keep-alive 的影响,其内部所有嵌套的组件都具有两个生命周期钩子函数,分别是 activated 和 deactivated,它们分别在组件激活和失活时触发。第一次 activated 触发是在 mounted 之后

在具体的实现上,keep-alive 在内部维护了一个 key 数组和一个缓存对象

key 数组记录目前缓存的组件 key 值,如果组件没有指定 key 值,则会为其自动生成一个唯一的 key 值

cache 对象以 key 值为键,vnode 为值,用于缓存组件对应的虚拟 DOM

在 keep-alive 的渲染函数中,其基本逻辑是判断当前渲染的 vnode 是否有对应的缓存,如果有,从缓存中读取到对应的组件实例;如果没有则将其缓存。

当缓存数量超过 max 数值时,keep-alive 会移除掉 key 数组的第一个元素。

javascript
// keep-alive 内部的声明周期函数
 created () {
     this.cache = Object.create(null)
     this.keys = []
@@ -23,8 +23,8 @@
     this.keys = []
 }
 

keep-alive 中的生命周期哪些

keep-alive 是 Vue 提供的一个内置组件,用来对组件进行缓存——在组件切换过程中将状态保留在内存中,防止重复渲染 DOM。

如果为一个组件包裹了 keep-alive,那么它会多出两个生命周期:deactivated、activated。同时,beforeDestroy 和 destroyed 就不会再被触发了,因为组件不会被真正销毁。

当组件被换掉时,会被缓存到内存中、触发 deactivated 生命周期;当组件被切回来时,再去缓存里找这个组件、触发 activated 钩子函数。

LRU 缓存算法

TODO

- - + + \ No newline at end of file diff --git a/vue/lifecycle.html b/vue/lifecycle.html index c3986eea..0f5130ea 100644 --- a/vue/lifecycle.html +++ b/vue/lifecycle.html @@ -6,13 +6,13 @@ Vue 生命周期 | Sunny's blog - - + + -
Skip to content
On this page

Vue 生命周期

创建 vue 实例和创建组件的流程基本一致

  1. 首先做一些初始化的操作,主要是设置一些私有属性到实例中
  2. 运行生命周期钩子函数beforeCreate
  3. 进入注入流程:处理属性、computed、methods、data、provide、inject,最后使用代理模式将它们挂载到实例中

data 为例:

javascript
function Vue(options) {
+    
Skip to content
On this page

Vue 生命周期

创建 vue 实例和创建组件的流程基本一致

  1. 首先做一些初始化的操作,主要是设置一些私有属性到实例中
  2. 运行生命周期钩子函数beforeCreate
  3. 进入注入流程:处理属性、computed、methods、data、provide、inject,最后使用代理模式将它们挂载到实例中

data 为例:

javascript
function Vue(options) {
   var data = options.data();
   observe(data); // 变成响应式数据
   var methods = options.methods; //直接赋值
@@ -65,8 +65,8 @@
 
 new Vue(vnode.componentOptions);
 
  1. 运行生命周期钩子函数created
  2. 渲染:生成render函数:如果有配置,直接使用配置的render,如果没有,使用运行时编译器,把模板编译为render
  3. 运行生命周期钩子函数beforeMount
  4. 创建一个Watcher,传入一个函数updateComponent,该函数会运行render,把得到的vnode再传入_update函数执行。 在执行render函数的过程中,会收集所有依赖,将来依赖变化时会重新运行updateComponent函数 在执行_update函数的过程中,触发patch函数,由于目前没有旧树,因此直接为当前的虚拟 dom 树的每一个普通节点生成 elm 属性,即真实 dom。 如果遇到创建一个组件的 vnode,则会进入组件实例化流程,该流程和创建 vue 实例流程基本相同,递归,最终会把创建好的组件实例挂载 vnode 的componentInstance属性中,以便复用。
  5. 运行生命周期钩子函数mounted

Vue 父子组件挂载顺序

重渲染?

  1. 数据变化后,所有依赖该数据的Watcher均会重新运行,这里仅考虑updateComponent函数对应的Watcher
  2. Watcher会被调度器放到nextTick中运行,也就是微队列中,这样是为了避免多个依赖的数据同时改变后被多次执行
  3. 运行生命周期钩子函数beforeUpdate
  4. updateComponent函数重新执行
  • 在执行render函数的过程中,会去掉之前的依赖,重新收集所有依赖,将来依赖变化时会重新运行updateComponent函数
  • 在执行_update函数的过程中,触发patch函数。
  • 新旧两棵树进行对比。
  • 普通html节点的对比会导致真实节点被创建、删除、移动、更新
  • 组件节点的对比会导致组件被创建、删除、移动、更新
  • 当新组件需要创建时,进入实例化流程
  • 当旧组件需要删除时,会调用旧组件的$destroy方法删除组件,该方法会先触发生命周期钩子函数beforeDestroy,然后递归调用子组件的$destroy方法,然后触发生命周期钩子函数- destroyed
  • 当组件属性更新时,相当于组件的updateComponent函数被重新触发执行,进入重渲染流程,和本节相同。
  1. 运行生命周期钩子函数updated

Vue 父子组件重新渲染顺序

总体流程

它可以总共分为 8 个阶段:创建前/后, 载入前/后,更新前/后,销毁前/销毁后。

  • beforeCreate:是 new Vue( ) 之后触发的第一个钩子,在当前阶段 data、methods、computed 以及 watch 上的数据和方法都不能被访问。

  • created:在实例创建完成后发生,当前阶段已经完成了数据观测,也就是可以使用数据,更改数据,在这里更改数据不会触发 updated 函数。可以做一些初始数据的获取,在当前阶段无法与 DOM 进行交互,如果非要想,可以通过 vm.$nextTick 来访问 DOM

  • beforeMount:发生在挂载之前,在这之前 template 模板已导入渲染函数编译。而当前阶段虚拟 DOM 已经创建完成,即将开始渲染。在此时也可以对数据进行更改,不会触发 updated

  • mounted:在挂载完成后发生,在当前阶段,真实的 DOM 挂载完毕,数据完成双向绑定,可以访问到 DOM 节点,使用 $refs 属性对 DOM 进行操作。

注意:

  • created:在模板渲染成 html 前调用,即通常初始化某些属性值,然后再渲染成视图。
  • mounted:在模板渲染成 html 后调用,通常是初始化页面完成后,再对 html 的 dom 节点进行一些需要的操作。
  • beforeUpdate:发生在更新之前,也就是响应式数据发生更新,虚拟 DOM 重新渲染之前被触发,你可以在当前阶段进行更改数据,不会造成重渲染。

  • updated:发生在更新完成之后,当前阶段组件 DOM 已完成更新。要注意的是避免在此期间更改数据,因为这可能会导致无限循环的更新。

  • beforeDestroy:发生在实例销毁之前,在当前阶段实例完全可以被使用,我们可以在这时进行善后收尾工作,比如清除计时器。

  • destroyed:发生在实例销毁之后,这个时候只剩下了 DOM 空壳。组件已被拆解,数据绑定被卸除,监听被移出,子实例也统统被销毁。

  • activated keep-alive 专属,组件被激活时调用

  • deactivated keep-alive 专属,组件被销毁时调用

常见问题

接口请求一般放在哪个生命周期中?

接口请求可以放在钩子函数 created、beforeMount、mounted 中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。

但是推荐在 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有以下优点:

  • 能更快获取到服务端数据,减少页面 loading 时间
  • SSR 不支持 beforeMount 、mounted 钩子函数,所以放在 created 中有助于代码的一致性
  • created 是在模板渲染成 html 前调用,即通常初始化某些属性值,然后再渲染成视图。如果在 mounted 钩子函数中请求数据可能导致页面闪屏问题

Vue 子组件和父组件执行顺序

加载渲染过程:

  1. 父组件 beforeCreate
  2. 父组件 created
  3. 父组件 beforeMount
  4. 子组件 beforeCreate
  5. 子组件 created
  6. 子组件 beforeMount
  7. 子组件 mounted
  8. 父组件 mounted

更新过程:

  1. 父组件 beforeUpdate
  2. 子组件 beforeUpdate
  3. 子组件 updated
  4. 父组件 updated

销毁过程:

  1. 父组件 beforeDestroy
  2. 子组件 beforeDestroy
  3. 子组件 destroyed
  4. 父组件 destoryed
- - + + \ No newline at end of file diff --git a/vue/nextTick.html b/vue/nextTick.html index da7f5281..77a10172 100644 --- a/vue/nextTick.html +++ b/vue/nextTick.html @@ -6,17 +6,17 @@ $nextTick 工作原理 | Sunny's blog - - + + -
Skip to content
On this page

$nextTick 工作原理

DANGER

写作中

Vue 的 nextTick 其本质是对 JavaScript 执行原理 EventLoop 的一种应用。

作用:vue 更新 DOM 是异步更新的,数据变化,DOM 的更新不会马上完成,nextTick 的回调是在下次 DOM 更新循环结束之后执行的延迟回调。

nextTick 的核心是利用了如 Promise 、MutationObserver、setImmediate、setTimeout 的原生 JavaScript 方法来模拟对应的微/宏任务的实现,本质是为了利用 JavaScript 的这些异步回调任务队列来实现 Vue 框架中自己的异步回调队列。

nextTick 不仅是 Vue 内部的异步队列的调用方法,同时也允许开发者在实际项目中使用这个方法来满足实际应用中对 DOM 更新数据时机的后续逻辑处理

nextTick 是典型的将底层 JavaScript 执行原理应用到具体案例中的示例,引入异步更新队列机制的原因 ∶

  • 如果是同步更新,则多次对一个或多个属性赋值,会频繁触发 UI/DOM 的渲染,可以减少一些无用渲染
  • 同时由于 VirtualDOM 的引入,每一次状态发生变化后,状态变化的信号会发送给组件,组件内部使用 VirtualDOM 进行计算得出需要更新的具体的 DOM 节点,然后对 DOM 进行更新操作,每次更新状态后的渲染过程需要更多的计算,而这种无用功也将浪费更多的性能,所以异步渲染变得更加至关重要

Vue 采用了数据驱动视图的思想,但是在一些情况下,仍然需要操作 DOM。有时候,可能遇到这样的情况,DOM1 的数据发生了变化,而 DOM2 需要从 DOM1 中获取数据,那这时就会发现 DOM2 的视图并没有更新,这时就需要用到了 nextTick 了。

由于 Vue 的 DOM 操作是异步的,所以,在上面的情况中,就要将 DOM2 获取数据的操作写在$nextTick 中。

vue
this.$nextTick(() => { // 获取数据的操作...})
+    
Skip to content
On this page

$nextTick 工作原理

DANGER

写作中

Vue 的 nextTick 其本质是对 JavaScript 执行原理 EventLoop 的一种应用。

作用:vue 更新 DOM 是异步更新的,数据变化,DOM 的更新不会马上完成,nextTick 的回调是在下次 DOM 更新循环结束之后执行的延迟回调。

nextTick 的核心是利用了如 Promise 、MutationObserver、setImmediate、setTimeout 的原生 JavaScript 方法来模拟对应的微/宏任务的实现,本质是为了利用 JavaScript 的这些异步回调任务队列来实现 Vue 框架中自己的异步回调队列。

nextTick 不仅是 Vue 内部的异步队列的调用方法,同时也允许开发者在实际项目中使用这个方法来满足实际应用中对 DOM 更新数据时机的后续逻辑处理

nextTick 是典型的将底层 JavaScript 执行原理应用到具体案例中的示例,引入异步更新队列机制的原因 ∶

  • 如果是同步更新,则多次对一个或多个属性赋值,会频繁触发 UI/DOM 的渲染,可以减少一些无用渲染
  • 同时由于 VirtualDOM 的引入,每一次状态发生变化后,状态变化的信号会发送给组件,组件内部使用 VirtualDOM 进行计算得出需要更新的具体的 DOM 节点,然后对 DOM 进行更新操作,每次更新状态后的渲染过程需要更多的计算,而这种无用功也将浪费更多的性能,所以异步渲染变得更加至关重要

Vue 采用了数据驱动视图的思想,但是在一些情况下,仍然需要操作 DOM。有时候,可能遇到这样的情况,DOM1 的数据发生了变化,而 DOM2 需要从 DOM1 中获取数据,那这时就会发现 DOM2 的视图并没有更新,这时就需要用到了 nextTick 了。

由于 Vue 的 DOM 操作是异步的,所以,在上面的情况中,就要将 DOM2 获取数据的操作写在$nextTick 中。

vue
this.$nextTick(() => { // 获取数据的操作...})
 
this.$nextTick(() => { // 获取数据的操作...})
 

所以,在以下情况下,会用到 nextTick:

  • 在数据变化后执行的某个操作,而这个操作需要使用随数据变化而变化的 DOM 结构的时候,这个操作就需要方法在 nextTick()的回调函数中。
  • 在 vue 生命周期中,如果在 created()钩子进行 DOM 操作,也一定要放在 nextTick()的回调函数中。

因为在 created()钩子函数中,页面的 DOM 还未渲染,这时候也没办法操作 DOM,所以,此时如果想要操作 DOM,必须将操作的代码放在 nextTick()的回调函数中。 nextTick:可以做什么不可以做什么? nextTick:里面调用 update 会是什么情况? 如果我循环更新 dom 节点并且执行它,会有什么结果? 循环调用的话 nextTick:里面有容错机制吗?

实现原理:nextTick 主要使用了宏任务和微任务。根据执行环境分别尝试采用

  • Promise:可以将函数延迟到当前函数调用栈最末端
  • MutationObserver :是 H5 新加的一个功能,其功能是监听 DOM 节点的变动,在所有 DOM 变动完成后,执行回调函数
  • setImmediate:用于中断长时间运行的操作,并在浏览器完成其他操作(如事件和显示更新)后立即运行回调函数
  • 如果以上都不行则采用 setTimeout 把函数延迟到 DOM 更新之后再使用

原因是宏任务消耗大于微任务,优先使用微任务,最后使用消耗最大的宏任务。

参考资料

https://juejin.cn/post/6844903557372575752

- - + + \ No newline at end of file diff --git a/vue/reactive.html b/vue/reactive.html index e855bd3d..409a9cdf 100644 --- a/vue/reactive.html +++ b/vue/reactive.html @@ -6,13 +6,13 @@ Vuejs 数据响应原理 | Sunny's blog - - + + -
Skip to content
On this page

Vuejs 数据响应原理

Vue2

https://cn.vuejs.org/v2/guide/reactivity.html

通过 Object.defineProperty 遍历对象的每一个属性,把数据变成 getter,setter。读取属性 getter, 更改属性 setter。形成了响应式数据。组件 render 函数会生成虚拟 DOM 树,影响到界面。怎么让响应式数据和虚拟 dom 连接起来呢?render 运行的时候用到了响应式数据,于是收集了依赖,数据 变化,会通知 watchwatch 会重新运行 render 函数

响应式数据的最终目标,是当对象本身或对象属性发生变化时,将会运行一些函数,最常见的就是 render 函数。 在具体实现上,vue2 用到了几个核心模块

  1. Observer
  2. Dep
  3. Watcher
  4. Scheduler

Observer

Observer 要实现的目标非常简单,就是把一个普通的对象转换为响应式的对象

为了实现这一点,Observer 把对象的每个属性通过 Object.defineProperty 转换为带有 getter 和 setter 的属性,这样一来,当访问或设置属性时,vue 就有机会做一些别的事情。

Observer 是 vue 内部的构造器,我们可以通过 Vue 提供的静态方法 Vue.observable( object )间接的使用该功能。

javascript
var obj = {
+    
Skip to content
On this page

Vuejs 数据响应原理

Vue2

https://cn.vuejs.org/v2/guide/reactivity.html

通过 Object.defineProperty 遍历对象的每一个属性,把数据变成 getter,setter。读取属性 getter, 更改属性 setter。形成了响应式数据。组件 render 函数会生成虚拟 DOM 树,影响到界面。怎么让响应式数据和虚拟 dom 连接起来呢?render 运行的时候用到了响应式数据,于是收集了依赖,数据 变化,会通知 watchwatch 会重新运行 render 函数

响应式数据的最终目标,是当对象本身或对象属性发生变化时,将会运行一些函数,最常见的就是 render 函数。 在具体实现上,vue2 用到了几个核心模块

  1. Observer
  2. Dep
  3. Watcher
  4. Scheduler

Observer

Observer 要实现的目标非常简单,就是把一个普通的对象转换为响应式的对象

为了实现这一点,Observer 把对象的每个属性通过 Object.defineProperty 转换为带有 getter 和 setter 的属性,这样一来,当访问或设置属性时,vue 就有机会做一些别的事情。

Observer 是 vue 内部的构造器,我们可以通过 Vue 提供的静态方法 Vue.observable( object )间接的使用该功能。

javascript
var obj = {
   a: 1,
   c: {
     d: 3,
@@ -717,8 +717,8 @@
   }
 }
 
- - + + \ No newline at end of file diff --git a/vue/slot.html b/vue/slot.html index 9d6a34f3..299dab12 100644 --- a/vue/slot.html +++ b/vue/slot.html @@ -6,15 +6,15 @@ 一篇精通 slot | Sunny's blog - - + + -
Skip to content
On this page

一篇精通 slot

DANGER

写作中

slot 是什么?有什么作用?

slot 又名插槽,是 Vue 的内容分发机制,组件内部的模板引擎使用 slot 元素作为承载分发内容的出口。插槽 slot 是子组件的一个模板标签元素,而这一个标签元素是否显示,以及怎么显示是由父组件决定的。slot 又分三类,默认插槽,具名插槽和作用域插槽。

  • 默认插槽:又名匿名查抄,当 slot 没有指定 name 属性值的时候一个默认显示插槽,一个组件内只有有一个匿名插槽。
  • 具名插槽:带有具体名字的插槽,也就是带有 name 属性的 slot,一个组件可以出现多个具名插槽。
  • 作用域插槽:默认插槽、具名插槽的一个变体,可以是匿名插槽,也可以是具名插槽,该插槽的不同点是在子组件渲染作用域插槽时,可以将子组件内部的数据传递给父组件,让父组件根据子组件的传递过来的数据决定如何渲染该插槽。本质是子组件可以通过插槽的位置绑定一些数据,让父组件插槽位置可以用这个数据。

实现原理

当子组件 vm 实例化时,获取到父组件传入的 slot 标签的内容,存放在 vm.slot 中,默认插槽为 um. slot.default,具名插槽为 vm.slot.xxx,xxx 为插槽名,当组件执行渲染函数时候,遇到 slot 标签,使用 slot 中的内容进行替换,此时可以为插槽传递数据,若存在数据,则可称该插槽为作用域插槽。

- - +
Skip to content
On this page

一篇精通 slot

DANGER

写作中

slot 是什么?有什么作用?

slot 又名插槽,是 Vue 的内容分发机制,组件内部的模板引擎使用 slot 元素作为承载分发内容的出口。插槽 slot 是子组件的一个模板标签元素,而这一个标签元素是否显示,以及怎么显示是由父组件决定的。slot 又分三类,默认插槽,具名插槽和作用域插槽。

  • 默认插槽:又名匿名查抄,当 slot 没有指定 name 属性值的时候一个默认显示插槽,一个组件内只有有一个匿名插槽。
  • 具名插槽:带有具体名字的插槽,也就是带有 name 属性的 slot,一个组件可以出现多个具名插槽。
  • 作用域插槽:默认插槽、具名插槽的一个变体,可以是匿名插槽,也可以是具名插槽,该插槽的不同点是在子组件渲染作用域插槽时,可以将子组件内部的数据传递给父组件,让父组件根据子组件的传递过来的数据决定如何渲染该插槽。本质是子组件可以通过插槽的位置绑定一些数据,让父组件插槽位置可以用这个数据。

实现原理

当子组件 vm 实例化时,获取到父组件传入的 slot 标签的内容,存放在 vm.slot 中,默认插槽为 um. slot.default,具名插槽为 vm.slot.xxx,xxx 为插槽名,当组件执行渲染函数时候,遇到 slot 标签,使用 slot 中的内容进行替换,此时可以为插槽传递数据,若存在数据,则可称该插槽为作用域插槽。

+ + \ No newline at end of file diff --git a/vue/v-model.html b/vue/v-model.html index ed2ab009..039c8a01 100644 --- a/vue/v-model.html +++ b/vue/v-model.html @@ -6,13 +6,13 @@ 探索 v-model 原理 | Sunny's blog - - + + -
Skip to content
On this page

探索 v-model 原理

v-model即可以作用于表单元素,又可作用于自定义组件,无论是哪一种情况,它都是一个语法糖,最终会生成一个属性和一个事件

当其作用于表单元素时vue根据作用的表单元素类型而生成合适的属性和事件。例如,作用于普通文本框的时候,它会生成value属性和input事件,而当其作用于单选框或多选框时,它会生成checked属性和change事件。

v-model也可作用于自定义组件,当其作用于自定义组件时,默认情况下,它会生成一个value属性和input事件。

html
<Comp v-model="data" />
+    
Skip to content
On this page

探索 v-model 原理

v-model即可以作用于表单元素,又可作用于自定义组件,无论是哪一种情况,它都是一个语法糖,最终会生成一个属性和一个事件

当其作用于表单元素时vue根据作用的表单元素类型而生成合适的属性和事件。例如,作用于普通文本框的时候,它会生成value属性和input事件,而当其作用于单选框或多选框时,它会生成checked属性和change事件。

v-model也可作用于自定义组件,当其作用于自定义组件时,默认情况下,它会生成一个value属性和input事件。

html
<Comp v-model="data" />
 <!-- 等效于 -->
 <Comp :value="data" @input="data=$event" />
 
<Comp v-model="data" />
@@ -41,8 +41,8 @@
 <!-- 等效于 -->
 <Comp :number="data" @change="data=$event" />
 

总结

首先要对数据进行劫持监听,所以我们需要设置一个监听器 Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者 Watcher 看是否需要更新。

因为订阅者是有很多个,所以我们需要有一个消息订阅器 Dep 来专门收集这些订阅者,然后在监听器 Observer 和订阅者 Watcher 之间进行统一管理的。

接着,我们还需要有一个指令解析器 Compile,对每个节点元素进行扫描和解析,将相关指令对应初始化成一个订阅者 Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者 Watcher 接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。

因此接下去我们执行以下 3 个步骤,实现数据的双向绑定:

  1. 实现一个监听器 Observer,用来劫持并监听所有属性,如果有变动的,就通知订阅者。

  2. 实现一个订阅者 Watcher,可以收到属性的变化通知并执行相应的函数,从而更新视图。

  3. 实现一个解析器 Compile,可以扫描和解析每个节点的相关指令,并根据初始化模板数据以及初始化相应的订阅器。

- - + + \ No newline at end of file diff --git a/vue/vdom.html b/vue/vdom.html index f31fb7a9..dd512d6f 100644 --- a/vue/vdom.html +++ b/vue/vdom.html @@ -6,13 +6,13 @@ 虚拟 DOM | Sunny's blog - - + + -
Skip to content
On this page

虚拟 DOM

什么是虚拟 dom?

虚拟 dom 本质上就是一个普通的 JS 对象,用于描述视图的界面结构 在 vue 中,每个组件都有一个render函数,每个render函数都会返回一个虚拟 dom 树,这也就意味着每个组件都对应一棵虚拟 DOM 树

查看虚拟 DOM:

javascript
mounted() {
+    
Skip to content
On this page

虚拟 DOM

什么是虚拟 dom?

虚拟 dom 本质上就是一个普通的 JS 对象,用于描述视图的界面结构 在 vue 中,每个组件都有一个render函数,每个render函数都会返回一个虚拟 dom 树,这也就意味着每个组件都对应一棵虚拟 DOM 树

查看虚拟 DOM:

javascript
mounted() {
   console.log(this._vnode);
 },
 
mounted() {
@@ -67,8 +67,8 @@
 

这个过程主要分析出哪些是静态节点,给其打一个标记,为后续更新渲染可以直接跳过静态节点做优化

深度遍历 AST,查看每个子树的节点元素是否为静态节点或者静态节点根。如果为静态节点,他们生成的 DOM 永远不会改变,这对运行时模板更新起到了极大的优化作用。

(3)生成代码

javascript
const code = generate(ast, options);
 
const code = generate(ast, options);
 

generate 将 ast 抽象语法树编译成 render 字符串并将静态部分放到 staticRenderFns 中,最后通过 new Function(render) 生成 render 函数。

- - + + \ No newline at end of file diff --git a/vue/vs.html b/vue/vs.html index e75a41c7..41a161b1 100644 --- a/vue/vs.html +++ b/vue/vs.html @@ -6,15 +6,15 @@ Vue 和 React 的核心区别 | Sunny's blog - - + + -
Skip to content
On this page

Vue 和 React 的核心区别

VueAngular 以及 React 的区别是什么?

关于 Vue 和其他框架的不同,官方专门写了一篇文档,从性能、体积、灵活性等多个方面来进行了说明。 详细可以参阅:https://cn.vuejs.org/v2/guide/comparison.html

Composition API 与 React Hook 很像,区别是什么

从 React Hook 的实现角度看,React Hook 是根据 useState 调用的顺序来确定下一次重渲染时的 state 是来源于哪个 useState,所以出现了以下限制

  • 不能在循环、条件、嵌套函数中调用 Hook
  • 必须确保总是在你的 React 函数的顶层调用 Hook
  • useEffect、useMemo 等函数必须手动确定依赖关系

而 Composition API 是基于 Vue 的响应式系统实现的,与 React Hook 的相比

  • 声明在 setup 函数内,一次组件实例化只调用一次 setup,而 React Hook 每次重渲染都需要调用 Hook,使得 React 的 GC 比 Vue 更有压力,性能也相对于 Vue 来说也较慢
  • Compositon API 的调用不需要顾虑调用顺序,也可以在循环、条件、嵌套函数中使用
  • 响应式系统自动实现了依赖收集,进而组件的部分的性能优化由 Vue 内部自己完成,而 React Hook 需要手动传入依赖,而且必须必须保证依赖的顺序,让 useEffect、useMemo 等函数正确的捕获依赖变量,否则会由于依赖不正确使得组件性能下降。

虽然 Compositon API 看起来比 React Hook 好用,但是其设计思想也是借鉴 React Hook 的。

Vue 和 React 区别

定位相同:处理 UI 层的,vue 提倡渐进式处理,react 没有 vue 推崇模版写法,react 是 all in js jsx, vue 支持 jsx, react 不支持 vue 的 template react hooks, vue3 借鉴了,都有 hoos 风格的 api UI 更新策略:react 传入一个新数据,不能修改旧数据,vue 会根据两次数据渲染 dom 的 diff 更新 UI,数据变化,就会计划更新 UI,都会延迟更新 vue 文化:全部封装好;React 推崇第三方库结合 vue 有 keep-alive, react 没有,重新渲染,需要自己实现 vue css scoped;react 需要第三方:css modules/style-component

相似之处:

  • 都将注意力集中保持在核心库,而将其他功能如路由和全局状态管理交给相关的库;
  • 都有自己的构建工具,能让你得到一个根据最佳实践设置的项目模板;
  • 都使用了 Virtual DOM(虚拟 DOM)提高重绘性能;
  • 都有 props 的概念,允许组件间的数据传递;
  • 都鼓励组件化应用,将应用分拆成一个个功能明确的模块,提高复用性。

不同之处 :

1)数据流 Vue 默认支持数据双向绑定,而 React 一直提倡单向数据流 2)虚拟 DOM Vue2.x 开始引入"Virtual DOM",消除了和 React 在这方面的差异,但是在具体的细节还是有各自的特点。

  • Vue 宣称可以更快地计算出 Virtual DOM 的差异,这是由于它在渲染过程中,会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。
  • 对于 React 而言,每当应用的状态被改变时,全部子组件都会重新渲染。当然,这可以通过 PureComponent/shouldComponentUpdate 这个生命周期方法来进行控制,但 Vue 将此视为默认的优化。

3)组件化 React 与 Vue 最大的不同是模板的编写。

  • Vue 鼓励写近似常规 HTML 的模板。写起来很接近标准 HTML 元素,只是多了一些属性。
  • React 推荐你所有的模板通用 JavaScript 的语法扩展——JSX 书写。

具体来讲:React 中 render 函数是支持闭包特性的,所以 import 的组件在 render 中可以直接调用。但是在 Vue 中,由于模板中使用的数据都必须挂在 this 上进行一次中转,所以 import 一个组件完了之后,还需要在 components 中再声明下。 4)监听数据变化的实现原理不同

  • Vue 通过 getter/setter 以及一些函数的劫持,能精确知道数据变化,不需要特别的优化就能达到很好的性能
  • React 默认是通过比较引用的方式进行的,如果不优化(PureComponent/shouldComponentUpdate)可能导致大量不必要的 vDOM 的重新渲染。这是因为 Vue 使用的是可变数据,而 React 更强调数据的不可变。

5)高阶组件 react 可以通过高阶组件(HOC)来扩展,而 Vue 需要通过 mixins 来扩展。 高阶组件就是高阶函数,而 React 的组件本身就是纯粹的函数,所以高阶函数对 React 来说易如反掌。相反 Vue.js 使用 HTML 模板创建视图组件,这时模板无法有效的编译,因此 Vue 不能采用 HOC 来实现。 6)构建工具 两者都有自己的构建工具:

  • React ==> Create React APP
  • Vue ==> vue-cli

7)跨平台

  • React ==> React Native
  • Vue ==> Weex

Vue 的优点

  • 轻量级框架:只关注视图层,是一个构建数据的视图集合,大小只有几十 kb ;
  • 简单易学:国人开发,中文文档,不存在语言障碍 ,易于理解和学习;
  • 双向数据绑定:保留了 angular 的特点,在数据操作方面更为简单;
  • 组件化:保留了 react 的优点,实现了 html 的封装和重用,在构建单页面应用方面有着独特的优势;
  • 视图,数据,结构分离:使数据的更改更为简单,不需要进行逻辑代码的修改,只需要操作数据就能完成相关操作;
  • 虚拟 DOM:dom 操作是非常耗费性能的,不再使用原生的 dom 操作节点,极大解放 dom 操作,但具体操作的还是 dom 不过是换了另一种方式;
  • 运行速度更快:相比较于 react 而言,同样是操作虚拟 dom,就性能而言, vue 存在很大的优势。
- - +
Skip to content
On this page

Vue 和 React 的核心区别

VueAngular 以及 React 的区别是什么?

关于 Vue 和其他框架的不同,官方专门写了一篇文档,从性能、体积、灵活性等多个方面来进行了说明。 详细可以参阅:https://cn.vuejs.org/v2/guide/comparison.html

Composition API 与 React Hook 很像,区别是什么

从 React Hook 的实现角度看,React Hook 是根据 useState 调用的顺序来确定下一次重渲染时的 state 是来源于哪个 useState,所以出现了以下限制

  • 不能在循环、条件、嵌套函数中调用 Hook
  • 必须确保总是在你的 React 函数的顶层调用 Hook
  • useEffect、useMemo 等函数必须手动确定依赖关系

而 Composition API 是基于 Vue 的响应式系统实现的,与 React Hook 的相比

  • 声明在 setup 函数内,一次组件实例化只调用一次 setup,而 React Hook 每次重渲染都需要调用 Hook,使得 React 的 GC 比 Vue 更有压力,性能也相对于 Vue 来说也较慢
  • Compositon API 的调用不需要顾虑调用顺序,也可以在循环、条件、嵌套函数中使用
  • 响应式系统自动实现了依赖收集,进而组件的部分的性能优化由 Vue 内部自己完成,而 React Hook 需要手动传入依赖,而且必须必须保证依赖的顺序,让 useEffect、useMemo 等函数正确的捕获依赖变量,否则会由于依赖不正确使得组件性能下降。

虽然 Compositon API 看起来比 React Hook 好用,但是其设计思想也是借鉴 React Hook 的。

Vue 和 React 区别

定位相同:处理 UI 层的,vue 提倡渐进式处理,react 没有 vue 推崇模版写法,react 是 all in js jsx, vue 支持 jsx, react 不支持 vue 的 template react hooks, vue3 借鉴了,都有 hoos 风格的 api UI 更新策略:react 传入一个新数据,不能修改旧数据,vue 会根据两次数据渲染 dom 的 diff 更新 UI,数据变化,就会计划更新 UI,都会延迟更新 vue 文化:全部封装好;React 推崇第三方库结合 vue 有 keep-alive, react 没有,重新渲染,需要自己实现 vue css scoped;react 需要第三方:css modules/style-component

相似之处:

  • 都将注意力集中保持在核心库,而将其他功能如路由和全局状态管理交给相关的库;
  • 都有自己的构建工具,能让你得到一个根据最佳实践设置的项目模板;
  • 都使用了 Virtual DOM(虚拟 DOM)提高重绘性能;
  • 都有 props 的概念,允许组件间的数据传递;
  • 都鼓励组件化应用,将应用分拆成一个个功能明确的模块,提高复用性。

不同之处 :

1)数据流 Vue 默认支持数据双向绑定,而 React 一直提倡单向数据流 2)虚拟 DOM Vue2.x 开始引入"Virtual DOM",消除了和 React 在这方面的差异,但是在具体的细节还是有各自的特点。

  • Vue 宣称可以更快地计算出 Virtual DOM 的差异,这是由于它在渲染过程中,会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。
  • 对于 React 而言,每当应用的状态被改变时,全部子组件都会重新渲染。当然,这可以通过 PureComponent/shouldComponentUpdate 这个生命周期方法来进行控制,但 Vue 将此视为默认的优化。

3)组件化 React 与 Vue 最大的不同是模板的编写。

  • Vue 鼓励写近似常规 HTML 的模板。写起来很接近标准 HTML 元素,只是多了一些属性。
  • React 推荐你所有的模板通用 JavaScript 的语法扩展——JSX 书写。

具体来讲:React 中 render 函数是支持闭包特性的,所以 import 的组件在 render 中可以直接调用。但是在 Vue 中,由于模板中使用的数据都必须挂在 this 上进行一次中转,所以 import 一个组件完了之后,还需要在 components 中再声明下。 4)监听数据变化的实现原理不同

  • Vue 通过 getter/setter 以及一些函数的劫持,能精确知道数据变化,不需要特别的优化就能达到很好的性能
  • React 默认是通过比较引用的方式进行的,如果不优化(PureComponent/shouldComponentUpdate)可能导致大量不必要的 vDOM 的重新渲染。这是因为 Vue 使用的是可变数据,而 React 更强调数据的不可变。

5)高阶组件 react 可以通过高阶组件(HOC)来扩展,而 Vue 需要通过 mixins 来扩展。 高阶组件就是高阶函数,而 React 的组件本身就是纯粹的函数,所以高阶函数对 React 来说易如反掌。相反 Vue.js 使用 HTML 模板创建视图组件,这时模板无法有效的编译,因此 Vue 不能采用 HOC 来实现。 6)构建工具 两者都有自己的构建工具:

  • React ==> Create React APP
  • Vue ==> vue-cli

7)跨平台

  • React ==> React Native
  • Vue ==> Weex

Vue 的优点

  • 轻量级框架:只关注视图层,是一个构建数据的视图集合,大小只有几十 kb ;
  • 简单易学:国人开发,中文文档,不存在语言障碍 ,易于理解和学习;
  • 双向数据绑定:保留了 angular 的特点,在数据操作方面更为简单;
  • 组件化:保留了 react 的优点,实现了 html 的封装和重用,在构建单页面应用方面有着独特的优势;
  • 视图,数据,结构分离:使数据的更改更为简单,不需要进行逻辑代码的修改,只需要操作数据就能完成相关操作;
  • 虚拟 DOM:dom 操作是非常耗费性能的,不再使用原生的 dom 操作节点,极大解放 dom 操作,但具体操作的还是 dom 不过是换了另一种方式;
  • 运行速度更快:相比较于 react 而言,同样是操作虚拟 dom,就性能而言, vue 存在很大的优势。
+ + \ No newline at end of file diff --git a/vue/vue-cli.html b/vue/vue-cli.html index 0ac30b72..38979354 100644 --- a/vue/vue-cli.html +++ b/vue/vue-cli.html @@ -6,15 +6,15 @@ vue-cli 到底帮我们做了什么 | Sunny's blog - - + + -
Skip to content
On this page

vue-cli 到底帮我们做了什么

vue-cli 中的工程化

  1. vue.js:vue-cli 工程的核心,主要特点是双向数据绑定和组件系统。
  2. vue-router:vue 官方推荐使用的路由框架。
  3. vuex:专为 Vue.js 应用项目开发的状态管理器,主要用于维护 vue 组件间共用的一些 变量 和 方法。
  4. axios(或者 fetch、ajax):用于发起 GET 、或 POST 等 http 请求,基于 Promise 设计。
  5. vux 等:一个专为 vue 设计的移动端 UI 组件库。
  6. webpack:模块加载和 vue-cli 工程打包器。
  7. eslint:代码规范工具

vue-cli 工程常用的 npm 命令有哪些?

下载 node_modules 资源包的命令:npm install

启动 vue-cli 开发环境的 npm 命令:npm run dev

vue-cli 生成 生产环境部署资源 的 npm 命令:npm run build

用于查看 vue-cli 生产环境部署资源文件大小的 npm 命令:npm run build --report

- - +
Skip to content
On this page

vue-cli 到底帮我们做了什么

vue-cli 中的工程化

  1. vue.js:vue-cli 工程的核心,主要特点是双向数据绑定和组件系统。
  2. vue-router:vue 官方推荐使用的路由框架。
  3. vuex:专为 Vue.js 应用项目开发的状态管理器,主要用于维护 vue 组件间共用的一些 变量 和 方法。
  4. axios(或者 fetch、ajax):用于发起 GET 、或 POST 等 http 请求,基于 Promise 设计。
  5. vux 等:一个专为 vue 设计的移动端 UI 组件库。
  6. webpack:模块加载和 vue-cli 工程打包器。
  7. eslint:代码规范工具

vue-cli 工程常用的 npm 命令有哪些?

下载 node_modules 资源包的命令:npm install

启动 vue-cli 开发环境的 npm 命令:npm run dev

vue-cli 生成 生产环境部署资源 的 npm 命令:npm run build

用于查看 vue-cli 生产环境部署资源文件大小的 npm 命令:npm run build --report

+ + \ No newline at end of file diff --git a/vue/vue-compile.html b/vue/vue-compile.html index 6882c351..82ca2399 100644 --- a/vue/vue-compile.html +++ b/vue/vue-compile.html @@ -6,15 +6,15 @@ Vue 编译器为什么如此强大 | Sunny's blog - - + + -
Skip to content
On this page

Vue 编译器为什么如此强大

说一下 vue 模版编译的原理是什么

简单说,Vue 的编译过程就是将 template 转化为 render 函数的过程。会经历以下阶段:

  • 生成 AST
  • 优化
  • codegen

首先解析模版,生成 AST 语法树(一种用 JavaScript 对象的形式来描述整个模板)。 使用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相关处理。

Vue 的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变化,对应的 DOM 也不会变化。那么优化过程就是深度遍历 AST 树,按照相关条件对树节点进行标记。这些被标记的节点(静态节点)我们就可以跳过对它们的比对,对运行时的模板起到很大的优化作用。

编译的最后一步是将优化后的 AST 树转换为可执行的代码。

说一下 Vue complier 的实现原理是什么样的?

在使用 vue 的时候,我们有两种方式来创建我们的 HTML 页面,第一种情况,也是大多情况下,我们会使用模板 template 的方式,因为这更易读易懂也是官方推荐的方法;第二种情况是使用 render 函数来生成 HTML,它比 template 更接近最终结果。

complier 的主要作用是解析模板,生成渲染模板的 render, 而 render 的作用主要是为了生成 VNode

complier 主要分为 3 大块:

  • parse:接受 template 原始模板,按着模板的节点和数据生成对应的 ast
  • optimize:遍历 ast 的每一个节点,标记静态节点,这样就知道哪部分不会变化,于是在页面需要更新时,通过 diff 减少去对比这部分 DOM,提升性能
  • generate 把前两步生成完善的 ast,组成 render 字符串,然后将 render 字符串通过 new Function 的方式转换成渲染函数

Vue 模版编译原理

vue 中的模板 template 无法被浏览器解析并渲染,因为这不属于浏览器的标准,不是正确的 HTML 语法,所有需要将 template 转化成一个 JavaScript 函数,这样浏览器就可以执行这一个函数并渲染出对应的 HTML 元素,就可以让视图跑起来了,这一个转化的过程,就成为模板编译。模板编译又分三个阶段,解析 parse,优化 optimize,生成 generate,最终生成可执行函数 render。

  • 解析阶段:使用大量的正则表达式对 template 字符串进行解析,将标签、指令、属性等转化为抽象语法树 AST。
  • 优化阶段:遍历 AST,找到其中的一些静态节点并进行标记,方便在页面重渲染的时候进行 diff 比较时,直接跳过这一些静态节点,优化 runtime 的性能。
  • 生成阶段:将最终的 AST 转化为 render 函数字符串。

源码实现

TODO

- - +
Skip to content
On this page

Vue 编译器为什么如此强大

说一下 vue 模版编译的原理是什么

简单说,Vue 的编译过程就是将 template 转化为 render 函数的过程。会经历以下阶段:

  • 生成 AST
  • 优化
  • codegen

首先解析模版,生成 AST 语法树(一种用 JavaScript 对象的形式来描述整个模板)。 使用大量的正则表达式对模板进行解析,遇到标签、文本的时候都会执行对应的钩子进行相关处理。

Vue 的数据是响应式的,但其实模板中并不是所有的数据都是响应式的。有一些数据首次渲染后就不会再变化,对应的 DOM 也不会变化。那么优化过程就是深度遍历 AST 树,按照相关条件对树节点进行标记。这些被标记的节点(静态节点)我们就可以跳过对它们的比对,对运行时的模板起到很大的优化作用。

编译的最后一步是将优化后的 AST 树转换为可执行的代码。

说一下 Vue complier 的实现原理是什么样的?

在使用 vue 的时候,我们有两种方式来创建我们的 HTML 页面,第一种情况,也是大多情况下,我们会使用模板 template 的方式,因为这更易读易懂也是官方推荐的方法;第二种情况是使用 render 函数来生成 HTML,它比 template 更接近最终结果。

complier 的主要作用是解析模板,生成渲染模板的 render, 而 render 的作用主要是为了生成 VNode

complier 主要分为 3 大块:

  • parse:接受 template 原始模板,按着模板的节点和数据生成对应的 ast
  • optimize:遍历 ast 的每一个节点,标记静态节点,这样就知道哪部分不会变化,于是在页面需要更新时,通过 diff 减少去对比这部分 DOM,提升性能
  • generate 把前两步生成完善的 ast,组成 render 字符串,然后将 render 字符串通过 new Function 的方式转换成渲染函数

Vue 模版编译原理

vue 中的模板 template 无法被浏览器解析并渲染,因为这不属于浏览器的标准,不是正确的 HTML 语法,所有需要将 template 转化成一个 JavaScript 函数,这样浏览器就可以执行这一个函数并渲染出对应的 HTML 元素,就可以让视图跑起来了,这一个转化的过程,就成为模板编译。模板编译又分三个阶段,解析 parse,优化 optimize,生成 generate,最终生成可执行函数 render。

  • 解析阶段:使用大量的正则表达式对 template 字符串进行解析,将标签、指令、属性等转化为抽象语法树 AST。
  • 优化阶段:遍历 AST,找到其中的一些静态节点并进行标记,方便在页面重渲染的时候进行 diff 比较时,直接跳过这一些静态节点,优化 runtime 的性能。
  • 生成阶段:将最终的 AST 转化为 render 函数字符串。

源码实现

TODO

+ + \ No newline at end of file diff --git a/vue/vue-interview.html b/vue/vue-interview.html index 18a53a4b..911a1ca7 100644 --- a/vue/vue-interview.html +++ b/vue/vue-interview.html @@ -6,13 +6,13 @@ 那些年,被问烂了的 Vuejs 面试题 | Sunny's blog - - + + -
Skip to content
On this page

那些年,被问烂了的 Vuejs 面试题

说一下 v-ifv-show 的区别

  • 共同点:都是动态显示 DOM 元素
  • 区别点:
    • 手段 v-if 是动态的向 DOM 树内添加或者删除 DOM 元素 v-show 是通过设置 DOM 元素的 display 样式属性控制显隐
    • 编译过程 v-if   切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件 v-show 只是简单的基于 css 切换
    • 编译条件 v-if   是惰性的,如果初始条件为假,则什么也不做。只有在条件第一次变为真时才开始局部编译 v-show 是在任何条件下(首次条件是否为真)都被编译,然后被缓存,而且 DOM 元素保留
    • 性能消耗 v-if   有更高的切换消耗 v-show 有更高的初始渲染消耗
    • 使用场景 v-if   适合运营条件不大可能改变 v-show 适合频繁切换

如何让 CSS 值在当前的组件中起作用

vue 文件中的 style 标签上,有一个特殊的属性:scoped。当一个 style 标签拥有 scoped 属性时,它的 CSS 样式就只能作用于当前的组件,也就是说,该样式只能适用于当前组件元素。通过该属性,可以使得组件之间的样式不互相污染。如果一个项目中的所有 style 标签全部加上了 scoped,相当于实现了样式的模块化。

scoped 的实现原理

vue 中的 scoped 属性的效果主要通过 PostCSS 转译实现的。PostCSS 给一个组件中的所有 DOM 添加了一个独一无二的动态属性,然后,给 CSS 选择器额外添加一个对应的属性选择器来选择该组件中 DOM,这种做法使得样式只作用于含有该属性的 DOM,即组件内部 DOM

例如:

转译前

javascript
<template>
+    
Skip to content
On this page

那些年,被问烂了的 Vuejs 面试题

说一下 v-ifv-show 的区别

  • 共同点:都是动态显示 DOM 元素
  • 区别点:
    • 手段 v-if 是动态的向 DOM 树内添加或者删除 DOM 元素 v-show 是通过设置 DOM 元素的 display 样式属性控制显隐
    • 编译过程 v-if   切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的事件监听和子组件 v-show 只是简单的基于 css 切换
    • 编译条件 v-if   是惰性的,如果初始条件为假,则什么也不做。只有在条件第一次变为真时才开始局部编译 v-show 是在任何条件下(首次条件是否为真)都被编译,然后被缓存,而且 DOM 元素保留
    • 性能消耗 v-if   有更高的切换消耗 v-show 有更高的初始渲染消耗
    • 使用场景 v-if   适合运营条件不大可能改变 v-show 适合频繁切换

如何让 CSS 值在当前的组件中起作用

vue 文件中的 style 标签上,有一个特殊的属性:scoped。当一个 style 标签拥有 scoped 属性时,它的 CSS 样式就只能作用于当前的组件,也就是说,该样式只能适用于当前组件元素。通过该属性,可以使得组件之间的样式不互相污染。如果一个项目中的所有 style 标签全部加上了 scoped,相当于实现了样式的模块化。

scoped 的实现原理

vue 中的 scoped 属性的效果主要通过 PostCSS 转译实现的。PostCSS 给一个组件中的所有 DOM 添加了一个独一无二的动态属性,然后,给 CSS 选择器额外添加一个对应的属性选择器来选择该组件中 DOM,这种做法使得样式只作用于含有该属性的 DOM,即组件内部 DOM

例如:

转译前

javascript
<template>
   <div class="example">hi</div>
 </template>
 
@@ -319,8 +319,8 @@
 
Vue.mixin({
   beforeCreate() {        // ...逻辑        // 这种方式会影响到每个组件的 beforeCreate 钩子函数    }})
 

虽然文档不建议在应用中直接使用 mixin,但是如果不滥用的话也是很有帮助的,比如可以全局混入封装好的 ajax 或者一些工具函数等等。

mixins 应该是最常使用的扩展组件的方式了。如果多个组件中有相同的业务逻辑,就可以将这些逻辑剥离出来,通过 mixins 混入代码,比如上拉下拉加载数据这种逻辑等等。 另外需要注意的是 mixins 混入的钩子函数会先于组件内的钩子函数执行,并且在遇到同名选项的时候也会有选择性的进行合并。

内置组件 Transition

官网详细文档:https://cn.vuejs.org/v2/guide/transitions.html

时机

Transition组件会监控slot唯一根元素的出现和消失,并会在其出现和消失时应用过渡效果 Transition不生成任何元素,只是为了生成过渡效果 具体的监听内容是:

  • 它会对新旧两个虚拟节点进行对比,如果旧节点被销毁,则应用消失效果,如果新节点是新增的,则应用进入效果
  • 如果不是上述情况,则它会对比新旧节点,观察其v-show是否变化,true->false应用消失效果,false->true应用进入效果

流程

类名规则:

  1. 如果transition上没有定义name,则类名为v-xxxx
  2. 如果transition上定义了name,则类名为${name}-xxxx
  3. 如果指定了类名,直接使用指定的类名

指定类名见:自定义过渡类名

1. 进入效果

2. 消失效果

过渡组

Transision可以监控其内部的单个 dom 元素的出现和消失,并为其附加样式

如果要监控一个 dom 列表,就需要使用TransitionGroup组件

它会对列表的新增元素应用进入效果,删除元素应用消失效果,对被移动的元素应用v-move样式

被移动的元素之所以能够实现过渡效果,是因为TransisionGroup内部使用了 Flip 过渡方案

- - + + \ No newline at end of file diff --git a/vue/vue-router.html b/vue/vue-router.html index c7c27af8..d9a1c4ed 100644 --- a/vue/vue-router.html +++ b/vue/vue-router.html @@ -6,13 +6,13 @@ VueRouter 路由从使用到源码 | Sunny's blog - - + + -
Skip to content
On this page

VueRouter 路由从使用到源码

源码实现

先看我写的 mini-router,后续会补充文章

Vue 的路由实现原理

解释 hash 模式和 history 模式的实现原理

# 后面 hash 值的变化,不会导致浏览器向服务器发出请求,浏览器不发出请求,就不会刷新页面;通过监听 hashchange 事件可以知道 hash 发生了哪些变化,然后根据 hash 变化来实现更新页面部分内容的操作。

history 模式的实现,主要是 HTML5 标准发布的两个 APIpushStatereplaceState,这两个 API 可以在改变 URL,但是不会发送请求。这样就可以监听 url 变化来实现更新页面部分内容的操作。

两种模式的区别:

  • 首先是在 URL 的展示上,hash 模式有“#”,history 模式没有
  • 刷新页面时,hash 模式可以正常加载到 hash 值对应的页面,而 history 没有处理的话,会返回 404,一般需要后端将所有页面都配置重定向到首页路由
  • 在兼容性上,hash 可以支持低版本浏览器和 IE

router 和route 的区别

$route 对象表示当前的路由信息,包含了当前 URL 解析得到的信息。包含当前的路径,参数,query 对象等。

  • $route.path:字符串,对应当前路由的路径,总是解析为绝对路径,如 "/foo/bar"。
  • $route.params: 一个 key/value 对象,包含了 动态片段 和 全匹配片段,如果没有路由参数,就是一个空对象。
  • route.query.user == 1,如果没有查询参数,则是个空对象。
  • $route.hash:当前路由的 hash 值 (不带 #) ,如果没有 hash 值,则为空字符串。
  • $route.fullPath:完成解析后的 URL,包含查询参数和 hash 的完整路径。
  • $route.matched:数组,包含当前匹配的路径中所包含的所有片段所对应的配置参数对象。
  • $route.name:当前路径名字
  • $route.meta:路由元信息

$route 对象出现在多个地方:

  • 组件内的 this.$routeroute watcher 回调(监测变化处理)
  • router.match(location) 的返回值
  • scrollBehavior 方法的参数
  • 导航钩子的参数,例如 router.beforeEach 导航守卫的钩子函数中,tofrom 都是这个路由信息对象。

$router 对象是全局路由的实例,是 router 构造方法的实例。

$router 对象常用的方法有:

  • push:向 history 栈添加一个新的记录
  • go:页面路由跳转前进或者后退
  • replace:替换当前的页面,不会向 history 栈添加一个新的记录

vueRouter 有哪几种导航守卫?

  • 全局前置/钩子:beforeEach、beforeR-esolve、afterEach
  • 路由独享的守卫:beforeEnter
  • 组件内的守卫:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave

解释一下 vueRouter 的完整的导航解析流程是什么

一次完整的导航解析流程如下:

  1. 导航被触发。
  2. 在失活的组件里调用离开守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。
  5. 在路由配置里调用 beforeEnter
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter
  8. 调用全局的 beforeResolve 守卫(2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。

如何监听 pushstatereplacestate 的变化呢?

History.replaceStatepushState 不会触发 popstate 事件,所以我们可以通过在方法中创建一个新的全局事件来实现  pushstatereplacestate 变化的监听。

具体做法为:

这样就创建了 2 个全新的事件,事件名为 pushStatereplaceState,我们就可以在全局监听:

这样就可以监听到 pushStatereplaceState 行为。

javascript
var _wr = function (type) {
+    
Skip to content
On this page

VueRouter 路由从使用到源码

源码实现

先看我写的 mini-router,后续会补充文章

Vue 的路由实现原理

解释 hash 模式和 history 模式的实现原理

# 后面 hash 值的变化,不会导致浏览器向服务器发出请求,浏览器不发出请求,就不会刷新页面;通过监听 hashchange 事件可以知道 hash 发生了哪些变化,然后根据 hash 变化来实现更新页面部分内容的操作。

history 模式的实现,主要是 HTML5 标准发布的两个 APIpushStatereplaceState,这两个 API 可以在改变 URL,但是不会发送请求。这样就可以监听 url 变化来实现更新页面部分内容的操作。

两种模式的区别:

  • 首先是在 URL 的展示上,hash 模式有“#”,history 模式没有
  • 刷新页面时,hash 模式可以正常加载到 hash 值对应的页面,而 history 没有处理的话,会返回 404,一般需要后端将所有页面都配置重定向到首页路由
  • 在兼容性上,hash 可以支持低版本浏览器和 IE

router 和route 的区别

$route 对象表示当前的路由信息,包含了当前 URL 解析得到的信息。包含当前的路径,参数,query 对象等。

  • $route.path:字符串,对应当前路由的路径,总是解析为绝对路径,如 "/foo/bar"。
  • $route.params: 一个 key/value 对象,包含了 动态片段 和 全匹配片段,如果没有路由参数,就是一个空对象。
  • route.query.user == 1,如果没有查询参数,则是个空对象。
  • $route.hash:当前路由的 hash 值 (不带 #) ,如果没有 hash 值,则为空字符串。
  • $route.fullPath:完成解析后的 URL,包含查询参数和 hash 的完整路径。
  • $route.matched:数组,包含当前匹配的路径中所包含的所有片段所对应的配置参数对象。
  • $route.name:当前路径名字
  • $route.meta:路由元信息

$route 对象出现在多个地方:

  • 组件内的 this.$routeroute watcher 回调(监测变化处理)
  • router.match(location) 的返回值
  • scrollBehavior 方法的参数
  • 导航钩子的参数,例如 router.beforeEach 导航守卫的钩子函数中,tofrom 都是这个路由信息对象。

$router 对象是全局路由的实例,是 router 构造方法的实例。

$router 对象常用的方法有:

  • push:向 history 栈添加一个新的记录
  • go:页面路由跳转前进或者后退
  • replace:替换当前的页面,不会向 history 栈添加一个新的记录

vueRouter 有哪几种导航守卫?

  • 全局前置/钩子:beforeEach、beforeR-esolve、afterEach
  • 路由独享的守卫:beforeEnter
  • 组件内的守卫:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave

解释一下 vueRouter 的完整的导航解析流程是什么

一次完整的导航解析流程如下:

  1. 导航被触发。
  2. 在失活的组件里调用离开守卫。
  3. 调用全局的 beforeEach 守卫。
  4. 在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。
  5. 在路由配置里调用 beforeEnter
  6. 解析异步路由组件。
  7. 在被激活的组件里调用 beforeRouteEnter
  8. 调用全局的 beforeResolve 守卫(2.5+)。
  9. 导航被确认。
  10. 调用全局的 afterEach 钩子。
  11. 触发 DOM 更新。
  12. 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。

如何监听 pushstatereplacestate 的变化呢?

History.replaceStatepushState 不会触发 popstate 事件,所以我们可以通过在方法中创建一个新的全局事件来实现  pushstatereplacestate 变化的监听。

具体做法为:

这样就创建了 2 个全新的事件,事件名为 pushStatereplaceState,我们就可以在全局监听:

这样就可以监听到 pushStatereplaceState 行为。

javascript
var _wr = function (type) {
   var orig = history[type];
   return function () {
     var rv = orig.apply(this, arguments);
@@ -305,8 +305,8 @@
 }
 
 

二、Vue 路由钩子在生命周期函数的体现

  1. 完整的路由导航解析流程(不包括其他生命周期)
  • 触发进入其他路由。
  • 调用要离开路由的组件守卫 beforeRouteLeave
  • 调用局前置守卫 ∶ beforeEach
  • 在重用的组件里调用 beforeRouteUpdate
  • 调用路由独享守卫 beforeEnter。
  • 解析异步路由组件。
  • 在将要进入的路由组件中调用 beforeRouteEnter
  • 调用全局解析守卫 beforeResolve
  • 导航被确认。
  • 调用全局后置钩子的 afterEach 钩子。
  • 触发 DOM 更新(mounted)。
  • 执行 beforeRouteEnter 守卫中传给 next 的回调函数
  1. 触发钩子的完整顺序

路由导航、keep-alive、和组件生命周期钩子结合起来的,触发顺序,假设是从 a 组件离开,第一次进入 b 组件 ∶

  • beforeRouteLeave:路由组件的组件离开路由前钩子,可取消路由离开。
  • beforeEach:路由全局前置守卫,可用于登录验证、全局路由 loading 等。
  • beforeEnter:路由独享守卫
  • beforeRouteEnter:路由组件的组件进入路由前钩子。
  • beforeResolve:路由全局解析守卫
  • afterEach:路由全局后置钩子
  • beforeCreate:组件生命周期,不能访问 tAis。
  • created;组件生命周期,可以访问 tAis,不能访问 dom。
  • beforeMount:组件生命周期
  • deactivated:离开缓存组件 a,或者触发 a 的 beforeDestroy 和 destroyed 组件销毁钩子。
  • mounted:访问/操作 dom。
  • activated:进入缓存组件,进入 a 的嵌套子组件(如果有的话)。
  • 执行 beforeRouteEnter 回调函数 next。
  1. 导航行为被触发到导航完成的整个过程
  • 导航行为被触发,此时导航未被确认。
  • 在失活的组件里调用离开守卫 beforeRouteLeave。
  • 调用全局的 beforeEach 守卫。
  • 在重用的组件里调用 beforeRouteUpdate 守卫(2.2+)。
  • 在路由配置里调用 beforeEnteY。
  • 解析异步路由组件(如果有)。
  • 在被激活的组件里调用 beforeRouteEnter。
  • 调用全局的 beforeResolve 守卫(2.5+),标示解析阶段完成。
  • 导航被确认。
  • 调用全局的 afterEach 钩子。
  • 非重用组件,开始组件实例的生命周期:beforeCreate&created、beforeMount&mounted
  • 触发 DOM 更新。
  • 用创建好的实例调用 beforeRouteEnter 守卫中传给 next 的回调函数。
  • 导航完成

Vue-router 跳转和 location.href 有什么区别

  • 使用 location.href= /url来跳转,简单方便,但是刷新了页面;
  • 使用 history.pushState( /url ) ,无刷新页面,静态跳转;
  • 引进 router ,然后使用 router.push( /url ) 来跳转,使用了 diff 算法,实现了按需加载,减少了 dom 的消耗。其实使用 router 跳转和使用 history.pushState() 没什么差别的,因为 vue-router 就是用了 history.pushState() ,尤其是在 history 模式下。

params 和 query 的区别

用法:query 要用 path 来引入,params 要用 name 来引入,接收参数都是类似的,分别是 this.$route.query.namethis.$route.params.name

url 地址显示:query 更加类似于 ajax 中 get 传参,params 则类似于 post,说的再简单一点,前者在浏览器地址栏中显示参数,后者则不显示

注意:query 刷新不会丢失 query 里面的数据 params 刷新会丢失 params 里面的数据。

Vue-router 导航守卫有哪些

  • 全局前置/钩子:beforeEach、beforeResolve、afterEach
  • 路由独享的守卫:beforeEnter
  • 组件内的守卫:beforeRouteEnter、beforeRouteUpdate、beforeRouteLeave
- - + + \ No newline at end of file diff --git a/vue/vue3-onepage.html b/vue/vue3-onepage.html index 631f503d..2b15eedc 100644 --- a/vue/vue3-onepage.html +++ b/vue/vue3-onepage.html @@ -6,13 +6,13 @@ Vue3 新特性 TODO: 性能提升 | Sunny's blog - - + + -
Skip to content
On this page

Vue3 新特性 TODO: 性能提升

createApp

javascript
// vue2
+    
Skip to content
On this page

Vue3 新特性 TODO: 性能提升

createApp

javascript
// vue2
 const app = new Vue(options);
 // 使用插件:Vue.use()
 app.$mount("#app");
@@ -783,8 +783,8 @@
 };
 </script>
 

说一下 vue3.0 是如何变得更快的?

优化 Diff 算法

相比 Vue 2Vue 3 采用了更加优化的渲染策略。去掉不必要的虚拟 DOM 树遍历和属性比较,因为这在更新期间往往会产生最大的性能开销。

这里有三个主要的优化:

  • 首先,在 DOM 树级别。

在没有动态改变节点结构的模板指令(例如 v-ifv-for)的情况下,节点结构保持完全静态。

当更新节点时,不再需要递归遍历 DOM 树。所有的动态绑定部分将在一个平面数组中跟踪。这种优化通过将需要执行的树遍历量减少一个数量级来规避虚拟 DOM 的大部分开销。

  • 其次,编译器积极地检测模板中的静态节点、子树甚至数据对象,并在生成的代码中将它们提升到渲染函数之外。这样可以避免在每次渲染时重新创建这些对象,从而大大提高内存使用率并减少垃圾回收的频率。
  • 第三,在元素级别。

编译器还根据需要执行的更新类型,为每个具有动态绑定的元素生成一个优化标志。

例如,具有动态类绑定和许多静态属性的元素将收到一个标志,提示只需要进行类检查。运行时将获取这些提示并采用专用的快速路径。

综合起来,这些技术大大改进了渲染更新基准,Vue 3.0 有时占用的 CPU 时间不到 Vue 2 的十分之一。

体积变小

重写后的 Vue 支持了 tree-shaking,像修剪树叶一样把不需要的东西给修剪掉,使 Vue 3.0 的体积更小。

需要的模块才会打入到包里,优化后的 Vue 3.0 的打包体积只有原来的一半(13kb)。哪怕把所有的功能都引入进来也只有 23kb,依然比 Vue 2.x 更小。像 keep-alive、transition 甚至 v-for 等功能都可以按需引入。

并且 Vue 3.0 优化了打包方法,使得打包后的 bundle 的体积也更小。

官方所给出的一份惊艳的数据:打包大小减少 41%,初次渲染快 55%,更新快 133%,内存使用减少 54%

说一说相比 vue3.x 对比 vue2.x 变化

  1. 源码组织方式变化:使用 TS 重写
  2. 支持 Composition API:基于函数的 API,更加灵活组织组件逻辑(vue2 用的是 options api)
  3. 响应式系统提升:Vue3 中响应式数据原理改成 proxy,可监听动态新增删除属性,以及数组变化
  4. 编译优化:vue2 通过标记静态根节点优化 diff,Vue3 标记和提升所有静态根节点,diff 的时候只需要对比动态节点内容
  5. 打包体积优化:移除了一些不常用的 api(inline-template、filter)
  6. 生命周期的变化:使用 setup 代替了之前的 beforeCreate 和 created
  7. Vue3 的 template 模板支持多个根标签
  8. Vuex 状态管理:创建实例的方式改变,Vue2 为 new Store , Vue3 为 createStore
  9. Route 获取页面实例与路由信息:vue2 通过 this 获取 router 实例,vue3 通过使用 getCurrentInstance/ userRoute 和 userRouter 方法获取当前组件实例
  10. Props 的使用变化:vue2 通过 this 获取 props 里面的内容,vue3 直接通过 props
  11. 父子组件传值:vue3 在向父组件传回数据时,如使用的自定义名称,如 backData,则需要在 emits 中定义一下
- - + + \ No newline at end of file diff --git a/vue/vuex.html b/vue/vuex.html index 31d2214b..bd1ca33a 100644 --- a/vue/vuex.html +++ b/vue/vuex.html @@ -6,13 +6,13 @@ Vuex 思想和源码 | Sunny's blog - - + + -
Skip to content
On this page

Vuex 思想和源码

源码实现

先看我写的 mini-vuex,后续会补充文章

可以结合 mini-pinia 一起食用

Vuex 的原理

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。

  • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。

  • 改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样可以方便地跟踪每一个状态的变化。

  • Vuex 为 Vue Components 建立起了一个完整的生态圈,包括开发中的 API 调用一环。

    (1)核心流程中的主要功能:

  • Vue Components 是 vue 组件,组件会触发(dispatch)一些事件或动作,也就是图中的 Actions;

  • 在组件中发出的动作,肯定是想获取或者改变数据的,但是在 vuex 中,数据是集中管理的,不能直接去更改数据,所以会把这个动作提交(Commit)到 Mutations 中;

  • 然后 Mutations 就去改变(Mutate)State 中的数据;

  • 当 State 中的数据被改变之后,就会重新渲染(Render)到 Vue Components 中去,组件展示更新后的数据,完成一个流程。

(2)各模块在核心流程中的主要功能:

  • Vue Components∶ Vue 组件。HTML 页面上,负责接收用户操作等交互行为,执行 dispatch 方法触发对应 action 进行回应。
  • dispatch∶ 操作行为触发方法,是唯一能执行 action 的方法。
  • actions∶ 操作行为处理模块。负责处理 Vue Components 接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台 API 请求的操作就在这个模块中进行,包括触发其他 action 以及提交 mutation 的操作。该模块提供了 Promise 的封装,以支持 action 的链式触发。
  • commit∶ 状态改变提交操作方法。对 mutation 进行提交,是唯一能执行 mutation 的方法。
  • mutations∶ 状态改变操作方法。是 Vuex 修改 state 的唯一推荐方法,其他修改方式在严格模式下将会报错。该方法只能进行同步操作,且方法名只能全局唯一。操作之中会有一些 hook 暴露出来,以进行 state 的监控等。
  • state∶ 页面状态管理容器对象。集中存储 Vuecomponents 中 data 对象的零散数据,全局唯一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用 Vue 的细粒度数据响应机制来进行高效的状态更新。
  • getters∶ state 对象读取方法。图中没有单独列出该模块,应该被包含在了 render 中,Vue Components 通过该方法读取全局 state 对象。

Vuex 中 action 和 mutation 的区别

mutation 中的操作是一系列的同步函数,用于修改 state 中的变量的的状态。当使用 vuex 时需要通过 commit 来提交需要操作的内容。mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是实际进行状态更改的地方,并且它会接受 state 作为第一个参数:

javascript
const store = new Vuex.Store({
+    
Skip to content
On this page

Vuex 思想和源码

源码实现

先看我写的 mini-vuex,后续会补充文章

可以结合 mini-pinia 一起食用

Vuex 的原理

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。

  • Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。

  • 改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样可以方便地跟踪每一个状态的变化。

  • Vuex 为 Vue Components 建立起了一个完整的生态圈,包括开发中的 API 调用一环。

    (1)核心流程中的主要功能:

  • Vue Components 是 vue 组件,组件会触发(dispatch)一些事件或动作,也就是图中的 Actions;

  • 在组件中发出的动作,肯定是想获取或者改变数据的,但是在 vuex 中,数据是集中管理的,不能直接去更改数据,所以会把这个动作提交(Commit)到 Mutations 中;

  • 然后 Mutations 就去改变(Mutate)State 中的数据;

  • 当 State 中的数据被改变之后,就会重新渲染(Render)到 Vue Components 中去,组件展示更新后的数据,完成一个流程。

(2)各模块在核心流程中的主要功能:

  • Vue Components∶ Vue 组件。HTML 页面上,负责接收用户操作等交互行为,执行 dispatch 方法触发对应 action 进行回应。
  • dispatch∶ 操作行为触发方法,是唯一能执行 action 的方法。
  • actions∶ 操作行为处理模块。负责处理 Vue Components 接收到的所有交互行为。包含同步/异步操作,支持多个同名方法,按照注册的顺序依次触发。向后台 API 请求的操作就在这个模块中进行,包括触发其他 action 以及提交 mutation 的操作。该模块提供了 Promise 的封装,以支持 action 的链式触发。
  • commit∶ 状态改变提交操作方法。对 mutation 进行提交,是唯一能执行 mutation 的方法。
  • mutations∶ 状态改变操作方法。是 Vuex 修改 state 的唯一推荐方法,其他修改方式在严格模式下将会报错。该方法只能进行同步操作,且方法名只能全局唯一。操作之中会有一些 hook 暴露出来,以进行 state 的监控等。
  • state∶ 页面状态管理容器对象。集中存储 Vuecomponents 中 data 对象的零散数据,全局唯一,以进行统一的状态管理。页面显示所需的数据从该对象中进行读取,利用 Vue 的细粒度数据响应机制来进行高效的状态更新。
  • getters∶ state 对象读取方法。图中没有单独列出该模块,应该被包含在了 render 中,Vue Components 通过该方法读取全局 state 对象。

Vuex 中 action 和 mutation 的区别

mutation 中的操作是一系列的同步函数,用于修改 state 中的变量的的状态。当使用 vuex 时需要通过 commit 来提交需要操作的内容。mutation 非常类似于事件:每个 mutation 都有一个字符串的 事件类型 (type) 和 一个 回调函数 (handler)。这个回调函数就是实际进行状态更改的地方,并且它会接受 state 作为第一个参数:

javascript
const store = new Vuex.Store({
   state: {
     count: 1,
   },
@@ -73,8 +73,8 @@
 })
 
 

如何在组件中批量使用 Vuex

使用 mapGetters 辅助函数, 利用对象展开运算符将 getter 混入 computed 对象中 使用 mapMutations 辅助函数,在组件中这么使用

- - + + \ No newline at end of file