├── .prettierrc ├── .gitattributes ├── .vscode └── settings.json ├── about.md ├── datas ├── fe.前端工程化.pdf ├── fe.前端开发路线图.png ├── http │ ├── HTTP协议.png │ ├── HTTP协议.xmind │ ├── 从输入URL发生了什么.png │ └── 从输入URL发生了什么.xmind ├── linux.目录结构概况.png ├── security │ ├── auth │ │ ├── SSO.png │ │ ├── TokenJwt.png │ │ ├── OAuthGitHub.png │ │ ├── CookieSession.png │ │ ├── SSO.drawio │ │ ├── CookieSession.drawio │ │ ├── OAuthGitHub.drawio │ │ └── TokenJwt.drawio │ ├── 安全漏洞与防范.png │ └── 安全漏洞与防范.xmind └── fe.VueNext和React知识地图.jpeg ├── .vuepress ├── public │ ├── wx.png │ ├── head.png │ ├── logo.png │ ├── favicon.ico │ ├── examples │ │ ├── nest-lifecycle-events │ │ │ ├── index.html │ │ │ ├── code.js │ │ │ └── index.js │ │ └── nest-request-lifecycle │ │ │ ├── index.html │ │ │ └── code.js │ ├── bg.svg │ └── lib │ │ └── knowcess │ │ └── knowcess.css ├── config │ ├── data │ │ └── open-source-code │ │ │ ├── index.ts │ │ │ ├── author.ts │ │ │ └── contributor.ts │ └── styles │ │ └── index.scss ├── client.ts ├── icons │ ├── SimpleIconsJuejin.vue │ ├── index.ts │ ├── SimpleIconsGitee.vue │ ├── SimpleIconsGithub.vue │ ├── SimpleIconsLeetcode.vue │ ├── SimpleIconsZhihu.vue │ └── SimpleIconsWechat.vue ├── config.ts └── components │ └── OpenSourceCode.vue ├── .gitignore ├── blogs ├── vue │ ├── image │ │ ├── vue2-analysis │ │ │ ├── 1.png │ │ │ └── reactive.png │ │ └── vue2-vuex-analysis │ │ │ └── vuex.png │ ├── lib │ │ ├── image │ │ │ ├── vlib-starter-2 │ │ │ │ ├── 1.png │ │ │ │ ├── 2.png │ │ │ │ ├── 3.png │ │ │ │ ├── 4.png │ │ │ │ ├── 5.png │ │ │ │ ├── 6.png │ │ │ │ └── 7.png │ │ │ ├── vlib-starter-3 │ │ │ │ ├── 1.png │ │ │ │ ├── 2.png │ │ │ │ ├── 3.png │ │ │ │ ├── 4.png │ │ │ │ └── 5.png │ │ │ ├── vlib-starter-4 │ │ │ │ └── 1.png │ │ │ ├── vlib-starter-5 │ │ │ │ ├── 1.png │ │ │ │ ├── 2.png │ │ │ │ ├── 3.png │ │ │ │ └── 4.png │ │ │ ├── vlib-starter-6 │ │ │ │ ├── 1.png │ │ │ │ ├── 2.png │ │ │ │ ├── 3.png │ │ │ │ ├── 4.png │ │ │ │ └── 5.png │ │ │ ├── vlib-starter-7 │ │ │ │ ├── 1.png │ │ │ │ ├── 2.png │ │ │ │ ├── 3.png │ │ │ │ ├── 4.png │ │ │ │ ├── 5.png │ │ │ │ ├── 6.png │ │ │ │ ├── 7.png │ │ │ │ └── 8.png │ │ │ ├── vlib-starter-8 │ │ │ │ ├── 1.png │ │ │ │ ├── 2.png │ │ │ │ ├── 3.png │ │ │ │ ├── 4.png │ │ │ │ ├── 5.png │ │ │ │ └── 6.png │ │ │ ├── vlib-starter-9 │ │ │ │ ├── 1.png │ │ │ │ ├── 2.png │ │ │ │ ├── 3.png │ │ │ │ ├── 4.png │ │ │ │ ├── 5.png │ │ │ │ ├── 6.png │ │ │ │ └── 7.png │ │ │ └── vlib-starter-10 │ │ │ │ ├── 1.png │ │ │ │ ├── 10.png │ │ │ │ ├── 11.png │ │ │ │ ├── 12.png │ │ │ │ ├── 13.png │ │ │ │ ├── 14.png │ │ │ │ ├── 15.png │ │ │ │ ├── 16.png │ │ │ │ ├── 17.png │ │ │ │ ├── 18.png │ │ │ │ ├── 2.png │ │ │ │ ├── 3.png │ │ │ │ ├── 4.png │ │ │ │ ├── 5.png │ │ │ │ ├── 6.png │ │ │ │ ├── 7.png │ │ │ │ ├── 8.jpg │ │ │ │ ├── 8.png │ │ │ │ └── 9.png │ │ ├── guide.md │ │ ├── vlib-starter-6.md │ │ └── vlib-starter-5.md │ ├── vue2-router-analysis.md │ ├── vue2-vuex-analysis.md │ └── vben │ │ └── vben-deep-1.md ├── node │ ├── image │ │ └── koa-analysis │ │ │ └── koa.png │ ├── tinylib-analysis │ │ ├── guide.md │ │ ├── promisify.md │ │ ├── only-allow.md │ │ ├── configstore.md │ │ ├── validate-npm-package-name.md │ │ ├── koa-compose.md │ │ ├── vue-dev-server.md │ │ ├── create-vue.md │ │ ├── co.md │ │ └── update-notifier.md │ └── koa-analysis.md ├── nest │ └── image │ │ ├── nest-datascope │ │ └── sql.png │ │ └── nest-base │ │ └── lifecycle-events.png ├── react │ └── image │ │ └── redux-analysis │ │ └── redux.png ├── share │ ├── image │ │ ├── annual-summary-2020 │ │ │ ├── a1.jpg │ │ │ ├── a2.jpg │ │ │ ├── b1.jpg │ │ │ ├── b2.jpg │ │ │ ├── c1.jpg │ │ │ └── d1.png │ │ ├── project-review-rxjy-2021 │ │ │ ├── 1.png │ │ │ ├── 2.png │ │ │ ├── 3.png │ │ │ └── 4.png │ │ └── vnext-netease-music │ │ │ ├── 03-toc.png │ │ │ ├── 01-light.png │ │ │ └── 02-dark.png │ ├── annual-summary-2020.md │ └── about-vuepress-plugin-vmi.md ├── typescript │ └── image │ │ └── type-operation-gobang-game │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── 4.png │ │ ├── 5.png │ │ ├── 6.png │ │ ├── 7.png │ │ ├── 8.png │ │ └── 9.png ├── README.yaml ├── javascript │ └── design-pattern │ │ ├── factory.md │ │ ├── guide.md │ │ ├── adapter.md │ │ ├── facade.md │ │ ├── singleton.md │ │ ├── observer.md │ │ ├── iterator.md │ │ ├── strategy.md │ │ └── proxy.md ├── algorithm │ └── leetcode │ │ ├── 20.valid-parentheses.md │ │ ├── 71.simplify-path.md │ │ ├── 203.remove-linked-list-elements.md │ │ ├── 1.two-sum.md │ │ ├── 206.reverse-linked-list.md │ │ ├── 46.permutations.md │ │ ├── 61.rotate-list.md │ │ ├── 82.remove-duplicates-from-sorted-list-ii.md │ │ ├── 141.linked-list-cycle.md │ │ └── 622.design-circular-queue.md ├── css │ ├── bem-naming-rules.md │ └── bem-element-ui.md └── server │ └── nginx-deploy.md ├── code.md ├── message.md ├── package.json ├── .editorconfig └── README.md /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100 3 | } 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.* linguist-language=javascript 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true 3 | } 4 | -------------------------------------------------------------------------------- /about.md: -------------------------------------------------------------------------------- 1 | --- 2 | hideComments: true 3 | --- 4 | 5 | 这个人很懒,什么都没有写!!! 6 | -------------------------------------------------------------------------------- /datas/fe.前端工程化.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/datas/fe.前端工程化.pdf -------------------------------------------------------------------------------- /datas/fe.前端开发路线图.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/datas/fe.前端开发路线图.png -------------------------------------------------------------------------------- /.vuepress/public/wx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/.vuepress/public/wx.png -------------------------------------------------------------------------------- /datas/http/HTTP协议.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/datas/http/HTTP协议.png -------------------------------------------------------------------------------- /datas/http/HTTP协议.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/datas/http/HTTP协议.xmind -------------------------------------------------------------------------------- /datas/linux.目录结构概况.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/datas/linux.目录结构概况.png -------------------------------------------------------------------------------- /.vuepress/public/head.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/.vuepress/public/head.png -------------------------------------------------------------------------------- /.vuepress/public/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/.vuepress/public/logo.png -------------------------------------------------------------------------------- /.vuepress/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/.vuepress/public/favicon.ico -------------------------------------------------------------------------------- /datas/http/从输入URL发生了什么.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/datas/http/从输入URL发生了什么.png -------------------------------------------------------------------------------- /datas/http/从输入URL发生了什么.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/datas/http/从输入URL发生了什么.xmind -------------------------------------------------------------------------------- /datas/security/auth/SSO.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/datas/security/auth/SSO.png -------------------------------------------------------------------------------- /datas/security/安全漏洞与防范.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/datas/security/安全漏洞与防范.png -------------------------------------------------------------------------------- /datas/security/安全漏洞与防范.xmind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/datas/security/安全漏洞与防范.xmind -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | .vuepress/.temp 4 | .vuepress/.cache 5 | *.lock 6 | dist 7 | node_modules 8 | -------------------------------------------------------------------------------- /datas/fe.VueNext和React知识地图.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/datas/fe.VueNext和React知识地图.jpeg -------------------------------------------------------------------------------- /datas/security/auth/TokenJwt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/datas/security/auth/TokenJwt.png -------------------------------------------------------------------------------- /blogs/vue/image/vue2-analysis/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/image/vue2-analysis/1.png -------------------------------------------------------------------------------- /datas/security/auth/OAuthGitHub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/datas/security/auth/OAuthGitHub.png -------------------------------------------------------------------------------- /blogs/node/image/koa-analysis/koa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/node/image/koa-analysis/koa.png -------------------------------------------------------------------------------- /datas/security/auth/CookieSession.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/datas/security/auth/CookieSession.png -------------------------------------------------------------------------------- /blogs/nest/image/nest-datascope/sql.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/nest/image/nest-datascope/sql.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-2/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-2/1.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-2/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-2/2.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-2/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-2/3.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-2/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-2/4.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-2/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-2/5.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-2/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-2/6.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-2/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-2/7.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-3/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-3/1.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-3/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-3/2.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-3/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-3/3.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-3/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-3/4.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-3/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-3/5.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-4/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-4/1.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-5/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-5/1.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-5/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-5/2.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-5/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-5/3.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-5/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-5/4.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-6/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-6/1.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-6/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-6/2.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-6/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-6/3.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-6/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-6/4.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-6/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-6/5.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-7/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-7/1.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-7/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-7/2.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-7/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-7/3.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-7/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-7/4.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-7/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-7/5.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-7/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-7/6.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-7/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-7/7.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-7/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-7/8.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-8/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-8/1.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-8/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-8/2.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-8/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-8/3.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-8/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-8/4.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-8/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-8/5.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-8/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-8/6.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-9/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-9/1.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-9/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-9/2.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-9/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-9/3.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-9/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-9/4.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-9/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-9/5.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-9/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-9/6.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-9/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-9/7.png -------------------------------------------------------------------------------- /blogs/react/image/redux-analysis/redux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/react/image/redux-analysis/redux.png -------------------------------------------------------------------------------- /blogs/vue/image/vue2-analysis/reactive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/image/vue2-analysis/reactive.png -------------------------------------------------------------------------------- /blogs/vue/image/vue2-vuex-analysis/vuex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/image/vue2-vuex-analysis/vuex.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-10/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-10/1.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-10/10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-10/10.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-10/11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-10/11.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-10/12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-10/12.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-10/13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-10/13.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-10/14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-10/14.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-10/15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-10/15.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-10/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-10/16.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-10/17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-10/17.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-10/18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-10/18.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-10/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-10/2.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-10/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-10/3.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-10/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-10/4.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-10/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-10/5.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-10/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-10/6.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-10/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-10/7.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-10/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-10/8.jpg -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-10/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-10/8.png -------------------------------------------------------------------------------- /blogs/vue/lib/image/vlib-starter-10/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/vue/lib/image/vlib-starter-10/9.png -------------------------------------------------------------------------------- /blogs/share/image/annual-summary-2020/a1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/share/image/annual-summary-2020/a1.jpg -------------------------------------------------------------------------------- /blogs/share/image/annual-summary-2020/a2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/share/image/annual-summary-2020/a2.jpg -------------------------------------------------------------------------------- /blogs/share/image/annual-summary-2020/b1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/share/image/annual-summary-2020/b1.jpg -------------------------------------------------------------------------------- /blogs/share/image/annual-summary-2020/b2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/share/image/annual-summary-2020/b2.jpg -------------------------------------------------------------------------------- /blogs/share/image/annual-summary-2020/c1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/share/image/annual-summary-2020/c1.jpg -------------------------------------------------------------------------------- /blogs/share/image/annual-summary-2020/d1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/share/image/annual-summary-2020/d1.png -------------------------------------------------------------------------------- /blogs/nest/image/nest-base/lifecycle-events.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/nest/image/nest-base/lifecycle-events.png -------------------------------------------------------------------------------- /blogs/share/image/project-review-rxjy-2021/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/share/image/project-review-rxjy-2021/1.png -------------------------------------------------------------------------------- /blogs/share/image/project-review-rxjy-2021/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/share/image/project-review-rxjy-2021/2.png -------------------------------------------------------------------------------- /blogs/share/image/project-review-rxjy-2021/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/share/image/project-review-rxjy-2021/3.png -------------------------------------------------------------------------------- /blogs/share/image/project-review-rxjy-2021/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/share/image/project-review-rxjy-2021/4.png -------------------------------------------------------------------------------- /blogs/share/image/vnext-netease-music/03-toc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/share/image/vnext-netease-music/03-toc.png -------------------------------------------------------------------------------- /blogs/share/image/vnext-netease-music/01-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/share/image/vnext-netease-music/01-light.png -------------------------------------------------------------------------------- /blogs/share/image/vnext-netease-music/02-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/share/image/vnext-netease-music/02-dark.png -------------------------------------------------------------------------------- /blogs/typescript/image/type-operation-gobang-game/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/typescript/image/type-operation-gobang-game/1.png -------------------------------------------------------------------------------- /blogs/typescript/image/type-operation-gobang-game/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/typescript/image/type-operation-gobang-game/2.png -------------------------------------------------------------------------------- /blogs/typescript/image/type-operation-gobang-game/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/typescript/image/type-operation-gobang-game/3.png -------------------------------------------------------------------------------- /blogs/typescript/image/type-operation-gobang-game/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/typescript/image/type-operation-gobang-game/4.png -------------------------------------------------------------------------------- /blogs/typescript/image/type-operation-gobang-game/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/typescript/image/type-operation-gobang-game/5.png -------------------------------------------------------------------------------- /blogs/typescript/image/type-operation-gobang-game/6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/typescript/image/type-operation-gobang-game/6.png -------------------------------------------------------------------------------- /blogs/typescript/image/type-operation-gobang-game/7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/typescript/image/type-operation-gobang-game/7.png -------------------------------------------------------------------------------- /blogs/typescript/image/type-operation-gobang-game/8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/typescript/image/type-operation-gobang-game/8.png -------------------------------------------------------------------------------- /blogs/typescript/image/type-operation-gobang-game/9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/haiweilian/blogs/HEAD/blogs/typescript/image/type-operation-gobang-game/9.png -------------------------------------------------------------------------------- /blogs/node/tinylib-analysis/guide.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 分析 Node 小型库源码系列 3 | date: 2021-03-29 4 | tags: 5 | - Node 6 | categories: 7 | - 后端 8 | - 源码分析 9 | --- 10 | -------------------------------------------------------------------------------- /.vuepress/config/data/open-source-code/index.ts: -------------------------------------------------------------------------------- 1 | import author from "./author"; 2 | import contributor from "./contributor"; 3 | 4 | export default { 5 | author, 6 | contributor, 7 | }; 8 | -------------------------------------------------------------------------------- /code.md: -------------------------------------------------------------------------------- 1 | --- 2 | hideComments: true 3 | --- 4 | 5 | ## Author 6 | 7 | 8 | 9 | ## Contributor 10 | 11 | 12 | -------------------------------------------------------------------------------- /.vuepress/client.ts: -------------------------------------------------------------------------------- 1 | import { defineClientConfig } from "@vuepress/client"; 2 | import "./config/styles/index.scss"; 3 | 4 | export default defineClientConfig({ 5 | enhance({ app }) {}, 6 | }); 7 | -------------------------------------------------------------------------------- /message.md: -------------------------------------------------------------------------------- 1 | --- 2 | hideComments: false 3 | --- 4 | 5 | ::: info 6 | 欢迎大家在此留下你的建议和意见,或者在 [GitHub Issue](https://github.com/haiweilian/blogs/issues) 提交你的问题,或来 [GitHub Discussions](https://github.com/haiweilian/blogs/discussions) 进行讨论。 7 | ::: 8 | -------------------------------------------------------------------------------- /.vuepress/config/styles/index.scss: -------------------------------------------------------------------------------- 1 | // 时间线年份倒序排列 2 | .timeline-content { 3 | display: flex; 4 | flex-direction: column-reverse; 5 | } 6 | 7 | // 主题推荐底部隐藏 8 | .footer-wrapper { 9 | > span:nth-child(1) { 10 | display: none; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /blogs/README.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | title: HaiWeiLian's Blog 3 | date: 2020-01-01 4 | tags: 5 | - Css 6 | - Scss 7 | - Html 8 | - JavaScript 9 | - DesignPattern 10 | - TypeScript 11 | - Vue 12 | - Vuex 13 | - VueRouter 14 | - React 15 | - Redux 16 | - Node 17 | - Nest 18 | - Nginx 19 | - LeetCode 20 | - Git 21 | - VsCode 22 | categories: 23 | - 前端 24 | - 后端 25 | - 算法 26 | - 技术分享 27 | - 源码分析 28 | - 开发工具 29 | --- 30 | 31 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "blogs", 3 | "author": "haiweilian@foxmail.com", 4 | "scripts": { 5 | "dev": "vuepress dev .", 6 | "start": "vuepress dev .", 7 | "build": "vuepress build ." 8 | }, 9 | "dependencies": { 10 | "@vicons/carbon": "^0.12.0", 11 | "@vuepress/bundler-vite": "2.0.0-rc.13", 12 | "@vuepress/bundler-webpack": "2.0.0-rc.13", 13 | "sass": "^1.75.0", 14 | "vue": "^3.4.19", 15 | "vuepress": "2.0.0-rc.13", 16 | "vuepress-theme-reco": "2.0.0-rc.16" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /.vuepress/icons/SimpleIconsJuejin.vue: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | root = true 3 | 4 | # indentation 5 | [*] 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [{*.md,*.MD}] 14 | trim_trailing_whitespace = false 15 | 16 | # The indent size used in the `package.json` file cannot be changed 17 | # https://github.com/npm/npm/pull/3180#issuecomment-16336516 18 | [{*.yml,*.yaml,package.json,*.json}] 19 | indent_style = space 20 | indent_size = 2 21 | -------------------------------------------------------------------------------- /.vuepress/icons/index.ts: -------------------------------------------------------------------------------- 1 | export * from "../../node_modules/@vicons/carbon"; 2 | // download by https://icones.js.org/ 3 | export { default as SimpleIconsGitee } from "./SimpleIconsGitee.vue"; 4 | export { default as SimpleIconsGithub } from "./SimpleIconsGithub.vue"; 5 | export { default as SimpleIconsJuejin } from "./SimpleIconsJuejin.vue"; 6 | export { default as SimpleIconsLeetcode } from "./SimpleIconsLeetcode.vue"; 7 | export { default as SimpleIconsWechat } from "./SimpleIconsWechat.vue"; 8 | export { default as SimpleIconsZhihu } from "./SimpleIconsZhihu.vue"; 9 | -------------------------------------------------------------------------------- /.vuepress/icons/SimpleIconsGitee.vue: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /blogs/javascript/design-pattern/factory.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 设计模式之工厂模式 3 | date: 2021-01-24 4 | tags: 5 | - JavaScript 6 | - DesignPattern 7 | categories: 8 | - 前端 9 | --- 10 | 11 | ## 定义 12 | 13 | 当需要根据不同参数产生不同实例,这些实例都有相同的行为。 14 | 15 | ## 实现 16 | 17 | 提供创建对象的接口,把成员对象的创建工作转交为一个外部对象,好出在于消除对象之间的耦合(也就是相互影响)。 18 | 19 | ## 应用 20 | 21 | **消息通知** 22 | 23 | 如一个工厂函数,根据参数提示不同的消息类型。 24 | 25 | ```js 26 | let Notification = function (options = {}) { 27 | let instance = new Object(); 28 | instance.type = options.type; 29 | instance.message = `消息类型-${options.message}`; 30 | instance.visible = true; 31 | return instance; 32 | }; 33 | 34 | const n = Notification({ 35 | type: "success", 36 | message: "success", 37 | }); 38 | console.log(n.message); // 消息类型-success 39 | 40 | const m = Notification({ 41 | type: "error", 42 | message: "error", 43 | }); 44 | console.log(m.message); // 消息类型-error 45 | ``` 46 | -------------------------------------------------------------------------------- /.vuepress/icons/SimpleIconsGithub.vue: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /.vuepress/public/examples/nest-lifecycle-events/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | nest-lifecycle-events 6 | 10 | 11 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /.vuepress/public/examples/nest-request-lifecycle/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | nest-request-lifecycle 6 | 10 | 11 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /.vuepress/icons/SimpleIconsLeetcode.vue: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /.vuepress/icons/SimpleIconsZhihu.vue: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /.vuepress/config/data/open-source-code/author.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | name: "bfehub/**", 4 | repo: "bfehub", 5 | link: "https://github.com/bfehub", 6 | desc: "前端工程化解决方案工具包,开发模板、实用插件、代码规范", 7 | }, 8 | { 9 | name: "vnext-analysis", 10 | repo: "haiweilian/vnext-analysis", 11 | link: "https://github.com/haiweilian/vnext-analysis", 12 | desc: "Vue3 体系源码分析、流程标记、思维导图、Mini 版实现。", 13 | }, 14 | { 15 | name: "taggd-manager", 16 | repo: "haiweilian/taggd-manager", 17 | link: "https://github.com/haiweilian/taggd-manager", 18 | desc: "图片打点工具(Image Tag),支持移动、缩放、自定义事件。", 19 | }, 20 | { 21 | name: "vivy-nest-admin", 22 | repo: "haiweilian/vivy-nest-admin", 23 | link: "https://github.com/haiweilian/vivy-nest-admin", 24 | desc: "基于 Nest.js & React.js & TypeORM 的后台权限管理系统。", 25 | }, 26 | { 27 | name: "electron-smallest-updater", 28 | repo: "haiweilian/electron-smallest-updater", 29 | link: "https://github.com/haiweilian/electron-smallest-updater", 30 | desc: "Electron Resources 按需最小更新、自动构建发布包及信息。", 31 | }, 32 | ]; 33 | -------------------------------------------------------------------------------- /blogs/javascript/design-pattern/guide.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 设计模式与开发实践系列 3 | date: 2021-01-10 4 | updated: 2021-01-10 5 | tags: 6 | - JavaScript 7 | - DesignPattern 8 | categories: 9 | - 前端 10 | --- 11 | 12 | 设计模式相关的总结和例子,学习资料。 13 | 14 | [JavaScript 设计模式与开发实践](https://www.ituring.com.cn/book/1632) 15 | 16 | ## 设计模式定义 17 | 18 | **设计模式的定义** 19 | 20 | 在面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案;也就是⼀套被反复使⽤、多数⼈知晓的、经过分类的、代码设计经验的总结。 21 | 22 | **分辨模式的关键是意图而不是结构** 23 | 24 | 有很多模式的类图和结构确实很相似,但这不太重要,辨别模式的关键是这个模式出现的场景,以及为我们解决了什么问题。 25 | 26 | ## 设计模式原则 27 | 28 | **单一职责原则(SRP)** 29 | 30 | 一个对象或方法只做一件事情。 31 | 32 | 就一个类而言,应该仅有一个引起它变化的原因。每个职责都是变化的一个轴线,如果一个方法承担了过多的职责,那么在需求的变迁过程中,需要改写这个方法的可能性就越大。 33 | 34 | **最少知识原则(LKP)** 35 | 36 | 一个软件实体应当 尽可能少地与其他实体发生相互作用。这里的软件实体是一个广义的概念,不仅包括对象,还包括类、模块、函数、变量等。 37 | 38 | 最少知识原则要求我们在设计程序时,应当尽量减少对象之间的交互。如果两个对象之间不必彼此直接通信,那么这两个对象就不要发生直接的相互联系,可以转交给第三方进行处理。 39 | 40 | **开放-封闭原则(OCP)** 41 | 42 | 软件实体(类、模块、函数)等应该是可以 扩展的,但是不可修改。 43 | 44 | 当需要改变一个程序的功能或者给这个程序增加新功能的时候,可以使用增加代码的方式,尽量避免改动程序的源代码。 45 | 46 | ## 设计模式类型 47 | 48 | - [设计模式之单例模式》](./singleton.md) 49 | - [设计模式之策略模式》](./strategy.md) 50 | - [设计模式之代理模式》](./proxy.md) 51 | - [设计模式之迭代器模式》](./iterator.md) 52 | - [设计模式之发布订阅模式》](./observer.md) 53 | - [设计模式之适配器模式》](./adapter.md) 54 | - [设计模式之外观模式》](./facade.md) 55 | - [设计模式之工厂模式》](./factory.md) 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | modules: 4 | - BannerBrand 5 | - Blog 6 | - MdContent 7 | - Footer 8 | bannerBrand: 9 | bgImage: /bg.svg 10 | title: HaiWeiLian's Blog 11 | description: 学习、记录、分享 12 | tagline: 将有一天你会拥有美好的生活,感谢在那每一次跌倒了以后,又重新来过。 13 | buttons: 14 | - { text: 网络日志, link: "/posts.html" } 15 | - { text: 开源项目, link: "/code.html", type: "plain" } 16 | socialLinks: 17 | - { icon: "SimpleIconsGithub", link: "https://github.com/haiweilian" } 18 | - { icon: "SimpleIconsGitee", link: "https://gitee.com/haiweilian" } 19 | - { icon: "SimpleIconsJuejin", link: "https://juejin.cn/user/360295547023176" } 20 | - { icon: "SimpleIconsLeetcode", link: "https://leetcode.cn/u/haiweilian" } 21 | - { icon: "SimpleIconsWechat", link: "/wx.png" } 22 | blog: 23 | socialLinks: 24 | - { icon: "SimpleIconsGithub", link: "https://github.com/haiweilian" } 25 | - { icon: "SimpleIconsGitee", link: "https://gitee.com/haiweilian" } 26 | - { icon: "SimpleIconsJuejin", link: "https://juejin.cn/user/360295547023176" } 27 | - { icon: "SimpleIconsLeetcode", link: "https://leetcode.cn/u/haiweilian" } 28 | - { icon: "SimpleIconsWechat", link: "/wx.png" } 29 | footer: 30 | record: 京ICP备16015934号 31 | recordLink: http://www.beian.miit.gov.cn 32 | startYear: 2020 33 | --- 34 | -------------------------------------------------------------------------------- /blogs/share/annual-summary-2020.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 年度总结-二零二零年度总结 3 | date: 2020-12-27 4 | updated: 2020-12-27 5 | categories: 6 | - 技术分享 7 | --- 8 | 9 | 匆匆忙忙今年已到年尾,可能因为疫情原因和业务压力好像今年过的异常的快没怎么潇洒就过去了(最近北京的朋友注意安全)。赶上周末把自己今年做了哪些事总结一下。 10 | 11 | ## 追番 12 | 13 | 自己还是比较喜欢看动漫的,今年没看什么电视剧、电影。在 b 站逛了一年,基本新番都看了也看老的了,贴出来几个感觉特别好看的。 14 | 15 | ![年度总结](./image/annual-summary-2020/a1.jpg) 16 | ![年度总结](./image/annual-summary-2020/a2.jpg) 17 | 18 | ## 读书 19 | 20 | 今年就读了一本书,最后两个月算是把红宝书看了一遍,早上起不来都是晚上看的。[这是整理的笔记](https://juejin.cn/post/6909455840361742343) 21 | 22 | ![年度总结](./image/annual-summary-2020/b2.jpg) 23 | 24 | ## 学习 25 | 26 | 今年是学习最多的一年,当然进步也是最大的。差不多把大前端的东西都搞了一下广度很多但有深度的还是用得到的那些。学的最多的是: vue2 技术栈的源码、研究了 webpack 、学习并使用了 nodejs 和 egg、Js 的设计模式、数据结构与算法。 27 | 28 | ![年度总结](./image/annual-summary-2020/b1.jpg) 29 | 30 | ## 开源 31 | 32 | 今年在 github 上自嗨了很久,做了几个开源项目,也写了很多文章和实战练习。星星还是少的可怜,不过能有星星就能很高兴了。 33 | 34 | ![年度总结](./image/annual-summary-2020/d1.png) 35 | 36 | ## 工作 37 | 38 | 也不说什么原因了,身边的同事一如既往的更新的很快。今年重心都在业务,也没有给公司推进一些技术性的提升,比去年主导开发组件库、标准库差了好多。 39 | 40 | ## FLAG 41 | 42 | - 读书:小黄书、红宝书第二遍深入、nodejs 深入浅出、软技能。 43 | - 学习:数据结构与算法、vue3 技术栈源码、typeScript、计算机基础。 44 | - 开源:参与有质量的项目、星星突破二百。 45 | - 工作:开心工作、工资翻倍。 46 | 47 | ## END 48 | 49 | 😃 哈哈,不是妖精的尾巴中的最强恶魔 😈。留着放掘金年度征文链接的。 50 | 51 | [掘金年度征文 | 2020 与我的技术之路 征文活动正在进行中......](https://juejin.cn/post/6901125532729999374) 52 | -------------------------------------------------------------------------------- /blogs/javascript/design-pattern/adapter.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 设计模式之适配器模式 3 | date: 2021-01-24 4 | tags: 5 | - JavaScript 6 | - DesignPattern 7 | categories: 8 | - 前端 9 | --- 10 | 11 | ## 定义 12 | 13 | 适配模式的作用是解决两个软件实体间的接口不兼容的问题。使用适配器模式之后,原本由于接口不兼容而不能工作的两个软件实体可以一起工作。 14 | 15 | ## 实现 16 | 17 | 使用适配器对能改的接口进行包装,转换成另一个接口的所需。 18 | 19 | ## 应用 20 | 21 | **数据格式转换** 22 | 23 | 如有一个渲染函数接收以下数据格式,这个函数在业务内部,不希望改动它。 24 | 25 | ```js 26 | // let sku = [ 27 | // { 28 | // name: "大小", 29 | // value: [ 30 | // { name: "大", id: "100" }, 31 | // { name: "小", id: "200" }, 32 | // ], 33 | // }, 34 | // ] 35 | function renderData(data) { 36 | data.forEach((item) => { 37 | console.log(item.name, item.value); 38 | }); 39 | } 40 | ``` 41 | 42 | 而现有的数据格式不满足渲染函数,这时就需要一个适配器去转换它。 43 | 44 | ```js 45 | let sku = [ 46 | { id: "100", name: "大小", value: "大" }, 47 | { id: "100", name: "大小", value: "小" }, 48 | ]; 49 | function arrayAdapter(data) { 50 | let maps = {}; 51 | 52 | data.forEach((item) => { 53 | if (maps[item.name] === void 0) { 54 | maps[item.name] = []; 55 | } 56 | maps[item.name].push(item); 57 | }); 58 | 59 | return Object.entries(maps).map(([name, value]) => { 60 | return { 61 | name, 62 | value, 63 | }; 64 | }); 65 | } 66 | 67 | // 先用适配器进行数据转换,在调用渲染函数。 68 | renderData(arrayAdapter(sku)); 69 | ``` 70 | -------------------------------------------------------------------------------- /.vuepress/icons/SimpleIconsWechat.vue: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /.vuepress/config/data/open-source-code/contributor.ts: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | name: "v-viewer", 4 | repo: "mirari/v-viewer", 5 | link: "https://github.com/mirari/v-viewer/pulls?q=author%3Ahaiweilian", 6 | desc: "About Image viewer component for vue, supports rotation, scale, zoom and so on, based on viewer.js", 7 | }, 8 | { 9 | name: "nutui", 10 | repo: "jdf2e/nutui", 11 | link: "https://github.com/jdf2e/nutui/pulls?q=author%3Ahaiweilian", 12 | desc: "京东风格的移动端 Vue2、Vue3 组件库 、支持多端小程序(A Vue.js UI Toolkit for Mobile Web)", 13 | }, 14 | { 15 | name: "vant", 16 | repo: "youzan/vant", 17 | link: "https://github.com/youzan/vant/pulls?q=author%3Ahaiweilian", 18 | desc: "A lightweight, customizable Vue UI library for mobile web apps.", 19 | }, 20 | { 21 | name: "watermark", 22 | repo: "pansyjs/watermark", 23 | link: "https://github.com/pansyjs/watermark/pulls?q=author%3Ahaiweilian", 24 | desc: "🛡 强大的水印组件,助你快速的给网页添加水印。", 25 | }, 26 | { 27 | name: "vite-plugin-mock", 28 | repo: "vbenjs/vite-plugin-mock", 29 | link: "https://github.com/vbenjs/vite-plugin-mock/pulls?q=author%3Ahaiweilian", 30 | desc: "A mock plugin for vite.use mockjs.", 31 | }, 32 | { 33 | name: "ant-design-vue", 34 | repo: "vueComponent/ant-design-vue", 35 | link: "https://github.com/vueComponent/ant-design-vue/pulls?q=author%3Ahaiweilian", 36 | desc: "🌈 An enterprise-class UI components based on Ant Design and Vue. 🐜", 37 | }, 38 | ]; 39 | -------------------------------------------------------------------------------- /blogs/javascript/design-pattern/facade.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 设计模式之外观模式 3 | date: 2021-01-24 4 | tags: 5 | - JavaScript 6 | - DesignPattern 7 | categories: 8 | - 前端 9 | --- 10 | 11 | ## 定义 12 | 13 | 为子系统中的一组接口提供一个一致的界面。 14 | 15 | ## 实现 16 | 17 | 定义一个高层接口,这个接口使子系统更加容易使用。 18 | 19 | ## 应用 20 | 21 | **多个方法一起被调用** 22 | 23 | ```js 24 | function start() { 25 | console.log("start"); 26 | } 27 | 28 | function doing() { 29 | console.log("doing"); 30 | } 31 | 32 | function end() { 33 | console.log("end"); 34 | } 35 | 36 | // 外观函数,将一些处理统一起来,方便调用。 37 | function execute() { 38 | start(); 39 | doing(); 40 | end(); 41 | } 42 | 43 | // 此处直接调用了高层函数。 44 | execute(); 45 | ``` 46 | 47 | **处理兼容性** 48 | 49 | 涉及到兼容性,参数支持多格式,对外暴露统一的 api,对外只用一个函数,内部判断实现。 50 | 51 | ```js 52 | const Event = { 53 | stop(e) { 54 | if (typeof e.preventDefault() === "function") { 55 | e.preventDefault(); 56 | } else if (typeof e.stopPropagation() === "function") { 57 | e.stopPropagation(); 58 | } else if (typeof e.returnValue === "boolean") { 59 | e.returnValue = true; 60 | } else if (typeof e.cancelBubble === "boolean") { 61 | e.cancelBubble = true; 62 | } else { 63 | return false; 64 | } 65 | }, 66 | addEvent(dom, type, fn) { 67 | if (dom.addEventListener) { 68 | dom.addEventListener(type, fn, false); 69 | } else if (dom.attachEvent) { 70 | dom.attachEvent("on" + type, fn); 71 | } else { 72 | dom["on" + type] = fn; 73 | } 74 | }, 75 | }; 76 | ``` 77 | -------------------------------------------------------------------------------- /blogs/javascript/design-pattern/singleton.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 设计模式之单例模式 3 | date: 2021-01-10 4 | tags: 5 | - JavaScript 6 | - DesignPattern 7 | categories: 8 | - 前端 9 | --- 10 | 11 | ## 定义 12 | 13 | 保证一个类仅有一个实例,并提供一个访问它的全局访问点。 14 | 15 | ## 实现 16 | 17 | 实现的方法为先判断实例是否存在,如果存在则直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。 18 | 19 | ## 应用 20 | 21 | **后续返回同样的值** 22 | 23 | ```js 24 | function getSingle() { 25 | let result; 26 | return function (name) { 27 | return result || (result = name); 28 | }; 29 | } 30 | 31 | let single = getSingle(); 32 | console.log(single("嘻嘻")); // 嘻嘻 33 | console.log(single("哈哈")); // 嘻嘻 34 | console.log(single("嘤嘤")); // 嘻嘻 35 | ``` 36 | 37 | **登录浮框的例子** 38 | 39 | 如当我们点击登录按钮的时候,页面中出现一个登录浮框。而这个登录框是唯一的,无论点击多少次按钮,这个浮框只会出现一次。那我们一开始就创建好不也是一样的吗。那如果不点击,那不就造成浪费了吗。这种当需要的时候再创建,也叫惰性单例。 40 | 41 | ```js 42 | // 把单例的逻辑抽离出来 43 | function getSingle(fn) { 44 | let result; 45 | return function () { 46 | return result || (result = fn.apply(this, arguments)); 47 | }; 48 | } 49 | 50 | // 登录浮框的逻辑 51 | function createLoginLayer() { 52 | console.log("...登录浮框,只会初始化一次"); 53 | let div = document.createElement("div"); 54 | div.innerHTML = "我是登录浮框"; 55 | div.style.display = "none"; 56 | document.body.appendChild(div); 57 | return div; 58 | } 59 | 60 | // 事件处理 61 | let createSingleLoginLayer = getSingle(createLoginLayer); 62 | let loginBtn = document.getElementById("loginBtn"); 63 | loginBtn.addEventListener("click", function () { 64 | let loginLayer = createSingleLoginLayer(); 65 | loginLayer.style.display = "block"; 66 | }); 67 | ``` 68 | -------------------------------------------------------------------------------- /datas/security/auth/SSO.drawio: -------------------------------------------------------------------------------- 1 | 7VnLcpswFP0aLdPhYfFYgsHtopnJTBZNlwrIQIORK+TYztdXAvEmKWn8StKNR7qSLuice88VMtDnq91XitbxNQlxCjQl3AHdA5pm2Rr/FYZ9aZjpsDRENAlLk9oYbpMnLI2KtG6SEOediYyQlCXrrjEgWYYD1rEhSsm2O21J0u5T1yjCA8NtgNKh9UcSslhaVcNuBr7hJIrloy3NLAdWqJosd5LHKCTblkn3gT6nhLCytdrNcSqwq3Ap1y2eGa1fjOKMTVlwFWiLa9v2HrPoJv/5myyYZ1xJL48o3cgNAx8C1wH2DPim+HUsR74/21egULLJQiz8qkB3t3HC8O0aBWJ0y6OA22K2SuXwMknTOUkJLdbqIcLWMuD2nFHygFsjRmDh+yUfke+EKcO7Zzer1hDy0MNkhRnd8ylywUyCvu92ty0KK17iFnvVPCSjJqodN8DyhsT2FTjrIzhbwPGBMysaC2CpwDeAPQeWJyjgqDuqaNg2p+CgDCytAAejDNxbcAaVwzCgGxdGwWxCqLvvMNQN68KAVofhikMuqrJLKItJRDKU+o3VbXAW8dfM+U7IWqL7CzO2lxUCbRjpYo93CbuTy0X7p2h/gbLn7VpD3r7qZHy/d5UD0WmtEt1mWdGr1pX7E5t6mTOOAdnQAL8AlqyGDNEIsxfmGeMxQHGKWPLYfY+DM1rv5iyMqq9g9AzMqPo5qYEDVctxnickA5qR8td27ylvRaJVq5woNJYFXLOw2MB1CyX0gA2LIUUUozcJ4RHqR61hf9M1TTmasNmXkwbmRGVTuspmnlDajIkJBM+ZP8bwVCATJ1+jrEqdXk7xJ7VHR1JtxMcr0++ZJ505K2cXl5T159UFnDa0fztuaCdMSlV/D1mpaZdD6lSl7ZF6SqV9H6SqIx/B/7V2+ofdBYitao5Q2E/ULHTExRvvBSniXAZTzjNTpbN3njmldL7xPNNiDY6QVtkm56J8wg1J+EbqoNH0btDU14+Vi3KbclX7urDvqB99Zs9RicPAURFX9bbfUALGr8x4Jrt60fCAMx+7RLPFxZkLB3HJE5F1IxGlSZSJMOWhgCk3iHRNApQ6cmCVhGFZWnCePKH7wpWIqrXYdYEDdAH0hC9eTfKysBwo/WvmKgLgMP2t0ew/VlEeu0HjRCyKGzQIbBfYvkgQ8oCzDwA/7CaSMoTfPCn8I9rLS5ijANcS8PM8+Mjwj0T/aeG3P7kewf6337n1qKp0n0OP4OzC9Egf++/ww+rRAP7j6RHvNv8Kl8ep5q913f8D -------------------------------------------------------------------------------- /blogs/javascript/design-pattern/observer.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 设计模式之发布订阅模式 3 | date: 2021-01-17 4 | tags: 5 | - JavaScript 6 | - DesignPattern 7 | categories: 8 | - 前端 9 | --- 10 | 11 | ## 定义 12 | 13 | 发布-订阅模式又叫观察者模式,在这种模式中,并不是一个对象调用另一个对象的方法,而是一个对象订阅另一个对象的特定活动并在状态改变后获得通知。订阅者也成为观察者,而被观察的对象成为发布者,当发生了一个事件的时候,发布者会通知(调用)所有订阅者并传递消息。 14 | 15 | ## 实现 16 | 17 | 首先要指定谁充当发布者。然后给发布者添加一个缓存列表,用于存放回调函数以便通知订阅者。最后发布消息的时候,发布者会遍历这个缓存列表,依次触发里面存放的订阅者的回调函数。 18 | 19 | ## 应用 20 | 21 | **DOM 事件** 22 | 23 | 原生 dom 事件本身就是发布-订阅模式。 24 | 25 | ```js 26 | // 订阅 点击事件 27 | document.body.addEventListener("click", function () { 28 | alert(1); 29 | }); 30 | 31 | // 模拟用户点击,发布 点击事件 32 | document.body.click(); 33 | ``` 34 | 35 | **发布-订阅** 36 | 37 | 下面是一个简单的通用的发布订阅模式。 38 | 39 | ```js 40 | class Event { 41 | constructor() { 42 | this.callbacks = {}; 43 | } 44 | 45 | // 解绑 46 | off(name) { 47 | this.callbacks[name] = null; 48 | } 49 | 50 | // 订阅 51 | on(name, fn) { 52 | (this.callbacks[name] || (this.callbacks[name] = [])).push(fn); 53 | } 54 | 55 | // 发布 56 | emit(name, args) { 57 | let cbs = this.callbacks[name]; 58 | if (cbs) { 59 | cbs.forEach((cb) => { 60 | cb.call(this, args); 61 | }); 62 | } 63 | } 64 | } 65 | 66 | let events = new Event(); 67 | 68 | // 订阅 do 事件 69 | events.on("do", (doing) => { 70 | console.log(`正在${doing}...`); 71 | }); 72 | 73 | // 发布 do 事件 74 | events.emit("do", "学习"); // 正在学习... 75 | events.emit("do", "看书"); // 正在看书... 76 | 77 | // 解绑 do 事件后,不会再通知 78 | events.off("do"); 79 | events.emit("do", "学习"); 80 | events.emit("do", "看书"); 81 | ``` 82 | -------------------------------------------------------------------------------- /.vuepress/public/examples/nest-lifecycle-events/code.js: -------------------------------------------------------------------------------- 1 | export const code = `// app.ts ============================================================= 2 | import { NestFactory } from "@nestjs/core"; 3 | import { AppModule } from "./app.module"; 4 | 5 | async function bootstrap() { 6 | const app = await NestFactory.create(AppModule); 7 | console.log("Init completed"); 8 | 9 | app.enableShutdownHooks(); // 必须启用才能监听到信号 10 | 11 | await app.listen(3000, () => { 12 | console.log("Listener start"); 13 | }); 14 | 15 | setTimeout(() => { 16 | app.close(); 17 | // process.kill(process.pid); 18 | }, 5000); 19 | } 20 | bootstrap(); 21 | 22 | // app.module.ts ============================================================= 23 | import { 24 | BeforeApplicationShutdown, 25 | Module, 26 | OnApplicationBootstrap, 27 | OnApplicationShutdown, 28 | OnModuleDestroy, 29 | OnModuleInit, 30 | } from "@nestjs/common"; 31 | 32 | @Module({}) 33 | export class AppModule 34 | implements 35 | OnModuleInit, 36 | OnApplicationBootstrap, 37 | OnModuleDestroy, 38 | BeforeApplicationShutdown, 39 | OnApplicationShutdown 40 | { 41 | onModuleInit() { 42 | console.log("onModuleInit"); 43 | } 44 | 45 | onApplicationBootstrap() { 46 | console.log("onApplicationBootstrap"); 47 | } 48 | 49 | onModuleDestroy() { 50 | console.log("onModuleDestroy"); 51 | } 52 | 53 | beforeApplicationShutdown(signal?: string) { 54 | console.log("beforeApplicationShutdown", signal); 55 | } 56 | 57 | onApplicationShutdown(signal?: string) { 58 | console.log("onApplicationShutdown", signal); 59 | } 60 | }`; 61 | -------------------------------------------------------------------------------- /blogs/javascript/design-pattern/iterator.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 设计模式之迭代器模式 3 | date: 2021-01-17 4 | tags: 5 | - JavaScript 6 | - DesignPattern 7 | categories: 8 | - 前端 9 | --- 10 | 11 | ## 定义 12 | 13 | 迭代器模式是指提供一种方法顺序访问一个聚合对象中的各个元素,而又不需要暴露该对象的内部表示。 14 | 15 | ## 实现 16 | 17 | 迭代器模式可以把迭代的过程从业务逻辑中分离出来,在使用迭代器模式之后,即不关心对象的内部构造,也可以顺序访问其中的每个元素。 18 | 19 | ## 应用 20 | 21 | **内部迭代器** 22 | 23 | 内部迭代器这个用的很多了,`forEach`、`map` 等。如下数组和对象都可以迭代。 24 | 25 | ```js 26 | function each(obj, cb) { 27 | if (Array.isArray(obj)) { 28 | for (var i = 0; i < obj.length; ++i) { 29 | cb.call(obj[i], i, obj[i]); 30 | } 31 | } else { 32 | for (var i in obj) { 33 | cb.call(obj[i], i, obj[i]); 34 | } 35 | } 36 | } 37 | 38 | each([1, 2, 3], function (index, value) { 39 | console.log(index, value); 40 | }); 41 | // 0 1 42 | // 1 2 43 | // 2 3 44 | 45 | each({ a: 1, b: 2 }, function (index, value) { 46 | console.log(index, value); 47 | }); 48 | 49 | // a 1 50 | // b 2 51 | ``` 52 | 53 | **外部迭代器** 54 | 55 | 原生的外部迭代器;任何实现 Iterator 接口的对象都可以作为迭代器使用,Iterator 接口主要供 `for...of` 消费。 56 | 57 | 原生实现的有 `Array` 、`Map` 、`Set` 、`String` 、`TypedArray` 、`arguments` 、`NodeList`,可以直接使用。 58 | 59 | ```js 60 | let list = ["a", "b", "c"]; 61 | for (let item of list) { 62 | console.log(item); 63 | } 64 | // a 65 | // b 66 | // c 67 | ``` 68 | 69 | Iterator 的遍历过程是:每一次调用 `next` 方法,都会返回数据结构的当前成员的信息,返回一个包含 `value` 和 `done` 两个属性的对象。其中,`value` 属性是当前成员的值,`done` 属性是一个布尔值,表示遍历是否结束。 70 | 71 | ```js 72 | let listr = list[Symbol.iterator](); // 调用内部的迭代函数 73 | console.log(listr.next()); // { value: 'a', done: false } 74 | console.log(listr.next()); // { value: 'b', done: false } 75 | console.log(listr.next()); // { value: 'c', done: false } 76 | console.log(listr.next()); // { value: undefined, done: true } 77 | ``` 78 | -------------------------------------------------------------------------------- /datas/security/auth/CookieSession.drawio: -------------------------------------------------------------------------------- 1 | 7VlNc+I4EP01Os6WP2XpaBuYVO3sKbW1s6cpxxbgjbEoIwaYX7/dsoxt7CSwAxNStZeU3JJa6td66qdA3Hi1/1wl6+UfMhMFcaxsT9wJcRwWuPAXDYfa4AesNiyqPKtNdmt4zH8IY7SMdZtnYtMbqKQsVL7uG1NZliJVPVtSVXLXHzaXRX/VdbIQA8NjmhRD6195ppbGalPedjyIfLE0SzMnqDtWSTPYRLJZJpncdUzulLhxJaWqW6t9LArErsGlnjd7ofe4sUqU6pwJDz59WFtf6J+Hb4d99C3Z7LzfPxkv35NiawImU5+EUxJCgxLGSBSQaUDCiIQzE4c6NOBUcltmAv3bxI12y1yJx3WSYu8OTgPYlmpVmO55XhSxLGSl57pZItg8BftGVfJZdHpoysTTHHqGETbbFZUS+47JRPxZyJVQ1QGGmF7PgH/of+46qaTGtuxksRmXmNOzODpuAYaGwfgCvJ0RvCnhMWETBJ6FJLRvg/ecpSIdxfuJ+Z5vXQdvn94Z4LY/QFFkwHDzKSu1lAtZJsW0tUYtzghLO+aLlGuD7j9CqYO5rpKtkn3sxT5XXzvtv9HVb45vPid741p/HJqPEgL+2v3oTsPvdp7+aia+mLeN3FapeAUdczmrpFoI9co4rx6HyL16CipRJCr/3r87r55Td4REAeGcRBGSKJoQPsz6Rdy5/s1jO2cywbFuRgX6nlSwelQIzqSC3adCcDsqeGdSwb0rKngDKkD5cAmzCIMGJ2FIWEymDKsJWqCeeySkutbMdG33dRfVRaeeVU8Ht7QAIKKnCloLpcE9tTidxYCBHuEzIxu4hcvzEJepK1wUYhogmksWcDsLUCyPrPECfDJcd3U0Ni7mWM/ioLsguFiHrrcVso6a8Ug002WW4iyIfsxPjWjPk57HdZ0OYTjDBsQFvnHYWBXH8D3cr9hsclnqceCF4fxLUPA6KDBUZ9EU3cOmoY27cM3KgDFEixv0MNGO9SjUp1jK5xxiifVQR8dCSaRPALoLEBjs4honht5xgWMeA/RbHxpYD4JqMRnm5DWUUrOTURh+5r5OinxRQrsQc3UbIXMH9zcdKXsc88IpJiKM9amrz8dMsy/Q5+PN0giQqHE8UwBQgEyMELgcXkWh6VjlWVbXB7HJfyRP2hVewGuZl0pH7kfEn6AvKAmbujpcqbS67klu/GFu+GhqblVZg3MUSUsofUXgVXnk0Usc6VPJsOyjp8/qp88ZSZ8d/Mr8HUXC/4+EsdPNzpRGTX7uRBs1++6xUishVAFQAe2frjvXfye49N3rjPOuT+Z7fyc0h/xNNjTH707Y0Oz7hacCRfUfcV2kalk5YEpH0I4JuY7cPcrzWk2PuNIqBURq+zLpSFOojKFvukLnPAl/gZr+bxbnVIP3dj8s7UwXby3GeUT4qE7/INr3Hu6ksX+cDo8VRc0baTkMyQCUEWum3y76nOqXSnN4P7qMsk+yxEey5P5SGcVHk/QmDz58Jk7eI95IJsbpcnEi4LP9xUj3dX52c6f/Ag== -------------------------------------------------------------------------------- /blogs/algorithm/leetcode/20.valid-parentheses.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: LeetCode-20.有效的括号 3 | date: 2021-03-05 4 | tags: 5 | - LeetCode 6 | categories: 7 | - 算法 8 | --- 9 | 10 | ## 题目描述 11 | 12 | 给定一个只包括 `'(',')'`,`'{','}'`,`'[',']'` 的字符串,判断字符串是否有效。 13 | 14 | 有效字符串需满足: 15 | 16 | 1. 左括号必须用相同类型的右括号闭合。 17 | 2. 左括号必须以正确的顺序闭合。 18 | 19 | 注意空字符串可被认为是有效字符串。 20 | 21 | **示例 1:** 22 | 23 | ```md 24 | 输入: "()[]{}" 25 | 输出: true 26 | ``` 27 | 28 | **示例 2:** 29 | 30 | ```md 31 | 输入: "([)]" 32 | 输出: false 33 | ``` 34 | 35 | **示例 3:** 36 | 37 | ```md 38 | 输入: "{[]}" 39 | 输出: true 40 | ``` 41 | 42 | ## 解法一之栈 43 | 44 | **使用栈记录左括号,循环时如果是左括号就入栈,如果是右括号就出栈,最后栈里面没有元素就是有效的** 45 | 46 | 1. 可以先判断下是不是偶数,如果不是那肯定是无效的。 47 | 2. 然后可以创建一个栈,和一个左括号和右括号的映射表。 48 | 3. 当遍历的时候,如果遇到左括号就入栈(`push`)一个字符;如果遇到右括号就出栈(`pop`)一个字符,并且取出这个字符对应的右括号判断和当前的右括号是否一致,不一致直接返回。 49 | 4. 最后当遍历结束时,判断栈中还有没有字符。有则返回 `false`,没有返回 `true`。 50 | 51 | **代码实现** 52 | 53 | ```js 54 | /** 55 | * @param {string} s 56 | * @return {boolean} 57 | */ 58 | var isValid = function (s) { 59 | // 判断是否是偶数 60 | if (s.length % 2 !== 0) return false; 61 | 62 | // 创建一个栈,和左右括号对应的字典 63 | let stack = []; 64 | let obj = { "(": ")", "{": "}", "[": "]" }; 65 | 66 | for (let i = 0; i < s.length; i++) { 67 | let k = s[i]; 68 | if (k in obj) { 69 | // 如果是左括号,入栈。 70 | stack.push(k); 71 | } else { 72 | // 如果不是左括号,那么按理说应该开始闭合标签了。 73 | // 取出栈中的最后一个字符的右括号和当前字符对比,如果不匹配直接结束。 74 | if (obj[stack.pop()] !== k) { 75 | return false; 76 | } 77 | } 78 | } 79 | 80 | // 如果栈中没有字符,那么就是有效的 81 | return !stack.length; 82 | }; 83 | // =================================================================== 84 | // ========================== @test ================================== 85 | // =================================================================== 86 | console.log(isValid("()")); // true 87 | console.log(isValid("()[]{}")); // true 88 | console.log(isValid("([)]")); // false 89 | ``` 90 | -------------------------------------------------------------------------------- /blogs/algorithm/leetcode/71.simplify-path.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: LeetCode-71.简化路径 3 | date: 2021-03-07 4 | tags: 5 | - LeetCode 6 | categories: 7 | - 算法 8 | --- 9 | 10 | ## 题目描述 11 | 12 | 以 Unix 风格给出一个文件的绝对路径,你需要简化它。或者换句话说,将其转换为规范路径。[Linux/Unix 中的绝对路径 vs 相对路径](https://blog.csdn.net/u011327334/article/details/50355600) 13 | 14 | 请注意,返回的规范路径必须始终以斜杠 `/` 开头,并且两个目录名之间必须只有一个斜杠 `/`。最后一个目录名(如果存在)不能以 `/` 结尾。此外,规范路径必须是表示绝对路径的最短字符串。 15 | 16 | **示例 1:** 17 | 18 | ```md 19 | 输入:"/home/" 20 | 输出:"/home" 21 | 解释:注意,最后一个目录名后面没有斜杠。 22 | ``` 23 | 24 | **示例 2:** 25 | 26 | ```md 27 | 输入:"/../" 28 | 输出:"/" 29 | 解释:从根目录向上一级是不可行的,因为根是你可以到达的最高级。 30 | ``` 31 | 32 | **示例 3:** 33 | 34 | ```md 35 | 输入:"/home//foo/" 36 | 输出:"/home/foo" 37 | 解释:在规范路径中,多个连续斜杠需要用一个斜杠替换。 38 | ``` 39 | 40 | **示例 4:** 41 | 42 | ```md 43 | 输入:"/a/../../b/../c//.//" 44 | 输出:"/c" 45 | ``` 46 | 47 | ## 解法一之栈 48 | 49 | **使用栈记录每一步的操作,根据操作分别入栈和出栈,最后再拆分成字符串** 50 | 51 | 1. 首先应该做的是把字符串以 `/` 拆分成数组依次遍历,其中的元素可能是以下几种情况。 52 | 2. 如果是 `".."` 是返回上一层,那么出栈一个。 53 | 3. 如果是 `""` 或者 `"."` 不影响层级,不变。 54 | 4. 如果是其他的正常字符,则把当前字符入栈。 55 | 5. 最后把数组以 `"/"` 拆分成字符串并在前面加上 `"/"` 表示绝对路径。 56 | 57 | **代码实现** 58 | 59 | ```js 60 | /** 61 | * @param {string} path 62 | * @return {string} 63 | */ 64 | var simplifyPath = function (path) { 65 | let stack = []; 66 | let paths = path.split("/"); 67 | 68 | for (let i = 0; i < paths.length; i++) { 69 | let p = paths[i]; 70 | if (p === "..") { 71 | // 如果是 ".." 回到到上一层,则出栈一层 72 | stack.pop(); 73 | } else if (p != "" && p != ".") { 74 | // "" 和 "." 不管,其他入栈一层 75 | stack.push(p); 76 | } 77 | } 78 | 79 | // 最后拆分成字符串,并以 "/" 开头 80 | return `/${stack.join("/")}`; 81 | }; 82 | // =================================================================== 83 | // ========================== @test ================================== 84 | // =================================================================== 85 | console.log(simplifyPath("/a/./b/../../c/")); // "/c" 86 | console.log(simplifyPath("/a//b////c/d//././/..")); // "/a/b/c" 87 | console.log(simplifyPath("../../")); // "/" 88 | ``` 89 | -------------------------------------------------------------------------------- /.vuepress/public/examples/nest-lifecycle-events/index.js: -------------------------------------------------------------------------------- 1 | import { code } from "./code.js"; 2 | import Knowcess from "../../lib/knowcess/knowcess.js"; 3 | const html = Prism.highlight(code, Prism.languages.javascript, "javascript"); 4 | 5 | const knowcess = new Knowcess({ 6 | root: "#demo", 7 | code: `
${html}
`, 8 | direction: "horizontal", 9 | linePosition: 12, 10 | }); 11 | const logStack = knowcess.createStack("Log"); 12 | 13 | knowcess 14 | .step(() => { 15 | knowcess.showLine().moveLine(6).showCommentary("Nest 程序执行解析模块依赖。"); 16 | }) 17 | .step(() => { 18 | knowcess.moveLine(7).updateCommentary("模块依赖解析完成。"); 19 | logStack.push("Init completed"); 20 | }) 21 | .step(() => { 22 | knowcess 23 | .moveLine(42) 24 | .updateCommentary("所有模块依赖解析完成后调用,遍历所有的 onModuleInit 钩子并等待执行完成。"); 25 | logStack.push("onModuleInit"); 26 | }) 27 | .step(() => { 28 | knowcess 29 | .moveLine(46) 30 | .updateCommentary( 31 | "所有模块初始化完成后调用,遍历所有的 onApplicationBootstrap 钩子并等待执行完成。" 32 | ); 33 | logStack.push("onApplicationBootstrap"); 34 | }) 35 | .step(() => { 36 | knowcess.moveLine(12).updateCommentary("开始监听服务。"); 37 | logStack.push("Listener start"); 38 | }) 39 | .step(() => { 40 | knowcess.moveLine(16).updateCommentary("当显式调用(app.close())或接收到终止信号。"); 41 | logStack.push({ text: "Close", style: { background: "red", textAlign: "center" } }); 42 | }) 43 | .step(() => { 44 | knowcess 45 | .moveLine(50) 46 | .updateCommentary("收到终止信号后调用,遍历所有的 onModuleDestroy 钩子并等待执行完成。"); 47 | logStack.push("onModuleDestroy"); 48 | }) 49 | .step(() => { 50 | knowcess 51 | .moveLine(54) 52 | .updateCommentary( 53 | "在所有 onModuleDestroy 执行完成后调用,遍历所有的 beforeApplicationShutdown 钩子并等待执行完成。" 54 | ); 55 | logStack.push("beforeApplicationShutdown"); 56 | }) 57 | .step(() => { 58 | knowcess 59 | .moveLine(58) 60 | .updateCommentary("服务关闭后调用,遍历所有的 onApplicationShutdown 钩子并等待执行完成。"); 61 | logStack.push("onApplicationShutdown"); 62 | }); 63 | -------------------------------------------------------------------------------- /blogs/algorithm/leetcode/203.remove-linked-list-elements.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: LeetCode-203.移除链表元素 3 | date: 2021-03-11 4 | tags: 5 | - LeetCode 6 | categories: 7 | - 算法 8 | --- 9 | 10 | ## 题目描述 11 | 12 | 删除链表中等于给定值 `val` 的所有节点。 13 | 14 | **示例 1:** 15 | 16 | ```md 17 | 输入: 1->2->6->3->4->5->6, val = 6 18 | 输出: 1->2->3->4->5 19 | ``` 20 | 21 | ## 解法一之遍历和哨兵节点 22 | 23 | **因为链表是通过指针连接的,所以我们只需要让需要删除的节点的上一个节点和下一个节点连接起来,这样链条上就删除了这个节点** 24 | 25 | 举例 `1->2->6->3->4->5` 要删除值为 `2` 的节点。 26 | 27 | 1. 应该先判断 `1` 的下一个节点是不是 `2`。 28 | 2. 如果是就让 `1` 的指针指向下一个节点(需要删除的)的下一个节点 `1->2->6->3->4->5` => `1->6->3->4->5`。 29 | 3. 如果不是就从下一个节点开始继续遍历。 30 | 31 | **哨兵节点** 32 | 33 | 上面的逻辑有个问题,就是会跳过头节点的判断。因为一开始就是判断的当前节点(1)的下一个节点(2)。 34 | 35 | 所以解决方案就是让第一个节点变成第二个节点,临时定义一个新的头节点,称为哨兵节点。`1->2->6->3->4->5` => `0-1->2->6->3->4->5`。 36 | 37 | 最后在返回的时候去除哨兵节点即可。 38 | 39 | **代码实现** 40 | 41 | ```js 42 | /** 43 | * @param {ListNode} head 44 | * @param {number} val 45 | * @return {ListNode} 46 | */ 47 | var removeElements = function (head, val) { 48 | // 定义一个哨兵节点,便于遍历 49 | let ele = { 50 | next: head, 51 | }; 52 | let cur = ele; 53 | 54 | while (cur.next) { 55 | // 依次判断下一个节点的值是不是等于输入值 56 | if (cur.next.val === val) { 57 | // 让当前节点的指针指向下一个节点的下一个节点 58 | cur.next = cur.next.next; 59 | } else { 60 | cur = cur.next; 61 | } 62 | } 63 | 64 | return ele.next; 65 | }; 66 | 67 | // =================================================================== 68 | // ========================== @test ================================== 69 | // =================================================================== 70 | let ListNode = { 71 | val: 1, 72 | next: { 73 | val: 2, 74 | next: { 75 | val: 3, 76 | next: { 77 | val: 2, 78 | next: null, 79 | }, 80 | }, 81 | }, 82 | }; 83 | 84 | function print(head) { 85 | let cur = head; 86 | let link = []; 87 | while (cur) { 88 | link.push(cur.val); 89 | cur = cur.next; 90 | } 91 | return `长度为 ${link.length} : 值为 ${link.join("->")}`; 92 | } 93 | 94 | console.log(print(removeElements(ListNode, 2))); // 长度为 2 : 值为 1->3 95 | ``` 96 | -------------------------------------------------------------------------------- /.vuepress/config.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from "path"; 2 | import { defineUserConfig } from "vuepress"; 3 | import { viteBundler } from "@vuepress/bundler-vite"; 4 | import recoTheme from "vuepress-theme-reco"; 5 | 6 | export default defineUserConfig({ 7 | lang: "zh-CN", 8 | title: "HaiWeiLian's Blog", 9 | description: "HaiWeiLian's Blog", 10 | head: [["link", { rel: "icon", href: "/favicon.ico" }]], 11 | alias: { 12 | "@vicons/carbon": resolve(__dirname, "icons"), 13 | }, 14 | bundler: viteBundler({}), 15 | theme: recoTheme({ 16 | style: "@vuepress-reco/style-default", 17 | logo: "/logo.png", 18 | author: "haiweilian", 19 | authorAvatar: "/head.png", 20 | docsRepo: "https://github.com/haiweilian/blogs", 21 | docsBranch: "master", 22 | editLinkText: "编辑此页面", 23 | lastUpdatedText: "最后更新时间", 24 | navbar: [ 25 | { text: "首页", icon: "Home", link: "/" }, 26 | { 27 | text: "分类", 28 | icon: "Categories", 29 | children: [ 30 | { 31 | text: "分类", 32 | children: [ 33 | { text: "前端", link: "/categories/qianduan/1" }, 34 | { text: "后端", link: "/categories/houduan/1" }, 35 | { text: "源码分析", link: "/categories/yuanmafenxi/1" }, 36 | ], 37 | }, 38 | ], 39 | }, 40 | { text: "标签", icon: "Tag", link: "/tags/JavaScript/1" }, 41 | { text: "项目", icon: "Code", link: "/code" }, 42 | { text: "归档", icon: "Calendar", link: "/timeline" }, 43 | { text: "留言板", icon: "Chat", link: "/message" }, 44 | { text: "关于我", icon: "User", link: "/about" }, 45 | ], 46 | algolia: { 47 | appId: "94TMHLGIH3", 48 | apiKey: "75161c78510900b859429e0122e77a5b", 49 | indexName: "haiweilian", 50 | insights: true, 51 | }, 52 | commentConfig: { 53 | type: "giscus", 54 | options: { 55 | repo: "haiweilian/blogs", 56 | repoId: "MDEwOlJlcG9zaXRvcnkyNDQxMTI5Nzc=", 57 | category: "Announcements", 58 | categoryId: "DIC_kwDODozeUc4Caz_p", 59 | mapping: "pathname", 60 | }, 61 | }, 62 | }), 63 | }); 64 | -------------------------------------------------------------------------------- /.vuepress/components/OpenSourceCode.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 27 | 28 | 88 | -------------------------------------------------------------------------------- /datas/security/auth/OAuthGitHub.drawio: -------------------------------------------------------------------------------- 1 | 7Vtbc6s2EP41eowHEAjxCLaTM23PTDLJtMnTGQyyTYMtF8uJfX59JRAGIsUmx/jWaV4irS6RvtW3u1oRAPuz9V0WLqbfaUxSYBnxGsABsCzLtFz+S0g2hQSZRiGYZElciMxK8Jj8JFJYdlslMVk2OjJKU5YsmsKIzuckYg1ZmGX0vdltTNPmX12EE6IIHqMwVaV/JTGbSqmJvKrhG0kmU/mncbnhWVh2ljtZTsOYvtdEcAhgP6OUFaXZuk9SAV6JSzHu9pPW7cIyMmdtBnx/ipJojGcPv//2HCRv9y/rh5sbOctbmK7khsHQAf4Q+LyAAMYgcMHQBX4A/Fu5D7Ypwcnoah4TMb8JYPA+TRh5XISRaH3nx4HLpmyWyuZxkqZ9mtIsHwvjkOBxxOVLltFXUmtBESajMW9Rd1gul2SMrGsiueM7QmeEZRveRbZiCb48fbasvtdUWepnWtNi2S+Up2eynbgCmBckxl/A29LgjYDXB3gggMc+8M3j4D3GEYm0eI+wYztGN3hDtB9wdErAoQL4XcK+rUY62B3gecDHO2A3vgw7irGLbB3s0HdsmI+gc1ZXVP7TjTrQGc//z1l/kvwDk9e3P+lrnMQPT/HLjQUVdEnMDa6s0oxN6YTOw3RYSYMm/lWfPyhdSNT/JoxtpPcIV4w2dULWCXuulV/EVD1H1gZrOXNe2ZSVOd/vc71SjDKRWwqqgXmtMfKeZAmHjGRSWGxb7PVT8y1FS7rKIrIDw9INhtmEsF1Y649GRtKQJW/NdXSuaI1jsVA4E2SZj5aLHBGjO5Eg8S3wOIm5x3Jyp4VzN2bnhVuATeXgfcmahmkymfNySsZsq06FiBqFtnVNWx7u4yY+FjehdU5uGg1uui3JaV4aOa2W5MQHkjMf6mdZuKl1WNBkzpa1me+F4HPvDJshIy8UM1ZnaLu0A0y+Ygk4SyHgx5hzckfwI5s8MLRBEAA8FLeBhE257+bL5t08LoeS3kWU6vkA2+IykCZccT/4skRP7tYtgPuVfEmijCtHLkIYplRwepTx0qRg92VJrHaAcRj6YqOijwECX/QJODwohxCL/nVsqv4ewK6ck9tMLx/IZ/DymEjcAYwm9opp5TPwlXDDywveIL88bMG9LKv7kQJas6uLUI8XE0HVNZ0tJmprd2+MnmHgD8bXtM9sfHFL4+udw/haxodYHJ/A+GLF+H6Nxgext/sgaVvfFyQdka3u5URJvxYkOXtY2iEhvZaEhPY5GPnxdoxOEQ7ZzuWcH8+2j3KCxCRHNPOtTxU67qlq2JEOjob3mbHWWGolDDy3pVZSTW1zTduIrPtkk61ePXqtUgXVpQKBwNSFsRwopo9VIw6YOPWBgDOJwtSXDbMkjgs2k2XyMxzlUwlqyBPF53UC4AzEXJzAy4LLHanHgk4z7tWpx9B50qM5UkPRjtXbdRe5ehU4TRVAQ1WB9uZxNA2oyXHYjh/XrgqIm6qwkaoK95SqsO2zRgXbSPKl1qKPCTr049Bu68idAx35YTRR3cjONAy3VwHwhjl/DIBh/pDK+eNIavl1CcrTWCiiMTnMhR8/VYI09upoj3l6llzh61HP+BA8uxY8c5qkDAWOnic5jHaOhnZ51lc80TZzvFagZHe30pxZ2oRxnlcWbs0VaWaRIb5wAuqyH6cloPo4/n/y49MDjFrSzHbPyjOk4RkW/klwpAgGUcW86qsgTp/b3PMhERJKz2fJBwjfEHG8DBjRpTPL1cR/p2UWVDO1dq8WQmzhV6KLa4/FHdSMxXVhxmljcUtRhdP7VRdyfdrY73PwSbWhmidUaAML+8MVUlHiP68NnZ06rTZcfVDmCeTFc/MAeI70EZ4hb0fegV8ydgAkbmHvT/ohnK0me91e+dye5x250+UGfv+T3LWdacvZr4uT2vtyPTVVPAlMrx3oG8tqAG2ajgK03Q3QvFp9yF48h1T/DgCH/wI= -------------------------------------------------------------------------------- /datas/security/auth/TokenJwt.drawio: -------------------------------------------------------------------------------- 1 | 7VpZb9s4EP41BNqHDXRRIh8lH7soeiyQYtt9VCzGVqqYWZlp4v76HR66acVJ7MRZLBC05JAckt8MPw5HRv7k+v73Mr1ZfeIZK5DnZPfInyLPcwMvhP+kZKslxAm0YFnmmenUCM7zX8wIHSO9zTO26XQUnBciv+kKF3y9ZgvRkaVlye+63S550Z31Jl2ygeB8kRZD6bc8EysjdUPaNPzB8uXKTE28SDdcp1Vns5PNKs34XUvkz5A/KTkXunR9P2GFBK/CRY+b72itF1aytdhnwPnVX26yYdk/Xz58unI/f5lcsc+/GS0/0+LWbBjNMIpnKIZCiAhBSYRmEYoTFM/NPsS2Aqfkt+uMSf0u8pO7VS7Y+U26kK134A4gW4nrwjRf5kUx4QUv1Vg/Sxm5XIB8I0r+g7VawgVhF5fQYtbGSsHud27araEEH2T8molyC13MgMCAv+1W71qmDI1s1bJi1S813rOsFTcAQ8Fg/Ai8PQveIaITRKYSeBKj2D0O3pdkwRZWvC8IDrBzGLxxeGKARwMQWQYH3FR5KVZ8yddpMWukSQOzRKXp85HzGwPuFRNia9gqvRW8Cz27z8X3VvlvqerMw6Y6vTeqVWVbVdaw3+/tSnuYrDfjVK0aqHcotzVuNECB35YLNoKWbxg2LZdMjPQL7E5QsiIV+c/uOg5uUtd/TZs6Z7hlVXdPk57hjlFPz6L4NS3qW1gxQpSiJJGsmEwRxc8jw8NfJa63J7V5zrG4zXUHoLzkQeiQWzRObgf06OAteHQw8Gi41n1EHETcsQhrFkhvT+bK/wMUk1ah7owRdUzAQD1EJkrhHJFQ6dFTECmRnUOUuIjYDpRZD6wzLADI5KKE0lIoa/UlXmv1u+MV2QQbCFUTrNWxrQOqE7VoiuJYFVQfvegEtqqHz81W+xt7xKL91qKHkyk4AUi9DTqvcJ21ljiy1UD2obGSgMVmZtHUVU1EzqsLMK8swKhAdiZUadZTkK8Qiq2rGm3NjOWu5cyhnBn+PAUmeAZYsVbsOVqB57Smh20kiM6OFsinRb5cQ7lgl+I4UeMpcOv/geNjSBnvScrha5IyfiopD2itOndtptDHVKshvioAzRJ1HieKwGEoNlxIpyjG3eM+r2glUU16zlkz3HMmnP/ImTrsmrxCqVMuH3bmfOSLVEJwLngpkxqyG3BNpPQ/kepHMZE8qgsTRY2KPun4BTFyHbRg6vfBkjvhajC8p1gROujLTE7h2Ei3vh1H1gNKYnPjwE41ncO/JKjsp5YBTQnuXguOHCivUqKGR9KixHlgrv5FUc1Vu1NtptPi517o64evT8/kzYS+NT27XXqOXpCew7cQM4ej9Hy8qHOE5IZ6akalisO9HWSw41g/zL5Pk/Q4O1RLDWUQSK1bHsa+OrQMOtEj9Q3gsDvZdKLs1I8eT4GeHEtGY48nWR9ZAEXY4VsAOKwEgYQuh5s/Ng3XeZZptmOb/Fd6oVRJOrnh+VqojeIE4anUBQS30Vx3oByJ7/cCeTw0hd0SxzKELeE+8PX6GfXW4fdwB37PAn/0ovDTvc7BgEL/MwZxe9RELdTkvKRFPBsz7Zkc6V4Hb942Xtc2gcU29EVNM0yDdwMgeOYkVNlmKkMWudltwdPsnaK0qfyT70EoRO9NSki/gMBObFEy8U6FNpF6lVLZFuP3rSdUnWzDMshJYsujSn593/Ol4rxGLEB6T5X6im8Z1bMdOPd4Zh3mgj98+1ql7LrIPwvN3gdf5maYRcjywZeGkZ+GxwHctQRfVsCP94l9PM1zrGMkO0YqiRC1DpBmTWJutibxq1M+MxP8Veqv7kR9wrQ+k4uIVI5lUh1I2slykHl1iWL1+GllMPqTh3LNcZXqlm+V+oQTteS9ovyTONm+JUdsP9nh0TyNDO/SQVZincXyZ0jyGizSzSbXP8FISzEUP5hpOP1Eg/fMXxC0jIkttqxkeyckzAx/ynii8aWod/XXQXGlQu/TjGq8ZKgo6inq3yIah4Ei5W71tp/hgZb4+lAeuOOrwkPO9KRPGAf0wH1TXd6OXNcLeWD/d1LkiR5Iei+MwD+UB0K1+XGi7t78xNOf/Qs= -------------------------------------------------------------------------------- /.vuepress/public/bg.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /blogs/algorithm/leetcode/1.two-sum.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: LeetCode-1.两数之和 3 | date: 2021-03-04 4 | tags: 5 | - LeetCode 6 | categories: 7 | - 算法 8 | --- 9 | 10 | ## 题目描述 11 | 12 | 给定一个整数数组 `nums` 和一个目标值 `target`,请你在该数组中找出和为目标值的那两个整数,并返回他们的数组下标。 13 | 14 | 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。 15 | 16 | **示例 1:** 17 | 18 | ```md 19 | 给定 nums = [2, 7, 11, 15], target = 9 20 | 21 | 因为 nums[0] + nums[1] = 2 + 7 = 9 22 | 23 | 所以返回 [0, 1] 24 | ``` 25 | 26 | ## 解法一之暴力破解 27 | 28 | **直接暴力破解,双层循环,两数相加判断。** 29 | 30 | 分别拿两次循环的数字,判断是不是目标值,并且判断不是同一个元素。 31 | 32 | **代码实现** 33 | 34 | 我们看到嵌套循环,应该立马就可以得出这个算法的时间复杂度为 O(n2)。 35 | 36 | ```js 37 | /** 38 | * @param {number[]} nums 39 | * @param {number} target 40 | * @return {number[]} 41 | */ 42 | var twoSum = function (nums, target) { 43 | for (let i = 0; i < nums.length; i++) { 44 | for (let j = 0; j < nums.length; j++) { 45 | // 双层循环,两数相加判断 && 判断不是同一个元素 46 | if (nums[i] + nums[j] === target && i != j) { 47 | return [i, j]; 48 | } 49 | } 50 | } 51 | }; 52 | // =================================================================== 53 | // ========================== @test ================================== 54 | // =================================================================== 55 | console.log(twoSum([2, 7, 11, 15], 9)); // [ 0, 1 ] 56 | console.log(twoSum([2, 7, 2, 4], 4)); // [ 0, 2 ] 57 | ``` 58 | 59 | ## 解法二之对象记录 60 | 61 | **使用对象记录当前元素的目标值和当前索引,下次再循环如果对象中包含此值,那么此值就是目标值** 62 | 63 | 1. 每当循环一个数的时候,我们可以知道需要的目标值是几 `(target - num = 目标值)`。 64 | 2. 如果没找到,就把目标值当成 `key`,当前索引当成 `value`,存进对象里。 65 | 3. 那么下次循环的时候,当前值在对象里,那么就找到了目标值取出索引与当前索引一起返回。 66 | 67 | **代码实现** 68 | 69 | 这次只循环了一次,时间复杂度 O(n),空间复杂度 O(n),典型用空间换时间。 70 | 71 | ```js 72 | /** 73 | * @param {number[]} nums 74 | * @param {number} target 75 | * @return {number[]} 76 | */ 77 | var twoSum = function (nums, target) { 78 | let obj = {}; 79 | for (let i = 0; i < nums.length; i++) { 80 | let num = nums[i]; 81 | 82 | // 如果当前值在对象,取出索引。 83 | if (num in obj) { 84 | return [obj[num], i]; 85 | } 86 | 87 | // 如果没找到,就把目标值当成 key,当前索引当成 value,存进对象里。 88 | // 如果此次循环是 2, 那么我的目标值就是 7 索引是 1。 89 | obj[target - num] = i; 90 | } 91 | }; 92 | // =================================================================== 93 | // ========================== @test ================================== 94 | // =================================================================== 95 | console.log(twoSum([2, 7, 11, 15], 9)); // [ 0, 1 ] 96 | console.log(twoSum([2, 7, 2, 4], 4)); // [ 0, 2 ] 97 | ``` 98 | -------------------------------------------------------------------------------- /blogs/node/tinylib-analysis/promisify.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: promisify 源码分析-将基于 Callback 的函数转换 Promise 3 | date: 2021-12-22 4 | tags: 5 | - Node 6 | categories: 7 | - 后端 8 | - 源码分析 9 | --- 10 | 11 | ## 前言 12 | 13 | 在 `node` 的工具函数有 [util.promisify](http://nodejs.cn/api/util/util_promisify_original.html) 函数将基于 `Callback` 的函数转换 `Promise` 使用。因为 `node` 中有依赖不方便观看,所有找了一个和它实现基本一致的 [es6-promisify](https://github.com/mikehall314/es6-promisify) 单独的实现。那么来看看怎么实现一个这样的函数吧。 14 | 15 | ## 源码 16 | 17 | 总体概括高阶函数的应用,首先就是包装原始函数,并返回一个 `Promise`,然后接管原始函数的 `callback` 参数,当原始函数调用 `callback` 的时候,就可以根据返回的信息去调用 `Promise` 的 `resolve` 或 `reject`。 18 | 19 | ### 示例 20 | 21 | 一个简单的示例。 22 | 23 | ```js 24 | import { promisify } from "../lib/promisify.js"; 25 | 26 | // 原始函数 27 | function load(src, callback) { 28 | setTimeout(() => { 29 | callback(null, "name"); 30 | }); 31 | } 32 | 33 | // 返回一个内部函数 34 | const loadPromise = promisify(load); 35 | 36 | // 调用函数并返回一个 Promise 37 | loadPromise("src") 38 | .then((res) => { 39 | console.log(res); // name 40 | }) 41 | .catch((err) => { 42 | console.log(err); 43 | }); 44 | ``` 45 | 46 | ### 实现 47 | 48 | 核心实现处理了单参数和多参数的实现。 49 | 50 | ```js 51 | export function promisify(original) { 52 | // 判断是否是一个函数 53 | if (typeof original !== "function") { 54 | throw new TypeError("Argument to promisify must be a function"); 55 | } 56 | 57 | // 多个参数自定义的参数名称 58 | const argumentNames = original[customArgumentsToken]; 59 | 60 | // 自定义的 Promise 或者 原生 Promise 61 | const ES6Promise = promisify.Promise || Promise; 62 | 63 | // promisify(load) 返回一个函数,执行这个函数返回一个 Promise。 64 | return function (...args) { 65 | return new ES6Promise((resolve, reject) => { 66 | // 忘 args 里追加 callback,那么在普通函数执行回调的时候,就是直接追加的这个函数 67 | args.push(function callback(err, ...values) { 68 | // 根据 node 错误优先的写法,判断是否有错误信息 69 | if (err) { 70 | return reject(err); 71 | } 72 | 73 | // 如果参数剩余参数只有一个或者没有指定参数名称返回第一个 74 | if (values.length === 1 || !argumentNames) { 75 | return resolve(values[0]); 76 | } 77 | 78 | // 如果指定了参数名称,转化为对象返回。 79 | const o = {}; 80 | values.forEach((value, index) => { 81 | const name = argumentNames[index]; 82 | if (name) { 83 | o[name] = value; 84 | } 85 | }); 86 | 87 | resolve(o); 88 | }); 89 | 90 | // 执行调用函数 91 | original.apply(this, args); 92 | }); 93 | }; 94 | } 95 | ``` 96 | 97 | ## 总结 98 | 99 | 1. 虽然代码很少,函数的灵活性运用的很巧妙。 100 | -------------------------------------------------------------------------------- /blogs/share/about-vuepress-plugin-vmi.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 在 VuePress 中实现接近 Dumi Demo 的体验 3 | date: 2022-06-12 4 | categories: 5 | - 技术分享 6 | --- 7 | 8 | ## 前言 9 | 10 | [项目地址](https://github.com/bfehub/vmi) 11 | 12 | [详细文档及快速预览](https://bfehub.github.io/vmi/zh/guide/) 13 | 14 | 在 `react` 体系中大部分都会选择 `dumi` 作为写文档的工具。 15 | 16 | 而在 `vue` 体系中大部分都会选择 `VuePress` 或者 `VitePress`,但它们都提供了很强大的文字的编写能力,但没有提供 `demo` 的演示能力。 17 | 18 | 社区中有很多插件的解决方案但没有无限接近 `dumi` 的 `demo` 体验。所以产生了通过一系列插件去实现 `dumi` 体验的想法,同时又能保持 `VuePress` 强大的文档编写能力和极速的热更新。选择 `VuePress` 的原因是它的扩展能力,而且也支持使用 `Vite` 作为服务。 19 | 20 | 目前实现了以下特性: 21 | 22 | - 强大的 demo 演示能力,支持多种展示模式。 23 | 24 | - 支持页面路径映射,自定义组织组件文档。 25 | 26 | - 支持翻译缺失,自动生成缺失语言的页面。 27 | 28 | ## 组件演示 29 | 30 | 基本上实现了 `dumi` 的所有的配置和功能。 31 | 32 | - 包括但不限于 `inline`、`iframe` 、`debug` 模式。 33 | 34 | - 用做展示的 `title`、`desc` 等属性的配置。 35 | 36 | - 展示组件导入的子文件。 37 | 38 | 上图看效果吧,还有所有的配置项。 39 | 40 | ![01](./image/vnext-netease-music/01-light.png) 41 | 42 | ![02](./image/vnext-netease-music/02-dark.png) 43 | 44 | ![03](./image/vnext-netease-music/03-toc.png) 45 | 46 | ## 页面映射 47 | 48 | 通常在编写组件的文档的时候我们可能会把组件的文档直接和组件的实现放在一起,这时候 `VuePress` 不能很好的处理路径。此功能对应 `dumi` 的标准文档结构。 49 | 50 | 如果有以下目录结构。 51 | 52 | ```sh 53 | ├── docs 54 | │ ├── README.md 55 | │ ├── components // 介绍组件的文档 56 | │ │ └── README.md 57 | ├── packages 58 | │ ├── components 59 | │ │ ├── npm-badge 60 | │ │ │ ├── docs // 具体组件的文档 61 | │ │ │ │ ├── index.md 62 | │ │ │ │ └── index.zh-CN.md 63 | │ │ │ ├── examples 64 | │ │ │ │ └── basic.vue 65 | │ │ │ └── src 66 | │ │ │ └── npm-badge.vue 67 | ``` 68 | 69 | 以上组件的文档的 _访问路径_ 和 _临时文件_ 的路径就会被转换成。 70 | 71 | `/packages/components/npm-badge/docs/index.md` => `/components/npm-badge/index.md` 72 | 73 | `/packages/components/npm-badge/docs/index.zh-CN.md` => `/zh/components/npm-badge/index.md` 74 | 75 | ## 翻译缺失 76 | 77 | 将默认语言的文档作为未翻译语言的兜底文档,对应 `dumi` 的翻译缺失功能。 78 | 79 | 如果有以下目录结构。 80 | 81 | ```sh 82 | ├── docs 83 | │ ├── guide 84 | │ │ └── page-missing.md 85 | │ └── zh 86 | │ │ └── guide 87 | │ │ ├── # 缺失的 page-missing.md 页面 88 | │ components 89 | │ ├── npm-badge 90 | │ │ ├── docs 91 | │ │ │ ├── index.md 92 | │ │ │ └── # 如果使用 *页面映射* 插件同样支持生成 index.zh-CN.md 页面 93 | ``` 94 | 95 | 那么就会自动生成 `/zh/guide/page-missing.html` 和 `/zh/components/npm-badge/index.html` 的页面和路由。 96 | 97 | ## 结尾 98 | 99 | [项目地址](https://github.com/bfehub/vmi) 100 | 101 | [详细文档及快速预览](https://bfehub.github.io/vmi/zh/guide/) 102 | 103 | 其实还有其他功能可以同步过来使用,比如 `移动端组件研发` 和 `组件 API 自动生成` 这部分还在思考中有兴趣的伙伴们多多参与。 104 | -------------------------------------------------------------------------------- /blogs/algorithm/leetcode/206.reverse-linked-list.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: LeetCode-206.反转链表 3 | date: 2021-03-12 4 | tags: 5 | - LeetCode 6 | categories: 7 | - 算法 8 | --- 9 | 10 | ## 题目描述 11 | 12 | 反转一个单链表。 13 | 14 | **示例 1:** 15 | 16 | ```md 17 | 输入: 1->2->3->4->5->NULL 18 | 输出: 5->4->3->2->1->NULL 19 | ``` 20 | 21 | **进阶:** 22 | 23 | 你可以迭代或递归地反转链表。你能否用两种方法解决这道题? 24 | 25 | ## 解法一之迭代 26 | 27 | **在遍历链表时,让当前的节点的指针指向上一个节点,因为当前节点指向了上一个节点切断了链表的引用,所有还要提前存储当前节点之后的节点** 28 | 29 | 举例如有链表 `head = 1->2->3->null`。我们需要定义三个变量来实现反转。 30 | 31 | 1. `pre`  上一个节点,默认 `pre = null`。 32 | 2. `cur` 当前遍历的节点,默认 `cur = head(1->2->3->null)`。 33 | 3. `next` 保存剩余的节点,默认 `next = null`。 34 | 35 | 以下为执行过程 36 | 37 | - 第一次反转,当前为 `1`。 38 | 39 | 1. 需要先保存当前节点之后的所有节点 `next = cur.next`,结果为 `next = 2->3->null`。 40 | 2. 让当前节点的指针指向上一个节点 `cur.next = pre`,结果为 `cur = 1->null`。注意这时已经切断了当前的链表,所以才需要使用 `next` 保存。 41 | 3. 让当前节点变为上一个节点 `pre = cur`,结果为 `pre = 1->null`。 42 | 4. 这样就完成了第一次反转,剩余的节点继续遍历 `cur = next`,结果为 `cur = 2->3->null`。 43 | 44 | - 第二次反转,当前为 `2`。 45 | 46 | 1. 需要先保存当前节点之后的所有节点 `next = cur.next`,结果为 `next = 3->null`。 47 | 2. 让当前节点的指针指向上一个节点 `cur.next = pre`,结果为 `cur = 2->1->null`。 48 | 3. 让当前节点变为上一个节点 `pre = cur`,结果为 `pre = 2-1->null`。 49 | 4. 这样就完成了第一次反转,剩余的节点继续遍历 `cur = next`,结果为 `cur = 3->null`。 50 | 51 | - 第三次反转,当前为 `3`。 52 | 53 | 1. 需要先保存当前节点之后的所有节点 `next = cur.next`,结果为 `next = null`。 54 | 2. 让当前节点的指针指向上一个节点 `cur.next = pre`,结果为 `cur = 4-2->1->null`。 55 | 3. 让当前节点变为上一个节点 `pre = cur`,结果为 `pre = 3-2-1->null`。 56 | 4. 这样就完成了第一次反转,剩余的节点继续遍历 `cur = next`,结果为 `cur = null`。 57 | 58 | - 第三次反转,当前为 `null`,结束遍历,返回最终的结果 `pre`。 59 | 60 | **代码实现** 61 | 62 | ```js 63 | /** 64 | * @param {ListNode} head 65 | * @return {ListNode} 66 | */ 67 | var reverseList = function (head) { 68 | let pre = null; 69 | let cur = head; 70 | let next = null; 71 | 72 | while (cur) { 73 | // 保存下一个节点 74 | next = cur.next; 75 | // 让当前节点的指针指向上一个节点 76 | cur.next = pre; 77 | // 让当前节点变为上一个节点 78 | pre = cur; 79 | // 重置当前节点 80 | cur = next; 81 | } 82 | 83 | // 解构赋值,和上面逻辑一样。 84 | // while (cur) { 85 | // ;[cur.next, pre, cur] = [pre, cur, cur.next] 86 | // } 87 | 88 | return pre; 89 | }; 90 | // @lc code=end 91 | 92 | // =================================================================== 93 | // ========================== @test ================================== 94 | // =================================================================== 95 | let ListNode = { 96 | val: 1, 97 | next: { 98 | val: 2, 99 | next: { 100 | val: 3, 101 | next: null, 102 | }, 103 | }, 104 | }; 105 | 106 | function print(head) { 107 | let cur = head; 108 | let link = []; 109 | while (cur) { 110 | link.push(cur.val); 111 | cur = cur.next; 112 | } 113 | return `长度为 ${link.length} : 值为 ${link.join("->")}`; 114 | } 115 | 116 | console.log(print(reverseList(ListNode))); // 长度为 3 : 值为 3->2->1 117 | ``` 118 | -------------------------------------------------------------------------------- /blogs/algorithm/leetcode/46.permutations.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: LeetCode-46.全排列 3 | date: 2021-03-06 4 | tags: 5 | - LeetCode 6 | categories: 7 | - 算法 8 | --- 9 | 10 | ## 题目描述 11 | 12 | 给定一个 没有重复 数字的序列,返回其所有可能的全排列。 13 | 14 | **示例 1:** 15 | 16 | ```md 17 | 输入: [1,2,3] 18 | 输出: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]] 19 | ``` 20 | 21 | ## 解法一递归、回溯 22 | 23 | **最重要的理解两个概念,一个是递归的递和归的过程,一个是回溯的思想。** 24 | 25 | 递归:递归的过程中形成一个系统栈,分为*递*和*归*的过程, 函数调用之前的代码是*递*的过程执行的,函数调用之后的代码是*归*的过程执行的。 26 | 27 | 回溯:如执行时形成一个路径,当不能满足条件的时候再一步一步的回退回去 `做操作 -> 递归 -> 撤销操作`。 28 | 29 | 先看一下树形结构,此图来自 [题解](https://leetcode-cn.com/problems/permutations/solution/hui-su-suan-fa-python-dai-ma-java-dai-ma-by-liweiw)。 30 | 31 | ![题解](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/510df202a5574a3392f1dd6cf71ae622~tplv-k3u1fbpfcp-zoom-1.image) 32 | 33 | 上图描述了一个递归的过程和回溯的过程,在每次遍历的时候,找到一个不重复的值,然后继续递归直到条件结束,然后在归的过程中回溯到上次值,再次查找不同的值。 34 | 35 | 举例 `[1, 2, 3]` 现在需要遍历每一个元素,每遍历一个元素如果没有就收集起来(`temp = []`)就递归继续查找。 36 | 37 | 1. 第一层递归的循环;`i = 0, temp = [1]` 往下递归;此时还有 `i = 1、i = 2` 未执行。 38 | 2. 第二层递归的循环:`1` 已经存在跳过本次继续 `i = 1, temp = [1, 2]` 往下递归;此时还有 `i = 2` 未执行。 39 | 3. 第三层递归的循环:`1、2` 已经存在跳过本次继续 `i = 2, temp = [1, 2, 3]` 往下递归;此时没有可执行。 40 | 4. 第四层递归:每次递归开始判断了数量是否足够,这里已经完成了一个组合 `[1, 2, 3]` 收集起来直接返回,循环就不执行了。开始了归 + 回溯的过程。 41 | 5. 第三层递归的回溯:撤回一步 `temp = [1, 2]`,没有循环完成。 42 | 6. 第二层递归的回溯里的循环:撤回一步 `temp = [1]`,此时 `i = 2` 还可以继续执行。`i = 2, temp = [1, 3]` 往下递归;此时没有可执行。 43 | - 第一层递归的循环:`1` 已经存在跳过本次继续 `i = 1, temp = [1, 3, 2]` 往下递归。 44 | - 第一层递归:又得到了一个组合 `[1, 3, 2]` 收集起来直接返回。开始了归 + 回溯的过程。 45 | - 第一层递归的回溯:撤回一步 `temp = [1, 3]`,没有循环完成。 46 | 7. 第二层递归的回溯:撤回一步 `temp = [1]`,没有循环完成。 47 | 8. 第一层递归的回溯:撤回一步 `temp = []`,此时还有 `i = 1、i = 2` 可执行。 48 | 9. 以下逻辑就会按照以上逻辑再走两遍。 49 | 10. ... 50 | 51 | **代码实现** 52 | 53 | ```js 54 | /** 55 | * @param {number[]} nums 56 | * @return {number[][]} 57 | */ 58 | var permute = function (nums) { 59 | let list = []; // 结果集合 60 | let temp = []; // 临时集合 61 | let flag = 0; 62 | 63 | function backtrack() { 64 | flag++; 65 | 66 | // 当排完一个组合,收集结果。开始 归 的过程。 67 | if (temp.length === nums.length) { 68 | return list.push([...temp]); 69 | } 70 | 71 | for (let i = 0; i < nums.length; i++) { 72 | // 如果已经存在了,下一次循环跳过 73 | if (temp.includes(nums[i])) continue; 74 | 75 | // 收集元素 76 | temp.push(nums[i]); 77 | console.log(`递-${flag}-${i}`, temp); 78 | 79 | backtrack(temp); 80 | 81 | // 回溯的过程:删除一个回到上次,对应 push 操作。 82 | temp.pop(); 83 | console.log(`归-${flag}-${i}`, temp); 84 | } 85 | } 86 | 87 | backtrack(); 88 | 89 | return list; 90 | }; 91 | 92 | // =================================================================== 93 | // ========================== @test ================================== 94 | // =================================================================== 95 | console.log(permute([1, 2, 3])); 96 | // [ 97 | // [ 1, 2, 3 ], 98 | // [ 1, 3, 2 ], 99 | // [ 2, 1, 3 ], 100 | // [ 2, 3, 1 ], 101 | // [ 3, 1, 2 ], 102 | // [ 3, 2, 1 ] 103 | // ] 104 | ``` 105 | -------------------------------------------------------------------------------- /blogs/algorithm/leetcode/61.rotate-list.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: LeetCode-61.旋转链表 3 | date: 2021-03-09 4 | tags: 5 | - LeetCode 6 | categories: 7 | - 算法 8 | --- 9 | 10 | ## 题目描述 11 | 12 | 给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。 13 | 14 | **示例 1:** 15 | 16 | ```md 17 | 输入: 1->2->3->4->5->NULL, k = 2 18 | 输出: 4->5->1->2->3->NULL 19 | 解释: 20 | 向右旋转 1 步: 5->1->2->3->4->NULL 21 | 向右旋转 2 步: 4->5->1->2->3->NULL 22 | ``` 23 | 24 | **示例 1:** 25 | 26 | ```md 27 | 输入: 0->1->2->NULL, k = 4 28 | 输出: 2->0->1->NULL 29 | 解释: 30 | 向右旋转 1 步: 2->0->1->NULL 31 | 向右旋转 2 步: 1->2->0->NULL 32 | 向右旋转 3 步: 0->1->2->NULL 33 | 向右旋转 4 步: 2->0->1->NULL 34 | ``` 35 | 36 | ## 解法一 37 | 38 | **如果要旋转得先构造成一个环,然后再找到新的头节点再断开这个环。** 39 | 40 | 举例 `1->2->3->4->5` 移动 `k = 1` 位,结果为 `5->1->2->3->4`。 41 | 42 | 1. 构成一个环的办法,循环链表获取未节点指向头节点,并获取链表的长度(`size = 5`)。成环后 `1->2->3->4->5->1->2->3->4->5->...`。 43 | 2. 假设现在指针 `H` 默认指向 `1`,那么右旋转一步后,`H` 指针应该指向 `5`(可以拿表感受下)。 44 | 3. 那么新的头节点的位置在 `size - k` => `5 - 1 = 4`(右旋转,后面的就转上来了)。 45 | 4. 如果我们要把 `5` 当头节点,那么必须断开这个环链表。断开链表必须从上一个节点断开那么需要往前找一个节点。`size - k - 1` => `5 - 1 - 1 = 3`。 46 | 5. 但是如果当 `k > size` 好像就不是那么回事了,如 `k = 6` 套用公式为 `5 - 6 - 1 = -2`。其实当 `k` 是旋转链表长度的整数倍时,它和未旋转是一样的,所以可以对 `k` 取余数 `6 % 5 = 1` 相当于只走了一步。 47 | 6. 所以得出的公式为 `size - (k % size) - 1` 套用公式得出 `5 - (1 % 5) - 1 = 3`。所以我们需要循环 `3` 次找到 `4`节点。 48 | 7. 最后把 `4` 节点的下一个节点保存起来,再断开 `4` 节点 和 `5` 节点得到以 `5`节点开头的链表。 49 | 50 | **代码实现** 51 | 52 | ```js 53 | /** 54 | * @param {ListNode} head 55 | * @param {number} k 56 | * @return {ListNode} 57 | */ 58 | var rotateRight = function (head, k) { 59 | if (!head || !head.next || !k) return head; 60 | 61 | let pre = head; 62 | let size = 1; 63 | 64 | // 获取链表的长度 65 | while (pre.next) { 66 | pre = pre.next; 67 | size++; 68 | } 69 | 70 | // 让链表的末位节点指向头节点构成一个环。 71 | // 这里的引用关系:因为最后的 pre.next 是对 head 最后一个的引用,所以现在 head 现在是一个从头开始的环。 72 | pre.next = head; 73 | 74 | // 顺时针旋转,所以要算出走几步才能到达新的头节点。 75 | // 当 K 比链表长度数值要大时,K 要对链表长度取余。因为当 K 是旋转链表长度的整数倍时,它和未旋转是一样的。 76 | let length = size - (k % size) - 1; 77 | for (let i = 0; i < length; i++) { 78 | head = head.next; 79 | } 80 | 81 | // 保存新的头节点 => 4.next = 5 82 | pre = head.next; 83 | 84 | // 断开头节点和上个节点的环 => 4 和 5 断开 85 | head.next = null; 86 | 87 | return pre; 88 | }; 89 | 90 | // =================================================================== 91 | // ========================== @test ================================== 92 | // =================================================================== 93 | let ListNode = { 94 | val: 1, 95 | next: { 96 | val: 2, 97 | next: { 98 | val: 3, 99 | next: { 100 | val: 4, 101 | next: { 102 | val: 5, 103 | next: null, 104 | }, 105 | }, 106 | }, 107 | }, 108 | }; 109 | 110 | function print(head) { 111 | let cur = head; 112 | let link = []; 113 | while (cur) { 114 | link.push(cur.val); 115 | cur = cur.next; 116 | } 117 | return `长度为 ${link.length} : 值为 ${link.join("->")}`; 118 | } 119 | 120 | console.log(print(rotateRight(ListNode, 1))); // 长度为 5 : 值为 5->1->2->3->4 121 | ``` 122 | -------------------------------------------------------------------------------- /blogs/javascript/design-pattern/strategy.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 设计模式之策略模式 3 | date: 2021-01-17 4 | tags: 5 | - JavaScript 6 | - DesignPattern 7 | categories: 8 | - 前端 9 | --- 10 | 11 | ## 定义 12 | 13 | 定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。 14 | 15 | ## 实现 16 | 17 | 策略模式的目的就是将算法的使用和算法的实现分离开来。一个基于策略模式的程序至少由两部分组成。 18 | 19 | 第一部分是一组策略类(可变),策略类封装了具体的算法,并负责具体的计算过程。 20 | 21 | 第二部分是环境类 Context(不变),Context 接受客户的请求,然后将请求委托给某一个策略类。要做到这一点,说明 Context 中要维持对某个策略对象的引用。 22 | 23 | ## 应用 24 | 25 | **奖金计算** 26 | 27 | 绩效为 S 的人年终奖有 4 倍工资,绩效为 A 的人年终奖有 3 倍工资,而绩效为 B 的人年终奖是 2 倍工资。 28 | 29 | 如果不使用策略模式,可能会一个一个的写判断。 30 | 31 | ```js 32 | let calculateBonus = function (performanceLevel, salary) { 33 | if (performanceLevel === "S") { 34 | return salary * 4; 35 | } 36 | if (performanceLevel === "A") { 37 | return salary * 3; 38 | } 39 | if (performanceLevel === "B") { 40 | return salary * 2; 41 | } 42 | }; 43 | 44 | console.log(calculateBonus("S", 10000)); // 40000 45 | console.log(calculateBonus("A", 10000)); // 30000 46 | console.log(calculateBonus("B", 10000)); // 20000 47 | ``` 48 | 49 | 使用策略模式,就可以把逻辑拆开。 50 | 51 | ```js 52 | // ~策略类 strategies 53 | let strategies = { 54 | S: function (salary) { 55 | return salary * 4; 56 | }, 57 | A: function (salary) { 58 | return salary * 3; 59 | }, 60 | B: function (salary) { 61 | return salary * 2; 62 | }, 63 | }; 64 | 65 | // ~环境类 calculateBonus 66 | let calculateBonus = function (level, salary) { 67 | return strategies[level](salary); 68 | }; 69 | 70 | console.log(calculateBonus("S", 10000)); // 40000 71 | console.log(calculateBonus("A", 10000)); // 30000 72 | console.log(calculateBonus("B", 10000)); // 20000 73 | ``` 74 | 75 | **表单验证** 76 | 77 | 这里定义一组策略类用于定义验证规则,然后使用环境类添加一组验证规则。 78 | 79 | ```js 80 | // ~策略类 strategies 81 | let strategies = { 82 | isNonEmpry: function (value, error) { 83 | if (value === "") { 84 | return error; 85 | } 86 | }, 87 | isMobile: function (value, error) { 88 | if (!/^(\+?0?86\-?)?1[3456789]\d{9}$/.test(value)) { 89 | return error; 90 | } 91 | }, 92 | }; 93 | 94 | // ~环境类 Validator 95 | let Validator = function () { 96 | this.cache = []; 97 | this.add = function (value, rule, message) { 98 | // 保存一个函数,返回对应的验证结果 99 | this.cache.push(function () { 100 | return strategies[rule](value, message); 101 | }); 102 | }; 103 | this.validator = function () { 104 | for (let i = 0; i < this.cache.length; i++) { 105 | // 循环已经保存的验证规则,如果有错误的,就直接返回错误信息 106 | let error = this.cache[i](); 107 | if (error) { 108 | return error; 109 | } 110 | } 111 | }; 112 | }; 113 | 114 | // ~测试组 Validator 115 | let validator1 = new Validator(); 116 | validator1.add("", "isNonEmpry", "不能为空"); 117 | validator1.add("123123", "isMobile", "不是一个手机号"); 118 | console.log("validator1:", validator1.validator()); // validator1: 不能为空 119 | 120 | let validator2 = new Validator(); 121 | validator2.add("123123", "isNonEmpry", "不能为空"); 122 | validator2.add("123123", "isMobile", "不是一个手机号"); 123 | console.log("validator2:", validator2.validator()); // validator2: 不是一个手机号 124 | ``` 125 | -------------------------------------------------------------------------------- /blogs/node/tinylib-analysis/only-allow.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: only-allow 源码分析-强制统一规范包管理器 3 | date: 2021-12-23 4 | tags: 5 | - Node 6 | categories: 7 | - 后端 8 | - 源码分析 9 | --- 10 | 11 | ## 前言 12 | 13 | 应用场景是强制使用统一的包管理器,统一使用 `npm` or `yarn ` or `pnpm`。 14 | 15 | ## 源码 16 | 17 | 利用 `npm` 的 `preinstall` 钩子在安装包之前执行一段检测脚本。 18 | 19 | ### 依赖 20 | 21 | ```js 22 | // 当前运行的包管理工具 npm/yarn/pnpm 23 | const whichPMRuns = require("which-pm-runs"); 24 | // 在总端创建一个框 25 | const boxen = require("boxen"); 26 | ``` 27 | 28 | ### 运行环境 29 | 30 | 先来想一个问题,它是怎么知道我们是使用什么来运行的。 31 | 32 | 先来看 `which-pm-runs` 的实现,它是通过 `process.env.npm_config_user_agent` 来判断的,`npm_config_user_agent` 是包管理工具实现的一个环境变量字段,类似于浏览器的 `navigator.userAgent`。 33 | 34 | 通过 `npm_config_user_agent` 能得到 `yarn/1.22.17 npm/? node/v14.17.0 darwin x64`,通过解析这个字符串就可以知道通过什么来运行的。 35 | 36 | ```js 37 | module.exports = function () { 38 | if (!process.env.npm_config_user_agent) { 39 | return undefined; 40 | } 41 | return pmFromUserAgent(process.env.npm_config_user_agent); 42 | }; 43 | 44 | // yarn/1.22.17 npm/? node/v14.17.0 darwin x64 45 | function pmFromUserAgent(userAgent) { 46 | const pmSpec = userAgent.split(" ")[0]; 47 | const separatorPos = pmSpec.lastIndexOf("/"); 48 | // 返回名称和版本 49 | return { 50 | name: pmSpec.substr(0, separatorPos), 51 | version: pmSpec.substr(separatorPos + 1), 52 | }; 53 | } 54 | ``` 55 | 56 | ### 判断参数 57 | 58 | 所以我们只需要获取到 `process.argv` 的参数,和 `which-pm-runs` 返回的 `name` 对比是否一致,入股不一致提示出信息。 59 | 60 | ```js 61 | // 获取命令行参数和判断解析出 npm/yarn/pnpm 62 | const argv = process.argv.slice(2); 63 | if (argv.length === 0) { 64 | console.log("Please specify the wanted package manager: only-allow "); 65 | process.exit(1); 66 | } 67 | const wantedPM = argv[0]; 68 | if (wantedPM !== "npm" && wantedPM !== "pnpm" && wantedPM !== "yarn") { 69 | console.log( 70 | `"${wantedPM}" is not a valid package manager. Available package managers are: npm, pnpm, or yarn.` 71 | ); 72 | process.exit(1); 73 | } 74 | 75 | // 如果现在使用工具和指定的不一致则输出提示 76 | const usedPM = whichPMRuns(); 77 | if (usedPM && usedPM.name !== wantedPM) { 78 | const boxenOpts = { borderColor: "red", borderStyle: "double", padding: 1 }; 79 | switch (wantedPM) { 80 | case "npm": 81 | console.log(boxen('Use "npm install" for installation in this project', boxenOpts)); 82 | break; 83 | case "pnpm": 84 | console.log( 85 | boxen( 86 | `Use "pnpm install" for installation in this project. 87 | 88 | If you don't have pnpm, install it via "npm i -g pnpm". 89 | For more details, go to https://pnpm.js.org/`, 90 | boxenOpts 91 | ) 92 | ); 93 | break; 94 | case "yarn": 95 | console.log( 96 | boxen( 97 | `Use "yarn" for installation in this project. 98 | 99 | If you don't have Yarn, install it via "npm i -g yarn". 100 | For more details, go to https://yarnpkg.com/`, 101 | boxenOpts 102 | ) 103 | ); 104 | break; 105 | } 106 | process.exit(1); 107 | } 108 | ``` 109 | 110 | ## 总结 111 | 112 | 1. 学到了 `npm_config_user_agent` 环境变量。 113 | 114 | 2. 强制性的规范更有助于避免错误。 115 | -------------------------------------------------------------------------------- /blogs/vue/vue2-router-analysis.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Vue Router 原理分析,实现检测路由变化并匹配组件渲染 3 | date: 2020-05-10 4 | tags: 5 | - Vue 6 | - VueRouter 7 | categories: 8 | - 前端 9 | - 源码分析 10 | --- 11 | 12 | 简单实现,实现最基础的功能。 13 | 14 | ## 查看示例 15 | 16 | [查看示例](https://github.com/haiweilian/demos/tree/master/Vue/vue-router-simple-imp) 17 | 18 | ## 初始化项目 19 | 20 | 先用 vue-cli 初始一个 vue 项目,router 模式要选择 `hash` 模式。 21 | 22 | ## 核心实现 23 | 24 | ### 需求分析 25 | 26 | 分析下需要实现的功能。 27 | 28 | - 实现一个插件:创建 `VueRouter` 类并把 `$router` 挂载到 Vue 原型上。 29 | - 具体功能实现: 30 | - 实现响应式的匹配。 31 | - 实现 `router-link` 组件。 32 | - 实现 `router-view` 组件。 33 | 34 | ### 实现 VueRouter 35 | 36 | 创建一个 vue-router.js 替换掉 vuex。 37 | 38 | ```js 39 | // 在 router 文件下创建一个 vue-router.js 替换 vue-router。 40 | // import VueRouter from 'vue-router' 41 | import VueRouter from "./vue-router"; 42 | ``` 43 | 44 | 创建一个插件,并实现 `VueRouter` 类,并把 `$router` 挂载到 Vue 原型上。 45 | 46 | ```javascript 47 | // router/vue-router.js 48 | let Vue; // bind on install 49 | 50 | class VueRouter { 51 | constructor(options) { 52 | this.$options = options; 53 | } 54 | } 55 | 56 | // Vue 插件的实现方式,必须导出一个 install 函数。Vue.use() 的时候自动调用。 57 | VueRouter.install = function (_Vue) { 58 | Vue = _Vue; 59 | 60 | Vue.mixin({ 61 | // 在实例初始化之后再执行,这里是为了延迟执行。 62 | // 因为是为了保证 Vue 的实例存在,用于在原型上挂载 $router。 63 | beforeCreate() { 64 | if (this.$options.router) { 65 | Vue.prototype.$router = this.$options.router; 66 | Vue.prototype.$router.onHashChange(); 67 | } 68 | }, 69 | }); 70 | }; 71 | 72 | export default VueRouter; 73 | ``` 74 | 75 | ### 实现响应式的匹配 76 | 77 | ```js 78 | class VueRouter { 79 | constructor(options) { 80 | this.$options = options; 81 | 82 | // 定义一个响应式的 current, 如果它变了,那么使用它的组件会 render。 83 | // `Vue.util.defineReactive` 未公开文档,在 vue 源码的 src/core/global-api/index.js 里面。 84 | Vue.util.defineReactive(this, "current", ""); 85 | 86 | // 当路由变化的时候,重新赋值当前路径 87 | window.addEventListener("hashchange", () => { 88 | this.onHashChange(); 89 | }); 90 | } 91 | 92 | onHashChange() { 93 | this.current = window.location.hash.slice(1) || "/"; 94 | } 95 | } 96 | ``` 97 | 98 | ### 实现 router-link 99 | 100 | ```js 101 | VueRouter.install = function (_Vue) { 102 | // 注册 router-link 组件,利用 a 标签跳转。 103 | Vue.component("router-link", { 104 | props: { 105 | to: { 106 | type: String, 107 | required: true, 108 | }, 109 | }, 110 | render(h) { 111 | return h( 112 | "a", 113 | { 114 | attrs: { href: `#${this.to}` }, 115 | }, 116 | this.$slots.default 117 | ); 118 | }, 119 | }); 120 | }; 121 | ``` 122 | 123 | ### 实现 router-view 124 | 125 | ```js 126 | VueRouter.install = function (_Vue) { 127 | // 注册 router-view 组件,匹配对应 path 路径的 component 组件。 128 | Vue.component("router-view", { 129 | render(h) { 130 | let component = null; 131 | const { $options, current } = this.$router; 132 | $options.routes.forEach((item) => { 133 | if (item.path === current) { 134 | component = item.component; 135 | } 136 | }); 137 | return h(component); 138 | }, 139 | }); 140 | }; 141 | ``` 142 | -------------------------------------------------------------------------------- /blogs/javascript/design-pattern/proxy.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 设计模式之代理模式 3 | date: 2021-01-17 4 | tags: 5 | - JavaScript 6 | - DesignPattern 7 | categories: 8 | - 前端 9 | --- 10 | 11 | ## 定义 12 | 13 | 为一个对象提供一个代用品或占位符,以便控制对它的访问。 14 | 15 | ## 实现 16 | 17 | 当不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象。 18 | 19 | ## 应用 20 | 21 | **虚拟代理** 22 | 23 | 虚拟代理形式:某一个花销很大的操作,可以通过虚拟代理的方式延迟到这种需要它的时候才去创建执行。 24 | 25 | _函数防抖:如果短时间内大量触发同一事件,只会执行一次函数。_ 26 | 27 | ```js 28 | function debounce(fn, delay) { 29 | let timer = null; 30 | 31 | return function () { 32 | let arg = arguments; 33 | 34 | // 每次操作时,清除上传的定时器 35 | clearTimeout(timer); 36 | timer = null; 37 | 38 | // 定义新的定时器,一段时间后进行操作 39 | timer = setTimeout(function () { 40 | fn.apply(this, arg); 41 | }, delay); 42 | }; 43 | } 44 | 45 | let count = 0; 46 | let handerCount = function () { 47 | count++; 48 | }; 49 | 50 | // 代理原始函数 51 | let debounceCount = debounce(handerCount, 500); 52 | 53 | // 开始频繁操作,数字不会变 54 | let timer = setInterval(() => { 55 | debounceCount(); 56 | console.log(count); 57 | }, 100); 58 | 59 | // 停止频繁操作,500ms 后数字变化 60 | setTimeout(() => { 61 | clearInterval(timer); 62 | setTimeout(() => { 63 | console.log(count); 64 | }, 500); 65 | }, 2000); 66 | ``` 67 | 68 | _函数节流:如果短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的时间期限内不再工作,直至过了这段时间才重新生效_ 69 | 70 | ```js 71 | function throttle(fn, delay) { 72 | let timer = null; 73 | 74 | return function () { 75 | let arg = arguments; 76 | 77 | // 如果上次执行的时间间隔未到,不执行下一次。 78 | if (timer) { 79 | return; 80 | } 81 | 82 | timer = setTimeout(function () { 83 | fn.apply(this, arg); 84 | clearTimeout(timer); 85 | timer = null; 86 | }, delay); 87 | }; 88 | } 89 | 90 | let count = 0; 91 | let handerCount = function () { 92 | count++; 93 | }; 94 | 95 | // 代理原始函数 96 | let throttleCount = throttle(handerCount, 500); 97 | 98 | // 开始频繁操作,间隔 500ms 数字才会变 99 | let timer = setInterval(() => { 100 | throttleCount(); 101 | console.log(count); 102 | }, 100); 103 | ``` 104 | 105 | **缓存代理** 106 | 107 | 为一些开销大的运算提供暂时的存储,在下次运算时,如果传递进来的参数跟之前一致,则可以直接返回前面存储的运算结果。 108 | 109 | ```js 110 | // 乘积:脑补是一个很复杂的预算 111 | let mult = function () { 112 | console.log("mult..."); 113 | let a = 1; 114 | for (let i = 0; i < arguments.length; i++) { 115 | a = a * arguments[i]; 116 | } 117 | return a; 118 | }; 119 | 120 | // 加和:脑补是一个很复杂的预算 121 | let plus = function () { 122 | console.log("plus..."); 123 | let a = 1; 124 | for (let i = 0; i < arguments.length; i++) { 125 | a = a + arguments[i]; 126 | } 127 | return a; 128 | }; 129 | 130 | // 创建缓存代理的工厂 131 | let createProxyFactory = function (fn) { 132 | let cache = {}; 133 | return function () { 134 | // 以参数为 key 把计算结果存起来,如果存在就直接返回 135 | let args = Array.from(arguments).join(","); 136 | if (args in cache) { 137 | return cache[args]; 138 | } 139 | return (cache[args] = fn.apply(this, arguments)); 140 | }; 141 | }; 142 | 143 | let proxyMult = createProxyFactory(mult); 144 | let proxyPlus = createProxyFactory(plus); 145 | console.log(proxyMult(1, 2, 3, 4, 5)); 146 | console.log(proxyMult(1, 2, 3, 4, 5)); 147 | console.log(proxyPlus(1, 2, 3, 4, 5)); 148 | console.log(proxyPlus(1, 2, 3, 4, 5)); 149 | // mult... 150 | // 120 151 | // 120 152 | // plus... 153 | // 16 154 | // 16 155 | ``` 156 | -------------------------------------------------------------------------------- /blogs/node/tinylib-analysis/configstore.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: configstore 源码分析-配置存储 3 | date: 2021-12-21 4 | tags: 5 | - Node 6 | categories: 7 | - 后端 8 | - 源码分析 9 | --- 10 | 11 | ## 前言 12 | 13 | 用于存储应用程序的配置到本地文件。 14 | 15 | ## 源码 16 | 17 | ### 依赖 18 | 19 | ```js 20 | import path from "path"; 21 | import os from "os"; 22 | // fs 模块的扩展 23 | import fs from "graceful-fs"; 24 | // linux 的基础目录 25 | import { xdgConfig } from "xdg-basedir"; 26 | // fs.writeFile 的扩展 27 | import writeFileAtomic from "write-file-atomic"; 28 | // 从嵌套对象中获取、设置或删除属性 29 | import dotProp from "dot-prop"; 30 | // 生成唯一的随机字符串 31 | import uniqueString from "unique-string"; 32 | ``` 33 | 34 | ### 全局变量 35 | 36 | 这里有一个知识点是 [Linux 中的权限](https://blog.csdn.net/mlz_2/article/details/105250259])。 37 | 38 | ```js 39 | // 获取配置目录,如果是 linux 则是 xdgCongfig, 反之获取临时文件的默认目录路径 40 | const configDirectory = xdgConfig || path.join(os.tmpdir(), uniqueString()); 41 | // 权限错误提示语 42 | const permissionError = "You don't have access to this file."; 43 | // 文件权限配置,0700、0600是 linux 表示权限的方式 https://blog.csdn.net/mlz_2/article/details/105250259 44 | // 0600:拥有者具有文件的读、写权限,其他用户没有 45 | // 0700:拥有者具有文件的读、写、执行权限,其他用户没有 46 | const mkdirOptions = { mode: 0o0700, recursive: true }; 47 | const writeFileOptions = { mode: 0o0600 }; 48 | ``` 49 | 50 | ### 存储逻辑 51 | 52 | 主要实现就是和正常的操作对象差不多,主要就是使用了 [getter](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/get) 在读取属性时从本地文件读取到内容。使用了 [setter](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/set) 在设置属性时写入值到本地文件。 53 | 54 | ```js 55 | export default class Configstore { 56 | constructor(id, defaults, options = {}) { 57 | // 获取到自定义的存储路径 58 | const pathPrefix = options.globalConfigPath 59 | ? path.join(id, "config.json") 60 | : path.join("configstore", `${id}.json`); 61 | 62 | // 获取最终的存储路径,系统路径 + 自定义路径 63 | // /Users/lianhaiwei/.config/configstore/configstore-debug.json 64 | this._path = options.configPath || path.join(configDirectory, pathPrefix); 65 | 66 | // 如果传入默认值则初始化 67 | if (defaults) { 68 | this.all = { 69 | ...defaults, 70 | ...this.all, 71 | }; 72 | } 73 | } 74 | 75 | // 读取文件里的所有值 76 | get all() { 77 | try { 78 | return JSON.parse(fs.readFileSync(this._path, "utf8")); 79 | } catch (error) { 80 | // .... 81 | } 82 | } 83 | 84 | // 设置所有值到文件 85 | set all(value) { 86 | try { 87 | // 文件是否存在,不存在创建 88 | fs.mkdirSync(path.dirname(this._path), mkdirOptions); 89 | // 写入文件并格式化了字符串 90 | writeFileAtomic.sync(this._path, JSON.stringify(value, undefined, "\t"), writeFileOptions); 91 | } catch (error) { 92 | // ... 93 | } 94 | } 95 | 96 | // 获取一个值 97 | get(key) { 98 | return dotProp.get(this.all, key); 99 | } 100 | 101 | // 设置一个值 102 | set(key, value) { 103 | const config = this.all; 104 | 105 | if (arguments.length === 1) { 106 | for (const k of Object.keys(key)) { 107 | dotProp.set(config, k, key[k]); 108 | } 109 | } else { 110 | dotProp.set(config, key, value); 111 | } 112 | 113 | // 从新触发 set all 保存最新的值 114 | this.all = config; 115 | } 116 | } 117 | ``` 118 | 119 | ## 总结 120 | 121 | 1. 知道了 `fs` 模块中的 `mode` 配置代表的具体的含义,与 `linux` 中的权限。 122 | 123 | 2. 在查找依赖的作用的时候,也有作者推荐了更好的依赖库。比如更现代的 [conf](https://github.com/sindresorhus/conf) 和 [env-paths](https://github.com/sindresorhus/env-paths)获取全平台存储配置路径。 124 | -------------------------------------------------------------------------------- /blogs/algorithm/leetcode/82.remove-duplicates-from-sorted-list-ii.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: LeetCode-82.删除排序链表中的重复元素-ii 3 | date: 2021-03-10 4 | tags: 5 | - LeetCode 6 | categories: 7 | - 算法 8 | --- 9 | 10 | ## 题目描述 11 | 12 | 给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字。 13 | 14 | **示例 1:** 15 | 16 | ```md 17 | 输入: 1->2->3->3->4->4->5 18 | 输出: 1->2->5 19 | ``` 20 | 21 | **示例 2:** 22 | 23 | ```md 24 | 输入: 1->1->1->2->3 25 | 输出: 2->3 26 | ``` 27 | 28 | ## 解法一 29 | 30 | **因为可能删除头节点,需要一个哨兵节点。然后需要两个指针,pre 用来定位相同节点的开头前一个指针,cur 用来查找相同节点的结尾** 31 | 32 | 1. 因为删除重复元素时有可能需要删除第一个,首先我们要新建一个哨兵节点。 33 | 2. 然后需要两个指针,命名 `pre` 为前一个指针,`cur` 为当前指针,因为删除链表需要找到当前节点的前一个节点和后一个节点,所以都用 `next.val` 来比较。 34 | 3. 如果 `pre.next.val` 和 `cur.next.val` 不相同就不需要删除,那么都往后走一位继续下一个判断。 35 | 4. 如果 `pre.next.val` 和 `cur.next.val` 相同,那么 `cur` 继续往后走 `pre` 不动,直到找到不相同的。 36 | 5. 最后 `pre.next` 和 `cur.next` 之间的就是相同的节点删除掉。 37 | 38 | 举例 `1->2->2->3->3->4` 的运行步骤。 39 | 40 | 1. 初始的时候 `pre = -1->1->2->2->3->3->4`(`-1`为哨兵节点)、`cur = 1->2->2->3->3->4`。 41 | 2. 第一次判断 `pre.next.val = 1` 和 `cur.next.val = 2` 不相等。都往后走一位结果为。`pre = 1->2->2->3->3->4` 、`cur = 2->2->3->3->4`。 42 | 3. 第二次判断 `pre.next.val = 2` 和 `cur.next.val = 2` 相等。`pre` 不动。`cur` 继续往后走。 43 | 4. 第三次判断 `pre.next.val = 2` 和 `cur.next.val = 3` 不相等。让 `pre.next = cur.next` 这样就删除了相同的 `2`。 44 | 5. 继续下一轮的判断。 45 | 46 | **代码实现** 47 | 48 | ```js 49 | /** 50 | * @param {ListNode} head 51 | * @return {ListNode} 52 | */ 53 | var deleteDuplicates = function (head) { 54 | // 可能要删除第一个,所以要定义一个哨兵节点。 55 | let ret = { next: head }; 56 | let pre = ret; 57 | let cur = head; 58 | 59 | while (cur && cur.next) { 60 | // 如果上个节点和下个节点不相等,都往后移动一位。 61 | if (cur.next.val != pre.next.val) { 62 | cur = cur.next; 63 | pre = pre.next; 64 | } else { 65 | // 如果相等,连续查找相等的值,这样可以一次性跳过。 66 | while (cur && cur.next && cur.next.val === pre.next.val) { 67 | cur = cur.next; 68 | } 69 | // 把相同的节点跳过,继续下次查找 70 | pre.next = cur.next; 71 | cur = cur.next; 72 | } 73 | } 74 | 75 | return ret.next; 76 | }; 77 | 78 | // =================================================================== 79 | // ========================== @test ================================== 80 | // =================================================================== 81 | let ListNode = { 82 | val: 1, 83 | next: { 84 | val: 2, 85 | next: { 86 | val: 2, 87 | next: { 88 | val: 3, 89 | next: { 90 | val: 3, 91 | next: { 92 | val: 4, 93 | next: null, 94 | }, 95 | }, 96 | }, 97 | }, 98 | }, 99 | }; 100 | 101 | function print(head) { 102 | let cur = head; 103 | let link = []; 104 | while (cur) { 105 | link.push(cur.val); 106 | cur = cur.next; 107 | } 108 | return `长度为 ${link.length} : 值为 ${link.join("->")}`; 109 | } 110 | 111 | console.log(print(deleteDuplicates(ListNode))); // 长度为 2 : 值为 1->4 112 | ``` 113 | 114 | ## 总结 115 | 116 | 链表一共打卡了五篇,最后来一个对链表认知的总结。 117 | 118 | **抽象概念:链表代表了一种唯一指向思想** 119 | 120 | _链表的查询_ 121 | 122 | 1. 查询链表常用 `while` 循环。结束条件可能为 `cur && cur.next` 查询到末位,或者等于某个值 `cur.val === cur.next.val`。 123 | 124 | _链表的删除_ 125 | 126 | 2. 删除链表都需要从上一个节点开始操作,因为要删除必须改变指针域,如 `cur.next = cur.next.next`。 127 | 128 | _哨兵节点_ 129 | 130 | 3. 当不方便操作头节点的时候需要用到哨兵节点,如删除节点需要从上一个节点开始操作。 131 | 132 | _链表引用_ 133 | 134 | 4. 链表是通过指针引用的,列如从原始链表取出一个节点,现在改变这个节点那么原始链表的节点也会改变。为什么说这个呢,因为这个引用关系有时非常绕,容易忽略这个问题。也要善用这个特性。 135 | 136 | 5. 断开指针引用就用 `cur.next = null`。 137 | -------------------------------------------------------------------------------- /blogs/algorithm/leetcode/141.linked-list-cycle.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: LeetCode-141.环形链表 3 | date: 2021-03-13 4 | tags: 5 | - LeetCode 6 | categories: 7 | - 算法 8 | --- 9 | 10 | ## 题目描述 11 | 12 | 给定一个链表,判断链表中是否有环。 13 | 14 | 如果链表中有某个节点,可以通过连续跟踪 `next` 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 `pos` 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 `pos` 是 `-1`,则在该链表中没有环。注意:`pos` 不作为参数进行传递,仅仅是为了标识链表的实际情况。 15 | 16 | 如果链表中存在环,则返回 `true` 。 否则,返回 `false` 。 17 | 18 | **示例 1:** 19 | 20 | ![示例](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/471c37b93f6a45d9ac14c7cb046275da~tplv-k3u1fbpfcp-zoom-1.image) 21 | 22 | ```md 23 | 输入:head = [3,2,0,-4], pos = 1 24 | 输出:true 25 | 解释:链表中有一个环,其尾部连接到第二个节点。 26 | ``` 27 | 28 | **示例 2:** 29 | 30 | ![示例](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fa158b036552485593f76e258e4def91~tplv-k3u1fbpfcp-zoom-1.image) 31 | 32 | ```md 33 | 输入:head = [1,2], pos = 0 34 | 输出:true 35 | 解释:链表中有一个环,其尾部连接到第一个节点。 36 | ``` 37 | 38 | **示例 3:** 39 | 40 | ![示例](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e41099d0d74b42b4a354655de75bff6a~tplv-k3u1fbpfcp-zoom-1.image) 41 | 42 | ```md 43 | 输入:head = [1], pos = -1 44 | 输出:false 45 | 解释:链表中没有环。 46 | ``` 47 | 48 | **进阶:** 49 | 50 | 你能用 O(1)(即,常量)内存解决此问题吗? 51 | 52 | ## 解法一集合记录 53 | 54 | **使用集合记录已经遍历过的节点,如果再次遍历到那么它就是一个环形链表** 55 | 56 | 1. 记录可以使用 `Array`、`Set` 等数据结构,只要能存储就可以了。 57 | 2. 在遍历的时候如果集合里没有这个节点就存储进去;如果有这个节点,证明再次遍历到了这个节点,那么它就是一个环形链表。 58 | 59 | **代码实现** 60 | 61 | 由于使用空间保存了每个节点,这时的空间复杂度 O(n)。 62 | 63 | ```js 64 | /** 65 | * @param {ListNode} head 66 | * @return {boolean} 67 | */ 68 | var hasCycle = function (head) { 69 | // 创建一个集合记录 70 | let cache = new Set(); 71 | while (head) { 72 | // 如果已经存在,证明已经遍历过一次了,那么它就是一个环形链表。 73 | if (cache.has(head)) { 74 | return true; 75 | } else { 76 | cache.add(head); 77 | } 78 | head = head.next; 79 | } 80 | return false; 81 | }; 82 | 83 | // =================================================================== 84 | // ========================== @test ================================== 85 | // =================================================================== 86 | let ListNode = { 87 | val: 1, 88 | next: { 89 | val: 2, 90 | next: { 91 | val: 3, 92 | next: null, 93 | }, 94 | }, 95 | }; 96 | ListNode.next.next.next = ListNode.next; 97 | 98 | console.log(hasCycle(ListNode)); // true 99 | ``` 100 | 101 | ## 解法二之快慢指针 102 | 103 | **如果是一个圆,快指针最终都会追上慢指针** 104 | 105 | 如操场跑步,一个人跑的快(fast)、一个人跑的慢(slow),迟早跑的快的会追上跑的慢的(多跑了一圈)。 106 | 107 | 1. 首先快的和慢的都是从起点出发的。快的跑两步,慢的跑一步。 108 | 2. 如果快的会先到达终点,证明不是一个环形。 109 | 3. 如果两者会再次相遇(节点相等),证明是一个环形。 110 | 111 | **代码实现** 112 | 113 | 没有使用额外的空间,这时空间复杂度 O(1)。 114 | 115 | ```js 116 | /** 117 | * @param {ListNode} head 118 | * @return {boolean} 119 | */ 120 | var hasCycle = function (head) { 121 | let fast = head; 122 | let slow = head; 123 | // 如果快的到达终点,那么它不是一个环形链表。 124 | while (fast && fast.next) { 125 | // 快的跑两步 126 | fast = fast.next.next; 127 | // 慢的跑一步 128 | slow = slow.next; 129 | // 如果快的追上了慢的,那么它就是一个环形链表。 130 | if (fast === slow) { 131 | return true; 132 | } 133 | } 134 | return false; 135 | }; 136 | // @lc code=end 137 | 138 | // =================================================================== 139 | // ========================== @test ================================== 140 | // =================================================================== 141 | let ListNode = { 142 | val: 1, 143 | next: { 144 | val: 2, 145 | next: { 146 | val: 3, 147 | next: null, 148 | }, 149 | }, 150 | }; 151 | ListNode.next.next.next = ListNode.next; 152 | 153 | console.log(hasCycle(ListNode)); // true 154 | ``` 155 | -------------------------------------------------------------------------------- /blogs/css/bem-naming-rules.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 使用 BEM 命名思想来组织和描述更加清晰的结构 3 | date: 2020-01-16 4 | tags: 5 | - Css 6 | - Scss 7 | categories: 8 | - 前端 9 | --- 10 | 11 | [BEM](https://en.bem.info/methodology/quick-start/)是什么?BEM 是一种 CSS 命名思想,用于组织 HTML 中选择器的结构,利于 CSS 代码的维护,使得代码结构更清晰。其背后的想法是将用户界面分为独立的块。即使使用复杂的 UI,也使得结构变的清晰和简洁。 12 | 13 | ## 核心思想 14 | 15 | BEM 的意思就是块(block)、元素(element)、修饰符(modifier)。依次举例。 16 | 17 | ### 块(block) 18 | 19 | 块在功能、逻辑和视觉上而不依赖其它组件的部分独立的组件,可以重复使用。语法:`B`。 20 | 21 | 如一个 `header` 就是一个块。 22 | 23 | ```html 24 |
25 | ``` 26 | 27 | 块可以嵌套块, 如 `header`、`nav` 都是一个独立的块。 28 | 29 | ```html 30 |
31 | 32 |
33 | ``` 34 | 35 | ### 元素(element) 36 | 37 | 元素依赖属于块的某部分,不能单独使用。语法:`B__E` 使用双下划线 `__` 分隔。 38 | 39 | 如 `nav__item` 就是 `nav` 的元素。 40 | 41 | ```html 42 |
43 | 47 |
48 | ``` 49 | 50 | ### 修饰符(modifier) 51 | 52 | 用于修饰块或元素,体现出外形行为状态等特征的。比如我们常定义的 `.active` `.current`。语法:`B--M`、`B__E--M` 用双连字符 `--` 分隔。 53 | 54 | 如用 `nav--small` 修饰 `nav` 的大小,用 `nav__item--active` 修饰 `nav__item` 的选中状态。 55 | 56 | ```html 57 |
58 | 62 |
63 | ``` 64 | 65 | ## BEM 规范 66 | 67 | ### 命名规范 68 | 69 | 在 BEM 中每个 E(元素)只能出现一次,不存在 `B__E__E`(元素的元素)。 70 | 71 | ```css 72 | .nav__item { 73 | } 74 | 75 | /* 不允许 */ 76 | .nav__item__item { 77 | } 78 | ``` 79 | 80 | 单词之间你可用 kebab-case(短横线命名)命名或 camelCased (驼峰式)。 81 | 82 | ```css 83 | /* camelCased */ 84 | .elNav { 85 | } 86 | 87 | /* kebab-case */ 88 | .el-nav { 89 | } 90 | ``` 91 | 92 | ### 选择器规范 93 | 94 | 不允许使用 id 选择器。如果使用 id 选择器,就失去了复用的价值。 95 | 96 | ```css 97 | /* 不允许使用id选择器作为css的样式名 */ 98 | #nav { 99 | } 100 | ``` 101 | 102 | 不允许使用元素选择器,必要时必须使用子选择器(基本没有规范会推荐使用的)。 103 | 104 | ```css 105 | /* 不能使用元素选择器 */ 106 | .nav li { 107 | } 108 | 109 | /* 必要时必须使用子选择器 */ 110 | .nav > li { 111 | } 112 | ``` 113 | 114 | 选择器层级尽量平级。(名字以及够长,冲突的可能性不大。可以减少渲染时间)。 115 | 116 | ```css 117 | /* 尽量不要嵌套使用 */ 118 | .nav { 119 | } 120 | .nav .nav__item { 121 | } 122 | 123 | /* 应该尽量平级 */ 124 | .nav { 125 | } 126 | .nav__item { 127 | } 128 | ``` 129 | 130 | ## 使用预编译工具 131 | 132 | 使用 scss、less 可以减少我们的前缀编写量。 133 | 134 | ```scss 135 | // index.scss 136 | .nav { 137 | &__item { 138 | display: inline-block; 139 | &--active { 140 | color: blue; 141 | } 142 | } 143 | &--small { 144 | font-size: 12px; 145 | } 146 | } 147 | ``` 148 | 149 | 编译后展开。 150 | 151 | ```css 152 | .nav { 153 | display: flex; 154 | } 155 | .nav--small { 156 | font-size: 12px; 157 | } 158 | .nav__item { 159 | display: inline-block; 160 | } 161 | .nav__item--active { 162 | color: blue; 163 | } 164 | ``` 165 | 166 | ## BEM 变体,其他命名方案 167 | 168 | BEM 重要的是思想,具体使用哪种连字符有多种方案。[官方也采用了社区的命名方案](https://en.bem.info/methodology/naming-convention/#alternative-naming-schemes)。不管哪种风格根据自己的团队或喜好选择一种风格。 169 | 170 | ## 实践理论 171 | 172 | - BEM 块的划分,与层级无关。 173 | 174 | - BEM 最难的部分之一是明确作用域是从哪开始和到哪结束的,以及什么时候使用(不使用)它。 175 | 176 | ## 如何看待 BEM 177 | 178 | 每一种解决方案总会有人反对和支持或许它适用于某些场景,[如何看待 CSS 中 BEM 的命名方式?](https://www.zhihu.com/question/2193515),就像文中说的“取其精华去其糟粕,BEM 的规范不一定是最佳实践,如果真的毫无价值,是会马上被历史所淹没的”。 179 | 180 | 而近年来的基于 Vue、React 的前端 ui 框架,也有使用 BEM 命名,您可以到官网审查元素查看 Html 源代码。[element-ui](https://element.eleme.cn/#/zh-CN/component/installation)、[vant](https://youzan.github.io/vant/#/zh-CN/intro) ... 181 | 182 | ## 参考资料 183 | 184 | 185 | 186 | 187 | -------------------------------------------------------------------------------- /blogs/vue/lib/guide.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 实践 Vue3 组件库系列 3 | date: 2022-08-10 4 | tags: 5 | - Vue 6 | categories: 7 | - 前端 8 | --- 9 | 10 | ## 前言 11 | 12 | 一开始我们项目中的通用组件是放在项目中的,基于开源组件库的扩展,补充些组件和功能。但随着项目的拆分组件也需要公用,就把组件提取成了一个 GIT 子模块来达到通用。也随着新开项目的工具链的升级不能直接引用源码的原因又把组件打包处理。 13 | 14 | 这中间也不断按照开源组件库的标准完善和补充,中间也记录了不少的知识点。所以准备把这些整理和归纳写成一个系列文章。 15 | 16 | ## 说明 17 | 18 | 本系列的所有的代码都会发布在 [vlib-starter](https://github.com/bfehub/vlib-starter) 这个模板内。每一个独立的功能都会创建一个分支。 19 | 20 | - 此项目主要是作为学习使用,特殊特性自己修改。 21 | 22 | - 此项目的每个分支都只包含最初必要的功能,会做必要的错误更新不会新增大的更新,保证文章中的代码和仓库中的代码不会大的出入。 23 | 24 | - 此项目的每个分支都是相对独立的功能,每个实现不代表最好的解决方案,你可以按需参考或自行实现另一种方案。 25 | 26 | - 此项目的代码在当前版本都是可运行的,可能由于依赖破坏性的升级导致错误(你可以反馈升级依赖)。 27 | 28 | - 此系列的文章中涉及到的工具或类库自行提前查阅学习,并没有篇幅详细介绍如何从零使用,会给出一些学习资料, 29 | 30 | - 由于不常写文章文笔有限写的不好的部分请见谅,不过对代码质量还是有信心的。 31 | 32 | ## 目录 33 | 34 | 全部的最新内容你可以在 [Vue3 组件库实践](https://juejin.cn/column/7130951037547970567) 这个专栏里查看,掘金的右边导航也可以看到专栏的目录。 35 | 36 | ### 01-lint 37 | 38 | 本章我们使用 [ESLint](https://eslint.org/)、[StyleLint](https://stylelint.io/)、[TypeScript ESLint](https://typescript-eslint.io/docs/) 这些工具和工具插件搭建项目的代码规范,并设计成一个可共享的预设配置。让你的团队共享同一份代码规范。当然也会在项目中加入列如 [Husky](https://github.com/typicode/husky)、[Commitlint](https://github.com/conventional-changelog/commitlint)、[Lint Staged](https://github.com/okonet/lint-staged) 之类的提交规范工具。 39 | 40 | [>>> 实践 Vue3 组件库-可共享的编码规范和提交规范](./vlib-starter-2.md) 41 | 42 | ### 02-docs 43 | 44 | 本章我们基于 [VuePress](https://v2.vuepress.vuejs.org/zh/) 搭建组件文档,并且自己去实现一个解析插件来实现组件效果展示,方便你实现各种自定义的需求。如果你更喜欢 [VitePress](https://vitepress.vuejs.org/) 后面也会有单独的实现可供切换选择。 45 | 46 | [>>> 实践 Vue3 组件库-基于 VuePress 开发组件文档](./vlib-starter-3.md) 47 | 48 | ### 03-ui 49 | 50 | 本章我们基于 [Vant](https://vant-ui.github.io/vant/#/zh-CN) 组件库去扩展一个组件(SFC + TSX)。选择一个组件库扩展的原因是我们工作中基本不会从头搭建组件,如果基于一个组件库开发是由一些需要注意点的。如果你想基于其他的组件库或者不基于组件库那也是没有问题的,后面代码中都没有强绑定。 51 | 52 | [>>> 实践 Vue3 组件库-如何基于组件库扩展业务组件(组件篇一)](./vlib-starter-4.md) 53 | 54 | ### 04-ui-test 55 | 56 | 本章我们基于 [Vitest](https://cn.vitest.dev/) 和 [Vue Test Utils](https://test-utils.vuejs.org/) 了解单元测试的基本概念和补充组件的单元测试。 57 | 58 | [>>> 实践 Vue3 组件库-基于 Vitest/VTU 的组件单元测试(组件篇二)](./vlib-starter-5.md) 59 | 60 | ### 05-ui-gen 61 | 62 | 本章我们开发一个组件模板并编写创建脚本,一键生成一个全新的组件模板避免开发一个组件复制组件结构。 63 | 64 | [>>> 实践 Vue3 组件库-如何实现 Gen 一个新组件(组件篇三)](./vlib-starter-6.md) 65 | 66 | ### 06-build 67 | 68 | 本章我们使用 [Gulp](https://gulpjs.com/)、[Rollup](https://rollupjs.org/guide/en/)、[ESBuild](https://esbuild.github.io/) 编写各种子任务,打包多种格式组件产物,以及生成组件的类型声明文件。 69 | 70 | [>>> 实践 Vue3 组件库-如何实现组件打包与组件声明文件(打包篇一)](./vlib-starter-7.md) 71 | 72 | ### 07-build-style 73 | 74 | 本章我们使用 [Gulp](https://gulpjs.com/) 生态的插件打包样式代码,并开发一个 [unplugin-vue-components](https://github.com/antfu/unplugin-vue-components) 解析器实现组件和样式的自动导入和按需加载。 75 | 76 | [>>> 实践 Vue3 组件库-如何实现样式打包与按需引入(打包篇二)](./vlib-starter-8.md) 77 | 78 | ### 08-build-helper 79 | 80 | 本章我们了解 VSCode 和 WebStorm 编辑器提示全局组件代码的配置原理,并实现根据文档自动生成这些配置文件优化业务开发体验。 81 | 82 | [>>> 实践 Vue3 组件库-如何实现全局组件在代码编辑器中的智能提示(打包篇三)](./vlib-starter-9.md) 83 | 84 | ### 09-publish 85 | 86 | 本章我们发布我们的文档和组件。使用 Changesets 作为我们多包管理工具发布项目,使用 Github Pages 或 Vercel 发布项目文档。了解和使用一些 GitHub 上功能,比如 Actions 任务、Issues 标准、其他美化项目的功能。 87 | 88 | [>>> 实践 Vue3 组件库-基于 changesets 的版本管理及自动化发布](./vlib-starter-10.md) 89 | 90 | ### 10-... 91 | 92 | 暂时规划的就这么多,后续如果有新的想法会补充进来。如果你觉得哪些必要补充可以互动交流。 93 | 94 | ## 如何开始 95 | 96 | 首先你可以把 [0-init](https://github.com/bfehub/vlib-starter/tree/0-init) 这个初始结构 Clone 下来,接下来我们就基于这个结构一步步完善。 97 | 98 | - 你需要了解 [pnpm](https://pnpm.io/zh/installation) 的基础使用。 99 | 100 | - 你需要了解 [vue3](https://staging-cn.vuejs.org/) 的基础使用。 101 | 102 | - 你需要了解 [typescript](https://www.typescriptlang.org/) 的基础使用。 103 | 104 | - 你需要了解 [nodejs](https://nodejs.org/en/) 的基础使用。 105 | 106 | - 以及上面用到的工具都贴出了链接你可提前了解学习。 107 | 108 | ## 结尾 109 | 110 | 那么就开始我们的组件库搭建之旅吧!!! 111 | -------------------------------------------------------------------------------- /blogs/node/tinylib-analysis/validate-npm-package-name.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: validate-npm-package-name 源码分析-检测 NPM 包是否符合标准 3 | date: 2021-12-22 4 | tags: 5 | - Node 6 | categories: 7 | - 后端 8 | - 源码分析 9 | --- 10 | 11 | ## 前言 12 | 13 | 这个包是 `npm` 官方出的一个检测 `npm` 包是否符合标准。 14 | 15 | ## 源码 16 | 17 | ### 依赖 18 | 19 | 依赖了一个模块 [builtins](https://github.com/juliangruber/builtins/blob/master/index.js) 里面枚举了 `node` 的核心模块,我们不能和内置模块起一样的名字,不然不就冲突了吗。 20 | 21 | ```js 22 | var builtins = require("builtins"); 23 | ``` 24 | 25 | ### 变量 26 | 27 | 有一个 `scopedPackagePattern` 长正则是用来验证 _命名空间_ 格式的,用 [regulex](https://jex.im/regulex) 来解析下比较清楚。以 `@` 开头,中间 `一或多个字符`,然后包含 `/`,最后以 `一或多个字符` 结尾。 28 | 29 | ![1.png](https://raw.githubusercontent.com/haiweilian/tinylib-analysis/main/009.validate-npm-package-name/.docs/images/1.png) 30 | 31 | ```js 32 | // 命名空间的格式验证 33 | var scopedPackagePattern = new RegExp("^(?:@([^/]+?)[/])?([^/]+?)$"); 34 | // 黑名单列表 35 | var blacklist = ["node_modules", "favicon.ico"]; 36 | ``` 37 | 38 | ### 判断 39 | 40 | 然后就是对名称各种判断了,都是能看得懂的常规判断。 41 | 42 | ```js 43 | var validate = (module.exports = function (name) { 44 | // 警告信息 45 | var warnings = []; 46 | // 错误信息 47 | var errors = []; 48 | 49 | // 名称不能为 null 50 | if (name === null) { 51 | errors.push("name cannot be null"); 52 | return done(warnings, errors); 53 | } 54 | 55 | // 名称不能为 undefined 56 | if (name === undefined) { 57 | errors.push("name cannot be undefined"); 58 | return done(warnings, errors); 59 | } 60 | 61 | // 名称必须是一个字符串 62 | if (typeof name !== "string") { 63 | errors.push("name must be a string"); 64 | return done(warnings, errors); 65 | } 66 | 67 | // 名称不能为空 68 | if (!name.length) { 69 | errors.push("name length must be greater than zero"); 70 | } 71 | 72 | // 名称不能以 . 开头 73 | if (name.match(/^\./)) { 74 | errors.push("name cannot start with a period"); 75 | } 76 | 77 | // 名称不能以 _ 开头 78 | if (name.match(/^_/)) { 79 | errors.push("name cannot start with an underscore"); 80 | } 81 | 82 | // 名称前后不能包含空格 83 | if (name.trim() !== name) { 84 | errors.push("name cannot contain leading or trailing spaces"); 85 | } 86 | 87 | // 名称不能是黑名单列表的 88 | blacklist.forEach(function (blacklistedName) { 89 | if (name.toLowerCase() === blacklistedName) { 90 | errors.push(blacklistedName + " is a blacklisted name"); 91 | } 92 | }); 93 | 94 | // 名称不能是内置核心模块 95 | builtins.forEach(function (builtin) { 96 | if (name.toLowerCase() === builtin) { 97 | warnings.push(builtin + " is a core module name"); 98 | } 99 | }); 100 | 101 | // 名称最大长度不能超过 214 102 | if (name.length > 214) { 103 | warnings.push("name can no longer contain more than 214 characters"); 104 | } 105 | 106 | // 名称不能包含大小字符 107 | if (name.toLowerCase() !== name) { 108 | warnings.push("name can no longer contain capital letters"); 109 | } 110 | 111 | // 不能包含特殊字符 112 | if (/[~'!()*]/.test(name.split("/").slice(-1)[0])) { 113 | warnings.push('name can no longer contain special characters ("~\'!()*")'); 114 | } 115 | 116 | // 如果是命名空间 117 | if (encodeURIComponent(name) !== name) { 118 | // Maybe it's a scoped package name, like @user/package 119 | var nameMatch = name.match(scopedPackagePattern); 120 | if (nameMatch) { 121 | var user = nameMatch[1]; 122 | var pkg = nameMatch[2]; 123 | if (encodeURIComponent(user) === user && encodeURIComponent(pkg) === pkg) { 124 | return done(warnings, errors); 125 | } 126 | } 127 | 128 | errors.push("name can only contain URL-friendly characters"); 129 | } 130 | 131 | return done(warnings, errors); 132 | }); 133 | ``` 134 | 135 | ## 总结 136 | 137 | 1. 实话看源码比看文档的规则清楚多了。 138 | 139 | 2. 如果要写一个脚手架啥的也可以查看这种规则去写或者可以直接使用。 140 | -------------------------------------------------------------------------------- /.vuepress/public/lib/knowcess/knowcess.css: -------------------------------------------------------------------------------- 1 | .knowcess { 2 | display: flex; 3 | flex-direction: column; 4 | position: relative; 5 | max-height: 90vh; 6 | overflow: hidden; 7 | } 8 | 9 | .knowcess-source { 10 | flex: 1; 11 | overflow: hidden; 12 | position: relative; 13 | background: #2d2d2d; 14 | } 15 | 16 | .knowcess-source .knowcess-line { 17 | position: absolute; 18 | left: 0; 19 | right: 0; 20 | top: 0; 21 | margin-top: 10px; /* same .knowcess-code padding-top */ 22 | background: rgba(255, 255, 255, 0.2); 23 | z-index: 1; 24 | line-height: 1.5; /* same .knowcess-code line-height */ 25 | font-size: 1em; /* same .knowcess-code font-size */ 26 | opacity: 0; 27 | } 28 | 29 | .knowcess-source .knowcess-line::before { 30 | font-size: 1em; 31 | content: '33'; 32 | color: transparent; 33 | } 34 | 35 | .knowcess-source .knowcess-code { 36 | padding: 10px; 37 | position: relative; 38 | } 39 | 40 | .knowcess-source .knowcess-code pre[class*='language-'] { 41 | margin: 0px; 42 | padding: 0px; 43 | } 44 | 45 | .knowcess-interactive { 46 | display: flex; 47 | flex-direction: column; 48 | background: #f1f1f1; 49 | } 50 | 51 | .knowcess-stacks { 52 | display: flex; 53 | flex-direction: column; 54 | } 55 | 56 | .knowcess-stacks .knowcess-stacks-item { 57 | display: flex; 58 | border-bottom: 1px solid #fff; 59 | } 60 | 61 | .knowcess-stacks .knowcess-stacks-label { 62 | box-sizing: border-box; 63 | width: 6rem; 64 | height: 35px; 65 | line-height: 35px; 66 | padding: 0px 5px; 67 | background: #ddd; 68 | } 69 | 70 | .knowcess-stacks .knowcess-stacks-value { 71 | box-sizing: border-box; 72 | flex: 1; 73 | height: 35px; 74 | line-height: 35px; 75 | padding: 0px 5px; 76 | background: #f1f1f1; 77 | overflow-x: auto; 78 | white-space: nowrap; 79 | } 80 | 81 | .knowcess-stacks .knowcess-stacks-value span { 82 | display: inline-block; 83 | position: relative; 84 | box-sizing: border-box; 85 | height: 26px; 86 | line-height: 26px; 87 | background: #ddd; 88 | padding: 0px 5px; 89 | margin-right: 5px; 90 | white-space: nowrap; 91 | opacity: 0; 92 | } 93 | 94 | .knowcess-action { 95 | display: flex; 96 | justify-content: space-between; 97 | background: #ddd; 98 | padding: 5px; 99 | } 100 | 101 | .knowcess-action span { 102 | cursor: pointer; 103 | } 104 | 105 | .knowcess-action span svg { 106 | vertical-align: middle; 107 | } 108 | 109 | .knowcess-action .knowcess-action-play { 110 | display: block; 111 | } 112 | 113 | .knowcess-action .knowcess-action-pause { 114 | display: none; 115 | } 116 | 117 | .knowcess-commentary { 118 | width: 80%; 119 | background: rgba(0, 0, 0, 0.77); 120 | border-radius: 7px; 121 | padding: 1em; 122 | color: #fff; 123 | position: absolute; 124 | left: 50%; 125 | top: 50%; 126 | font-size: 1.5rem; 127 | transform: translate(-50%, -50%); 128 | line-height: 1.5; 129 | opacity: 0; 130 | } 131 | 132 | /* horizontal layout */ 133 | .knowcess-horizontal { 134 | flex-direction: row; 135 | } 136 | 137 | .knowcess-horizontal .knowcess-source { 138 | flex: 2; 139 | } 140 | 141 | .knowcess-horizontal .knowcess-interactive { 142 | flex: 1; 143 | overflow: hidden; 144 | } 145 | 146 | .knowcess-horizontal .knowcess-stacks { 147 | flex: 1; 148 | overflow-y: auto; 149 | } 150 | 151 | .knowcess-horizontal .knowcess-stacks .knowcess-stacks-item { 152 | flex-direction: column; 153 | } 154 | 155 | .knowcess-horizontal .knowcess-stacks .knowcess-stacks-label { 156 | width: 100%; 157 | } 158 | 159 | .knowcess-horizontal .knowcess-stacks .knowcess-stacks-value { 160 | display: flex; 161 | flex-direction: column-reverse; 162 | max-height: calc(90vh - 35px - 42px); 163 | overflow-y: auto; 164 | } 165 | 166 | .knowcess-horizontal .knowcess-stacks .knowcess-stacks-value span { 167 | display: block; 168 | margin: 5px 0px; 169 | } 170 | -------------------------------------------------------------------------------- /blogs/node/tinylib-analysis/koa-compose.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: koa-compose 源码分析-洋葱模型串联中间件 3 | date: 2021-11-09 4 | tags: 5 | - Node 6 | categories: 7 | - 后端 8 | - 源码分析 9 | --- 10 | 11 | ## 前言 12 | 13 | 先来看下洋葱中间件机制,这种灵活的中间件机制也让 `koa` 变得非常强大。 14 | 15 | ![1](https://raw.githubusercontent.com/haiweilian/tinylib-analysis/main/003.koa-compose/.doc/images/1.png) 16 | 17 | ## 源码 18 | 19 | ### 核心实现 20 | 21 | 可以看到只有短短的几十行,本质上就是一个嵌套的高阶函数,外层的中间件嵌套着内层的中间件。利用递归的机制一层嵌套一层,调用 next 之前是 _递(req)_,之后是 _归(res)_。 22 | 23 | ```js 24 | function compose(middleware) { 25 | return function (context, next) { 26 | // last called middleware # 27 | let index = -1; 28 | // 从下标为 0 开始执行中间件。 29 | return dispatch(0); 30 | 31 | function dispatch(i) { 32 | if (i <= index) return Promise.reject(new Error("next() called multiple times")); 33 | index = i; 34 | // 找出数组中存放的相应的中间件 35 | let fn = middleware[i]; 36 | 37 | // 不存在返回,最后一个中间件调用 next 也不会报错。 38 | if (i === middleware.length) fn = next; 39 | if (!fn) return Promise.resolve(); 40 | 41 | try { 42 | return Promise.resolve( 43 | // 执行当前中间件 44 | fn( 45 | // 第一个参数是 ctx。 46 | context, 47 | // 第二个参数是 next,代表下一个中间件。 48 | dispatch.bind(null, i + 1) 49 | ) 50 | ); 51 | } catch (err) { 52 | return Promise.reject(err); 53 | } 54 | } 55 | }; 56 | } 57 | ``` 58 | 59 | ### Koa 示例 60 | 61 | 简单写个示例看它是如何在 `Koa` 中应用的。 62 | 63 | 首先通过 `use` 收集了所有的中间件,在执行的时候当前中间的 `next` 参数是下一个中间件,那么执行 `next` 自然就进入了下一个中间件。 64 | 65 | 其次把这种调用行为看做递归行为,当我们达到终点的时候(最后一个),发生回溯行为直到最初的调用。 66 | 67 | ```js 68 | const compose = require("../index"); 69 | 70 | class App { 71 | middlewares = []; 72 | use(fn) { 73 | this.middlewares.push(fn); 74 | } 75 | run() { 76 | compose(this.middlewares)(); 77 | } 78 | } 79 | 80 | const app = new App(); 81 | 82 | // 收集中间件 83 | app.use(async (ctx, next) => { 84 | console.log(1); 85 | await next(); 86 | console.log(6); 87 | }); 88 | app.use(async (ctx, next) => { 89 | console.log(2); 90 | await next(); 91 | console.log(5); 92 | }); 93 | app.use(async (ctx, next) => { 94 | console.log(3); 95 | await next(); 96 | console.log(4); 97 | }); 98 | 99 | // 执行中间件 100 | app.run(); // 1->2->3->4->5->6 101 | ``` 102 | 103 | ### VueRouter 示例 104 | 105 | 通过上面我们会发现调用 `next` 尤为重要,那么还在那见过 `next` 参数呢? 106 | 107 | 如果知道 VueRouter 的使用,那么在导航守卫中有 `next` 如果定义了未调用是不会进入下一个路由的。 108 | 109 | 其实在 VueRouter 并不是使用递归去实现的,而是巧妙的利用了 `Promise` 链。 110 | 111 | 如下有个简单的实现,把守卫函数都包装成 `Promise`,并且定义 `next` 函数,只有调用 `next` 函数才会执行 `resolve()`,然后使用 `reduce` 依次追加上 `promise.then` 实现串联。 112 | 113 | ```js 114 | class VueRouter { 115 | guards = []; 116 | beforeEach(guard) { 117 | this.guards.push(guardToPromiseFn(guard)); 118 | } 119 | run() { 120 | runGuardQueue(this.guards); 121 | } 122 | } 123 | 124 | const router = new VueRouter(); 125 | router.beforeEach((to, from, next) => { 126 | console.log(1); 127 | next(); 128 | }); 129 | router.beforeEach((to, from) => { 130 | console.log(2); 131 | }); 132 | 133 | router.run(); // 1 -> 2 134 | 135 | // 串行执行守卫 136 | function runGuardQueue(guards) { 137 | // Promise.resolve().then(() => guard1()).then(() => guard2()) 138 | // guard() 执行后返回的 Promise 139 | return guards.reduce((promise, guard) => promise.then(() => guard()), Promise.resolve()); 140 | } 141 | 142 | // 把守卫包装成 Promise 143 | function guardToPromiseFn(guard, to, from) { 144 | return () => { 145 | return new Promise((resolve, reject) => { 146 | // 定义 next ,当执行 next 的时候这个 Promise 才会从 pending -> resolve 147 | const next = () => resolve(); 148 | // 执行守卫函数并把 next 函数传递过去 149 | guard(to, from, next); 150 | // 如果守卫函数没有定义 next,默认执行 next 151 | if (guard.length < 3) next(); 152 | }); 153 | }; 154 | } 155 | ``` 156 | 157 | ## 总结 158 | 159 | 复习一遍,简单实现两个示例。 160 | -------------------------------------------------------------------------------- /blogs/vue/vue2-vuex-analysis.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Vuex 原理分析,实现响应式的状态管理 3 | date: 2020-05-08 4 | tags: 5 | - Vue 6 | - Vuex 7 | categories: 8 | - 前端 9 | - 源码分析 10 | --- 11 | 12 | 简单实现,实现最基础的功能。先来一张官网图加深下印象。 13 | 14 | ![](./image/vue2-vuex-analysis/vuex.png) 15 | 16 | ## 查看示例 17 | 18 | [查看示例](https://github.com/haiweilian/demos/tree/master/Vue/vuex-simple-imp) 19 | 20 | ## 初始化项目 21 | 22 | 先用 vue-cli 初始一个 vue 项目,简单的编写一下 vuex 的代码,使它能够正常的运行起来。 23 | 24 | ```js 25 | // store/index.js 26 | import Vue from "vue"; 27 | import Vuex from "vuex"; 28 | 29 | Vue.use(Vuex); 30 | 31 | export default new Vuex.Store({ 32 | state: { 33 | counter: 0, 34 | }, 35 | getters: { 36 | doubleCount(state) { 37 | return state.counter * 2; 38 | }, 39 | }, 40 | mutations: { 41 | add(state) { 42 | state.counter++; 43 | }, 44 | }, 45 | actions: { 46 | add({ commit }) { 47 | setTimeout(() => { 48 | commit("add"); 49 | }, 1000); 50 | }, 51 | }, 52 | }); 53 | ``` 54 | 55 | ```vue 56 | 57 | 67 | 70 | ``` 71 | 72 | ## 核心概念 73 | 74 | 分析下上面的代码都是什么作用。 75 | 76 | - state 77 | 78 | `state` 保存状态,初始值是定义在 `state` 里的。使用 `$store.state.xxx` 访问。 79 | 80 | - mutations 81 | 82 | `mutations` 同步修改状态。使用 `$store.commit('add')` 触发。 83 | 84 | - actions 85 | 86 | `actions` 异步修改状态。使用 `$store.dispatch('add')` 触发。 87 | 88 | - getters 89 | 90 | `getters` 派生出新状态。使用 `$store.getters.xxx` 访问。 91 | 92 | ## 核心实现 93 | 94 | ### 需求分析 95 | 96 | 好了,已经知道了 vuex 的用法了,分析下需要实现的功能。 97 | 98 | - 实现一个插件:创建 `Store` 类并把 `$store` 挂载到 Vue 原型上。 99 | - 具体功能实现: 100 | - 实现响应式的 `state`。 101 | - 实现 `commit('type')` 方法,根据 `type` 执行对应的 `mutations`。 102 | - 实现 `dispatch('type')` 方法,根据 `type` 执行对应的 `actions`。 103 | - 实现 `getters`,对`state` 做派生。 104 | 105 | ### 实现 Store 106 | 107 | 创建一个 vuex.js 替换掉 vuex。 108 | 109 | ```js 110 | // 在 store 文件下创建一个 vuex.js 替换vuex。 111 | // import Vuex from 'vuex' 112 | import Vuex from "./vuex"; 113 | ``` 114 | 115 | 创建一个插件,并实现 `Store` 类,并把 `$store` 挂载到 Vue 原型上。 116 | 117 | ```javascript 118 | // store/vuex.js 119 | let Vue; // bind on install 120 | 121 | class Store { 122 | constructor(options = {}) {} 123 | } 124 | 125 | // Vue 插件的实现方式,必须导出一个 install 函数。Vue.use() 的时候自动调用。 126 | function install(_Vue) { 127 | Vue = _Vue; 128 | 129 | Vue.mixin({ 130 | // 在实例初始化之后再执行,这里是为了延迟执行。 131 | // 因为是为了保证 Vue 的实例存在,用于在原型上挂载 $store。 132 | beforeCreate() { 133 | if (this.$options.store) { 134 | Vue.prototype.$store = this.$options.store; 135 | } 136 | }, 137 | }); 138 | } 139 | 140 | export default { Store, install }; 141 | ``` 142 | 143 | ### 实现 state 144 | 145 | `vuex` 是利用 `vue` 在做数据响应机制的,所以只需要创建一个 `vue` 实例。 146 | 147 | ```js 148 | class Store { 149 | constructor(options = {}) { 150 | // 利用 Vue 实现数据响应。 151 | this.vm = new Vue({ 152 | data: { 153 | // $ 和 _ 开头的属性不会被代理,也就是不能通过 vm.xxx 访问。 154 | $$state: options.state, 155 | }, 156 | }); 157 | } 158 | 159 | // 当访问 $store.state 的时候返回隐藏在内部的状态。 160 | get state() { 161 | return this.vm._data.$$state; 162 | } 163 | 164 | // 当尝试直接取修改 state 的时候,抛出错误。 165 | set state(v) { 166 | console.error("不能修改State"); 167 | } 168 | } 169 | ``` 170 | 171 | ### 实现 mutations 172 | 173 | `mutations` 是通过 `commit` 执行触发的。 174 | 175 | ```js 176 | class Store { 177 | constructor(options = {}) { 178 | this._mutations = options.mutations; 179 | } 180 | // 实现 commit 方法,执行 mutations 中的处理函数。 181 | commit = (type, payload) => { 182 | const fn = this._mutations[type]; 183 | if (fn) { 184 | fn(this.state, payload); 185 | } else { 186 | console.error("未知mutations类型"); 187 | } 188 | }; 189 | } 190 | ``` 191 | 192 | ### 实现 actions 193 | 194 | `actions` 是通过 `dispatch` 执行触发的。 195 | 196 | ```js 197 | class Store { 198 | constructor(options = {}) { 199 | this._actions = options.actions; 200 | } 201 | // 实现 dispatch 方法,执行 actions 中的处理函数。 202 | dispatch = (type, payload) => { 203 | const fn = this._actions[type]; 204 | if (fn) { 205 | fn(this, payload); 206 | } else { 207 | console.error("未知actions类型"); 208 | } 209 | }; 210 | } 211 | ``` 212 | 213 | ### 实现 getters 214 | 215 | 当 `getters` 依赖的 `state` 的时候会自动会触发更新,其实就是一个计算属性。 216 | 217 | ```js 218 | class Store { 219 | constructor(options = {}) { 220 | this.getters = {}; 221 | 222 | // 把 getters 的属性添加为计算属性。 223 | // 当依赖的 state 变化的时候,getters 就会更新。 224 | const computed = {}; 225 | Object.entries(options.getters).forEach(([key, value]) => { 226 | computed[key] = () => value(this.state); 227 | // 当访问 getters.xxx 的时候,返回对应的计算属性。 228 | Object.defineProperty(this.getters, key, { 229 | get: () => this.vm[key], 230 | }); 231 | }); 232 | 233 | // 利用 Vue 实现数据响应。 234 | this.vm = new Vue({ 235 | computed, 236 | }); 237 | } 238 | } 239 | ``` 240 | -------------------------------------------------------------------------------- /blogs/node/tinylib-analysis/vue-dev-server.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: vue-dev-server 源码分析-从"玩具 Vite"去理解 Vite 原理 3 | date: 2021-11-08 4 | tags: 5 | - Node 6 | categories: 7 | - 后端 8 | - 源码分析 9 | --- 10 | 11 | ## 前言 12 | 13 | 项目地址 。 14 | 15 | ## 示例 16 | 17 | **运行命令** 18 | 19 | ```sh 20 | npm i @vue/dev-server 21 | npx vue-dev-server 22 | ``` 23 | 24 | **如何工作** 25 | 26 | - 浏览器请求导入作为原生 ES 模块导入 - 没有捆绑。 27 | 28 | - 服务器拦截对 \*.vue 文件的请求,即时编译它们,然后将它们作为 JavaScript 发回。 29 | 30 | - 对于提供在浏览器中工作的 ES 模块构建的库,只需直接从 CDN 导入它们。 31 | 32 | - 导入到 .js 文件中的 npm 包(仅包名称)会即时重写以指向本地安装的文件。 目前,仅支持 vue 作为特例。 其他包可能需要进行转换才能作为本地浏览器目标 ES 模块公开。 33 | 34 | ## 环境 35 | 36 | 配置 `launch.json` 或直接在 `package.json` 调试脚本。 37 | 38 | ## 源码 39 | 40 | ### 搭建静态服务 41 | 42 | 开启一个本地服务用的 [express](https://github.com/expressjs/express)。`app.use(middleware)` 是添加中间件,每个服务都会经过此中间件。 43 | 44 | ```js 45 | // bin/vue-dev-server.js 46 | const express = require("express"); 47 | const { vueMiddleware } = require("../middleware"); 48 | 49 | const app = express(); 50 | const root = process.cwd(); 51 | 52 | // 自定义中间件 53 | app.use(vueMiddleware()); 54 | 55 | // 目录作为静态资源 56 | app.use(express.static(root)); 57 | 58 | app.listen(3000, () => { 59 | console.log("server running at http://localhost:3000"); 60 | }); 61 | ``` 62 | 63 | ### 入口请求页面 64 | 65 | 启动服务之后看 `test/index.html` 的内容当做入口去解析。这也是现在 `vite` 一直采用的方式使用 `html` 做为入口。 66 | 67 | ```html 68 | 69 | 70 | 71 | 72 | 73 | Vue Dev Server 74 | 75 | 76 |
77 | 81 | 82 | 83 | ``` 84 | 85 | ### 处理 \*.js 86 | 87 | 在中间件里对每个请求处理,下面简写代码先不考虑缓存,后面再看怎么实现缓存。 88 | 89 | 从入口开启判断 `js` 文件,做的事情就是把 `js` 文件解析成 `ast` 并处理 `import` 语句。 90 | 91 | ```js 92 | if (req.path.endsWith(".js")) { 93 | // 当前 js 结尾,这里指 main.js 入口 94 | // 读取文件内容并转换 import 语句,最后在加入缓存 95 | const result = await readSource(req); 96 | out = transformModuleImports(result.source); 97 | send(res, out, "application/javascript"); 98 | } 99 | ``` 100 | 101 | 转成 `ast` 解析文件中的 `import` 语句,这里用的 [recast](https://github.com/benjamn/recast),用什么无所谓只要能解析。如果要学习 `ast` 还是推荐从 `babel` 入手毕竟资料多点。 102 | 103 | 只处理了 `npm` 包的路径,因为在浏览器中 `import vue from 'vue'` 并不知道是一个包。通过 [validate-npm-package-name](https://github.com/npm/validate-npm-package-name) 判断是不是 `npm` 包,加一个特殊的路径标识标记用于后续的判断。 104 | 105 | ```js 106 | // ./transformModuleImports.js 107 | function transformModuleImports(code) { 108 | const ast = recast.parse(code); 109 | recast.types.visit(ast, { 110 | // 遍历所有的 Import 声明语句 111 | visitImportDeclaration(path) { 112 | const source = path.node.source.value; 113 | // 处理 npm 包的路径, vue -> /__modules/vue 114 | // 因为实际代理的没有 node_modules 文件夹的 115 | if (!/^\.\/?/.test(source) && isPkg(source)) { 116 | path.node.source = recast.types.builders.literal(`/__modules/${source}`); 117 | } 118 | this.traverse(path); 119 | }, 120 | }); 121 | // 最后再把 ast 转成成 代码字符串 返回 122 | return recast.print(ast).code; 123 | } 124 | ``` 125 | 126 | ### 处理 \_\_modules 127 | 128 | 请求完 `main.js` 之后,首先第一个 `import Vue from 'vue'`,经过上面的转换已经变成了 `import Vue from "/__modules/vue"` 内容了。 129 | 130 | ```js 131 | if (req.path.startsWith("/__modules/")) { 132 | // 当是 __modules 开头的时候,证明是 npm 包前面已经处理过了,通过 loadPkg 从 node_modules 读取,在返回文件 133 | const pkg = req.path.replace(/^\/__modules\//, ""); 134 | out = (await loadPkg(pkg)).toString(); 135 | send(res, out, "application/javascript"); 136 | } 137 | ``` 138 | 139 | ### 处理 \*.vue 140 | 141 | 接着 `vue` 文件,使用 `vue` 的 `compiler` 模块去编译 `sfc` 成 `render` 函数后返回。 142 | 143 | ```js 144 | if (req.path.endsWith(".vue")) { 145 | // 把单文件组件编译成 render 函数 146 | const result = await bundleSFC(req); 147 | // 让浏览器用 JavaScript 引擎解析。 148 | // 小知识:浏览器不通过后缀名判断文件类型 149 | send(res, result.code, "application/javascript"); 150 | } 151 | ``` 152 | 153 | 如果文件里再发起请求,那么还是如上述所处理的一样。 154 | 155 | ### LRU 缓存 156 | 157 | 最后再说一下里面的缓存,缓存是一种常用的优化手段,但是也不能无限的缓存,特别是大内容那内存岂不是要爆炸。所以有种方案是 _LRU(Least Recently Used)_,简单来说就是就是把最不常用的从缓存中删除掉的思想。此项目中用的 [lru-cache](https://github.com/isaacs/node-lru-cache) 可以看官方文档。下面用代码简单实现一个缓存。 158 | 159 | 如果了解 `vue` 中 `keep-alive` 组件,就知道 `keep-alive` 能缓存组件和设置最大的缓存个数,就是利用 LRU 思想实现的。 160 | 161 | ```js 162 | // 缓存的 key 集合 163 | const keys = new Set(); 164 | // 最大缓存个数 165 | const max = 5; 166 | 167 | // 添加缓存 168 | function add(key) { 169 | if (keys.has(key)) { 170 | // 如果缓存中存在: 把这个 key 从集合中删除再添加,保持 key 的活跃度。 171 | // 旧:[1, 2, 3] 172 | // add(1) 173 | // 新:[2, 3, 1] 174 | keys.delete(key); 175 | keys.add(key); 176 | } else { 177 | // 如果缓存中存在:则添加一个缓存 178 | keys.add(key); 179 | // 如果缓存个数大于最大的缓存数,则删除最久不用的 key。 180 | // 最久是 key 集合中的第一个,因为每次命中缓存都会从新添加到后面。 181 | if (keys.size > max) { 182 | keys.delete(keys.values().next().value); 183 | } 184 | } 185 | console.log([...keys]); 186 | } 187 | 188 | add(1); // [1] 189 | add(2); // [1, 2] 190 | add(3); // [1, 2, 3] 191 | add(1); // [2, 3, 1] 192 | 193 | add(4); // [2, 3, 1, 4] 194 | add(5); // [2, 3, 1, 4, 5] 195 | add(6); // [3, 1, 4, 5, 6] 最大缓存 5,最久不使用 2 的删除了。 196 | ``` 197 | 198 | ## 总结 199 | 200 | 1. 首先又扩展知识储备 _recast(AST 解析)_、_validate-npm-package-name(检测包名)_ 、_lru-cache(LRU 缓存)_ 的用法和用处。 201 | 202 | 2. 了解 `Vite` 的核心实现原理。 203 | -------------------------------------------------------------------------------- /blogs/vue/lib/vlib-starter-6.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 实践 Vue3 组件库-如何实现 Gen 一个新组件(组件篇三) 3 | date: 2022-08-17 4 | tags: 5 | - Vue 6 | categories: 7 | - 前端 8 | --- 9 | 10 | 现在我们已经完整的开发一个组件了,如果我们再开发一个组件还要从头一个个的创建文件,那么有没有简便的方案呢?当然有我们开发一个生成脚本,一行命令创建一个组件。 11 | 12 | 本篇新增的完整代码可查看单独的分支 [ui-gen](https://github.com/bfehub/vlib-starter/tree/5-ui-gen)。 13 | 14 | > 如果你还不了解这个系列要做什么,那你可以先阅读 [【实践 Vue3 组件库-介绍一下这个系列】](./vlib-starter-1.md) 的介绍,以便你对整个系列有清晰的认识。 15 | 16 | ## 前置文章 17 | 18 | 你需要先阅读以下文章。 19 | 20 | - [实践 Vue3 组件库-基于开源组件库扩展业务组件(组件篇一)](./vlib-starter-4.md) 21 | 22 | - [实践 Vue3 组件库-基于 Vitest/VTU 的组件单元测试(组件篇二)](./vlib-starter-5.md) 23 | 24 | ## 创建文件 25 | 26 | 首先我们要有模板,之前我们重头写过一个 `back-top` 组件,所有我们吧 `back-top` 当成一个基础的版本,创建与组件相同的模板文件,现在我们有以下结构。 27 | 28 | ![1.png](./image/vlib-starter-6/1.png) 29 | 30 | ## 总端交互 31 | 32 | 创建的时候我们可以采用交互的方法输入组件名称和选择模板。我们需要用到 [tsx](https://github.com/esbuild-kit/tsx) 是一个执行 ts 文件的工具;[prompts](https://github.com/terkelg/prompts) 帮助我们创建总端交互的窗口操作。 33 | 34 | ```sh 35 | # 工具包 36 | pnpm add tsx prompts fs-extra -D --filter @bfehub/vlib-ui 37 | # 类型包 38 | pnpm add @types/prompts @types/fs-extra -D --filter @bfehub/vlib-ui 39 | ``` 40 | 41 | 添加脚本,使用 `tsx` 执行文件。 42 | 43 | ```json 44 | // packages/vlib-ui/package.json 45 | { 46 | "scripts": { 47 | "gen": "tsx scripts/generate/component.ts" 48 | } 49 | } 50 | ``` 51 | 52 | 编写初始化函数,我们使用 `prompts` 交互获取要创建的组件名称和创建的类型。并验证组件名称是否符合规范和是否存在。 53 | 54 | ```ts 55 | // packages/vlib-ui/scripts/generate/component.ts 56 | import path from "path"; 57 | import prompts from "prompts"; 58 | import fs from "fs-extra"; 59 | 60 | function checkComponentName(name: string) { 61 | return !/^[a-z][a-z|-]*[a-z]$/.test(name); 62 | } 63 | 64 | function checkComponentExist(name: string) { 65 | return fs.existsSync(path.resolve(process.cwd(), `src/${name}`)); 66 | } 67 | 68 | async function init() { 69 | const result = await prompts([ 70 | { 71 | type: "text", 72 | name: "name", 73 | message: "输入组件名称", 74 | validate: (name) => { 75 | if (checkComponentName(name)) { 76 | return "组件名称请使用(kebab-case)方式命名!"; 77 | } 78 | if (checkComponentExist(name)) { 79 | return `组件库中已经存在名为${name}的组件!`; 80 | } 81 | return true; 82 | }, 83 | }, 84 | { 85 | type: "select", 86 | name: "type", 87 | message: "选择组件模板", 88 | choices: [ 89 | { title: "sfc", value: "sfc" }, 90 | { title: "tsx", value: "tsx" }, 91 | ], 92 | }, 93 | ]); 94 | console.log("参数:", result); 95 | } 96 | init(); 97 | ``` 98 | 99 | ![2.png](./image/vlib-starter-6/2.png) 100 | 101 | ## 生成文件 102 | 103 | 首先我们编写一个静态的配置,代表根据哪个模板生成到哪个文件。 104 | 105 | ```ts 106 | /** 107 | * 获取需要创建的文件 108 | */ 109 | const getCreatedFiles = (name: string, type?: string) => { 110 | return [ 111 | { 112 | file: "index.ts", 113 | template: "index.ts.tpl", 114 | }, 115 | { 116 | file: "README.md", 117 | template: "README.md.tpl", 118 | }, 119 | 120 | { 121 | file: "src/props.ts", 122 | template: "src.props.ts.tpl", 123 | }, 124 | type === "tsx" 125 | ? { 126 | file: `src/${name}.tsx`, 127 | template: "src.component.tsx.tpl", 128 | } 129 | : { 130 | file: `src/${name}.vue`, 131 | template: "src.component.vue.tpl", 132 | }, 133 | { 134 | file: "style/index.scss", 135 | template: "style.index.scss.tpl", 136 | }, 137 | { 138 | file: "__demos__/basic.vue", 139 | template: "__demos__.basic.vue.tpl", 140 | }, 141 | { 142 | file: `__tests__/${name}.test.tsx`, 143 | template: "__tests__.component.test.tsx.tpl", 144 | }, 145 | ]; 146 | }; 147 | ``` 148 | 149 | 先梳理下我们的创建的流程:先读取到模板 -> 获取到输入的数据 -> 模板和数据加载一起渲染 -> 输出文件。渲染可以是任何的模板引擎,因为我们的依赖包里已经有 `loadsh` 了,所以就直接用 `loadsh.template` 渲染。 150 | 151 | ```ts 152 | /** 153 | * 添加一个组件 154 | */ 155 | const addComponent = async (name: string, type?: string) => { 156 | getCreatedFiles(name, type).forEach(async (item) => { 157 | // 读取模板 158 | const tplPath = path.resolve(__dirname, `./template/${item.template}`); 159 | let data = await fs.readFile(tplPath, "utf-8"); 160 | 161 | // 编译模板 162 | const compiled = _.template(data); 163 | data = compiled({ 164 | name, 165 | type, 166 | camelCaseName: _.camelCase(name), 167 | pascalCaseName: _.upperFirst(_.camelCase(name)), 168 | }); 169 | 170 | // 输入模板 171 | const outputPath = path.resolve(process.cwd(), `src/${name}/${item.file}`); 172 | await fs.outputFile(outputPath, data); 173 | console.log(`已创建:${outputPath}`); 174 | }); 175 | }; 176 | ``` 177 | 178 | 在交互完获取到名称和类型后调用生成。 179 | 180 | ```ts 181 | async function init() { 182 | //... 183 | if (!result.name) return; 184 | await addComponent(result.name, result.type); 185 | } 186 | 187 | init(); 188 | ``` 189 | 190 | 执行 `pnpm run gen` 文件就会生成。 191 | 192 | ![3.png](./image/vlib-starter-6/3.png) 193 | 194 | ## 编写模板插值 195 | 196 | 现在我们生成的内容是空的,我们可以复制 `back-top` 的内容去除具体的实现只保留结构部分并用变量替换名称。 197 | 198 | - 找到 `back-top` 用 `<%= name %>` 替换。 199 | 200 | - 找到 `backTop` 用 `<%= camelCaseName %>` 替换。 201 | 202 | - 找到 `BackTop` 用 `<%= pascalCaseName %>` 替换。 203 | 204 | ![4.png](./image/vlib-starter-6/4.png) 205 | 206 | 模板修改完成后重新生成输出结果。 207 | 208 | ![5.png](./image/vlib-starter-6/5.png) 209 | 210 | ## 你可以... 211 | 212 | - 你可以根据本章内容自己实现一遍完善我们的组件库。 213 | 214 | - 如果对你有帮助可以点个 **赞** 和 **关注** 以示鼓励。 215 | -------------------------------------------------------------------------------- /blogs/node/tinylib-analysis/create-vue.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: create-vue 源码分析-Vue 团队公开的全新脚手架工具 3 | date: 2021-11-07 4 | tags: 5 | - Node 6 | categories: 7 | - 后端 8 | - 源码分析 9 | --- 10 | 11 | ## 前言 12 | 13 | Vue 新公开的脚手架工具 。 14 | 15 | ## 示例 16 | 17 | 只需在终端执行 `npm init vue@next` 按照提示即可创建一个项目。 18 | 19 | ## 环境 20 | 21 | ### 调试环境 22 | 23 | 因为涉及到终端交互,所以要用总端运行命令。这里采用调试自动附加功能。这里单独记录一次,这个已经单独提取出来以后引用这个地址。 24 | 25 | [自动附加调试方法](https://github.com/haiweilian/tinylib-analysis/issues/1) 26 | 27 | 1. 在 `VS Code` 里按下 `cmd + shift + p` 打开命令面板。 28 | 29 | 2. 搜索 `toggle auto attach` 并确认。 30 | 31 | ![1](https://raw.githubusercontent.com/haiweilian/tinylib-analysis/main/.docs/debug/images/1.png) 32 | 33 | 3. 选择仅带标志(仅在给定 "--inspect" 标志时自动附加)。 34 | 35 | ![2](https://raw.githubusercontent.com/haiweilian/tinylib-analysis/main/.docs/debug/images/2.png) 36 | 37 | 4. 用 `node --inspect xxx.js` 运行文件进入调试模式。 38 | 39 | ![3](https://raw.githubusercontent.com/haiweilian/tinylib-analysis/main/.docs/debug/images/3.png) 40 | 41 | ### 调试问题 42 | 43 | 调试的时候 `__dirname` 错误问题,解决办法如下。 44 | 45 | ```js 46 | // https://stackoverflow.com/questions/64383909/dirname-is-not-defined-in-node-14-version 47 | // https://github.com/nodejs/help/issues/2907 48 | 49 | import { fileURLToPath } from "url"; 50 | import { dirname } from "path"; 51 | 52 | const __filename = fileURLToPath(import.meta.url); 53 | const __dirname = dirname(__filename); 54 | // 作者:若川 55 | // 链接:https://juejin.cn/post/7018344866811740173 56 | // 来源:稀土掘金 57 | // 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。 58 | ``` 59 | 60 | ## 源码 61 | 62 | ### 解析命令参数 63 | 64 | 开始先解析命令行传递的参数,用到 [minimist](https://github.com/substack/minimist) 库解析用法参考文档。 65 | 66 | ```js 67 | async function init() { 68 | const cwd = process.cwd(); 69 | // http://nodejs.cn/api/process.html#processargv 70 | // https://github.com/substack/minimist 71 | const argv = minimist(process.argv.slice(2), { 72 | alias: { 73 | typescript: ["ts"], 74 | "with-tests": ["tests", "cypress"], 75 | router: ["vue-router"], 76 | }, 77 | boolean: true, 78 | }); 79 | } 80 | ``` 81 | 82 | ### 交互式询问 83 | 84 | 总端交互通过 [prompts](https://github.com/terkelg/prompts) 库实现用法参考文档。 85 | 86 | ```js 87 | // 最后的结果 { key: boolean } 的格式 88 | let result = {}; 89 | 90 | try { 91 | result = await prompts( 92 | [ 93 | // .... 94 | { 95 | name: "needsVuex", 96 | type: () => (isFeatureFlagsUsed ? null : "toggle"), 97 | message: "Add Vuex for state management?", 98 | initial: false, 99 | active: "Yes", 100 | inactive: "No", 101 | }, 102 | { 103 | name: "needsTests", 104 | type: () => (isFeatureFlagsUsed ? null : "toggle"), 105 | message: "Add Cypress for testing?", 106 | initial: false, 107 | active: "Yes", 108 | inactive: "No", 109 | }, 110 | ], 111 | { 112 | onCancel: () => { 113 | throw new Error(red("✖") + " Operation cancelled"); 114 | }, 115 | } 116 | ); 117 | } catch (cancelled) { 118 | process.exit(1); 119 | } 120 | ``` 121 | 122 | 如下选择之后结果保存在 `result` 下输入结果如下。 123 | 124 | ![1](https://raw.githubusercontent.com/haiweilian/tinylib-analysis/main/001.create-vue/.docs/images/1.png) 125 | 126 | ```js 127 | { 128 | projectName: "vue-create-test"; 129 | needsVuex: true; 130 | needsTypeScript: true; 131 | needsTests: false; 132 | needsRouter: true; 133 | needsJsx: true; 134 | } 135 | ``` 136 | 137 | ### 配置 + 模板 = 文件 138 | 139 | 先看一个基础怎么生成的。 140 | 141 | ```js 142 | const templateRoot = path.resolve(__dirname, "template"); 143 | const render = function render(templateName) { 144 | const templateDir = path.resolve(templateRoot, templateName); 145 | // 1、根据传入的路径 在 template 文件下读取对应文件并写入目录 146 | // 2、深度合并两个 package.json 的内容 147 | renderTemplate(templateDir, root); 148 | }; 149 | 150 | // 基础模板 151 | render("base"); 152 | ``` 153 | 154 | ![2](https://raw.githubusercontent.com/haiweilian/tinylib-analysis/main/001.create-vue/.docs/images/2.png) 155 | 156 | 其他的生成都是一样的逻辑,就是读入模板中的片段组合起来最终生成完整的。 157 | 158 | ```js 159 | // Add configs. 160 | if (needsJsx) { 161 | render("config/jsx"); 162 | } 163 | if (needsRouter) { 164 | render("config/router"); 165 | } 166 | if (needsVuex) { 167 | render("config/vuex"); 168 | } 169 | if (needsTests) { 170 | render("config/cypress"); 171 | } 172 | if (needsTypeScript) { 173 | render("config/typescript"); 174 | } 175 | 176 | // Render code template. 177 | // Render entry file (main.js/ts). 178 | // ... 179 | ``` 180 | 181 | ### 友好提示 182 | 183 | 提示不同的包管理工具命令,但是这个需要使用对应的命令去初始化,比如 `npm init`, `yarn init`、 `pnpm init` 184 | 185 | ```js 186 | // Instructions: 187 | // Supported package managers: pnpm > yarn > npm 188 | // Note: until is resolved, 189 | // it is not possible to tell if the command is called by `pnpm init`. 190 | const packageManager = /pnpm/.test(process.env.npm_execpath) 191 | ? "pnpm" 192 | : /yarn/.test(process.env.npm_execpath) 193 | ? "yarn" 194 | : "npm"; 195 | ``` 196 | 197 | 最后还可以使用 [kolorist](https://github.com/marvinhagemeister/kolorist) 包输出一些带颜色的提示语。 198 | 199 | ```js 200 | console.log(`\nDone. Now run:\n`); 201 | if (root !== cwd) { 202 | console.log(` ${bold(green(`cd ${path.relative(cwd, root)}`))}`); 203 | } 204 | console.log(` ${bold(green(getCommand(packageManager, "install")))}`); 205 | console.log(` ${bold(green(getCommand(packageManager, "dev")))}`); 206 | console.log(); 207 | ``` 208 | 209 | ## 总结 210 | 211 | 1. 首先扩展知识储备 _minimist(解析命令参数)_、_prompts(终端交互)_ 、_kolorist(终端颜色)_ 的用法和用处。 212 | 213 | 2. 回顾整个流程从开始的 _参数解析_ 到根据 _解析结果_ 匹配对应的 _预设模板_ 得出 _最终文件_ 的过程,应该有所谓各种生成不过是 _数据 + 模板 = 文件_ 的感慨。 214 | -------------------------------------------------------------------------------- /blogs/css/bem-element-ui.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 从 Element UI 的 Theme 源码来看命名规范 BEM 的应用 3 | date: 2020-01-21 4 | tags: 5 | - Css 6 | - Scss 7 | categories: 8 | - 前端 9 | --- 10 | 11 | 最近自己在写自己的博客页面,想着就参考框架的组织结构,自己是对 element-ui 是比较熟悉的,就拿来参考了。[element-ui](https://github.com/ElementUI/theme-chalk) 的样式是使用 scss 预处理器,和 BEM 思想写的。整体来说组织的还是挺好的,从中可以学到一些封装思想,可以提升一下自己。 12 | 13 | ## 目录结构 14 | 15 | 你可以把[theme-chalk](https://github.com/ElementUI/theme-chalk)的源码下载下来,以便对比着看。下面是几个比较重要的文件(2019-12-21 最新版本为 v.2.13.0)。 16 | 17 | ```sh 18 | ├── gulpfile.js // 打包配置,可以把代码压缩 cssmin() 注释掉,以便查看编译后的代码。 19 | ├── lib // 打包文件存放目录。 20 | ├── src 21 | │ ├── common 22 | │ │ └── var.scss // 全局变量,大小、颜色。 23 | │ ├── mixins 24 | │ │ ├── config.scss // 定义了BEM的分隔符。 25 | │ │ ├── function.scss 26 | │ │ ├── mixins.scss // 主要定义了实现BEM类,也和 function.scss 相关联。 27 | │ │ └── utils.scss 28 | │ ├── radio-group.scss 29 | │ ├── radio.scss 30 | │ ├── ...其他具体的组件... 31 | └── 32 | ``` 33 | 34 | ## BEM 支持 35 | 36 | 源码中最重要的一个概念 `BEM` ,贯穿全局。 37 | 38 | ### BEM 分割符 39 | 40 | 在 `src/mixins/config.scss` 定义了命名空间以及块、元素、修饰的分割符。了解过 `BEM` 的大概都知道,`BEM` 最最重要的是思想,而分隔符可以按照一定的规范去定义,这里不再多说了。 41 | 42 | 命名空间和块。框架都会选择一个相对应的前缀,避免样式冲突,如:`.el-input`。 43 | 44 | ```scss 45 | $namespace: "el"; 46 | ``` 47 | 48 | 元素。使用的 `__`  双下划线,如: `.el-input__inner`。 49 | 50 | ```scss 51 | $element-separator: "__"; 52 | ``` 53 | 54 | 修饰。使用的 `--` 双连字符,如:`.el-input--small`。 55 | 56 | ```scss 57 | $modifier-separator: "--"; 58 | ``` 59 | 60 | 状态。使用的 `is-` 作为前缀,如:`.el-input.is-disabled`。 61 | 62 | ```scss 63 | $state-prefix: "is-"; 64 | ``` 65 | 66 | ### BEM Mixin 67 | 68 | 在 `src/mixins/mixins.scss` 定义了 `@mixin b($block)` 、`@mixin e($element)` 、 `@mixin m($modifier)` 、 `@mixin when($state)` 几个主要的 `mixin`。 用一个源码举例,`src/input.scss` 涉及到了所有的方法,也比较常用。 69 | 70 | mixin 中涉及到的几个知识点: 71 | 72 | - [!global](https://sass-lang.com/documentation/variables#shadowing) 把局部变量设置成全局变量。 73 | 74 | - [@content](https://sass-lang.com/documentation/at-rules/mixin#content-blocks) 把@include mixin 包含的内容块导入到此处。 75 | 76 | - [@at-root](https://sass-lang.com/documentation/at-rules/at-root) 忽略嵌套,从根层级书写。 77 | 78 | #### @mixin b(\$block) 79 | 80 | 定义生成块。参数为块的名称。 81 | 82 | ```scss 83 | @include b(input) { 84 | display: inline-block; 85 | } 86 | ``` 87 | 88 | 编译后。 89 | 90 | ```css 91 | .el-input { 92 | display: inline-block; 93 | } 94 | ``` 95 | 96 | #### @mixin e(\$element) 97 | 98 | 定义生成元素。参数是元素的名称,可以传入多个,`($element1, $element2, ...)`。mixin 中的 `@if hitAllSpecialNestRule` 可以先跳过,下面单说。 99 | 100 | ```scss 101 | @include b(input) { 102 | @include e(inner) { 103 | padding: 0 15px; 104 | } 105 | 106 | @include e((suffix, suffix-inner)) { 107 | position: absolute; 108 | } 109 | } 110 | ``` 111 | 112 | 编译后。 113 | 114 | ```css 115 | .el-input__inner { 116 | padding: 0 15px; 117 | } 118 | 119 | .el-input__suffix, 120 | .el-input__suffix-inner { 121 | position: absolute; 122 | } 123 | ``` 124 | 125 | #### @mixin m(\$modifier) 126 | 127 | 定义生成修饰。参数是修饰的名称,可以传入多个,`($modifier1, $modifier2, ...)`。 128 | 129 | ```scss 130 | @include b(input) { 131 | @include m(medium) { 132 | height: 30px; 133 | } 134 | 135 | @include m((mini, small)) { 136 | height: 20px; 137 | } 138 | } 139 | ``` 140 | 141 | 编译后。 142 | 143 | ```css 144 | .el-input--medium { 145 | height: 30px; 146 | } 147 | 148 | .el-input--mini, 149 | .el-input--small { 150 | height: 20px; 151 | } 152 | ``` 153 | 154 | #### @mixin when(\$state) 155 | 156 | 定义条件状态。参数是状态的名称。 157 | 158 | ```scss 159 | @include b(input) { 160 | @include when(disabled) { 161 | cursor: not-allowed; 162 | } 163 | } 164 | ``` 165 | 166 | 编译后. 167 | 168 | ```css 169 | .el-input.is-disabled { 170 | cursor: not-allowed; 171 | } 172 | ``` 173 | 174 | ### BEM Function 175 | 176 | 在 `@mixin e` 里面有一个判断 `@if hitAllSpecialNestRule($selector)`,它的作用是判断什么情况下使用选择器层级嵌套。 177 | 178 | 在 `bem` 的规范中,推荐的是选择器层级**尽量平级,减少嵌套** 所以也就是在上面的 mixin 中都使用了 `@at-root` 规则 。 179 | 而实际中我们不可避免使用嵌套,在不同的场景下实现样式覆盖。如在不同大小,和不同状态我们是不是应该给下级设置不同的大小和颜色。 180 | 181 | function 涉及到的几个知识点: 182 | 183 | - [inspect](https://sass-lang.com/documentation/modules/meta#inspect) 返回字符串的表示形式。 184 | 185 | - [str-slice](https://sass-lang.com/documentation/modules/string#slice) 截取字符串。 186 | 187 | - [str-index](https://sass-lang.com/documentation/modules/string#index) 包含指定字符串的索引。 188 | 189 | #### containsModifier(\$selector) 190 | 191 | 在修饰符之下嵌套元素。 192 | 193 | ```scss 194 | @include b(input) { 195 | @include m(medium) { 196 | @include e(inner) { 197 | padding: 0 10px; 198 | } 199 | } 200 | } 201 | ``` 202 | 203 | 编译后。 204 | 205 | ```css 206 | .el-input--medium .el-input__inner { 207 | padding: 0 10px; 208 | } 209 | ``` 210 | 211 | #### containWhenFlag(\$selector) 212 | 213 | 在状态下嵌套元素。 214 | 215 | ```scss 216 | @include b(input) { 217 | @include when(disabled) { 218 | @include e(inner) { 219 | color: #eeeeee; 220 | } 221 | } 222 | } 223 | ``` 224 | 225 | 编译后。 226 | 227 | ```css 228 | .el-input.is-disabled .el-input__inner { 229 | color: #eeeeee; 230 | } 231 | ``` 232 | 233 | #### containPseudoClass(\$selector) 234 | 235 | 在伪类下嵌套元素。 236 | 237 | ```scss 238 | @include b(input) { 239 | &:hover { 240 | @include e(inner) { 241 | border: 1px solid blue; 242 | } 243 | } 244 | } 245 | ``` 246 | 247 | 编译后。 248 | 249 | ```scss 250 | .el-input:hover .el-input__inner { 251 | border: 1px solid blue; 252 | } 253 | ``` 254 | 255 | #### hitAllSpecialNestRule(\$selector) 256 | 257 | 以上三种情况都会在特定场景下生效。最后通过或者 `or` 判断。 258 | 259 | ## 参考文献 260 | 261 | 使用 `scss` 和 `bem` 可以很好的组织我们的 css 结构,推荐学习。源码中还有栅格布局`row.scss` 和 `col.scss` 文件写的也很简洁,我现在项目就是采用的这一套生成的。 262 | 263 | 264 | 265 | 266 | -------------------------------------------------------------------------------- /blogs/algorithm/leetcode/622.design-circular-queue.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: LeetCode-622.设计循环队列 3 | date: 2021-03-08 4 | tags: 5 | - LeetCode 6 | categories: 7 | - 算法 8 | --- 9 | 10 | ## 题目描述 11 | 12 | 设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。 13 | 14 | 循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。 15 | 16 | 你的实现应该支持如下操作: 17 | 18 | - `MyCircularQueue(k)`: 构造器,设置队列长度为 k 。 19 | - `Front`: 从队首获取元素。如果队列为空,返回 -1 。 20 | - `Rear`: 获取队尾元素。如果队列为空,返回 -1 。 21 | - `enQueue(value)`: 向循环队列插入一个元素。如果成功插入则返回真。 22 | - `deQueue()`: 从循环队列中删除一个元素。如果成功删除则返回真。 23 | - `isEmpty()`: 检查循环队列是否为空。 24 | - `isFull()`: 检查循环队列是否已满。 25 | 26 | **示例 1:** 27 | 28 | ```md 29 | MyCircularQueue circularQueue = new MyCircularQueue(3); // 设置长度为 3 30 | circularQueue.enQueue(1); // 返回 true 31 | circularQueue.enQueue(2); // 返回 true 32 | circularQueue.enQueue(3); // 返回 true 33 | circularQueue.enQueue(4); // 返回 false,队列已满 34 | circularQueue.Rear(); // 返回 3 35 | circularQueue.isFull(); // 返回 true 36 | circularQueue.deQueue(); // 返回 true 37 | circularQueue.enQueue(4); // 返回 true 38 | circularQueue.Rear(); // 返回 4 39 | ``` 40 | 41 | ## 解法一原生 42 | 43 | **使用原生的数组和方法实现循环队列,只关注功能实现** 44 | 45 | 思路是利用原生数组已有队列的特性,使用 `push` (入队) 和 `shift` (出队) 很容易实现,再判断下数组的长度即可。和题目的描述不一致但能实现全部操作先实现一遍吧。 46 | 47 | **代码实现** 48 | 49 | ```js 50 | /** 51 | * @param {number} k 52 | */ 53 | var MyCircularQueue = function (k) { 54 | this.k = k; // 队列长度 55 | this.queue = []; // 队列 56 | }; 57 | 58 | /** 59 | * @param {number} value 60 | * @return {boolean} 61 | */ 62 | MyCircularQueue.prototype.enQueue = function (value) { 63 | if (this.isFull()) { 64 | return false; 65 | } else { 66 | this.queue.push(value); 67 | return true; 68 | } 69 | }; 70 | 71 | /** 72 | * @return {boolean} 73 | */ 74 | MyCircularQueue.prototype.deQueue = function () { 75 | if (this.isEmpty()) { 76 | return false; 77 | } else { 78 | this.queue.shift(); 79 | return true; 80 | } 81 | }; 82 | 83 | /** 84 | * @return {number} 85 | */ 86 | MyCircularQueue.prototype.Front = function () { 87 | if (this.isEmpty()) { 88 | return -1; 89 | } 90 | return this.queue[0]; 91 | }; 92 | 93 | /** 94 | * @return {number} 95 | */ 96 | MyCircularQueue.prototype.Rear = function () { 97 | if (this.isEmpty()) { 98 | return -1; 99 | } 100 | return this.queue[this.queue.length - 1]; 101 | }; 102 | 103 | /** 104 | * @return {boolean} 105 | */ 106 | MyCircularQueue.prototype.isEmpty = function () { 107 | return this.queue.length === 0; 108 | }; 109 | 110 | /** 111 | * @return {boolean} 112 | */ 113 | MyCircularQueue.prototype.isFull = function () { 114 | return this.queue.length === this.k; 115 | }; 116 | ``` 117 | 118 | ## 解法二模拟 119 | 120 | **在一个固定长度的队列里,入队时添加一个元素,出队时队首往后移一位,如果假溢出那么重头开始存储。** 121 | 122 | ![设计循环队列](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/98113b21d1bb4891a0d6df9593f3740f~tplv-k3u1fbpfcp-zoom-1.image) 123 | 124 | 一个固定长度循环队列的特性,会使用之前已经空闲的空间,如上图 3 所示,当已经到达队列最尾部的时候,前面的还有空间可以使用达到一个循环的效果。 125 | 126 | 1. 在 `js` 中可以使用 `new Array(k)`,元素为 `empty` 长度为 `k` 的一个数组。 127 | 2. 初始的时候队首和队尾的索引都在开头 0 的位置。 128 | 3. 当有元素入队时候,元素是添加在队尾的位置的,之后再把队尾往后移动一位。 129 | 4. 当有元素出队的时候,把队首往后移动一位那么队首之前的元素已经空出来了无用了。 130 | 5. 当入队和出队的时候要记录现有元素的个数,入队加 `1` 、出队减 `1`。用于判断是满队(`count = k`)还是空队(`count = 0`)。 131 | 6. 假溢出问题解决。虽然队首和队尾一直往后移动索引会超过队列的长度,可以利用 _取余_ 让索引重头开始。 132 | 133 | **代码实现** 134 | 135 | ```js 136 | /** 137 | * @param {number} k 138 | */ 139 | var MyCircularQueue = function (k) { 140 | this.k = k; // 队列长度 141 | this.queue = new Array(k); // 指定长度的队列 142 | this.head = 0; // 队首索引 143 | this.tail = 0; // 队尾索引 144 | this.count = 0; // 已入队的元素数量 145 | }; 146 | 147 | /** 148 | * @param {number} value 149 | * @return {boolean} 150 | */ 151 | MyCircularQueue.prototype.enQueue = function (value) { 152 | if (this.isFull()) { 153 | return false; 154 | } 155 | 156 | // 往队尾增加一个元素 157 | this.queue[this.tail] = value; 158 | // 让队尾往后移动一位。(取余操作时为了当假溢出的时候重头开始,达到循环效果) 159 | this.tail = (this.tail + 1) % this.k; 160 | // 队列里的元素数量加一 161 | this.count += 1; 162 | 163 | return true; 164 | }; 165 | 166 | /** 167 | * @return {boolean} 168 | */ 169 | MyCircularQueue.prototype.deQueue = function () { 170 | if (this.isEmpty()) { 171 | return false; 172 | } 173 | 174 | // 让队首往后移动一位,代表出队了一个。 175 | this.head = (this.head + 1) % this.k; 176 | // 队列里的元素数量减一 177 | this.count -= 1; 178 | 179 | return true; 180 | }; 181 | 182 | /** 183 | * @return {number} 184 | */ 185 | MyCircularQueue.prototype.Front = function () { 186 | if (this.isEmpty()) { 187 | return -1; 188 | } 189 | 190 | return this.queue[this.head]; 191 | }; 192 | 193 | /** 194 | * @return {number} 195 | */ 196 | MyCircularQueue.prototype.Rear = function () { 197 | if (this.isEmpty()) { 198 | return -1; 199 | } 200 | 201 | // 队首的索引 + 元素个数 - 1 = 队尾的索引 202 | return this.queue[(this.head + this.count - 1) % this.k]; 203 | }; 204 | 205 | /** 206 | * @return {boolean} 207 | */ 208 | MyCircularQueue.prototype.isEmpty = function () { 209 | return this.count === 0; 210 | }; 211 | 212 | /** 213 | * @return {boolean} 214 | */ 215 | MyCircularQueue.prototype.isFull = function () { 216 | return this.count === this.k; 217 | }; 218 | 219 | // =================================================================== 220 | // ========================== @test ================================== 221 | // =================================================================== 222 | let circularQueue = new MyCircularQueue(3); // 设置长度为 3 223 | console.log(circularQueue.enQueue(1)); // 返回 true 224 | console.log(circularQueue.enQueue(2)); // 返回 true 225 | console.log(circularQueue.enQueue(3)); // 返回 true 226 | console.log(circularQueue.enQueue(4)); // 返回 false,队列已满 227 | console.log(circularQueue.Rear()); // 返回 3 228 | console.log(circularQueue.isFull()); // 返回 true 229 | console.log(circularQueue.deQueue()); // 返回 true 230 | console.log(circularQueue.enQueue(4)); // 返回 true 231 | console.log(circularQueue.Rear()); // 返回 4 232 | ``` 233 | -------------------------------------------------------------------------------- /blogs/server/nginx-deploy.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Nginx 的基础使用和部署 3 | date: 2020-01-22 4 | updated: 2020-01-22 5 | tags: 6 | - Nginx 7 | categories: 8 | - 后端 9 | --- 10 | 11 | ## 安装 12 | 13 | 安装使用各平台默认包管理安装比较好,会自动解决依赖,比源码安装方便很多。 14 | 15 | - CentOS 16 | 17 | ```sh 18 | sudo yum install nginx 19 | ``` 20 | 21 | - Ubuntu 22 | 23 | ```sh 24 | sudo apt install nginx 25 | ``` 26 | 27 | 安装完成之后执行 `sudo nginx` 启动。就可以通过服务器的 _公网 ip_ ,访问到 nginx 的默认页面。 28 | 29 | ## 常用命令 30 | 31 | 启动 32 | 33 | ```sh 34 | nginx 35 | ``` 36 | 37 | 重新加载配置 38 | 39 | ```sh 40 | nginx -s reload 41 | ``` 42 | 43 | 重启(重新打开日志文件) 44 | 45 | ```sh 46 | nginx -s reopen 47 | ``` 48 | 49 | 停止(快速停止) 50 | 51 | ```sh 52 | nginx -s stop 53 | ``` 54 | 55 | 退出(完整有序的停止) 56 | 57 | ```sh 58 | nginx -s quit 59 | ``` 60 | 61 | 测试配置是否有错误(测试通过后,就可以放心重新启动了)。 62 | 63 | ```sh 64 | nginx -t 65 | ``` 66 | 67 | ## 目录结构 68 | 69 | 如果通过包管理安装默认路径在 `/etc/nginx`。 70 | 71 | 在 `nginx.conf` 默认配置文件里面,有这么一句是把文件配置文件拆分了。 72 | 73 | ```sh 74 | # ... 75 | include /etc/nginx/conf.d/*.conf; 76 | # ... 77 | ``` 78 | 79 | 这样我们就可以不修改主配置,在 `/etc/nginx/conf.d` 文件夹下建立一个 `xxx.com.conf`文件, 把一个站点的配置分开来便于管理。 80 | 81 | ## 部署&https 82 | 83 | https 证书各大云服务商都有免费申请的,相关教程可以看提供的文档。 84 | 85 | 这里如阿里云[阿里云申请免费 SSL 证书](https://yq.aliyun.com/articles/637307) & [在 Nginx/Tengine 服务器上安装证书](https://help.aliyun.com/document_detail/98728.html) 86 | 87 | 下面就直接放*代理静态*和*代理端口*两种代理的配置示例。**示例是我的真实域名,按照自己的修改即可** 88 | 89 | ### 代理静态 90 | 91 | 代理静态,在 `/etc/nginx/conf.d` 下新建 `www.haiweilian.com.conf`,写入以下内容。 92 | 93 | ```sh 94 | # For more information on configuration, see: 95 | # * Official English Documentation: http://nginx.org/en/docs/ 96 | # * Official Russian Documentation: http://nginx.org/ru/docs/ 97 | 98 | server { 99 | # 监听的端口 100 | listen 80; 101 | listen [::]:80; 102 | # 访问的域名 103 | server_name haiweilian.com www.haiweilian.com; 104 | # 静态资源目录 105 | root /home/haiweilian.github.io; 106 | 107 | # Load configuration files for the default server block. 108 | include /etc/nginx/default.d/*.conf; 109 | 110 | # 将所有http请求通过rewrite重定向到https。 111 | # 如果不开启https,注释掉此配置即可。 112 | rewrite ^(.*)$ https://$host$1 permanent; 113 | 114 | location / { 115 | 116 | } 117 | 118 | error_page 404 /404.html; 119 | location = /40x.html { 120 | } 121 | 122 | error_page 500 502 503 504 /50x.html; 123 | location = /50x.html { 124 | } 125 | } 126 | 127 | # Settings for a TLS enabled server. 128 | # 如果不开启https,注释掉此配置即可。 129 | 130 | server { 131 | # 监听的端口 132 | listen 443 ssl http2; 133 | listen [::]:443 ssl http2; 134 | # 访问的域名 135 | server_name haiweilian.com www.haiweilian.com; 136 | # 静态资源目录 137 | root /home/haiweilian.github.io; 138 | 139 | # https 证书相关 140 | ssl_certificate "/etc/nginx/cert/www.haiweilian.com.pem"; 141 | ssl_certificate_key "/etc/nginx/cert/www.haiweilian.com.key"; 142 | ssl_session_timeout 5m; 143 | ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; 144 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 145 | ssl_prefer_server_ciphers on; 146 | 147 | # Load configuration files for the default server block. 148 | include /etc/nginx/default.d/*.conf; 149 | 150 | location / { 151 | # 如果找不到资源从定向到,如 vue 开启 history 模式。 152 | try_files $uri $uri/ /index.html; 153 | } 154 | 155 | error_page 404 /404.html; 156 | location = /40x.html { 157 | } 158 | 159 | error_page 500 502 503 504 /50x.html; 160 | location = /50x.html { 161 | } 162 | } 163 | ``` 164 | 165 | ## 代理端口 166 | 167 | 代理端口,在 `/etc/nginx/conf.d` 下新建 `api.haiweilian.com.conf`,写入以下内容。 168 | 169 | ```sh 170 | # For more information on configuration, see: 171 | # * Official English Documentation: http://nginx.org/en/docs/ 172 | # * Official Russian Documentation: http://nginx.org/ru/docs/ 173 | 174 | server { 175 | # 监听的端口 176 | listen 80; 177 | listen [::]:80; 178 | # 访问的域名 179 | server_name api.haiweilian.com; 180 | # root _; 181 | 182 | # Load configuration files for the default server block. 183 | include /etc/nginx/default.d/*.conf; 184 | 185 | # 将所有http请求通过rewrite重定向到https。 186 | # 如果不开启https,注释掉此配置即可。 187 | rewrite ^(.*)$ https://$host$1 permanent; 188 | 189 | location / { 190 | 191 | } 192 | 193 | error_page 404 /404.html; 194 | location = /40x.html { 195 | } 196 | 197 | error_page 500 502 503 504 /50x.html; 198 | location = /50x.html { 199 | } 200 | } 201 | 202 | # Settings for a TLS enabled server. 203 | # 如果不开启https,注释掉此配置即可。 204 | 205 | server { 206 | # 监听的端口 207 | listen 443 ssl http2; 208 | listen [::]:443 ssl http2; 209 | # 访问的域名 210 | server_name api.haiweilian.com; 211 | # root _; 212 | 213 | # https 证书相关 214 | ssl_certificate "/etc/nginx/cert/api.haiweilian.com.pem"; 215 | ssl_certificate_key "/etc/nginx/cert/api.haiweilian.com.key"; 216 | ssl_session_timeout 5m; 217 | ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; 218 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2; 219 | ssl_prefer_server_ciphers on; 220 | 221 | # Load configuration files for the default server block. 222 | include /etc/nginx/default.d/*.conf; 223 | 224 | location / { 225 | # 增加代理指向端口 226 | proxy_pass http://localhost:7001; 227 | proxy_set_header Upgrade $http_upgrade; 228 | proxy_set_header Connection 'upgrade'; 229 | proxy_set_header Host $host; 230 | proxy_cache_bypass $http_upgrade; 231 | } 232 | 233 | error_page 404 /404.html; 234 | location = /40x.html { 235 | } 236 | 237 | error_page 500 502 503 504 /50x.html; 238 | location = /50x.html { 239 | } 240 | } 241 | ``` 242 | 243 | 添加完后执行 `nginx -t` 测试配置,成功之后执行 `nginx -s reload` 重新加载配置。 244 | 245 | ## 结束总结 246 | 247 | 在搞懂 nginx 的目录和部署步骤之后遇到的错误和优化查资料就可以自己解决了。 248 | -------------------------------------------------------------------------------- /blogs/vue/vben/vben-deep-1.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Vben Admin 深入理解之插件、环境变量的设计 3 | date: 2021-08-28 4 | tags: 5 | - Vue 6 | categories: 7 | - 前端 8 | --- 9 | 10 | 个人认为 [Vben Admin](https://github.com/anncwb/vue-vben-admin) 是一个很好的项目,完成度和文档都比较完善,技术栈也是一直保持最新的。当我在跟着文档操作一遍后,却对很多东西不明所以而没有立即投入项目中使用。 11 | 12 | 当然也必要等全部理解完了再去使用,用着看着呗毕竟优秀点很多。指南部分文档对怎么使用写的很清楚而源码中高度的封装,而这部分也常常是需要根据实际业务修改的地方,所以决定把先把这部分的源码看一下再使用。 13 | 14 | ### 疑问点 15 | 16 | 本部分主要分析环境变量的处理和怎么使用一个 Vite 插件。 17 | 18 | 在 `Vite` 中只有以 `VITE_` 开头的变量会被嵌入到客户端侧的包中。新加了一个规则是以 `VITE_GLOB_` 开头的的变量,在打包的时候会被加入 `_app.config.js`。 19 | 20 | - 那么怎么处理的环境变量做了什么转换、做了什么扩展? 21 | - 那么 `_app.config.js` 是怎么生成的、内容怎么写入的? 22 | 23 | ### 解析环境变量文件 24 | 25 | 首先从 `vite.config.ts` 入口开始。 26 | 27 | 使用 `loadEnv` 读取对应的环境配置文件(.env + .env.\[mode\] + .env.\[mode\].local),可在后续配置中使用。加载方式详情[环境变量和模式](https://cn.vitejs.dev/guide/env-and-mode.html)。 28 | 29 | ```ts 30 | // vite.config.ts 31 | import { loadEnv } from 'vite'; 32 | import { wrapperEnv } from './build/utils'; 33 | export default ({ command, mode }: ConfigEnv): UserConfig => { 34 | const root = process.cwd(); 35 | 36 | // 加载对应模式的环境变量 37 | const env = loadEnv(mode, root); 38 | 39 | // 解析处理环境变量的值 40 | const viteEnv = wrapperEnv(env); 41 | }; 42 | }; 43 | ``` 44 | 45 | 使用 `wrapperEnv` 对读取到内容做进一步处理。 46 | 47 | ```ts 48 | // build/utils.ts 49 | export function wrapperEnv(envConf: Recordable): ViteEnv { 50 | const ret: any = {}; 51 | 52 | for (const envName of Object.keys(envConf)) { 53 | let realName = envConf[envName].replace(/\\n/g, "\n"); 54 | 55 | // 转化布尔值 56 | realName = realName === "true" ? true : realName === "false" ? false : realName; 57 | 58 | // 转化端口配置 59 | if (envName === "VITE_PORT") { 60 | realName = Number(realName); 61 | } 62 | 63 | // 转化代理配置 64 | if (envName === "VITE_PROXY") { 65 | try { 66 | realName = JSON.parse(realName); 67 | } catch (error) {} 68 | } 69 | 70 | ret[envName] = realName; 71 | 72 | // 为什么要赋值到 process.env 呢?后续有什么用? 73 | if (typeof realName === "string") { 74 | process.env[envName] = realName; 75 | } else if (typeof realName === "object") { 76 | process.env[envName] = JSON.stringify(realName); 77 | } 78 | } 79 | return ret; 80 | } 81 | ``` 82 | 83 | 然后把解析后 `viteEnv` 传入插件列表中使用。 84 | 85 | ```ts 86 | // vite.config.ts 87 | export default ({ command, mode }: ConfigEnv): UserConfig => { 88 | const viteEnv = wrapperEnv(env); 89 | return { 90 | plugins: createVitePlugins(viteEnv, isBuild), 91 | }; 92 | }; 93 | ``` 94 | 95 | ### 是怎么生成 \_app.config.js 96 | 97 | 经过查找逻辑,在执行完 `build` 之后,使用 [esno](https://github.com/antfu/esno) 执行了一个脚本去生成的。 98 | 99 | ```sh 100 | "build": "cross-env NODE_ENV=production vite build && esno ./build/script/postBuild.ts" 101 | ``` 102 | 103 | 主要作用是解析环境变量文件,并把内容写入到文件中。 104 | 105 | ```ts 106 | // build/script/buildConf.ts 107 | function createConfig({ configName, config, configFileName = GLOB_CONFIG_FILE_NAME }) { 108 | try { 109 | // 文件内容 110 | const windowConf = `window.${configName}`; 111 | const configStr = `${windowConf}=${JSON.stringify(config)}; 112 | Object.freeze(${windowConf}); 113 | Object.defineProperty(window, "${configName}", { 114 | configurable: false, 115 | writable: false, 116 | }); 117 | `.replace(/\s/g, ""); 118 | fs.mkdirp(getRootPath(OUTPUT_DIR)); 119 | // 生成文件 120 | writeFileSync(getRootPath(`${OUTPUT_DIR}/${configFileName}`), configStr); 121 | } catch (error) { 122 | console.log(chalk.red("configuration file configuration file failed to package:\n" + error)); 123 | } 124 | } 125 | 126 | export function runBuildConfig() { 127 | // 获取环境变量配置文件中以 VITE_GLOB_ 开头的值,和 Vite 一样通过 dotenv 解析 128 | const config = getEnvConfig(); 129 | // 获取变量配置名称 `__PRODUCTION__${env.VITE_GLOB_APP_SHORT_NAME}__CONF__` 130 | const configFileName = getConfigFileName(config); 131 | // 生成代码并写入文件 132 | createConfig({ config, configName: configFileName }); 133 | } 134 | ``` 135 | 136 | 最后就是在 `index.html` 引入 `_app.config.js`,这里使用 [vite-plugin-html](https://github.com/anncwb/vite-plugin-html) 也是 Vben 作者自己写的。 137 | 138 | 插件都是集中管理在 `build/vite/plugin` 里的,处理完配置后返回数组并配置到 `vite` 的 `plugins` 配置上。 139 | 140 | ```ts 141 | // build/vite/plugin/html.ts 142 | export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) { 143 | const { VITE_GLOB_APP_TITLE, VITE_PUBLIC_PATH } = env; 144 | 145 | const path = VITE_PUBLIC_PATH.endsWith("/") ? VITE_PUBLIC_PATH : `${VITE_PUBLIC_PATH}/`; 146 | 147 | // 获取路径配置 148 | const getAppConfigSrc = () => { 149 | return `${path || "/"}${GLOB_CONFIG_FILE_NAME}?v=${pkg.version}-${new Date().getTime()}`; 150 | }; 151 | 152 | const htmlPlugin: Plugin[] = html({ 153 | minify: isBuild, 154 | inject: { 155 | data: { 156 | title: VITE_GLOB_APP_TITLE, 157 | }, 158 | // build 模式引入 app.config.js 文件 159 | tags: isBuild 160 | ? [ 161 | { 162 | tag: "script", 163 | attrs: { 164 | src: getAppConfigSrc(), 165 | }, 166 | }, 167 | ] 168 | : [], 169 | }, 170 | }); 171 | return htmlPlugin; 172 | } 173 | ``` 174 | 175 | ### 环境变量的使用 176 | 177 | 首先我们还是可以使用 `vite` 提供的方式。 178 | 179 | ```ts 180 | import.meta.env.VITE_XXX; 181 | ``` 182 | 183 | 根据文档可知道通过 `useGlobSetting` 函数来进行获取动态配置。 184 | 185 | ```ts 186 | // src/hooks/setting/index.ts 187 | export const useGlobSetting = () => { 188 | const { VITE_GLOB_APP_TITLE } = getAppEnvConfig(); // 生产环境动态配置 189 | const glob = { 190 | title: VITE_GLOB_APP_TITLE, 191 | }; 192 | return glob; 193 | }; 194 | ``` 195 | 196 | 生产环境动态配置的实现。 197 | 198 | ```ts 199 | // src/utils/env.ts 200 | export function getAppEnvConfig() { 201 | // 获取配置的别名 202 | const ENV_NAME = getConfigFileName(import.meta.env); 203 | 204 | // 如果是开发开发环境从环境变量中获取 205 | // 如果是生产环境从 window 属性中获取 (_app.config.js) 206 | const ENV = import.meta.env.DEV ? import.meta.env : window[ENV_NAME]; 207 | 208 | const { VITE_GLOB_APP_TITLE } = ENV; 209 | 210 | return { 211 | VITE_GLOB_APP_TITLE, 212 | }; 213 | } 214 | ``` 215 | 216 | ### 总结 217 | 218 | 我们知道当我们配置了一个环境变量后 Vben Admin 做了哪些处理又是怎么来的。这样我们在使用的时候如果有问题或增加新的插件逻辑我们也能随心所欲。 219 | -------------------------------------------------------------------------------- /blogs/node/tinylib-analysis/co.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: co 源码分析-异步编程之 Generator 自动执行 3 | date: 2021-11-11 4 | tags: 5 | - Node 6 | categories: 7 | - 后端 8 | - 源码分析 9 | --- 10 | 11 | ## 前言 12 | 13 | co 库用于 Generator 函数的自动执行。。 14 | 15 | ## 源码 16 | 17 | ### 手动执行 18 | 19 | 如果一个 `Generator` 函数,我们想让它执行完毕,就需要要不断的调用 `next` 方法。 20 | 21 | ```js 22 | function* gen() { 23 | console.log(1); 24 | 25 | yield new Promise((resolve) => { 26 | setTimeout(() => { 27 | resolve(); 28 | console.log(2); 29 | }); 30 | }); 31 | 32 | yield new Promise((resolve) => { 33 | setTimeout(() => { 34 | resolve(); 35 | console.log(3); 36 | }); 37 | }); 38 | 39 | console.log(4); 40 | } 41 | 42 | // 生成器对象 43 | let g = gen(); 44 | 45 | // 1、执行一步 输出 1 46 | // 2、返回 Promise,成功后执行 then,输出 2 47 | g.next().value.then(() => { 48 | // 3、再执行一步,返回 Promise,成功后执行 then,输出 3 49 | g.next().value.then(() => { 50 | // 4、再执行一步,结束 输出 4 51 | g.next(); 52 | }); 53 | }); 54 | ``` 55 | 56 | ### Co 57 | 58 | 那么 `co` 怎么自动执行的,主要点在 `next` 函数,会把多种格式 `value` 转化为 `Promise` ,给这个 `Promise` 对象添加 `then` 方法,当异步操作成功时执行 `then` 中的 `onFullfilled` 函数,`onFullfilled` 函数中又去执行 `g.next`,从而让 `Generator` 继续执行,然后再返回一个 `Promise`,再在成功时执行 `g.next`,然后再返回直到结束。 59 | 60 | ```js 61 | function co(gen) { 62 | var ctx = this; 63 | var args = slice.call(arguments, 1); 64 | 65 | return new Promise(function (resolve, reject) { 66 | if (typeof gen === "function") { 67 | // 生成器对象 68 | gen = gen.apply(ctx, args); 69 | } 70 | if (!gen || typeof gen.next !== "function") return resolve(gen); 71 | 72 | // 执行一次 73 | onFulfilled(); 74 | 75 | function onFulfilled(res) { 76 | var ret; 77 | try { 78 | // 执行下一步,指针指向下一个,传入参数传入上次 yield 表达式的值。 79 | ret = gen.next(res); 80 | } catch (e) { 81 | return reject(e); 82 | } 83 | next(ret); 84 | // return null; 85 | } 86 | 87 | function next(ret) { 88 | // 是否结束,结束直接返回 89 | if (ret.done) return resolve(ret.value); 90 | // 把各种值包装成 Promise 91 | var value = toPromise.call(ctx, ret.value); 92 | // 添加 then 方法,当前 Promise 完成后调用 onFulfilled 再次调用 next 93 | if (value && isPromise(value)) { 94 | return value.then(onFulfilled, onRejected); 95 | } 96 | } 97 | }); 98 | } 99 | ``` 100 | 101 | ```js 102 | co(function* () { 103 | console.log(1); 104 | 105 | yield new Promise((resolve) => { 106 | setTimeout(() => { 107 | resolve(); 108 | console.log(2); 109 | }); 110 | }); 111 | 112 | yield new Promise((resolve) => { 113 | setTimeout(() => { 114 | resolve(); 115 | console.log(3); 116 | }); 117 | }); 118 | 119 | console.log(4); 120 | }); 121 | ``` 122 | 123 | ### Async Await 124 | 125 | `async ... await` 是现在的终极方案,简单明了。 126 | 127 | ```js 128 | (async () => { 129 | console.log(1); 130 | 131 | await new Promise((resolve) => { 132 | setTimeout(() => { 133 | resolve(); 134 | console.log(2); 135 | }); 136 | }); 137 | 138 | await new Promise((resolve) => { 139 | setTimeout(() => { 140 | resolve(); 141 | console.log(3); 142 | }); 143 | }); 144 | 145 | console.log(4); 146 | })(); 147 | ``` 148 | 149 | ### Babel 150 | 151 | 到这里我有点好奇 `babel` 是怎么转码 `async ... await`,所以我在[官网](https://babeljs.io/repl)把上面代码转码了一下,发现实现方式和 `co` 的方式基本一致。 152 | 153 | ```js 154 | "use strict"; 155 | 156 | function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { 157 | try { 158 | var info = gen[key](arg); 159 | var value = info.value; 160 | } catch (error) { 161 | reject(error); 162 | return; 163 | } 164 | // 判断是否结束 165 | if (info.done) { 166 | resolve(value); 167 | } else { 168 | // 当前 Promise 完成后调用 then 再次调用 next 169 | Promise.resolve(value).then(_next, _throw); 170 | } 171 | } 172 | 173 | function _asyncToGenerator(fn) { 174 | return function () { 175 | var self = this, 176 | args = arguments; 177 | return new Promise(function (resolve, reject) { 178 | // 对象生成器对象 179 | var gen = fn.apply(self, args); 180 | function _next(value) { 181 | asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); 182 | } 183 | function _throw(err) { 184 | asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); 185 | } 186 | // 执行一次 187 | _next(undefined); 188 | }); 189 | }; 190 | } 191 | 192 | // 由于 babel 只是完整的模拟了 生成器函数 所有我们把 babel 实现的 regeneratorRuntime 函数直接替换原生的 生成器函数 是可以的。 193 | _asyncToGenerator(function* () { 194 | console.log(1); 195 | 196 | yield new Promise((resolve) => { 197 | setTimeout(() => { 198 | resolve(); 199 | console.log(2); 200 | }); 201 | }); 202 | 203 | yield new Promise((resolve) => { 204 | setTimeout(() => { 205 | resolve(); 206 | console.log(3); 207 | }); 208 | }); 209 | 210 | console.log(4); 211 | })(); 212 | 213 | // Generator 的实现,内部实现复杂我们替换成 Generator。 214 | // _asyncToGenerator( 215 | // /*#__PURE__*/ regeneratorRuntime.mark(function _callee() { 216 | // return regeneratorRuntime.wrap(function _callee$(_context) { 217 | // while (1) { 218 | // switch ((_context.prev = _context.next)) { 219 | // case 0: 220 | // console.log(1); 221 | // _context.next = 3; 222 | // return new Promise(function (resolve) { 223 | // setTimeout(function () { 224 | // resolve(); 225 | // console.log(2); 226 | // }); 227 | // }); 228 | 229 | // case 3: 230 | // _context.next = 5; 231 | // return new Promise(function (resolve) { 232 | // setTimeout(function () { 233 | // resolve(); 234 | // console.log(3); 235 | // }); 236 | // }); 237 | 238 | // case 5: 239 | // console.log(4); 240 | 241 | // case 6: 242 | // case "end": 243 | // return _context.stop(); 244 | // } 245 | // } 246 | // }, _callee); 247 | // }) 248 | // )(); 249 | ``` 250 | 251 | ## 总结 252 | 253 | 1. 从开始就一直用的 `async` 没怎么关注过 `generator`,这次也算是了解了。 254 | 255 | 2. 从最初 `callback`,到现在使用 `promise`、`generator`、`async` 尽量把异步编程同步写法。_个人理解_:再到 `co` 和 `babel` 转码的源码理解到我们的同步写法是对以前回调写法进一步的封装。 256 | -------------------------------------------------------------------------------- /blogs/vue/lib/vlib-starter-5.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 实践 Vue3 组件库-基于 Vitest/VTU 的组件单元测试(组件篇二) 3 | date: 2022-08-16 4 | tags: 5 | - Vue 6 | categories: 7 | - 前端 8 | --- 9 | 10 | 简单解释一下单元测试:就是对函数的输入输出进行测试,判断我们输入的用例的结果和我们实际输入的结果是否相同。平常工作中就会写一些测试可能不是系统性的,比如测试一个函数的返回值是否正确。 11 | 12 | ```ts 13 | // 实现 14 | function sum(a, b) { 15 | return a + b; 16 | } 17 | // 测试 18 | function test() { 19 | if (sum(1, 1) !== 2) { 20 | throw new Error("sum(1, 1) did not return 2"); 21 | } 22 | } 23 | test(); 24 | ``` 25 | 26 | 下面我们搭建一个系统性的单元测试,本篇新增的完整代码可查看单独的分支 [ui-test](https://github.com/bfehub/vlib-starter/tree/4-ui-test)。 27 | 28 | > 如果你还不了解这个系列要做什么,那你可以先阅读 [【实践 Vue3 组件库-介绍一下这个系列】](./vlib-starter-1.md) 的介绍,以便你对整个系列有清晰的认识。 29 | 30 | ## 前置文章 31 | 32 | 你需要先阅读以下文章。 33 | 34 | - [实践 Vue3 组件库-基于开源组件库扩展业务组件(组件篇一)](./vlib-starter-4.md) 35 | 36 | ## 环境配置 37 | 38 | 我们基于 [vitest](https://cn.vitest.dev/) 和 [vtu](https://test-utils.vuejs.org/) 做测试我们先把相关的依赖和环境配置好。 39 | 40 | ```sh 41 | # 前置依赖 42 | pnpm add vite vitest -D --filter @bfehub/vlib-ui 43 | 44 | # Vue 语法插件 45 | pnpm add @vitejs/plugin-vue @vitejs/plugin-vue-jsx unplugin-vue-define-options -D --filter @bfehub/vlib-ui 46 | 47 | # Vue 测试环境 48 | pnpm add @vue/test-utils happy-dom -D --filter @bfehub/vlib-ui 49 | 50 | # Vitest 网页界面 51 | pnpm add @vitest/ui -D --filter @bfehub/vlib-ui 52 | 53 | # 测试覆盖率 54 | pnpm add @vitest/coverage-c8 -D --filter @bfehub/vlib-ui 55 | ``` 56 | 57 | 在 `package.json` 加入以下的脚本命令。 58 | 59 | ```json 60 | // packages/vlib-ui/package.json 61 | { 62 | "scripts": { 63 | "test": "vitest", 64 | "test:ui": "vitest --ui", 65 | "test:coverage": "vitest run --coverage" 66 | } 67 | } 68 | ``` 69 | 70 | 在 `vitest.config.ts` 中配置环境和插件。 71 | 72 | ```ts 73 | // packages/vlib-ui/vitest.config.ts 74 | import { defineConfig } from "vitest/config"; 75 | import vue from "@vitejs/plugin-vue"; 76 | import vueJsx from "@vitejs/plugin-vue-jsx"; 77 | import vueDefineOptions from "unplugin-vue-define-options/vite"; 78 | 79 | // https://cn.vitest.dev/config/ 80 | export default defineConfig({ 81 | plugins: [vueDefineOptions(), vue(), vueJsx()], 82 | optimizeDeps: { 83 | disabled: true, 84 | }, 85 | test: { 86 | clearMocks: true, 87 | environment: "happy-dom", 88 | transformMode: { 89 | web: [/\.[jt]sx$/], 90 | }, 91 | }, 92 | }); 93 | ``` 94 | 95 | ## 基本使用 96 | 97 | 使用 `vitest` 只需优先记住三类方法的用法即可。 98 | 99 | - `describe`:将相关的测试组合在一起(可以任意的嵌套和分组,一般一个文件定义一个主标题)。 100 | 101 | - `test`:包含一组断言方法(也可以认为分组,一般是一个分支或一个功能的测试)。 102 | 103 | - `expect`:一个断言(可以理解为判断,是对一个值或状态的具体判断)。 104 | 105 | 我们开头说的 `sum` 方法用这些方法就可以改写为。 106 | 107 | ```tsx 108 | import { describe, test, expect } from "vitest"; 109 | describe("Sum", () => { 110 | test("测试数字相加", () => { 111 | expect(sum(1, 1)).toBe(2); 112 | 113 | expect(sum(0.1, 0.2)).toBeCloseTo(0.3); 114 | }); 115 | 116 | test("测试字符串相加", () => { 117 | expect(sum("1", "1")).toBe("11"); 118 | }); 119 | }); 120 | ``` 121 | 122 | 使用 `vtu` 建议跟着官网的案例走一遍,了解常用的 `mount`、`find`、`emitted`、`trigger` 等测试方法。刚开始写测试会有一种无法下手的感觉,不知道怎么写或者不会写。这时我们可以去参考开源组件库类似的组件慢慢就熟悉了。 123 | 124 | ```tsx 125 | import { mount } from "@vue/test-utils"; 126 | 127 | // The component to test 128 | const MessageComponent = { 129 | template: "

{{ msg }}

", 130 | props: ["msg"], 131 | }; 132 | 133 | test("displays message", () => { 134 | const wrapper = mount(MessageComponent, { 135 | props: { 136 | msg: "Hello world", 137 | }, 138 | }); 139 | 140 | // Assert the rendered text of the component 141 | expect(wrapper.text()).toContain("Hello world"); 142 | }); 143 | ``` 144 | 145 | ## 测试组件 146 | 147 | 我们是新手肯定是先写组件后写测试,还可以看着组件渲染的效果写。 148 | 149 | ![1.png](./image/vlib-starter-5/1.png) 150 | 151 | 在组件的目录下,我们新建一个 `__tests__/back-top.test.tsx`,使用 `tsx` 的原因是挂载组件比较方便。 152 | 153 | ```tsx 154 | // packages/vlib-ui/src/back-top/__tests__/back-top.test.tsx 155 | import { nextTick } from "vue"; 156 | import { mount } from "@vue/test-utils"; 157 | import { describe, test, expect } from "vitest"; 158 | import { BackTop } from "../index"; 159 | ``` 160 | 161 | 我们要为 `BackTop` 组件写测试,使用 `describe` 定义个分组。 162 | 163 | ```tsx 164 | describe("BackTop.vue", () => {}); 165 | ``` 166 | 167 | 我们要测试 `BackTop` 的渲染是否正确,使用 `test` 定义个测试。 168 | 169 | ```tsx 170 | describe("BackTop.vue", () => { 171 | test("render", async () => {}); 172 | }); 173 | ``` 174 | 175 | 第一步先把要测试的组件 `BackTop` 挂载上。 176 | 177 | ```tsx 178 | describe("BackTop.vue", () => { 179 | test("render", async () => { 180 | // 挂载组件 181 | const wrapper = mount( 182 | () => ( 183 |
184 |
185 | 186 |
187 |
188 | ), 189 | { attachTo: document.body } 190 | ); 191 | await nextTick(); 192 | }); 193 | }); 194 | ``` 195 | 196 | 接下来我们写很多的断言,判断当前的状态和事件是否正确。 197 | 198 | ```tsx 199 | describe("BackTop.vue", () => { 200 | test("render", async () => { 201 | // ... 202 | 203 | // 初始化时元素隐藏 204 | expect(wrapper.find(".vlib-back-top").exists()).toBe(false); 205 | 206 | // 滚动到 2000 之后元素显示 207 | wrapper.element.scrollTop = 2000; 208 | await wrapper.trigger("scroll"); 209 | expect(wrapper.find(".vlib-back-top").exists()).toBe(true); 210 | 211 | // 验证 props 是否渲染正确 212 | expect(wrapper.find(".vlib-back-top").attributes("style")).toBe("right: 100px; bottom: 200px;"); 213 | 214 | // 点击返回触发 click 事件 215 | await wrapper.trigger("click"); 216 | expect(wrapper.emitted("click")).toBeDefined(); 217 | }); 218 | }); 219 | ``` 220 | 221 | 最后输出快照查看整体的 DOM 结构渲染结果。 222 | 223 | ```tsx 224 | describe("BackTop.vue", () => { 225 | test("render", async () => { 226 | // ... 227 | 228 | // 输出快照到单独的文件 229 | expect(wrapper.html()).toMatchSnapshot(); 230 | }); 231 | }); 232 | ``` 233 | 234 | ![2.png](./image/vlib-starter-5/2.png) 235 | 236 | 运行 `pnpm run test:coverage` 测试并输出测试报告,打开 `coverage/index.html` 可以看到测试情况。由于 `sfc` 的组件测试不到 `v-if` 分支,之前还写了 `tsx` 版本的,可以看到当传入 `slot` 时这个分支没有测试到,那么接下来就可以继续添加一个测试提升覆盖率。 237 | 238 | ```tsx 239 | describe("BackTop.vue", () => { 240 | test("render slot", async () => { 241 | // 你可以自己实现... 242 | }); 243 | }); 244 | ``` 245 | 246 | ![3.png](./image/vlib-starter-5/3.png) 247 | 248 | ![4.png](./image/vlib-starter-5/4.png) 249 | 250 | ## 你可以... 251 | 252 | - 你可以根据本章内容自己实现一遍完善我们的组件库。 253 | 254 | - 你可以补充未完成的测试完善覆盖率。 255 | 256 | - 如果对你有帮助可以点个 **赞** 和 **关注** 以示鼓励。 257 | -------------------------------------------------------------------------------- /blogs/node/tinylib-analysis/update-notifier.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: update-notifier 源码分析-检测 NPM 包是否更新 3 | date: 2021-12-22 4 | tags: 5 | - Node 6 | categories: 7 | - 后端 8 | - 源码分析 9 | --- 10 | 11 | ## 前言 12 | 13 | 用于提示当前本地的 `npm` 包是否是最新版本并给予提示。 14 | 15 | ![1.png](https://raw.githubusercontent.com/haiweilian/tinylib-analysis/main/008.update-notifier/.docs/images/1.png) 16 | 17 | ## 源码 18 | 19 | ### 依赖 20 | 21 | 可以看到依赖了非常多的依赖包,实现是靠这些的组合这也考研了知识储备量。下面从三个阶段在解析整个的流程。 22 | 23 | ```js 24 | const { spawn } = require("child_process"); 25 | const path = require("path"); 26 | const { format } = require("util"); 27 | // 懒加载模块 28 | const importLazy = require("import-lazy")(require); 29 | // 配置存储 30 | const configstore = importLazy("configstore"); 31 | // 终端字符颜色 32 | const chalk = importLazy("chalk"); 33 | // 语义化版本 34 | const semver = importLazy("semver"); 35 | // 语义化版本比较差异 36 | const semverDiff = importLazy("semver-diff"); 37 | // 获取 npm 上的最新版本号 38 | const latestVersion = importLazy("latest-version"); 39 | // 检测运行文件的报管理工具 npm or yarn 40 | const isNpm = importLazy("is-npm"); 41 | // 检测安装包是否全局安装 42 | const isInstalledGlobally = importLazy("is-installed-globally"); 43 | // 检测安装包是否 yarn 全局安装 44 | const isYarnGlobal = importLazy("is-yarn-global"); 45 | // 检测项目是否使用 yarn 46 | const hasYarn = importLazy("has-yarn"); 47 | // 在终端创建一个框显示 48 | const boxen = importLazy("boxen"); 49 | // 配置基础路径 50 | const xdgBasedir = importLazy("xdg-basedir"); 51 | // 检测当前环境是否是持续集成环境 52 | const isCi = importLazy("is-ci"); 53 | // 占位符的模板 54 | const pupa = importLazy("pupa"); 55 | ``` 56 | 57 | ### 解析配置阶段 58 | 59 | 这一步主要是对传入的参数进行解析,并存储起来。并利用了 `configstore` 持久化存储信息。 60 | 61 | ```js 62 | class UpdateNotifier { 63 | // 解析配置阶段 64 | constructor(options = {}) { 65 | // 解析配置,从不同参数中解析出 packageName 和 packageVersion 66 | this.options = options; 67 | options.pkg = options.pkg || {}; 68 | options.distTag = options.distTag || "latest"; 69 | 70 | // Reduce pkg to the essential keys. with fallback to deprecated options 71 | // TODO: Remove deprecated options at some point far into the future 72 | options.pkg = { 73 | name: options.pkg.name || options.packageName, 74 | version: options.pkg.version || options.packageVersion, 75 | }; 76 | 77 | if (!options.pkg.name || !options.pkg.version) { 78 | throw new Error("pkg.name and pkg.version required"); 79 | } 80 | 81 | this.packageName = options.pkg.name; 82 | this.packageVersion = options.pkg.version; 83 | 84 | // 检测更新的间隔时间 85 | this.updateCheckInterval = 86 | typeof options.updateCheckInterval === "number" ? options.updateCheckInterval : ONE_DAY; 87 | 88 | // 是否禁用 89 | this.disabled = 90 | "NO_UPDATE_NOTIFIER" in process.env || 91 | process.env.NODE_ENV === "test" || 92 | process.argv.includes("--no-update-notifier") || 93 | isCi(); 94 | 95 | // npm 脚本时通知 96 | this.shouldNotifyInNpmScript = options.shouldNotifyInNpmScript; 97 | 98 | if (!this.disabled) { 99 | try { 100 | // 存储配置到本地文件 101 | const ConfigStore = configstore(); 102 | this.config = new ConfigStore(`update-notifier-${this.packageName}`, { 103 | optOut: false, 104 | lastUpdateCheck: Date.now(), 105 | }); 106 | } catch { 107 | // ... 108 | } 109 | } 110 | } 111 | } 112 | ``` 113 | 114 | ### 检测更新阶段 115 | 116 | 这一步主要做检测判断,比如通过时间判断是否应该再次检测,通过本地的包信息和远程最新的包信息检测是否是最新版本。检测的时候开启了一个单独的子进程去检测,并通过本地存储的信息交互结果。 117 | 118 | ```js 119 | class UpdateNotifier { 120 | // 检测更新阶段 121 | check() { 122 | // .... 123 | 124 | // 是否超过检测的间隔时间 125 | if (Date.now() - this.config.get("lastUpdateCheck") < this.updateCheckInterval) { 126 | return; 127 | } 128 | 129 | // 执行检测脚本 130 | spawn(process.execPath, [path.join(__dirname, "check.js"), JSON.stringify(this.options)], { 131 | detached: true, 132 | stdio: "ignore", 133 | }).unref(); 134 | } 135 | 136 | async fetchInfo() { 137 | // 获取到最新的版本信息 138 | const { distTag } = this.options; 139 | const latest = await latestVersion()(this.packageName, { 140 | version: distTag, 141 | }); 142 | // 返回两个版本的差异信息 143 | return { 144 | latest, 145 | current: this.packageVersion, 146 | type: semverDiff()(this.packageVersion, latest) || distTag, 147 | name: this.packageName, 148 | }; 149 | } 150 | } 151 | ``` 152 | 153 | ### 通知更新阶段 154 | 155 | 最后就是在通过 `boxen` 在总端输出提示信息。 156 | 157 | ```js 158 | class UpdateNotifier { 159 | // 通知更新阶段 160 | notify(options) { 161 | const suppressForNpm = !this.shouldNotifyInNpmScript && isNpm().isNpmOrYarn; 162 | if ( 163 | !process.stdout.isTTY || 164 | suppressForNpm || 165 | !this.update || 166 | !semver().gt(this.update.latest, this.update.current) 167 | ) { 168 | return this; 169 | } 170 | 171 | options = { 172 | isGlobal: isInstalledGlobally(), 173 | isYarnGlobal: isYarnGlobal()(), 174 | ...options, 175 | }; 176 | 177 | // 根据环境提示命令 178 | let installCommand; 179 | if (options.isYarnGlobal) { 180 | installCommand = `yarn global add ${this.packageName}`; 181 | } else if (options.isGlobal) { 182 | installCommand = `npm i -g ${this.packageName}`; 183 | } else if (hasYarn()()) { 184 | installCommand = `yarn add ${this.packageName}`; 185 | } else { 186 | installCommand = `npm i ${this.packageName}`; 187 | } 188 | 189 | // 创建终端的提示信息 190 | const defaultTemplate = 191 | "Update available " + 192 | chalk().dim("{currentVersion}") + 193 | chalk().reset(" → ") + 194 | chalk().green("{latestVersion}") + 195 | " \nRun " + 196 | chalk().cyan("{updateCommand}") + 197 | " to update"; 198 | 199 | const template = options.message || defaultTemplate; 200 | 201 | options.boxenOptions = options.boxenOptions || { 202 | padding: 1, 203 | margin: 1, 204 | align: "center", 205 | borderColor: "yellow", 206 | borderStyle: "round", 207 | }; 208 | 209 | const message = boxen()( 210 | pupa()(template, { 211 | packageName: this.packageName, 212 | currentVersion: this.update.current, 213 | latestVersion: this.update.latest, 214 | updateCommand: installCommand, 215 | }), 216 | options.boxenOptions 217 | ); 218 | 219 | if (options.defer === false) { 220 | console.error(message); 221 | } else { 222 | process.on("exit", () => { 223 | console.error(message); 224 | }); 225 | 226 | process.on("SIGINT", () => { 227 | console.error(""); 228 | process.exit(); 229 | }); 230 | } 231 | 232 | return this; 233 | } 234 | } 235 | ``` 236 | 237 | ## 总结 238 | 239 | 1. 看完这个发现这一个小功能依赖的是真多,而我们也要善于通过第三方的各种小功能进行组合达到自己的需求。 240 | -------------------------------------------------------------------------------- /blogs/node/koa-analysis.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Koa 原理分析,中间件机制“洋葱模型” 3 | date: 2021-03-29 4 | tags: 5 | - Node 6 | categories: 7 | - 后端 8 | - 源码分析 9 | --- 10 | 11 | 先来看下洋葱中间件机制,这种灵活的中间件机制也让 `koa` 变得非常强大。 12 | 13 | ![洋葱圈模型](./image/koa-analysis/koa.png) 14 | 15 | ## 查看示例 16 | 17 | [查看示例](https://github.com/haiweilian/demos/tree/master/Node/koa-simple-imp) 18 | 19 | ## 初始化项目 20 | 21 | 先来看一个使用 `node` 创建一个服务。 22 | 23 | ```js 24 | let http = require("http"); 25 | let server = http.createServer((req, res) => { 26 | res.writeHead(200); 27 | res.end("hello world"); 28 | }); 29 | server.listen(3000, () => { 30 | console.log("listenning on 3000"); 31 | }); 32 | ``` 33 | 34 | 使用 `koa` 创建一个服务。 其实 `koa` 内部也是用的原生的 `http` 模块。 35 | 36 | ```js 37 | const Koa = require("koa"); 38 | const app = new Koa(); 39 | 40 | app.use(async (ctx) => { 41 | ctx.body = "Hello World"; 42 | }); 43 | 44 | app.listen(3000); 45 | ``` 46 | 47 | ## 基础实现 48 | 49 | #### 四大模块 50 | 51 | 文档上也是很简单,只有 `Application`、`Context` 、`Request` 、`Response` 四部分。 52 | 53 | - `Application` 是暴露出来的构造函数,有 `listen`、`use` 等等。 54 | 55 | - `Context` 对象是 `koa` 的应用上下文。 56 | 57 | - `Request` 对象是在 node 的 原生请求对象之上的抽象。 58 | 59 | - `Response` 对象是在 node 的原生响应对象之上的抽象。 60 | 61 | #### 封装 Application 62 | 63 | 将原生请求封装在类内部中。 64 | 65 | ```js 66 | const http = require("http"); 67 | class Koa { 68 | constructor() { 69 | this.middlewares = []; // 中间件集合 70 | } 71 | 72 | // 创建服务,内部调用 http.createServer 方法。 73 | listen(...args) { 74 | const server = http.createServer(async (req, res) => { 75 | // ... 76 | }); 77 | 78 | server.listen(...args); 79 | } 80 | } 81 | module.exports = Koa; 82 | ``` 83 | 84 | #### 封装 Context、Request、Response 85 | 86 | 就是对原生的 `request`、`response` 使用 `setter` 和 `getter` 进行了一个功能的封装,方便开发者调用。 87 | 88 | - 封装 `request.js`,简单的几个。 89 | 90 | ```js 91 | module.exports = { 92 | get url() { 93 | return this.req.url; 94 | }, 95 | get method() { 96 | return this.req.method.toLowerCase(); 97 | }, 98 | }; 99 | ``` 100 | 101 | - 封装 `response.js`,简单的几个。 102 | 103 | ```js 104 | module.exports = { 105 | get body() { 106 | return this._body; 107 | }, 108 | set body(val) { 109 | this._body = val; 110 | }, 111 | }; 112 | ``` 113 | 114 | - 封装 `context.js`,简单的几个。 115 | 116 | ```js 117 | module.exports = { 118 | get url() { 119 | return this.request.url; 120 | }, 121 | get body() { 122 | return this.response.body; 123 | }, 124 | set body(val) { 125 | this.response.body = val; 126 | }, 127 | get method() { 128 | return this.request.method; 129 | }, 130 | }; 131 | ``` 132 | 133 | - 最后在构造函数里创建上下文。 134 | 135 | ```js 136 | const http = require("http"); 137 | const context = require("./context"); 138 | const request = require("./request"); 139 | const response = require("./response"); 140 | 141 | class Koa { 142 | constructor() {} 143 | 144 | // 创建服务,内部调用 http.createServer 方法。 145 | listen(...args) { 146 | const server = http.createServer(async (req, res) => { 147 | // 创建上下文 148 | const ctx = this.createContext(req, res); 149 | // 返回结果 150 | res.end(ctx.body); 151 | }); 152 | 153 | server.listen(...args); 154 | } 155 | 156 | // 创建上下文,一下各种赋值是为了使用方便。 157 | createContext(req, res) { 158 | const ctx = Object.create(context); 159 | 160 | // Koa 抽象 request/response 的对象。 161 | ctx.request = Object.create(request); 162 | ctx.response = Object.create(response); 163 | 164 | // Node 的 request/response 对象。 165 | ctx.req = ctx.request.req = req; 166 | ctx.res = ctx.response.res = res; 167 | 168 | return ctx; 169 | } 170 | } 171 | 172 | module.exports = Koa; 173 | ``` 174 | 175 | ## 中间件实现 176 | 177 | #### 洋葱圈模型 178 | 179 | 添加中间件是通过 `use` 添加,这其实是一个收集的过程。重点是通过 `compose` 函数,相比较 `redux` 的实现方式容易理解的多。 180 | 181 | ```js 182 | class Koa { 183 | constructor() { 184 | this.middlewares = []; // 中间件集合 185 | } 186 | 187 | // 创建服务,内部调用 http.createServer 方法。 188 | listen(...args) { 189 | const server = http.createServer(async (req, res) => { 190 | // 创建上下文 191 | const ctx = this.createContext(req, res); 192 | 193 | // 组合中间件 194 | const compose = this.compose(this.middlewares); 195 | await compose(ctx); 196 | 197 | // 返回结果 198 | res.end(ctx.body); 199 | }); 200 | 201 | server.listen(...args); 202 | } 203 | 204 | // 收集中间件,存到一个集合中。 205 | use(middleware) { 206 | this.middlewares.push(middleware); 207 | } 208 | 209 | // 中间件组合,本质上就是一个嵌套的高阶函数,外层的中间件嵌套着内层的中间件。 210 | // 有点像递归的机制,一层嵌套一层。调用 next 之前是 "递",之后是 "归"。 211 | compose(middlewares) { 212 | return function (ctx) { 213 | // 从下标为 0 开始执行中间件。 214 | dispatch(0); 215 | function dispatch(i) { 216 | // 找出数组中存放的相应的中间件 217 | const middleware = middlewares[i]; 218 | 219 | // 不存在返回成功,最后一个中间件调用 next 也不会报错。 220 | if (!middleware) { 221 | return Promise.resolve(); 222 | } 223 | return Promise.resolve( 224 | middleware( 225 | // 第一个参数是 ctx。 226 | ctx, 227 | // 第二个参数是 next,允许继续进入下一个中间件。 228 | function next() { 229 | return dispatch(i + 1); 230 | } 231 | ) 232 | ); 233 | } 234 | }; 235 | } 236 | } 237 | ``` 238 | 239 | #### 测试中间件 240 | 241 | 以下应该输出 `1->2->3->4->5->6` 的顺序。 242 | 243 | ```js 244 | app.use(async (ctx, next) => { 245 | console.log(1); 246 | await next(); 247 | console.log(6); 248 | }); 249 | app.use(async (ctx, next) => { 250 | console.log(2); 251 | await next(); 252 | console.log(5); 253 | }); 254 | app.use(async (ctx, next) => { 255 | console.log(3); 256 | await next(); 257 | console.log(4); 258 | }); 259 | ``` 260 | 261 | #### 路由中间件 262 | 263 | 例如路由是如下定义使用。 264 | 265 | ```js 266 | router.get("/get", (ctx, next) => { 267 | ctx.body = "get"; 268 | }); 269 | router.get("/post", (ctx, next) => { 270 | ctx.body = "post"; 271 | }); 272 | app.use(router.routes()); 273 | ``` 274 | 275 | 实现的过程就是注册路由信息,当接收到请求的时候,匹配路径执行对应的中间件。 276 | 277 | ```js 278 | class Router { 279 | constructor() { 280 | this.stack = []; 281 | } 282 | 283 | // 注册路由信息,收集到一个集合中。 284 | register(path, methods, middleware) { 285 | let route = { path, methods, middleware }; 286 | this.stack.push(route); 287 | } 288 | 289 | // get 请求。 290 | get(path, middleware) { 291 | this.register(path, "get", middleware); 292 | } 293 | 294 | // post 请求。 295 | post(path, middleware) { 296 | this.register(path, "post", middleware); 297 | } 298 | 299 | // 暴露出一个总的路由中间件。 300 | routes() { 301 | let stock = this.stack; 302 | 303 | return async function (ctx, next) { 304 | let currentPath = ctx.url; 305 | let route; 306 | 307 | // 根据 path 和 method 匹配对应对应的路由 308 | for (let i = 0; i < stock.length; i++) { 309 | let item = stock[i]; 310 | if (currentPath === item.path && item.methods.indexOf(ctx.method) >= 0) { 311 | route = item.middleware; 312 | break; 313 | } 314 | } 315 | 316 | // 执行对应的函数,返回结果。 317 | if (typeof route === "function") { 318 | route(ctx, next); 319 | return; 320 | } 321 | 322 | await next(); 323 | }; 324 | } 325 | } 326 | 327 | module.exports = Router; 328 | ``` 329 | -------------------------------------------------------------------------------- /.vuepress/public/examples/nest-request-lifecycle/code.js: -------------------------------------------------------------------------------- 1 | export const code = `// main.ts ============================================================= 2 | import { NestFactory } from "@nestjs/core"; 3 | import { AppModule } from "./app.module"; 4 | import { GlobalMiddleware } from "./app.middleware"; 5 | 6 | async function bootstrap() { 7 | const app = await NestFactory.create(AppModule); 8 | app.use(GlobalMiddleware); 9 | await app.listen(3000); 10 | } 11 | bootstrap(); 12 | 13 | // app.module.ts ============================================================= 14 | import { MiddlewareConsumer, Module, NestModule } from "@nestjs/common"; 15 | import { AppController } from "./app.controller"; 16 | import { AppService } from "./app.service"; 17 | import { Modulemiddleware } from "./app.middleware"; 18 | import { APP_FILTER, APP_GUARD, APP_INTERCEPTOR, APP_PIPE } from "@nestjs/core"; 19 | import { GlobalGuard } from "./app.guard"; 20 | import { GlobalInterceptor } from "./app.interceptor"; 21 | import { GlobalPipe } from "./app.pipe"; 22 | import { GlobalFilter } from "./app.filter"; 23 | 24 | @Module({ 25 | imports: [], 26 | controllers: [AppController], 27 | providers: [ 28 | AppService, 29 | { 30 | provide: APP_GUARD, 31 | useClass: GlobalGuard, 32 | }, 33 | { 34 | provide: APP_INTERCEPTOR, 35 | useClass: GlobalInterceptor, 36 | }, 37 | { 38 | provide: APP_PIPE, 39 | useClass: GlobalPipe, 40 | }, 41 | { 42 | provide: APP_FILTER, 43 | useClass: GlobalFilter, 44 | }, 45 | ], 46 | }) 47 | export class AppModule implements NestModule { 48 | configure(consumer: MiddlewareConsumer) { 49 | consumer.apply(Modulemiddleware).forRoutes("*"); 50 | } 51 | } 52 | 53 | // app.controller.ts ============================================================= 54 | import { BadRequestException, Controller, Get, InternalServerErrorException, Query, UseFilters, UseGuards, UseInterceptors, UsePipes } from '@nestjs/common'; 55 | import { AppService } from "./app.service"; 56 | import { ControllerGuard, RouteGuard } from "./app.guard"; 57 | import { ControllerInterceptor, RouteInterceptor } from "./app.interceptor"; 58 | import { ControllerPipe, RouteParamPipe, RoutePipe } from "./app.pipe"; 59 | import { ControllerFilter, RouteFilter } from "./app.filter"; 60 | 61 | @Controller() 62 | @UseGuards(ControllerGuard) 63 | @UseInterceptors(ControllerInterceptor) 64 | @UsePipes(ControllerPipe) 65 | @UseFilters(ControllerFilter) 66 | export class AppController { 67 | constructor(private readonly appService: AppService) {} 68 | 69 | @Get() 70 | @UseGuards(RouteGuard) 71 | @UseInterceptors(RouteInterceptor) 72 | @UsePipes(RoutePipe) 73 | @UseFilters(RouteFilter) 74 | getHello( 75 | @Query("id", new RouteParamPipe(1)) id, 76 | @Query("name", new RouteParamPipe(2)) name 77 | ): string { 78 | console.log("AppController.getHello: 调用控制器"); 79 | 80 | const random = Math.random(); 81 | if (random < 0.2) { 82 | throw new BadRequestException("请求异常"); 83 | } else if (random < 0.4) { 84 | throw new InternalServerErrorException("内部服务器错误"); 85 | } else if (random < 0.6) { 86 | throw new Error("未知异常"); 87 | } 88 | 89 | return this.appService.getHello(); 90 | } 91 | } 92 | 93 | // app.middleware.ts ============================================================= 94 | import { NestMiddleware } from "@nestjs/common"; 95 | 96 | export const GlobalMiddleware = (req, res, next) => { 97 | console.log("GlobalMiddleware: 全局绑定中间件"); 98 | next(); 99 | }; 100 | 101 | export class Modulemiddleware implements NestMiddleware { 102 | use(req, res, next) { 103 | console.log("Modulemiddleware: 模块绑定中间件"); 104 | next(); 105 | } 106 | } 107 | 108 | // app.guard.ts ============================================================= 109 | import { CanActivate } from "@nestjs/common"; 110 | 111 | export class GlobalGuard implements CanActivate { 112 | canActivate() { 113 | console.log("GlobalGuard: 全局守卫"); 114 | return true; 115 | } 116 | } 117 | 118 | export class ControllerGuard implements CanActivate { 119 | canActivate() { 120 | console.log("ControllerGuard: 控制器守卫"); 121 | return true; 122 | } 123 | } 124 | 125 | export class RouteGuard implements CanActivate { 126 | canActivate() { 127 | console.log("RouteGuard: 路由守卫"); 128 | return true; 129 | } 130 | } 131 | 132 | // app.interceptor.ts ============================================================= 133 | import { NestInterceptor } from "@nestjs/common"; 134 | import { tap } from "rxjs"; 135 | 136 | export class GlobalInterceptor implements NestInterceptor { 137 | intercept(context, next) { 138 | console.log("GlobalInterceptor: 全局拦截器-控制器前"); 139 | return next.handle().pipe( 140 | tap(() => { 141 | console.log("GlobalInterceptor: 全局拦截器-控制器后"); 142 | }) 143 | ); 144 | } 145 | } 146 | 147 | export class ControllerInterceptor implements NestInterceptor { 148 | intercept(context, next) { 149 | console.log("ControllerInterceptor: 控制器拦截器-控制器前"); 150 | return next.handle().pipe( 151 | tap(() => { 152 | console.log("ControllerInterceptor: 控制器拦截器-控制器后"); 153 | }) 154 | ); 155 | } 156 | } 157 | 158 | export class RouteInterceptor implements NestInterceptor { 159 | intercept(context, next) { 160 | console.log("RouteInterceptor: 路由拦截器-控制器前"); 161 | return next.handle().pipe( 162 | tap(() => { 163 | console.log("RouteInterceptor: 路由拦截器-控制器后"); 164 | }) 165 | ); 166 | } 167 | } 168 | 169 | // app.pipe.ts ============================================================= 170 | import { PipeTransform } from "@nestjs/common"; 171 | 172 | export class GlobalPipe implements PipeTransform { 173 | transform(value) { 174 | console.log("GlobalPipe: 全局管道"); 175 | return value; 176 | } 177 | } 178 | 179 | export class ControllerPipe implements PipeTransform { 180 | transform(value) { 181 | console.log("ControllerPipe: 控制器管道"); 182 | return value; 183 | } 184 | } 185 | 186 | export class RoutePipe implements PipeTransform { 187 | transform(value) { 188 | console.log("RoutePipe: 路由管道"); 189 | return value; 190 | } 191 | } 192 | 193 | export class RouteParamPipe implements PipeTransform { 194 | constructor(private type) {} 195 | transform(value) { 196 | console.log("RouteParamPipe: 路由参数管道" + this.type); 197 | return value; 198 | } 199 | } 200 | 201 | // app.filter.ts ============================================================= 202 | import { ExceptionFilter, Catch, BadRequestException, InternalServerErrorException } from '@nestjs/common'; 203 | 204 | @Catch() 205 | export class GlobalFilter implements ExceptionFilter { 206 | catch(exception, host) { 207 | console.log("GlobalFilter: 全局异常过滤器", exception); 208 | host.switchToHttp().getResponse().json(exception.message); 209 | } 210 | } 211 | 212 | @Catch(InternalServerErrorException) 213 | export class ControllerFilter implements ExceptionFilter { 214 | catch(exception, host) { 215 | console.log("ControllerFilter: 控制器异常过滤器", exception); 216 | host.switchToHttp().getResponse().json(exception.message); 217 | } 218 | } 219 | 220 | @Catch(BadRequestException) 221 | export class RouteFilter implements ExceptionFilter { 222 | catch(exception, host) { 223 | console.log("RouteFilter: 路由异常过滤器", exception); 224 | host.switchToHttp().getResponse().json(exception.message); 225 | } 226 | }`; 227 | --------------------------------------------------------------------------------