From a15781667411e66440a615be2c68f3ce9f082c21 Mon Sep 17 00:00:00 2001 From: superBiuBiu Date: Mon, 21 Oct 2024 00:15:42 +0000 Subject: [PATCH] Site updated: 2024-10-21 00:15:42 --- .github/workflows/HexoSeoAutoPush.yml | 30 ++++++++++++++++++ 10d9fe66.html | 1 + 116b0940.html | 1 + 12474b91.html | 1 + 18c4d7af.html | 1 + 192840d8.html | 1 + 1ac67065.html | 9 ++++++ 1e714f22.html | 1 + 1f4ec17a.html | 1 + 212685ca.html | 1 + 2192bb69.html | 1 + 220ca648.html | 1 + 23924cfb.html | 1 + 25924f9f.html | 1 + 2681a891.html | 1 + 2724e.html | 1 + 273b44bb.html | 1 + 284a3682.html | 1 + 2aefebb.html | 1 + 2cb9cb36.html | 1 + 2d4609.html | 1 + 2e3bcd80.html | 1 + 30ebed6d.html | 1 + 316d79f9.html | 1 + 3183a5d5.html | 1 + 3388f24c.html | 1 + 36e87c18.html | 1 + 36fd9db3.html | 1 + 374f9bfd.html | 1 + 393ca5ba.html | 1 + 394d2f27.html | 1 + 39cea9d6.html | 1 + 3a777744.html | 1 + 3be58dac.html | 1 + 3cb9e66e.html | 1 + 3fc93d78.html | 1 + 40766dbf.html | 1 + 413722d6.html | 1 + 415dc4d6.html | 1 + 41635aab.html | 1 + 42c1e321.html | 1 + 43093808.html | 1 + 434010ba.html | 1 + 447d3137.html | 1 + 451acffa.html | 1 + 45513c12.html | 1 + 4561a0ef.html | 1 + 4687ae16.html | 1 + 4809f4cc.html | 1 + 4ab6d7aa.html | 1 + 4b280335.html | 1 + 4f2816f0.html | 1 + 4f8d5179.html | 1 + 50575b3e.html | 1 + 50b2d4ac.html | 1 + 50c7a259.html | 1 + 53412998.html | 1 + 5391ecf8.html | 1 + 53b29abd.html | 1 + 5419262f.html | 1 + 54e18b38.html | 1 + 55a3c0ce.html | 1 + 565d1ba5.html | 1 + 5744dcde.html | 1 + 5978735e.html | 1 + 5c543e29.html | 1 + 5dcd009d.html | 1 + 607299df.html | 1 + 60af1bf8.html | 1 + 63116b94.html | 1 + 638af414.html | 1 + 64b8ea48.html | 1 + 65204d30.html | 1 + 66bcb49.html | 1 + 6708b1a2.html | 1 + 67b77f3c.html | 1 + 6ace453b.html | 1 + 6b274c12.html | 1 + 739d241.html | 1 + 73c88c12.html | 1 + 7429306f.html | 1 + 75bf0d7.html | 1 + 7613e319.html | 1 + 76cdd9a1.html | 1 + 77ecdc83.html | 1 + 7afd4786.html | 1 + 7c53a2bd.html | 1 + 7c9666d2.html | 1 + 7e386358.html | 1 + 7ec112ae.html | 1 + 8009c290.html | 1 + 81611d8.html | 1 + 83d071ca.html | 1 + 84b2339c.html | 1 + 881e4206.html | 1 + 8a2682cd.html | 1 + 8b5e5cf7.html | 1 + 8d55d49a.html | 17 ++++++++++ 8eb2cbd0.html | 1 + 9131599a.html | 1 + 914c2cae.html | 1 + 9151d943.html | 1 + 91ff580f.html | 1 + 92310ed8.html | 1 + 928aeeef.html | 2 ++ 933f6d7f.html | 1 + 93d99268.html | 1 + 9435f721.html | 1 + 95c05373.html | 1 + 96254c31.html | 1 + 9df9c857.html | 1 + 9e915fa8.html | 1 + 9ff8515e.html | 1 + a00af0bf.html | 1 + a23f7333.html | 1 + a50ac5da.html | 1 + a816b0a1.html | 1 + about/index.html | 1 + af3652d4.html | 1 + archives/2022/02/index.html | 1 + archives/2022/03/index.html | 1 + archives/2022/04/index.html | 1 + archives/2022/04/page/2/index.html | 1 + archives/2022/05/index.html | 1 + archives/2022/05/page/2/index.html | 1 + archives/2022/05/page/3/index.html | 1 + archives/2022/05/page/4/index.html | 1 + archives/2022/06/index.html | 1 + archives/2022/07/index.html | 1 + archives/2022/07/page/2/index.html | 1 + archives/2022/08/index.html | 1 + archives/2022/09/index.html | 1 + archives/2022/10/index.html | 1 + archives/2022/11/index.html | 1 + archives/2022/12/index.html | 1 + archives/2022/index.html | 1 + archives/2022/page/10/index.html | 1 + archives/2022/page/11/index.html | 1 + archives/2022/page/12/index.html | 1 + archives/2022/page/2/index.html | 1 + archives/2022/page/3/index.html | 1 + archives/2022/page/4/index.html | 1 + archives/2022/page/5/index.html | 1 + archives/2022/page/6/index.html | 1 + archives/2022/page/7/index.html | 1 + archives/2022/page/8/index.html | 1 + archives/2022/page/9/index.html | 1 + archives/2023/02/index.html | 1 + archives/2023/03/index.html | 1 + archives/2023/04/index.html | 1 + archives/2023/05/index.html | 1 + archives/2023/06/index.html | 1 + archives/2023/09/index.html | 1 + archives/2023/10/index.html | 1 + archives/2023/11/index.html | 1 + archives/2023/12/index.html | 1 + archives/2023/index.html | 1 + archives/2023/page/2/index.html | 1 + archives/2023/page/3/index.html | 1 + archives/2024/01/index.html | 1 + archives/2024/02/index.html | 1 + archives/2024/03/index.html | 1 + archives/2024/04/index.html | 1 + archives/2024/05/index.html | 1 + archives/2024/06/index.html | 1 + archives/2024/07/index.html | 1 + archives/2024/08/index.html | 1 + archives/2024/10/index.html | 1 + archives/2024/index.html | 1 + archives/2024/page/2/index.html | 1 + archives/2024/page/3/index.html | 1 + archives/index.html | 1 + archives/page/10/index.html | 1 + archives/page/11/index.html | 1 + archives/page/12/index.html | 1 + archives/page/13/index.html | 1 + archives/page/14/index.html | 1 + archives/page/15/index.html | 1 + archives/page/16/index.html | 1 + archives/page/17/index.html | 1 + archives/page/2/index.html | 1 + archives/page/3/index.html | 1 + archives/page/4/index.html | 1 + archives/page/5/index.html | 1 + archives/page/6/index.html | 1 + archives/page/7/index.html | 1 + archives/page/8/index.html | 1 + archives/page/9/index.html | 1 + b691d3ea.html | 1 + b92efe42.html | 1 + b9640c69.html | 1 + b9ff50aa.html | 1 + ba8189ed.html | 1 + baidu.txt | 10 ++++++ bbed5cd2.html | 1 + bbee8271.html | 1 + bd7bee46.html | 2 ++ be489729.html | 1 + be7ed336.html | 1 + bedba37b.html | 1 + bfae07e0.html | 1 + bing.json | 1 + c1036aa3.html | 1 + c108c2d.html | 1 + c2752c3f.html | 1 + c3c76e8e.html | 1 + c6b5dfff.html | 1 + c7600fcc.html | 1 + categories/Antd/React/index.html | 1 + categories/Antd/index.html | 1 + categories/Css/index.html | 1 + categories/HTML/css/index.html | 1 + categories/HTML/echarts/index.html | 1 + categories/HTML/index.html | 1 + categories/HTML/javascript/css/index.html | 1 + categories/HTML/javascript/index.html | 1 + categories/HTML/javascript/wechat/index.html | 1 + categories/HTML/javscript/css/index.html | 1 + categories/HTML/javscript/echarts/index.html | 1 + .../HTML/javscript/echarts/vue/index.html | 1 + categories/HTML/javscript/index.html | 1 + categories/HTML/javscript/page/2/index.html | 1 + categories/HTML/javscript/page/3/index.html | 1 + categories/HTML/javscript/page/4/index.html | 1 + categories/HTML/javscript/page/5/index.html | 1 + categories/HTML/javscript/page/6/index.html | 1 + .../HTML/javscript/socket/express/index.html | 1 + categories/HTML/javscript/socket/index.html | 1 + .../HTML/javscript/typescript/index.html | 1 + categories/HTML/javscript/vite/index.html | 1 + categories/HTML/javscript/vue/index.html | 1 + .../HTML/javscript/vue/page/2/index.html | 1 + .../HTML/javscript/vue/page/3/index.html | 1 + categories/HTML/javscript/webpack/index.html | 1 + categories/HTML/javscript/wechat/index.html | 1 + categories/HTML/page/2/index.html | 1 + categories/HTML/page/3/index.html | 1 + categories/HTML/page/4/index.html | 1 + categories/HTML/page/5/index.html | 1 + categories/HTML/page/6/index.html | 1 + categories/HTML/page/7/index.html | 1 + categories/HTML/page/8/index.html | 1 + categories/JAVA/index.html | 1 + categories/MATLAB/index.html | 1 + .../QQ\345\206\234\345\234\272/index.html" | 1 + categories/React/index.html | 1 + categories/antd/index.html | 1 + categories/antd/react/dva/index.html | 1 + categories/antd/react/index.html | 1 + categories/docker/index.html | 1 + categories/docker/rustDesk/index.html | 1 + categories/electron/index.html | 1 + categories/github/action/index.html | 1 + categories/github/index.html | 1 + categories/hexo/index.html | 1 + .../hexo/\347\263\273\347\273\237/index.html" | 1 + categories/index.html | 1 + categories/javscript/html/index.html | 1 + categories/javscript/index.html | 1 + categories/javscript/jQuery/index.html | 1 + categories/python/index.html | 1 + categories/react/index.html | 1 + categories/ts/index.html | 1 + categories/ts/typescript/HTML/index.html | 1 + .../ts/typescript/HTML/javscript/index.html | 1 + categories/ts/typescript/index.html | 1 + categories/typescript/index.html | 1 + categories/valine/LeanCloud/index.html | 1 + .../index.html" | 1 + categories/valine/index.html | 1 + categories/vant/index.html | 1 + categories/vant/vue/index.html | 1 + categories/vue/index.html | 1 + categories/wechat/index.html | 1 + categories/x64dbg/index.html | 1 + .../\345\210\267\351\242\230/CSS/index.html" | 1 + .../\345\210\267\351\242\230/index.html" | 1 + .../page/2/index.html" | 1 + .../page/3/index.html" | 1 + .../index.html" | 1 + .../\345\211\215\347\253\257/index.html" | 1 + .../\345\220\216\347\253\257/index.html" | 1 + .../\345\250\261\344\271\220/index.html" | 1 + .../\345\267\245\345\205\267/index.html" | 1 + .../page/2/index.html" | 1 + .../index.html" | 1 + .../\346\277\200\346\264\273/index.html" | 1 + .../index.html" | 1 + .../\346\212\230\350\205\276/index.html" | 1 + .../\347\247\201\346\234\215/index.html" | 1 + .../\347\263\273\347\273\237/index.html" | 1 + .../\351\232\217\350\256\260/index.html" | 1 + .../\351\235\242\350\257\225/HTML/index.html" | 1 + .../HTML/javscript/index.html" | 1 + .../HTML/javscript/vue/index.html" | 1 + .../\351\235\242\350\257\225/index.html" | 1 + cb27eea3.html | 1 + cb63a8.html | 10 ++++++ cc4cf5d9.html | 1 + cd8d332b.html | 1 + ce8945f.html | 1 + css/index.css | 1 + placeholder => css/var.css | 0 d054e8a4.html | 1 + d1a0c593.html | 1 + d1af866b.html | 1 + d236288b.html | 1 + d2befc3c.html | 1 + d2cac383.html | 1 + d3048434.html | 1 + d47a757b.html | 1 + d9f65d5.html | 1 + dac4a301.html | 1 + e031a88b.html | 1 + e03e7bbd.html | 1 + e08c9b47.html | 1 + e0e17efd.html | 1 + e39b897f.html | 1 + e40fe344.html | 1 + e4e505ca.html | 1 + eb7b141c.html | 1 + ebe3cda6.html | 1 + ec12f96a.html | 1 + ef46a99b.html | 1 + efae7552.html | 1 + f28d574.html | 1 + f4cb1979.html | 1 + f54fdcb0.html | 1 + f8f8513a.html | 1 + fa4383.html | 1 + fcdd2823.html | 1 + fce322fe.html | 1 + google.txt | 10 ++++++ hidden/index.html | 1 + img/404.jpg | Bin 0 -> 12420 bytes img/favicon.png | Bin 0 -> 323 bytes img/friend_404.gif | Bin 0 -> 53439 bytes index.html | 1 + js/main.js | 3 ++ js/search/algolia.js | 5 +++ js/search/local-search.js | 1 + js/tw_cn.js | 1 + js/utils.js | 7 ++++ link/index.html | 1 + navigation/index.html | 1 + page/10/index.html | 1 + page/11/index.html | 1 + page/12/index.html | 1 + page/13/index.html | 1 + page/14/index.html | 1 + page/15/index.html | 1 + page/16/index.html | 1 + page/17/index.html | 1 + page/2/index.html | 1 + page/3/index.html | 1 + page/4/index.html | 1 + page/5/index.html | 1 + page/6/index.html | 1 + page/7/index.html | 1 + page/8/index.html | 1 + page/9/index.html | 1 + "tags/51\345\225\206\345\234\272/index.html" | 1 + tags/API/index.html | 1 + tags/Antd/index.html | 1 + tags/ES5/index.html | 1 + tags/ES6/index.html | 1 + tags/HTML/index.html | 1 + tags/HTML/page/2/index.html | 1 + tags/HTML/page/3/index.html | 1 + tags/HTML/page/4/index.html | 1 + tags/HTML/page/5/index.html | 1 + tags/JAVA/index.html | 1 + tags/JavaScript/index.html | 1 + tags/LeanCloud/index.html | 1 + tags/MATLAB/index.html | 1 + tags/NAT/index.html | 1 + tags/NodeJs/index.html | 1 + "tags/OSS\345\255\230\345\202\250/index.html" | 1 + tags/Pycharm/index.html | 1 + tags/QQ/index.html | 1 + "tags/QQ\345\206\234\345\234\272/index.html" | 1 + tags/React/index.html | 1 + tags/action/index.html | 1 + tags/antd/index.html | 1 + tags/artalk/index.html | 1 + tags/canvas/index.html | 1 + tags/css/index.html | 1 + "tags/css\345\270\203\345\261\200/index.html" | 1 + tags/docker/index.html | 1 + tags/dva/index.html | 1 + tags/echarts/index.html | 1 + tags/express/index.html | 1 + tags/flash/index.html | 1 + tags/flex/index.html | 1 + .../flex\345\270\203\345\261\200/index.html" | 1 + tags/git/index.html | 1 + tags/github/index.html | 1 + tags/grid/index.html | 1 + tags/hexo/index.html | 1 + tags/html/index.html | 1 + tags/idea/index.html | 1 + tags/jQuery/index.html | 1 + tags/java/index.html | 1 + tags/javascript/index.html | 1 + tags/javascript/page/2/index.html | 1 + tags/javscript/index.html | 1 + tags/javscript/page/2/index.html | 1 + tags/javscript/page/3/index.html | 1 + tags/javscript/page/4/index.html | 1 + tags/javscript/page/5/index.html | 1 + tags/js/index.html | 1 + tags/miniConda/index.html | 1 + tags/mock/index.html | 1 + tags/mockjs/index.html | 1 + tags/mouseInc/index.html | 1 + tags/picgo/index.html | 1 + tags/pinia/index.html | 1 + tags/react/index.html | 1 + tags/rustDesk/index.html | 1 + tags/socket/index.html | 1 + tags/ts/index.html | 1 + tags/typescript/index.html | 1 + tags/typescript/page/2/index.html | 1 + tags/uni-app/index.html | 1 + tags/valine/index.html | 1 + tags/vant/index.html | 1 + tags/vite/index.html | 1 + tags/vue-admin/index.html | 1 + tags/vue/index.html | 1 + tags/vue/page/2/index.html | 1 + tags/vue/page/3/index.html | 1 + tags/vue/page/4/index.html | 1 + tags/x64dbg/index.html | 1 + .../index.html" | 1 + "tags/\345\206\234\345\234\272/index.html" | 1 + .../index.html" | 1 + .../page/2/index.html" | 1 + .../page/3/index.html" | 1 + "tags/\345\210\267\351\242\230/index.html" | 1 + .../page/2/index.html" | 1 + .../page/3/index.html" | 1 + "tags/\345\211\215\347\253\257/index.html" | 1 + "tags/\345\220\216\347\253\257/index.html" | 1 + .../index.html" | 1 + .../index.html" | 1 + .../index.html" | 1 + .../index.html" | 1 + .../index.html" | 1 + "tags/\345\267\245\345\205\267/index.html" | 1 + .../page/2/index.html" | 1 + .../index.html" | 1 + .../index.html" | 1 + "tags/\346\212\223\345\214\205/index.html" | 1 + "tags/\346\212\230\350\205\276/index.html" | 1 + "tags/\346\213\226\346\213\275/index.html" | 1 + .../index.html" | 1 + .../index.html" | 1 + .../index.html" | 1 + .../index.html" | 1 + "tags/\346\234\272\345\234\272/index.html" | 1 + "tags/\346\277\200\346\264\273/index.html" | 1 + "tags/\347\231\275\345\253\226/index.html" | 1 + .../index.html" | 1 + .../index.html" | 1 + .../index.html" | 1 + "tags/\347\263\273\347\273\237/index.html" | 1 + .../index.html" | 1 + .../index.html" | 1 + .../index.html" | 1 + .../index.html" | 1 + "tags/\351\232\217\350\256\260/index.html" | 1 + "tags/\351\235\242\350\257\225/index.html" | 1 + .../index.html" | 1 + test/404.html | 1 + test/favicon.ico | Bin 0 -> 25931 bytes test/index.html | 1 + test/index.txt | 6 ++++ test/next.svg | 1 + test/test.zip | Bin 0 -> 448721 bytes test/vercel.svg | 1 + 480 files changed, 573 insertions(+) create mode 100644 .github/workflows/HexoSeoAutoPush.yml create mode 100644 10d9fe66.html create mode 100644 116b0940.html create mode 100644 12474b91.html create mode 100644 18c4d7af.html create mode 100644 192840d8.html create mode 100644 1ac67065.html create mode 100644 1e714f22.html create mode 100644 1f4ec17a.html create mode 100644 212685ca.html create mode 100644 2192bb69.html create mode 100644 220ca648.html create mode 100644 23924cfb.html create mode 100644 25924f9f.html create mode 100644 2681a891.html create mode 100644 2724e.html create mode 100644 273b44bb.html create mode 100644 284a3682.html create mode 100644 2aefebb.html create mode 100644 2cb9cb36.html create mode 100644 2d4609.html create mode 100644 2e3bcd80.html create mode 100644 30ebed6d.html create mode 100644 316d79f9.html create mode 100644 3183a5d5.html create mode 100644 3388f24c.html create mode 100644 36e87c18.html create mode 100644 36fd9db3.html create mode 100644 374f9bfd.html create mode 100644 393ca5ba.html create mode 100644 394d2f27.html create mode 100644 39cea9d6.html create mode 100644 3a777744.html create mode 100644 3be58dac.html create mode 100644 3cb9e66e.html create mode 100644 3fc93d78.html create mode 100644 40766dbf.html create mode 100644 413722d6.html create mode 100644 415dc4d6.html create mode 100644 41635aab.html create mode 100644 42c1e321.html create mode 100644 43093808.html create mode 100644 434010ba.html create mode 100644 447d3137.html create mode 100644 451acffa.html create mode 100644 45513c12.html create mode 100644 4561a0ef.html create mode 100644 4687ae16.html create mode 100644 4809f4cc.html create mode 100644 4ab6d7aa.html create mode 100644 4b280335.html create mode 100644 4f2816f0.html create mode 100644 4f8d5179.html create mode 100644 50575b3e.html create mode 100644 50b2d4ac.html create mode 100644 50c7a259.html create mode 100644 53412998.html create mode 100644 5391ecf8.html create mode 100644 53b29abd.html create mode 100644 5419262f.html create mode 100644 54e18b38.html create mode 100644 55a3c0ce.html create mode 100644 565d1ba5.html create mode 100644 5744dcde.html create mode 100644 5978735e.html create mode 100644 5c543e29.html create mode 100644 5dcd009d.html create mode 100644 607299df.html create mode 100644 60af1bf8.html create mode 100644 63116b94.html create mode 100644 638af414.html create mode 100644 64b8ea48.html create mode 100644 65204d30.html create mode 100644 66bcb49.html create mode 100644 6708b1a2.html create mode 100644 67b77f3c.html create mode 100644 6ace453b.html create mode 100644 6b274c12.html create mode 100644 739d241.html create mode 100644 73c88c12.html create mode 100644 7429306f.html create mode 100644 75bf0d7.html create mode 100644 7613e319.html create mode 100644 76cdd9a1.html create mode 100644 77ecdc83.html create mode 100644 7afd4786.html create mode 100644 7c53a2bd.html create mode 100644 7c9666d2.html create mode 100644 7e386358.html create mode 100644 7ec112ae.html create mode 100644 8009c290.html create mode 100644 81611d8.html create mode 100644 83d071ca.html create mode 100644 84b2339c.html create mode 100644 881e4206.html create mode 100644 8a2682cd.html create mode 100644 8b5e5cf7.html create mode 100644 8d55d49a.html create mode 100644 8eb2cbd0.html create mode 100644 9131599a.html create mode 100644 914c2cae.html create mode 100644 9151d943.html create mode 100644 91ff580f.html create mode 100644 92310ed8.html create mode 100644 928aeeef.html create mode 100644 933f6d7f.html create mode 100644 93d99268.html create mode 100644 9435f721.html create mode 100644 95c05373.html create mode 100644 96254c31.html create mode 100644 9df9c857.html create mode 100644 9e915fa8.html create mode 100644 9ff8515e.html create mode 100644 a00af0bf.html create mode 100644 a23f7333.html create mode 100644 a50ac5da.html create mode 100644 a816b0a1.html create mode 100644 about/index.html create mode 100644 af3652d4.html create mode 100644 archives/2022/02/index.html create mode 100644 archives/2022/03/index.html create mode 100644 archives/2022/04/index.html create mode 100644 archives/2022/04/page/2/index.html create mode 100644 archives/2022/05/index.html create mode 100644 archives/2022/05/page/2/index.html create mode 100644 archives/2022/05/page/3/index.html create mode 100644 archives/2022/05/page/4/index.html create mode 100644 archives/2022/06/index.html create mode 100644 archives/2022/07/index.html create mode 100644 archives/2022/07/page/2/index.html create mode 100644 archives/2022/08/index.html create mode 100644 archives/2022/09/index.html create mode 100644 archives/2022/10/index.html create mode 100644 archives/2022/11/index.html create mode 100644 archives/2022/12/index.html create mode 100644 archives/2022/index.html create mode 100644 archives/2022/page/10/index.html create mode 100644 archives/2022/page/11/index.html create mode 100644 archives/2022/page/12/index.html create mode 100644 archives/2022/page/2/index.html create mode 100644 archives/2022/page/3/index.html create mode 100644 archives/2022/page/4/index.html create mode 100644 archives/2022/page/5/index.html create mode 100644 archives/2022/page/6/index.html create mode 100644 archives/2022/page/7/index.html create mode 100644 archives/2022/page/8/index.html create mode 100644 archives/2022/page/9/index.html create mode 100644 archives/2023/02/index.html create mode 100644 archives/2023/03/index.html create mode 100644 archives/2023/04/index.html create mode 100644 archives/2023/05/index.html create mode 100644 archives/2023/06/index.html create mode 100644 archives/2023/09/index.html create mode 100644 archives/2023/10/index.html create mode 100644 archives/2023/11/index.html create mode 100644 archives/2023/12/index.html create mode 100644 archives/2023/index.html create mode 100644 archives/2023/page/2/index.html create mode 100644 archives/2023/page/3/index.html create mode 100644 archives/2024/01/index.html create mode 100644 archives/2024/02/index.html create mode 100644 archives/2024/03/index.html create mode 100644 archives/2024/04/index.html create mode 100644 archives/2024/05/index.html create mode 100644 archives/2024/06/index.html create mode 100644 archives/2024/07/index.html create mode 100644 archives/2024/08/index.html create mode 100644 archives/2024/10/index.html create mode 100644 archives/2024/index.html create mode 100644 archives/2024/page/2/index.html create mode 100644 archives/2024/page/3/index.html create mode 100644 archives/index.html create mode 100644 archives/page/10/index.html create mode 100644 archives/page/11/index.html create mode 100644 archives/page/12/index.html create mode 100644 archives/page/13/index.html create mode 100644 archives/page/14/index.html create mode 100644 archives/page/15/index.html create mode 100644 archives/page/16/index.html create mode 100644 archives/page/17/index.html create mode 100644 archives/page/2/index.html create mode 100644 archives/page/3/index.html create mode 100644 archives/page/4/index.html create mode 100644 archives/page/5/index.html create mode 100644 archives/page/6/index.html create mode 100644 archives/page/7/index.html create mode 100644 archives/page/8/index.html create mode 100644 archives/page/9/index.html create mode 100644 b691d3ea.html create mode 100644 b92efe42.html create mode 100644 b9640c69.html create mode 100644 b9ff50aa.html create mode 100644 ba8189ed.html create mode 100644 baidu.txt create mode 100644 bbed5cd2.html create mode 100644 bbee8271.html create mode 100644 bd7bee46.html create mode 100644 be489729.html create mode 100644 be7ed336.html create mode 100644 bedba37b.html create mode 100644 bfae07e0.html create mode 100644 bing.json create mode 100644 c1036aa3.html create mode 100644 c108c2d.html create mode 100644 c2752c3f.html create mode 100644 c3c76e8e.html create mode 100644 c6b5dfff.html create mode 100644 c7600fcc.html create mode 100644 categories/Antd/React/index.html create mode 100644 categories/Antd/index.html create mode 100644 categories/Css/index.html create mode 100644 categories/HTML/css/index.html create mode 100644 categories/HTML/echarts/index.html create mode 100644 categories/HTML/index.html create mode 100644 categories/HTML/javascript/css/index.html create mode 100644 categories/HTML/javascript/index.html create mode 100644 categories/HTML/javascript/wechat/index.html create mode 100644 categories/HTML/javscript/css/index.html create mode 100644 categories/HTML/javscript/echarts/index.html create mode 100644 categories/HTML/javscript/echarts/vue/index.html create mode 100644 categories/HTML/javscript/index.html create mode 100644 categories/HTML/javscript/page/2/index.html create mode 100644 categories/HTML/javscript/page/3/index.html create mode 100644 categories/HTML/javscript/page/4/index.html create mode 100644 categories/HTML/javscript/page/5/index.html create mode 100644 categories/HTML/javscript/page/6/index.html create mode 100644 categories/HTML/javscript/socket/express/index.html create mode 100644 categories/HTML/javscript/socket/index.html create mode 100644 categories/HTML/javscript/typescript/index.html create mode 100644 categories/HTML/javscript/vite/index.html create mode 100644 categories/HTML/javscript/vue/index.html create mode 100644 categories/HTML/javscript/vue/page/2/index.html create mode 100644 categories/HTML/javscript/vue/page/3/index.html create mode 100644 categories/HTML/javscript/webpack/index.html create mode 100644 categories/HTML/javscript/wechat/index.html create mode 100644 categories/HTML/page/2/index.html create mode 100644 categories/HTML/page/3/index.html create mode 100644 categories/HTML/page/4/index.html create mode 100644 categories/HTML/page/5/index.html create mode 100644 categories/HTML/page/6/index.html create mode 100644 categories/HTML/page/7/index.html create mode 100644 categories/HTML/page/8/index.html create mode 100644 categories/JAVA/index.html create mode 100644 categories/MATLAB/index.html create mode 100644 "categories/QQ\345\206\234\345\234\272/index.html" create mode 100644 categories/React/index.html create mode 100644 categories/antd/index.html create mode 100644 categories/antd/react/dva/index.html create mode 100644 categories/antd/react/index.html create mode 100644 categories/docker/index.html create mode 100644 categories/docker/rustDesk/index.html create mode 100644 categories/electron/index.html create mode 100644 categories/github/action/index.html create mode 100644 categories/github/index.html create mode 100644 categories/hexo/index.html create mode 100644 "categories/hexo/\347\263\273\347\273\237/index.html" create mode 100644 categories/index.html create mode 100644 categories/javscript/html/index.html create mode 100644 categories/javscript/index.html create mode 100644 categories/javscript/jQuery/index.html create mode 100644 categories/python/index.html create mode 100644 categories/react/index.html create mode 100644 categories/ts/index.html create mode 100644 categories/ts/typescript/HTML/index.html create mode 100644 categories/ts/typescript/HTML/javscript/index.html create mode 100644 categories/ts/typescript/index.html create mode 100644 categories/typescript/index.html create mode 100644 categories/valine/LeanCloud/index.html create mode 100644 "categories/valine/LeanCloud/\347\231\275\345\253\226\346\214\207\345\215\227/index.html" create mode 100644 categories/valine/index.html create mode 100644 categories/vant/index.html create mode 100644 categories/vant/vue/index.html create mode 100644 categories/vue/index.html create mode 100644 categories/wechat/index.html create mode 100644 categories/x64dbg/index.html create mode 100644 "categories/\345\210\267\351\242\230/CSS/index.html" create mode 100644 "categories/\345\210\267\351\242\230/index.html" create mode 100644 "categories/\345\210\267\351\242\230/page/2/index.html" create mode 100644 "categories/\345\210\267\351\242\230/page/3/index.html" create mode 100644 "categories/\345\210\267\351\242\230/\347\254\224\350\257\225\350\257\225\345\215\267/index.html" create mode 100644 "categories/\345\211\215\347\253\257/index.html" create mode 100644 "categories/\345\220\216\347\253\257/index.html" create mode 100644 "categories/\345\250\261\344\271\220/index.html" create mode 100644 "categories/\345\267\245\345\205\267/index.html" create mode 100644 "categories/\345\267\245\345\205\267/page/2/index.html" create mode 100644 "categories/\345\267\245\345\205\267/\345\260\217\347\250\213\345\272\217/index.html" create mode 100644 "categories/\345\267\245\345\205\267/\346\277\200\346\264\273/index.html" create mode 100644 "categories/\345\267\245\345\205\267\345\272\223/index.html" create mode 100644 "categories/\346\212\230\350\205\276/index.html" create mode 100644 "categories/\347\247\201\346\234\215/index.html" create mode 100644 "categories/\347\263\273\347\273\237/index.html" create mode 100644 "categories/\351\232\217\350\256\260/index.html" create mode 100644 "categories/\351\235\242\350\257\225/HTML/index.html" create mode 100644 "categories/\351\235\242\350\257\225/HTML/javscript/index.html" create mode 100644 "categories/\351\235\242\350\257\225/HTML/javscript/vue/index.html" create mode 100644 "categories/\351\235\242\350\257\225/index.html" create mode 100644 cb27eea3.html create mode 100644 cb63a8.html create mode 100644 cc4cf5d9.html create mode 100644 cd8d332b.html create mode 100644 ce8945f.html create mode 100644 css/index.css rename placeholder => css/var.css (100%) create mode 100644 d054e8a4.html create mode 100644 d1a0c593.html create mode 100644 d1af866b.html create mode 100644 d236288b.html create mode 100644 d2befc3c.html create mode 100644 d2cac383.html create mode 100644 d3048434.html create mode 100644 d47a757b.html create mode 100644 d9f65d5.html create mode 100644 dac4a301.html create mode 100644 e031a88b.html create mode 100644 e03e7bbd.html create mode 100644 e08c9b47.html create mode 100644 e0e17efd.html create mode 100644 e39b897f.html create mode 100644 e40fe344.html create mode 100644 e4e505ca.html create mode 100644 eb7b141c.html create mode 100644 ebe3cda6.html create mode 100644 ec12f96a.html create mode 100644 ef46a99b.html create mode 100644 efae7552.html create mode 100644 f28d574.html create mode 100644 f4cb1979.html create mode 100644 f54fdcb0.html create mode 100644 f8f8513a.html create mode 100644 fa4383.html create mode 100644 fcdd2823.html create mode 100644 fce322fe.html create mode 100644 google.txt create mode 100644 hidden/index.html create mode 100644 img/404.jpg create mode 100644 img/favicon.png create mode 100644 img/friend_404.gif create mode 100644 index.html create mode 100644 js/main.js create mode 100644 js/search/algolia.js create mode 100644 js/search/local-search.js create mode 100644 js/tw_cn.js create mode 100644 js/utils.js create mode 100644 link/index.html create mode 100644 navigation/index.html create mode 100644 page/10/index.html create mode 100644 page/11/index.html create mode 100644 page/12/index.html create mode 100644 page/13/index.html create mode 100644 page/14/index.html create mode 100644 page/15/index.html create mode 100644 page/16/index.html create mode 100644 page/17/index.html create mode 100644 page/2/index.html create mode 100644 page/3/index.html create mode 100644 page/4/index.html create mode 100644 page/5/index.html create mode 100644 page/6/index.html create mode 100644 page/7/index.html create mode 100644 page/8/index.html create mode 100644 page/9/index.html create mode 100644 "tags/51\345\225\206\345\234\272/index.html" create mode 100644 tags/API/index.html create mode 100644 tags/Antd/index.html create mode 100644 tags/ES5/index.html create mode 100644 tags/ES6/index.html create mode 100644 tags/HTML/index.html create mode 100644 tags/HTML/page/2/index.html create mode 100644 tags/HTML/page/3/index.html create mode 100644 tags/HTML/page/4/index.html create mode 100644 tags/HTML/page/5/index.html create mode 100644 tags/JAVA/index.html create mode 100644 tags/JavaScript/index.html create mode 100644 tags/LeanCloud/index.html create mode 100644 tags/MATLAB/index.html create mode 100644 tags/NAT/index.html create mode 100644 tags/NodeJs/index.html create mode 100644 "tags/OSS\345\255\230\345\202\250/index.html" create mode 100644 tags/Pycharm/index.html create mode 100644 tags/QQ/index.html create mode 100644 "tags/QQ\345\206\234\345\234\272/index.html" create mode 100644 tags/React/index.html create mode 100644 tags/action/index.html create mode 100644 tags/antd/index.html create mode 100644 tags/artalk/index.html create mode 100644 tags/canvas/index.html create mode 100644 tags/css/index.html create mode 100644 "tags/css\345\270\203\345\261\200/index.html" create mode 100644 tags/docker/index.html create mode 100644 tags/dva/index.html create mode 100644 tags/echarts/index.html create mode 100644 tags/express/index.html create mode 100644 tags/flash/index.html create mode 100644 tags/flex/index.html create mode 100644 "tags/flex\345\270\203\345\261\200/index.html" create mode 100644 tags/git/index.html create mode 100644 tags/github/index.html create mode 100644 tags/grid/index.html create mode 100644 tags/hexo/index.html create mode 100644 tags/html/index.html create mode 100644 tags/idea/index.html create mode 100644 tags/jQuery/index.html create mode 100644 tags/java/index.html create mode 100644 tags/javascript/index.html create mode 100644 tags/javascript/page/2/index.html create mode 100644 tags/javscript/index.html create mode 100644 tags/javscript/page/2/index.html create mode 100644 tags/javscript/page/3/index.html create mode 100644 tags/javscript/page/4/index.html create mode 100644 tags/javscript/page/5/index.html create mode 100644 tags/js/index.html create mode 100644 tags/miniConda/index.html create mode 100644 tags/mock/index.html create mode 100644 tags/mockjs/index.html create mode 100644 tags/mouseInc/index.html create mode 100644 tags/picgo/index.html create mode 100644 tags/pinia/index.html create mode 100644 tags/react/index.html create mode 100644 tags/rustDesk/index.html create mode 100644 tags/socket/index.html create mode 100644 tags/ts/index.html create mode 100644 tags/typescript/index.html create mode 100644 tags/typescript/page/2/index.html create mode 100644 tags/uni-app/index.html create mode 100644 tags/valine/index.html create mode 100644 tags/vant/index.html create mode 100644 tags/vite/index.html create mode 100644 tags/vue-admin/index.html create mode 100644 tags/vue/index.html create mode 100644 tags/vue/page/2/index.html create mode 100644 tags/vue/page/3/index.html create mode 100644 tags/vue/page/4/index.html create mode 100644 tags/x64dbg/index.html create mode 100644 "tags/\344\270\203\347\211\233\344\272\221/index.html" create mode 100644 "tags/\345\206\234\345\234\272/index.html" create mode 100644 "tags/\345\210\267\345\210\267\345\210\267/index.html" create mode 100644 "tags/\345\210\267\345\210\267\345\210\267/page/2/index.html" create mode 100644 "tags/\345\210\267\345\210\267\345\210\267/page/3/index.html" create mode 100644 "tags/\345\210\267\351\242\230/index.html" create mode 100644 "tags/\345\210\267\351\242\230/page/2/index.html" create mode 100644 "tags/\345\210\267\351\242\230/page/3/index.html" create mode 100644 "tags/\345\211\215\347\253\257/index.html" create mode 100644 "tags/\345\220\216\347\253\257/index.html" create mode 100644 "tags/\345\233\276\344\271\246\345\205\204\345\274\237/index.html" create mode 100644 "tags/\345\233\276\346\240\207\345\272\223/index.html" create mode 100644 "tags/\345\244\247\345\212\233\351\207\221\345\210\232/index.html" create mode 100644 "tags/\345\244\257\345\244\247\345\212\233/index.html" create mode 100644 "tags/\345\260\217\347\250\213\345\272\217/index.html" create mode 100644 "tags/\345\267\245\345\205\267/index.html" create mode 100644 "tags/\345\267\245\345\205\267/page/2/index.html" create mode 100644 "tags/\345\267\245\345\205\267\345\272\223/index.html" create mode 100644 "tags/\345\276\256\344\277\241\345\260\217\347\250\213\345\272\217/index.html" create mode 100644 "tags/\346\212\223\345\214\205/index.html" create mode 100644 "tags/\346\212\230\350\205\276/index.html" create mode 100644 "tags/\346\213\226\346\213\275/index.html" create mode 100644 "tags/\346\217\222\347\224\273\345\272\223/index.html" create mode 100644 "tags/\346\220\234\347\213\227\350\276\223\345\205\245\346\263\225/index.html" create mode 100644 "tags/\346\226\207\345\277\203\344\270\200\350\250\200/index.html" create mode 100644 "tags/\346\230\216\346\234\210\346\227\240\345\277\203\350\276\205\345\212\251/index.html" create mode 100644 "tags/\346\234\272\345\234\272/index.html" create mode 100644 "tags/\346\277\200\346\264\273/index.html" create mode 100644 "tags/\347\231\275\345\253\226/index.html" create mode 100644 "tags/\347\231\275\345\253\226\346\214\207\345\215\227/index.html" create mode 100644 "tags/\347\247\273\345\212\250\347\253\257/index.html" create mode 100644 "tags/\347\251\277\350\266\212\347\201\253\347\272\277/index.html" create mode 100644 "tags/\347\263\273\347\273\237/index.html" create mode 100644 "tags/\350\207\252\345\256\232\344\271\211\347\237\255\350\257\255/index.html" create mode 100644 "tags/\350\277\255\344\273\243\345\231\250/index.html" create mode 100644 "tags/\351\207\215\350\243\205\347\263\273\347\273\237/index.html" create mode 100644 "tags/\351\230\277\351\207\214\344\272\221/index.html" create mode 100644 "tags/\351\232\217\350\256\260/index.html" create mode 100644 "tags/\351\235\242\350\257\225/index.html" create mode 100644 "tags/\351\262\250\351\261\274\350\276\205\345\212\251/index.html" create mode 100644 test/404.html create mode 100644 test/favicon.ico create mode 100644 test/index.html create mode 100644 test/index.txt create mode 100644 test/next.svg create mode 100644 test/test.zip create mode 100644 test/vercel.svg diff --git a/.github/workflows/HexoSeoAutoPush.yml b/.github/workflows/HexoSeoAutoPush.yml new file mode 100644 index 000000000..a515bc782 --- /dev/null +++ b/.github/workflows/HexoSeoAutoPush.yml @@ -0,0 +1,30 @@ + +name: Hexo SEO Auto Push + +on: + schedule: + - cron: 0 4 * * * + watch: + types: [started] +jobs: + build: + runs-on: ubuntu-latest + if: github.event.repository.owner.id == github.event.sender.id || github.event_name == 'schedule' + steps: + - uses: actions/checkout@main + - uses: actions/setup-node@main + with: + node-version: latest + - run: | + npm init -y + npm install hexo-seo-autopush + + - name: google push + run: npx hexoautopush ${{secrets.google_client_email}} ${{secrets.google_private_key}} + + - name: bing push + run: curl -X POST "https://ssl.bing.com/webmaster/api.svc/json/SubmitUrlBatch?apikey=${{secrets.bing_apikey}}" -H "Content-Type:application/json" -H "charset:utf-8" -d @bing.json + + - name: baidu push + run: curl -H "Content-Type:text/plain" --data-binary @baidu.txt "http://data.zz.baidu.com/urls?site=https://www.dreamlove.top&token=${{secrets.baidu_token}}" + \ No newline at end of file diff --git a/10d9fe66.html b/10d9fe66.html new file mode 100644 index 000000000..bbd103954 --- /dev/null +++ b/10d9fe66.html @@ -0,0 +1 @@ +今日刷题-微任务和宏任务 | 梦洁小站-属于你我的小天地

今日刷题-微任务和宏任务

题目1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
执行以下程序,输出结果为()

var a = 100;

function a(){

var a = 200;

console.log(a);

}

a();

A: 100

B: 200

C: 抛出异常

D: f a(){var a = 200;console.log(a);}
  • 答案

    • C
  • 解析

    • 首先注意,函数提升优先级高于变量提升(也就是先函数提升才轮到变量) 之前我一直以为是被覆盖….

    • 所以上面的代码相当于

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      function a(){

      var a = 200;

      console.log(a);

      }
      var a
      a = 200;
      a = 100;
      a();
      //所以报错~

题目2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var num = prompt('请输入分母:')
try{
console.log('a');
value = 0 / num;
console.log('b');
}
catch(e){
console.log('c');
}
finally{
console.log('d');
}
A: a , c , d

B: a , b , d

C: a , b , c , d

D: a , b , c
  • 答案

    • B
  • 解析

    • 虽然说0不能做分母,如果做了会报错,在其他语言是这样子的,但是JavaScript不会,因为JavaScript有NaN类型~
    • 所以 0 / 0 => NaN 不会报错,所以不会捕捉到异常
    • finally不管try…catch是成功还是失败都会执行

题目3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
console.log(1);

let a = setTimeout(() => {console.log(2)}, 0);

console.log(3);

Promise.resolve(4).then(b => {
console.log(b);

clearTimeout(a);
});

console.log(5);

A: 1 , 2 , 3 , 4 , 5
B: 1 , 3 , 4 , 5
C: 1 , 3 , 5 , 4
D: 1 , 3 , 5 , 4 , 2
  • 答案

    • C
  • 解析

    • js代码分为同步任务和异步任务,js先执行同步任务后执行任务(比如定时器,promise)

    • 并且异步任务分为(微任务和宏任务,优先级为微任务 > 宏任务)

    • 所以代码先输出1,3,5,然后执行promise,由于promise当中清除了定时器a,所以不会执行定时器了

文章作者: 梦洁
文章链接: https://www.dreamlove.top/10d9fe66.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

评论
\ No newline at end of file diff --git a/116b0940.html b/116b0940.html new file mode 100644 index 000000000..c3b0282c1 --- /dev/null +++ b/116b0940.html @@ -0,0 +1 @@ +Vant组件当中van-list的使用在自定义列表当中 | 梦洁小站-属于你我的小天地

Vant组件当中van-list的使用在自定义列表当中

起因

  • vant官网没有提及自定义列表当中的使用,并且只有一些简单的例子,这里记录下我使用vant-list的记录代码

需求和使用

文章作者: 梦洁
文章链接: https://www.dreamlove.top/116b0940.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

评论
\ No newline at end of file diff --git a/12474b91.html b/12474b91.html new file mode 100644 index 000000000..c721831ea --- /dev/null +++ b/12474b91.html @@ -0,0 +1 @@ +Typescript的学习笔记 | 梦洁小站-属于你我的小天地

Typescript的学习笔记

前置准备

  • nodejs必安装
  • 全局安装typescript
1
npm install typescript -g
  • 使用tsc命令对ts文件进行编译

    • 进入命名行
    • 进入ts文件所在目录
    • 执行命名tsc xxx.ts即可,xxx.ts中xxx为文件名
      • 如果没有在报错的情况下进行编译,默认情况下依旧会进行编译,但是可以后期配置不编译
    • 编译可以编译为任意js(兼容性处理更加好),后期可以通过配置文件进行配置
  • 文章很多参考和学习这位博主的

ts的基本类型

  • 类型声明是TS非常重要的一个特点

  • 通过类型声明可以指定TS中变量(参数、形参)的类型

  • 指定类型后,当为变量赋值时,TS编译器会自动检查值是否符合类型声明,符合则赋值,否则报错

  • 简而言之,类型声明给变量设置了类型,使得变量只能存储某种类型的值

ts一些类型

类型例子描述
number1, -33, 2.5任意数字
string‘hi’, “hi”, hi任意字符串
booleantrue、false布尔值true或false
字面量其本身限制变量的值就是该字面量的值
any*任意类型
unknown*类型安全的any
void空值(undefined)没有值(或undefined)
never没有值不能是任何值
object{name:’孙悟空’}任意的JS对象
array[1,2,3]任意JS数组
tuple[4,5]元组,TS新增类型,固定长度数组
enumenum{A, B}枚举,TS中新增类型

类型声明细节

类型声明(手动)

1
2
3
4
5
6
7
8
9
10
//手动指明类型
let 变量: 类型;

//手动指明类型并赋值
let 变量: 类型 = 值;

//设置函数形参的变量类型和返回值的类型
function fn(参数: 类型, 参数: 类型): 类型{
...
}

类型声明(自动)

  • 变量的声明和赋值是同时进行的,ts就可以自动对变量进行类型检测的
1
2
3
//ts会自动指明类型
//这样子变量类型就会自动根据变量值判断
let 变量 = 变量值;

示例

1
2
3
4
5
// 变量的声明和赋值是同时进行
//指明了是string类型
let typeString = '123';
typeString = "456";
// typeString = false;//报错

类型声明特殊情况-使用字面量

  • 使用字面量去指定变量的类型,通过字面量可以确定变量的取值范围
1
2
3
4
5
6
7
8
9
10
11
let zml:10;
zml = 10;
// zml = 100;//报错

let sex: "男" | "女";
sex = "男";
sex = "女";
// sex = "未知";//报错

let color: 'red' | 'blue' | 'black';
let num: 1 | 2 | 3 | 4 | 5;

ts的其他类型说明

number

  • 这个不多说声明

  • 支持进制表示

  • 示例代码

1
2
3
4
5
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
let big: bigint = 100n;

boolean

  • 示例代码
1
2
let isDone: boolean = false;
//let isDone: boolean = 134;报错

string

  • 示例代码

    1
    2
    3
    4
    5
    6
    7
    8
    let color: string = "blue";
    color = 'red';

    let fullName: string = `Bob Bobbington`;
    let age: number = 37;
    let sentence: string = `Hello, my name is ${fullName}.

    I'll be ${age + 1} years old next month.`;

any

  • 使用any任意类型,相当于对该变量关闭了ts的类型检测,和普通的js代码一样了

    • 示例代码;
1
2
3
4
5
6
7
8
9
10
11
12
13
 //声明的时候指明类型,显式的any  
let d: any = 4;
d = 'hello';
d = true;

//声明变量的时候不指定类型ts自动判断变量的类型为any,,相当于声明隐式any
let d;

//声明显示的ayn
let anyType:any;

let s:string = '123';
s = anyType;//不会发生报错,因为d的类型是any,会祸害其他人

unknown

  • 示例代码

    1
    2
    3
    4
    5
    let notSure: unknown = 4;
    notSure = 'hello';

    let str:string = '123';
    // str = notSure;//发生报错,unknown不会祸害其他
  • 为什么要使用unknown

    • 第一个原因就是any会祸害其他变量,导致any类型的可以和任何变量兼容,而unknown却做不到

    • 第二个原因就是TypeScript 不允许使用 unknown 的变量,除非将该变量强制转换为已知类型或收窄其类型。(这样子就很好的对变量起到了一个类型的判断)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      const x: unknown = 1;
      x*x;//报错 提示对象的类型为 "unknown"

      if(typeof x === 'number'){
      //将该变量强制转换为已知类型或收窄其类型
      //不报错
      console.log(x*x);
      //不报错
      console.log(<number>x * <number>x);
      }


      const x:any = 1;
      x*x;//不会报错

void

  • 表示没有返回值

  • 示例代码

    1
    2
    3
    4
    5
    6
    let unusable: void = undefined;

    //void类型
    function fn1():void{
    console.log("123");
    }

never

  • 表示永远不会返回结果,比如这个函数是用来报错的,所以永远不会返回结果

  • 示例代码

    1
    2
    3
    function error(message: string): never {
    throw new Error(message);
    }

object

  • 不过这种object类型不太经常用,因为很多情况下都是object

  • 比如 typeof null返回值是object

  • 并且ts认为函数也是一个object

  • 示例代码

    1
    let obj: object = {};

{}

  • 用来指定对象中可以包含哪些属性,并且结构要一模一样,多了,少了都不可以!
  • 比如 let b: {name:string} //设定指向一个对象,并且有name属性,并且name属性为string
  • 属性名后面加一个问号,表示这个属性是可选的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//sex为可选
let myObj: { name: string, sex?: string };

myObj = { name: '李白' };//不报错
myObj = { name: '李白', sex: '男' };//不报错

//当然,我们也可以用接口来实现,这样子会更加好看
interface myInter {
name:string;
sex?:string;
}

let myObj2:myInter = {
name:"李白",
sex:"男"
}
  • [propName:string]:any表示任意类型的属性值

    • propName名字可以随便的,这里只是为了区分下,也可以[xxxx:string]:any
    • 当然,如果需要指明value的类型,可以把any改为指定的类型,比如[propName:string]:string 表示value要为string类型
    1
    2
    3
    4
    5
    //表示有必须要有key为name,value类型为string的值,和其他任意数量的任意key(类型必须要为string)和value(类型随意)
    let myObj: { name: string, [propName: string]: any }
    myObj = { 'name': '李白', "sex": '男' };
    myObj = { 'name': '李白', "sex": '男', 'hobby': "吃饭" };//不报错
    myObj = { 'name': '李白', "sex": '男', 'hobby': 555 };//不报错

设置函数结构的类型声明

  • 语法: (形参1:类型1 , 形参2:类型2 [,…] ) => 返回值
  • 比如 希望getSum为函数,并且有二个参数,均为number,并且返回值为number
1
2
3
function getSum(a: number, b: number): number {
return a + b;
}
  • 希望getResult为函数,并且有二个参数,一个为object,另一个为string,并且返回值为number或者string
1
2
3
function getResult(a: object, b: string): number | string {
return a + b;
}

array(数组)

  • 之前的数组的什么都可以存的,在ts当中可以设置存储数据的类型

  • 格式

    • 类型[]
      • 比如let typeNumberArray:number[];创建一个全部类型都是number的数组
    • Array<类型>(数组泛型的表示)
      • 比如let typeNumberArray:Array<number>;创建一个全部类型都是number的数组
  • 示例代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //表示数组里面只可以存储number类型
    let typeNumberArray:number[];
    let typeNumberArray:Array<number>;
    typeNumberArray = [1,2,3];
    // typeNumberArray = [1,2,'3'];//报错


    let a:string[];//代表字符串数组
    let b:number[];//代表数字数组
  • 需要注意的是,数组的一些方法,比如说push也会受到约束

1
2
3
4
5
6
7
8
let myArray:number[] = [1,2,3,4,56];

//无问题
myArray.push(123);

//报错
//类型“string”的参数不能赋给类型“number”的参数
myArray.push('2');

tuple(元组)

  • 元组就是固定长度的数组,里面存储的值多了不可以,少了也不可以

  • 和数组的类型声明不用的就是反转了下,现在[]在前面了,类型在后面了

  • 示例代码

    1
    2
    3
    4
    5
    6
    7
    let x: [string, number];
    x = ["hello", 10];

    let h: [string, string];
    h = ['123', '456'];
    h = ['123'];//少了,报错;
    h = ['123', '456', '689'];//多了,报错

enum(枚举)

  • 示例代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    enum Sex {
    Male,
    Female
    }

    //设置一个数组的结构为userName和sex
    let userInfo: { userName: string, sex: Sex };

    userInfo = {
    userName:"李白",
    //使用枚举
    sex:Sex.Male
    }
  • 上面代码编译后的js代码

1
2
3
4
5
6
7
8
9
10
11
12
13
var Sex;
(function (Sex) {
Sex[Sex["Male"] = 0] = "Male";
Sex[Sex["Female"] = 1] = "Female";
})(Sex || (Sex = {}));
//设置一个数组的结构为userName和sex
var userInfo;
userInfo = {
userName: "李白",
//使用枚举
sex: Sex.Male
};

&(表示同时满足条件)

1
2
3
4
5
6
//表示key当中既要有name属性和age属性,并且符合规定的value类型
let all: { name: string } & { age: number };
all = {
name: "李白",
age: 1000
}

|(联合类型)

  • 通过|来分割类型,从而使得某一个变量类型可以为多个
    • 比如如下示例let unionType:string|number;代表的意思就是unionType类型可以为string,也可以为number,但是不能为其他类型
1
2
3
4
5
6
7
let unionType:string|number;
unionType = "我是字符串";
unionType = 123;

//报错
// //不能将类型“boolean”分配给类型“string | number”。
unionType = false;

type(类型别名)

  • 关键字是type
1
2
3
4
5
type myType = 1 | 2 | 3 | 4 | 5 | 6;
let one: myType;
let two: myType;
let three: myType;
let four: myType;

类型断言

  • 有时候二边类型编译器判断是不同,但是我们已经明确知道了二边类型是一样的,这时候就可以使用类型断言

  • 语法 变量 as 类型 或者 <类型>变量

  • 比如: 如果直接将unknown赋值给其他类型的变量,是会发生报错的,所以我们需要类型断言

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    let notSure: unknown = 'biubiu';

    let strType = '123';

    //不进行断言,会报错
    // strType = notSure;

    //进行类型断言
    strType = notSure as string;
    //或者这种形式的类型断言

    strType = <string> notSure;

    //类型断言
    function getLength2(str:number|string):number{

    // return (str as string).length;
    // 或者
    return (<string>str).length;
    }

非空断言

  • 相当与是多加了一层判断,存在才会执行下去
1
2
3
4
5
// 非空断言
function func3(arg?:string):number{
//这种情况ts就会提示'对象可能为'未定义''
return arg.length;
}

  • 添加了非空断言(相当与是return arg && arg.length)
1
2
3
4
// 非空断言
function func3(arg?:string):number{
return arg!.length;
}

ts编译

自动编译(单个文件编译,无需配置文件)

  • 编译文件的时候,使用 -w指令后,ts编译器会自动监视文件的变化,并在文件发生变化时候对文件进行重新编译
  • 示例: tsc xxx.ts -w
  • 缺点: 只能对一个文件进行ts,因为没有配置文件!

自动编译(整个编译,有配置文件)

  • 配置文件名称为tsconfig.json

  • tsconfig.json是一个JSON文件,里面填写配置文件项

  • 注意: tsconfig.json是可以添加注释的~其他的JSON不可以哦

  • tsconfig.json配置对象如下,官网API

1.include

  • 设置希望被编译文件所在的目录,为一个数组
  • 默认值为: ["**/*"] 代表当前命名行下所有文件夹和文件

示例:

1
2
3
4
{
// 所有src目录和tests目录下的文件和文件夹里面的ts文件,都会被编译
"include": ["src/**/*", "tests/**/*"],
}

了解下 *** 在tsconfig.json的作用

  • **/ 递归匹配任意子目录
  • * 表示匹配任意文件

2.exclude

  • 设置排除列表
  • 默认值:["node_modules", "bower_components", "jspm_packages"]

示例:

1
2
3
4
{
// test目录及下层等的ts文件都不会编译
"exclude": ["./test/**/*"],
}

3.files

  • 指定被编译文件的列表,只有需要编译的文件少时才会用到

示例: 列表中的文件都会被ts编译器所编译

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"files": [
"core.ts",
"sys.ts",
"types.ts",
"scanner.ts",
"parser.ts",
"utilities.ts",
"binder.ts",
"checker.ts",
"tsc.ts"
]
}

4.extends

示例:

1
2
3
4
{
//上述示例中,当前配置文件会自动打包包含config目录下的base.json文件当中的所有信息
"extends":"./config/base",
}

5.compilerOptions(重要)

  • 编译器的选项,比如说编译模式之类的,里面是由keyvalue组成的
  • 下面为常见的key
1.taget
  • 用来指明ts被编译为js的版本,默认值为”es3”
  • 可选值为: 'es3', 'es5', 'es6', 'es2015', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'esnext'.

target设置为es6的效果

1
2
3
4
5
6
7
8
//编译前				//编译后
var a = 123; => var a = 123;
var b = 'true'; => var b = 'true';
let c = '我是动感超人'; => let c = '我是动感超人';
console.log(c); => console.log(c);
setTimeout(()=>{ => {setTimeout(function () {

}, 900); }, 900);
2.module
  • 指明要使用的模块化规范
  • 可选值为:'none', 'commonjs', 'amd', 'system', 'umd', 'es6', 'es2015', 'es2020', 'es2022', 'esnext', 'node12', 'nodenext'

实例如图: 设置模块化规范为commonjs

index.ts使用es6模块化规范导出say函数,app.ts引入say函数并输出,然后查看编译后的app.js

3.outDir
  • 指定编译后的js文件所在目录
  • 默认情况下,编译后的js文件会和ts文件位于相同的目录,设置outDir后可以改变编译后文件的位置

示例:

1
2
3
4
5
6
{
"compilerOptions": {
// 将编译后的js文件输出到当前配置文件所在目录下的dist文件夹下
"outDir": "./dist",
}
}
4.outFile
  • 将所有的文件编译为一个js文件,默认会将所有编写在全局作用域下的代码合并为一个js文件
  • 如果module配置项指定了none,style或者amd,则会将模块一起合并到文件之中,这几个才可以,es6不可以
5.lib
  • 指明要使用的库
  • 可以写的值
1
'es5', 'es6', 'es2015', 'es7', 'es2016', 'es2017', 'es2018', 'es2019', 'es2020', 'es2021', 'esnext', 'dom', 'dom.iterable', 'webworker', 'webworker.importscripts', 'webworker.iterable', 'scripthost', 'es2015.core', 'es2015.collection', 'es2015.generator', 'es2015.iterable', 'es2015.promise', 'es2015.proxy', 'es2015.reflect', 'es2015.symbol', 'es2015.symbol.wellknown', 'es2016.array.include', 'es2017.object', 'es2017.sharedmemory', 'es2017.string', 'es2017.intl', 'es2017.typedarrays', 'es2018.asyncgenerator', 'es2018.asynciterable', 'es2018.intl', 'es2018.promise', 'es2018.regexp', 'es2019.array', 'es2019.object', 'es2019.string', 'es2019.symbol', 'es2020.bigint', 'es2020.promise', 'es2020.sharedmemory', 'es2020.string', 'es2020.symbol.wellknown', 'es2020.intl', 'es2021.promise', 'es2021.string', 'es2021.weakref', 'es2021.intl', 'esnext.array', 'esnext.symbol', 'esnext.asynciterable', 'esnext.intl', 'esnext.bigint', 'esnext.string', 'esnext.promise', 'esnext.weakref'.
6.allowJs
  • 是否对js文件进行编译,因为当前目录不止会有ts,可能还有js文件
  • 默认为false,代表不编译js
  • 为true代表编译,为false代表不编译js
7.checkJs
  • 是否对js文件进行检查
  • 默认为false,代表不检查
8.removeComments
  • 是否移除注释
  • 默认为false代表不移除
9.noEmit
  • 不生成编译后的文件
  • 默认为false,代表生成编译后的文件
  • 一般用在只想用ts进行语法检查的情况下使用这个配置项
10.noEmitOnError
  • 当有错误的时候不生成编译后的文件
  • 默认为false,代表ts文件有报错也生成js文件,所以我们经常可以看到有报错还编译生成了js文件就是这个原因

严格检查系列

11.strict(总开关)
  • 开启了这个,下面所有的配置项都会设置为true,默认值为false
12.alwaysStrict
  • 总是以严格模式对代码进行编译

  • 注意:如果使用了es6模块化,那么就自动开启了严格模式,所以编译后不会添加”use strict”关键字

  • 值可以为 true | false

13.noImplicitAny
  • 禁止隐式的any类型,默认为false
  • 设置为true后,隐式的any类型不被允许

14.noImplicitThis
  • 禁止类型不明确的this

如图 所以要指明this的类型 测试了.断言是不可以的忽略这语句的

忽略错误

  • 比如根据id获取,可能会获取不到,但是已经确认了 后面加一个 !
    • var a = document.getElementById(“food”)!;
  • 或者直接@ts-ignore 忽略当前行错误

其他的一些忽略

1
2
3
4
忽略全文
// @ts-nocheck
取消忽略全文
// @ts-check

ts-node(编译运行ts代码)

安装:

1
npm install ts-node -g

使用: xxx.ts为文件名称

ts-node xxx.ts

interface(接口)

  • 接口,我理解为就是一个数据类型,和number,undefined,string等类似

简单的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
interface Person{
name:string;
age:number;
}
//使用接口
let tom:Person = {
name:"李白",
age:25
}
//多一个,不行
// index.ts(9,5): error TS2322: Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.
// Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.
let tom:Person = {
name:"李白",
age:25,
gender:"男"
}

//少一个,也不行
//// index.ts(6,5): error TS2322: Type '{ name: string; }' is not assignable to type 'Person'.
// Property 'age' is missing in type '{ name: string; }'.
let tom:Person = {
name:"李白"
}

可选属性

  • 有时我们希望不要完全匹配一个形状,那么可以用可选属性
    • 可选属性的含义是该属性可以不存在
1
2
3
4
5
6
7
8
interface Person {
name:string;
// ?: 代表可选
age?:number;
}
let tom:Person = {
name:"李白"
}
  • 这时仍然不允许添加未定义的属性
1
2
3
4
5
6
7
8
9
10
11
12
13
interface Person {
name: string;
age?: number;
}

let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};

// examples/playground/index.ts(9,5): error TS2322: Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.
// Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.

任意属性

  • 有时候我们希望一个接口允许有任意的属性,可以使用如下方式:
    • 任意属性就是属性名不固定,属性值类型也是不固定
1
2
3
4
5
6
7
8
9
10
interface Person {
name:String;
age?:number;
[propName:string]:any;
}
let tom:Person = {
name:"李白",
//gender:"男" 都可以
//sex:"男" 都可以
}
  • 需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//如此示例
//定义了一个可选属性age
//定义了一个任意属性
//所以
//可选属性和确定属性必须要是任意属性的子集才可以
interface Person {
name: string;
age?: number;
[propName: string]: string;
}

let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};

// index.ts(3,5): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'.
// index.ts(7,5): error TS2322: Type '{ [x: string]: string | number; name: string; age: number; gender: string; }' is not assignable to type 'Person'.
// Index signatures are incompatible.
// Type 'string | number' is not assignable to type 'string'.
// Type 'number' is not assignable to type 'string'.
  • 上例中,任意属性的值允许是 string,但是可选属性 age 的值却是 numbernumber 不是 string 的子属性,所以报错了。
  • 另外,在报错信息中可以看出,此时 { name: 'Tom', age: 25, gender: 'male' } 的类型被推断成了 { [x: string]: string | number; name: string; age: number; gender: string; },这是联合类型和接口的结合。
  • 一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型:
1
2
3
4
5
6
7
8
9
10
11
interface Person {
name:string;
age?:number;
[propName:string]:string|number;
}
let tom:Person = {
name:"Tom",
age:25,
gender:'male',
}
//这里就不会报错了,因为可选属性age是联合属性(string,number)的子集,所以不报错

只读属性

  • 有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly 定义只读属性:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
interface Person {
//只读属性
readonly id:number;
name:string;
//可选属性
age?:number;
//任意属性
[propName:string]:any;
}

let tom:Person = {
id:8888,
name:"汤姆",
gender:"男"
}


//对只读属性进行修改
//报错信息--无法分配到 "id" ,因为它是只读属性。
tom.id = 9999;

接口表示数组

  • 接口也可以用来描述数组:
  • 注意
    • 这里的[index:number]:number;当中的index和之前的propName一样,可以自定义名称,这里为了便于区分就命名为index
    • 代表的意思就是:当keynumber的时候,所对应的value必须要为number类型
  • 回想下
    • 回想下在之前写到过的[propName: string]: any },代表的就是任意的对象属性
    • 代表的意思就是:当keystring类型的时候,所对应的value必须要为number类型
1
2
3
4
5
interface numberList  {
[index:number]:number;
}

let list:numberList = [1,2,3,4,5];

接口表示一个含函数的对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface computed {
param1:string;
param2:string;
getParams():number;
}

// 接口表示
let obj3:computed = {
param1:'123',
param2:'456',
getParams():number{
return 123;
},
}
console.log(obj3);

函数

  • 函数-分为函数的声明,和函数的表达式
    • 函数的声明:函数声明,和var类型的变量一样,会把代码提示到最前面定义(变量提升和函数提升)
    • 函数的表达式:函数在代码执行的时候才会被执行
1
2
3
4
5
6
7
8
9
// 函数声明(Function Declaration)
function sum(x, y) {
return x + y;
}

// 函数表达式(Function Expression)
let mySum = function (x, y) {
return x + y;
};

函数的声明

  • 需要注意的是,函数在typescript当中,声明/定义的时候,参数有几个,使用的时候就应该传入几个,多或者少都不可以
1
2
3
4
5
6
function sum(x: number, y: number): number {
return x + y;
}

//报错 - 应有 2 个参数,但获得 3 个。
sum(1, 2, 3);

函数的表达式

  • 注意不要混淆了 TypeScript 中的=> 和 ES6 中的=>。 在 TypeScript 的类型定义中,=>用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。
  • 变量类型(var或则const或者let) 接收的变量名:(参数)=>返回值类型 = 函数表达式
1
2
3
4
5
6
7
8
9
10
11
12
13
const getSum1 = function sum(x: number, y: number): number {
return x + y;
};

console.log(getSum1(1, 2));//输出3

//完整形式如下
const getSum2: (x: number, y: number) => number = function sum(x: number, y: number): number {
return x + y;
};

//格式差不多是这种
const getSum2:(参数)=>返回值类型 = 函数表达式

鼠标放在getSum1getSum2上面,编译器都可以推断出来有什么参数和返回值类型

用接口定义函数的形状

  • 对象,数组接口的区别就是有一个小括号,并且小括号里面可以写多个变量名和类型,并用逗号分割
  • 如下例子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 回想,之前定义变量的接口是怎么定义的
interface rule1 {
name: string;
age: number;
sex?: string;
}

let personInfo: rule1 = { name: '李白', age: 18, sex: '男' };

// 数组的话
interface rule2 {
[index: number]: number;
}
let personList: rule2 = [1, 2, 3, 4, 4, 5];

// 接口定义函数的形状

interface rule3 {
(x: number, y: number): number;
}
let fn1: rule3 = function (x: number, y: number): number {
return x + y;
};

剩余参数

  • es6中,可以使用...rest的方式获取函数中的剩余参数
1
2
3
4
5
6
7
8
9
10
function push(array, ...items) {
items.forEach((item) => {
array.push(item);
});
}
let a: any[] = [];
push(a, 1, 2, 3, 4, 5);
//输出[ 1, 2, 3, 4, 5 ]
console.log(a);

  • 事实上,items 是一个数组。所以我们可以用数组的类型来定义它:
1
2
3
4
5
6
7
8
9
10
function push(array:any[],...items:any[]){
items.forEach(item=>{
array.push(item)
});
}

let a = [];
push(a, 1, 2, 3, 4, 5);
//输出[ 1, 2, 3, 4, 5 ]
console.log(a);

泛型

那么什么是泛型呢?

  • 泛型就是值在定义函数,接口,类的时候,不预先指定具体的类型,而是在使用的时候才去指定类型
  • 简单来说就是我现在不去规定你这一个变量的具体类型,而是在使用的时候动态去指明(橡皮泥一样)

如果没有泛型,想一想接收一个什么样子类型的参数,就返回什么类型的参数要怎么做,做法就是如下,一个一个写出来

1
2
3
4
5
6
7
8
9
/* 返回一个number类型的数据,也就是输入number,返回number */
function returnValue(value1:number):number{
return value1;
}

/* 返回一个string类型的数据,也就是输入string,返回string */
function returnValue(value1:string):string{
return value1;
}
  • 但是如果有了泛型,就不用这样子了,我们直接在函数后面添加<T>,然后其他类型改为<T>即可实现泛型
    • 这样子就可以做到上面所说的接收什么类型,就返回什么类型的参数
1
2
3
4
5
6
7
function returnValue<T>(value1:T):T{
return value1;
}
//使用可以指明传入的参数的类型
console.log(returnValue<string>('动感超人'));//输出 动感超人
//也可以干脆不指明
console.log(returnValue(123));//输出 123
  • 再来看看下面案例,我们在函数名的后面添加了<T>,其中T代表任意输入的类型,在后面的输入value:T 和输出Array<T>中即可使用了
1
2
3
4
5
6
7
8
9
10
function createArray<T>(length:number,value:T):Array<T>{
let result:T[] = [];
for(let i = 0; i<length; i++){
result[i] = value;
}
return result;
}
console.log(createArray<string>(3,'x'));//[ 'x', 'x', 'x' ]
//或者
console.log(createArray(3,'x'));//[ 'x', 'x', 'x' ]

多个类型参数

  • 下列函数的功能就是定义一个函数,传入一个二个值的函数,可以交换这二个值
    • 其实你仔细看也不是很复杂,就是类型的定义,
    • changValue后面的<T,U>代表会接收二种未指明类型的数据,分别用TU来表示
    • tuple:[T,U]代表是含有二个值的元组(元组就是固定长度的数组)
    • 函数最后面的:[U,T]代表返回值为一个元组,并且类型要是先U类型,然后在T类型
1
2
3
4
5
6
7
function changeValue<T,U>(tuple:[T,U]):[U,T]{
let tempTuple:[U,T] = [tuple[1],tuple[0]];
return tempTuple;
}

//输出 [ 20, '李白' ]
console.log(changeValue(['李白',20]));

泛型约束

  • 在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法,使用就需要泛型约束

  • 泛型约束,大白话说就是规定泛型的内容,必须要符合我们规定的才可以

  • 关键字extends和接口的使用

  • 下面这个案例当中,泛型 T 不一定包含属性 length,所以编译的时候报错了。

1
2
3
4
5
function charge<T>(arg:T):T{
//报错,提示 类型“T”上不存在属性“length”
console.log(arg.length);
return arg;
}
  • 这时,我们可以对泛型进行约束,只允许这个函数传入那些包含 length 属性的变量。这就是泛型约束:
    • 下面案例当中,我们约束了泛型T的范围,使其必须要符合接口lengthWise的形状,也就是必须要包含length属性
    • 这里调用charge("李白")不会报错,是因为"李白"本身是含有length属性的,并且也满足我规定的length属性为number类型
1
2
3
4
5
6
7
8
9
interface lengthWise {
length:number;
}

function charge<T extends lengthWise>(arg:T):T{
console.log(arg.length);
return arg;
}
console.log(charge("李白"));//不报错
  • 此时如果调用 loggingIdentity 的时候,传入的 arg 不包含 length,那么在编译阶段就会报错了
1
2
3
4
5
6
7
8
9
10
interface lengthWise {
length:number;
}

function charge<T extends lengthWise>(arg:T):T{
console.log(arg.length);
return arg;
}
console.log(charge("李白"));//不报错
console.log(charge(123));//类型“number”的参数不能赋给类型“lengthWise”的参数

内置对象

  • @参考文章 - 写的很好
  • 最好了解了解内置对象,不然你就会出现如下问题
    • 我通过document.querySelectorAll()获取dom元素,但是返回的变量类型是什么啊?我应该给接收这个变量规定什么类型啊?
    • ……

ECMAScript 的内置对象

1
2
3
4
let b:Boolean = new Boolean(1);
let e:Error = new Error("Error occurred");
let d:Date = new Date();
let r:RegExp = /[a-z]/;

更多的内置对象,可以查看 MDN 的文档

而他们的定义文件,则在 TypeScript 核心库的定义文件中。

DOM 和 BOM 的内置对象

TypeScript 中会经常用到这些类型:

1
2
3
4
5
let body:HTMLElement = document.body;
let allDiv:NodeList = document.querySelectorAll("div");
document.addEventListener("click",function(e:MouseEvent){
//Do something
})

class类

  • 其实我们一些类的思想已经有了,比如
    • 操作浏览器要使用window对象
      • 比如网页的前进后退
    • 操作网页要使用document对象
      • 比如操作网页的dom元素
    • 操作控制台要使用console对象
      • 比如输出错误,输出警告
  • ts当中的的类是作为一种类型存在的

如图,演示了一种Person类型,一种string类型

如图,演示了一种Person类型,一种string类型

类的实例属性

  • 实例属性,就是实例化对象的属性,每一个实例对象当中的实例属性都是独立的
  • 声明实例属性的时候,既可以赋予初始值,也可以后期通过constructor函数来赋初始值
1
2
3
4
5
class Person {
age: number;
sex: string;
gender = '男';
}

类的构造函数

  • 构造函数,就是为实例属性赋予初始值的一个函数!
  • 注意,构造函数当中不需要返回值和返回值类型!
  • 注意,如果没有在constructor上方注明实例属性,那么输入代码的时候可能会没有提示

正确的(含有实例属性)

1
2
3
4
5
6
7
8
9
10
class Person {
age: number;
sex: string;
constructor(age, sex) {
this.age = age;
this.sex = sex;
}
}
var p1 = new Person(100,'男');
console.log(p1.age);

错误的,没有声明实例属性

  • constructor当中,this执行的是新创建的实例对象
  • 所以this.age访问的是实例属性,所以这里提示类型Person上不存在属性age
1
2
3
4
5
6
7
8
9
10
11
class Person {
// age: number;
// sex: string;
constructor(age, sex) {
this.age = age;
this.sex = sex;
}
}
var p1 = new Person(100,'男');
console.log(p1.age);

没有声明实例属性的报错提示

没有声明实例属性的报错提示

类的继承

extends(js,ts都有)

  • 不多说,看代码~关键字extends,记得调用super去调用父类的构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//父类
class Animal {
name: string;
food: string;
//叫声
ww: string;
constructor(name: string, food: string, ww: string) {
this.food = food;
this.name = name;
this.ww = ww;
}
say() {
console.log(`我会${this.ww}叫`);
}
}

//子类 - 继承 Animal类
class Dog extends Animal {
constructor(name: string, food: string, ww: string) {
super(name, food, ww);
}
}
var xiaobai = new Dog("小白", "狗粮", "汪汪");
console.log(xiaobai);

可见性修饰符(用于属性或方法)

public

  • 公有:默认值,所有的成员都可以在任何地方访问,不管是继承后的子类,还是自己,都可以
  • 没有说明可见性修饰符则默认是public

protected

  • 保护: 只可以在其声明所在类和子类当中使用(实例化对象不可用)

示例代码: 设置叫声为私有属性

1
2
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
//父类
class Animal {
name: string;
food: string;
//叫声
protected ww: string;//设置为私有属性
constructor(name: string, food: string, ww: string) {
this.food = food;
this.name = name;
this.ww = ww;
}
say() {
console.log(`我会${this.ww}叫`);
}
}

//子类 - 继承 Animal类
class Dog extends Animal {
constructor(name: string, food: string, ww: string) {
super(name, food, ww);
}
}
var xiaobai = new Dog("小白", "狗粮", "汪汪");

//报错
//提示:属性“ww”受保护,只能在类“Animal”及其子类中访问
console.log(xiaobai.ww);

报错提示

报错提示

修饰符用于方法

修饰符用于方法

private

  • 私有:只可以在其声明所在类中使用,其他都不可以使用

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Person{
private name: string;
private age: number;

constructor(name: string, age: number){
this.name = name; // 可以修改
this.age = age;
}

sayHello(){
console.log(`大家好,我是${this.name}`);
}
}

class Employee extends Person{

constructor(name: string, age: number){
super(name, age);
this.name = name; //子类中不能修改
}
}

const p = new Person('孙悟空', 18);
p.name = '猪八戒';// 不能修改

readonly修饰符(只可用于属性)

  • readonly表示只读,用来防止在构造函数之外对属性进制赋值!!!

  • 使用readonly关键字修饰该属性是只读的,注意只能修饰属性不能修饰方法。

  • 接口或者 {} 表示的对象类型,也可以使用readonly

  • 只要是readonly来修饰的属性,必须手动提供明确的类型,否者就成为了一个字面量!

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person {
//只要是readonly来修饰的属性,必须手动提供明确的类型,否者就成为了一个字面量!
readonly age: number = 18;
constructor(age: number) {
this.age = age;
}
}

var p1 = new Person(100);
console.log(p1);
//报错,提示无法分配到 "age" ,因为它是只读属性
p1.age = 200;

示例代码:用于{}

1
2
3
let obj: { readonly name: string } = { name: 'jack' };
//报错,提示无法分配到 "name" ,因为它是只读属性。
obj.name = 'Mike';

接口(interface)

  • 接口的作用类似于抽象类,不同点在于接口中的所有方法和属性都是没有实值的换句话说接口中的所有方法都是抽象方法接口主要负责定义一个类的结构,接口可以去限制一个对象的接口,对象只有包含接口中定义的所有属性和方法时才能匹配接口。同时,可以让一个类去实现接口,实现接口时类中要保护接口中的所有属性。
  • 可以使用接口对对象进行约束
  • 也可以使用接口对类进行约束

示例:使用接口对对象进行约束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
interface Person {
name: string;
//规定要有一个函数为'sayHello',并且没有返回值
sayHello(): void;
}

//规定这个per的对象结构为Person定义的结构
function fn1(per: Person) {
//调用per当中的sayHello
per.sayHello();
}
var myObj = {
name: "孙悟空",
//es6的写法
// sayHello(){
// console.log("叫我齐天大圣!");
// }
//或者这种es5的写法也可以
sayHello: function () {
console.log("叫我齐天大圣!");
}
}

fn1(myObj);

示例:使用接口对类进行约束(接口的实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
interface Person {
name: string;
sayHello(): void;
}

class Student implements Person {
constructor(public name: string) {
}

sayHello() {
console.log('大家好,我是' + this.name);
}
}

示例1:接口的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 接口的实现

interface computed {
param1:string;
param2:string;
getParams():number;
}

class myFn implements computed {
param1: string;
param2:string;
getParams():number{
return 123;
}
constructor(param1:string,param2:string){
this.param1 = param1;
this.param2 = param2;
}
}

const one = new myFn('123','456');
console.log(one.getParams());

抽象类/抽象方法

  • 抽象类是专门用来被其他类所继承的类,它只能被其他类所继承不能用来创建实例
  • 抽象类中可以有抽象方法普通方法
  • 抽象方法只能定义在抽象类当中
  • 子类必须要对抽象类的抽象方法进行重写

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
abstract class Animals {
//定义抽象方法
abstract run(): void;
say() {
console.log("动物在叫");
}
}
class Dog extends Animals {
run() {
console.log("狗会汪汪叫");
}
}

var xiaobai = new Dog();
xiaobai.run();//输出:狗会汪汪叫
xiaobai.say();//输出:动物在叫

泛型(Generic)

  • 定义一个函数或类时,有些情况下无法确定其中要使用的具体类型(返回值、参数、属性的类型不能确定),此时泛型便能够发挥作用。

  • 格式,在括号前面添加<>

  • 比如一个函数,我要求有一个参数,用户传入这个参数的类型就是这个函数返回值的类型,那要怎么做?使用泛型

不推荐使用any!

1
2
3
4
//!不推荐使用any!
function test(arg: any): any{
return arg;
}

使用泛型

  • 这里的<T>就是泛型,T是我们给这个类型起的名字(不一定非叫T),设置泛型后即可在函数中使用T来表示该类型。所以泛型其实很好理解,就表示某个类型。
1
2
3
function test<T>(arg: T) {
return arg;
}

那么如何使用泛型定义的函数?

  • 方式一:直接使用
  • 使用时可以直接传递参数使用,类型会由TS自动推断出来,但有时编译器无法自动推断时还需要使用下面的方式
1
2
3
4
5
6
7
8
9
function test<T>(arg: T) {
return arg;
}

//方式一(直接使用):
console.log(test("李白"));//输出:李白
console.log(test(123));//输出:123


  • 方式二:指明类型
1
2
3
4
5
6
7
function test<T>(arg: T) {
return arg;
}

// 方式二:指明类型
console.log(test<string>('杜甫'));//输出:杜甫
console.log(test<boolean>(true));//输出:true
  • 可以同时指定多个泛型,泛型间使用逗号隔开:
1
2
3
4
5
function test2<T, K>(arg1: T, arg2: K): K {
return arg2;
}

test2<number, string>(100, '杜甫');
  • 类中同样可以使用泛型:
1
2
3
4
5
6
7
class MyClass<T>{
prop: T;

constructor(prop: T){
this.prop = prop;
}
}
  • 除此之外,也可以对泛型的范围进行约束

使用T extends MyInter表示泛型T必须是MyInter的子类,不一定非要使用接口类和抽象类同样适用。

1
2
3
4
5
6
7
interface MyInter{
length: number;
}

function test<T extends MyInter>(arg: T): number{
return arg.length;
}

其他

函数默认值

1
2
3
4
// 设置name 默认值为'李白' age为'18'
function sayHello(name: string = "李白", age: string = "18") {
console.log("我的名字叫",name,"年龄为",age);
}
文章作者: 梦洁
文章链接: https://www.dreamlove.top/12474b91.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

评论
avatar
梦洁
小小的字,有大大的梦想~分享我的前端学习过程,经历,错误,和一些其他折腾过程
关注下我(* ̄▽ ̄*)
公告
不断更新中,有问题请留言回复(会通过邮箱提醒~)
目录
  1. 1. 前置准备
  2. 2. ts的基本类型
    1. 2.1. 类型声明细节
      1. 2.1.1. 类型声明(手动)
      2. 2.1.2. 类型声明(自动)
      3. 2.1.3. 类型声明特殊情况-使用字面量
    2. 2.2. ts的其他类型说明
      1. 2.2.1. number
      2. 2.2.2. boolean
      3. 2.2.3. string
      4. 2.2.4. any
      5. 2.2.5. unknown
      6. 2.2.6. void
      7. 2.2.7. never
      8. 2.2.8. object
      9. 2.2.9. {}
      10. 2.2.10. 设置函数结构的类型声明
      11. 2.2.11. array(数组)
      12. 2.2.12. tuple(元组)
      13. 2.2.13. enum(枚举)
      14. 2.2.14. &(表示同时满足条件)
      15. 2.2.15. |(联合类型)
      16. 2.2.16. type(类型别名)
    3. 2.3. 类型断言
    4. 2.4. 非空断言
  3. 3. ts编译
    1. 3.1. 自动编译(单个文件编译,无需配置文件)
    2. 3.2. 自动编译(整个编译,有配置文件)
      1. 3.2.1. 1.include
      2. 3.2.2. 2.exclude
      3. 3.2.3. 3.files
      4. 3.2.4. 4.extends
      5. 3.2.5. 5.compilerOptions(重要)
        1. 3.2.5.1. 1.taget
        2. 3.2.5.2. 2.module
        3. 3.2.5.3. 3.outDir
        4. 3.2.5.4. 4.outFile
        5. 3.2.5.5. 5.lib
        6. 3.2.5.6. 6.allowJs
        7. 3.2.5.7. 7.checkJs
        8. 3.2.5.8. 8.removeComments
        9. 3.2.5.9. 9.noEmit
        10. 3.2.5.10. 10.noEmitOnError
        11. 3.2.5.11. 11.strict(总开关)
        12. 3.2.5.12. 12.alwaysStrict
        13. 3.2.5.13. 13.noImplicitAny
        14. 3.2.5.14. 14.noImplicitThis
    3. 3.3. 忽略错误
    4. 3.4. ts-node(编译运行ts代码)
  4. 4. interface(接口)
    1. 4.1. 可选属性
    2. 4.2. 任意属性
    3. 4.3. 只读属性
  5. 5. 接口表示数组
  6. 6. 接口表示一个含函数的对象
  7. 7. 函数
    1. 7.1. 函数的声明
    2. 7.2. 函数的表达式
    3. 7.3. 用接口定义函数的形状
    4. 7.4. 剩余参数
  8. 8. 泛型
    1. 8.1. 那么什么是泛型呢?
    2. 8.2. 多个类型参数
    3. 8.3. 泛型约束
  9. 9. 内置对象
    1. 9.1. ECMAScript 的内置对象
    2. 9.2. DOM 和 BOM 的内置对象
  10. 10. class类
    1. 10.1. 类的实例属性
    2. 10.2. 类的构造函数
    3. 10.3. 类的继承
      1. 10.3.1. extends(js,ts都有)
    4. 10.4. 可见性修饰符(用于属性或方法)
      1. 10.4.1. public
      2. 10.4.2. protected
      3. 10.4.3. private
    5. 10.5. readonly修饰符(只可用于属性)
    6. 10.6. 接口(interface)
    7. 10.7. 抽象类/抽象方法
    8. 10.8. 泛型(Generic)
  11. 11. 其他
    1. 11.1. 函数默认值
最新文章
\ No newline at end of file diff --git a/18c4d7af.html b/18c4d7af.html new file mode 100644 index 000000000..a721def31 --- /dev/null +++ b/18c4d7af.html @@ -0,0 +1 @@ +油猴(篡改猴)学习记录 | 梦洁小站-属于你我的小天地

油猴(篡改猴)学习记录

第一个Hello World

  • 注意点:默认只匹配了http网站,如果需要https网站,需要自己添加@match https://*/*
  • 代码如下
    • 这样子访问任意网站就可以输出Hello World
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ==UserScript==
// @name 第一个脚本
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @match http://*/*
// @match https://*/*
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant none
// ==/UserScript==

(function() {
'use strict';
console.log("你好,世界")
})();

重要了解

@grant

  • @grant 用于将 GM_*GM.* 函数、 unsafeWindow 对象和一些强大的 window 函数列入白名单。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // @grant GM_setValue
    // @grant GM_getValue
    // @grant GM.setValue
    // @grant GM.getValue
    // @grant GM_setClipboard
    // @grant unsafeWindow
    // @grant window.close
    // @grant window.focus
    // @grant window.onurlchange
  • 由于关闭和聚焦选项卡是一个强大的功能,因此也需要将其添加到 @grant 语句中。如果 @grant 后跟 none ,则禁用沙盒。在此模式下,没有 GM_* 函数,但 GM_info 属性将可用。

1
// @grant none
  • 如果没有给定 @grant 标记,则假定为空列表。但是,这与使用 none 不同。
  • 说白了就是你不设置@grant标记,你就不能使用GM_addElement等等GM_的函数

@match

  • 主要运行脚本的网站
  • 如果需要在全部链接上运行,就只需添加如下
1
@match *://*/*
  • 需要注意的是@match规则是匹配多少次,就运行多少次编写的脚本文件

  • 下面的例子就可以很好的说明match几次就执行多少次脚本
1
2
3
4
5
6
7
8
9
10
// @match        http://www.yinghuavideo.com/v/*
// @match https://tup.yinghuavideo.com/*
// 输出查看
console.log(window.location.href)

//输出如下内容
//第一次输出
http://www.yinghuavideo.com/v/5971-9.html
//第二次输出
https://tup.yinghuavideo.com/?vid=https://cdn18.vipyz-cdn3.com/20230902/15434_377b32aa/index.m3u8$mp4

@require

  • 油猴给我们提供了一个@require属性给我们来引用用户脚本,并且油猴给我们提供了md5,sha256等校验方法来校验引用的脚本是否正确,例如下面这样:

    1
    // @require      https://cdn.jsdelivr.net/npm/vue@2.6.12/dist/vue.min.js#md5=xxxx
  • 如果md5不正确,console中则会显示下面的内容

  • 也支持 SHA-256MD5 哈希
  • 如果给出多个哈希(用逗号或分号分隔),则当前支持的最后一个哈希值由 Tampermonkey 使用。所有哈希都需要以十六进制或 Base64 格式编码。
1
2
3
// @require              https://code.jquery.com/jquery-2.1.1.min.js#md5=45eef...
// @require https://code.jquery.com/jquery-2.1.2.min.js#md5-ac56d...,sha256-6e789...
// @require https://code.jquery.com/jquery-3.6.0.min.js#sha256-/xUj+3OJU...ogEvDej/m4=

@resource

  • 一些可以通过GM_getResourceTextGM_getResourceURL访问的静态资源。 后面写名值对,名是资源的名称,值是相应的url,中间以空格隔开(所以名字中不能包含空格😄),可多次定义

    1
    2
    // @resource logo https://my.cdn.com/logo.png
    // @resource text https://my.cdn.com/some-text.txt
  • 然后就可以使用getResourceText引入了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// ==UserScript==
// @name TEST调试专用
// @namespace https://blog.csdn.net/jx520
// @version 0.1
// @author jerryjin
// @match *://*/*
// @grant GM_getResourceText
// @resource myTxt https://cdn.jsdelivr.net/gh/wandou-cc/blog-ui@20230314_v1/index.css
// ==/UserScript==
(async function() {
'use strict';
let txt = GM_getResourceText('myTxt');
console.log(txt);
let json = JSON.parse(txt);
console.log(json);
})();

@GM_addElement

  • 可以用来添加script,css,或者为指定的DOM添加对应属性或元素

添加script-1

  • head下添加
1
2
3
GM_addElement('script',{
textContent: " window.foo = 'bar' "
})

添加script-指向url

  • head下添加
1
2
3
4
GM_addElement('script',{
src:'https://example.com/script.js',
type:'text/javascript',
})

  • 下面这个操作就不解释什么意思了,应该都看得懂
1
2
3
4
5
6
7
GM_addElement(document.getElementsByTagName('div')[0], 'img', {
src: 'https://example.com/image.png'
});

GM_addElement(shadowDOM, 'style', {
textContent: 'div { color: black; };'
});
  • style应该这么用
1
2
3
4
5
6
7
GM_addElement("style", {
textContent: `
body #git-hub-box-bt {
background: #242429 !important;
border-color: #555666 !important
}`,
});

@GM_addStyle

  • 传入的参数就是css样式,css样式这么写,这里就可以写,用模板语法会很方便,和css书写一样,下面例子就是模板语法添加的样式信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
GM_addStyle(
`
.abc{
position:absolute;
left:0;
right:0;
}
#id{
background:red,
}
body{
padding-left:10px;
background:blue;
}
`
)

@GM_setValue

  • GM_setValue(key,value)
1
2
GM_setValue("someKey", "someData");
await GM.setValue("otherKey", "otherData");
  • 暂时不明白为什么需要await

@GM_getValue

  • GM_getValue(key,defaultValue)
    • key不存在的时候,则返回默认值
1
2
const someKey = GM_getValue("someKey", null);
const otherKey = await GM.getValue("otherKey", null);

@GM_deleteValue

  • GM_deleteValue("someKey")
  • 从用户脚本的存储中删除对应的key
1
2
GM_deleteValue("someKey");
await GM.deleteValue("otherKey");

@GM_listValues

  • GM_listValues 函数返回所有存储数据的key列表。
1
2
3
4
5
6
7
8
9
10
11
12
const keys = GM_listValues();
const asyncKeys = await GM.listValues();

//刚刚设置了
//GM_setValue('sexInfo',JSON.stringify({
// name:'李白',
// age:18,
// sex:'男',
//}))

//输出 数组: ['sexInfo']
console.log(GM_listValues());

unsafeWindow

  • 作用:允许脚本可以完整访问原始页面,包括原始页面的脚本和变量。
  • 简单理解为自己脚本里面的window是独立于外面的,无法访问到原始网页的window对象里面的信息
  • 注意unsafeWindowwindow是不一样的
  • 比如有一个网页,代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
var from4 = '我是来自网页4.html的内容';
</script>
</body>
</html>
  • 然后我们@grant引入unsafeWindow
1
2
3
4
5
//油猴脚本内容如下

console.log(window.from4,unsafeWindow.from4);
//网页输出结果为
// undefined '我是来自网页4.html的内容'
  • 可以看到,我们在代码直接访问window是获取不到的,需要使用unsafeWindow

@GM_registerMenuCommand

  • GM_registerMenuCommand(name, callback, accessKey);

    • name: 包含要为菜单项显示的文本的字符串。

    • callback: 回调:选择菜单项时要调用的函数。该函数将传递单个参数,即当前活动的选项卡。从Tampermonkey 4.14开始,MouseEvent或KeyboardEvent作为函数参数传递。

    • accessKey: 访问键:菜单项的可选访问键。这可用于为菜单项创建快捷方式。例如,如果访问键为“s”,则用户可以在打开Tampermonkey的弹出菜单时按“s”来选择菜单项。

  • 该函数返回可用于注销命令的菜单项 ID

    • 也就是通过GM_unregisterMenuCommand(menuCmdId)
1
2
3
const menu_command_id = GM_registerMenuCommand("Show Alert", function(event: MouseEvent | KeyboardEvent) {
alert("Menu item selected");
}, "a");
  • 如果需要通过alt或者ctrl这种组合键的,就需要如下的做法了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(function () {
"use strict";
const userSelfKey = "h"; //结合后面,设置用户按下alt + h 完成回调
//点击后执行的回调
const callback = () => {
console.log("点击执行回调");
};
GM_registerMenuCommand(`是否启动(Alt+${userSelfKey})`,);
document.addEventListener("keydown", (e) => {
if ((e.altKey, e.key.toLowerCase() === userSelfKey)) {
callback();
}
});
})();

@run-at

  • 说通俗点就是代码什么时候注入(因为注入时机不同,可以对网页的操作量也不同)
  • 官方文档 @地址
  • 就如同作者说的,想要替换setInterval函数,达到时间加速,就必须在调用之前被替换,所以就应该更改run-at的值
1
2
3
4
5
6
7
8
首先来介绍一下时间加速的原理.一般情况下,都是使用setInterval来做定时器,我们只要把这个定时器的时间缩短,比如之前是1s触发一次,现在变成500ms触发一次,那么就相当于时间缩短了一倍.

怎么缩短呢?我们可以劫持setInterval这个函数,传入值为1000,我们把他变为500.代码类似下面这样:

let hookSetInterval=window.setInterval;//将系统提供的setInterval保存
window.setInterval=function(a,b){//将系统的setInterval替换为我们自己的
return hookSetInterval(a,b/2);//经过处理后再调用系统的setInterval
}

@GM_xmlhttpRequest

  • 和自带的ajax请求和fetch更强大,支持跨域这个GM_xmlHttpRequest
  • 重要提示:如果要使用此方法,请同时查看有关 @connect 的文档。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// ==UserScript==
// @name 02.GM_xmlhttpRequest演示
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @match https://bbs.tampermonkey.net.cn/*
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @require file://D:\develop\phpstudy_pro\WWW\studyMaster\油猴脚本\day3\02.GM_xmlhttpRequest演示.js
// @grant GM_xmlhttpRequest
// @connect bbs.tampermonkey.net.cn
// ==/UserScript==

//index.js
(function () {
'use strict';
GM_xmlhttpRequest({
method: 'GET',
url: 'https://bbs.tampermonkey.net.cn/home.php?mod=spacecp&ac=favorite&type=thread&id=1268&formhash=fa62a5ea&infloat=yes&handlekey=k_favorite&inajax=1&ajaxtarget=fwin_content_k_favorite',
onload: (response) => {
console.log(response,response.response)
}
})
})();

@connect

  • 设置允许通过GM_xmlhttpRequest连接访问的域名(包括子域名)。
  • 说白了就是如果在调用@GM_xmlhttpRequest的时候,好像会有一个确认访问域的对话框

代码编写前置工作

前置工作1-@require引入本地文件

  • 方式1:可以在油猴那边编写,然后运行
  • 方法2:借助于@require引入本地文件,然后借助于第三方编辑器进行编写
    • 注意,需要开启允许访问文件网址才可以引入本地文件

  • 然后将下面的文件路径替换为你自己的就好了
    • @require file://文件路径: 将文件路径替换为自己的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ==UserScript==
// @name 我的脚本开发
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @match http://*/*
// @match https://*/*
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @require file://文件路径
//比如我的如下
// @require file://D:\develop\phpstudy_pro\WWW\vue\classWork\油猴\1.helloworld.js
// @grant none
// ==/UserScript==

前置工作2-添加jQuery便携DOM操作

  • 如果不想用原生的或者原生的会用但是没jQuery方便,可以用jQuery来操作DOM,如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// ==UserScript==
// @name 我的脚本开发
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @match http://*/*
// @match https://*/*
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @require https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.js
// @require file://D:\develop\phpstudy_pro\WWW\vue\classWork\油猴\1.helloworld.js
// @grant none
// ==/UserScript==

  • 这样子我们编写脚本直接就可以使用了
    • 下面是我文件的helloworld.js内容
1
2
3
4
5
(function() {
'use strict';
console.log($);
console.log("你好,世界")
})();

代码正式编写

1.练习-1

  • 一个网页添加一个可操作的方块,并有几个功能按钮,效果图如下

1
2
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
(function () {
"use strict";
//创建外层容器
const wrapperDOM = $(`<div></div>`).css({
width: 300,
height: 150,
backgroundColor: "hotpink",
position: "absolute",
top: 60,
right: 0,
});

const button1DOM = $("<button>点击我滚动到底部</button>");
const button2DOM = $(
"<button>点击我将'梦洁小站-属于你我的小天地'更改为'这是我的天下了'</button>"
);
const button3DOM = $("<button>点击我发表评论</button>");

button1DOM.click(() => {
window.scrollTo(0, document.documentElement.scrollHeight);
});

button2DOM.click(() => {
document.getElementById("site-title").textContent = "这是我的天下了";
});

button3DOM.click(() => {
const temp = Date.now();
localStorage.removeItem("WALINE_COMMENT_BOX_EDITOR");
localStorage.removeItem("WALINE_USER_META");
localStorage.setItem("WALINE_COMMENT_BOX_EDITOR", "自动填充评论" + temp);
localStorage.setItem(
"WALINE_USER_META",
JSON.stringify({
nick: "自动填充昵称" + temp,
mail: "自动填充邮箱" + temp,
})
);
window.location.reload();
window.scrollTo(0, document.documentElement.scrollHeight); //先滚动到底部
document.documentElement.addEventListener("load", () => {
window.scroll(0, document.documentElement.scrollHeight);
});
});

wrapperDOM.append([button1DOM, button2DOM, button3DOM]);

//添加到网页当中
$("html").append(wrapperDOM).css({
position: "relative",
});
})();

2.练习-2-CSDN黑夜模式

2.1个人中心页面黑夜模式添加

  • 然后我们尝试把user-skin-Black添加上去,发现变化了

  • 我们书写下油猴脚本代码
1
2
3
4
5
(function() {
'use strict';
const skinDivDOM = document.getElementById('userSkin');
console.log(skinDivDOM.classList.add('user-skin-Black'))
})();
  • 但是发现顶部还没有变化

  • 观察发现是这一个类的问题
    • vip黑色背景为:csdn-toolbar-dark.css
    • 普通用户背景为:csdn-toolbar-default.css

  • 我们油猴试试添加下这个css文件看看,代码就变成了下面这样子
    • @match后期优化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// ==UserScript==
// @name day2-实现csdn黑色效果
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @match *://*/*
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @resource darkToolBarCss https://g.csdnimg.cn/common/csdn-toolbar/csdn-toolbar-dark.css
// @require file://D:\develop\phpstudy_pro\WWW\studyMaster\油猴脚本\day2-实现csdn黑色效果\index.js
// @grant GM_addStyle
// @grant GM_getResourceText
// @grant GM_addElement
// ==/UserScript==

//index.js内容
(function() {
'use strict';
const skinDivDOM = document.getElementById('userSkin');
//个人页面内容区黑色化
skinDivDOM.classList.add('user-skin-Black')
//顶部导航栏黑色化
const darkToolBarCss = GM_getResourceText('darkToolBarCss')
GM_addStyle(darkToolBarCss)
})();

  • 添加完成后是这样子的

  • 发现差一点效果和vip的

vip效果

  • 查看代码,然后优化后代码如下
1
2
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
// ==UserScript==
// @name day2-实现csdn黑色效果
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @match https://blog.csdn.net/*
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @resource darkToolBarCss https://g.csdnimg.cn/common/csdn-toolbar/csdn-toolbar-dark.css
// @require file://D:\develop\phpstudy_pro\WWW\studyMaster\油猴脚本\day2-实现csdn黑色效果\index.js
// @grant GM_addStyle
// @grant GM_getResourceText
// @grant GM_addElement
// ==/UserScript==

//index.js内容如下
(function() {
'use strict';
const skinDivDOM = document.getElementById('userSkin');
//个人页面内容区黑色化
skinDivDOM.classList.add('user-skin-Black')
//顶部导航栏黑色化
const darkToolBarCss = GM_getResourceText('darkToolBarCss')
GM_addStyle(darkToolBarCss)
//顶部导航栏细节优化
const toolBarDOM = document.querySelector('#csdn-toolbar .toolbar-inside');
toolBarDOM.style.background = '#242429';
//顶部导航栏图片替换
const toolBarImgDark = document.querySelector('#csdn-toolbar > div > div > div.toolbar-container-left > div > a > img');
toolBarImgDark.src = 'https://img-home.csdnimg.cn/images/20211028053651.png';
})();

2.2文章内容黑夜模式添加

  • 老样子,观察下标题的样式,看看有什么区别
    • 发现vip用户的黑色背景下的标题多了这个样式,而普通用户没有这个skin-clickm…1.min.css样式文件(不信看网络加载的css文件)

  • 那就很简单了,优化下逻辑代码和添加css,结果如下
1
2
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// ==UserScript==
// @name day2-实现csdn黑色效果
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @match https://blog.csdn.net/*
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @resource darkToolBarCss https://g.csdnimg.cn/common/csdn-toolbar/csdn-toolbar-dark.css
// @resource darkArticleCss https://csdnimg.cn/release/blogv2/dist/pc/themesSkin/skin-clickmove/skin-clickmove-3ae5e69ee1.min.css
// @require file://D:\develop\phpstudy_pro\WWW\studyMaster\油猴脚本\day2-实现csdn黑色效果\index.js
// @grant GM_addStyle
// @grant GM_getResourceText
// @grant GM_addElement
// ==/UserScript==


//index.js
(function() {
'use strict';
/**
* 个人中心黑色背景化
*/
const skinDivDOM = document.getElementById('userSkin');
if(skinDivDOM){
//个人页面内容区黑色化
skinDivDOM.classList.add('user-skin-Black')
}
//顶部导航栏黑色化
const darkToolBarCss = GM_getResourceText('darkToolBarCss')
if(!darkToolBarCss){
console.error('获取顶部导航栏黑色化样式文件失败,请检查css链接是否失效')
return;
}
GM_addStyle(darkToolBarCss)
//顶部导航栏细节优化
const toolBarDOM = document.querySelector('#csdn-toolbar .toolbar-inside');
if(toolBarDOM){
toolBarDOM.style.background = '#242429';
}
//顶部导航栏图片替换
const toolBarImgDark = document.querySelector('#csdn-toolbar > div > div > div.toolbar-container-left > div > a > img');
if(toolBarImgDark){
toolBarImgDark.src = 'https://img-home.csdnimg.cn/images/20211028053651.png';
}
/**
* 文章黑色背景化
*/
const darkArticleCss = GM_getResourceText('darkArticleCss');
if(!darkArticleCss){
console.error('获取文章黑色背景化样式文件失败,请检查css链接是否失效')
return;
}
GM_addStyle(darkArticleCss)
})();

3.脚本自动化之模拟点击和表单填写

1
2
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
// ==UserScript==
// @name 01.脚本自动化之模拟点击和表单填写
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @match https://bbs.tampermonkey.net.cn/*
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @require file://D:\develop\phpstudy_pro\WWW\studyMaster\油猴脚本\day3\01.脚本自动化之模拟点击和表单填写.js
// ==/UserScript==


//index.js
(function() {
'use strict';
const timer = setInterval(() => {
const loginNameDOM = document.querySelector("input[name = 'username']");
const loginPassDOM = document.querySelector("input[name = 'password']");
const loginBtnDOM = document.querySelector("button[name = 'loginsubmit']")
console.log('循环')
if(loginNameDOM && loginPassDOM){
loginNameDOM.value = 'testAccount';
loginPassDOM.value = 'testPassword'
//点击登录
loginBtnDOM.click();
//界面显示,清空循环
clearInterval(timer);
}
},1000)

})();

4.视频倍速播放

5.bilibili一键三连

  • 我们长按三连看看

  • 参数重要的几个
1
2
3
4
5
csrf: 发现是从cookie获取

aid:发现可以通过window.__INITIAL_STATE__.aid来获取,至于为什么这样子,服务器渲染知识了,百度~

其他几个可能和投银币数量有关的参数在里面,但是不管他了~
  • 至于为什么是3秒,好像需要等页面加载完成后再插入,否则会无限刷新
1
2
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
35
36
37
38
39
40
41
42
43
44
45
46
// ==UserScript==
// @name 03.b站一键三连
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @match https://www.bilibili.com/video/*
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @require https://cdn.bootcdn.net/ajax/libs/js-cookie/3.0.2/js.cookie.min.js
// @require file://D:\develop\phpstudy_pro\WWW\studyMaster\油猴脚本\day3\03.b站一键三连.js
// @grant unsafeWindow
// ==/UserScript==

//index.js

(function() {
'use strict';
setTimeout(() => {
const videoBarDOM = document.querySelector('.video-toolbar-left');
const btnDOM = document.createElement('button');
btnDOM.textContent = '三连';
btnDOM.onclick = () => {
const aid = unsafeWindow.__INITIAL_STATE__.aid;
const csrf = Cookies.get('bili_jct');
fetch('https://api.bilibili.com/x/web-interface/archive/like/triple',{
method:"POST",
headers:{
'Content-Type': 'application/x-www-form-urlencoded',
},
credentials:'include',
body:`aid=${aid}&csrf=${csrf}`,
}).then((res) => {
return res.json();
}).then(result => {
const code = result.code;
if(code === 0){
alert("三连成功!刷新页面可见");
}else{
alert("三连失败/(ㄒoㄒ)/~~");
}
})
}
videoBarDOM.append(btnDOM)
},3000)
})();

6.樱花动漫简易去广告

  • 原理很简单,display:none就可以隐藏了,视频播放暂停的广告一开始是display:none,暂停的时候被修改为display:block,所以我们把宽度,高度更改为0就可以了
1
2
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// ==UserScript==
// @name 05.樱花动漫简易去广告
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @match http://www.yinghuavideo.com/v/*
// @match https://tup.yinghuavideo.com/*
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @require file://D:\develop\phpstudy_pro\WWW\studyMaster\油猴脚本\day3\05.樱花动漫简易去广告.js
// @grant GM_addStyle
// ==/UserScript==


//index.js
(function () {
'use strict';
const url = window.location.href;
if (url.indexOf('yinghuavideo.com') !== -1) {
GM_addStyle(`
#HMcoupletDivleft {
display:none !important;
}
#HMcoupletDivright {
display:none !important;
}
`);
//移除右下角
const ttt = document.querySelector('divz');
if (ttt) {
ttt.style.display = 'none';
}
}
if (url.indexOf('tup.yinghuavideo.com') !== -1) {
let i = 0;
const timer = setInterval(() => {
const tempDOM = document.getElementById('adv_wrap_hh');
//超过一分钟还没加载出来,取消加载
if(++i >= 60){
clearInterval(timer);
}
//移除暂停视频广告
if (tempDOM) {
tempDOM.style.cssText = 'width:0!important;height:0!important';
clearInterval(timer);
}
},1000)
}

})();

  • 效果

7.bilibili小尾巴-hook思想

  • 我们发送一个消息看看

  • 参数有下面这些

  • 发送的方式是fetch

1
2
3
4
5
6
7
8
9
const { fetch: originalFetch } = window;

window.fetch = async (...args) => {
let [resource, config ] = args;
// request interceptor here
const response = await originalFetch(resource, config);
// response interceptor here
return response;
};
  • 代码如下
    • decodeURIComponent目的是小尾巴所有的字符都编码,不管是不是特殊字符
1
2
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
// ==UserScript==
// @name 06.bilibili小尾巴-hook思想.js
// @namespace http://tampermonkey.net/
// @version 0.1
// @description try to take over the world!
// @author You
// @match https://www.bilibili.com/video/*
// @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @require file://D:\develop\phpstudy_pro\WWW\studyMaster\油猴脚本\day3\06.bilibili小尾巴-hook思想.js
// @grant unsafeWindow
// ==/UserScript==

//index.js
(function() {
'use strict';
let tailContent = '\n-----我是可爱的小尾巴'
const { fetch:originalFetch } = unsafeWindow;
unsafeWindow.fetch = async (...args) => {
let [ resource,config ] = args;
//resource 网站url
if(resource.includes('api.bilibili.com/x/v2/reply/add')){
//config 请求设置
let { body } = config || {};
const newBody = body.replace(/message=(.*?)&/,(match,$1) => `message=${$1}${decodeURIComponent(tailContent)}&`)
config.body = newBody;
}
const response = await originalFetch(resource,config);
return response
}
})();

效果

链接

  • 点击查看源

  • 我们的却是这样子

  • 点击查看源

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
fetch('https://api.bilibili.com/x/web-interface/archive/like/triple',{
method:"POST",
headers:{
'Content-Type': 'application/x-www-form-urlencoded',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.62'
},
credentials:'include',
body:JSON.stringify({
aid,
csrf,
eab_x: 2,
ramval: 13,
source: 'web_normal',
ga: 1,
})
}).then((res) => {
return res.json();
}).then(json => {
console.log('结果',json)
})
  • 错误原因,我使用了json方式发送数据,这种对应的Content-type应该为application/json

    • 而哔哩哔哩那种是Content-Type为application/x-www-form-urlencoded,也就是类似于key=value&key2=value2…这种形式
    • 而Content-Type为form-data,一般是文件上传的,如下图

    Content-Type为form-data

技巧

使用GM_xmlhttpRequest看不到请求怎么办?

  • 找到扩展程序,点击下面的

  • 然后会打开一个调试窗口,再次发送请求,就可以看到了

错误记录

because it violates the following Content Security Policy directive

  • 油猴运行出现这个
1
because it violates the following Content Security Policy directive: "script-src *.weiyun.com *.qq.com *.gtimg.cn *.gtimg.com *.idqqimg.com *.idqqimg.cn *.tenpay.com *.qpic.cn *.url.cn *.qpimg.cn *.myqcloud.com cdn-go.cn cdn.addon.tencentsuite.com blob: 'self' 'unsafe-inline' 'unsafe-eval'". Note that 'script-src-elem' was not explicitly set, so 'script-src' is used as a fallback.
1
2
3
4
5
6
7
const formData = new FormData();
formData.append('period',expireTime.value + '')
formData.append('pwd','6666')
formData.append('eflag_disable','true')
formData.append('channel_list','[]')
formData.append('schannel','4')
formData.append('fid_list','[297649734372532]')

还有,如果设置了formData发送post数据,一定要省略 xhr.setRequestHeader(‘Content-Type’, ‘application/x-www-form-urlencoded’); 这行代码,否者数据无法正常切割

  • 否者会出现服务器需要下面格式的数据

  • 因为手动设置了Content-Type', 'application/x-www-form-urlencoded,而导致的发送数据无法切割问题
    • 所以切记,切记

文章作者: 梦洁
文章链接: https://www.dreamlove.top/18c4d7af.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

评论
avatar
梦洁
小小的字,有大大的梦想~分享我的前端学习过程,经历,错误,和一些其他折腾过程
关注下我(* ̄▽ ̄*)
公告
不断更新中,有问题请留言回复(会通过邮箱提醒~)
最新文章
\ No newline at end of file diff --git a/192840d8.html b/192840d8.html new file mode 100644 index 000000000..430e0f673 --- /dev/null +++ b/192840d8.html @@ -0,0 +1 @@ +Antd React Form.Item内部是自定义组件怎么自定义返回值 | 梦洁小站-属于你我的小天地

Antd React Form.Item内部是自定义组件怎么自定义返回值

需求

  • 当我们点击提交,需要返回用户名和选中树的id信息,但是,我不关要返回树的id信息,还需要返回选中树的名称
1
2
3
4
5
6
7
8
9
10
11
12
13
//默认返回的
{
userName:'梦洁',
treeInfo:'leaf1-value'
}
//但是需要返回的如下
{
userName:'梦洁'
treeInfo:{
name:'leaf1-name',
value:'leaf1-value'
}
}

做法

了解下Form.Item怎么获取值的

  • 大概就是这样子

实现自定义

  • 主组件index.tsx
1
2
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import {Form,Input,Button} from "antd";
import SelfTreeSelect from "../../component/SelfTreeSelect";

const Index = () => {
const [form] = Form.useForm();
const onFinish = (values: any) => {
console.log('Success:', values);
};

/*初始化值*/
const setInitValue = () => {
form.setFieldsValue({
username:'梦洁',
treeInfo:{
value:'leaf1-value',
//这里传入数组是因为对于树来说,可以多选,所以后端保存的值也可能是数组,
list:['leaf1-title']
}
})
}
return (
<>
<Form
form={form}
name="basic"
onFinish={onFinish}
>
<Form.Item
label="用户名"
name="username"
rules={[{required: true, message: 'Please input your username!'}]}
>
<Input/>
</Form.Item>
{/*自定义组件*/}
<Form.Item
name="treeInfo"
label="树的信息"
>
<SelfTreeSelect/>
</Form.Item>
<Form.Item >
<Button type="primary" htmlType="submit">
Submit
</Button>
</Form.Item>
</Form>
<Button onClick={setInitValue}>点击我初始化值</Button>
</>
);
};

export default Index;
  • 自定义组件SelfTreeSelect.tsx
1
2
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import React, {useEffect, useState} from 'react';
import { TreeSelect } from 'antd';
const treeData = [
{
value: 'parent 1-value',
title: 'parent 1-title',
children: [
{
value: 'parent 1-0-value',
title: 'parent 1-0-title',
children: [
{
value: 'leaf1-value',
title: 'leaf1-title',
},
{
value: 'leaf2-value',
title: 'leaf2-title',
},
],
},
],
},
];
/* 默认值,初始值?form设置值 */
const SelfTreeSelect = (props:any) => {
const { value,onChange } = props;
console.log('查看传入的值',props)
/*这里简单演示下回填值*/
useEffect(() => {
setCurrentValue(value?.list[0])
}, [props]);
/*内部值,这样子就可以设置这个值来实现默认值的操作了*/
const [currentValue, setCurrentValue] = useState<string>();

const onSelectChange = (newValue: string,selectList:never[]) => {
console.log('输出新值第一个为选中的value,第二个为选中的title数组',newValue,selectList)
setCurrentValue(newValue);
onChange({
value:newValue,
list:selectList,
})
};
return (
<TreeSelect
showSearch
style={{ width: '100%' }}
value={currentValue}
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
placeholder="Please select"
allowClear
treeDefaultExpandAll
onChange={onSelectChange}
treeData={treeData}
/>
);
};

export default SelfTreeSelect;
文章作者: 梦洁
文章链接: https://www.dreamlove.top/192840d8.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

评论
\ No newline at end of file diff --git a/1ac67065.html b/1ac67065.html new file mode 100644 index 000000000..2657323c1 --- /dev/null +++ b/1ac67065.html @@ -0,0 +1,9 @@ +今日刷题-类型转换 | 梦洁小站-属于你我的小天地

今日刷题-类型转换

题目1

1
2
3
4
5
6
7
8
9
下面有关JavaScript内部对象的描述,正确的有?(多选)
A: History 对象包含用户(在浏览器窗口中)访问过的 URL

B: Location 对象包含有关当前 URL 的信息

C: Window 对象表示浏览器中打开的窗口

D: Navigator 对象包含有关浏览器的信息

  • 答案
    • 牛客网的答案为A,B,C,D
    • 我个人认为history不包含访问过的URL(当然源代码我没看过)
  • 解析
    • Navagator:提供有关浏览器的信息
    • Window:Window对象处于对象层次的最顶层,它提供了处理Navagator窗口的方法和属性
    • Location:提供了与当前打开的URL一起工作的方法和属性,是一个静态的对象
    • History:提供了与历史清单有关的信息
    • Document:包含与文档元素一起工作的对象,它将这些元素封装起来供编程人员使用

题目2

1
2
3
4
5
6
7
8
9
以下哪些表达式的结果为true(多选) (多选)
A: undefined == null

B: isNaN("100")

C: parseInt("1a") === 1

D: [] instanceof Array

  • 答案

    • A,C,D
  • 解析

    • A: 二个等于号( == ) ,类型不相同的时候,会发生隐式转换,隐式转换具体可以看这个博客

    • B: isNaN() 用来判断一个数是否是NaN

      1
      2
      3
      4
      5
      6
      7
      //isNaN(转换的值),当要'转换的值'为NaN的时候或者是'转换的值'转换后为NaN
      console.log(isNaN('e'));//ture
      console.log(isNaN('11'));//false 因为字符串可以被转化为数字,不能转换为NaN
      console.log(isNaN('a111'));//true 字符串转换不了数字
      console.log(isNaN('1a11'));//true 字符串转换不了数字
      console.log(isNaN('null'));//false 因为null被转化为数字为0,0不会为NaN
      console.log(isNaN(NaN));//true
    • C: parseInt(string,radix) 解析一个字符串并返回指定基数的十进制整数(未指定radix则返回10进制), radix 是2-36之间的整数,表示被解析字符串的基数。

      1
      2
      3
      4
      5
      注意:string字符串只会被解析从第一个字符开始直到不是数字的字符部分(第0个开始,直到遇到是非数字的就截断返回)
      console.log(parseInt('223'));//返回数字223
      console.log(parseInt('223a18'));//返回数字223
      console.log(parseInt('a223'));//返回NaN,因为第一个字符不是数字,所以直接返回NaN

    • D: 空数组也是数组的实例化对象

题目3

1
2
3
4
5
6
7
8
以下哪些操作会触发Reflow:(多选)
A: alert(obj.className)

B: alert(obj.offsetHeight)

C: obj.style.height = "100px"

D: obj.style.color = "red"
  • 答案

    • B,C
  • 解析

    • Reflow 回流 原文链接:http://www.cnblogs.com/Peng2014/p/4687218.html)

      • 简要:

        整个在浏览器的渲染过程中(页面初始化,用户行为改变界面样式,动画改变界面样式等)reflow(回流)和repaint(重绘) 会大大影响web性能,尤其是手机页面。因此我们在页面设计的时候要尽量减少reflow和repaint。

      • Reflow几乎是无法避免的。现在界面上流行的一些效果,比如树状目录的折叠、展开(实质上是元素的显 示与隐藏)等,都将引起浏览器的 reflow。鼠标滑过、点击……只要这些行为引起了页面上某些元素的占位面积、定位方式、边距等属性的变化,都会引起它内部、周围甚至整个页面的重新渲染。通常我们都无法预估浏览器到底会 reflow 哪一部分的代码,它们都彼此相互影响着。

      • 下面情况会导致reflow发生(是可能会!)

        1. 改变窗口大小
        2. 改变文字大小
        3. 内容的改变(比如用户在输入框中输入文字)
        4. 操作伪类 如:hover
        5. 操作class属性(会引发样式的改变情况下)
        6. js操作DOM
        7. 计算offsetWidth和offsetHeight
        8. 设置style属性
    • Repaint 重绘

      • 如果只是改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性,将只会引起浏览器 repaint(重绘)。repaint 的速度明显快于 reflow
    • 回流重绘说简单点

      • 回流,当页面的元素发生大小改变的时候的时候,会发生回流
        • 比如说里一个div里面字体大小发生改变,或者是这个div大小发生改变都会引起回流
        • 再通俗点就是,你变这么胖,我要改变下布局了,这就是回流!
      • 重绘
        • 只改变某个dom元素的颜色或者文字颜色,则会进行重绘
        • 再通俗点就是,我皮肤晒黑了,没有变胖,不需要改变布局,只需要重新绘制下就可以!

题目4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
请问以下JS代码的输出是?
function father() {
this.num = 935;
this.work = ['read', 'write', 'listen'];
}
function son() {}
son.prototype = new father();
let son1 = new son();
let son2 = new son();
son1.num = 117;
son1.work.pop();
console.log(son2.num);
console.log(son2.work);

A: 117、['read', 'write']

B: 935、['read', 'write','listen']

C: 935、['read', 'write']

D: 117、['read', 'write','listen']

  • 答案

    • C
  • 解析

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    function father() {
    this.num = 935;
    this.work = ['read', 'write', 'listen'];
    }
    function son() {}
    son.prototype = new father();
    // son.prototype = { num:935, work:['read','write','listen'] }

    let son1 = new son(); // son1 = { }
    let son2 = new son(); // son2 = { }

    son1.num = 117; // son1 = { num: 117 }

    son1.work.pop();
    //son1自己没有work,去原型里找到work,并删除work里的最后一项,
    //此时son.prototype = { num:935, work:['read','write'] }

    console.log(son2.num);// son2自己没有num,去原型里找,有num:935

    console.log(son2.work);
    //son1和son2原型是同一个,所以此时原型里的work是['read', 'write']

题目5

1
2
3
4
5
6
7
8
9
10
11
12
上面这段代码运行后得到的结果分别是什么?
console.log(([])?true:false);
console.log(([]==false?true:false));
console.log(({}==false)?true:false)
A: false true true

B: true true true

C: true false true

D: true true false

  • 答案

    • D
  • 解析

    • 布尔类型里只有这几参数个返回false,其它都为true

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      Boolean(undefined) // false

      Boolean(null) // false

      Boolean(0) // false

      Boolean(NaN) // false

      Boolean('') // false

      //其他转换均为true
      Boolean([]) //true
    • 需要知道: 布尔类型与其它任何类型进行比较,布尔类型和其他类型均会转换为number类型进行比较

      • Number([]) => 为0
      • Number(true) => 为1 Number(false) => 为0
      • Number({}) => 为NaN (Number转换类型的参数如果为对象返回的就是NaN)
    • //相当于 true?true:false
      +console.log(([])?true:false);//返回true
      +
      +//相当于 0 == 0 ? true : false
      +console.log(([]==false?true:false));//返回true
      +
      +//相当于 NaN == 0 ? true : false
      +console.log(({}==false)?true:false);//返回false
      +
文章作者: 梦洁
文章链接: https://www.dreamlove.top/1ac67065.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

评论
\ No newline at end of file diff --git a/1e714f22.html b/1e714f22.html new file mode 100644 index 000000000..8539c5ba3 --- /dev/null +++ b/1e714f22.html @@ -0,0 +1 @@ +图标库网站-汇集优秀的图标,插画等 | 梦洁小站-属于你我的小天地

图标库网站-汇集优秀的图标,插画等

当你想打开阿里的iconfont想找点图标时,却看到这句话:

💣iconfont系统升级维护中,给您带来不便,敬请谅解!

你肯定:

特喵的!作为一个互联网熟知的图标库,居然还整这?


🌟不过不用担心!我带着16个精品icon库来救急啦~

昨天中午已经发在DY,小伙伴反应都很不错


🔺illust(个人添加,个人推荐,我喜欢里面的插画~)

不过貌似需要科学上网~

链接🔗:https://zh-tw.ac-illust.com/

🔺IconPark
字节跳动商业化CUX设计团队精心打造的致力于图标应用规范化、统一化,提供丰富多样的高质量图标内容

链接🔗:https://iconpark.oceanengine.com/official


🔺Feather
简单漂亮的开源图标

链接🔗:https://feathericons.com/


🔺3DICONS

制作精美的开源 3D 图标100% 免费用于商业和个人用途

链接🔗:https://3dicons.co/


🔺**Heroicons
**

精美的手工 SVG 图标-230个

链接🔗:https://heroicons.com/


🔺**Tabler Icons
**

1400+icon

链接🔗:https://tablericons.com/


🔺**Iconoir
**

1000+icon

链接🔗:https://iconoir.com/


🔺**Halloween
**

🎃 万圣节3D图标

链接🔗:https://halloween.wannathis.one/#do


🔺**Undesign
**

为制造商、开发人员和设计师收集免费的设计工具和资源

链接🔗:https://undesign.learn.uno/


🔺イラストセンター

日本免费商用插图网站,风格萌

链接🔗:https://illustcenter.com/


🔺**Game-Icons
**

免费游戏图标

链接🔗:https://game-icons.net/


🔺**Orioniconliblrary
**

适用于的 Web、iOS、Android 和设计项目的 8613 专业和免费 SVG 图标

链接🔗:https://orioniconlibrary.com/all-icons


🔺**Iconhub
**

可调节类型大小粗细

链接🔗:https://iconhub.io/


🔺**css.gg
**

免费开源

链接🔗:https://css.gg/app


🔺**Ionicons
**

高级设计的图标,用于 Web、iOS、Android 和桌面应用程序。支持 SVG 和网络字体

链接🔗:https://ionic.io/ionicons


🔺**Basicons
**

产品设计和开发的基本图标

链接🔗:https://basicons.xyz/

文章作者: 梦洁
文章链接: https://www.dreamlove.top/1e714f22.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

评论
\ No newline at end of file diff --git a/1f4ec17a.html b/1f4ec17a.html new file mode 100644 index 000000000..cb8a49b7f --- /dev/null +++ b/1f4ec17a.html @@ -0,0 +1 @@ +Github action的学习 | 梦洁小站-属于你我的小天地

Github action的学习

前置知识

第一个action

  • action都建立在.github/workflows文件夹下
  • 可以从这里建立

  • 也可以从顶部Actions标签进入

  • 第一个action内容如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
name: 第一个Action 

on: workflow_dispatch #允许您从 "操作 "选项卡手动运行此工作流

jobs:
abcdef: #自定义名称
runs-on: ubuntu-latest #运行环境

steps: #步骤代表作为任务一部分执行的任务序列(可顺序也可并行)
- name: 第一个action运行任务单行
run: echo hello #单行

- name: 多行输出
run: |
echo hello1
echo hello2

  • 运行

第二个action-运行在window-server上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
name: 第一个Action 

on: workflow_dispatch #允许您从 "操作 "选项卡手动运行此工作流

jobs:
#第一个任务
abcdef: #自定义名称
runs-on: ubuntu-latest #运行环境

steps: #步骤代表作为任务一部分执行的任务序列(可顺序也可并行)
- name: 第一个action运行任务单行
run: echo hello #单行

- name: 多行输出
run: |
echo hello1
echo hello2
# 第二个任务
second:
runs-on: windows-latest
steps:
- name: 第二个任务(并行)
run: |
echo "hello github actinos"
Get-Date

(todo)github action里的jobs的并行和串行

  • 并行的时候,第一个执行失败,第二个也依旧会执行(也就是执行都是独立的)

小练习-1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
name: pytest
on: workflow_dispatch
jobs:
pytest:
runs-on: ubuntu-latest #规定工作环境
steps: #设置步骤
- name: 克隆仓库
run: | # 默认安装了git
git clone https://github.com/superBiuBiuMan/github-action-demo1.git
cd ./github-action-demo1
- name: 安装依赖
run: pip install pytest
- name: 执行测试
run: pytest

github 里面的actions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
name: pytest
on: workflow_dispatch
jobs:
pytest:
runs-on: ubuntu-latest #规定工作环境
steps: #设置步骤
- name: 克隆仓库(使用checkout actions)
uses: actions/checkout@v4.1.1
with: #如果克隆的是运行项目下的仓库,可以省略其实
repository: superBiuBiuMan/github-action-demo1
- name: 安装依赖
run: pip install pytest
- name: 执行测试
run: pytest
  • 运行结果

(todo)job

  • job既可以是并行的,也可以是顺序的

上下文

  • 也就是一些写好的变量供我们使用去读取值
上下文名称类型说明
githubobject工作流程运行的相关信息。 有关更多信息,请参阅 github 上下文
envobject包含工作流、作业或步骤中设置的变量。 有关更多信息,请参阅 env 上下文
varsobject包含存储库、组织或环境级别设置的变量。 有关更多信息,请参阅 vars 上下文
jobobject有关当前运行的作业的信息。 有关更多信息,请参阅 job 上下文
jobsobject仅适用于可重用工作流,包含可重用工作流中的作业输出。 有关更多信息,请参阅 jobs 上下文
stepsobject有关当前作业中已运行的步骤的信息。 有关更多信息,请参阅 steps 上下文
runnerobject有关运行当前作业的运行器的信息。 有关更多信息,请参阅 runner 上下文
secretsobject包含可用于工作流运行的机密的名称和值。 有关更多信息,请参阅 secrets 上下文
strategyobject有关当前作业的矩阵执行策略的信息。 有关更多信息,请参阅 strategy 上下文
matrixobject包含在工作流中定义的应用于当前作业的矩阵属性。 有关更多信息,请参阅 matrix 上下文
needsobject包含定义为当前作业依赖项的所有作业的输出。 有关更多信息,请参阅 needs 上下文
inputsobject包含可重用或手动触发的工作流的输入。 有关更多信息,请参阅 inputs 上下文

(todo)github action的变量

帮助文档

action市场

用的比较多的actions

  • 缓存(免费有时间限制嘛)
1
2
- name: Cache node modules
uses: actions/cache@v1
文章作者: 梦洁
文章链接: https://www.dreamlove.top/1f4ec17a.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

评论
\ No newline at end of file diff --git a/212685ca.html b/212685ca.html new file mode 100644 index 000000000..5e1b3fd59 --- /dev/null +++ b/212685ca.html @@ -0,0 +1 @@ +vue3获取ref实例结合ts的InstanceType | 梦洁小站-属于你我的小天地

vue3获取ref实例结合ts的InstanceType

前言

  • 有时候我们模板引用,但是在使用的时候,ts提示却不行,没有提示组件通过defineExpose暴露的方法名称,虽然这不是很影响,但是可以解决还是可以解决下~
1
2
3
4
5
6
7
8
9
10
11
<!-- MyModal.vue -->
<script setup lang="ts">
import { ref } from 'vue'

const sayHello = () => (console.log('我会说hello'))

defineExpose({
sayHello
})
</script>

  • 然后我们在父级使用,输入完成MyModalRef.value会发现没有sayHello这个函数提示,所以这个时候我们就需要使用InstanceType 工具类型来获取其实例类型
1
2
3
4
5
6
7
8
9
10
<!-- App.vue -->
<script setup lang="ts">
import MyModal from './MyModal.vue'
const MyModalRef = ref()

const handleOperation = () => {
MyModalRef.value.sayHello
}
</script>

使用InstanceType 工具类型获取其实例类型:

1
2
3
4
5
6
7
8
9
10
11
<!-- MyModal.vue -->
<script setup lang="ts">
import { ref } from 'vue'

const sayHello = () => (console.log('我会说hello'))

defineExpose({
open
})
</script>

父级使用

1
2
3
4
5
6
7
8
9
10
<!-- App.vue -->
<script setup lang="ts">
import MyModal from './MyModal.vue'

const MyModalRef = ref<InstanceType<typeof MyModal> | null>(null)

const handleOperation = () => {
MyModalRef.value?.sayHello()
}
</script>

后言

  • 貌似依旧没有提示使用InstanceType在提示的时候,然后输入错误内容也没有在编译前进行报错….,不过vue官方这样子说了,那就听他的吧(其实我一般不用,不过也学到了)
  • @vue官方API为组件模板引用标注类型
文章作者: 梦洁
文章链接: https://www.dreamlove.top/212685ca.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

评论
\ No newline at end of file diff --git a/2192bb69.html b/2192bb69.html new file mode 100644 index 000000000..ab6539f58 --- /dev/null +++ b/2192bb69.html @@ -0,0 +1 @@ +vercel和netlify部署代码并解决接口代理转发的问题(和Nginx功能一样) | 梦洁小站-属于你我的小天地

vercel和netlify部署代码并解决接口代理转发的问题(和Nginx功能一样)

前言

  • 部署过程就不说了,部署完成后是这样子的

  • 然后访问链接,无法访问

解决

  • 依次点击 Settings–>Domains,在输入框中输入你的域名并点击 Add 按钮。

  • 以此域名为例子demo.gshopfront.dreamlove.top为例,点击添加

  • 我们前往域名管理系统,记录下绿色的值以腾讯云的为例

  • 上图中的Name对应的是主机记录,Value对应的是记录值,记录类型选择CNAME

  • 验证成功,vercel自动生成ssl证书当中

  • 访问成功

vercel解决接口代理问题

1
npm i -D http-proxy-middleware
  • 目录建立vercel.json,注释记得删除
1
2
3
4
5
6
7
8
9
//注释记得删除
{
"rewrites": [
{
"source": "/api/(.*)", //要匹配的路径前缀(我们本地访问的路径),结合自己的前缀设置
"destination": "/api/proxy" //需要转发给哪一个目录下的配置
}
]
}
  • 建立api/proxy.js文件
    • 注意: 由于这里的接口不需要pathRewrite,所以就删除了pathRewrite配置
      • 效果,当我们访问搭建好的目录https://demo.gshopfront.dreamlove.top/api/user的时候,就会被转发给http://gmall-h5-api.atguigu.cn/api/user
    • 如果需要去除掉api这个前缀,就把pathRewrite打开,
      • 效果:当我们访问搭建好的目录https://demo.gshopfront.dreamlove.top/api/user的时候,就会被转发给http://gmall-h5-api.atguigu.cn/user,(去除掉了api)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// api/proxy.js
// 该服务为 vercel serve跨域处理
const { createProxyMiddleware } = require('http-proxy-middleware')

module.exports = (req, res) => {
let target = ''
// 代理目标地址
// 这里使用 backend 主要用于区分 vercel serverless 的 api 路径
// target 替换为你跨域请求的服务器 如: http://gmall-h5-api.atguigu.cn
if (req.url.startsWith('/api')) {
target = 'http://gmall-h5-api.atguigu.cn'
}
// 创建代理对象并转发请求
createProxyMiddleware({
target,
changeOrigin: true,
pathRewrite: {
// 通过路径重写,去除请求路径中的 `/api`
// 如果开启了,那么 /api/user/login 将被转发到 http://gmall-h5-api.atguigu.cn/user/login
//'^/api/': '/',
},
})(req, res)
}
  • 目录结构

  • 编辑好后推送,然后等待重新编译
  • 之后会出现此下拉栏目

  • 如果没有出现Functions栏目,就点击这里

  • 这样子也可以出现Function

  • 选择我们的api/proxy

  • 然后就可以正常啦

  • 如果部署遇到这种问题error @achrinza/node-ipc@9.2.2: The engine "node" is incompatible with this module. Expected version "8 || 10 || 12 || 14 || 16 || 17". Got "18.13.0"

  • 发现vercel设置node版本后重新部署也不行,不知道为什么,…

netlify解决接口代理问题

  • 建立netlify.toml文件
1
2
3
4
[[redirects]]
from = "/prod-api/*"
to = "http://gmall-h5-api.atguigu.cn/:splat"
status = 200

参考文章

文章作者: 梦洁
文章链接: https://www.dreamlove.top/2192bb69.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

评论
\ No newline at end of file diff --git a/220ca648.html b/220ca648.html new file mode 100644 index 000000000..6fe674a84 --- /dev/null +++ b/220ca648.html @@ -0,0 +1 @@ +今日刷题-注意数组的一些方法 | 梦洁小站-属于你我的小天地

今日刷题-注意数组的一些方法

题目1

1
2
3
4
5
在下列Promise所提供的方法中,用来向成功或者失败的回调函数队列中添加回调函数的是( )
A: done
B: fail
C: always
D: then
  • 答案

    • D
  • 解析

    • A没有,B也不存在,C为一个扩展(不管有没有成功失败都会调用),D的回调有二个参数,第一个是成功回调,第二个是失败回调

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      <script>
      const promise1 = new Promise((resolve, reject) => {
      resolve("事情解决");
      });
      //promise对象.then(成功回调,失败回调);
      promise1.then((value) => {
      console.log("我是成功回调", value);
      }, (reason) => {
      console.log("我是失败回调", value);
      })
      </script>
    • 至于C选项,是一个扩展,可以安装npm ``installes6-promise-always –save

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      //always(data, error) data: resolve的数据。 error: reject的数据。
      require("es6-promise-always")

      axios.get("/").then(()=>{

      //处理逻辑

      }).always(()=>{

      console.log("请求结束")

      hideLoading();

      })
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      //Promise杂烩示例
      axios("www.baidu.com").then(function(data) {
      if(50>10) {
      return data.json();
      }
      throw new TypeError("出错了!");
      })
      .then(function(data) { console.log("我要处理返回的数据") })
      .catch(function(error) { console.log("失败原因",error); })
      .finally(function() { console.log("不管怎么样我都要执行") });

题目2

1
2
3
4
5
6
7
8
9
10
以下符合 ES6 写法的有
A: class Foo
{
constructor() {return Object.create(null);}
}
Foo()
B: var m=1;
export m;
C: export var firstName=’Michael’;
D: 在A模块中export{readFile}后,在B模块中import readFile from ‘A’可以获取到readFile
  • 答案

    • C
  • 解析

    • A: Fun() 把 class 当成方法来用? var fun = new Func() 这样用就对了

    • B :暴露方式不对

      1
      2
      3
      4
      5
      6
      1. 分别暴露 export let length="100";export function add(){console.log("干饭")}
      引入: export {length,add} from "./module.js";
      2. 统一暴露 export {暴露的数据键值对} 比如:var a = 10; export { a };
      引入: export {length,add} from "./module.js";
      3. 默认暴露 export default 暴露的数据,比如export default var a = 10;
      引入:import a from "./module.js";
    • C: 正确的,分别暴露

    • D: 缺少了 { } 符号,正确应该为import {readFile} from ‘A’

export文档 import文档

题目3

1
2
3
4
5
6
7
以下代码执行后,array 的结果是?
let array = [,1,,2,,3];
array = array.map((i) => ++i)
A: [,2,,3,,4]
B: [NaN,2,NaN,3,NaN,4]
C: [1,2,1,3,1,4]
D: [null,2,null,3,null,4]
  • 答案
    • A
  • 解析
    • orEach(), filter(), reduce(), every() 和some()都会跳过空位。
    • map()会跳过空位,但会保留这个值。
    • join()和toString()会将空位视为undefined,而undefined和null会被处理成空字符串。
    • ES6 中都会将空位当做undefined

题目4

1
2
3
4
5
以下哪些方法会返回一个数组?(多选)
A: Object.keys()
B: String.prototype.split()
C: Array.prototype.join()
D: Promise.all()
  • 答案

    • A,B
  • 解析

    • A: Object.keys()返回对象的key值所组成的数组(key要为可枚举)
    • B: String.prototype.split()分割字符串,返回分割后的数组
    • C: Array.prototype.join()方法将一个数组(或一个类数组对象)的所有元素连接成一个字符串并返回这个字符串。如果数组只有一个项目,那么将返回该项目而不使用分隔符。
    • D: Promise.all(iterable) 方法返回一个 Promise 实例,此实例在 iterable 参数内所有的 promise 都“完成(resolved)”或参数中不包含 promise 时回调完成(resolve);
      如果参数中 promise 有一个失败(rejected),此实例回调失败(reject),失败原因的是第一个失败 promise 的结果。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <script>
    var obj = {
    name:"李白",
    age:1000,
    };
    Object.defineProperty(obj,"sex",{
    value:"男",
    enumerable:false
    });
    console.log(obj);//{name: '李白', age: 1000, sex: '男'}
    console.log(Object.keys(obj));//['name', 'age']
    </script>
文章作者: 梦洁
文章链接: https://www.dreamlove.top/220ca648.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

评论
\ No newline at end of file diff --git a/23924cfb.html b/23924cfb.html new file mode 100644 index 000000000..e78b1c54a --- /dev/null +++ b/23924cfb.html @@ -0,0 +1 @@ +ProcessOn的官网(less+jquery实现三端) | 梦洁小站-属于你我的小天地

ProcessOn的官网(less+jquery实现三端)

ProcessOn的官网(less+jquery实现三端)

  • 很基础的一个网站,没有什么特别的技术(懒加载也没有)
  • 手机端,苹果端,PC端都通用,具体可以试试
  • 不过有些小bug,那个图片滚动的,可能有些bug
  • 在线演示地址

展示

PC端

手机端(浏览器测试)

平板端(浏览器测试)

头部渐变效果(“免费扩容”这几个字)

滚动列表效果

底部出现效果

一些改变时候的过渡

下载地址

ProcessOn官网前端页面

文章作者: 梦洁
文章链接: https://www.dreamlove.top/23924cfb.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

评论
\ No newline at end of file diff --git a/25924f9f.html b/25924f9f.html new file mode 100644 index 000000000..0d332c879 --- /dev/null +++ b/25924f9f.html @@ -0,0 +1 @@ +tdesign的白天黑夜模式实现原理 | 梦洁小站-属于你我的小天地

tdesign的白天黑夜模式实现原理

  • 以tdesign为例

  • 我们看下源码

    • 可以看到,tdesign是借助于:root选择器,并结合属性选择器来设置的,当html标签存在一个名叫theme-mode属性的时候,如果值为dark就使用白天的主题,否则就使用黑天的主题

  • 我们切换下颜色,更改html当中的属性,可以看到下图的区别
1
2
3
4
5
6
7
8
9
10
11
12
13
<!DOCTYPE html>

<!-- 黑夜模式,设置 theme-mode="dark" -->
<html lang="en" theme-mode="dark">
...
</html>

<!-- 白天模式 设置 theme-mode="light"-->
<html lang="en" theme-mode="light">
...
</html>


文章作者: 梦洁
文章链接: https://www.dreamlove.top/25924f9f.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

评论
\ No newline at end of file diff --git a/2681a891.html b/2681a891.html new file mode 100644 index 000000000..a6a51b2a8 --- /dev/null +++ b/2681a891.html @@ -0,0 +1 @@ +vue3.0的学习 | 梦洁小站-属于你我的小天地

vue3.0的学习

一.创建vue3.0工程

1.使用vue-cli创建

1
2
3
4
5
6
7
8
9
## 查看@vue/cli版本,确保@vue/cli版本在4.5.0以上
vue --version
## 安装或者升级你的@vue/cli
npm install -g @vue/cli
## 创建
vue create vue_test
## 启动
cd vue_test
npm run serve

2.使用 vite 创建

官方文档:https://v3.cn.vuejs.org/guide/installation.html#vite

vite官网:https://vitejs.cn

  • 什么是vite?—— 新一代前端构建工具。
  • 优势如下:
    • 开发环境中,无需打包操作,可快速的冷启动。
    • 轻量快速的热重载(HMR)。
    • 真正的按需编译,不再等待整个应用编译完成。
  • 创建
1
2
3
4
5
6
7
8
## 创建工程
npm init vite-app <project-name>
## 进入工程目录
cd <project-name>
## 安装依赖
npm install
## 运行
npm run dev
  • 传统构建 与 vite构建对比图

传统的构建

vite的构建

一(extra).分析vue2.x和vue3.x结构

  • 主要区别

    • 1.引入的不在是vue2.x时候的vue构造函数了,而是引入的是一个名字叫createApp的工厂函数,并且比原来的构造函数更轻(我理解的是大小更小,加载更快)

    • 2.在vue3.x结构当中,template部分的内容可以不被一个统一的容器所包裹了

      左vue2.x 右vue3.x

vue2.x的 main.js 初始时的代码

1
2
3
4
5
6
import Vue from "vue";
import App from "./App.vue";

new Vue({
render:h=>h(App),
}).$mount("#app");

vue3.x的 main.js 初始化时的代码

1
2
3
import {createApp} from "vue";
import App from "./App.vue";
createApp(App).mount("#app");

二.常用的Composition API(组合API)

1.setup

setup是vue3.0新增加的一个配置项,为一个函数,里面可以写函数代码,注意区分下methods当中的写法

  • methods当中书写一个函数的写法
    • 这里是通过简写的形式书写的,实际上的完整的写法应该是sayWelcom: ()=>{ ... }
1
2
3
4
5
6
7
methods:{
...
sayWelcom(){
alert("欢迎大家来到唐朝")
},
...
},
  • setup当中书写一个函数的写法
    • 可以看到,是直接书写一个函数
1
2
3
4
5
6
7
8
9
10
11
12
13
setup() {
...
let name = '李白';
function sayHello(){
//注意作用域的问题
console.log(name);
alert("大家好,我叫李白");
}
...
return {
sayHello,
}
},

组件所用到的数据,方法,都可以写在setup当中,当然,你也可以写在vue2当中的data,methods等当中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
data(){
return {
money:"20K"
}
},
methods:{
//vue2当中的可以混合使用vue3的setup
sayOther(){
console.log(`你的名字叫${this.name},工资是${this.money}`);
}
},
// setup是新增的一个配置项,为一个函数,数据,方法,什么的都可以写在这里面
setup() {
//下面二行代码是不考虑数据响应式的写法
let name = "李白";
let age = 1000;


//需要返回一个对象,对象当中的属性和方法,均可以在模板当中直接使用
return {
name,
age,
}
},

setup的返回值

  • 若返回一个对象,则对象中的属性方法等 在模板中均可以直接使用。(重点关注!)
1
2
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
35
36
37
38
39
export default {
//与此同时,vue2当中的也可以使用
data(){
return {
money:"20K"
}
},
methods:{
sayWelcom(){
alert("欢迎大家来到唐朝")
},
//vue2当中的可以混合使用vue3的setup
sayOther(){
console.log(`你的名字叫${this.name},工资是${this.money}`);
}
},
// setup是新增的一个配置项,为一个函数,数据,方法,什么的都可以写在这里面
setup() {
//下面二行代码是不考虑数据响应式的写法
let name = "李白";
let age = 1000;

function sayHello(){
//注意作用域的问题
console.log(name);
alert("大家好,我叫李白");
}
function sayOtherSetup(){
console.log(`你的名字叫${name},工资是${this.money}`);//你的名字叫李白,工资是undefined
}
//需要返回一个对象,对象当中的属性和方法,均可以在模板当中直接使用
return {
name,
age,
sayHello,
sayOtherSetup
}
},
};
  • 若返回一个渲染函数:则可以自定义渲染内容。(了解)

h就是渲染函数,在组件使用记得要引入 import {h} from "vue"

注意点

  • vue2.x的配置,比如data,methods,computed可以访问到setup中的属性,方法,但是,setup不能访问到vue2.x的data,methods,computed等,因为setup当中的this为undefined

    • 因为我们通过data,methods,computed访问都是this.xxx来进行范围数据或者方法的,但是在vue3当中,setup里是没有this的,所以就不能访问了
  • setup不能是一个async函数,因为返回值不再是return的对象, 而是promise, 模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense的配合

2.ref函数-处理基本数据类型

  • 首先肯定是引入

    1
    import {ref} from "vue";

ref函数作用:

  • 创建一个包含响应式数据的引用对象
  • js当中操作数据:xxx.value
  • 模板当中读取,不需要添加.value,直接<div>{{xxx}}</div>

ref函数语法:

  • const xxx = ref(initValue)

  • 查看ref函数创建完成后的数据对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import {ref} from "vue";
    export default {
    setup() {
    //ref作用:定义一个响应式函数,返回响应数据
    let name = ref("李白");
    console.log(name);
    return {
    name,
    };
    },
    };

    输出的内容

    输出查看ref函数生成的变量-传入基本数据类型

  • 对ref传入的基本数据类型进行响应式修改

    • 别漏掉了一层value!
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import {ref} from "vue";
    export default {
    setup() {
    //ref作用:定义一个响应式函数
    let name = ref("李白");
    //响应式修改
    name.value = "李黑";
    return {
    name,
    };
    },
    };

ref函数创建的对象,全称为 Reference implement 引用实行对象 ,这里简称为引用对象

3.ref函数-处理对象数据类型

  • 首先肯定也是引入

    1
    import {ref} from "vue"
  • ref函数参数传入一个对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import {ref} from "vue";
    export default {
    setup() {
    //ref作用:定义一个响应式函数
    let job = ref({
    salary:"30K",
    type:"前端工程师"
    })
    console.log(job);
    return {
    job,
    };
    },
    };

    输出查看变量job

    输出查看ref函数生成的变量-传入对象

  • 对ref传入的对象进行响应式修改

    • 注意不要少了一层value,使得指向真正保存数据的结构
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import {ref} from "vue";
    export default {
    setup() {
    //ref作用:定义一个响应式函数
    let job = ref({
    salary:"30K",
    type:"前端工程师"
    })
    //改变ref当中的对象-响应式修改
    job.value.salary = "100k";
    job.value.type = "高级前端工程师";

    return {
    job,
    };
    },
    };

4.ref函数总结

  • ref当中,可以接收的数据类型可以是:基本数据类型,也可以是对象类型
  • 基本数据类型的数据vue实现响应式的原理:依靠Object.defineProperty()getset完成
  • 对象/引用数据类型的数据vue实现响应式的原理: 求助了Vue3.0中的一个新函数—— reactive函数。

5.reactive函数

  • 首先是引入

    1
    import {reactive} from "vue"

reactive作用:

  • 定义一个对象数据类型的响应式数据,基本类型不要使用(基本类型的使用请使用ref函数)
    • 如果对基本数据类型使用了,会提示value cannot be made reactive: 我是基本数据类型,let test = reactive("我是基本数据类型");使用这代码就会提示这个

reactive语法:

  • const 代理对象 = reactive(源对象)源对象接收一个对象或者数组

注意点:

  • 通过reactive函数处理的源对象,不管是何种方式的添加,删除,数据都是响应式的(这一点和vue2不同)

    • reactive定义的数组或者对象,不能直接对整体进行赋值修改,否则定义的数据将失去响应性!!!!!
      • 因为通过reactive所创建的响应书数据返回的是Proxy对象,如果(保存这个地址的变量)对其整体赋值,那么就不是一个Proxy对象了,而是一个普通的对象了,所以就会失去响应式
  • 比方说vue2.x给对象添加一个数据要是响应式,必须要通过this.$set或者Vue.set来实现),vue3.0却不用

  • 比方说vue2.x对数组进行修改,添加,只能使用vue重写的那几个方法,但是vue3.x当中,通过reactive操作的,可以通过下标来操作数组,vue3.0操作过后数组依旧是响应式数据

  • reactive定义的响应式数据是“深层次的”,不管有多少层,都是默认全部处理为响应式

  • 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。

示例:

1
2
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
35
36
37
38
39
import { reactive, ref } from "vue";
export default {
name: "App",
components: {},
// setup是新增的一个配置项,为一个函数,数据,方法,什么的都可以写在这里面
setup() {
//ref对基本数据类型的处理
let name = ref("李白");
let age = ref(1000);
// let test = reactive("我是基本数据类型");
//通过reactive函数实现对象的响应式数据
let job = reactive({
type:"前端工程师",
salary:"10K",
address:"尚硅谷",
hobby:["吃饭","睡觉","打豆豆"]
})

function changeValue() {
//改变ref当中的基本数据类型
name.value = "李黑";
age.value = 2000;

//改变reactive的值
job.type = "高级工程师"
job.salary = "30K"
job.address = "苹果"
job.hobby[0] = "吃零食";//vue3是可以这样子做的
}

//需要返回一个对象,对象当中的属性和方法,均可以在模板当中直接使用
return {
name,
age,
changeValue,
job,
};
},
};

失去响应式示例

  • reactive定义的数组或者对象,不能直接对整体进行赋值修改,否则定义的数据将失去响应性!!!!!

    • 因为通过reactive所创建的响应书数据返回的是Proxy对象,如果(保存这个地址的变量)对其整体赋值,那么就不是一个Proxy对象了,而是一个普通的对象了,所以就会失去响应式
  • 如下示例,当经过一秒后,页面显示的内容并没有被改变!

1
2
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
<template>
<div>
{{ banner }}
</div>
</template>

<script>
import { ref } from "vue";
import { reactive } from "vue";
import { onMounted } from "vue";
export default {
name: "App",
setup() {
let banner = reactive({ age: 0 });
//banner执行的是`Proxy`对象
console.log("修改之前的banner", banner);
onMounted(() => {
setTimeout(() => {
banner = { age: 1000 };
console.log("修改完成");
//banner指向了一个普通的对象
console.log("修改之后的banner", banner);
}, 2000);
});
return {
banner,
};
},
};
</script>

<style lang="less"></style>

6.reactive对比ref

  • 从定义数据角度对比:
    • ref用来定义:基本类型数据
    • reactive用来定义:对象(或数组)类型数据
    • 备注:ref也可以用来定义对象(或数组)类型数据, 它内部会自动通过reactive转为代理对象
  • 从原理角度对比:
    • ref通过Object.defineProperty()getset来实现响应式(数据劫持)。
    • reactive通过使用Proxy来实现响应式(数据劫持), 并通过Reflect操作源对象内部的数据。
  • 从使用角度对比:
    • ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value
    • reactive定义的数据:操作数据与读取数据:均不需要.value

7.vue2响应式原理-演示简版

实现原理:

  • 对象类型:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)。

    1
    2
    3
    4
    Object.defineProperty(data, 'count', {
    get () {},
    set () {}
    })
  • 数组类型:通过重写更新数组的一系列方法来实现拦截。(对数组的变更方法进行了包裹)。

    1
    2
    3
    4
    5
    6
    7
    push()
    pop()
    shift()
    unshift()
    splice()
    sort()
    reverse()
  • vue2.x当中直接添加,非响应式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    export default {
    data() {
    return {
    Person: {
    name: "李白",
    age: 1000,
    hobby: ["吃饭", "睡觉", "打豆豆"],
    },
    };
    },
    methods: {
    addSex() {
    // 直接添加,添加后的数据非响应式
    this.Person.sex = "男";
    console.log(this.Person);
    },
    },
    };
  • 通过this.$set(添加的对象,新增加的属性名称/要修改的索引下标,新添加值/新值),为响应式

    1
    2
    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
    <script>
    export default {
    name: "App",
    components: {},
    data() {
    return {
    Person: {
    name: "李白",
    age: 1000,
    hobby: ["吃饭", "睡觉", "打豆豆"],
    },
    };
    },
    methods: {
    addSex() {
    // 直接添加,添加后的数据非响应式
    // this.Person.sex = "男";
    // console.log(this.Person);
    //通过this.$set添加 响应式
    this.$set(this.Person, "sex", "男");
    },
    changeHobby(){
    //通过this.$set添加/修改 响应式
    this.$set(this.Person.hobby,2,"打游戏");
    this.$set(this.Person.hobby,3,"吃饭饭");
    }
    },
    };
    </script>

vue2对响应式数据存在的问题

  • 新增属性,删除属性,界面不会更新(vue3.x已经解决)
  • 直接通过下标修改数组,界面不会自动更新(vue3.x已经解决)
  • 以上vue2.x的解决都是通过重写方法或者this.$set\vue.set

8.vue3响应式原理和vue2响应式原理-分析简版

vue2响应式代码示例演示

1
2
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
<script>
// vue2的响应式原理 - 当我们在增加/删除/修改/读取的时候可以捕捉到
// 缺点,添加无法变成响应式,修改可以,读取也可以
var Person1 = {
name: "李白",
age: 1000,
}
var p1 = {};
Object.defineProperty(p1, "name", {
//如果不设置这个,delete 删除属性是删除不掉的
configurable: true,
get() {
console.log("你读取了name");
return Person1.name;
},
set(newValue) {
console.log("你设置了name,并将值设置为", newValue);
Person1.name = newValue;
}
});
Object.defineProperty(p1, "age", {
//如果不设置这个,delete 删除属性是删除不掉的
configurable: true,
get() {
console.log("你读取了age");
return Person1.age;
},
set(newValue) {
console.log("你设置了age,并将值设置为", newValue);
Person1.age = newValue;
}
});
</script>

vue3响应式原理代码示例演示

vue3响应式原理主要是通过调用window上面的方法 - Proxy

  • Proxy语法格式
    • new Proxy(要监视的数据,配置项目)
    • 配置项目当中,异步传入一个对象,里面可以配置
      • get(targetObj,propName){ ... }
      • set(targetObj,propName){ ... }
      • deleteProxy(targetObj,propName){ ... }
    • 参数targetObj为被修改的对象
    • 参数propName为被修改的属性名称
  • 最基本的实现-无检测,但是可以实现基本的p2删除,修改,添加,删除反馈到Person2,但是无法捕捉到变化
1
2
3
4
5
6
7
8
9
//vue3响应式原理 - 调用window上面的方法Proxy
var Person2 = {
name:"李白",
age:1000,
}
//这样子实现了p2修改,Person2也会被修改
var p2 = new Proxy(Person2,{

});
  • 响应式的实现-添加检测操作,这样子捕捉到对谁修改了,可以进行一些操作(也就是我们经常需要的数据变了,视图也变化的操作)

注意这里除了set get 还有一个新的 deleteProperty

1
2
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//vue3响应式原理 - 调用window上面的方法Proxy
var Person2 = {
name:"李白",
age:1000,
}
//这样子实现了p2修改,Person2也会被修改
var p2 = new Proxy(Person2,{
/* 参数1为被操作的对象 参数2为被操作的属性 */
get(targetObj,propName){
console.log(`检测到${propName}属性被读取了`);
return targetObj[propName]
},
set(targetObj,propName,newValue){
console.log(`检测到${propName}属性将要被修改为${newValue}`);
targetObj[propName] = newValue;
},
//删除检测
deleteProperty(targetObj,propName){
console.log(`属性${propName}将要被删除`);
return delete targetObj[propName];
}
});

var Person = {
name:"李白",
age:1000,
};
var p = new Proxy(Person,{
get(targetObj,propName){
//输出 {name: '李白', age: 1000} 'name'
console.log(targetObj,propName);
},
})
//触发get
console.log(p.name);


//试试看数组
var hobby = ['吃饭', '睡觉', '打豆豆'];
var p3 = new Proxy(hobby, {
get(targetObj, propName) {
console.log(`检测到${propName}属性被读取了`);
return targetObj[propName]
},
set(targetObj, propName, newValue) {
console.log(`检测到${propName}属性将要被修改为${newValue}`);
targetObj[propName] = newValue;
},
//删除检测
deleteProperty(targetObj, propName) {
console.log(`属性${propName}将要被删除`);
return delete targetObj[propName];
}
})
  • vue3官方真正实现是通过proxy(代理) Reflect(反射) 来实现的
1
2
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
35
36
37
38
39
40
41
42
43
44
45
//vue3响应式原理 - 调用window上面的方法Proxy
var Person2 = {
name: "李白",
age: 1000,
}
//这样子实现了p2修改,Person2也会被修改
var p2 = new Proxy(Person2, {
/* 参数1为操作的对象 参数2为操作的属性 */
get(targetObj, propName) {
console.log(`检测到${propName}属性被读取了`);
// return targetObj[propName];
//实际上vue是通过window上面的Reflect方法进行返回属性的
//因为Reflect可以会返回获取成功还是失败
return Reflect.get(targetObj,propName);
},
set(targetObj, propName, newValue) {
console.log(`检测到${propName}属性将要被修改为${newValue}`);
// targetObj[propName] = newValue;
return Reflect.set(targetObj,propName,newValue);
},
//删除检测
deleteProperty(targetObj, propName) {
console.log(`属性${propName}将要被删除`);
// return delete targetObj[propName];
//为什么不用delete,delete被占用了,所以用deleteProperty
return Reflect.deleteProperty(targetObj,propName);
}
});
//试试看数组
var hobby = ['吃饭', '睡觉', '打豆豆'];
var p3 = new Proxy(hobby, {
get(targetObj, propName) {
console.log(`检测到${propName}属性被读取了`);
return targetObj[propName]
},
set(targetObj, propName, newValue) {
console.log(`检测到${propName}属性将要被修改为${newValue}`);
targetObj[propName] = newValue;
},
//删除检测
deleteProperty(targetObj, propName) {
console.log(`属性${propName}将要被删除`);
return delete targetObj[propName];
}
})
  • 有人读取的时候调用 get
  • 有人修改/添加的时候调用set
  • 有人删除的时候调用deleteProperty

9.setup注意的二个点

注意点1-setup优于所有的生命周期先执行

  • beforeCreate之前,setup就会被执行,但是此时的setup当中的this是undefined(所以这就是为什么vue3.x的setup当中不可以使用vue2.x配置对象当中data,computed,methods等,因为执行setup函数的时候,data,computed,methods等都还没有被执行,怎么可以获取到里面的数据)

注意点2-setup的参数

  • 参数1—props:值为对象—-包含,组件外部传递过来的属性,并且组件内部使用props配置对象进行接收了的值,就会出现在setup的props参数当中
1
2
//父组件
<Demo weight="100kg" height="180"></Demo>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//子组件
import { reactive } from "vue";
export default {
//声明接收了父组件传递过来的值
props:['weight'],
setup(props, context) {
//输出 {weight: '100kg'}
//由于height没有被props配置对象声明接收
//所以输出setup参数当中的props没有出现height
console.log(props);
let data = reactive({
Person: {
name: "李白",
sex: "男",
},
});
return {
data,
};
},
};
  • 参数2—context:上下文对象

    • attrts: 值为对象,包含着从外部组件传递过来的属性,但是组件内部并没有在使用props配置对象接收的属性(attrs作用相当于捡下配置对象props的漏网之鱼)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      //父组件
      <Demo weight="100kg" height="180"></Demo>

      //子组件
      import { reactive } from "vue";
      export default {
      //声明接收了父组件传递过来的值
      props:['weight'],
      setup(props, context) {
      //输出Proxy {height: '180', __vInternal: 1}
      console.log(context.attrs);
      let data = reactive({
      Person: {
      name: "李白",
      sex: "男",
      },
      });
      return {
      data,
      };
      },
      };
    • slots: 收到的插槽内容 相当于this.$slots

    • emit: 分发自定义事件的函数(存储着组件接收到的自定义事件),类似于this.$emit ,使用方法和this.$emit一样

      • 当然了,vue3多出来的一个配置项目叫emits ,负责接收传递过来的自定义事件,和props配置对象一样.(老师演示的时候必须要加这个emits配置对象去接收自定义事件,不然会有警告提示,我使用的时候是不用加也可以的)

      • context.emit 的使用

        父组件

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        <template>
        <Demo @sayHello="hello" weight="100kg" height="180"></Demo>
        </template>

        <script>
        import Demo from "@/components/Demo";
        export default {
        name: "App",
        components: { Demo },
        setup() {
        function hello(value) {
        alert("我会说" + value);
        }
        return {
        hello,
        };
        },
        };
        </script>

        子组件

        1
        2
        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
        35
        36
        37
        38
        <template>
        <div>
        <h1>我是Demo</h1>
        {{ data.Person.name }}
        {{ data.Person.age }}
        <slot name="one"></slot>
        <button @click="chufa">按钮触发自定义事件</button>
        </div>
        </template>

        <script>
        import { reactive } from "vue";
        export default {
        name: "",
        props:['weight'],
        emits:["sayHello"],//可加可不加
        setup(props, context) {
        let data = reactive({
        Person: {
        name: "李白",
        sex: "男",
        },
        });
        function chufa(){
        context.emit("sayHello","叫我将军大人");
        }
        return {
        data,
        chufa
        };
        },

        };
        </script>

        <style>
        </style>

为什么自定义事件需要通过emit来去使用呢?

  • 之前已经说过了,setup当中是没有this的,所以我们不可以像vue2时候通过this.$emit来触发自定义事件,我们就必须要通过setup当中的第二个参数context来去代替this

10.vue3的computed

回顾vue2时候的computed

1
2
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
35
36
37
38
39
<template>
<div>
<h1>我是Demo</h1>
姓:<input v-model="name" />
<br />
名:<input v-model="last_name" />
<h1>全称展示:</h1>
全称修改:<input v-model="fullName" />
</div>
</template>

<script>
export default {
name: "",
data() {
return {
name: "李",
last_name: "白",
};
},
computed: {
//简写
// fullName() {
// return this.name + "-" + this.last_name;
// },

//全写
fullName:{
get(){
return this.name + "-" + this.last_name;
},
set(newValue){
[this.name,this.last_name] = newValue.split("-");
}
}
},
};
</script>

vue3时候的computed计算属性

  • 可以传入一个回调函数,返回值作为计算后的值,也可以传入一个配置对象,设置getter 和 setter

  • 首先引入包

    1
    import {computed} from "vue";
  • 添加只读,不可修改 - 简写方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import { computed, reactive } from "vue";
export default {
setup() {
let person = reactive({
name:"李",
last_name:"白",
});
//只读,不可修改
person.fullName = computed(()=>{
return person.name + "-" + person.last_name;
});

return {
person
}
},
};
  • 可读可修改,和computed一个,添加配置对象,设置getset即可实现可读可修改 -完整写法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    import { computed, reactive } from "vue";
    export default {
    setup() {
    let person = reactive({
    name:"李",
    last_name:"白",
    });
    //可读可修改
    person.fullName = computed({
    get(){
    return person.name + "-" + person.last_name;
    },
    set(newValue){
    //解构赋值
    [person.name,person.last_name] = newValue.split("-");
    }
    })
    return {
    person
    }
    },
    };

11.vue3的watch

  • 首先导入包

    1
    import {watch} from "vue";
  • 语法格式:

    • watch(监视的数据,回调函数)
      • 回调函数有二个参数,一个为newValue,一个为oldValue
  • 两个小“坑”:

    • 监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)
    • 监视reactive定义的响应式数据中某个属性时:deep配置有效
1
2
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
//情况一:监视ref定义的响应式数据
watch(sum,(newValue,oldValue)=>{
console.log('sum变化了',newValue,oldValue)
},{immediate:true})

//情况二:监视多个ref定义的响应式数据 使用数组形式
watch([sum,msg],(newValue,oldValue)=>{
console.log('sum或msg变化了',newValue,oldValue)
})

/* 情况三:监视reactive定义的响应式数据
若watch监视的是reactive定义的响应式数据,则无法正确获得oldValue!!
若watch监视的是reactive定义的响应式数据,则强制开启了深度监视
*/
watch(person,(newValue,oldValue)=>{
console.log('person变化了',newValue,oldValue)
},{immediate:true,deep:false}) //此处的deep配置不再奏效

//情况四:监视reactive定义的响应式数据中的[某个属性] 注意形式
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{immediate:true,deep:true})

//情况五:监视reactive定义的响应式数据中的[某些属性] 注意形式
watch([()=>person.job,()=>person.name],(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{immediate:true,deep:true})

//特殊情况
watch(()=>person.job,(newValue,oldValue)=>{
console.log('person的job变化了',newValue,oldValue)
},{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效
  • 针对监视是否有value的值的分析 ref对象讨论(reactive对象不需要讨论)

    • 结论:要监视数据的变化,必须要是真正保存数据的结构才可以监视成功

      • 所以ref创建的对象/引用数据类型需要添加一层value才可以监视到变化,或者添加一下深度监视

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        let person = ref({
        name: "李白",
        age: 18,
        jobList: {
        j1: {
        salary: 13,
        },
        },
        });
        watch(person.value,(newValue,oldValue)=>{
        console.log("person被改变了");
        })
        // 或者
        watch(person,(newValue,oldValue)=>{
        console.log("person被改变了");
        },{deep:true})

    • 对于ref所创建的基本数据类型,第一个参数直接写入就可以 比如let number = ref(0);

      • 因为真正监视的是ref所创建的refImpl对象,监视里面的RefImpl对象当中value的值的变化

      • 如图 当我们修改值的时候,实际上修改的是value的值,所以监视的时候发现value变化了,就有反应

    • 对于ref当中的引用数据类型,需要添加value获取真正保存数据的结构proxy 或者添加一下深度监视

      • 因为对于引用数据类型,ref是交给proxy来创建的,

      • 所以书写代码的时候,第一个参数要么为 person.value 或者第三个参数配置对象添加deep:true

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        let person = ref({
        name: "李白",
        age: 18,
        jobList: {
        j1: {
        salary: 13,
        },
        },
        });
        watch(person.value,(newValue,oldValue)=>{
        console.log("person被改变了");
        })
        // 或者
        watch(person,(newValue,oldValue)=>{
        console.log("person被改变了");
        },{deep:true})

        ref创建后的person

12.vue3的watchEffect

  • 首先是导入

    1
    import {watchEffect} from "vue";
  • watchEffect和watch的区别

    • watchEffect: 不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视用到的属性
  • watchEffect有点像computed:

    • computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值
    • watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值
  • 示例

通过watchEffect来监视用到的值

1
2
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
35
36
37
38
39
40
41
<template>
<div>
<h1>数字{{ number }}</h1>
<button @click="number += 1">单击我数字+1</button>
<hr/>
<h1>薪资:{{person.jobList.j1.salary}}K</h1>
<button @click="person.jobList.j1.salary += 1">单击我薪资+1</button>
</div>
</template>

<script>
import { ref, watch, reactive ,watchEffect} from "vue";
export default {
setup() {
let number = ref(0);
let person = ref({
name: "李白",
age: 18,
jobList: {
j1: {
salary: 13,
},
},
});
watchEffect(()=>{
//用到了numbere和person当中的name
//那么就会自动监视这二个值,当发生变化的时候就执行这个回调
const numberName = number.value + person.name;
console.log("watchEffect回调执行了");
})
return {
number,
person,
};
},
};
</script>

<style lang="less" scoped>
</style>

13.vue3的生命周期钩子

vue2的生命周期 @vue2生命周期介绍官网

vue3的生命周期 @vue3生命周期介绍官网

  • setup永远在所有的生命周期之前

  • Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名

    • beforeDestroy =>变为了 beforeUnmount
    • destroyed =>变为了 unmounted
  • 代码– 配置对象当中的生命周期钩子

1
2
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
<script>
import { ref } from "vue";
export default {
//vue3的生命周期钩子-配置项 在配置对象方面,不同点就在于beforeDestroy =>变为了beforeUnmounte ,desctroyed =>变为了unmounted
beforeCreate(){
console.log("--- beforecreate ---");
},
created(){
console.log("--- created ---");
},
beforeMount(){
console.log("--- beforeMount ---");
},
mounted(){
console.log("--- mounted ---");
},
beforeUpdate(){
console.log("--- beforeUpdate ---");
},
updated(){
console.log("--- updated --- ");
},
beforeUnmount(){
console.log("--- beforeUnmount ---");
},
unmounted(){
console.log("--- unmounted ---");
},
};
</script>
  • 代码 — vue3的组合函数的生命周期
1
2
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<script>
import { ref } from "vue";
//组合函数生命周期钩子
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
} from "vue";
export default {
// vue3的生命周期钩子
name: "",
setup() {
console.log("--- setup ---");
let number = ref(0);
// 组合函数生命周期钩子
onBeforeMount(() => {
//生命周期回调函数
console.log("--- onBeofreMount ---");
});
onMounted(() => {
//生命周期回调函数
console.log("--- onMounted ---");
});
onBeforeUpdate(() => {
//生命周期回调函数
console.log("--- onBeforeUpdate ---");
});
onUpdated(() => {
//生命周期回调函数
console.log("--- onUpdated ---");
});
onBeforeUnmount(() => {
//生命周期回调函数
console.log("--- onBeofreUnmount ---");
});
onUnmounted(() => {
//生命周期回调函数
console.log("--- onUnmounted ---");
});

return {
number,
};
}
};
</script>
  • 组合函数形式的生命周期钩子和vue2.x配置对象生命周期钩子的对应关系

    • beforeCreate===>setup()

    • created=======>setup()

    • beforeMount ===>onBeforeMount

    • mounted=======>onMounted

    • beforeUpdate===>onBeforeUpdate

    • updated =======>onUpdated

    • beforeUnmount ==>onBeforeUnmount

    • unmounted =====>onUnmounted

  • 其他都是加了一个on就没有了,还有就是,如果配置对象生命周期函数和组合函数生命周期一起写了,永远是组合函数的先执行

14.自定义hooks

  • 什么是hook?—— 本质是一个函数,把setup函数中使用的Composition API进行了封装。

  • 类似于vue2.x中的mixin。

  • 自定义hook的优势: 复用代码, 让setup中的逻辑更清楚易懂。

比如说有一个需求,单击鼠标可以获取鼠标的位置的函数功能,我们用hook看看

hooks/usePoint.js

1
2
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
import { reactive, onMounted, onUnmounted } from "vue";
export default function () {
let point = reactive({
x: 0,
y: 0,
});

//获取单击坐标
function getPoint(event) {
point.x = event.pageX;
point.y = event.pageY;
}
//生命周期
onMounted(() => {
//添加鼠标单击回调
window.addEventListener("click", getPoint);

});

//卸载后移除回调
onUnmounted(() => {
//移除回调
window.removeEventListener("click", getPoint);
});
//必须,相当于ref或者reactive执行后返回一个数据
return point;
}

App.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<template>

<Demo v-if="isShow"></Demo>
<Test/>
<button @click="isShow = !isShow">切换显示</button>
</template>

<script>
import Demo from "@/components/Demo";
import Test from "@/components/Test"
import { ref } from "vue";
export default {
name: "App",
components: { Demo ,Test},
setup() {

let isShow = ref(true);
return {
isShow,
};
},
};
</script>

componets/Demo.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div>我是Demo</div>
<h1>坐标轴:x轴{{ point.x }} y轴:{{ point.y }}</h1>
</template>

<script>
import usePoint from "../hooks/usePoint"
export default {
name: "",
setup() {
//使用hooks,并接受返回的数据用于展示
let point = usePoint();

return {
point,
}
},
};
</script>

components/Test.vue

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<template>
<div>
<hr/>
<h1>我是test</h1>
<h1>坐标轴:x轴{{ point.x }} y轴:{{ point.y }}</h1>
</div>
</template>

<script>
import usePoint from "../hooks/usePoint"
export default {
name: '',
setup(){
//使用hooks,并接受返回的数据用于展示
let point = usePoint();
return {
point,
}
}
}
</script>

15.toRef和toRefs

toRef

语法格式
  • toRef :(要分割的对象,分割的属性)
    • 比如toRef(person,'name'); 意思为分割响应式数据person当中的name
用途点
  • 不想在template模板当中,写这么多重复的,比如下方代码,太多person这个单词了,我们不想这样子,就可以在setup return的时候做些处理
1
2
3
4
5
6
7
8
9
10
<template>
<div>
<h1>我是Demo</h1>
姓:<input v-model="person.name"/>
<br/>
名:<input v-model="person.last_name"/>
<h1>全称展示:{{person.fullName}}</h1>
全称修改:<input v-model="person.fullName"/>
</div>
</template>
  • 切记,普通的这样子处理是不可以实现响应式的,
1
2
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
<script>
import {reactive} from "vue";
export default {
name: '',
setup(){
let person = reactive({
name:"李白",
age:18,
job:{
job1:{
salary:10
}
}
});
return {
//如果我们直接更改,是达不到响应式的
//因为下方的代码等同于这样子,未经过reactive处理的了
// return {
// name:"李白",
// age:18
// }
name:person.name,
age:person.age,
}
}
}
</script>
  • 这样子才可以实现响应式分割
1
2
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
35
36
37
38
<template>
<div>我是Demo</div>
<h1>{{person}}</h1>
<h1>姓名:{{name}}</h1>
<h1>年龄:{{age}}</h1>
<h1>工资:{{salary}}K</h1>
<button @click="name+='!'">改变姓名</button>
<button @click="age++">改变年龄</button>
<button @click="salary++">改变工资</button>
</template>

<script>
import {reactive,toRef,ref,toRefs} from "vue"
export default {
name: "",
setup(){
let person = reactive({
name:"李白",
age:18,
job:{
job1:{
salary:10
}
}
})
return {
person,
name:toRef(person,'name'),
age:toRef(person,'age'),
salary:toRef(person.job.job1,'salary')
}
}
};
</script>

<style lang="less" scoped>
</style>

toRef示意图-可以理解,通过toRef创建的,数据始终指向源数据

toRefs

语法格式
  • toRefs(要拆分的响应式数据) ,返回分割后的响应式数据对象
示例
1
2
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
<script>
import {reactive,toRef,ref,toRefs} from "vue"
export default {
name: "",
setup(){
let person = reactive({
name:"李白",
age:18,
job:{
job1:{
salary:10
}
}
})
console.log(toRefs(person));
return {
person,
name:toRef(person,'name'),
age:toRef(person,'age'),
salary:toRef(person.job.job1,'salary')
}
//用的比较多的是下方这种
//就将person对象拆分为了name,age,job这三个响应式数据
return {
...toRefs(person),
}
}
};
</script>

toRefs(person)的结果

toRefs示意图 将这个person对象拆分为多部分,每一部分都是指向源数据的部分

三.其他composition API

  • 这二者什么时候使用?

    • 如果有一个对象数据,结构比较深, 但变化时只是外层属性变化 ===> shallowReactive

    • 如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===> shallowRef

1.shallowRef和shallowReactive

shallowRef

  • 只处理基本数据类型的响应式,不对对象的响应式进行处理(在普通的ref当中,如果遇到的是对象,那么会将对象交给reactive进行处理,但是使用了shallowRef后就不会)

  • 使用依旧需要导入包

    1
    import {shallowRef} from "vue";
  • 怎么使用和ref一样的用法

  • 如图,shallRef传入一个对象后输出,会发现原来怎么样,就是怎么样的

图,shallRef传入一个对象后输出,会发现原来怎么样,就是怎么样的

  • 而原来的ref函数如果遇到对象会交给reactive进行处理

而原来的ref函数如果遇到对象会交给reactive进行处理

shallowReactive

  • 只使得对象第一层为响应式数据,更深层次不会变为响应式数据

  • 使用,依旧先导入

    1
    import {shallowReactive} from "vue";
  • 然后使用方法和reactive是一样的

示例代码

person为shallowReactive创建的,button想操作里面的salary,结果是不行的

1
2
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
35
36
<template>
<div>我是Demo</div>
<h1>{{person}}</h1>
<h1>姓名:{{person.name}}</h1>
<h1>年龄:{{person.age}}</h1>
<h1>工资:{{person.job.job1.salary}}K</h1>
<button @click="person.name+='!'">改变姓名</button>
<button @click="person.age++">改变年龄</button>
<button @click="person.job.job1.salary++">改变工资</button>
</template>

<script>
import {shallowReactive} from "vue"
export default {
name: "",
setup(){
let person = shallowReactive({
name:"李白",
age:18,
job:{
job1:{
salary:10
}
}
})
//不管是ref reactive shallowReactive shallowRef
// 返回的都是响应式数据
// 只不过深层次的是否是响应式就看使用哪一个方法了
return {
person,
}
}
};
</script>


  • 不管是ref reactive shallowReactive shallowRef 返回的都是响应式数据,只不过深层次的是否是响应式就看使用哪一个方法了
    • 所以你会发现,直接替换person,x这二个数据,也会引发视图变化

2.readonly和shallowReadonly

  • readonly: 让一个响应式数据变为只读的(只读),通俗点就是全部都是只读数据
  • shallowReadonly:让一个响应式数据变为只读的(只读),通俗点就是只有第一层数据为只读的
  • 应用场景: 不希望数据被修改时。
  • 都是传递给一个响应数据,将这个响应式数据变为只读的,注意,非响应式数据是数据被修改了,视图没有发生变化,这个时候数据还是可以被修改的

readonly

  • 先导入

    1
    import {readonly} from "vue";
  • 传入一个响应式数据,返回一个全部为只读的数据

shallowReadonly

  • 先导入

    1
    import {shallowReadonly} from "vue";
  • 传入一个响应式数据,返回一个第一层为只读的数据

二者的示例代码

1
2
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
35
36
37
38
<template>
<div>我是Demo</div>
<h1>姓名:{{name}}</h1>
<h1>年龄:{{age}}</h1>
<h1>工资:{{job.job1.salary}}K</h1>
<h1>{{a}}</h1>
<button @click="name+='!'">改变姓名</button>
<button @click="age++">改变年龄</button>
<button @click="job.job1.salary++">改变工资</button>
<button @click="a++">改变a</button>
</template>

<script>
import {ref,reactive,toRefs,readonly, shallowReadonly} from "vue"
export default {
name: "",
setup(){
let person = reactive({
name:"李白",
age:18,
job:{
job1:{
salary:10
}
}
});
let a = ref(100);
a = readonly(a);
//person = readonly(person);//全部变为了只读
person = shallowReadonly(person);//第一层变为了只读,其他没有变
return {
...toRefs(person),
a
}
}
};
</script>

代码效果:除了改变工资外,其他的都不可以修改

3.toRaw和markRaw

toRaw

  • 作用:将一个由reactive生成的响应式对象转为普通对象
  • 使用场景:用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新。
  • 语法: var 转换成的普通对象 = toRaw(响应式对象)

  • 引入

    1
    import {toRaw} from "vue";

markRaw

  • 作用:标记一个对象,使其永远不会再成为响应式对象。

  • 要知道,在vue3的时候,对一个响应式数据添加新值,那么这个新值也会变为响应式数据的,并且是可以通过数组索引来操作响应式数据的(但是在vue2是不可以的)

  • 应用场景:

    1. 有些值不应被设置为响应式的,例如复杂的第三方类库等。
    2. 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。
  • 引入

    1
    import {markRaw} from "vue";

二者的示例代码

1
2
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<template>
<div>我是Demo</div>
<h1>姓名:{{ name }}</h1>
<h1>年龄:{{ age }}</h1>
<h1>工资:{{ job.job1.salary }}K</h1>
<button @click="name += '!'">改变姓名</button>
<button @click="age++">改变年龄</button>
<button @click="job.job1.salary++">改变工资</button>
<button @click="showRawPerson">显示原始的数据</button>
<h1>车位信息:{{ person.car }}</h1>
<button @click="addCar">添加车位</button>
<button @click="person.car.name+='!'">车子名称改变</button>
<button @click="person.car.price++">车子价格改变</button>
</template>

<script>
import { reactive, toRefs, toRaw, markRaw } from "vue";
export default {
name: "",
setup() {
let person = reactive({
name: "李白",
age: 18,
job: {
job1: {
salary: 10,
},
},
});
function showRawPerson() {
//调用toRaw将响应式数据转化为普通数据
let rawPerson = toRaw(person);
//看看是否会影响person--测试是不会的
rawPerson.age++;
console.log(rawPerson);
}

function addCar() {
//将要新添加数据
//但是我不想新添加的数据为想响应式的,就可以使用markRaw
//进行标记,标记为非响应式数据
person.car = markRaw({
name: "宝马",
price: 40,
});
}

return {
person,
...toRefs(person),
showRawPerson,
addCar,
};
},
};
</script>

4.customRef

  • 功能:自定义ref,并对其依赖项跟踪和更新触发(可以显示控制)

  • 要自定义ref,需要记住下面二个

    • 需要返回customRef函数创建的东东
    • customRef函数自动传入的回调函数参数分别为tracker(追踪器),trigger(触发器),并且这个函数需要返回一个配置对象(配置对象包含setget)
  • 引入

    1
    import {customRef} from "vue";
  • 案例: 实现防抖,输入内容0.5秒后才更新

1
2
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
<template>
<div>我是Demo</div>
<input type="text" v-model="searchKey" />
<h1>{{ searchKey }}</h1>
</template>

<script>
import { customRef } from "vue";
export default {
name: "",
//创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制。
setup() {
//这里就产生了闭包
let searchKey = myRef(0);

//自定义的ref -- 防抖
function myRef(value) {
// 通过customRef去实现自定义 -- 毛坯房来自己处理
// ref --- 精装房来自己处理
let timer; //定时器
//所以自定义ref ,都需要返回毛坯房
return customRef((tracker, trigger) => {
//毛坯房里面,需要返回一个对象
return {
get() {
console.log("你调用了get");
//告诉vue这个值是需要更新的
tracker();
return value; //返回值
},
set(newValue) {
console.log("调用了set");
//告诉Vue去更新界面
clearTimeout(timer);
//0.5秒后更新视图
timer = setTimeout(() => {
trigger();
value = newValue;
}, 500);
},
};
});
}

return {
searchKey,
};
},
};
</script>

5.provide和inject

  • 作用:实现祖与后代组件间通信

  • 套路:父组件有一个 provide 选项来提供数据,后代组件有一个 inject 选项来开始使用这些数据

  • 引入

    1
    import {provide,inject} from "vue";
  • 语法格式

    • provide(‘提供的名称’,数据)
    • let 接收到的数据 = inject(‘provide提供的名称’)
  • 具体写法

    • 祖组件当中

      1
      2
      3
      4
      5
      6
      7
      8
      import {provide} from "vue";
      setup(){
      ......
      //发送数据
      let car = reactive({name:'奔驰',price:'40万'})
      provide('car',car)
      ......
      }
    • 后代组件当中

      1
      2
      3
      4
      5
      6
      7
      8
      import {inject} from "vue";
      setup(props,context){
      ......
      //接收数据
      const car = inject('car')
      return {car}
      ......
      }

6.响应式数据判断-isRef,isReactive,isProxy,isReadonly

  • isRef:检查一个值是否是ref对象
  • isReactive:检查一个对象是否由 reactive所创建的响应式代理
  • isProxy:检查一个对象是否是由readonly或者reactive函数所创建的响应式/只读数据代理
    • readonly函数对reactive响应式数据设置为只读是不会将其转换为对象的,而是依旧是proxy
  • isReadonly:检查一个对象是否是由readonly函数所创建的只读代理

示例代码

  • 此示例也证明了ref创建引用数据类型的响应式数据的时候,是交给reactive来进行创建的
1
2
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
import {reactive,isRef,isReactive,isReadonly,isProxy} from "vue";
setup(){
//使用reactive创建响应式数据
let info = reactive({
people: "一家四口人",
money: 8888888,
});
console.log("isRef", isRef(info)); //false
console.log("isReactive", isReactive(info)); //true
console.log("isReadonly", isReadonly(info)); //false
console.log("isProxy", isProxy(info)); //true
console.log("分割----------------------------------");
//使用ref创建响应式数据 - 传入一个对象
let info1 = ref({
people: "一家四口人",
money: 8888888,
});
console.log(isRef(info1)); //true
console.log(isReactive(info1)); //false
console.log(isProxy(info1)); //false

console.log("分割----------------------------------");
console.log(isRef(info1.value)); //false
console.log(isReactive(info1.value)); //true
console.log(isProxy(info1.value)); //true

}

四.新的组件

1.Fragment

  • 在vue2的时候,我们知道,组件必须要有一个根标签
  • 在vue2中,组件可以没有根标签,vue会将多个标签包含在一个fragment虚拟元素中
  • 好处:减少标签层级,减少内存占用
  • 如图 在vue开发提示中,没有书写根标签就会有一个蓝色的fragment

2.Teleport

  • 什么是teleport组件,teleport组件可以将我们的组件html结构移动到指定的dom位置的技术
  • 语法:<teleport to="移动的位置"></teleport>
    • 移动的位置可以是html,body等,也可以是选择器
  • 如图,就是将此弹窗移动到html标签下
1
2
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
<template>
<button @click="isShow = true">开启弹窗</button>
<!-- to当中填写要飞跃到的dom名称,比如html,body,设置可以传入一个选择器 -->
<teleport to="html">
<!-- 遮罩层 -->
<div class="mask" v-if="isShow">
<div class="content">
<h1>我是内容</h1>
<h1>我是内容</h1>
<h1>我是内容</h1>
<h1>我是内容</h1>
<h1>我是内容</h1>
<button @click="isShow = false">关闭弹窗</button>
</div>
</div>
</teleport>

</template>

<script>
import { ref } from "vue";
export default {
name: "",
setup() {
let isShow = ref(false);

return {
isShow,
};
},
};
</script>

<style scoped>
.mask {
background-color: rgba(0, 0, 0, 0.5);
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
.content {
border: 2px solid hotpink;
width: 300px;
background-color: aquamarine;
text-align: center;
position: absolute;
/* 参照于父元素移动 */
left: 50%;
top:50%;
/* 参照于自身移动 */
transform: translate(-50%,-50%);
}
</style>

图,在没有显示对话框前,通过teleport进行了占位,移动到了html结构下

3.suspense/异步加载

前置了解

  • 在普通方式的引入下(import xxx组件 from "xxxx"),通过这种方式,如果这个组件有一个组件没有加载完成,那么其他组件都不会显示

    如图 有多层的结构下,如果红色圈圈当中的组件没有加载完成,那么其他组件都不会显示的

有多层的结构下,如果红色圈圈当中的组件没有加载完成,那么其他组件都不会显示的

如图,我是APP我是儿子这二个同时被加载出来,不管哪一个组件实现加载完成,后一个组件必须要等待前一个组件加载完成才会显示

  • 所以vue3.x就有了异步加载(其实vue2.x的时候vue-router就有异步加载了)

注意要点

  • 异步加载的组件可以用suspense,也可以不用
  • 如果组件中setup的返回值是一个Promise对象,该组件必须要用suspense
  • 如果组件中setup出现了async,await,该组件必须要用suspense

单纯defineAsyncComponent函数使用(未使用suspense的异步加载)

  • 如果只需要异步加载组件,而不需要其他的显示效果,就defineAsyncComponent即可实现

示例代码

这样子加载Son组件的时候,就是异步的了,就不会因为Son组件没有加载完成而导致整个网页都没有显示

Son组件代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div class="son">
<h1>我是儿子</h1>
</div>
</template>

<script>
export default {
name: "Son",
};
</script>

<style>
.son{
background-color: green;
padding: 30px;
}
</style>

App组件代码

1
2
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
<template>
<div class="app">
<h1>我是APP</h1>
<Son></Son>
</div>
</template>

<script>
//原来方式引入
// import Son from "@/components/Son";

// 异步组件引入
import {defineAsyncComponent} from "vue";
const Son = defineAsyncComponent(()=>import("@/components/Son"))
export default {
name: "App",
components: {
Son,
},
};
</script>

<style>
.app {
background-color: hotpink;
padding: 40px;
}
</style>

动图效果,可以看到,先我是APP进行加载完成,然后Son加载完成后显示,不是一起显示的

defineAsyncComponent函数和suspense组件组合使用(异步加载)

等待异步组件时渲染一些额外内容,让应用有更好的用户体验,suspense组件和异步引入defineAsyncComponent函数组合显示

目的:
  • 为了让组件加载显示完成的时候不那么突兀,我们可以使用suspense组件
  • 可以使用asyncawait
使用:
  • 引入

    1
    2
    import {defineAsyncComponent} from "vue";
    //当然,suspense是组件,不需要引入
  • suspense组件提供二个插槽,一个是组件加载完成后显示的,插槽名称为default,一个是加载时候的内容,插槽名称为fallback

  • 注意:

    • 如果组件中setup的返回值是一个Promise对象,该组件必须要用suspense包裹
    • 如果组件中setup出现了async,await,该组件必须要用suspense包裹
示例代码

Son组件

1
2
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
35
36
37
38
39
<template>
<div class="son">
<h1>我是儿子 {{name}}</h1>

</div>
</template>

<script>
export default {
name: "Son",
// 这种写法效果和下面的一样
/* async setup() {
let name = "李白";
let p = new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ name });
}, 3000);
});
return await p
}, */

setup() {
let name = "李白";
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ name });
}, 3000);
});
},
};
</script>

<style>
.son {
background-color: green;
padding: 30px;
}
</style>

App组件

1
2
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
35
36
37
<template>
<div class="app">
<h1>我是APP</h1>
<!-- 为了不让显示这么突兀,我们就结合suspense组件(悬念,悬疑) -->
<suspense>
<!-- 提供二个插槽,一个加载完成后显示的,插槽名称为default,一个加载中时候的内容,插槽名称为fallback -->
<template v-slot:default>
<Son></Son>
</template>

<template v-slot:fallback> 加载中..... </template>
</suspense>
</div>
</template>

<script>
//原来方式引入
//import Son from "@/components/Son";

// 异步组件引入
import { defineAsyncComponent } from "vue";
const Son = defineAsyncComponent(() => import("@/components/Son.vue"));
export default {
name: "App",
components: {
Son,
},
};
</script>

<style>
.app {
background-color: hotpink;
padding: 40px;
}
</style>

五.其他

  • 我现在才知道,使用具名插槽还可以简写

    • 跟 v-on(简写为@xxx=”xxxx”) 和 v-bind(简写为:xxxx=”xxxx”) 一样,v-slot 也有缩写。
    • v-slot: 替换为字符#
    • 例如 v-slot:header 可以被重写为 #header
    • 所以上面使用suspense组件插槽还可以这样子写
    1
    2
    3
    4
    5
    6
    7
    8
    <suspense>
    <!-- 提供二个插槽,一个加载完成后显示的,插槽名称为default,一个加载中时候的内容,插槽名称为fallback -->
    <template #default>
    <Son></Son>
    </template>

    <template #fallback> 加载中..... </template>
    </suspense>
  • data选项应始终被声明为一个函数。

  • 过度类名的更改:

    • Vue2.x写法

      1
      2
      3
      4
      5
      6
      7
      8
      .v-enter,
      .v-leave-to {
      opacity: 0;
      }
      .v-leave,
      .v-enter-to {
      opacity: 1;
      }
    • Vue3.x写法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      .v-enter-from,
      .v-leave-to {
      opacity: 0;
      }

      .v-leave-from,
      .v-enter-to {
      opacity: 1;
      }
  • 移除keyCode作为 v-on 的修饰符,同时也不再支持config.keyCodes

  • 移除v-on.native修饰符

    • 父组件中绑定事件

      1
      2
      3
      4
      <my-component
      v-on:close="handleComponentEvent"
      v-on:click="handleNativeClickEvent"
      />
    • 子组件中声明自定义事件(emit配置对象接收到的就是自定义事件,否者就是原生事件),所以你即使在组件当中使用了.native修饰符,没有用emit接收的话,也是一个原生事件

      1
      2
      3
      4
      5
      <script>
      export default {
      emits: ['close']
      }
      </script>
  • 移除过滤器(filter)

    过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。

  • ……

文章作者: 梦洁
文章链接: https://www.dreamlove.top/2681a891.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

评论
avatar
梦洁
小小的字,有大大的梦想~分享我的前端学习过程,经历,错误,和一些其他折腾过程
关注下我(* ̄▽ ̄*)
公告
不断更新中,有问题请留言回复(会通过邮箱提醒~)
目录
  1. 1. 一.创建vue3.0工程
    1. 1.1. 1.使用vue-cli创建
    2. 1.2. 2.使用 vite 创建
  2. 2. 一(extra).分析vue2.x和vue3.x结构
    1. 2.1. vue2.x的 main.js 初始时的代码
    2. 2.2. vue3.x的 main.js 初始化时的代码
  3. 3. 二.常用的Composition API(组合API)
    1. 3.1. 1.setup
      1. 3.1.1. setup是vue3.0新增加的一个配置项,为一个函数,里面可以写函数代码,注意区分下methods当中的写法
      2. 3.1.2. 组件所用到的数据,方法,都可以写在setup当中,当然,你也可以写在vue2当中的data,methods等当中
      3. 3.1.3. setup的返回值
      4. 3.1.4. 注意点
    2. 3.2. 2.ref函数-处理基本数据类型
      1. 3.2.1. ref函数作用:
      2. 3.2.2. ref函数语法:
    3. 3.3. 3.ref函数-处理对象数据类型
    4. 3.4. 4.ref函数总结
    5. 3.5. 5.reactive函数
      1. 3.5.1. reactive作用:
      2. 3.5.2. reactive语法:
      3. 3.5.3. 注意点:
      4. 3.5.4. 示例:
      5. 3.5.5. 失去响应式示例
    6. 3.6. 6.reactive对比ref
    7. 3.7. 7.vue2响应式原理-演示简版
      1. 3.7.1. 实现原理:
      2. 3.7.2. vue2对响应式数据存在的问题
    8. 3.8. 8.vue3响应式原理和vue2响应式原理-分析简版
      1. 3.8.1. vue2响应式代码示例演示
      2. 3.8.2. vue3响应式原理代码示例演示
    9. 3.9. 9.setup注意的二个点
      1. 3.9.1. 注意点1-setup优于所有的生命周期先执行
      2. 3.9.2. 注意点2-setup的参数
      3. 3.9.3. 为什么自定义事件需要通过emit来去使用呢?
    10. 3.10. 10.vue3的computed
      1. 3.10.1. 回顾vue2时候的computed
      2. 3.10.2. vue3时候的computed计算属性
    11. 3.11. 11.vue3的watch
    12. 3.12. 12.vue3的watchEffect
    13. 3.13. 13.vue3的生命周期钩子
      1. 3.13.1. vue2的生命周期 @vue2生命周期介绍官网
      2. 3.13.2. vue3的生命周期 @vue3生命周期介绍官网
    14. 3.14. 14.自定义hooks
      1. 3.14.1. 比如说有一个需求,单击鼠标可以获取鼠标的位置的函数功能,我们用hook看看
    15. 3.15. 15.toRef和toRefs
      1. 3.15.1. toRef
        1. 3.15.1.1. 语法格式
        2. 3.15.1.2. 用途点
      2. 3.15.2. toRefs
        1. 3.15.2.1. 语法格式
        2. 3.15.2.2. 示例
  4. 4. 三.其他composition API
    1. 4.1. 1.shallowRef和shallowReactive
      1. 4.1.1. shallowRef
      2. 4.1.2. shallowReactive
    2. 4.2. 2.readonly和shallowReadonly
      1. 4.2.1. readonly
      2. 4.2.2. shallowReadonly
      3. 4.2.3. 二者的示例代码
    3. 4.3. 3.toRaw和markRaw
      1. 4.3.1. toRaw
      2. 4.3.2. markRaw
      3. 4.3.3. 二者的示例代码
    4. 4.4. 4.customRef
    5. 4.5. 5.provide和inject
    6. 4.6. 6.响应式数据判断-isRef,isReactive,isProxy,isReadonly
  5. 5. 四.新的组件
    1. 5.1. 1.Fragment
    2. 5.2. 2.Teleport
    3. 5.3. 3.suspense/异步加载
      1. 5.3.1. 前置了解
      2. 5.3.2. 注意要点
      3. 5.3.3. 单纯defineAsyncComponent函数使用(未使用suspense的异步加载)
      4. 5.3.4. defineAsyncComponent函数和suspense组件组合使用(异步加载)
        1. 5.3.4.1. 目的:
        2. 5.3.4.2. 使用:
        3. 5.3.4.3. 示例代码
  6. 6. 五.其他
最新文章
\ No newline at end of file diff --git a/2724e.html b/2724e.html new file mode 100644 index 000000000..53bc90d29 --- /dev/null +++ b/2724e.html @@ -0,0 +1 @@ +七牛云本想白嫖对象存储和cdn还是要一分钱 | 梦洁小站-属于你我的小天地

七牛云本想白嫖对象存储和cdn还是要一分钱

月初了,一看费用,竟然有一分钱

  • 一看账单,竟然的CDN-HTTPS的费用
    • 我心想,不是有白嫖的吗?为什么还有费用

  • 于是就问客服
    • 客服就说了如下,,,,,,,原来免费的是http,而不是https

  • 再细看,的确是http流量,好吧好吧~~~

文章作者: 梦洁
文章链接: https://www.dreamlove.top/2724e.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

评论
\ No newline at end of file diff --git a/273b44bb.html b/273b44bb.html new file mode 100644 index 000000000..6cd3d7cca --- /dev/null +++ b/273b44bb.html @@ -0,0 +1 @@ +express,multer,jQuery前端后端上传单个文件 | 梦洁小站-属于你我的小天地

express,multer,jQuery前端后端上传单个文件

先安装基本的模块

$ npm init -y

$ npm install express –save

$ npm install multer –save

附上multer的github当中别人汉化的API文档

github地址

我这使用的nodemon,如果那么使用node运行,修改记得重启

开始创建基本express(上传图片)

当前目录下创建fileup.js文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const express = require("express");
const multer = require("multer");
var app = express();//创建express实例

//这里是为了后期没有跨域问题设置的静态资源目录
//__dirname为NodeJS全局变量: 返回运行当前js的文件夹的绝对路径
app.use("/",express.static(__dirname));

//后期ajax提交地址就为:http://localhost:3000/file 端口号可在下方自行设置
app.post("/file",(req, res) => {
//前期测试接口发送数据,没有问题再进行下一步
res.send("file ok");
})

//设置在本地端口3000
app.listen("3000", () => {
console.log("-------------server start-------------------");
})

测试是否正常

这里使用postman测试

测试没有问题再继续,不然后期很难改

前端部分

当前目录下创建up.html

1
2
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
</head>

<body>

<input type="file" id="fileInput">
<button id="btn">上传文件</button>
<script>
//这里不用jQuery获取是后期需要用到DOM元素,用jQuery也可以,用get或则[]就可以转为DOM元素
var fileInput = document.getElementById("fileInput");
var $btn = $("#btn");
$btn.click(function () {
//创建表单
//这里new FormData不传入参数,因为这里只是上传一个图片,不存在其他数据,如果要传,传入form标签对应的DOM元素
var formData = new FormData();
//传入文件的key和value
//fileInput.files[0]获取第一个文件,为什么是数组的我猜是因为开发js的为了和多文件上传一样用
formData.append("fileusers", fileInput.files[0]);
//注意formData.append和formData.set区别,
$.ajax({
"type": "post",
"url": "http://localhost:3000/file",
//contentType:true;默认值,值为application/x-www-form-urlencoded
//contentType:false;值为multipart/form-data
//重要!!!!!!!!!!!!!!!!!!!!!!!!
"contentType": false,
//重要!!!!!!!!!!!!!!!!!!!!!!!!
"processData": false,
"data": formData,
"success": function (data) {
console.log(data);
},
"error": function (error) {
console.log("错误了", error);
}
});
});
</script>
</body>

</html>

前端需要注意的

  1. 注意formData.append和formData.set区别

    1. formData.append是如果存在值则会添加到后面,就像[{…},{…}]一样
    2. formData.set是会覆盖前面的,只保留一个
  2. jQuery上传数据的时候,记得设置下面二点

    1. “processData”: false
    2. “contentType”: false
  3. formData.append(“fileusers“, fileInput.files[0]); 当中fileusers在这里的作用

    1. 后端multer需要通过用户定义的key,在这里也就是fileusers来设置数据
  4. new FormData();如果需要传入参数,是传入form标签对应的DOM元素

multer使用

  • 大体分为而部分
    • 根据multer({})创建一个变量(对文件的一些设置和过滤),比如说 var fileOption = multer({})
    • 依据这个变量创建单例fileOption.single(key)或者fileOption.array(key,maxcount)
    • 使用这个单例作为过滤器来对文件进行过滤和操作(也就是中间件)

multer({})创建一个变量当中一些可以设置的(具体可以参考官网)

  1. limits{对象}(一个对象,指定一些数据大小的限制)

    KeyDescriptionDefault
    fieldNameSizefield 名字最大长度100 bytes
    fieldSizefield 值的最大长度1MB
    fields非文件 field 的最大数量无限
    fileSize在 multipart 表单中,文件最大长度 (字节单位)无限
    files在 multipart 表单中,文件最大数量无限
    parts在 multipart 表单中,part 传输的最大数量(fields + files)无限
    headerPairs在 multipart 表单中,键值对最大组数2000
  2. fileFilter{函数}设置一个函数来控制什么文件可以上传以及什么文件应该跳过

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function fileFilter (req, file, cb) {

    // 这个函数应该调用 `cb` 用boolean值来
    // 指示是否应接受该文件

    // 拒绝这个文件,使用`false`,像这样:
    cb(null, false)

    // 接受这个文件,使用`true`,像这样:
    cb(null, true)

    // 如果有问题,你可以总是这样发送一个错误:
    cb(new Error('I don\'t have a clue!'))

    }
  3. storage{返回值} 为运行multer.diskStorage({设置参数})的返回值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    const storage = multer.diskStorage({
    //定义文件存储位置
    destination: function (req, file, cb) {
    //回调函数file当中
    /* file对象的内容{
    fieldname: 'fileusers',//通过formData.append(key,value)添加的key值
    originalname: '_1464815392_.bmp',//文件名
    encoding: '7bit',//编码
    mimetype: 'image/bmp' //mimeType类型
    } */
    //cb(null,存储路径)
    cb(null, './myfile');//可能是以fileup.js在哪里运行为基准的参考的
    },
    //重定义上传的文件
    filename: function (req, file, cb) {
    //cb(null,新文件名称)
    cb(null, file.fieldname + '-' + Date.now())
    }
    })

    const upload = multer({ storage: storage })

multer代码

当前目录下创建fileup.js文件和myfile文件夹

1
2
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
const express = require("express");
const multer = require("multer");
var app = express(); //创建express实例

//这里是为了后期没有跨域问题设置的静态资源目录
//__dirname为NodeJS全局变量: 返回运行当前js的文件夹的绝对路径
app.use("/", express.static(__dirname));

//根据multer({})创建一个变量(对文件的一些设置和过滤)
var fileOption = multer({
//上传限制
limits: {
fileSize: 5 * 1024 * 1024 * 1024,
files: 1
},
//存储位置
storage: multer.diskStorage({
destination: function (req, file, cb) {
cb(null, './myfile');
},
//重定义上传的文件
filename: function (req, file, cb) {
//cb(null,新文件名称)
cb(null, file.fieldname + '-' + Date.now()+"-"+file.originalname);
}
}),
//过滤
fileFilter: function (req, file, cb) {
//这里就不写了~,如果添加了这个对象,一定要设置cb(null,true);不然数据过不去
cb(null,true);
}
});

//依据这个变量创建单例multer.single(key)或者multer.array(key,maxcount)
var fileOptionFunction=fileOption.single("fileusers");//传入通过formData.append(key,value)添加的key值
//后期ajax提交地址就为:http://localhost:3000/file 端口号可在下方自行设置
app.post("/file", (req, res) => {
//这里传入的是app.post当中的req,和res
fileOptionFunction(req,res,(error)=>{
if (error) {
return res.send({err:-1,msg:'上传图片不能大于5M'})
}
res.send({error:0,msg:"文件上传成功"})
})
})

//设置在本地端口3000
app.listen("3000", () => {
console.log("-------------server start-------------------");
})

multer部分需要注意的

  1. 如果使用app.post(“/file”, uploadOption.single(“fileusers”),(req, res) => { });好像无法处理文件过大时候的异常
  2. 如果使用上面fileup.js文件当中的方式,可以在回调当中处理文件过大时候的异常
  3. 如果设置了文件过滤,那么遇到不符合的文件扩展名,req.file的值会为undefined
  4. 如果设置了fileFilter文件过滤,一定要设置cb(null,true)(通过),或者cb(null,false);(不通过),不然留空的话数据会一直处于”pending”状态

具体文件目录结构和参考完整代码下载

启动后浏览器输入http://localhost:3000/up.html

目录文件结构

参考完整代码下载

express+multer+jQuery前端后端上传单个文件演示

文章作者: 梦洁
文章链接: https://www.dreamlove.top/273b44bb.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

评论
\ No newline at end of file diff --git a/284a3682.html b/284a3682.html new file mode 100644 index 000000000..0e35b3b40 --- /dev/null +++ b/284a3682.html @@ -0,0 +1 @@ +纯css+html实现的分页器功能 | 梦洁小站-属于你我的小天地

纯css+html实现的分页器功能

分页器

下载

github地址

使用

  1. 引入样式文件<link rel="stylesheet" href="./paginationself.css">

  2. 引入js代码文件<script src="./paginationself.js"></script>

  3. js代码添加

    1
    2
    var fy = document.getElementById("pagination_self");//父容器,负责存储分页器,id名不要改
    paginationself(fy, {});

具体参数

paginationself(fatherDom,options,callback)

  1. fatherDom:生成器生成的父容器
  2. options对象:参数配置
    • pageInfo对象
      • pageNum:当前页
      • totalPage:数据总个数
      • least:当总页数低于least的时候页码全部显示
      • size:一次显示多少页码
    • textInfo对象:设置显示文字
      • first
        • (默认为首页)
      • prev
        • (默认为上一页)
      • next
        • (默认为下一页)
      • last
        • (默认为尾页)
  3. callback:当用户单击页数的时候触发回调函数,会返回一个用户单击后的页码

参考示例

1
2
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
35
36
37
38
39
40
41
42
43
44
45
46
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<link rel="stylesheet" href="./paginationself.css">
<style>
/* 外壳样式,可选,写了就覆盖默认的 */
/* #pagination_self {
width: 98%;
height: 50px;
border: 1px solid #e5e5e5;
margin: 20px auto;
display: flex;
justify-content: center;
align-items: center;
} */
</style>
</head>

<body>
<!-- id名不可变动! -->
<div id="pagination_self">
</div>
<script src="./paginationself.js"></script>
<script>
// 使用
var fy = document.getElementById("pagination_self");
paginationself(fy, {
pageInfo: {
pageNum:2,
totalPage:25
}
}, (nowPage) => {
//此为回调函数,可省略~
console.log("当前显示的页为",nowPage);
});
//等同于
// paginationself(fy,{});
</script>
</body>

</html>

效果图

图1

图2

图3

图4

文章作者: 梦洁
文章链接: https://www.dreamlove.top/284a3682.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

评论
\ No newline at end of file diff --git a/2aefebb.html b/2aefebb.html new file mode 100644 index 000000000..236df9b9d --- /dev/null +++ b/2aefebb.html @@ -0,0 +1 @@ +QQ农场怀旧版搭建(附带搭建完成示例) | 梦洁小站-属于你我的小天地

QQ农场怀旧版搭建(附带搭建完成示例)

QQ农场搭建

  • 示例均在宝塔面板搭建
  • 搭建完成网站,欢迎━(`∀´)ノ亻!大家种种菜

所需依赖

1
2
3
mysql 5.5
php 5.4
nginx 1.22

  • 新建站点

  • 推荐配置
1
2
3
4
5
操作系统	不限制	UNIX/Linux/FreeBSD	Linux
PHP 版本 4.3.0+ 5.0.0+ 7.4.33
附件上传 允许 允许 支持/最大尺寸50M
MySQL 支持 MySQL4.0+ MySQL5.0+ 不支持
磁盘空间 30M+ 不限制 4959M
  • 否则可能出现下图的情况(php版本过高导致)

  • 如果出现了,就重新更换php版本,这里换为了php5.4

  • 下一步

  • 安装成功

  • 出现MySQL Error: 1146.这个错误是因为数据库中没有表数据,接下来,我们需要进入到系统中将数据库导入。

  • 也就是导入这个数据库

  • 我们导入刚刚的数据库

  • 导入

  • 确认

  • 确认覆盖,然后刷新就可以吗

  • 作为管理员,还可以修改数据呢

  • 也支持牧场

第二种搭建方式

  • 填写基本信息

  • 安装成功

  • 也可以记得更新下

如果需要线上下载

  • 要钱,算了

*

文章作者: 梦洁
文章链接: https://www.dreamlove.top/2aefebb.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

评论
\ No newline at end of file diff --git a/2cb9cb36.html b/2cb9cb36.html new file mode 100644 index 000000000..33bb1c0fa --- /dev/null +++ b/2cb9cb36.html @@ -0,0 +1 @@ +前端map标签(创建热点区域或是点击图片指定区域跳转对应链接)) | 梦洁小站-属于你我的小天地

前端map标签(创建热点区域或是点击图片指定区域跳转对应链接))

前言

  • 点击整张图片的某一部分,可以实现自定义跳转或者一些事件

利用img和map和area标签实现

1
2
3
4
5
6
7
<img src="/statics/images/course/planets.gif" width="145" height="126" alt="Planets" usemap="#planetmap">

<map name="planetmap">
<area shape="rect" coords="0,0,82,126" target="_blank" alt="Sun" href="/statics/images/course/sun.gif">
<area shape="circle" coords="90,58,3" target="_blank" alt="Mercury" href="/statics/images/course/merglobe.gif">
<area shape="circle" coords="124,58,8" target="_blank" alt="Venus" href="/statics/images/course/venglobe.gif">
</map>
  • img

    • 使用usemap属性和map标签进行关联
  • map标签

  • area标签

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    coords
    给热点区域设定具体的坐标值。这个值的数值和意义取决于这个值所描述的形状属性。
    对于矩形或长方形,这个 coords 值为两个 X,Y 对:左上、右下。
    对于圆形,这个值是 x,y,r,这里的 x,y 是一对确定圆的中心的坐标而 r 则表示的是半径值。
    对于多边和多边形,这个值是用 x,y 对表示的多边形的每一个点:x1,y1,x2,y2,x3,y3 等等。
    HTML4 里,值可能是像素数量或者百分比,区别是不是有 % 出现; HTML5 里,只可能是像素的数量。
    值有:
    default
    rect
    circle
    poly

快速定位coords

文章作者: 梦洁
文章链接: https://www.dreamlove.top/2cb9cb36.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

评论
\ No newline at end of file diff --git a/2d4609.html b/2d4609.html new file mode 100644 index 000000000..5b08b9c6e --- /dev/null +++ b/2d4609.html @@ -0,0 +1 @@ +今日刷题-隐式转换和String和new String | 梦洁小站-属于你我的小天地

今日刷题-隐式转换和String和new String

题目1

1
2
3
4
5
6
7
8
9
10
11
12
var p1 = {
name:'小明',
age:'12',
action:function(where,doing){
console.log(this.age + '岁的'+this.name + '在' + where + doing);
}
}
var p2 = {
name:'小红',
age:'15'
}
console.log(p1.action.call(p2,'操场上','运动'))
  • 输出结果
    • 15岁的小红在操场上运动
  • 分析
    • call可以改变this的执行,并且使得某一个函数成为对象的方法调用
    • 所以p1.action.call(p2,’操场上’,’运动’) = > p2.action(“操作上”,”运动) = > 输出 “15岁的小红在操场上运动”

题目2

以下哪个表达式的值为true?

1
2
3
4
A.	'1' === 1
B. isNaN(1/0)
C. 1 in [1]
D. 1 && 2 > 1
  • 答案
    • D
  • 分析
    • A: ===不会隐式转换,String类型不会等于Number类型 所以结果为false
    • B: 1/0在js当中返回 Infinity 所以isNaN(1/0)为false
    • C: in在数组可以用来检测是否有这个索引,在对象则是否是实例化对象
      • Arrays var trees = new Array(“redwood”, “bay”, “cedar”, “oak”, “maple”); 0 in trees // returns true
      • var color1 = new String(“green”); “length” in color1 // returns true
    • 算术运算符优先级 > 关系运算符 > 逻辑运算符,所以 1 && true = > true

题目3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//说错1,2,3,4的输出结果
function fn(value){

console.log(arguments instanceof Array); ...①

console.log(arguments.length);...②

console.log(value);...③

var arr = [...arguments];...④

}

fn(1,2,3);
  • 答案
    • ① : false
    • ② : 3
    • ③ : 1
    • ④ : [1,2,3,4]
  • 分析
    • ① : arguments为一个伪数组,实际上是一个对象,所以为false
    • ② : arguments接收函数传入过来的所有参数,所以结果为3
    • ③ : value为函数第一个传入过来的值,所以为1
    • ④ : 记住就好

题目4

1
2
3
4
5
6
下面哪些语句执行结果为true(多选)
A:'foo' == new function(){ return String('foo'); };
B:'foo' == new function(){ return new String('foo'); };
C:[] == 0
D:![]
E:!0
  • 答案
    • B C E
  • 分析
    • A,B:
      • 构造函数使用会返回一个对象:
        • 情况1:return 一个对象,那么构造函数就会返回你定义的这个对象 比如 return {a:xxx}; 就会返回这个 {a:xxx}
        • 情况2:未写return 或者return 基本数据类型,构造函数会默认返回一个对象
        • A选项当中的构造函数返回一个基本数据类型,所以构造函数默认返回一个空对象
          • String(‘foo’); 控制台输出为’foo’
        • B选项返回一个String对象,所以构造函数就返回了这个对象
          • new String(‘foo’);控制台输出为String {‘foo’}
    • C: 引用数据和基本数据比较,会将引用数据转基本数据在比较,[]转基本数据类型为”” , “” == 0 ;比较再转化为布尔类型,false == false,所以为true
    • D:未发生隐式转换,**[]**的布尔值有两种转换结果。直接转的话是true(在false六种值之外),还有一种是调用tostring转字符串”” 再转布尔就为false了,所以![] 为true
      • 如果[] == false ,发生了隐式转换,则[]会转化为false
    • E: 0转布尔为false,同样转false的还有 undefined null ‘’ NaN 0 false 这四种都会为FALSE . 其他均为TRUE.所以!false => true
文章作者: 梦洁
文章链接: https://www.dreamlove.top/2d4609.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

评论
\ No newline at end of file diff --git a/2e3bcd80.html b/2e3bcd80.html new file mode 100644 index 000000000..77eb877a2 --- /dev/null +++ b/2e3bcd80.html @@ -0,0 +1 @@ +grid布局的学习 | 梦洁小站-属于你我的小天地

grid布局的学习

前置

明确基本概念

  • 下图的基本概念先看看,这个必须要先了解

容器和项目

  • 在grid布局和flex布局身上,都有针对容器和项目的属性,在使用的时候需要注意这些属性是设置在哪里的

容器身上的属性

  1. grid-template-columns
  2. grid-template-rows
  3. grid ow-gap
  4. grid-column-gap
  5. grid-gap (3和4的简写)
  6. grid-template-areas
  7. grid-auto-flow
  8. justify-items
  9. align-items
  10. place-items(8和9的简写)
  11. justify-content
  12. align-content
  13. place-content(11和12的简写)
  14. grid-auto-columns
  15. grid-auto-rows

grid-template-columns/rows

  • 设置列/行的个数

    • 可以使用的属性有(常见的px就不举例了)
    • repeat(重复的次数,长度)
      • 重复的次数: 明确的数字或者使用auto-fill(重复的次数不确定即可使用此)
      • 长度: 设置项目的宽度或者长度
    1
    2
    3
    4
    5
    6
    7
    8
    grid-template-columns: 100px 100px 100px;
    等同于
    grid-template-columns: repeat(3,100px)


    grid-template-rows: 100px 100px 100px 100px;
    等同于
    grid-template-rows: repeat(4,100px);

    • fr:为了方便表示比例关系,网格布局提供了fr关键字(fraction 的缩写,意为”片段”)

    • minmax:函数产生一个长度范围,表示长度就在这个范围之中,它接受两个参数,分别为最小值和最大值

  • auto表示由浏览器自己决定长度

grid-row-gap/grid-column-gap

  • 设置项目相互之间的距离
  • 根据最新标准,上面三个属性名的grid-前缀已经删除,grid-column-gap和grid-row-gap 写成column-gap和row-gap,grid-gap写成gap
  • 二者的简写为grid-gap或者gap
    • 语法gap = <'row-gap'> <'column-gap'>

grid-template-areas

  • 一个区域由单个或多个单元格组成,由你决定 (具体使用,需要在项目属性里面设置)
    • 如果使用到了此属性,项目记得为其设置grid-area
1
2
3
4
5
6
7
8
9
grid-template-areas: 'a b c' 
'd e f'
等同于
grid-template-areas: 'a b c' 'd e f';

//当不需要利用的时候,则使用"点"(.)表示
grid-template-areas: 'a . c'
'd . f'
'g . i';
  • grid-template-areas: 'a a a' 'b b b' 'c c c';示例

效果图

1
2
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
35
36
37
38
39
40
41
42
43
44
45
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<style>
#container {
display: grid;
grid-template-columns: 50px 50px 50px;
grid-template-rows: 50px 50px 50px;
grid-template-areas: 'a a a' 'b b b' 'c c c';
}

.item {
font-size: 2em;
text-align: center;
border: 1px solid #e5e4e9;
}

.item-1 {
background-color: #ef342a;
grid-area: a;
}

.item-2 {
background-color: #f68f26;
grid-area: b;
}

.item-3 {
background-color: #4ba946;
grid-area: c;
}

</style>
</head>
<body>
<div id="container">
<div class="item item-1">1</div>
<div class="item item-2">2</div>
<div class="item item-3">3</div>
</div>
</body>
</html>

grid-auto-flow

  • 划分网格以后,容器的子元素会按照顺序,自动放置在每一个网格。默认的放置顺序是“先行后列”, 即先填满第一行,再开始放入第二行 (这个属性设置就是项目的排放顺序

  • 默认为row,可以设置的有

1
2
单个关键字:row、column,或 dense 中的一个。
两个关键字:row dense 或 column dense。

justify-items/align-items

  • justify-items:设置水平方向每一个项目在指定格子当中的排布规则,
  • align-items:设置垂直方向每一个项目在指定格子当中的排布规则
  • 简写属性为place-items
    • place-items: <align-items> <justify-items>;
  • 说简单点就是设置糖果在盒子里面的排布方式
    • 举个例子,justify-items:start设置糖果在盒子里面最开始的位置
    • justify-items:end设置糖果在盒子里面最末尾的位置

justify-content/align-content

  • 设置整个内容区域的水平和垂直的对其方式
  • justify-content(水平方向): start | end | center | stretch | space-around | space-between | space-evenly;
  • **align-content(**垂直方向):start | end | center | stretch | space-around | space-between | space-evenly;

grid-auto-columns/grid-auto-rows

  • 用来设置多出来的项目宽和高
  • 只有9个格子,但是却有10个div,多出来的怎么处理呢?

1
2
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<style>
#container {
display: grid;
height: 300px;
grid-template-columns: 50px 50px 50px;
grid-template-rows: 50px 50px 50px;
grid-auto-rows: 10px;
}

.item {
font-size: 2em;
text-align: center;
border: 1px solid #e5e4e9;
}

.item-1 {
background-color: #ef342a;
}

.item-2 {
background-color: #f68f26;
}

.item-3 {
background-color: #4ba946;
}

.item-4 {
background-color: #0376c2;
}

.item-5 {
background-color: #c077af;
}

.item-6 {
background-color: #f8d29d;
}

.item-7 {
background-color: #b5a87f;
}

.item-8 {
background-color: #d0e4a9;
}

.item-9 {
background-color: #4dc7ec;
}
.item-10 {
background-color: #ca22bf;
}
</style>
</head>
<body>
<div id="container">
<div class="item item-1">1</div>
<div class="item item-2">2</div>
<div class="item item-3">3</div>
<div class="item item-4">4</div>
<div class="item item-5">5</div>
<div class="item item-6">6</div>
<div class="item item-7">7</div>
<div class="item item-8">8</div>
<div class="item item-9">9</div>
<div class="item item-10">10</div>
</div>
</body>
</html>

网格线的命名知识点

  • 我们在使用grid-template-columns 或者 grid-template-rows的时候,可以为网格线进行命名操作
    • 使用方括号,指定每一根网格线的名字,方便以后的引用(也就是为grid-column-start使用)
    • 网格布局允许同一根线有多个名字,比如[fifth-line row-5]
1
2
3
4
5
.container {
display: grid;
grid-template-columns: [c1] 100px [c2] 100px [c3] auto [c4];
grid-template-rows: [r1] 100px [r2] 100px [r3] auto [r4];
}
  • 我们在使用grid-template-area进行区域命名的时候,就会引发网格线的重命名
1
2
3
4
5
6
7
grid-template-areas: 
'a b c'
'd e f'
'g h i';
注意,区域的命名会影响到网格线。每个区域的起始网格线,会自动命名为区域名-start,终止网格线自动命名为区域名-end。

比如,区域名为header,则起始位置的水平网格线和垂直网格线叫做header-start,终止位置的水平网格线和垂直网格线叫做header-end。

  • 怎么使用呢,可以在grid-row-start 或者 grid-row-end 或则grid-column-start 或者grid-column-end使用

1
2
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<style>
#container {
display: grid;
grid-template-columns: 50px 50px 50px;
grid-template-rows: 50px 50px 50px;
grid-template-areas: 'a b c' 'd e f' 'g h i';
}

.item {
font-size: 2em;
text-align: center;
border: 1px solid #e5e4e9;
}

.item-1 {
background-color: #ef342a;4
grid-column: a-start / c-end;
/* 当然你也可以 */
/* grid-column: 1 / 4; */
}

.item-2 {
background-color: #f68f26;
}

.item-3 {
background-color: #4ba946;
}

.item-4 {
background-color: #0376c2;
}

.item-5 {
background-color: #c077af;
}

.item-6 {
background-color: #f8d29d;
}

.item-7 {
background-color: #b5a87f;
}

.item-8 {
background-color: #d0e4a9;
}

.item-9 {
background-color: #4dc7ec;
}
</style>
</head>
<body>
<div id="container">
<div class="item item-1">1</div>
<div class="item item-2">2</div>
<div class="item item-3">3</div>
<div class="item item-4">4</div>
<div class="item item-5">5</div>
<div class="item item-6">6</div>
<div class="item item-7">7</div>
<div class="item item-8">8</div>
<div class="item item-9">9</div>
</div>
</body>
</html>

项目属性

  1. grid-column-start
  2. grid-column-end
  3. grid-row-start
  4. grid-row-end
  5. grid-column (1和2的简写形式)
  6. grid-row (3和4的简写形式)
  7. grid-area
  8. justify-self
  9. align-self
  10. place-self (8和9的简写形式)
  • 学之前来看看网格线网格线从1开始数,4个格子会有5个网格线

grid- column/row - start/end

  • 一句话解释,用来指定item的具体占据位置(或者说项目从第几行/列开始,到第几行/列结束)
  • 除了指定为第几个网格线,还可以指定为网格线的名字。

  • 此外,还可以使用span
    • span <number>:网格线将跨域的网格数量长度,如果省略了<number>,它默认为1。负的整数或0是无效的。
    • 比如下面的grid-column-start: span 2就是说我将经过2个网格线

grid-column / grid-row

  • grid-column属性是grid-column-start和grid-column-end的合并简写形式
  • grid-row属性是grid-rowstart属性和grid-row-end的合并简写形式

grid-area

  • 作用1:指定放置的区域名字
1
2
3
4
5
容器中设置
grid-template-areas: 'a a a' 'b b b' 'c c c '

项目中设置
grid-area:b

  • 作用2:简写,grid-area属性还可用作grid-row-start、grid-column-start、grid-row-end、grid-column-end的合并 简写形式,直接指定项目的位置
    • grid-area: <row-start> / <column-start> / <row-end> / <column-end>;

justify-self / align-self / place-self

  • justify-self属性设置单元格内容的水平位置(左中右),跟justify-items属性的用法完全一致, 但只作用于单个项目 (水平方向)
  • align-self属性设置单元格内容的垂直位置(上中下),跟align-items属性的用法完全一致, 也是只作用于单个项目 (垂直方向)
  • justify-self: start | end | center | stretch;

order

  • 默认情况下,所有的项目的order都是0,order可以被设置为正数或者负数,就像z-index一样
文章作者: 梦洁
文章链接: https://www.dreamlove.top/2e3bcd80.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

评论
\ No newline at end of file diff --git a/30ebed6d.html b/30ebed6d.html new file mode 100644 index 000000000..a84bc645d --- /dev/null +++ b/30ebed6d.html @@ -0,0 +1 @@ +linux普通服务器和NAT服务器下载transmission制作种子 | 梦洁小站-属于你我的小天地

linux普通服务器和NAT服务器下载transmission制作种子

基础

  • 安装
1
apt install transmission transmission-daemon
  • 修改配置文件
1
2
3
4
5
vi /var/lib/transmission/.config/transmission-daemon/settings.json

"rpc-password": "输入你的管理密码",
"rpc-username": "管理你的用户名",
"rpc-whitelist-enabled": false, //关闭白名单
  • 保存退出,重新载入配置
1
systemctl reload transmission-daemon.service
  • 安装增强图形化界面(可选)
1
wget https://github.com/ronggang/transmission-web-control/raw/master/release/install-tr-control-cn.sh
  • 执行安装脚本(如果系统不支持 bash 命令,请尝试将 bash 改为 sh
1
bash install-tr-control-cn.sh

常用命令

启动

1
systemctl start transmission-daemon.service

停止

1
systemctl stop transmission-daemon.service

配置文件默认路径

1
/var/lib/transmission-daemon/.config/transmission-daemon/

创建种子

  • -o指明种子生成路径
  • 通过以下命令,可以为/var/lib/transmission-daemon/downloads/macOS龙神Switch模拟器.zip 文件生成了种子,生成种子的位置在文件同级目录下,种子名为 macOS龙神Switch模拟器.torrent
1
transmission-create -s 2048 -t http://1337.abcvg.info:80/announce -t http://bt.okmp3.ru:2710/announce -t http://bz.tracker.bz:80/announce -t http://fxtt.ru:80/announce -t http://nyaa.tracker.wf:7777/announce -t http://open.acgnxtracker.com:80/announce -t http://p2p.0g.cx:6969/announce -t udp://v1046920.hosted-by-vdsina.ru:6969/announce -t udp://v2.iperson.xyz:6969/announce -t udp://vibe.sleepyinternetfun.xyz:1738/announce -t udp://www.torrent.eu.org:451/announce  -t http://frp.v2fy.com:8000/announce  -o /var/lib/transmission-daemon/downloads/macOS龙神Switch模拟器.torrent  /var/lib/transmission-daemon/downloads/macOS龙神Switch模拟器.zip &
1
transmission-create -s 2048 -o /var/lib/transmission-daemon/downloads/macOS龙神Switch模拟器.torrent  /var/lib/transmission-daemon/downloads/macOS龙神Switch模拟器.zip &

创建种子PT

1
2
3
4
5
6
7
8
9
10
11
12
transmission-create -p -t PT站tracker -o *.torrent -s 2048 /var/lib/transmission-daemon/downloads/123 &

-p 表示这是私用的种子,这个必须要加上
-o 生成的种子输出位置,不要忘记把名字打上
-t tracker的地址, 按实际PT站,大家自行修改
-s 每个文件块的大小,单位是KB,设置的是2M,也就是2048KB
最后空一格写源文件的位置,也就是文件的存放位置,可以是一个文件或者一整个目录
最后可以空一行加一个&,这样即使关掉窗口也可以在后台运行, 行尾\ 表示续行

- 相关 find / -name transmission 用来查找文件位置,要用的就是transmission-create

目录 /var/lib/transmission-daemon/downloads 是下载文件的默认存放路径

NAT机器搭建做种机器

  • 需要修改配置文件,网页端端口最好和NAT端口映射保持一致
    • 比如我访问端口配置文件填写的是50166,那么NAT映射也使用50166端口
  • 还有peer-port随机最大值和最小值也也需要在NAT端口访问内,一一对应
1
2
3
4

"peer-port": 50167,
"peer-port-random-high": 50181,
"peer-port-random-low": 50168,

普通服务器搭建做种机

  • 安装然后按照基础命令来就OK,但是别忘记开放端口了(peer-port-random-low-peer-port-random-high)范围

参考文章

文章作者: 梦洁
文章链接: https://www.dreamlove.top/30ebed6d.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

评论
\ No newline at end of file diff --git a/316d79f9.html b/316d79f9.html new file mode 100644 index 000000000..122889f51 --- /dev/null +++ b/316d79f9.html @@ -0,0 +1 @@ +React的学习笔记-(Bilibili李立超) | 梦洁小站-属于你我的小天地

React的学习笔记-(Bilibili李立超)

写法的变更

之前

1
2
const divNode = <div>你好,React</div>
ReactDOM.render(divNode,document.getElementById('root'))

会警告

现在

1
2
3
4
5
<script type="text/babel">
const divNode = <div>你好,React</div>
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(divNode)
</script>

同时,不支持渲染对象

1
2
3
4
5
6
7
8
const array = ['动感超人','西瓜超人'];//支持
const listObj = [
{name:'李白',sex:'男'},
{name:'李白2',sex:'男'},
]
const divNode = <div>你好,React {listObj}//不支持</div>
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(divNode)

引入

1
import ReactDOM from "react-dom/client"

Card封装为UI

  • 函数标签体
  • 传递类名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const Logs = () => {
return (
<Card className="logs">
<LogItem/>
</Card>
)
};

/*Card设置为其添加阴影效果*/
const Card = (props) => {
return (
<div className={`card ${props.className}`}>
{props.children}
</div>
);
};

表单的双向绑定

  • 通过setState和onChange来实现,
  • 非受控 没有使用state,受控,使用了state并绑定了值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<Card className="login-form">
<div className='login-form_item'>
<label >
<span className='label' >日期:</span>
<input type='date' value={inputDate} onChange={(e) => setInputDate(e.target.value)}/>
</label>
</div>

<div className='login-form_item'>
<label>
<span className='label'>内容:</span>
<input type='text' placeholder='请输入学习内容' value={inputContent} onChange={e => setInputContent(e.target.value)}/>
</label>
</div>

<div className='login-form_item'>
<label>
<span className='label'>时长:</span>
<input type='text' placeholder='请输入学习时长' value={inputTime} onChange={e => setInputTime(e.target.value)}/>
</label>
</div>
<div className='login-form_operation'>
<button className='login-form_operation_btn'>添加计划</button>
</div>
</Card>

数据为true的时候才会显示对话框

别漏掉了大括号哦

解决对话框层级问题

  • 出现情况:组件默认作为了父组件的后代,被渲染到了页面当中导致出现层级问题

  • 解决办法

    • 和根元素平级
    • 使用protal把组件渲染到网页指定位置
  • 使用方法

    • index.html添加一个容器
    1
    <div id='portal-root '></div>
    • 在使用的地方获取容器DOM
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    const protalDOM = document.getElement('portal-root');

    const Backdrop = () => {

    return ReactDOM.createProtal(jsx内容,目标位置)
    }


    const divPortal = document.getElementById('portal-root');
    return ReactDOM.createPortal((
    <div className='selfMask'>
    {props.children}
    </div>
    ),divPortal)

添加过滤功能

  • 注意字符串的问题,因为这里用的是全等于号,所以在传出去的时候转化为了数字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const handleChange = (e) => {
/*传递值*/
console.log(e.target.value)
props.onChange(+e.target.value);
}
return (
<div>
<select value={props.defaultValue} onChange={handleChange}>
<option value={2022}>2022</option>
<option value={2021}>2021</option>
<option value={2020}>2020</option>
</select>
</div>
);

create-react-app

  • React.StrictMode作用
    • 使用严格模式去渲染组件

内联样式和行内样式

  • 这个可以参考,根据state的值动态设置样式

  • import "./App.css" 会全局污染吗?会的,引入是全局的,其他组件也可以看到
    • import "./App.css" 就是全局引入,在App.js当中可以看到,其他组件也都可以使用App.css当中的类名

模块化css(避免全局污染 )

  1. 创建一个xxx.module.css
1
2
3
4
5
6
7
8
9
10
11
12
13
.p1{
color:red
}
.app_wrapper{
color:red
}
.app_wrapper_info {
background-color: green;
}
.app_wrapper_name {
background-color: blue;
}

  1. 组件中引入
1
2
//一般引入的名称为classes
import classes from "./App.module.css";
  1. 通过classes来设置类
1
2
注意,最好不要出现除字母,数字,下划线以外的类名
比如出现app-wrapper_name就不要出现'-'了
  • 如果通过.module.css包含标签,比如直接设置div为棕色,依旧会影响到全局组件

视口的设置

Context

  • 避免组件逐层传来传去,就可以使用context

一种组件通信方式,常用于[祖组件][后代组件]间的通信

处理字体大小

  • 在小手机是正常的,但是切换到大屏幕的手机就字体就有问题了

  • 原因

    • 像素比问题
    • 所以不要将字体设置为px,设置为rem即可
  • 但是假如你使用了转换插件,并且在input当中的placeholder的时候没有设置字体大小,就会出现这种情况,所以,最好的预防方法就是将出现字体的地方都设置下字体大小

public/img和src/asset

  • public下的可以被服务器所访问,也就是有专门的地址可以看到这个
  • src/asset不可以被服务器访问,也就是没有入口可以看到这个

避免事件冒泡

  • 在div不想被取消回调的div身上添加如下
1
<div className={classes.detail} onClick={e => e.stopPropagation()}>

遮罩层

  • 遮罩层如果需要接收所有的参数,可以这样子写
1
2
3
<div {...props} className={`${classes.selfMask} ${props.className}`} >
{children}
</div>
  • 但是注意,{...props}要写在前面哦,如果写在后面,会覆盖已经书写的className属性

项目很多情况出现冒泡

  • 很多情况出现冒泡,都需要在父元素身上绑定下stopPropagation
1
2
3
4
5
e.stopPropagation
<div className={classes.confirm_operation} onClick={e=>e.stopPropagation()}>
<button className={classes.confirm_operation_cancel} onClick={cancel}>取消</button>
<button className={classes.confirm_operation_clear} onClick={confirm}>清空</button>
</div>

清空购物车后数量没有变化

  • 因为之前的浅拷贝,用的都是从最原始的那个数据,所以想要解决清空购物车后数量 依旧存在的问题,以下二个方法

    • 使用深拷贝(但是这样子原始数据数量就不会改变了在这个项目里面)
    • 清空购物车的时候,手动清空下里面的amount
    1
    2
    3
    4
    5
    6
    //手动清空amount数量
    temp.list.forEach(item => item.amount = 0);
    temp.total = 0;
    temp.list = [];
    temp.money = 0;
    setCartData(temp)

useEffect和React严格模式和setState执行流程

  • React组件有部分逻辑都可以直接编写到组件的函数体中的,像是对数组调用filter、map等方法,像是判断某个组件是否显示等。但是有一部分逻辑如果直接写在函数体中,会影响到组件的渲染,这部分会产生“副作用”的代码,是一定不能直接写在函数体中。
  • 例如,如果直接将修改state的逻辑编写到了组件之中,就会导致组件不断的循环渲染,直至调用次数过多内存溢出。

React.StrictMode

  • 编写React组件时,我们要极力的避免组件中出现那些会产生“副作用”的代码。同时,如果你的React使用了严格模式,也就是在React中使用了React.StrictMode标签,那么React会非常“智能”的去检查你的组件中是否写有副作用的代码,当然这个智能是加了引号的,我们来看看React官网的文档是如何说明的:

Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions:

  • Class component constructor, render, and shouldComponentUpdate methods
  • Class component static getDerivedStateFromProps method
  • Function component bodies
  • State updater functions (the first argument to setState)
  • Functions passed to useState, useMemo, or useReducer

上文的关键字叫做“double-invoking”即重复调用,这句话是什么意思呢?大概意思就是,React并不能自动替你发现副作用,但是它会想办法让它显现出来,从而让你发现它。那么它是怎么让你发现副作用的呢?React的严格模式,在处于开发模式下,会主动的重复调用一些函数,以使副作用显现。所以在处于开发模式且开启了React严格模式时,这些函数会被调用两次:

类组件的的 constructor, render, 和 shouldComponentUpdate 方法
类组件的静态方法 getDerivedStateFromProps
函数组件的函数体
参数为函数的setState
参数为函数的useState, useMemo, or useReducer

重复的调用会使副作用更容易凸显出来,你可以尝试着在函数组件的函数体中调用一个console.log你会发现它会执行两次,如果你的浏览器中安装了React Developer Tools,第二次调用会显示为灰色。

  • 比如报这个错误Too many re-renders就是我们直接在函数体中调用setState方法,就会触发

    • 疑问:不是说,当新的state值和旧值相同的时候,不会触发组件的重新渲染,为什么下面这个代码会呢?
    • 解释:setCount被调用的时候,处于渲染阶段,不会判断值是否相同
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const App = () =>{
    const [count,setCount] = useState(0);

    setCount(0)

    return (
    <div>
    {count}
    </div
    )
    }

函数组件setState(或者是setXXXXX)执行流程

1
2
3
4
5
6
7
8
9
10
setState() --> dispatchSetData()

//1.会先判断,组件当前处于什么阶段
如果是渲染阶段 --> 不会检查state的值是否相同
如果不是渲染阶段 --> 会检查state的值是否相同
- 如果值不相同,则会对组件进行重写渲染
- 如果值相同,则不会组件进行重写渲染
如果值相同,React在一些情况下会继续执行当前组件的渲染
但是这个渲染不会触发其子组件的渲染,这次渲染不会产生实际的效果
这种情况通常发生在值第一次相同的时候

示例(非严格模式下)

App.jsx

1
2
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
35
import React, {useState} from 'react';
import B from "./B";
const App = () => {
console.log('App组件重新渲染了')
const [count,setCount] = useState(0);

// handleClick被点击触发的时候
// 是在非渲染阶段了
const handleClick = () => {
setCount(1);
/**
* 初始化的时候
* 输出 'App组件重新渲染了' 和 'B组件被重新渲染了'
*
* 第一次点击按钮 count 变为 1
* 输出 'App组件重新渲染了' 和 'B组件被重新渲染了'
*
* 第二次点击按钮 count 变为 1
* 输出 'App组件重新渲染了'
*
* 第三次点击按钮 count 变为 1
* 未输出任何内容
*/
}
return (
<div>
我是App{count}
<button onClick={handleClick}>点击我</button>
<B/>
</div>
);
};

export default App;

B.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
import React from 'react';

const B = () => {
console.log('B组件被重新渲染了')
return (
<div>
我是B组件
</div>
);
};

export default B;

还有这个示例也可以看看,setTimeout是异步的,执行的时候也是处于非渲染阶段,所以这个时候就不会导致重复调用从而产生Too many re-renders的报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React, {useState} from 'react';
const App = () => {
console.log('App组件重新渲染了')
const [count,setCount] = useState(0);

setTimeout(() => {
setCount(1)
},0)

return (
<div>
我是App{count}
</div>
);
};

export default App;

useEffect

  • useEffect是一个钩子函数,需要一个函数作为参数,这个作为参数的函数,默认将会在组件渲染完毕后执行(也就是在非渲染阶段才执行这个回调)

  • 在实际开发中,可以将那些会产生副作用的代码编写到useEffect的回调函数中,这样子就可以避免这些代码影响到组件的渲染

    • 在函数组件主体内(这里指在 React 渲染阶段)改变 DOM、添加订阅、设置定时器、记录日志以及执行其他包含副作用的操作都是不被允许的,因为这可能会产生莫名其妙的 bug 并破坏 UI 的一致性。

    • 使用 useEffect 完成副作用操作。赋值给 useEffect 的函数会在组件渲染到屏幕之后执行

  • 说通俗点就是我不可以在渲染阶段去修改state的值,我需要在他渲染完毕后再去修改(就是变根回调的执行时机)

  • 如果**不想每次都在渲染阶段useEffect被调用,**我们可以传入第二个参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
第二个参数为数组(即为依赖项目)

当数组当中的依赖项发送改变的时候,useEffect才会去调用
通过会将Effect当中所有的变量都设置为依赖项目
这样子一来可以确保这些值发生变化时候,会触发Effect变化

setState()是由钩子函数useState()生成的
useState()会确保组件的每次渲染都会获取到相同的setState()对象,所以stateState可以不设置进Effect
所以下面的setShowDetail,setShowCheckOut,可以写进去,也可以不写进去

当第二个参数为空数组,则意味着Effect只会在组件初始化的时候执行一次

const a = 10;

//产生一个闭包
useEffect(() => {
//确保a为最新值
console.log(a);
setShowDetail(false);
setShowCheckOut(false);
},[a,setShowDetail,setShowCheckOut])
  • 结合天俞老师的笔记
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
React中的副作用操作

* 发ajax请求获取数据
* 设置订阅 / 启动定时器
* 手动更改真实DOM

import React from "react";
React.useEffect(() => {
//do something
// ...

//返回的函数将在组件卸载前执行
return () => {
//在这里做一些收尾工作,会在下一次effect调用前执行
//比如清除定时器,取消订阅
}

},[stateValue]) //如果指定的是 [], 那么回调函数只会在第一次render后执行
//否者就会里面的值发生变化,就执行一次回调(因为render被重新执行了)
  • 总结
    • 如果想每次渲染都执行,第二个参数什么都不写
    • 如果想只当使用到的变量发生改变重新调用,那么第二个参数以数组的形式传入使用到的变量名(也就是写入依赖项目)
    • 如果想只执行一次(初始化的时候执行),,第二个参数传入空数组[]

通过useEffct改造input

  • 注释掉的部分为改造前
1
2
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
import React, {useEffect, useState} from 'react';
import classes from "./index.module.css";
import {FontAwesomeIcon} from "@fortawesome/react-fontawesome";
import {faSearch} from "@fortawesome/free-solid-svg-icons";
//import {throttle} from "lodash";

const Index = (props) => {
/*使用节流*/
//const handleChange = throttle((e) => {
// props.search(e.target.value);
//},150)

const [value,setValue] = useState('')
const handleChange = (e) => {
setValue(e.target.value.trim());//设置值
}

useEffect(() => {
props.search(value);
},[value])
return (
<div className={classes.filter}>
<div className={classes.filter_search}>
<FontAwesomeIcon className={classes.filter_search_icon} icon={faSearch} />
{/*<input type="text" className={classes.filter_search_input} placeholder={'请输入关键字'} onChange={handleChange}/>*/}
<input type="text" value={value} onChange={handleChange} className={classes.filter_search_input} placeholder={'请输入关键字'} />
</div>
</div>
);
};

export default Index;

疑问

  • 之前的时候,我设置购物车数量+1,总是会出现多加一次的情况
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/*增加*/
setCartData(prevState => {
const temp = {...prevState};
console.log(temp.list.includes(item))
if(!temp.list.includes(item)){
console.log('我是不包含')
//不包含
item.amount = 1;
temp.list.push(item);
}else{
console.log('我是包含')
//包含
console.log('之前',item.amount)
item.amount +=1;
console.log('之后',item.amount)
}
temp.total++;//数量+1
return temp;
})

我点击+号之后,再次点击+号,总是会重复调用

因为开启了严格模式,会去检查是否有副作用,就会调用二次(因为有时候调用二次副作用就凸显了出来)

useReducer

  • 操作useState生成的数据,在多个函数对state设置了(state定义和方法的添加不在同一个地方),如下图就是这种情况
1
2
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
35
36
//伪代码-1示例

const [carData,setCatData] = useState({})

//添加商品
const addItme = () => {};

//减少商品
const removeItme = () => {};

//清空购物车
const clearCart = () => {};


//伪代码-2示例
import React, {useState} from 'react';

const App = () => {
const [number,setNumber] = useState(0);
const handleReduce = () => {
setNumber(prevState => prevState - 1);
}
const handleAdd = () => {
setNumber(prevState => prevState + 1);
}
return (
<div>
<button onClick={handleReduce}>-</button>
<span>{number}</span>
<button onClick={handleAdd}>+</button>
</div>
);
};

export default App;

  • 所以我们可以使用useReducer
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
useReducer(reducer,initialArg,init)
参数:
reducer:为一个函数(state,action) => stateValue

reducer在执行的时候,会收到二个参数
* state 当前最新的state
* action(一般叫action) 普通的js对象,可以将要干的事情告诉action,
* 进而根据action中不同值来执行不同操作
* 为了避免type无效(区分操作的type),可以无论如何都会返回一个值
* 说白了reducer就是将对state的不同操作定义在同一个函数当中


initialArg: state的初始值,作用和useState()中的值是一样的


返回值:为数组

第一个参数: state 用来获取state的值
第二个参数:state修改的派发器
* 通过派发器可以发送操作state的命令
* 具体的修改行为将会由另外一个函数(reducer)执行


  • 说通俗点就是dispatch是一个派活的,reducer是干活的,reducer根据指令不同,从事不同工作
1
2
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
//可以通过...arg 后输出arg查看所有参数

const [count,countDispatch] = = useReducer((state,action) => {
const { type } = action.type;
if(type === 'ADD') {
return state + 1;
}else if(type ==='SUB'){
return state - 1;
}
return state;//避免没有type匹配失败导致问题
},1)

//或者
const [count,countDispatch] = = useReducer((state,action) => {
const { type } = action.type;
switch(type){
case 'ADD': return state + 1;
case 'SUB': return state -1;
default: return state;
}
},1)

import React, {useState,useReducer} from 'react';


const reducer = (prevState,action) => {
const {type} = action;//对传递过来的对象进行结构
switch (type){
case 'SUB': return prevState - 1;
case 'ADD': return prevState + 1;
default: return prevState;//避免未知的
}
}
const App = () => {
/* const [number,setNumber] = useState(0);
const handleReduce = () => {
setNumber(prevState => prevState - 1);
}
const handleAdd = () => {
setNumber(prevState => prevState + 1);
}*/
const [number,numberDispatch] = useReducer(reducer,0)
return (
<div>
{/*点击按钮,调用指挥者(dispatch),指挥者再去通知reducer执行对应操作*/}
<button onClick={() => numberDispatch({type:'SUB'})}>-</button>
<span>{number}</span>
<button onClick={() => numberDispatch({type:'ADD'})}>+</button>
</div>
);
};

export default App;


  • reduce通常会定义在组件的外部,避免重复创建(注意,是Reducer创建在外面,不是useReducer创建在外面,否则useReudcer创建在外面会提示React Hook “useReducer” cannot be called at the top level. React Hooks must be called in a React function component or a custom React)
1
2
3
4
5
6
7
8
9
10
11
12
//之前
const App = () => {
const [count,countDispatch] = useReducer(() => {

},0)
}

//修改之后
const reducer = () => { ... }
const App = () => {
const [count,countDispatch] = useReducer(reducer,0)
}

React.memo()

  • 组件被重新渲染有二种情况
    • 第一种: 组件的state状态改变导致重新渲染
    • 第二种: 父组件重新渲染导致子组件重新渲染

针对下面这种情况,我们就可以使用React.memo,可以看到,在没有使用React.memo的时候,App的值被改变,子组件也跟着改变了

  • 有时候这种渲染很不好,比如我子组件什么都没有变化,就是因为父组件变化了,我就要变化,所以为了避免重复渲染导致性能上的问题,我们可以使用React.memo

  • React.memo()是一个高阶函数

    • 高阶函数就是参数是函数或者返回值是函数的函数
  • 经过React.memo()包装过的新组件具有缓存功能

    • 包装过后,只有当组件的props发生变化后才会触发组件的重新渲染,否则总是返回缓存中结果
  • 示例(非严格模式下)

1
2
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
App.jsx

import React, {useState} from 'react';
import A from "./component/A";
const App = () => {
console.log('App被渲染了')
const [number,setNumer] = useState(0);
const handleClick = () => {
setNumer(prevState => prevState + 1);
}
return (
<div>
我是App
<button onClick={handleClick}>点击我+1</button>
{number}
<A/>
</div>
);
};

export default App;


A.jsx
import React from 'react';
import B from "./B";
const A = () => {
console.log('A被渲染了')
return (
<div>
我是A组件
<B/>
</div>
);
};

export default React.memo(A);

B.jsx
import React from 'react';

const B = () => {
console.log('B被渲染了')
return (
<div>
我是B组件
</div>
);
};

export default React.memo(B);

效果:点击+1多少次,子组件都不会被重新渲染

添加React.memo

useCallback

  • 使用React.memo后,的确可以做到缓存的功能,但是如果子组件接收了来自父组件通过props传递的函数,在使用React.memo后会发生什么?(复习下React.memo,发生props变化的时候才会重新渲染,否则就不会重新渲染)
1
2
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
35
36
37
App.jsx

import React, {useState} from 'react';
import A from "./component/A";
const App = () => {
console.log('App被渲染了')
const [number,setNumer] = useState(0);
const handleClick = () => {
setNumer(prevState => prevState + 1);
}
return (
<div>
我是App
<button onClick={handleClick}>点击我+1</button>
{number}
<A add={handleClick}/>
</div>
);
};

export default App;


A.jsx
import React from 'react';
const A = (props) => {
console.log('A被渲染了')
return (
<div>
我是A组件
<button onClick={props.add}>点击我操作App,使其数字+1</button>
</div>
);
};

export default React.memo(A);

效果图,可以看到,即使使用了React.memo,A组件接收了父组件传递过来的值的时候,依旧会因为父组件的改变而导致组件重新渲染(因为props改变了嘛)

  • 上述代码示例情况就是因为A组件的重新渲染导致handleClick被重新创建,导致重复渲染后相加的值并不是1了,所以我们可以使用useCalback来创建react中的回调函数去避免这种情况
  • useCallback是一个钩子函数,用来创建react中的回调函数
  • useCallback创建的回调函数不会在组件重新渲染时重新创建
  • useCallback参数
1
2
3
4
5
6
7
8
参数:
参数1:回调函数
参数2:依赖数组
当依赖数组的变量发生变化时,回调函数才会重新执行
如果不指定依赖数组(也就是第二个参数什么都不写),
回调函数每次被渲染的时候就会被重新执行
一定要将回调函数中使用到的所有变量设置到依赖当中
除了setState,因为这个不会变~
  • 总结

    • 如果想每次渲染都执行,第二个参数什么都不写
    • 如果想只当使用到的变量发生改变重新调用,那么第二个参数以数组的形式传入使用到的变量名(也就是写入依赖项目)
    • 如果想只执行一次(初始化的时候执行),第二个参数传入空数组[]
  • 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React, {useCallback, useState} from 'react';
import A from "./component/A";
const App = () => {
console.log('App被渲染了')
const [number,setNumer] = useState(0);
const handleClick = useCallback(() => {
setNumer(prevState => prevState + 1);
},[])
//const handleClick = () => {
// setNumer(prevState => prevState + 1);
//}
return (
<div>
我是App
<button onClick={handleClick}>点击我+1</button>
{number}
<A add={handleClick}/>
</div>
);
};

export default App;

可以看到,即使App发生了重写渲染,A组件也不会重新渲染,因为App当中的callBack我们设置为了只会在创建的时候执行一次

效果图

(todo)Strapi的使用

使用fetch

  • 这里简单记录下,具体的可以看@mdn-fetch
  • 需要注意的是
    • 也就除了网络故障时和请求被阻止,其他情况都是resolve
    • 所以我们可以通过判断ok来进行相应的操作
1
当接收到一个代表错误的 HTTP 状态码时,从 fetch() 返回的 Promise 不会被标记为 reject,即使响应的 HTTP 状态码是 404 或 500。相反,它会将 Promise 状态标记为 resolve(如果响应的 HTTP 状态码不在 200 - 299 的范围内,则设置 resolve 返回值的 ok 属性为 false),仅当网络故障时或请求被阻止时,才会标记为 reject。
  • 我们输出查看下请求返回的结果
1
2
3
4
5
6
7
fetch('http://localhost:3000/list')
.then(res => {
console.log(res)
})
.catch(error=>{

})

  • 所以可以这样子做,处理数据和错误
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fetch('http://localhost:3000/list1414')
.then(res => {
const {ok} = res;//获取请求结果
if(ok){
//请求成功
return res.json();
}
throw new Error('网络异常')
})
.then(res => {
console.log(res.data
);//根据接口不同而返回的数据不同
})
.catch(error=>{
//error.message获取错误文本信息
console.log(error.message);//输出错误信息
})
  • 示例student没有做任何逻辑修改,所以只列出app.jsx内容
1
2
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
35
36
37
38
39
40
41
42
import React, {useState,useEffect} from 'react';
import StudentList from "./components/StudentList";
import './App.css';
const App = () => {
const [stuData, setStuData] = useState([]);
const [isLoading,setLoading] = useState(false);//是否正在加载
const [isError,setError] = useState(false);//是否有错误
useEffect(() => {
setLoading(true);
setError(false)
fetch('http://localhost:3000/list')
.then(res => {
const {ok} = res;//获取请求结果
setLoading(false);
if(ok){
//请求成功
return res.json();
}
throw new Error('网络异常')
})
.then(res => {
setStuData(res);
})
.catch(error=>{
setError(true)
console.log(error.message);//输出错误信息
})
},[])
return (
<div className="app">
{/*不处于加载的时候就展示列表*/}
{ !isLoading && !isError && <StudentList stus={stuData}/> }
{/*加载状态*/}
{ isLoading && '加载数据中...' }
{/*是否出错*/}
{isError && '数据出错...'}
</div>
);
};

export default App;

  • 当然,你也可以使用async 和 await
    • 注意;useEffect的第一个参数不能是异步的
    • 我现在才知道原来try catch后面还有finally…………
1
2
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
35
36
37
38
39
40
41
42
43
import React, {useState,useEffect} from 'react';
import StudentList from "./components/StudentList";
import './App.css';
const App = () => {
const [stuData, setStuData] = useState([]);
const [isLoading,setLoading] = useState(false);//是否正在加载
const [isError,setError] = useState(false);//是否有错误
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true);
setError(false)
const res = await fetch('http://localhost:3000/list');
if(res.ok){
//请求成功
const data = await res.json();

setStuData(data)
}else{
throw new Error('网络异常')
}
}catch (e){
setError(e)
}finally {
setLoading(false);//结束加载loading
}
}
fetchData();
},[])
return (
<div className="app">
{/*不处于加载的时候就展示列表*/}
{ !isLoading && !isError && <StudentList stus={stuData}/> }
{/*/!*加载状态*!/*/}
{ isLoading && '加载数据中...' }
{/*/!*是否出错*!/*/}
{isError && '数据出错...'}
</div>
);
};

export default App;

自定义钩子(selfHooks)

  • 其实自定义钩子就是一个普通函数,只是他的名字需要使用use开头,(自定义钩子就是对其他钩子的封装 )

redux,RTK,RTKQ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
reduex核心思想
多个state放置在一起去统一管理

Reducer
对于state的所有操作,都保存到同一个函数

Store
仓库对象,无论是订阅还是派发任务,都是通过store
根据reducer创建对应的store

dispatch(Store里面的方法)
dispatch依旧是告诉reducer怎么去处理state数据
怎么理解从Store里面调用dispatch呢?因为store根据reducer创建的,所以想要操作数据,自然也应该是从store触发

getState(),从store获取数据

因为redux是适合所有的js的,所以是发布者,订阅者模式
所以store.subscribe(回调函数),发生变化的时候就会执行回调函数
1
2
3
4
5
6
7
8
9
10
11
12
function reducer(prestate,action){
preState 即将更新前state的值,reducer的返回值将作为state的新值
action 是一个普通的js对象,可以保存操作的信息
type表示操作类型
其他需要传递的参数,也可以在action设置
}

通常存储的是一个对象
const store = Redux.createStore(reducer,初始值);
//也可以在reducer指定初始值
function reducer(preState = 1 ,action) {.....}

redux在html当中的使用

  • 通过点击增加删除来对数字进行操作

1
2
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
35
36
37
38
39
40
41
42
43
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Redux</title>
<script src="https://cdn.bootcdn.net/ajax/libs/redux/4.2.0/redux.min.js"></script>
</head>
<body>
<button onclick="handleClickReduce()">减少</button>
<span id="number">1</span>
<button onclick="handeClickAdd()">增加</button>
<script>
const spanNumber = document.getElementById('number')
// 1.创建reducer
const reducer = (preState,action) => {
const {type} = action;
switch(type){
case 'ADD': return preState + 1 ;
case 'SUB': return preState - 1;
default: return preState;
}
}
// 2.根据reducer创建store
const store = Redux.createStore(reducer,1);
// 4.发生改变的回调
store.subscribe(() => {
console.log('发生改变了',store.getState())
spanNumber.textContent = store.getState();
})
const handleClickReduce = () => {
// 3.store进行改变
store.dispatch({type:'SUB'})
}
const handeClickAdd = () => {
// 3.store进行改变
store.dispatch({type:'ADD'})
}
</script>
</body>
</html>

  • (todo)如果需要指定加任意数,通过dispatch传递即可(reducer第二个action其实就是一个普通的对象)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
```

![](https://dreamos.oss-cn-beijing.aliyuncs.com/gitblog/202212152101002.png)



### redux的一些缺点

* 如果state过于复杂,将会非常难于维护
* 可以通过对state分组进行解决,通过创建多个reducer,然后将其合并为一个
* state每一次操作,都需要对state进行复制,然后再去修改,如果需要修改第三层的值,那岂不是很复杂
* case后边的**常量**维护起来比较麻烦
* 后面二个redux也想到了,所以可以使用`Redux Toolkit(RTK)`,RTK可以完全替换掉redux,RTK包含了redux

### RTK

* 安装全套(别忘记安装`react-redux`)

```bash
# NPM
npm install @reduxjs/toolkit react-redux

# Yarn
yarn add @reduxjs/toolkit react-redux
  • 创建对应的仓库片段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import {createSlice} from "@reduxjs/toolkit";

//1. 创建reducer的切片
//需要一个配置对象作为参数,通过对象的不同属性来指定它的配置
//会自动生成action
const stuSlice = createSlice({
name:'stu',//参与自动生成的action
//state的初始值
initialState: {
name:'动感超人',
},
//操作对应的state方法,通过不同方法操作state值
reducers:{
/*这个state是一个state代理,操作这个会影响到store对应的数据*/
setName(state,action){
state.name = '西瓜超人';//直接影响到state数据
}
}
});

//返回 {type: 'stu/setName', payload: '进去的参数'}
console.log(stuSlice.actions.setName('进去的参数'));

输出结果查看stuSlice

  • 创建reducer并暴露
1
2
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
35
36
37
38
39
40
41
import {createSlice,configureStore} from "@reduxjs/toolkit";

//1. 创建reducer的切片
//需要一个配置对象作为参数,通过对象的不同属性来指定它的配置
//会自动生成action
const stuSlice = createSlice({
name:'stu',//参与自动生成的action
//state的初始值
initialState: {
name:'动感超人',
age:18,
},
//操作对应的state方法,通过不同方法操作state值
reducers:{
/*这个state是一个state代理,操作这个会影响到store对应的数据*/
setName(state,action){
state.name = '西瓜超人';//直接影响到state数据
},
setAge(state,action){

}
}
});

//返回 {type: 'stu/setName', payload: '进去的参数'}
//console.log(stuSlice.actions.setName('进去的参数'));

//暴露所有生成action的函数
export const {setName,setAge} = stuSlice.actions;

//创建仓库并暴露
export default configureStore({
//reducer:stuSlice,
//或者传入一个对象
//key值用于标识不同的reducer
//value则传入对应的reducer,
reducer:{
student:stuSlice.reducer,
}
})

  • 使用redux
    • 传递着使用Provider传递store
    • 接受者
      • 通过useSelectore获取对应的state
      • 通过useDispatch获取派发操作store的对象(也就是dispatch)
      • 通过暴露的函数生成操作类型和操作值的对象(也就是setName的返回值)

src/index.js

1
2
3
4
5
6
7
8
9
10
11
import ReactDOM from "react-dom/client"
import App from "./App"
import {Provider} from "react-redux";
import store from "./store/index";
const root = ReactDOM.createRoot(document.getElementById('root'))

root.render(
<Provider store={store}>
<App/>
</Provider>
)

App.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React from 'react';
import {useSelector,useDispatch} from "react-redux";
import {setName} from "./store";

const App = () => {
const store = useSelector(state => state.student);//获取student的store
const dispatch = useDispatch();//获取派发器对象
const handleClick = () => {
//传递要设置的新名字
dispatch(setName('我是动感超人'+Date.now()))
}
return (
<div>
{store.name}
<button onClick={handleClick}>更改我的名字</button>
</div>
);
};

export default App;

存在的一些缺点和解决办法

  • 如果创建多个切片,并且每一个切片reducers都有setName方法,那么会导致暴露出去的方法重名了
  • 解决办法:不同的切片不同的文件(其实就是reducers分开)

store/index.js

1
2
3
4
5
6
7
8
9
10
11
import {configureStore} from "@reduxjs/toolkit";
import stuReducer from "./stuSlice"
import addressReducer from "./addressSlice"

export default configureStore({
reducer:{
student: stuReducer,
address: addressReducer,
}
})

store/stuSlice.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import {createSlice} from "@reduxjs/toolkit";

const stuSlice = createSlice({
name:'stu',
initialState:{
name:'傻瓜超人',
age:18,
},
reducers:{
setName(state,action){
state.name = action.payload;
},
setAge(state,action){
state.age = action.payload;
}
}
});
export const {setName,setAge} = stuSlice.actions;

export default stuSlice.reducer;

store/addressSlice.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import {createSlice} from "@reduxjs/toolkit"

const addressSlice = createSlice({
name:'address',
initialState:{
address:'高老庄'
},
reducers:{
setAddress(state,action){
state.address = action.payload
}
}
})
export const {setAddress} = addressSlice.actions;
export default addressSlice.reducer;

使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React from 'react';
import {useSelector,useDispatch} from "react-redux";
import {setName,setAge} from "./store/stuSlice";
import {setAddress} from "./store/addressSlice";

const App = () => {
const { student,address } = useSelector(state => state);
const dispatch = useDispatch();
return (
<div>
<span>姓名:{student.name}</span>
<span>年龄:{student.age}</span>
<span>地址:{address.address}</span>
<button onClick={() => dispatch(setName('张三'))}>设置姓名为"张三"</button>
<button onClick={() => dispatch(setAge(888))}>设置年龄为888</button>
<button onClick={() => dispatch(setAddress('地球村'))}>设置地址为"地球村"</button>
</div>
);
};

export default App;

RTKQ(redux toolkit query)

  • RTK不仅帮助我们解决了state的问题,同时,它还为我们提供了RTK Query用来帮助我们处理数据加载的问题。RTK Query是一个强大的数据获取和缓存工具。在它的帮助下,Web应用中的加载变得十分简单,它使我们不再需要自己编写获取数据和缓存数据的逻辑。

  • Web应用中加载数据时需要处理的问题:

    • 根据不同的加载状态显示不同UI组件
    • 减少对相同数据重复发送请求
    • 使用乐观更新,提升用户体验
    • 在用户与UI交互时,管理缓存的生命周期
  • 在之前我们就是通过自己每一次发送请求,就创建对应的状态,比如isLoading,isError来处理加载时候的状态的,有了RTKQ,我们就不需要做这些事情了,我们通过RTKQ来帮我们完成这事情

  • 更多说明和教学可以看看@李立超的RTKQ

第一步:配置RTKQ

store/studentApi.js

1
2
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
35
36
37
38
39
40
import {createApi, fetchBaseQuery} from "@reduxjs/toolkit/dist/query/react";

//创建Api对象
//createApi()用来创建RTKQ中的API对象
const studentApi = createApi({
//Api的reducer标识,不能和其他Api或reducer重复
reducerPath:'studentApi',
//指定查询的基础信息,发送请求使用的工具
//用来设置发送请求的工具,就是你是用什么发请求,RTKQ为我们提供了fetchBaseQuery作为查询工具,它对fetch进行了简单的封装,很方便,如果你不喜欢可以改用其他工具,这里暂时不做讨论。
baseQuery:fetchBaseQuery({
baseUrl:'http://localhost:3000/'
}),
endpoints:(build) => {
//build是请求的构建器,通过build来设置请求相关的信息
return {
//使用构建器来创建请求对象
getStudents:build.query({
query(arg) {
//用来指定请求的子路径
//可以直接返回请求的子路径,默认为get(axios也是默认get方式)
//也可以返回配置对象
return 'students';
},
//可以在这里将请求过来的数据进行转换并将结果返回
transformResponse(baseQueryReturnValue){
return 转换后的结果
}
})
}
}
})

//api对象创建后,对象中会根据各种方法自动的生成对应的钩子函数
//通过这些钩子函数,可以向服务器发送请求
//钩子函数的命名规则 endpoints中有一个getStudents 结合查询方式=>生成 useGetStudentsQuery
export const { useGetStudentsQuery } = studentApi;

//我们还需要使用store
export default studentApi;

第二步:创建store对象

store/index.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import {configureStore} from "@reduxjs/toolkit";
import studentApi from "./studentApi";
export default configureStore({
reducer:{
[studentApi.reducerPath]:studentApi.reducer,
},

middleware:getDefaultMiddleware => {
//getDefaultMiddleware调用后会返回所有的默认中间件
//传入studentApi的缓存生效
return getDefaultMiddleware().concat(studentApi.middleware)
}
})

随后主入口文件src/index.js

1
2
3
4
5
6
7
8
9
10
11
12
import ReactDOM from "react-dom/client"
import App from "./App"
import store from "./store"
import {Provider} from "react-redux";
const root = ReactDOM.createRoot(document.getElementById('root'))

root.render(
<Provider store={store}>
<App/>
</Provider>
)

第三步:使用RTKQ

  • 输出查看调用api中钩子查询的返回值
    • 可以看到,输出了三次,可以简单理解为发送前,发送中,发送完成
    • 每一个阶段都会有具体的字段进行标识,比如isLoading,isFetching,isSuccess,isError
    • 这样子就可以便于我们去操作(至少我们不用自己写什么isLoading,isError了,RTKQ帮我们完成了)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import React from 'react';
import {useGetStudentsQuery} from "./store/studentApi";

const App = () => {
//调用api查询数据
//这个钩子函数会返回一个对象作为返回值,请求过程中相关数据都在该对象中
const obj = useGetStudentsQuery();//调用Api中的钩子查询数据
console.log(obj)
return (
<div>
我是App
</div>
);
};

export default App;

  • 具体使用示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React from 'react';
import {useGetStudentsQuery} from "./store/studentApi";

const App = () => {
//调用api查询数据
//这个钩子函数会返回一个对象作为返回值,请求过程中相关数据都在该对象中
const { isLoading,data,isSuccess} = useGetStudentsQuery();//调用Api中的钩子查询数据
return (
<div>
{ isLoading && '加载中...'}
{isSuccess && data.map(item =>
<p key={item.id}>
姓名:{item.name} <br/>
性别:{item.sex} <br/>
年龄:{item.age} <br/>
地址:{item.address} <br/>
</p>)
}
</div>
);
};

export default App;

效果

数据库的结构

编辑信息使用RTK保持最新数据

  • 在之前编辑学生数据的时候,是通过props来传递数据的,但是如果有一个人修改了数据我们没有更新,就会导致我们编辑的时候使用的是旧数据,用户体验不好
  • 所以我们可以在编辑的时候重新请求服务器,重新查询数据
  • 使用RTKQ关键就是如果传参和如何定义接口
1
2
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
35
36
37
38
39
40
41
42
43
//接口定义
const studentApi = createApi({
// ....
endpoints:(build) => {
//build是请求的构建器,通过build来设置请求相关的信息
return {
getStudents:build.query({
query() {
//用来指定请求的子路径
return 'students';
}
}),
getStudentInfo:build.query({
query(id) {
return `students/${id}`;
}
})
}
}
// ....
})

export const { useGetStudentsQuery,useGetStudentInfoQuery} = studentApi;


//传参-使用的时候传参
//传入id,用作查询的参数
const {data,isSuccess} = useGetStudentInfoQuery(props.id);//异步请求
const [formData,setFormData] = useState({
name: props.name ? props.name : "",
sex: props.sex ? props.sex : "男",
age: props.age ? props.age : "",
address: props.address ? props.address : "",
});
//使用useEffect重新渲染数据当状态改变
//为了避免在渲染中操作state导致无限重复渲染,我们选择在useEffect
useEffect(() => {
if(isSuccess){
setFormData({
...data,
})
}
},[isSuccess])

设置缓存时间和数据转换-query

  • RTKQ是自带缓存的,默认为60秒,
  • 当我们点击修改的时候,会向后台发送请求,此时数据已经是缓存了,我们再次点击修改,数据已经被缓存,所以不会发送请求了,但是如果有人修改了数据,受到缓存的限制,不会重新请求,所以需要我们可以设置缓存时间keepUnusedDataFor,默认是60秒
  • 每一个接口查询都可以设置缓存时间,默认为60(单位秒)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
const studentApi = createApi({
// ....
endpoints:(build) => {
//build是请求的构建器,通过build来设置请求相关的信息
return {
getStudents:build.query({
query() {
//用来指定请求的子路径
return 'students';
}
}),
getStudentInfo:build.query({
query(id) {
return `students/${id}`;
},
//设置缓存时间为5秒
keepUnusedDataFor:5,
//加工处理请求过来的数据
transformResponse(baseQueryReturnValue){
return 转换后的结果
}
})
}
}
// ....
})

useQuery的返回值查看

先看图,输出useQuery的返回值

  • refetch函数

    • 一个函数,用来重新加载数据
  • status:string - 请求的状态

    • pedding:数据正在加载
    • fulfilled:数据加载完成
  • isFetching:boolean - 数据是否正在加载,不管第几次加载,就会设置其状态

  • isLoading:boolean - 表示数据是否第一次加载,通过调用refetch不会改变此状态

  • isSuccess:boolean - 请求是否成功

  • isUninitialized:boolean -请求是否还没有开始发送,常用在删除操作的请求上

  • error:对象 有错才存在对象,无错误就没有这个

  • data:最新返回的数据(在重新发送请求的时候,会保存上一次请求返回的数据)

  • currentData:当前参数的最新数据,(当参数变化的时候,会清空(变为undefined))

    • 比如在一个列表搜索想要的汉堡名称,搜索的时候我如果想清空原来的数据并展示等待图标,数据加载完成后才渲染返回的数据,就可以使用currentData,如果不想清空原来的数据,而是等到数据返回后替换原来的数据,就可以使用data

useQuery设置参数

  • useQuery第一个参数可以成为每一个请求的query函数传入的参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//比如传入id
import {useGetStudentInfoQuery} from "../store/studentApi";
const {data,isSuccess} = useGetStudentInfoQuery(id);//异步请求

//则我们可以在api当中接收
const studentApi = createApi({
//...
endpoints:(build) => {
getStudentInfo:build.query({
query(id){
return `students/${id}`
}
j
})
}
//...
})
  • useQuery第二个参数可以传入对象通过该对象可以对请求进行配置

    • 创建的时候传入的对api的配置可以说默认配置
    • 使用的时候传入的第二个参数配置可以更加具有定制化特点,和默认配置发生冲突的时候以这个为准
  • selectFromResult函数,返回值作为useQuery返回值

    • 比如我可以设置data的数据,对data数据进行过滤
    1
    2
    3
    4
    5
    6
    7
    8
    const res = useGetStudentsQuery(null,{
    selectFromResult: (result) => {
    if(result.data){
    result.data = result.data.filter(item => item.age < 18)
    }
    return result;
    }
    });
  • pollingInterval默认为0,设置轮询的间隔(隔一段时间发送请求),单位毫秒,为0表示不轮询

  • skip设置是否跳过当前请求,默认false

    • 比如一个组件既有编辑功能,也有添加功能,那么我们需要在添加功能的时候跳过请求,否则就添加的时候就会向后台请求初始化数据
    1
    2
    3
    4
    //当有id的时候,才请求数据,否则不请求
    const {data,isSuccess} = useGetStudentInfoQuery(props.id,{
    skip:!props.id,
    });//异步请求
  • refetchOnMountOrArgChange:默认false 设置是否每次都重新加载数据(也就是设置是否不使用缓存)也可以设置为数字,为数字的话就是设置缓存有效期

  • refetchOnFocus:默认false, 是否在重新获取焦点时重载数据(比如切换页面)

    • 如果设置为true,需要在store当中设置setupListeners(store.dispatch)
    1
    import {setupListeners} from "@reduxjs/toolkit/query"
  • refetchOnReConnect:默认false, 是否在重新连接后重载数据(没网了,重新连接了网)

RTKQ构造器构造API请求

  • 如果发送的不是get信息(当然,get也可以设置配置对象),我们就不可能像之前写query一样了
1
2
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
35
36
37
38
39
40
41
// 之前的写法
const studentAPi = createApi({
//...

endpoints:(build) => {
return {
getStudent:build.query({
query(){
return 'students'
}
})
}
}


//...
})

//如果现在是put或者delete或者post,就需要返回配置对象的形式了
//并且更改构建器为mutation
const studentApi = createApi({
//...

endpoints:(build) => {
return {
delStudent:build.mutation({
query(参数){
return {
url:'students/${参数}',
method:'delete',
//如果有参数,则需要添加body
body:传递的数据
}
}
})
}

}

//...
})
  • 删除的调用和查询的调用不太一样,因为删除不是页面加载后就立马删除,而是用户确认删除后才执行删除操作,添加的操作也是如此,修改的也是
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//引入删除的接口
import {useDelStudentInfoMutation} from "../store/studentApi";
//输出查看内容
const a = useDelStudentInfoMutation();

//打印的内容
[f,{isError,isLoading,isSuccess,isUninitialized,originalArgs,reset,status}];

//第一个为触发器,第二个是结果集

//所以具体中我们可以获取触发器,在需要的时候调用就会执行函数
//调用的结果和状态和可以从结果集获取
const [delStudent,{isSuccess}] = useDelStudentInfoMutation();


//点击删除学生,正常的操作应该要询问用户是否删除的
const handleClick = useCallback(async (id) => {
delStudent(id);
})

jsx的具体操作添加,修改

1
2
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
const [formData,setFormData] = useState({
name: props.name ? props.name : "",
sex: props.sex ? props.sex : "男",
age: props.age ? props.age : "",
address: props.address ? props.address : "",
});
//修改数据
const [editStudentInfo,{isSuccess:editSuccess}] = useEditStudentInfoMutation();
//添加数据
const [addStudent] = useAddStudentInfoMutation();

/*添加回调确认*/
const handleAdd = useCallback(async () => {
//执行添加数据
addStudent({
...formData,
});
//清空数据
setFormData({
name: "",
sex: "男",
age: "",
address: "",
})
});
/*修改回调确认*/
const handleEditConfirm = useCallback(async () => {
//执行修改数据
editStudentInfo({
id:props.id,
info:formData,
});
},)

studentApi.js的query定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//修改数据
editStudentInfo:build.mutation({
query(newInfo) {
return {
url:`students/${newInfo.id}`,
method:'put',
body:newInfo.info,
}
}
}),
//添加数据
addStudentInfo:build.mutation({
query(info){
return {
url:'students',
method:'post',
body:info,
}
}
})

RTKQ的数据标签

  1. 首先我们需要创建标签
1
2
3
4
5
6
7
8
9
const studentApi = createApi({
reducerPath:'xxxxx',
//用来指定API中的标签类型
tagTypes:['student'],

endpoints:(buidl) => {
//....
}
})
  1. 给请求的数据添加标签

    • 给API切片的钩子函数添加providesTags属性,属性值可以为数组字符串,数组对象,回调函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const studentApi = createApi({
    tagTypes:['student'],
    endpoints:(build) => {
    return {
    xxxxx:build.query({
    query(){
    return xxxx
    },
    providesTags:xxxxx
    })
    }
    }
    })
    • providesTags为数组字符串的时候
      • 等同于数组对象的简写
      • 只要type和invalidates对应,就会重新请求数据
    1
    2
    3
    providesTags:['student'];
    //等同于
    providesTags:[{type:'student'}]
    • providesTags为数组对象的时候
      • 所有数据(type和id)都需要和invalidates对应才会重新请求数据
      • id如果是字符串数字,如果有对应的id,依旧会失效
    1
    2
    providesTags:[{type:'student',id:100}];
    providesTags:[{type:'student',id:'100'}];
    • providesTags为回调函数的时候
    1
    2
    3
    4
    5
    6
    7
    8
    当providesTags属性的值为回调函数时,可以对标签的生效范围做更细致的划分
    参数1:网络请求的返回结果
      参数2:错误信息
      参数3:钩子函数中传入的实参
      //返回值为一个数组,符合数组元素条件的数据将生效
    providesTags:(result,error,params,meta) => {
    return [{type:'student',id:params.id}]
    }
  2. 给请求的数据设置要失效的标签

    • 通过invalidatesTags进行设置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    const studentApi = createApi({
    tagTypes:['student'],
    endpoints:(build) => {
    return {
    delStudent:build.mutation({
    query(id){
    return {
    url:xxxx,
    method:'delete',
    }
    invalidatesTags:xxxx
    }
    })
    }
    }
    })
    • invalidatesTags为数组字符串时候
      • 只要providesTags当中type包含在invalidatesTags,就会重新请求数据
      • 数组字符串的写法等同于数组对象的简写
    1
    2
    3
    4
    5
    6
    7
    8
    9
    invalidatesTags:['student'];

    providesTags:['student'];//让其失效
    providesTags:[{type:'student'}];//让其失效
    providesTags:[{type:'student',id:100}];//让其失效

    invalidatesTags:['student'];
    等同于,二个效果是一样的
    invalidatesTags:[{type:'student'}];
    • invalidatesTags为数组对象的时候
      • 指明id则让type和id二者都对应的标签失效
      • 未指明就和数组字符串失效规则一样
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    invalidates:[{type:'student',id:10}];

    providesTags:['student'];//不会失效
    providesTags:[{type:'student'}];//不会失效
    providesTags:[{type:'student',id:10}];//会失效

    //实测会
    providesTags:[{type:'student',id:'10'}];//会失效

    providesTags:[{type:'student',id:888}];//不会失效
    • invalidatesTags为回调函数的时候
    1
    2
    3
    4
    5
    6
    7
    invalidatesTags:(result,error,stu,meta) => 
    {
    return [
    {type:'student',id:stu.id},
    {type:'student',id:'LIST'}
    ]
    }
  • 示例,老师的例子
    • 当添加数据后,会刷新列表
    • 当编辑后,会刷新列表,但是如果数据没有变动的话,点击编辑就不会重新查询(存在缓存的前提下)
1
2
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import {createApi, fetchBaseQuery} from "@reduxjs/toolkit/dist/query/react";

//创建Api对象
//createApi()用来创建RTKQ中的API对象
const studentApi = createApi({
reducerPath:'studentApi',//Api的标识,不能和其他Api或reducer重复
//指定查询的基础信息,发送请求使用的工具
baseQuery:fetchBaseQuery({
baseUrl:'http://localhost:3000/'
}),
//用来指定api当中的标签
tagTypes:['student'],
endpoints:(build) => {
//build是请求的构建器,通过build来设置请求相关的信息
return {
getStudents:build.query({
query() {
//用来指定请求的子路径
return 'students';
},
providesTags:[{type:'student',id:'initList'}]
}),
//编辑的时候获取信息
getStudentInfo:build.query({
query(id) {
return `students/${id}`;
},
providesTags:(result, error, arg, meta) => {
return [
{type:'student',id:arg},
]
},
////设置缓存时间为5秒
//keepUnusedDataFor:5,
}),
//删除数据
delStudentInfo:build.mutation({
query(id){
return {
url:`students/${id}`,
method:'delete',
}
},
invalidatesTags:[{type:'student',id:'initList'}]
}),
//修改数据
editStudentInfo:build.mutation({
query(newInfo) {
return {
url:`students/${newInfo.id}`,
method:'put',
body:newInfo.info,
}
},
//修改数据的时候,只针对修改过的列表进行重新查询数据,未改变的不变动
invalidatesTags:(result, error, arg, meta) => {
return [
{type:'student',id:'initList'},
{type:'student',id:arg.id},
]
}
}),
//添加数据
addStudentInfo:build.mutation({
query(info){
return {
url:'students',
method:'post',
body:info,
}
},
//添加数据的时候重新刷新列表
invalidatesTags:[{type:'student',id:'initList'}]
})
}
}
})

//api对象创建后,对象中会根据各种方法自动的生成对应的钩子函数
//通过这些钩子函数,可以向服务器发送请求
//钩子函数的命名规则 endpoints中有一个getStudents 结合查询方式=>生成 useGetStudentsQuery
export const { useGetStudentsQuery,
useGetStudentInfoQuery,
useDelStudentInfoMutation,
useAddStudentInfoMutation,
useEditStudentInfoMutation,
} = studentApi;

//我们还需要使用store
export default studentApi;

RTKQ使用axios

  • 其实使用很简单,就是axios的二次封装
  1. 安装
1
npm install axios
  1. 更改
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import {createApi} from "@reduxjs/toolkit/dist/query/react";
import axios from "axios";

const studentApi = createApi({
reducerPath:'studentApi',//Api的标识,不能和其他Api或reducer重复
//指定查询的基础信息,发送请求使用的工具
//baseQuery:fetchBaseQuery({
// baseUrl:'http://localhost:3000/'
//}),
//更改为axios
baseQuery:axios.create({
baseURL:'http://localhost:3000/'
}),
})
  1. 请求体变更

    • 比如post或者put等传参需要使用data,而不是body了
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    //修改数据
    editStudentInfo:build.mutation({
    query(newInfo) {
    return {
    url:`students/${newInfo.id}`,
    method:'put',
    //之前fetch的时候
    //body:newInfo.info
    data:newInfo.info,
    }
    },
    //修改数据的时候,只针对修改过的列表进行重新查询数据,未改变的不变动
    invalidatesTags:(result, error, arg, meta) => {
    return [
    {type:'student',id:'initList'},
    {type:'student',id:arg.id},
    ]
    }
    }),

react-router-dom@5

  • 安装
1
yarn add react-router-dom@5
  • 使用
    • 技巧 ,可以为路由模式起别名,当我们更改的时候,就不需要去组件更改了

index.js主入口

1
2
3
4
5
6
7
8
9
10
import ReactDOM from "react-dom/client"
import App from "./App"
import {BrowserRouter as Router} from "react-router-dom";
const root = ReactDOM.createRoot(document.getElementById('root'))

root.render(
<Router>
<App/>
</Router>
)

App.jsx

  • 使用Route对路径进行注册
1
2
3
4
5
6
7
8
9
10
11
12
13

const App = () => {
return (
<div>
<Link to="/">主页</span>
<Link to="/about">关于</span>
<Route path={'/'} component={Home}></Route>
<Route path={'/about'} component={About}></Route>
</div>
);
};

export default App;
  • 注意
    • 默认情况下Route并不是严格匹配,只要url地址的头部和path一致,组件就会挂载,不会检查子路径(也就是说url地址中包含了path,就会挂载组件)
  • 所以上述代码会有问题,当我们进入/about的时候,即会加载Home组件和About,因为使用的是模糊匹配,所以我们可以使用精准匹配
1
2
<Route exact path={'/'} component={Home}></Route>
<Route exact path={'/about'} component={About}></Route>
  • 特殊版本的Link,可以根据不同的情况设置不同的样式。

  • 属性:

    1. activeClassName —— 字符串 链接激活时的class
    2. activeStyle —— 对象 链接激活时的样式
    3. isActive —— 函数,可动态判断链接是否激活
    4. style —— 函数,动态设置样式
    5. className —— 函数,动态设置class值

Route的传递方式

  • 方法1 — 通过component
1
2
import Home from "./Home";
<Route path="/student/:id" component={Home}/>
  • 方法2-通过render
    • render需要传入一个回调函数,回调函数有一个routeProps参数,返回值为要渲染的组件
1
2
3
4
5
6
7
import Home from "./Home";

//这样子就可以传递数据了,但是不会自动传递match,location,history了
<Route path="/student/:id" render = { () => <Home/> } />

//如果需要match.location,history
<Route path="/student/:id" render={(routeProps) => <Home {...routeProps}/>} />
  • 方法3-通过children来指定被挂载的组件

    • 用法1:children设置一个回调函数时候和render类似,写法一样可以说,当,该组件无论路径是否匹配都会挂载
    1
    2
    3
    4
    5
    <Route path="/student/:id" children={(routeProps) => <Home {...routeProps}/>}/> 

    //浏览器
    访问/ 加载home
    访问/abc 加载home
    • 用法2:传入一个jsx,路径匹配才加载

      • 当是无法传入match,location,history;但是可以用钩子函数解决
      1
      2
      3
      4
      5
      const match = useRouteMatch()
      const location = useLocation();
      const history = useHistory();

      const params = useParmas();//获取params参数
    1
    <Route path="/student/:id" children={ <Home}/>}/> 
  • 方法4-通过prop.children来

    • 依据是当路径不匹配依旧会挂载
1
2
3
4
5
6
7
8
9
10
<Route path="/student/:id">
<Home/>
</Route>

//如果需要match location history
<Route path="/student/:id">
{
routeProps => <Home {...routeProps}/>
}
</Route>
  • 传递的三大属性
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
match:{
isExact 检查路径是否完全匹配
params: {} (默认空对象)请求的参数
path:设置的path属性 比如/student /student/:id
url: 真实的路径比如/student /student/4
} 匹配的信息

location: {
has:
key:请求的id
search:(默认undefined)查询的字符串比如/student?name=admin
state:(默认undefined)

}地址信息

history: {
go自由跳转(方法)
goBack向后条(方法)
goForwar向前跳(方法)
push:历史记录添加一条新页面并跳转(方法)
replace:替换记录并跳转(方法),跳转可以传递state
replace({pathname:'/student/2,state:{name:李白},)
}控制页面的跳转

路由的嵌套

1
2
3
4
5
6
<Route path="/about">
<About/>
<Route path="/about/hello">
<Hello/>
</Route>
</Route>

Prompt组件

  • 跳转确认
  • 比如表单输入了,然后用户想跳转到另外一个页面,此时我们就可以使用Prompt组件,询问用户是否跳转
  • message属性设置提示信息
  • when属性当为true的时候才会进行循环,默认值是true
  • 下面示例,当input有值的时候,就会进行询问用户是否进行跳转
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React, {useState} from 'react';
import {Prompt} from "react-router-dom"
const MyForm = () => {
const [value,setValue] = useState('');
const [isPrompt,setPrompt] = useState(false);
const handleOnChange = (e) => {
setValue(e.target.value);
setPrompt(!!e.target.value.trim().length);
}
return (
<div>
<Prompt message={'确定离开当前页面吗'} when={isPrompt}/>
<p>form组件</p>
<input type='text' value={value} onChange={handleOnChange}/>
</div>
);
};
export default MyForm;

Redirect

  • 重定向功能

  • 属性

    • to
    • from
    • replace(默认替换方式为replace)
    • push
    1
    2
    //当访问abc的时候,自动重定向到form
    <Redirect from = "/abc" to="/form"/>
  • 如果有Switch,可以实现重定向功能

1
2
3
4
5
6
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
//当上面的都匹配不到的时候,就会匹配Redirect组件当中的to值
<Redirect to="/home"/>
</Switch>

react-router-dom@6

  • 在react路由@6版本当中,我们必须要在所有的Route外面包一层Routes,作用和Switch类型,Routes中的Route只有一个会被匹配

  • Route不再使用component属性,而是element属性

  • NavLink

  • useParams,useLocation没有变化

  • useMatch检查当前url是否匹配某个路由(注意,是路由,不是路径,比如路由是/student/:id,而不是/student/1)

    • 匹配:返回一个对象
    • 不匹配返回null
  • useHistory删除,使用useNavigate代替,获取用于跳转页面的函数

    • const nav = useNavigate();nav('/home')跳转到/home默认push方法
    • nav('/about',{replace:true}),使用replace跳转
  • Navigate标签添加

获取params

  • useParams可以获取params参数
    • 比如在路由设置了/home/:id,当我们访问/home/100那么就可以通过useParams获取id信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import React from 'react';
import {useParams} from "react-router-dom";

const STATIC_DATA = [
{id:1,name:'傻瓜超人'},
{id:2,name:'西瓜超人'},
{id:3,name:'动感超人'},
{id:4,name:'酸梅超人'},
]
const About = () => {
const {id} = useParams();
//不要忘记转化为了数字
const findData = STATIC_DATA.find(item => item.id === id*1)
console.log(findData)
return (
<div>
<h2>超人的类型</h2>
<h3>{findData.id} --- {findData.name}</h3>
</div>
);
};

export default About;

useNavigate(钩子)

  • 可以获取一个用于页面跳转的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React from 'react';
import {useNavigate} from "react-router-dom"
const Home = () => {
const router = useNavigate()
const handleClick = () => {
//使用replace模式(默认不设置是push模式)
router('/about/1',{
replace:true,
})
}
return (
<div>
我是精彩的主页
<button onClick={handleClick}>跳转到about页面</button>
</div>
);
};

export default Home;

路由的嵌套和Outlt占位

1
2
3
4
5
6
<Routes>
<Route path={'/home'} element={<Home/>}>
<Route path={'page'} element={<HomePage/>}/>
</Route>
<Route path={'/about/:id'} element={<About/>}> </Route>
</Routes>

Home.jsx使用Outlet占位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import React from 'react';
import {useNavigate,Outlet} from "react-router-dom"
const Home = () => {
const router = useNavigate()
const handleClick = () => {
//使用replace模式(默认不设置是push模式)
router('/about/1',{
replace:true,
})
}
return (
<div>
我是精彩的主页
<button onClick={handleClick}>跳转到about页面</button>
<Outlet/>
</div>
);
};

export default Home;

  • Outlet表示嵌套路由的组件,当嵌套路由中的路由匹配成功了,Outlet则表示嵌套路由的组件,没有匹配成功的话,Outlet就什么都不是
  • Navigate只要被渲染出来,就会引起视图的切换

  • replace属性用于控制跳转模式(push 或 replace,默认是push)

  • className的方式
1
2
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
35
//css内容
.active_style{
background-color: red;
}


//jsx内容
import React from 'react';
import {Link,NavLink} from "react-router-dom";
import "./test.css";
const Menu = () => {
const activeStyle = ({isActive}) => {
return isActive ? 'active_style' : null;
}
return (
<div>
<Link to={'/home'}>Home页面</Link>
{/*<NavLink to='/home/page' className={activeStyle}>Home页面下的page</NavLink>*/}
{/*或者*/}
<NavLink to={'/home/page'} className={
({isActive}) => {
return isActive ? 'active_style' : null;
}
}>Home页面下的page</NavLink>
<Link to={'/about'}>关于页面</Link>
</div>
);
};

export default Menu;

//激活的时候HTML状态
<a href="/home/page" aria-current="page" class="active_style">Home页面下的page</a>
//未激活的时候HTML状态
<a href="/home/page">Home页面下的page</a>
  • style的方式
1
2
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
import React from 'react';
import {Link,NavLink} from "react-router-dom";
const Menu = () => {
const activeStyle = ({isActive}) => {
return isActive ? {backgroundColor:'red'} : null;
}
return (
<div>
<Link to={'/home'}>Home页面</Link>
{/*<NavLink to='/home/page' style={*/}
{/* ({isActive}) => {*/}
{/* return isActive ? {backgroundColor:'red'} : null*/}
{/* }*/}
{/*}>Home页面下的page</NavLink>*/}
<NavLink to='/home/page' style={activeStyle}>Home页面下的page</NavLink>
<Link to={'/about'}>关于页面</Link>
</div>
);
};

export default Menu;

//激活的时候HTML状态
<a aria-current="page" class="active" href="/home/page" style="background-color: red;">Home页面下的page</a>

//未激活的时候HTML状态
<a class="" href="/home/page" style="">Home页面下的page</a>

其他钩子

useMemo

  • 可以缓存一切,类似于useEffect,useCallback
  • useCallback用来缓存函数对象,useMemo用来缓存函数的执行结果

React.forwardRef和useImperativeHandle

  • React.forwardRef可以暴露ref对象给外部(比如可以在A组件通过ref来操作B组件当中的input输入框(完整的一个DOM))

  • 而useImplerativeHandle可以指定暴露的ref对象给外部(比如A组件可以通过ref来操作B组件当中的input输入框的值(只可以操作这个输入框的值))

  • 我们先来看看,如果直接给一个自定义组件绑定ref会发生什么

    • 结果很明显,React提示Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?,同时,输出ref也无法获取到值多个dom对象,react也不知道要给你谁
    • 因为无法直接去获取react组件的dom对象,因为一个react组件可以含有

App.jsx组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import React,{useState,useRef} from 'react';
import Some from "./component/Some";
const App = () => {
const [count,setCount] = useState(0);
const someRef = useRef();
const handleShow = () => {
console.log(someRef.current)
}
return (
<div>
<p>总数:{count}</p>
<button onClick={() => setCount(prevState => prevState + 1)}>点击加1</button>
<button onClick={handleShow}>查看Some的ref的值</button>
<Some ref={someRef}/>
</div>
);
};

export default App;

Some.jsx组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React,{useRef} from 'react';

const Some = () => {
const inputRef = useRef();
const handleBtn = () => {
console.log(inputRef.current.value);
}
return (
<div>
我是Some组件
<input ref={inputRef}/>
<button onClick={handleBtn}>获取input的值</button>
</div>
);
};

export default Some;

  • 所以我们需要使用React.forwardRef在自定义组件Some当中

Some.jsx组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React,{useRef,forwardRef} from 'react';

const Some = forwardRef((props,ref) => {
const inputRef = useRef();
const handleBtn = () => {
console.log(inputRef.current.value);
}
return (
<div>
<p ref={ref}>我是Some组件</p>
<input ref={inputRef}/>
<button onClick={handleBtn}>获取input的值</button>
</div>
);
})

export default Some;

App.jsx组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React,{useState,useRef} from 'react';
import Some from "./component/Some";
const App = () => {
const [count,setCount] = useState(0);
const someRef = useRef();
const handleShow = () => {
console.log(someRef)
}
return (
<div>
<p>总数:{count}</p>
<button onClick={() => setCount(prevState => prevState + 1)}>点击加1</button>
<button onClick={handleShow}>查看Some的ref的值</button>
<Some ref={someRef}/>
</div>
);
};

export default App;

  • 但是这样子使用forwardRef很不安全,因为你在外部组件直接可以操控这个Some组件的input,完完全全拿到了input,相当于你为了量一块钻石的尺寸,把这个钻石送给了比如,所以我们可以使用useImperativeHandle,只把”钻石”的一些属性告诉他

App.jsx

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import React,{useState,useRef} from 'react';
import Some from "./component/Some";
const App = () => {
const [count,setCount] = useState(0);
const someRef = useRef();
const handleShow = () => {
const temp = count + 1;
setCount(temp);
/*设置Some组件当中的p标签的内容*/
someRef.current.setContext(temp)
}
return (
<div>
<p>总数:{count}</p>
<button onClick={handleShow}>点击加1并设置Some组件P的值</button>
<Some ref={someRef}/>
</div>
);
};

export default App;

Some.jsx

1
2
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
import React,{useRef,forwardRef,useImperativeHandle} from 'react';

const Some = forwardRef((props,ref) => {
const inputRef = useRef();
const pRef = useRef()
const handleBtn = () => {
console.log(inputRef.current.value);
}
useImperativeHandle(ref,() => {
/*返回值将作为App.jsx组件获取到的ref的值*/
return {
setContext(content){
pRef.current.textContent = content;
}
}
})
return (
<div>
<p ref={pRef}>我是Some组件</p>
<input ref={inputRef}/>
<button onClick={handleBtn}>获取input的值</button>
</div>
);
})

export default Some;

效果图

useEffect和useInsertionEffect和useLayoutEffect

  • 执行顺序从先到后useInsertionEffect > useLayoutEffect > useEffect
  • 当使用useEffect会出现闪的情况的时候,再考虑用其他二个

useDeferredValue

  • 当我们多个组件使用同一个state时,组件有可能会互相影响,一个组件卡顿,会导致所有组件都卡

碎笔记

  • 组件首字母必须要大写

  • react想要取消默认行为,可以通过事件对象来,比如event.preventDefault()取消默认行为,event.stopPropagation()取消事件冒泡

  • props是只读的,不能修改props属性

  • toLocaleString()

    • 没想到这么多可以使用这个方法

  • rsc-函数组件不带props

  • rsi函数组件带props

  • rcc-类组件

  • props.children表示函数的标签体

  • React中的钩子函数只能在函数组件或在自定义钩子中调用(是直接在函数组件使用

1
2
3
4
5
function App() {
function fn(){
usexxxxx,//错误,这不叫函数组件调用,而是在函数组件的内部函数调用了,错误
}
}
  • 在类中直接定义的箭头函数,this永远都指向实例对象
1
2
3
4
5
6
7
8
9
10
11
class MyClass {
fn = () => {
//在类中直接定义的箭头函数,this永远都指向实例对象
}

fn2(){
const fn3 = () => {
//这都不是在类中直接定义的箭头函数了,肯定不是指向实例对象
}
}
}
  • 为什么路由的时候不能使用超链接来实现路由的跳转?
    • 会导致向服务器发送请求重新加载页面,并且如果服务器没有做跳转操作下,在BrowserRouter模式下会发生404的情况通过链接跳转的方式(因为这个请求没有经过react-router进行处理),所以为了避免这种情况有二种解决办法
      1. 使用HashRouter 服务器不会处理#后面的东东,我们请求localhost/#/about,服务器只会处理localhost,不会处理/about
      2. 如果依旧需要使用BrowserRouter, 就需要修改服务器的配置,将所有请求都转发到index.html
1
2
3
//错误写法
<a href="/">跳转到主页</a>
<a href="/about">跳转到关于页</a>
  • fetchBaseQuery设置请求头并读取state的值
1
2
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
const studentApi = createApi({
// ...
baseQuery: fetchBaseQuery({
baseUrl: "http://localhost:1337/api",
prepareHeaders: (headers,abc) => {
console.log(abc);
//输出
endpoint: "getStudents"
extra: undefined
forced: false
getState: ƒ a()
type: "query"
return headers;
}
})
// ...
});


const studentApi = createApi({
// ...
baseQuery: fetchBaseQuery({
baseUrl: "http://localhost:1337/api",
prepareHeaders: (headers, {getState}) => {
const token = getState().auth.token;
return headers;
}
}),
// ...
});
  • 钩子只能在React组件和自定义钩子中使用
    • 钩子不能再嵌套函数或其他语句(if,switch,for)等中使用
文章作者: 梦洁
文章链接: https://www.dreamlove.top/316d79f9.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

评论
avatar
梦洁
小小的字,有大大的梦想~分享我的前端学习过程,经历,错误,和一些其他折腾过程
关注下我(* ̄▽ ̄*)
公告
不断更新中,有问题请留言回复(会通过邮箱提醒~)
目录
  1. 1. 写法的变更
  2. 2. Card封装为UI
  3. 3. 表单的双向绑定
  4. 4. 数据为true的时候才会显示对话框
  5. 5. 解决对话框层级问题
  6. 6. 添加过滤功能
  7. 7. create-react-app
  8. 8. 内联样式和行内样式
  9. 9. 模块化css(避免全局污染 )
  10. 10. 视口的设置
  11. 11. Context
  12. 12. 处理字体大小
  13. 13. public/img和src/asset
  14. 14. 避免事件冒泡
  15. 15. 遮罩层
  16. 16. 项目很多情况出现冒泡
  17. 17. 清空购物车后数量没有变化
  18. 18. useEffect和React严格模式和setState执行流程
    1. 18.1. React.StrictMode
    2. 18.2. 函数组件setState(或者是setXXXXX)执行流程
      1. 18.2.1. 示例(非严格模式下)
      2. 18.2.2. 还有这个示例也可以看看,setTimeout是异步的,执行的时候也是处于非渲染阶段,所以这个时候就不会导致重复调用从而产生Too many re-renders的报错
    3. 18.3. useEffect
    4. 18.4. 通过useEffct改造input
    5. 18.5. 疑问
  19. 19. useReducer
  20. 20. React.memo()
  21. 21. useCallback
  22. 22. (todo)Strapi的使用
  23. 23. 使用fetch
  24. 24. 自定义钩子(selfHooks)
  25. 25. redux,RTK,RTKQ
    1. 25.1. redux在html当中的使用
      1. 25.1.1. 存在的一些缺点和解决办法
    2. 25.2. RTKQ(redux toolkit query)
      1. 25.2.1. 第一步:配置RTKQ
      2. 25.2.2. 第二步:创建store对象
      3. 25.2.3. 第三步:使用RTKQ
    3. 25.3. 编辑信息使用RTK保持最新数据
      1. 25.3.1. 设置缓存时间和数据转换-query
    4. 25.4. useQuery的返回值查看
    5. 25.5. useQuery设置参数
    6. 25.6. RTKQ构造器构造API请求
    7. 25.7. RTKQ的数据标签
    8. 25.8. RTKQ使用axios
  26. 26. react-router-dom@5
    1. 26.1. NavLink
    2. 26.2. Route的传递方式
    3. 26.3. 路由的嵌套
    4. 26.4. Prompt组件
    5. 26.5. Redirect
  27. 27. react-router-dom@6
    1. 27.1. 获取params
    2. 27.2. useNavigate(钩子)
    3. 27.3. 路由的嵌套和Outlt占位
    4. 27.4. Navigate组件
    5. 27.5. NavLink
  28. 28. 其他钩子
    1. 28.1. useMemo
    2. 28.2. React.forwardRef和useImperativeHandle
    3. 28.3. useEffect和useInsertionEffect和useLayoutEffect
    4. 28.4. useDeferredValue
  29. 29. 碎笔记
最新文章
\ No newline at end of file diff --git a/3183a5d5.html b/3183a5d5.html new file mode 100644 index 000000000..96a82561c --- /dev/null +++ b/3183a5d5.html @@ -0,0 +1 @@ +微信小程序项目之网易云音乐,云音乐 | 梦洁小站-属于你我的小天地

微信小程序项目之网易云音乐,云音乐

微信小程序之网易云音乐的实现-云音乐

基本介绍

  • 基本功能都实现了,音乐的上一首下一首播放等,顺便把进度条的拖动播放写了下

  • 主页的每日推荐界面写了 ,登录通过账号密码登录(你也可以自己加一个验证码或者邮箱登录)

  • 基于iPhone6(375*667分辨率)开发,后面一些计算都是通过获取当前屏幕下的尺寸来计算的,但是不保证在其他分辨率下是否完好(我已经尽力了~)

  • 下载地址

  • 遇到的问题和解决办法

  • API地址(自己服务器的~)

  • 更改自己的API地址

    • 虽然目前我这服务器的可以用,但是服务器会有过期的一天

    • 项目使用 @网易云音乐 NodeJS 版 API

    • 可以去github地址下载后台,然后安装依赖包并修改/utils/config.js文件

    • 如果是自己电脑搭建API服务器,将host配置项修改为如下,因为此API服务器默认使用3000端口(但需要注意的是,如果自己电脑不是公网ip,在手机上调试可能需要内网穿透)

      1
      2
      3
      export default {
      host: 'http://localhost:3000',
      }

修改如下

云音乐网易音乐发布订阅添加订阅关系图

项目预览

首页

首页

视频

搜索界面

搜索界面

每日推荐

每日推荐

个人中心

个人中心

登录界面

登录界面

播放音乐界面

  • 动态图

  • 静态图

静态图1

静态图2

文章作者: 梦洁
文章链接: https://www.dreamlove.top/3183a5d5.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

评论
\ No newline at end of file diff --git a/3388f24c.html b/3388f24c.html new file mode 100644 index 000000000..20253744c --- /dev/null +++ b/3388f24c.html @@ -0,0 +1 @@ +记录下ant design vue tab和pagination(分页器)使用导致分页器total和其他不正常的问题 | 梦洁小站-属于你我的小天地

记录下ant design vue tab和pagination(分页器)使用导致分页器total和其他不正常的问题

问题

  • 使用ant design vue 的tab组件,每一个tab组件都有一个分页器pagination对象
  • 可以看到,我设置了默认tab项目为图片,这里的分页器按照了我要求显示

正常情况

  • 但是我一切换到其他tab,其他tab页面分页器显示就不正常

不正常的分页显示

分析

  • 可以看到ant design vue实现tab的原理就是v-if

  • 当没有轮到我们选择的tab项的时候,就不渲染,完全隐藏了(v-if也是一样的)

  • 所以问题就很有可能是渲染的原因

解决

  • 定位了问题,看看官方api文档有没有不使用v-if的办法
  • 可以看到,有一个forceRender属性在每一个tab项当中,我们试试看

  • 添加,重新查看效果

  • bug成功解决

文章作者: 梦洁
文章链接: https://www.dreamlove.top/3388f24c.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

评论
\ No newline at end of file diff --git a/36e87c18.html b/36e87c18.html new file mode 100644 index 000000000..d73e05211 --- /dev/null +++ b/36e87c18.html @@ -0,0 +1 @@ +React17+React Hook+TS4 最佳实践仿 Jira 企业级项目笔记 | 梦洁小站-属于你我的小天地

React17+React Hook+TS4 最佳实践仿 Jira 企业级项目笔记

前言

  • 个人笔记,记录个人过程,如有不对,敬请指出
  • React17+React Hook+TS4 最佳实践仿 Jira 企业级项目项目完成到第十章,剩下后面就没有看了,说的不是特别好
  • husky方便我们管理git hooks的工具

image-20221203204054233

REST-API风格

https://zhuanlan.zhihu.com/p/536437382

json-server

  • 安装
1
npm install -g json-server
  • 项目安装
1
npm install -D json-server 

项目开始

用jsx渲染开发工程列表

  • 初始化代码出现问题,表单收集的组件无法将请求结果发送给list组件
1
2
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
35
36
37
38
39
40
41
import React, { useEffect, useState } from "react";

const List = () => {
const [params,setParams] = useState({
name:'',
personId:'',
});
const [selectOptions,setSelectOptions] = useState([])
const [listData,setListData] = useState([]) //请求列表数据
useEffect(async () => {
try{
const response = await fetch('/abc');
if(response.ok){
const result = await response.json();//获取数据结果
setListData(result ?? [])
}
}catch (e){
console.log('发生错误');
}
},[params])
return (
<form>
<input type='text' value={params.name} onChange={event => setParams({ ...params,name:event.target.value })}/>
<select value={params.personId} onChange={event => setParams({
...params,
personId: event.target.value,
})}>
{
selectOptions.map(item => {
return (
<option value={item.value}>{ item.label }</option>
)
})
}
</select>
</form>
);
};

export default List;

  • 解决办法(3-2 用状态提升分享组件状态,完成工程列表页面),也就是将请求放在父组件当中
  • 顺带一提,如果我们使用的是vite创建的react项目,就无法像老师一样直接使用process.env来读取设置的变量了

学习自定义hook

  • useHooks(不管是自带的hooks还是自己创建的hooks),不可以在普通函数中运行,只能在hooks当中使用或者其他hooks使用,所以在自定义hooks的时候,需要以useXxx开头

使用自定义的useMount和useDebounce

  • useMount
1
2
3
4
/*只在初次挂载执行*/
export const useMount = (callback) => {
useEffect(callback,[])
}
  • useDebounce(防抖)
1
2
3
4
5
6
7
8
9
10
11
12
13
/*自定义防抖hooks*/
export const useDebounce = (value,delay) => {
const [debounceValue,setDebounceValue] = useState(value)
useEffect(() => {
const timer = setTimeout(() => { setDebounceValue(value) },delay);
return () => {
/*下一次effect执行前的处理*/
clearTimeout(timer)
}
},[value,delay])
return debounceValue;
}

  • useDebounce的使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*搜索参数*/
const [params,setParams] = useState({
name:'',
personId:'',
})
const debounceValue = useDebounce(params,2000);

/*
* 当搜索条件发生变化的时候,就更新
* */
useEffect(async () => {
/*请求获取列表数据*/
try{
const response = await fetch(`${apiUrl}/projects?${qs.stringify(cleanEmptyObj(debounceValue))}`)
if(response.ok){
const result = await response.json();
setListData(result)
}
}catch (e){
console.log(e);
}
},[debounceValue])
  • useDebounce的理解

    • 传入: 传入需要节流的值和延迟

    • 返回: 返回节流后的新state数据

    • 原理:

      • 内部对传入的value进行重新构建一个state,当传入的value发生改变的时候的时候,会被内部debounce创建的节流函数所捕捉,捕捉到后,如果中途没有重新捕捉到新的值,则会在设置的时间之后更新内部debounce的值,否则的话就会中断更新,重新计时
    • 图示原理

js改造为ts

  • 文件名更改

    • js -> 改为 ts
    • jsx -> 改为tsx
  • 遇到qs模块types缺失的情况: Could not find a declaration file for module 'qs'.xxxx,npm i --save-dev @types/qs,安装对应的types即可

1
yarn add @types/qs -D 
  • 注意箭头函数和普通函数的泛型书写位置
1
2
3
4
5
6
7
8
9
// 箭头函数
const fn1 = <T,U>() => {

}

//普通函数
function fn2<T,U>() {

}

鸭子类型和json-server中间件

  • 鸭子类型
    • ts是只看是否实现了这个接口当中的成员,实现了就可以通过,没有实现就不通过,不管有没有定义
    • 说通俗点就是只要你符合这个规定里面的规则,就是他说的一个东西
    • 比如鸭子会嘎嘎叫,只要你会嘎嘎叫,ts就认为你是鸭子
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
interface Base {
id:number
}
const test = (param:Base) => {

}
/*定义一个对象,对象当中的实现了接口Base的成员*/
const a = {
id:100,
name:'李白'
}
test(a);//不会报错

/*定义一个对象,对象当中没有实现Base的成员*/
const b = {
name:'李白'
}
test(b);//警告 'id' is declared here.

  • json-server中间件
    • 注意POST为大写
    • package.json更改"json-server": "json-server __json_server_mock/db.json --watch --port 3033 --middlewares __json_server_mock/middleware.js"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
module.exports = (req,res,next) => {
if(req.method === 'POST' && req.path === '/login'){
if(req.body.username === 'qiuye' && req.body.password === '123456'){
return res.status(200).json({
user:{
token:'我是token'
}
})
}
/*密码错误*/
else{
return res.status(400).json({message:'用户名或密码错误'})
}
}
next();
}

  • 登录表单
1
2
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
35
36
37
38
39
import React, { FormEvent } from "react";
const apiUrl = import.meta.env.VITE_REACT_APP_API_URL;
const Login = () => {
const login = (params:{username:string,password:string}) => {
fetch(`${apiUrl}/login`,{
method:'POST',
headers:{
'Content-Type':'application/json'
},
body:JSON.stringify(params),
}).then(async (response) =>{
if(response.ok){
//await response.json();
}
})
}
/*点击登录*/
const handleSubmit = (event:FormEvent) => {
event.preventDefault();//阻止默认行为
//@ts-ignore;
const username = event.target[0].value;
//@ts-ignore;
const password = event.target[1].value;
login({username,password})
}
return (
<form onSubmit={handleSubmit}>
<label htmlFor='username'>用户名</label>
<input type='text' id='username' />
<br/>
<label htmlFor='password'>密码</label>
<input type='password' id='password' />
<button type='submit'>登录</button>
</form>
);
};

export default Login;

安装jira-dev-tool

1
2
3
npx msw init public
//或者 指定到public目录下
npx msw init ./public

使用自定义useHttp处理登录状态

1
2
3
4
5
6
7
8
9
10
11
12
13
export const useHttp = () => {
const {userInfo} = useAuth();
return (...[url,config]:Parameters<typeof http>) => http(url,{...config,token:userInfo.token});
}

// 第一步:
parameters<typeof http>返回htpp的联合类型元组,也就是[url:string,{data,token,headers,...customConfig}:Config]
//所以你明白老师为什么要设置为一个数组了吧?因为这个是联合类型元组,描述的是数组的结构

// 第二步:使用扩展运算符展开函数参数
//如果不适用展开运算符,我们调用函数必须要 Example(['请求地址',config对象])

//如果使用了,就可以 Example('请求地址',config对象)
  • 在使用函数的时候,如果是数组,想要将数组当中的数据依次传入数组,可以使用扩展运算符
1
2
3
4
5
6
7
8
9
10
const arr1 = [1, -1, 0, 5, 3];
const min = Math.min(...arr1);
console.log(min);// -1

Math.min用法是传入0个或多个数字,0 个或多个数字,将在其中选择,并返回最小值。

const arr1 = [1, -1, 0, 5, 3];
const max = Math.max(...arr1);
console.log(max);// 5
Math.max用法是传入0个或多个数字,0 个或多个数字,将在其中选择,并返回最大值。

更改为antd

1
2
3
4
5
6
7
8
9
10
11
12
string.localeCompare(targetString,locales,options);


该方法返回的是一个数字用来表示一个参考字符串和对比字符串是排序在前,在后或者相同

返回值:返回值是一个数字,目前的主流浏览器都返回的是1、0、-1三个值,但是也有其他情况,所以不可以用绝对的值等于1、-1这种去判断返回的结果

返回值大于0:说明当前字符串string大于对比字符串targetString

返回值小于0:说明当前字符串string小于对比字符串targetString

返回值等于0:说明当前字符串string等于对比字符串targetString

使用css-in-js-Emotion

  • App.css样式更改为如下(App.css为全局样式)
1
2
3
4
5
6
7
8
html{
/*使得1rem === 10px*/
font-size: 62.5%;/* 这样子使得font-size为16px * 62.5% = 10px */
}

html body #root .App {
min-height: 100vh;
}
  • 安装emotion
1
yarn add @emotion/react @emotion/styled
  • 安装编辑器插件

    • webstorm: Styled Components & Styled JSX,不过最新版本的好像都自动安装了
    • vscode: vscode-styled-components
  • 使用emotion

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// html自带标签的使用
const Container = styled.div
`
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
`

// 组件的使用
import {Card} from "antd";
const ShowCard = styled(Card)
`
box-sizing: border-box;
width: 40rem;
min-height: 56rem;
padding: 3.2rem 4rem;
border-radius: 0.3rem;
box-shadow: 0 0 1rem rgba(0,0,0,.1);
`

  • 注意,不管有没有css样式后面,必须要接一个模板字符串,否者会报错
1
2
3
4
5
//不报错
const HeaderLeft = styled(Row)``;

//报错
const HeaderLeft = styled(Row);
  • 设置多个背景(学到了,学到了,)
    • background-image 属性用于为一个元素设置一个或者多个背景图像。
    • background-position属性为每一个背景图片设置初位置
      • 一个值为x,y设置相同的位置
      • 二个值分别为x轴位置和y轴位置
    • background-size属性设置背景图片大小
      • 一个值: 指定图片的宽度,高度为auto
      • 二个值: 分别指定图片的高度 和 宽度
      • 逗号分割多个值,设置多重背景
1
2
3
4
5
6
/*设置背景图*/
background-repeat: no-repeat;
background-position: left bottom, right bottom;
background-size: calc(((100vw - 40rem)/2) - 3.2rem ) ,calc(((100vw - 40rem)/2) - 3.2rem ),cover;
background-attachment: fixed;
background-image: url(${LeftBg}),url(${RightBg});

grid和flex各自的应用场景

  • 一看空间
    • 一般来说,一维布局用flex,二维布局用grid
  • 二看内容和布局
    • 从内容出发: 有一组内容(数量一般不固定),然后希望他们均匀分布在容器在当中,并且由内容自己的大小决定占据的空间(用flex)
    • 从布局出发:先规划网格(数量一般比较固定),然后再把元素往里填充(用grid)

css-in-js:Row组件实现

  • emotion允许我们像react一样传递参数来达到自定义样式的效果
    • Row组件样式
    • 当然,可以不写ts在这里,不过不写会有警告在tsx当中~
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import styled from "@emotion/styled";

export default styled.div<{
gap?: number | boolean,//右侧间距设置
between?: boolean,//内容是否居中
marginBottom?:number,//距离底部距离
}>
`
display: flex;
align-items: center;
justify-content: ${ props => props.between ? 'space-between' : undefined };
margin-bottom: ${ props => props.marginBottom ? props.marginBottom : 0 };
> * {
margin-top: 0!important;
margin-bottom: 0!important;
margin-right: ${ props => typeof props.gap === 'number' ? props.gap + 'rem' : props.gap ? '2rem' : undefined}
}
`
  • 使用
1
2
3
4
5
6
7
8
9
10
import styled from "@emotion/styled";
import Row from "../src/component/lib";

const HeaderLeft = styled(Row)``;
// 传入对应的参数即可
<HeaderLeft gap = {true}>
<h3>Logo</h3>
<h3>Logo</h3>
<h3>Logo</h3>
</HeaderLeft>

完成项目列表页面样式

  • 使用emotion的css

    • 在react当中,我们可以直接对组件使用style设置样式,但是不支持一些子元素选择符,伪类等一些高级选择器的
    1
    <MyComponent style={{ marginBottom:'2rem' }}/>
    • 所以我们可以使用@emotion/react来代替我们
    1
    2
    3
    4
    5
    /** @jsx jsx */
    import { jsx } from "@emotion/react";
    <Form css={{marginBottom: '2rem'}} >

    </Form/>
    • 老师是这样子写的,但是我报错了pragma and pragmaFrag cannot be set when runtime is automatic.,不知道为什么,就这样子吧,后面遇到再说
  • 图片以svg形式渲染

    • 我们如果直接在React使用img去使用svg图片的时候,并不能去设置svg参数了

1
2
3
import Logo from "../src/assets/svg/software-logo.svg";
// 无法设置svg属性了
<img src={Logo}/>
  • 所以我们应该使用如下方式去使用svg图片(通过组件的形式),这样子我们就可以设置svg的一些参数了

1
2
import { ReactComponent as SoftwareLogo } from "../src/assets/svg/software-logo.svg";
<SoftwareLogo width={'18rem'} color={'rgb(38,132,255)'}/>

清除警告todo

  • The href attribute is required for an anchor to be keyboard accessible. Provide a valid, navigable address a....

    • 原因:a标签没有href导致的,必须要一个合法的跳转链接,不可以#,也不可以javascript:;
    • 解决:替换为button,或者使用组件库,设置button的属性为link
  • React Hook useEffect has a missing dependency: 'callback'. Either include it or remove the dependency array.

    • 原因:依赖项没有加入在依懒收集里面导致报错
    • 解决:
1
2
3
4
useEffect(() => {
callback();
// todo 依懒项里加上callback会造成无限循环,这个和useCallback以及useMemo有关系
},[])
  • 不要乱用object
    • 覆盖范围很广,比如一个箭头函数变量,ts都认为这个是object
1
2
3
4
5
6
7
8
9
10
11
12
13
export const isVoid = (value:unknown) => value === undefined || value == null || value === '';

/*清除空对象*/
export const cleanEmptyObj = (obj: {[key:string]:unknown}) => {
const temp = {...obj};
Object.keys(temp).forEach(key => {
const value = temp[key]
if(isVoid(value)){
delete temp[key]
}
})
return temp;
}
  • Detected multiple renderers concurrently rendering the same context provider. This is currently unsupported.(我这边失败了,这个具体的就不加了)

    • 原因:jira-dev-tool问题
    • 解决:安装jira-dev-tool@next
    1
    2
    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
    yarn add jira-dev-tool@next

    App.tsc

    - import { loadDevTools } from 'jira-dev-tool';
    + import { loadServer,DevTools } from 'jira-dev-tool';

    //原来写法
    loadDevTools(() => {
    ReactDOM.render(
    <React.StrictMode>
    <AppProvider>
    <App />
    </AppProvider>
    </React.StrictMode>,
    document.getElementById('root')
    );
    });


    //更改为
    loadServer(() => {
    ReactDOM.render(
    <React.StrictMode>
    <AppProvider>
    <DevTools/>
    <App />
    </AppProvider>
    </React.StrictMode>,
    document.getElementById('root')
    );
    });

登录注册页面loading和error状态处理

需要注意的是try-catch是同步的

为什么不能用useAsync的error,因为error更新是异步的,try-catch是同步的

1
2
3
4
5
6
7
8
9
10
11
  const {run,isLoading,error} = useAsync(undefined,{throwOnError: true});
/*点击登录*/
const handleSubmit = async ({username,password}: {username:string,password:string}) => {
try {
await run(login({ username, password }))
}catch (e) {
console.log(e)
}
};

try ... catch执行完成后,才开始执行error的更新函数,所以你会发现,第一次error输出没有值,第二次error就有了(第二次的error为上一次出错的值)

未捕获错误(Uncaught Errors)-错误边界

1
2
3
4
5
6
7
8
事件处理

异步代码(例如 setTimeout 或 requestAnimationFrame 回调函数)

服务端渲染

它自身抛出来的错误(并非它的子组件)

  • 老师创建的组件的要求

    • 1.可以自定义错误显示的组件
    • 2.可以展示子组件当没有错误的时候
  • 代码

1
2
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
import React, {Component, ErrorInfo, ReactNode} from "react";

export type Props = {
children:ReactNode,//子组件
fallBackRender : (props: { error:Error | null }) => React.ReactElement,
}

export interface State {
error: Error | null;
}

export class Boundary extends Component<Props, State> {
state = {
error:null
}

static getDerivedStateFromError(error:Error){
return {
error
}
}

componentDidCatch(error:Error, errorInfo:ErrorInfo) {
// 你同样可以将错误日志上报给服务器
}

render() {
const { error } = this.state;
const { children, fallBackRender } = this.props;
if(error) return fallBackRender({error})
return children;
}
}

使用useRef实现useDocumentTitle

  • 需求

    • 每一个组件可以使用useDocumentTitle方法,第一个参数传入新标题,第二个标题传入卸载组件后是否复原
  • 疑问

    • 为什么useDocumentTitle当中的设置document.title要放置在useEffect
    1
    2
    3
    4
    5
    6
    7
    因为在函数组件主体内(这里指在 React 渲染阶段)改变 DOM、添加订阅、设置定时器、记录日志以及执行其他包含副作用的操作都是不被允许的,因为这可能会产生莫名其妙的 bug 并破坏 UI 的一致性。

    使用 useEffect 完成副作用操作。赋值给 useEffect 的函数会在组件渲染到屏幕之后执行。你可以把 effect 看作从 React 的纯函数式世界通往命令式世界的逃生通道。

    说通俗点就是useEffect可以放置一些副作用操作在里面,并设置为依赖.而我们的document.title就是一个副作用,所以需要放置在useEffect

    别忘记了`effect`单词本身的意思哦
    • useRef为什么要使用
1
2
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/* 网页标题更改 */
export const useDocumentTitle = (title:string,keepOnUnmount:boolean = true) => {
//获取旧标题
const oldTitle = document.title;
useEffect(() => {
document.title = title;
},[title])
//更改新标题(副作用,使用需要使用useEffect)
useEffect(() => {
return () => {
if(!keepOnUnmount) {
//卸载的时候执行
document.title = oldTitle
}
}
},[]);
}

//在上述代码中,因为闭包的问题,导致`oldTitle`可以保存最老的值

//但是控制台可能会有警告,所以我们加入依赖,但是这又导致了oldTitle永远都是最新的值(也就是每执行一次,当前组件的oldTime都会被更新为上一次的值,而不是我们所想的指向最初始的title)
export const useDocumentTitle = (title:string,keepOnUnmount:boolean = true) => {
//获取旧标题
const oldTitle = document.title;
useEffect(() => {
document.title = title;
},[title])
//更改新标题(副作用,使用需要使用useEffect)
useEffect(() => {
return () => {
if(!keepOnUnmount) {
//卸载的时候执行
document.title = oldTitle
}
}
},[keepOnUnmount,oldTitle]);
}


所以我们可以借助useRef
useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内持续存在。
//代码改造如下
export const useDocumentTitle = (title:string,keepOnUnmount:boolean = true) => {
//获取旧标题
const oldTitle = useRef(document.title).current;
//更改新标题(副作用,使用需要使用useEffect)
useEffect(() => {
document.title = title;
return () => {
if(!keepOnUnmount) {
//卸载的时候执行
document.title = oldTitle
}
}
},[title,keepOnUnmount,oldTitle]);
}

动态改变网页标签显示的标题#todo

  • 第一种
    • react-helmet
  • 第二种

添加项目列表和项目详情

  • react-router-dom和react-router的关系

    • react-router管理路由关系,计算结果由react-router-dom来消费使用
  • 为什么Link是从react-router-dom引入的

    • 因为Link会被渲染为一个a标签,并且需要处理点击事件,和浏览器是相关联的
  • 路由表和路由的书写技巧

    • 不可以写斜杆,写斜杆代表不管你从哪里开始,我都是从/开始的,
      所以我们需要在不破坏当前路由的基础上把路由加进去,所以不能有/
    • /带了斜杆就是根了(也就url的路径从我开始算起)
    • ./在不破坏当前路径下载后面添加内容,也可以不写./
    • 于是乎下面三个效果都是一样的,最终匹配的都是/home/message
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //路由表
    {
    path:'/home',
    element:<Home/>
    children:[
    path:"./message",
    path:'/home/message',
    path:'message',
    ]
    }
    //路由链接的to的书写也和这个相同,
    //下面二个均是在对应url后面添加相应的内容
    //比如之前url为/home,那么点击`message组件显示`,
    //那么url就会变为/home/message
    <NavLink to="message">Message组件显示</NavLink>
    <NavLink to="news">News组件显示</NavLink>
  • 一个很奇怪的问题

    • 必须要转化为字符串才可以
1
return <Link to={String(project.id)}>{ project.name } </Link>
  • 注意Navigate写法
1
2
3
4
5
6
7
8
9
10
<h1>ProjectScreen</h1>
<Link to={'kanban'}>看板</Link>
<Link to={'epic'}>任务组</Link>
<Routes>
<Route path={'kanban'} element={<KanbanScreen/>}></Route>
<Route path={'epic'} element={<EpicScreen/>}></Route>
{/*/!*<Navigate to={window.location.pathname + '/kanban'}/>*!/ react5写法*/}
<Route path={''} element={<Navigate to={'kanban'}/>}/>
</Routes>
</div>

useSearchParams初步完成

  • 可以用来获取search参数(也就是url后面的这种参数/a?id=4&age=14)
  • 返回一个URLSearchParams,
  • 所以要读取某一个值就需要使用get方法
1
2
3
4
//url
/a?id=4&age=14
const [a] = useSearchParams();
a.get(age);//输出14
  • 问题

    • 为什么没有as const会出现下图问题
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import {useSearchParams} from "react-router-dom";
    export const useUrlQueryParams = (keys:string[]) => {
    const [searchParams] = useSearchParams();
    return [
    keys.reduce((pre,key) => {
    return {...pre,[key]:searchParams.get(key) ?? ''}
    },{} ),
    searchParams,
    ]
    }

    查看返回值类型

    • 先来看一个小例子
      • 想一想a的类型是什么
    1
    const a = ['jack',12,{gender:'男'}]

    a的类型

    • 原因很简单,因为ts认为数组都是相同的,使用为了确保数组当中都有相同的,所以就使用了多个|
    • 所以我们return后面添加一个as const即可
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    export const useUrlQueryParams = (keys:string[]) => {
    const [searchParams] = useSearchParams();
    return [
    keys.reduce((pre,key) => {
    return {...pre,[key]:searchParams.get(key) ?? ''}
    },{} ),
    searchParams,
    ] as const
    }

  • 不过这还不够,我们点入reduce的ts声明可以看到,reduce当中previousValue返回值依赖于初始化时候传入的泛型,所以我们可以指明initialValue在reduce当中

1
reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T;
  • 最终初步完成如下
1
2
3
4
5
6
7
8
9
export const useUrlQueryParams = (keys:string[]) => {
const [searchParams,setSearchParams] = useSearchParams();
return [
keys.reduce((pre,key) => {
return {...pre,[key]:searchParams.get(key) ?? ''}
},{} as {[key in string]:string}),
setSearchParams,
] as const
}

useSearchParams完成(使用useMemo解决)

  • 上一次完成的方法我们使用发现会无限渲染我们可以借助why-did-you-render来检测是什么造成了页面渲染

  • 通过排查,useDebounce当中的useEffect发现依赖项目变化了,进而去重新渲染页面,但是params在每次渲染的时候都是一个新的,导致useEffect又认为发生了变化,进而重复无限渲染

    • 所以我们使用useEffect的时候,我们应该将基本类型和组件状态(使用useState)放置到依赖里面,非组件状态的对象,绝对不可以放在依赖里面!!
    • 所以检查我们可以使用why-did-you-render
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const [params] = useUrlQueryParams(['name','age'])

const debounceValue = useDebounce(params,100);

/*自定义防抖hooks*/
export const useDebounce = <T>(value:T,delay?:number):T => {
const [debounceValue,setDebounceValue] = useState(value)
useEffect(() => {
const timer = setTimeout(() => { setDebounceValue(value) },delay);
return () => {
/*下一次effect执行前的处理*/
clearTimeout(timer)
}
},[value,delay])
return debounceValue;
}
  • 所以我们循环遍历search参数的时候,返回的是一个对象,又因为react只对对象进行地址比较,所以就导致每次重新渲染返回的对象不同,所以就造成了重复渲染,解决后代码如下(使用useMemo)
1
2
3
4
5
6
7
8
9
10
11
12
export const useUrlQueryParams = <K extends string>(keys:K[]) => {
const [searchParams,setSearchParams] = useSearchParams();
return [
useMemo(() => {
return keys.reduce((pre,key) => {
return {...pre,[key]:searchParams.get(key) ?? ''}
},{} as {[key in K]:string})
},[searchParams]),
setSearchParams,
] as const
}

完成的URL状态管理代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
export const useUrlQueryParams = <K extends string>(keys:K[]) => {
const [searchParams,setSearchParams] = useSearchParams();
return [
useMemo(() => {
return keys.reduce((pre,key) => {
return {...pre,[key]:searchParams.get(key) ?? ''}
},{} as {[key in K]:string})
},[searchParams]),
(params:Partial<{[key in K] : unknown}>) => {
const o = cleanEmptyObj({...Object.fromEntries(searchParams),...params}) as URLSearchParamsInit
return setSearchParams(o);
}
] as const
}

实现Id-Select解决Id难题

  • 获取AntDesign组件当中属性的二种方式

    • 方法1:通过React.ComponentProps;
    1
    2
    3
    4
    import React from "react";
    import {Select} from "antd";

    type SelectProps = React.ComponentProps<typeof Select>;
    • 方法2:Ctrl按照进入组件,然后找到后复制粘贴就可以引入
    1
    import {SelectProps} from "antd/es/select";

用useEditProject编辑项目

柯里化

  • 因为有一个参数实现已经知道了,后一个参数需要等待才可以知道,就可以采用这种方式
1
2
3
4
5
6
7
8
9
const { mutate } = useEditProject();
const pinProject = (id:number) => (checked:boolean) => mutate({id,pin:checked})
<Pin checked={project.pin} onCheckedChange={pinProject(project.id)}/>

等同于
const pinProject = (id:number,checked:boolean) => {
mutate({id,pin:checked})
}
<Pin checked={project.pin} onCheckedChange={(checked:boolean) => pinProject(project.id,checked) }/>

惰性初始化和使用useRef保存函数和useState保存函数的方法

什么是惰性初始化

  • 惰性初始化的时候(也就是传入一个函数就是惰性初始),此函数会被立即执行,并将返回的值作为返回数组的第一个参数,其他和普通state是相同的(调用setState也会触发页面重新渲染)
  • 惰性初始代码
1
2
3
4
const [state,setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
})
  • 非惰性初始(也就是普通的state,传入的不是一个函数,)
1
2
3
4
5
const [state,setState] = useState(someExpensiveComputation(props));
const [state,setState] = useState({
name:'李白',
sex:'男'
})
  • 所以当我们想通过useState保存函数的时候,就不可以了,我们可以使用useRef来保存函数

useRef

  • 我觉得我快忘记了,再看看React官网的描述吧
    • 比较重点就是ref对象发生变化,并不会引发组件的重新渲染,useRef的值是保存在一个.current属性当中

  • 老师在最后说的一个问题,将代码改为下面样子,我们点击设置callBack后,再点击执行callBack-设置后不正常输出'init'按钮,发现输出的却是init
    • 这是因为组件在编译渲染完成后,执行callBack-设置后不正常输出'init'按钮始终指向初次时候的函数地址,又因为useRef即使被更新也不会被重新渲染,导致此按钮指向的依旧是初始化时候的函数
    • 而为什么执行callBack-设置后正常输出'updated'按钮却是正常输出,因为传入的是高阶函数,在执行的时候才会寻找current所指向的函数去执行,所以执行就没有问题
1
2
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
import * as React from 'react'
import './style.css';

export default function App() {
const callBackRef = React.useRef(() => {
return alert('init');
});
const callBackCurrent = callBackRef.current;
console.log('输出查看callBack的current(是否重新渲染)', callBackCurrent);
// 设置callBack
const setCallBack = () => {
callBackRef.current = () => {
return alert('updated');
};
};

//执行callBack - 正常
const fnCallBack = () => {
callBackRef.current();
};

return (
<div>
<button onClick={setCallBack}>设置callBack</button>
<button onClick={fnCallBack}>执行callBack-设置后正常输出'updated'</button>
<button onClick={callBackRef.current}>
执行callBack-设置后不正常输出'init'
</button>
<h1>Hello StackBlitz!</h1>
<p>Start editing to see some magic happen :)</p>
</div>
);
}

useState保存函数的方法

  • 既然存在惰性初始化,我们就让他执行就可以,我们返回一个函数就可以啦
1
2
3
const [retry,setRetry] = useState(() => () => {
//返回一个函数
})

优化异步请求

  • 出现的情况

    • 在外面等待数据返回的时候,突然退出登录,然后未中断请求,导致setData等操作会出现异常(我练习的时候好像没有,不过也学习下)
  • 解决异步的时候退出的问题

1
2
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
//1.建立useMountedRef,并设置状态
/*
* 返回组件的状态,如果没有挂载或者已经卸载,则返回false,
* */
export const useMountedRef = () => {
const mountedRef = useRef(false);
useEffect(() => {
mountedRef.current = true;
return () => {
mountedRef.current = false;//组件卸载
}
})
return mountedRef
}

// 伪代码,当调用then的时候判断是否组件卸载了
return promiseGive
.then((res:D) => {
if(mountedRef.current){
setData(res);
}
return Promise.resolve(res);//实现链式调用
})
.catch(error => {
setError(error);
if(config.throwOnError) return Promise.reject(error);
return error;//实现链式调用
})
  • 当使用useCallback,或者useMemo的时候,可以里面会有依赖state,然后我们会将依赖加入进去,但是加入后,会发生无限循环的问题,这个时候我们就可以使用setData的第二种形式了,然后结合useCallback或者useMemo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//第一种(造成无限循环)
//当data改变,触发重新渲染,因为当调用setData的时候,就代表data改变
//然后触发useCallback的重新渲染,然后又调用setData,又代表data改变
//又重新触发setData,,,,,,这样子无限循环下去
const handleChange = useCallback(() => {
const [data,setData] = useState({name:'李白',loading:false})
setData({...state,loading:true})
},[data,setData])


//第二种(解决无限循环)
const handleChange = useCallback(() => {
const [data,setData] = useState({name:'李白',loading:false})
setData((preState) => {...preState,loading:true})
},[setData])

什么时候使用useMemo,useCallback

  • 非基本类型想做依赖,就需要使用这二个
  • 比如在想自定义hooks的时候,返回了函数,或者非基本数据类型,或者并没有被useState包裹的数据的时候,就需要使用这二个了

状态提升

  • const其实变量提升(依照var的变量提升,我们可以想一想组件的状态提升)

  • 目前我们为了使用一个能被多个组件调用的方法或者是属性(可以称其为”全局方法或属性“),我们将其提升到共同的父组件当中,但是当子组件需要使用全局方法或属性的时候,父组件和要使用的子组件只有一层还好说,当有多层的时候,就会出现父传给A组件,A组件传递给B组件,B组件在传给C组件,C组件再来使用,来看看下面集中方法

    • 第一种: 放在全局状态,通过一层一层传递,

    • 第二种: 还有一种Context的方法

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      class App extends React.Component {
      render() {
      return <Toolbar theme="dark" />;
      }
      }

      function Toolbar(props) {
      // Toolbar 组件接受一个额外的“theme”属性,然后传递给 ThemedButton 组件。
      // 如果应用中每一个单独的按钮都需要知道 theme 的值,这会是件很麻烦的事,
      // 因为必须将这个值层层传递所有组件。
      return (
      <div>
      <ThemedButton theme={props.theme} />
      </div>
      );
      }

      class ThemedButton extends React.Component {
      render() {
      return <Button theme={this.props.theme} />;
      }
      }
      • 使用createContext传递
      1
      2
      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
      // Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树。
      // 为当前的 theme 创建一个 context(“light”为默认值)。
      const ThemeContext = React.createContext('light');
      class App extends React.Component {
      render() {
      // 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
      // 无论多深,任何组件都能读取这个值。
      // 在这个例子中,我们将 “dark” 作为当前的值传递下去。
      return (
      <ThemeContext.Provider value="dark">
      <Toolbar />
      </ThemeContext.Provider>
      );
      }
      }

      // 中间的组件再也不必指明往下传递 theme 了。
      function Toolbar() {
      return (
      <div>
      <ThemedButton />
      </div>
      );
      }

      class ThemedButton extends React.Component {
      // 指定 contextType 读取当前的 theme context。
      // React 会往上找到最近的 theme Provider,然后使用它的值。
      // 在这个例子中,当前的 theme 值为 “dark”。
      static contextType = ThemeContext;
      render() {
      return <Button theme={this.context} />;
      }
      }
    • 第三种: 组合组件(component composition)

      • 其实就是将组件传递(传递jsx) - 缺点是没有解决向下钻的问题,但是方法不需要传递了,(定义和调用很近),示例如下
      1
      2
      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
      // 使用component composition
      function Page(props) {
      const user = props.user;
      const userLink = (
      <Link href={user.permalink}>
      <Avatar user={user} size={props.avatarSize} />
      </Link>
      );
      return <PageLayout userLink={userLink} />;
      }

      // 现在,我们有这样的组件:
      <Page user={user} avatarSize={avatarSize} />
      // ... 渲染出 ...
      <PageLayout userLink={...} />
      // ... 渲染出 ...
      <NavigationBar userLink={...} />
      // ... 渲染出 ...
      {props.userLink}

      //未使用component composition之前
      <Page user={user} avatarSize={avatarSize} />
      // ... 渲染出 ...
      <PageLayout user={user} avatarSize={avatarSize} />
      // ... 渲染出 ...
      <NavigationBar user={user} avatarSize={avatarSize} />
      // ... 渲染出 ...
      <Link href={user.permalink}>
      <Avatar user={user} size={avatarSize} />
      </Link>

useUndo

  • 未使用useReducer的写法
1
2
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
import {useCallback, useState} from "react";

const UseUndo = <T>(initData:T) => {
const [state,setState] = useState<{
backList:T[],//过去的记录
present:T,//现在的值,
goList:T[],//前面的记录
}>({
backList:[],
goList:[],
present:initData,
})
const [canBack,setCanBack] = useState(() => state.backList.length > 0);//是否可以后退
const [canGo,setCanGo] = useState(() => state.goList.length > 0);//是否可以前进

/* 执行返回 */
const execBack = useCallback(() => {
setState((currentState) => {
if(!canBack) return currentState;
const { goList:oldGoList,backList:oldBackList } = currentState;
const present = oldBackList[oldBackList.length-1];
const backList = oldBackList.slice(0,oldGoList.length - 1);
const goList = [...oldGoList,present];
return {
goList,
backList,
present,
}
})
},[])

/* 执行前进 */
const execGo = useCallback(() => {
setState((currentState) => {
if(!canGo) return currentState;
const { goList:oldGoList,backList:oldBackList } = currentState;
const present = oldGoList[0];
const goList = oldGoList.slice(1);
const backList = [...oldBackList,present];
return {
goList,
backList,
present,
}
})
},[])

/* 设置值 */
const set = useCallback((newData:T) => {
setState((currentState) => {
const { goList:oldGoList,backList:oldBackList,present:oldPresent } = currentState;
if(newData === oldPresent) return currentState;
const backList = [...oldBackList,oldPresent];
return {
goList:[],
backList,
present:newData,
}
})
},[])


/* 重置 */
const reset = useCallback(() => {
setState((currentState) => {
const { goList,backList } = currentState;
return {
goList:[],
backList:[],
present:initData,
}
})
},[])

return [
state,
execBack,
execGo,
set,
reset,
canBack,
canGo,
] as const
}

export default UseUndo

  • 使用useReducer的写法,仅供参考
1
2
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import {useCallback, useReducer, useState} from "react";

export enum TypeOperation {
go='go',
back='back',
set='set',
reset='reset',
}

export interface State<T> {
backList:T[],//过去的记录
present:T,//现在的值,
goList:T[],//前面的记录
}

export interface Action<T> {
newPresent?:T,
type: TypeOperation,
}
const unDoReducer = <T>(state:State<T>,action:Action<T>) => {
const {type,newPresent} = action;
const { goList:oldGoList,backList:oldBackList,present:oldPresent } = state;
switch (type){
case "back": {
if(oldBackList.length ===0 ) return state;
const present = oldBackList[oldBackList.length-1];
const backList = oldBackList.slice(0,oldGoList.length - 1);
const goList = [...oldGoList,present];
return {
goList,
backList,
present,
}
}
case "go": {
if(oldGoList.length === 0) return state;
const present = oldGoList[0];
const goList = oldGoList.slice(1);
const backList = [...oldBackList,present];
return {
goList,
backList,
present,
}
}
case "reset":{
return {
goList:[],
backList:[],
present:newPresent,
}
}
case "set": {
if(newPresent === oldPresent) return state;
const backList = [...oldBackList,oldPresent];
return {
goList:[],
backList,
present:newPresent,
}
}
default: return state;
}
}


const UseUndo = <T>(initData:T) => {
const [state,dispatch] = useReducer(unDoReducer,{
backList:[],
goList:[],
present:initData,
})
const [canBack,setCanBack] = useState(() => state.backList.length > 0);//是否可以后退
const [canGo,setCanGo] = useState(() => state.goList.length > 0);//是否可以前进
/* 执行返回 */
const execBack = useCallback(() => dispatch({type:TypeOperation.back}),[dispatch])

/* 执行前进 */
const execGo = useCallback(() => dispatch({type:TypeOperation.go}),[dispatch])

/* 设置值 */
const set = useCallback((newData:T) => dispatch({type:TypeOperation.set,newPresent:newData}),[dispatch])

/* 重置 */
const reset = useCallback((newData:T) => dispatch({type:TypeOperation.reset,newPresent:newData}),[dispatch])

return [
state,
execBack,
execGo,
set,
reset,
canBack,
canGo,
] as const
}

export default UseUndo

  • 顺带一提,其实useReudcer里面的action,其实什么都可以,只是我们用的多的都是带有type的而已

redux

  • redux用在哪里都可以,react-redux连接redux.如果redux不使用在react,就可以不适用react-redux

  • redux作用就是用现在的state,产生下一个state

  • 当redux发生什么事情的时候,就会戳一下

    • redux发生dispatch,触发subscribe
    • dispatch -> counter -> store
  • redux保持一个同步之前学习的,为什么呢?保持纯洁性,因为如果是异步请求了,就不是可预测了的

    • 副作用,对现实世界产生影响
      • 比如发送请求,修改全局变量
  • redux怎么知道要更新数据呢?怎么知道要执行订阅的内容呢?

    • 判断前一次的数据是否和后一次的数据相同,相同就不更新,不相同就不更新
    • 这样子比较 变量 a === 变量b
  • redux-thunk在redux里面处理异步流行的一个库(注意,redux可以进行异步操作,但是redux-thunk可以帮助我们隐藏异步实现的细节)

  • 可以看到,组件内部并不想知道怎么请求的,(具体异步细节忽略)

reduxjs/tooltik和react-redux

  • 安装依赖
1
yarn add react-redux @reduxjs/tooltik

有关reduxjs/toolkit部分

  • 书写片段
    • project-list.slice.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import {createSlice} from "@reduxjs/toolkit";

export const projectListSlice = createSlice({
name:'projectListSlice',
initialState:{
projectModalOpen:false,
},
reducers:{
/* 开启对话框 */
openProjectModal(state,action){
//immer帮我们处理了,所以我们可以直接在返回的state书写
state.projectModalOpen = true;
},
/* 关闭对话框 */
closeProjectModal(state,action){
state.projectModalOpen = false;
}
}
})

export const projectListSliceReducer = projectListSlice.reducer;

  • 主入口
    • index.tsx
1
2
3
4
5
6
7
8
9
10
import {configureStore} from "@reduxjs/toolkit";
import {projectListSliceReducer} from "../pages/projectList/projectList.slice";

export const store = configureStore({
/* 设置状态管理 */
reducer:{
projectList:projectListSliceReducer
},
})

有关react-redux

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import {AuthProvider} from "./authContext";
import React, {ReactNode} from "react"
import {store} from "../store";//新增
import {Provider} from "react-redux";//新增
export const AppProvider = ({children}:{children:ReactNode}) => {
return (
<Provider store={store}>
<AuthProvider>
{ children }
</AuthProvider>
</Provider>
)
}
export default AppProvider;

使用

  • 获取设置的state参数const { useSelector } from "react-redux"
1
2
3
4
5
6
import {useSelector} from "react-redux"
const selectProjectModalOpen = state => state.projectList.projectModalOpen;
const showModal = useSelector(selectProjectModalOpen);


//等同于 const showModal = useSelector((state) => state.projectList.projectModalOpen)
  • 调用设置的方法const { useDispatch } from "react-redux"
1
2
3
4
const { useDispatch } from "react-redux";
import {projectListSliceActions} from "../projectList/projectList.slice";
const dispatch = useDispatch();//不需要传入任何参数,react-redux会自动去处理store
<button onClick={() => dispatch(projectListSliceActions.closeProjectModal())}>点击我关闭</button>
  • 这里面的projectListSliceActions对应下面暴露出来的projectListSliceActions
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import {createSlice} from "@reduxjs/toolkit";

export const projectListSlice = createSlice({
name:'projectListSlice',
initialState:{
projectModalOpen:false,
},
reducers:{
/* 开启对话框 */
openProjectModal(state){
//immer帮我们处理了,所以我们可以直接在返回的state书写
state.projectModalOpen = true;
},
/* 关闭对话框 */
closeProjectModal(state){
state.projectModalOpen = false;
}
}
})

export const projectListSliceReducer = projectListSlice.reducer;
export const projectListSliceActions = projectListSlice.actions;
//获取state,从store当中的reducer获取值
export const selectProjectModalOpen = (state) => state.projectList.projectModalOpen;

执行异步

方法1:

  • 在home.js中, 通过createAsyncThunk函数创建一个异步的action

    再在extraReducers中监听这个异步的action的状态, 当他处于fulfilled状态时, 获取到网络请求的数据, 并修改原来state中的数据

1
2
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
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"
import axios from "axios"

// 创建一个异步的action
export const fetchHomeMultidataAction = createAsyncThunk("fetch/homemultidata", async () => {
const res = await axios.get("http://123.207.32.32:8000/home/multidata")
// 返回结果会传递到监听函数的actions中
return res.data
})

const homeSlice = createSlice({
name: "home",
initialState: {
banners: [],
recommends: []
},
// extraReducers中针对异步action, 监听它的状态
extraReducers: {
[fetchHomeMultidataAction.fulfilled](state, { payload }) {
// 在fulfilled状态下, 将state中的banners和recommends修改为网络请求后的数据
state.banners = payload.data.banner.list
state.recommends = payload.data.recommend.list
}
}
})

export default homeSlice.reducer

  • 其他地方引入执行异步
1
2
3
import { useDispatch } from "react-redux";
const dispatch = useDispatch();
dispatch(fetchHomeMultidataAction())

方法2

1
2
3
4
5
6
7
如果我们不想通过在extraReducers在监听状态, 再修改state这种方法的话, 还有另外的一种做法

我们创建的fetchHomeMultidataAction这个异步action是接受两个参数的

参数一, extraInfo: 在派发这个异步action时, 如果有传递参数, 会放在extraInfo里面
参数二, store: 第二个参数将store传递过来
这样我们获取到结果后, 通过dispatch修改store中的state, 无需再监听异步action的状态
1
2
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
35
36
37
38
39
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"
import axios from "axios"

// 创建一个异步的action
export const fetchHomeMultidataAction = createAsyncThunk(
"fetch/homemultidata",
// 有传递过来两个个参数, 从store里面解构拿到dispatch
async (extraInfo, { dispatch }) => {
// 1.发送网络请求获取数据
const res = await axios.get("http://123.207.32.32:8000/home/multidata")
// 2.从网络请求结果中取出数据
const banners = res.data.data.banner.list
const recommends = res.data.data.recommend.list
// 3.执行dispatch, 派发action
dispatch(changeBanners(banners))
dispatch(changeRecommends(recommends))
}
)

const homeSlice = createSlice({
name: "home",
initialState: {
banners: [],
recommends: []
},
reducers: {
changeBanners(state, { payload }) {
state.banners = payload
},
changeRecommends(state, { payload }) {
state.recommends = payload
}
}
})

export const { changeBanners, changeRecommends } = homeSlice.actions

export default homeSlice.reducer

  • 其他地方引入执行异步
1
2
3
import { useDispatch } from "react-redux";
const dispatch = useDispatch();
dispatch(fetchHomeMultidataAction())

总结

  • 先看看片段
    • projectList.slice.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import {createSlice} from "@reduxjs/toolkit";

export const projectListSlice = createSlice({
name:'projectListSlice',
initialState:{
projectModalOpen:false,
},
reducers:{
/* 开启对话框 */
openProjectModal(state){
//immer帮我们处理了,所以我们可以直接在返回的state书写
state.projectModalOpen = true;
},
/* 关闭对话框 */
closeProjectModal(state){
state.projectModalOpen = false;
}
}
})

export const projectListSliceReducer = projectListSlice.reducer;
export const projectListSliceActions = projectListSlice.actions;
//获取state,从store当中的reducer获取值
export const selectProjectModalOpen = (state) => state.projectList.projectModalOpen;
  • store/index.ts
1
2
3
4
5
6
7
8
9
10
import {configureStore} from "@reduxjs/toolkit";
import {projectListSliceReducer} from "../pages/projectList/projectList.slice";

export const store = configureStore({
/* 设置状态管理 */
reducer:{
projectList:projectListSliceReducer
},
})

  • 获取数据使用useSelector (import {useSelector} from "react-redux")

    1
    2
    3
    4
    5
    import { useSelector } from "react-redux"
    const selectProjectModalOpen = state => state.projectList.projectModalOpen;
    const showModal = useSelector(selectProjectModalOpen);

    //等同于 const showModal = useSelector((state) => state.projectList.projectModalOpen)
  • 触发方法使用useDispatch ( import { useDispatch } from "react-redux" )

    1
    2
    3
    4
    5
    6
    import { useDispatch } from "react-redux"

    import {projectListSliceActions} = "./projectList.slice";

    <button onClick={() => dispatch(projectListSliceActions.closeProjectModal())}>点击我关闭</button>

可以自己加ts

用代码分割优化性能

1
2
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
import React,{lazy,Suspense} from 'react';
import {useAuth} from "./context/authContext";
import './App.css';
import {Boundary} from "./component/errorBoundary";
import {FullErrorFallBack, FullPageLoading} from "./component/lib";
//import UnAuthenticated from "./pages/unAuthenticated";
//import Authenticated from "./pages/authenticated";

const UnAuthenticated = lazy(() => import("./pages/unAuthenticated"))
const Authenticated = lazy(() => import("./pages/authenticated"))

function App() {
const {userInfo} = useAuth();
/*测试错误*/
return (
<div className='App'>
<Boundary fallBackRender={FullErrorFallBack}>
<Suspense fallback={FullPageLoading}>
{
userInfo && Object.keys(userInfo).length ? <Authenticated/> : <UnAuthenticated/>
}
</Suspense>
</Boundary>
</div>
);
}

export default App;

使用React.memo

  • 使用后,只有当组件的props或者全局状态,比如redux发生变化的时候,才会执行重新渲染

Profiler

  • 生产环境禁止使用

    • 如果需要,在编译的时候添加(如果是create react app)创建的话
    1
    2
    yarn build --profile
    npm run build --profile

单元测试(React)

  • 我们setupWorker以前曾为开发创建了一个假服务器。现在我们使用不同的函数,setupServer因为测试将在 Node.js 环境中运行,而不是在实际的浏览器环境中。只有浏览器环境具有 Service Worker 功能,因此我们在测试中使用 MSW 的方式实际上并不涉及 Service Worker。

    • 也就是说setupServer在node环境下使用的,不涉及到浏览器,setupWorker在浏览器中运行的
  • 最基础的单元测试

    • 随便哪里建立一个sum函数
    1
    2
    3
    export function sum(a, b) {
    return a + b;
    }
    • src/tests/sun.ts文件(其实你取名叫sun.test.ts也可以,其实都会识别)
    1
    2
    3
    4
    5
    import {sum} from "../utils";

    test('测试结果是否为100',() => {
    expect(sum(50,50)).toBe(100)
    })
  • 然后运行yarn test

    • 可以看到自动去寻找了__tests__当中的文件进行测试

先看看msw拦截异步请求的代码怎么写

  • 总的来说就是: 创建handler,使用handler,开始拦截

  • 1.创建handler

1
2
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
import {rest} from "msw";

export const handlers = [
// 用于登录
rest.post('/login',(req,res,context) => {
sessionStorage.setItem('is-authenticated','true');//设置登录状态为真

return res(
context.status(200)
)
}),

// 用于获取用户信息
rest.get('/user',(req, res, context) => {
const isAuthenticated = sessionStorage.getItem('is-authenticated');
//未认证的用户
if(!isAuthenticated){
return res(context.status(403),context.json({
errorMessage:'未进行认证'
}))
}
// 已经认证的用户
return res(context.status(200),context.json({
username:'admin',
address:'dreamlove.top'
}))
}),
]

  • 2.使用handler
1
2
3
4
5
import { setupWorker,SetupWorker } from "msw";
import { handlers } from "./handler";

export const worker:SetupWorker = setupWorker(...handlers)

  • 3.开始拦截(只要匹配到了handler当中的列表,就进行拦截)
    • 我访问/login或者/list就会被拦截,从而返回假数据
1
2
3
4
5
6
import { worker } from "./mocks/browser"

if(import.meta.env.DEV){
/* 开发环境下就启动 */
worker.start();
}

setupServer和setupWorker

  • 也就是说setupServer在node环境下使用的,不涉及到浏览器,setupWorker在浏览器中运行的
  • 老师的代码没有过多解释,我们就以下面代码做个简单说明
    • 其实老师大概步骤也和上面msw拦截异步请求代码一样
    • 先监听,然后拦截,之后才是创建
1
2
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
35
36
37
38
import {setupServer} from "msw/node";
import { rest } from "msw";//用于发送假数据

const server = setupServer();

//文件内所有测试开始前执行的钩子函数
beforeAll(() => {
server.listen();
})

//文件内每个测试完成后执行的钩子函数(执行完test之后执行的)
afterEach(() => {
server.resetHandlers();
})

//文件内所有测试完成后执行的钩子函数
afterEach(() => {
server.close();
})

test('发送异步请求',async () => {
/* 其实这一步就是向handler添加数据 */
server.use(rest.get('/list',(req,res,ctx) => {
return res(ctx.status(200),ctx.json([
{name:'李白1',age:18},
{name:'李白2',age:19},
{name:'李白3',age:20},
]))
}));
const response = await fetch('/list')
const result = await response.json();
expect(result).toEqual([
{name:'李白1',age:18},
{name:'李白2',age:19},
{name:'李白3',age:20},
])
})

  • 其实很简单,server.use理解为向监视器里面添加东西,添加的东西会被拦截并做处理,只不过我们每次测试完毕,都将里面的handler进行了清空

额外小知识

  • encodeURIComponent(转义URL当中部分字符的)

  • encodeURI(转移整个URL内容的)

  • .env,.env.development

    • 当为npm start的时候.webpack会去读取.env.development文件
    • 当为npm run build编译之后,webpack会去读取.env.development文件
    • 并将读取的文件作为一个对象存储在process.env当中
    • 比如.env.development有如下内容
    1
    abc = 'www.baidu.com'
    • 那么我们就可以读取
    1
    process.env.abc
    • 如果是vite读取,则需要通过如下来读取,并且设置的变量必须要以VITE_开头
    1
    const apiUrl = import.meta.env
  • 遇到这种不明确的情况(比如name=),到底是搜索名字为空的,还是要忽略这种情况呢?所以我们需要在前端做处理

  • TS7016: Could not find a declaration file for module './screens/projectList'. 'D:/develop/phpstudy_pro/WWW/React-cli/react_17_projectJira/src/screens/projectList/index.jsx' implicitly has an 'any' type.这种报错

    • 就是缺少声明文件,要么自己添加对应的xxx.d.ts文件或者使用//@ts-ignore进行忽略
  • useHooks(不管是自带的hooks还是自己创建的hooks),不可以在普通函数中运行,只能在hooks当中使用或者其他hooks使用,所以在自定义hooks的时候,需要以useXxx开头

文章作者: 梦洁
文章链接: https://www.dreamlove.top/36e87c18.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

评论
avatar
梦洁
小小的字,有大大的梦想~分享我的前端学习过程,经历,错误,和一些其他折腾过程
关注下我(* ̄▽ ̄*)
公告
不断更新中,有问题请留言回复(会通过邮箱提醒~)
目录
  1. 1. 前言
    1. 1.1. REST-API风格
    2. 1.2. json-server
  2. 2. 项目开始
    1. 2.1. 用jsx渲染开发工程列表
    2. 2.2. 学习自定义hook
      1. 2.2.1. 使用自定义的useMount和useDebounce
      2. 2.2.2. js改造为ts
    3. 2.3. 鸭子类型和json-server中间件
    4. 2.4. 安装jira-dev-tool
    5. 2.5. 使用自定义useHttp处理登录状态
    6. 2.6. 更改为antd
    7. 2.7. 使用css-in-js-Emotion
    8. 2.8. grid和flex各自的应用场景
    9. 2.9. css-in-js:Row组件实现
    10. 2.10. 完成项目列表页面样式
    11. 2.11. 清除警告todo
    12. 2.12. 登录注册页面loading和error状态处理
      1. 2.12.1. 需要注意的是try-catch是同步的
      2. 2.12.2. 未捕获错误(Uncaught Errors)-错误边界
    13. 2.13. 使用useRef实现useDocumentTitle
    14. 2.14. 动态改变网页标签显示的标题#todo
    15. 2.15. 添加项目列表和项目详情
    16. 2.16. useSearchParams初步完成
    17. 2.17. useSearchParams完成(使用useMemo解决)
    18. 2.18. 完成的URL状态管理代码
    19. 2.19. 实现Id-Select解决Id难题
    20. 2.20. 用useEditProject编辑项目
      1. 2.20.1. 柯里化
      2. 2.20.2. 惰性初始化和使用useRef保存函数和useState保存函数的方法
        1. 2.20.2.1. 什么是惰性初始化
        2. 2.20.2.2. useRef
        3. 2.20.2.3. useState保存函数的方法
    21. 2.21. 优化异步请求
      1. 2.21.0.1. 什么时候使用useMemo,useCallback
  3. 2.22. 状态提升
  4. 2.23. useUndo
  5. 2.24. redux
  6. 2.25. reduxjs/tooltik和react-redux
    1. 2.25.1. 有关reduxjs/toolkit部分
    2. 2.25.2. 有关react-redux
    3. 2.25.3. 使用
    4. 2.25.4. 执行异步
      1. 2.25.4.1. 方法1:
      2. 2.25.4.2. 方法2
    5. 2.25.5. 总结
  7. 2.26. 用代码分割优化性能
  8. 2.27. 使用React.memo
  9. 2.28. Profiler
  10. 2.29. 单元测试(React)
    1. 2.29.1. 先看看msw拦截异步请求的代码怎么写
    2. 2.29.2. setupServer和setupWorker
  • 3. 额外小知识
  • 最新文章
    \ No newline at end of file diff --git a/36fd9db3.html b/36fd9db3.html new file mode 100644 index 000000000..e431eb1ca --- /dev/null +++ b/36fd9db3.html @@ -0,0 +1 @@ +前端SVG的学习 | 梦洁小站-属于你我的小天地

    前端SVG的学习

    目标

    rect矩形

    • 矩形使用 <rect> 标签,默认填充色是黑色,当只设置宽高时,渲染出来的矩形就是黑色的矩形。

    • 矩形基础属性:

      • x: 左上角x轴坐标

      • y: 左上角y轴坐标

      • width: 宽度

      • height: 高度

      • rx: 圆角,x轴的半径

      • ry: 圆角,y轴的半径

    • 通过 xy 可以设置矩形位置

      • 以图像左上角为起点进行移动

    • rx,ry如果只设置了一个的值,另一个值默认相同

    • 当rect的宽度高度相同并且rx值为宽度一半的时候,就是一个圆形了

    1
    2
    3
    4
    5
    6
    7
    <svg width="300" height="300" style="border: 1px solid red;">
    <rect
    width="100"
    height="100"
    rx="50"
    ></rect>
    </svg>

    圆circle

    圆形使用 <circle> 标签,基础属性有:

    • cx: 圆心在x轴的坐标
    • cy: 圆心在y轴的坐标
    • r: 半径
    • cx,cy都设置为0
    1
    2
    3
    <svg width="300" height="300" style="border: 1px solid red;">
    <circle cx="0" cy="0" r="50"></circle>
    </svg>

    ellipse椭圆

    椭圆使用 <ellipse> 标签,基础属性有:

    • cx: 圆心在x轴的坐标
    • cy: 圆心在y轴的坐标
    • rx: x轴的半径
    • ry: y轴的半径
    • <ellipse><circle> 差不多,只是将半径拆成x轴和y轴的。

    line线条

    很好理解,点组成了线,所以就需要2点的坐标去组成线

    直线使用 <line> 标签,基础属性有:

    • x1: 起始点x坐标
    • y1: 起始点y坐标
    • x2: 结束点x坐标
    • y2: 结束点y坐标
    • stroke: 描边颜色
    • 只有 x1x2 ,没有 x3 ,也没有 y3
    • 需要注意的是,<line> 需要使用设置 stroke 属性才会有绘制效果。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <svg width="300" height="300" style="border: 1px solid red;">
    <line
    x1="0"
    y1="0"
    x2="100"
    y2="100"
    stroke="black"
    ></line>
    </svg>

    polyline折线

    使用 <polyline> 可以绘制折线,基础属性有:

    • points点集(多个坐标点,可用逗号分隔,也可以不用)
    • stroke描边颜色
    • fill填充颜色
      • fill 默认值是黑色,不设置fillnone会自动闭合线条进行填充(前提设置了stroke颜色值)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <svg width="300" height="300" style="border: 1px solid red;">
    <polyline
    //效果相同points="10 10, 200 80, 230 230"
    points="10 10 200 80 230 230"
    stroke="blue"
    fill="none"
    >
    </polyline>
    </svg>
    • 很好理解,从(10,10)出发,到(200,80),再从(200,80)到(230,230) (坐标系)

    多边形polygon

    多边形使用 <polygon> 标签,基础属性和 <polyline> 差不多:

    • points: 点集
    • stroke: 描边颜色
    • fill: 填充颜色
      • <polygon> 会自动闭合(自动将起始点和结束点链接起来)。
    1
    2
    3
    4
    5
    6
    7
    <svg width="300" height="300" style="border: 1px solid red;">
    <polygon
    points="10 10 200 80 230 230"
    stroke="blue"
    fill="none"
    ></polygon>
    </svg>

    path直线路径

    其实在 SVG 里,所有基本图形都是 <path> 的简写。所有描述轮廓的数据都放在 d 属性里,ddata 的简写。

    d 属性又包括以下主要的关键字(注意大小写!):

    • M: 起始点坐标,moveto 的意思。每个路径都必须以 M 开始。M 传入 xy 坐标,用逗号或者空格隔开。
    • L: 轮廓坐标,lineto 的意思。L 是跟在 M 后面的。它也是可以传入一个或多个坐标。大写的 L 是一个绝对位置
    • l: 这是小写 L,和 L 的作用差不多,但 l 是一个相对位置
    • H: 和上一个点的Y坐标相等,是 horizontal lineto 的意思。它是一个绝对位置
    • h: 和 H 差不多,但 h 使用的是相对定位
    • V: 和上一个点的X坐标相等,是vertical lineto 的意思。它是一个绝对位置
    • v: 这是一个小写的 v ,和大写 V 的差不多,但小写 v 是一个相对定位。
    • Z: 关闭当前路径,closepath 的意思。它会绘制一条直线回到当前子路径的起点。
    1
    2
    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
      <svg width="300" height="300" style="border: 1px solid red;">
    <path
    d="M 10 10 L 100 10 L 100 300"
    stroke="blue"
    fill="none"
    >
    </path>
    </svg>

    如果全是使用大写 L 来描述每个点的位置,那可以把 L 也去掉,直接写点集,上面svg等同于下面
    <svg width="300" height="300" style="border: 1px solid red;">
    <path
    d="M 10 10 100 10 100 300"
    stroke="blue"
    fill="none"
    >
    </path>
    </svg>

    你也可以添加逗号分隔下坐标,效果也是一样的
    <svg width="300" height="300" style="border: 1px solid red;">
    <path
    d="M 10 10, 100 10, 100 300"
    stroke="blue"
    fill="none"
    >
    </path>
    </svg>

    • 步骤,画笔落在坐标轴(10,10),绘制(10,10)到(100,10)的线条,再绘制(100,10)到(100,300)的线条

    Z(闭合路径)

    • d 的数据集里,使用 Z 可以闭合路径。

    • 注意要大写

    1
    2
    3
    4
    5
    6
    7
    8
    <svg width="300" height="300" style="border: 1px solid red;">
    <path
    d="M 10 10 L 100 10 L 100 300 Z"
    stroke="blue"
    fill="none"
    >
    </path>
    </svg>

    l(轮廓坐标相对位置)

    • 使用 L 的小写方式 l 可以实现相对位置写法。
      • l 里的参数会与前一个点的 xy 进行相加,得到一个新的坐标。
      • 公式
      • 后一个坐标 = (前一个x坐标 + 现在x坐标,前一个y坐标 +现在y坐标)
    • **也就是我从你这个位置出发,再次走多少距离,**而不是想绝对位置一样告诉你到哪里
    1
    2
    3
    4
    5
    6
    7
    8
    <svg width="300" height="300" style="border: 1px solid red;">
    <path
    d="M 10 10 l 90 0 l -10 300 Z"
    stroke="blue"
    fill="none"
    >
    </path>
    </svg>
    • d="M 10 10 l 90 0 l -10 300 Z"等同于M 10 10 L 100 10 L 100 300 Z

    H和h(y轴绝对和相对)

    • H 后面只需传入 X坐标 即可,它的 Y坐标 与前一个点相同。
      • 不能写反了,必须要先H,在写入X坐标
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <svg width="310" height="300" style="border: 1px solid red;">
    <!-- 等同于d="M 10 0 L 100 0 Z"
    (10,0) 到 (300,0)
    -->
    <path
    d="M 10 0 H 300 Z"
    stroke="blue"
    fill="none"
    >
    </path>
    </svg>
    • 上面的代码中,d="M 10 10 H 100" 等同于 d="M 10 10 L 100 10"

    • h和小l作用差不多,都是从我这里开始出发,要走多少距离的意思
      • h传入的数据会和前一个点的 X坐标 相加
      • 公式
      • 后一个坐标 = (前一个x坐标 + 现在x坐标,前一个y坐标)
      • 相对谁,谁就不变
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
     //这里把width设置称为了310
    <svg width="310" height="300" style="border: 1px solid red;">
    <!-- 等同于d="M 10 0 L 100 0 Z"
    (10,0) 到 (300,0)
    -->
    <path
    d="M 10 0 H 300 Z"
    stroke="blue"
    fill="none"
    >
    </path>
    </svg>
    <svg width="310" height="300" style="border: 1px solid red;">
    <!--
    坐标计算
    (10,0) 到 (290 + 10,0)
    -->
    <path
    d="M 10 0 h 290 Z"
    stroke="blue"
    fill="none"
    >
    </path>
    </svg>

    V和v(x轴绝对和相对)

    • V 后面只需传入 Y坐标 即可,它的 X坐标 与前一个点相同。
      • 公式
      • 后一个坐标 = (前一个x坐标,现在的y坐标)
    1
    2
    3
    4
    5
    6
    7
    8
    <svg width="300" height="300" style="border: 1px solid red;">
    <path
    d="M 10 10 V 100"
    stroke="blue"
    fill="none"
    >
    </path>
    </svg>
    • d="M 10 10 V 100"等同于d = "M 10 10 10 100"

    • vV 的作用差不多,小写 v 是一个相对位置。
      • 后一个坐标 = ( 现在x坐标,前一个y坐标 + 现在y坐标)
      • 相对谁,谁就不变

    容易搞混怎么办?

    • 记住H相对于Y轴,V相当与X轴
    • 然后对应小写则是相加(减)与之相对坐标相反的,其他不用变
      • 比如小写h,大写的H是相对于Y轴,那么小写h计算坐标的时候就不要动y轴了,只变化x轴
      • 比如小写v,只需要变动y轴,x轴和他没关系,始终不变
    • 相对谁,谁就不变

    椭圆弧公式

    • 用Illustrator去做

    SVG 中可以使用 path 配合 A属性 绘制椭圆弧。

    1
    A(rx, ry, xr, laf, sf, x, y)
    • rx: 椭圆X轴半径
    • ry: 椭圆Y轴半径
    • xr: 椭圆旋转角度
    • laf: 是否选择弧长较长的那一段。0: 短边(小于180度); 1: 长边(大于等于180度)
    • sf: 是否顺时针绘制。0: 逆时针; 1: 顺时针
    • x: 终点X轴坐标
    • y: 终点Y轴坐标

    设置样式的方法

    设置 SVG 元素样式其实和 CSS 差不多,常见的方法有4种。

    • 属性样式
    • 内联样式
    • 内部样式
    • 外部样式

    属性样式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <svg width="400" height="400" style="border: 1px solid red;">
    <rect
    x="100"
    y="100"
    width="200"
    height="100"
    fill="pink"
    />
    </svg>

    内联样式

    把所有样式写在 style 属性里

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <svg width="400" height="400" style="border: 1px solid red;">
    <rect
    x="100"
    y="100"
    width="200"
    height="100"
    style="fill: pink;"
    />
    </svg>

    内部样式

    将样式写在 <style> 标签里,为标签添加类名或者其他,通过选择器进行样式更改

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <style>
    .rect {
    fill: pink;
    }
    </style>

    <svg width="400" height="400" style="border: 1px solid red;">
    <rect
    x="100"
    y="100"
    width="200"
    height="100"
    class="rect"
    />
    </svg>

    外部样式

    • 将样式写在 .css 文件里,然后在页面中引入该 CSS 文件。

    常用的属性

    • 接下来讲到的所有常规属性,除了在元素属性上设置之外,都支持在 CSS 中设置。
    • fill填充颜色
    • fill-opacity填充透明度
      • 取值是 0 - 10 代表完全透明,1 代表完全不透明。小于 0 的值会被改为 0,大于 1 的值会被改为 1
    • stroke描边颜色
    • stroke-opacity描边的不透明度
    • stroke-width描边的宽度
    • stroke-dasharray虚线描边
    • stroke-dashoffset虚线偏移量
    • stroke-linecap线帽
      • butt: 平头(默认值)
      • round: 圆头
      • square: 方头

    stroke-linecap

    • stroke-linejoin拐角
      • 拐角就是折线的交接点,可以使用 stroke-linejoin 设置,它接收以下属性:
      • miter: 尖角(默认)
      • round: 圆角
      • bevel: 平角

    stroke-linejoin

    • shape-rendering消除锯齿

    如果你觉得 SVG 在浏览器显示出来的图像有点模糊,那可能是开启了 反锯齿 功能,可以通过 CSS 属性关闭该功能。

    1
    shape-rendering: crispEdges;

    将该属性设置到对应的 svg 元素上,就会关闭反锯齿功能,突显看起来就会清晰很对,但在某些情况关闭了该功能会让图像看起来有点毛躁的感觉。

    如果想开启反锯齿功能,可以这样设置:shape-rendering: geometricPrecision;

    text(渲染文本)

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/36fd9db3.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/374f9bfd.html b/374f9bfd.html new file mode 100644 index 000000000..0b743f6f9 --- /dev/null +++ b/374f9bfd.html @@ -0,0 +1 @@ +QQ农场明月鲨鱼学习记录 | 梦洁小站-属于你我的小天地

    QQ农场明月鲨鱼学习记录

    前言

    [明月]重要概念

    URL栏简写

    1
    2
    3
    4
    5
    6
    7
    8
    9
    ??g_tk   等效    ?g_tk=XXXX
    &&g_tk 等效 &g_tk=XXXX
    {!nc} 等效 农场主链接地址:https://nc.qzone.qq.com/cgi-bin/
    {!ncf} 等效 农场主链接地址: https://farm.qzone.qq.com/cgi-bin/
    {!mc} 等效 牧场主链接地址: https://mc.qzone.qq.com/cgi-bin/
    {!wnc} 等效 文字版农场主链接地址
    {!wmc} 等效 文字版牧场主链接地址
    {!hydra}等效https://hydra.qzone.qq.com/cgi-bin/
    {!card}等效 魔卡主链接地址https://card.qzone.qq.com/cgi-bin/

    POST请求简写

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    &&uIdx,{uIdx}   等效    &uIdx=XXXX
    &&ownerId,{ownerId} 用户农牧场UID等效 &ownerId=XXXX
    &&uinY ,{uinY} 等效 &uinY=XXXX
    &&uin 用户QQ等效 &uin=XXXX
    &&uIdY 等效 &uIdY=XXXX
    &&uId 用户农牧场UID等效 &uId=XXXX
    {Nc} 等效 farmTime=XXXX&farmKey=XXXX&farmTime2=XXXX
    {Mc} 等效 farmTime=XXXX&farmKey=null&pastureKey=XXXX
    {Ncs} 等效 {Nc}&&uIdx&&uIdY
    {Mcs} 等效 {Mc}&&uIdY
    {wncs} 等效 {Ncs}&platform=XXXX,其中安卓是platform=14,苹果是platform=16
    {wmcs} 等效 {Mcs}&platform=XXXX,其中安卓是platform=14,苹果是platform=16

    Msg

    • 输出条件

    End项-根据条件跳转

    • 基本语法格式
      • 条件为:大于号,小于号,等于号,小于等于号等
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    {
    "End":"条件{另外一项名称(模糊匹配)}"
    }

    当然,也有更加复杂的,这里就不多解释了,用到的时候再说,附上大佬总结的内容
    End栏 “结束/跳转条件”设置方法(常用)
    ①需要设置多个不同的判断并循环结束/跳转方法时,请用;作为分割符区分,如果第1个判断为真,就执行第1个结束/跳转。如果第1个判断为假就判断第2个是否为真,以此类推
    ②简单方法: 88|89|90 表示符合888990中任意一个数立即结束处理
    ③叠加运算: 88{+3} 表示符合88并累计出现3次则结束处理
    ④跳转处理: 88{另一项} 表示符合88跳转到当前组另一项,88{+3:另一项}表示符合88并累计3次时跳转到另一项
    88|99|100{+3:&ownerId:另一项}都支持
    ⑤跳转名称设置方法:如果项名是”04项名” 简单设置成{04}即可,要图简单就必须养成良好的命名习惯
    {ok} 用于控制用户列勾选状态满足条件即取消勾选
    {end}用于控制多用户循环处理,满足条件即立即结束处理当前用户
    ⑧用多条件和跳转(重点): >10&<15{01};{msg}>10&{#num}<10{01} 满足全部条件时跳转
    ⑨用多条件或跳转(重点): >10|<15{01};{msg}>10|{#num}<10{01} 满足某个条件立即跳转
    {#t_s}+14400<=[t]{01} 当t_s+14400秒小于当前系统时间戳[t]时跳转01
    多条件和跳转时,判断语句的数值来源有三方面,输出过滤(如>10&<15),后台存值(比如{#num}<10),以及打印在消息栏的内容(比如{msg}>10)
    {*:另一项}:与Post里{*:数组}配合使用,表示遍历使用完数组元素后跳转到另一项.对于Post里{*:数组}来说,{*:另一项}{另一项}88{另一项}区别在于,{*:另一项}是遍历完再跳转则{另一项}只跳转一次,{另一项}是边遍历边跳转则数组长度有多大则跳转就有多少次,形如88{另一项}这种带条件的是边遍历边判断符合88的就跳转到另一项,符合88的有几次就跳转几次
    注意: 无条件跳转可能会触发死循环导致程序自动保护关闭,明月会杀进程的,得不偿失.建议都带上条件使用.
    • 现在有一个需求,就是查看当前的code,如果code等于1就跳转执行另外一项任务
    1
    2
    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
    {
    "牧场-收获时光(秋叶)":{
    "00查看并领取签到奖励":{
    "Url":"{!mc}cgi_pasture_kingdoms_activity?act=a16index",
    "Note":"秋叶(zmqdream@qq.com)",
    "Post":"{Mc}&&uIdx&pays=3",
    "Msg":"code",
    "MsgOut":"code",
    //当输出的code为1的时候,就跳转到{02}开头的项目
    //也就是跳转到02领取签到奖励
    "End": "=1{02}"
    },
    "01领取任务奖励":{
    "Url":"{!mc}cgi_pasture_kingdoms_activity?act=a16task",
    "Note":"秋叶(zmqdream@qq.com)",
    "Post":"{Mc}&&uIdx&pays=3&id={*:1~10}",
    "Msg":"s",
    "MsgOut":"s",
    "End": "登"
    },
    "02领取签到奖励":{
    "Url":"{!mc}cgi_pasture_kingdoms_activity?act=a16draw",
    "Note":"秋叶(zmqdream@qq.com)",
    "Post":"{Mc}&&uIdx&pays=3&id={*:1~7}",
    "Msg":"s",
    "MsgOut":"s",
    "End": "登"
    }
    }
    }
    • 我们执行看看

      • 填写=1{02}跳转到了02领取签到奖励

      • 填写=1{02领取签到奖励}跳转到了02领取签到奖励

    • 可以看到,就是模糊搜索进行跳转~
      • 如果有多个匹配到了,就会顺序执行
    • 顺带一提,如果在项名的尾部添加个 @ 符号 例如: 04项名@ 当处理到04项的时候将跳过不重复处理此项组执行是把所有不带@符号的项都执行一次,所以不保证同一个组里的代码执行跳转逻辑
      • 也就是我们执行组的时候该项会被跳过执行

    • 顺带一提,如果在项名的尾部添加二个@@符号,则表示禁止手动执行该项目

      • 也就是我们执行的时候会被禁止

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/374f9bfd.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/393ca5ba.html b/393ca5ba.html new file mode 100644 index 000000000..57f0ebbda --- /dev/null +++ b/393ca5ba.html @@ -0,0 +1 @@ +微信小程序相关知识点和云音乐项目制作遇到的问题及解决 | 梦洁小站-属于你我的小天地

    微信小程序相关知识点和云音乐项目制作遇到的问题及解决

    小程序项目想下载项目的可以下载看看~

    下载地址

    弹性盒布局和移动端适配的一些知识

    微信小程序的适配方案

    • 小程序适配单位:rpx
    • 小程序规定任何屏幕小的宽度为750rpx
      • 所以小程序会根据屏幕的宽度不同自动计算rpx值的大小
    • iPhone6情况下(其实也就是DPR为2的情况下) 1px = 2rpx
    • 举个例子,比如设计师在iPhone6尺寸(375*667)下设计UI图,那么有一张图片的大小为100px * 50px ,那么在微信小程序当中对应的rpx是多少呢? 相当于计算下方关系,当然,不需要我们自己去计算,微信小程序会自动转换
    • 计算得结果等于200,所以是200rpx

    什么是响应式和自适应

    • 一句话:响应式对不同页面做出代码变更,自适应不做代码变更

    响应式

    • 像素达到临界点就使用另外一套样式,通过媒体查询的方式,也就是据屏幕的大小自动的调整页面的展现方式

    • 响应式的概念应该是覆盖了自适应,但是包括的东西更多了

    自适应

    • 百分比布局,宽度使用百分比,文字使用em,rem等
    • 自适应是为了解决如何才能在不同大小的设备上呈现相同的网页

    小程序配置

    全局配置

    • @官网全局配置API地址
    • 小程序根目录下的 app.json 文件用来对微信小程序进行全局配置。文件内容为一个 JSON 对象,有以下属性:
    • 比如说配置页面底部的导航

    • 比如说配置页面路由项目

    • 比如说小程序标题名

    页面配置

    • @官网页面配置API
    • app.json当中的部分配置项和对单个页面的配置项(二者通用)
    • 在全局配置当中需要有window字段,而单个页面的配置项.json文件当中是不需要添加window字段的

    全局配置(app.json),需要添加window字段

    为某一个页面配置(比如说video页面video.json),就不需要添加window字段了

    sitemap

    小程序框架接口

    App.js

    • @官网API接口

    • App.js 只有一个,所以执行App({...})在里面,而在每一个页面执行的是Page({...})函数

    • 可以配置一些生命周期,并且可以用来全局数据globalData

    • App.js当中内容代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    App({
    onLaunch (options) {
    // Do something initial when launch.
    },
    onShow (options) {
    // Do something when show.
    },
    onHide () {
    // Do something when hide.
    },
    onError (msg) {
    console.log(msg)
    },
    //存储全局数据
    globalData: 'I am global data'
    })
    • 如果需要获取App.js所生成的实例化对象,其他js文件需要调用getApp()函数,经常用来读取设置的全局数据
    1
    2
    3
    // other.js
    var appInstance = getApp()
    console.log(appInstance.globalData) // I am global data

    xxx.js(不同页面不同的xxx.js)

    • @官网API

    • 每一个页面js当中执行的是Page({...})函数,App.js 只有一个,执行App({...})在里面

    • 不管有多少页码,每一个页面的xxx.js基本内容差不多都是这样子

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    // pages/test/test.js
    Page({

    /**
    * 页面的初始数据
    */
    data: {

    },

    /**
    * 生命周期函数--监听页面加载
    */
    onLoad(options) {

    },

    /**
    * 生命周期函数--监听页面初次渲染完成
    */
    onReady() {

    },

    /**
    * 生命周期函数--监听页面显示
    */
    onShow() {

    },

    /**
    * 生命周期函数--监听页面隐藏
    */
    onHide() {

    },

    /**
    * 生命周期函数--监听页面卸载
    */
    onUnload() {

    },

    /**
    * 页面相关事件处理函数--监听用户下拉动作
    */
    onPullDownRefresh() {

    },

    /**
    * 页面上拉触底事件的处理函数
    */
    onReachBottom() {

    },

    /**
    * 用户点击右上角分享
    */
    onShareAppMessage() {

    }
    })

    WXML语法

    • @官方API
    • 和vue不同的是,vue一般对于变量是要在属性前添加v-bind 或者简写为:的,微信小程序则需要通过{{ js代码 }}来操作变量
    数据绑定
    1
    2
    <!--wxml-->
    <view> {{message}} </view>
    • 提供数据的一方
      1
      2
      3
      4
      5
      6
      // page.js
      Page({
      data: {
      message: 'Hello MINA!'
      }
      })
    列表渲染
    • @官方API

    • 微信小程序如果需要操作的话,也需要绑定key

      • 绑定key的时候,不需要书写{{ }}符号,直接传入key值,比如循环的每一项为item,里面有一个字段为id,那么传入wx:key的时候只需要wx:key="id" ,而不是wx:key="{{id}}"
    • wx:for语句有别与vue,在vue当中需要手动指明循环的变量,而微信小程序如果在不指定的情况下,循环项目自动命名为item,当前循环的索引自动命名为index,也可以使用如下代码去重命名,不过,一般都是二三层循环的时候才去,一般都是默认情况

      1
      2
      3
      使用 wx:for-item 可以指定数组当前元素的变量名,

      使用 wx:for-index 可以指定数组当前下标的变量名:
    1
    2
    <!--wxml-->
    <view wx:for="{{array}}" wx:key="index"> {{item}} </view>
    1
    2
    3
    4
    5
    6
    // page.js
    Page({
    data: {
    array: [1, 2, 3, 4, 5]
    }
    })
    条件渲染
    • @官方API

    • 在框架中,使用 wx:if="" 来判断是否需要渲染该代码块:

    1
    2
    3
    <view wx:if="{{length > 5}}"> 1 </view>
    <view wx:elif="{{length > 2}}"> 2 </view>
    <view wx:else> 3 </view>

    block wx:if

    • 可以用于包括要判断的元素,并且不做渲染,这样子就不用在外面套一层view再去判断是否渲染了
    • <block/> 并不是一个组件,它仅仅是一个包装元素,不会在页面中做任何渲染,只接受控制属性。

    WXS文件

    事件系统/事件

    • @官方API-介绍
    • @官方API - 事件分类
    • 在学事件之前,我们先回顾理解下标准事件流和非标准事件流
    • 除上官网公布的事件之外的其他组件自定义事件如无特殊声明都是非冒泡事件,如 form 的submit事件,input 的input事件,scroll-view 的scroll事件,(详见各个组件)
    标准事件流
    • 捕获: 从最外层祖先开始捕获
    • 执行: 从最内层开始执行事件(执行相同的事件)
    • 冒泡:从最内层开始执行,从内向外执行的过程交冒泡
    非标准事件流
    • IE浏览器专属
    • 特点:没有捕获阶段
    事件格式及示例
    • bind + 事件名 (不会阻止事件冒泡)
    • catch + 事件名 (会阻止事件冒泡)
    • 常见的事件tap,就相当于PC端的click

    示例

    • 当用户点击该组件的时候会在该页面对应的 Page 中找到相应的事件处理函数。
    1
    2
    3
    4
    5
    <!--
    id以及h5的自定义属性data-xxx
    是为了在同一件事件回调函数当中可以区分是哪一个组件调用了
    -->
    <view id="tapTest" data-hi="Weixin" bindtap="tapName"> 单击我 </view>
    • 在相应的 Page 定义中写上相应的事件处理函数,参数是event。
    1
    2
    3
    4
    5
    Page({
    tapName: function(event) {
    console.log(event)
    }
    })

    打印输出event

    1
    2
    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
    {
    "type":"tap",
    "timeStamp":895,
    "target": {
    "id": "tapTest",
    "dataset": {
    "hi":"Weixin"
    }
    },
    "currentTarget": {
    "id": "tapTest",
    "dataset": {
    "hi":"Weixin"
    }
    },
    "detail": {
    "x":53,
    "y":14
    },
    "touches":[{
    "identifier":0,
    "pageX":53,
    "pageY":14,
    "clientX":53,
    "clientY":14
    }],
    "changedTouches":[{
    "identifier":0,
    "pageX":53,
    "pageY":14,
    "clientX":53,
    "clientY":14
    }]
    }

    数据绑定

    • 小程序没有自带的双向绑定,但是可以通过this.setData来实现修改数据后视图也变化
    • this.data.key = value修改data当中的数据,注意和vue进行区分**,vue为什么不用加一层data,那是因为vue做了数据劫持**
    • 这样子是不会引起视图的改变的! 小程序没有自带的双向绑定

    • 如果想要触发视图中数据的更新,那么就需要借助setData这个方法用了,setData的机制去把视图层和逻辑层做一个“中转站”两边连接起来。 注意这里的回调函数必须要用箭头函数,否者this指向不正确

    如图

    小程序获取,查询,元素/组件的信息

    • @官方API - boundingClientRect

    • @官方API -createSelectorQuery

    • 其功能类似于 DOM 的 getBoundingClientRect

    • 获取元素节点信息语法格式

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      NodesRef.boundingClientRect(function callback)

      //返回对象的信息均为px格式

      function callback
      回调函数,在执行 SelectorQuery.exec 方法后,节点信息会在 callback 中返回。
      返回对象
      属性 类型 说明
      id string 节点的 ID
      dataset Object 节点的 dataset
      left number 节点的左边界坐标
      right number 节点的右边界坐标
      top number 节点的上边界坐标
      bottom number 节点的下边界坐标
      width number 节点的宽度(包括padding,包括border)
      height number 节点的高度(包括padding,包括border)
    • wx.createSelectorQuery()

      • 返回一个 SelectorQuery 对象实例。在自定义组件或包含自定义组件的页面中,应使用 this.createSelectorQuery() 来代替。
    • 获取元素节点信息和查询节点案例

    wxml

    1
    2
    <!--pages/boundingClientRect/boundingClientRect.wxml-->
    <view class="list-container">查看我多大吧</view>

    wxss

    1
    2
    3
    4
    5
    6
    7
    8
    /* pages/boundingClientRect/boundingClientRect.wxss */
    .list-container{
    width: 300rpx;
    height: 300rpx;
    padding: 10rpx;
    background-color: red;
    border: 6rpx solid blue;
    }

    js

    1
    2
    3
    4
    5
    6
    7
    onLoad(options) {
    //select查询单个结果
    // 也有selectAll查询全部结果
    wx.createSelectorQuery().select(".list-container").boundingClientRect((res)=>{
    console.log(res);
    }).exec();
    },

    输出结果

    1
    2
    3
    4
    5
    6
    7
    8
    bottom: 166,
    dataset:{},
    height: 166,
    id: "",
    left: 0,
    right: 166,
    top: 0,
    width: 166

    vue和小程序都是单向数据流

    什么是单向数据流

    • 在父传子的前提下,父组件的数据发生会通知子组件自动更新
    • 子组件内部,不能直接修改父组件传递过来的props => props是只读的

    vue

    • 单向数据流

    • 但是实现了双向数据绑定 v-model 和 input事件结合使用

    • 修改状态数据

      • this.key = value
    • 同步修改 model数据结构 和 视图同时修改

    小程序

    • 单向数据流
    • 同步修改 model数据结构 和 视图 同时修改,通过this.setData

    App.json当中配置路由,路由跳转及需要注意的点

    注意点

    • App.json当中的配置项pages在配置路由页面的时候不可以再加上 /

    错误示范如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 不要在前面添加 /
    {
    "pages": [
    "/pages/index/index",
    "/pages/logs/logs",
    "/pages/test/test"
    ],
    "sitemapLocation": "sitemap.json"
    }

    正确示范如下

    1
    2
    3
    4
    5
    6
    7
    8
    {
    "pages": [
    "pages/index/index",
    "pages/logs/logs",
    "pages/test/test"
    ],
    "sitemapLocation": "sitemap.json"
    }
    • 在路由跳转的时候,必须要加上 / 才可以正确跳转
    1
    2
    3
    4
    //关闭当前页面,跳转路由
    wx.redirectTo({
    url:"/pages/logs/logs"
    })
    • 不同的路由跳转会有所区别,有的是不允许跳转到 tabbar 页面

    小程序使用分包

    • @官方API - 全局配置(app.json)当中的subpackages配置项

    • @官方API - 使用分包和示例

    • 特点

      • 加载小程序的时候先加载主播,当需要访问分包的页面时候才加载分包内容
      • 分包的页面可以访问主包的文件,数据,图片等资源
      • 目前小程序分包大小有以下限制:
        • 整个小程序所有分包大小不超过 20M
        • 单个分包/主包大小不能超过 2M
    • 主包

      • 主包通常放置启动页(tabBar)页面
    • 分包

      • 除了主包,其他就都是分包了
    • 常规分包和独立分包

      • 常规分包

        • 开发者通过在app.jsonsubpackages字段声明项目分包结构
        • 特点
          • 加载小程序的时候先加载主包,当需要访问分包页面的时候才加载分包内容
          • 分包的页面可以访问主包的文件,数据,图片等资源
      • 独立分包

        • app.jsonsubpackages字段当中的每一个配置项中添加independent为true

        • 特点

          • 独立分包可以单独访问分包的内容,不需要下载主包
          • 独立分包不能依赖主包或者其他包的内容
        • 独立分包使用场景

          • 通常某些页面和当前小程序的其他页面关联不大的时候可以进行独立分包
          • 比如: 临时加的广告页 或者是 活动页

    使用常规分包的具体步骤和示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    打包之前的app.json的pages配置项
    "pages": [
    "pages/index/index",
    "pages/songDetail/songDetail",
    "pages/video/video",
    "pages/search/search",
    "pages/recommendSong/recommendSong",
    "pages/personal/personal",
    "pages/login/login"
    ],
    (1).为分包建立文件夹
    1
    这里就建立一个名字叫做mysubpackages文件夹
    (2).将除了启动页(tabBar)的页面文件夹放置在刚刚创建的文件夹当中

    • 注意,这里为了演示,其实应该将这里文件夹放置在pages文件夹下方的,也就是/mysubpackages/pages文件夹下方,和主包的目录结构保持一致的
    (3).app.json配置项修改

    app.json当中的pages配置项只留下主包

    1
    2
    3
    4
    5
     "pages": [
    "pages/index/index",
    "pages/video/video",
    "pages/personal/personal"
    ],

    将分包写入app.json当中的subpackages配置项

    • subpackages 中,每个分包的配置有以下几项:
    1
    2
    3
    4
    5
    6
    字段	类型	说明
    root String 分包根目录
    name String 分包别名,分包预下载时可以使用
    pages StringArray 分包页面路径,相对于分包根目录
    完整路径为root+pages
    independent Boolean 分包是否是独立分包

    app.json当中subpackages配置项

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    "subpackages": [
    {
    "root": "mysubpackages",
    "name": "所有分包",
    "pages": [
    "login/login",
    "recommendSong/recommendSong",
    "search/search",
    "songDetail/songDetail"
    ]
    }
    ],
    (4).更改跳转路径
    1
    2
    3
    4
    5
    6
    7
    //跳转到搜索界面
    toSearchPage(){
    wx.navigateTo({
    // url: '/pages/search/search',
    url:"/mysubpackages/login/login"
    });
    },
    (5).目录结构

    分包预下载

    微信小程序生命周期

    • @官方API - 生命周期
    • 复习下vue的生命周期
      • beforeCreated,created,beforeMount,mounted,beforeUpdate,updated,beforeDestroy,destroyed
    • 小程序生命周期
      • onLoad
      • onShow(会重复出现)
      • onReady
      • onHide 路由重定向的时候如果选择了比如说wx.navigatorTo之类的保活的,就会产生隐藏生命周期
      • onUnload 相当于是销毁的生命周期
    • 具体的图片

    小程序使用npm包步骤

    (1).初始化

    1
    npm init -y

    (2).勾选使用npm(有的项目需要勾选,现在没有这个选项了)

    (3).下载需要的包

    1
    npm install moment --save

    (4).引入包

    1
    2
    // pages/boundingClientRect/boundingClientRect.js
    import moment from "moment"

    (5).引入包后构建npm

    • 开发工具 —> 工具 —> 构建npm
    • 会将node_modules中的包打包到miniprogram_npm中

    如果步骤错了,会报这个错误module 'pages/boundingClientRect/moment.js' is not defined, require args is 'moment''

    只需要再次构建下npm就可以

    • 注意
      • 测试的时候引入axios包,然后按照这个流程发现不行,可能是小程序不支持axios

    小程序的ajax请求发送和二次封装

    • @官方API - wx.request
    • 需要注意的是
      • 在h5的时候,全局对象为window,但是在微信小程序是没有window的,取而代之的全局对象是wx
      • 所以是用wx.xxxx来调用微信提供的方法
      • wx.request规定必须是https协议 ,最大并发为10

    ajax请求发送

    发送请求示例代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    wx.request({
    url: 'example.php', //仅为示例,并非真实的接口地址
    data: {
    x: '',
    y: ''
    },
    header: {
    'content-type': 'application/json' // 默认值
    },
    success (res) {
    console.log(res.data)
    }
    })

    二次封装

    1
    2
    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
    // 二次封装请求
    import config from "./config";
    /**
    * params:
    * url:请求地址
    * method:请求方式(默认get)
    * data:请求数据(默认 {} )
    */
    export default (url, data = {}, method = "get") => {
    return new Promise((resolve, reject) => {
    wx.request({
    url: config.host + url,
    method,
    data,
    header: {
    //写入配置项
    },
    success(res) {
    //Do Something
    },
    fail(reason) {
    //返回错误原因
    reject(reason);
    }
    });
    });
    }

    小程序使用自定义组件

    (1)建立文件夹

    • 1.先依次建立一个文件夹叫componets/NavHeader

    (2)右键新建Component

    • 2.1.在NavHeader文件夹右键,选中新建Component,输入与文件夹同名的名称
    • 2.2.NavHeader文件夹当中的结构和普通的页面结构一样,唯一不同的是不会自动在app.json当中的配置项pages中注册,因为不是页面嘛

    image-20220713161024645

    (3)构建NavHeader的结构

    • 3.1 书写NavHeader结构并使用NavHeader.js当中的propertyies属性

      • 可以看到,选择新建Component生成的js文件会自动添加如下内容,我们使用的多的,就是 properties
      1
      2
      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
      // components/NavHeader/NavHeader.js
      Component({
      /**
      * 组件的属性列表
      */
      properties: {
      //书写自定义属性
      title:{
      //指明属性类型
      type:String,
      value:"未传入时候的默认值-title"
      },
      desc:{
      type:String,
      value:"未传入时候的默认值-desc"
      }
      },

      /**
      * 组件的初始数据
      */
      data: {

      },

      /**
      * 组件的方法列表
      */
      methods: {

      }
      })

    • 3.2 在NavHeader.wxml中使用NavHeader.jsproperties属性

      1
      2
      3
      4
      5
      <!--components/NavHeader/NavHeader.wxml-->
      <view>
      <text>标题:{{title}}</text>
      <text>描述:{{desc}}</text>
      </view>

    (4)其他组件注册使用自定义组件

    • 在要使用此组件的页面的xxx.json里面的配置项目usingCompoents去注册,比如下方的index.json当中注册使用组件NavHeader

    index.json当中使用自定义组件NavHeader(相当于注册组件吧有点)

    1
    2
    3
    4
    5
    6

    {
    "usingComponents": {
    "NavHeader":"/components/NavHeader/NavHeader"
    }
    }

    (5使用并传值

    index.wxml中使用(也就是xxx.wxml中使用)

    1
    2
    <!-- 使用自定义组件 -->
    <NavHeader title="我是标题" desc="我是描述"></NavHeader>

    tabBar底部导航的实现

    • @官方API- tabBar

    • tabBar配置项用的比较多的

      • color: tab 上的文字默认颜色,仅支持十六进制颜色
      • selectedColor:tab 上的文字选中时的颜色,仅支持十六进制颜色
      • backgroundColor:tab 的背景色,仅支持十六进制颜色
      • list: tab 的列表,详见 list 属性说明,最少 2 个、最多 5 个 tab
    • 注意的是

      • 假如有需求需要计算一个容器的高度比如通过calc计算,那么在计算高度的时候,比如减去一部分的高度,这个时候可以忽略tabBar的高度,而不用减去,因为微信小程序会自动处理

    示例 app.json当中添加配置项

    1
    2
    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
    {
    "tabBar": {
    "color": "#333",
    "selectedColor": "#d43c33",
    "backgroundColor": "#fff",
    "list":
    [
    {
    "pagePath": "pages/index/index",
    "text": "首页",
    "iconPath": "/static/images/tabs/tab-home.png",
    "selectedIconPath": "/static/images/tabs/tab-home-current.png"
    },
    {
    "pagePath": "pages/video/video",
    "text": "视频",
    "iconPath": "/static/images/tabs/video.png",
    "selectedIconPath": "/static/images/tabs/video-current.png"
    },
    {
    "pagePath": "pages/personal/personal",
    "text": "个人中心",
    "iconPath": "/static/images/tabs/tab-my.png",
    "selectedIconPath": "/static/images/tabs/tab-my-current.png"
    }
    ]
    },
    }

    效果图

    js文件使用this.data.xxx和this.xxx和this.setData的区别

    1
    2
    3
    4
    5
    6
    // pages/test/test.js
    Page({
    data: {

    },
    })

    this.data.xxx

    • this.data.xxx 操作当前配置项当中data的属性值,但是操作不会更新视图

    this.xxx

    • 操作当前由Page生成的实例化对象上的属性
    • 可以用来绑定一个共用,唯一的值,比如说单例

    this.setData

    • this.setData({ value: 'leaf' })可以更新配置项当中data的属性值,并且会引发视图更新
    1
    2
    3
    this.setData({
    要修改的data当中的key值: 新值
    })

    元素当中id和data-xxx在辨别数据的使用

    • 有时候我们想一个函数被二个不同功能的组件所触发,但是所处理的功能不同,就可以为组件添加id或者是data-xxx来区分
    • id区分和data-xxx区分主要区别就是id只能有一个,而data-xxx可以有多个
    • 在回调函数的事件对象当中通过currentTarget 或者target来获取id或者data-xxx属性
      • currentTarget和target的区别主要在于是否是事件委派,如果没有事件委派,那么二者没有区别
      • 如果有事件委派/委托,区别如下
      • currentTarget是绑定事件的元素(父元素)
      • target: 是触发事件的元素(子元素)

    有如下结构

    可以看到,test.wxml的二个view组件都赋予了同一个回调函数,那么如何区分呢?这里添加了iddata-index

    1
    2
    3
    4
    5
    6
    7
    <!--pages/test/test.wxml-->
    <text>\n</text>
    <text>\n</text>
    <text>\n</text>
    <view bindtap="handleTouch" id="one" data-index="0">我是组件1</view>
    <text>\n</text>
    <view bindtap="handleTouch" id="two" data-index="1">我是组件2</view>

    test.js内容

    1
    2
    3
    handleTouch(event){
    console.log(event);
    },

    我们任意单击一个view,输出查看下事件对象event,可以看到,currentTarget和target记录着一些数据,正好是我们所设置的数据,那么就可以以此来区分是哪一个组件触发的了

    完整js代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    handleTouch(event) {
    //获取组件的id
    let id = event.target.id;
    if (id === 'one') {
    console.log("组件1触发的");
    }
    if (id === 'two') {
    console.log("组件2触发的");
    }
    },

    当然,也可以通过data-xxx来区分

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    handleTouch(event) {
    //获取组件的id
    let index = event.target.dataset.index;
    if (index === '0') {
    console.log("组件1触发的");
    }
    if (index === '1') {
    console.log("组件2触发的");
    }
    },

    文本超出显示省略号或文本超出两行后显示省略号

    • 需要注意的是
      • overflow属性只会在块级元素才会生效

    文本超出显示省略号

    • 关键代码就是下面这三行
    1
    2
    3
    white-space:nowrap;
    text-overflow:ellipsis;
    over-flow:hidden

    示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <title>Document</title>
    <style>
    .show{
    width: 200px;
    height: 50px;
    border: 1px solid red;
    /* 设置超出部分为省略号代替 */
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    }
    </style>
    </head>
    <body>
    <div class="show">
    本人也是经过了深思熟虑,在每个日日夜夜思考这个问题。 这种事实对本人来说意义重大,相信对这个世界也是有一定意义的。 那么, 既然如此, 问题的关键究竟为何? 就我个人来说,对我的意义,不能不说非常重大
    </div>
    </body>
    </html>

    文本超出两行后显示省略号

    • 关键代码如下
    1
    2
    3
    4
    display: -webkit-box;
    -webkit-box-orient: vertical;
    /* 设置超出几行省略 */
    -webkit-line-clamp: 2;

    示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <title>Document</title>
    <style>
    .show {
    width: 200px;
    border: 1px solid red;
    text-overflow: ellipsis;
    overflow: hidden;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    /* 设置超出几行省略 */
    -webkit-line-clamp: 2;
    }
    </style>
    </head>

    <body>
    <p class="show">
    本人也是经过了深思熟虑,在每个日日夜夜思考这个问题。 这种事实对本人来说意义重大,相信对这个世界也是有一定意义的。 那么, 既然如此, 问题的关键究竟为何? 就我个人来说,对我的意义,不能不说非常重大
    </p>
    </body>

    </html>

    小程序的轻提示(Toast)

    • @官方API - Toast
    • 和其他不同的就是小程序的轻提示除了none,其他都只限于7个汉字的长度
    • 并且小程序的轻提示-wx.showLoading必须要调用wx.hideToast进行手动关闭才可以,其他的就不用

    小程序当中视频的操作

    • @官方API - video

    • 想要操作视频,比如说跳转到指定位置,倍速播放的时候,就需要对视频(video)进行操作了

    • 大致操作就是先创建属于这个视频的上下文对象,然后对上下文对象进行操作

      • 设计到视频的操作有很多,可以查看官方API的说明~
      • bindtimeupdate : 播放进度变化时触发
      • bindfullscreenchange: 视频进入和退出全屏时触发
    • 下方步骤为操作一个video的基本步骤

    (0)wxml基本结构

    1
    2
    3
    4
    5
    6
    <!--pages/videotest/videotest.wxml-->
    <view>
    <!-- 视频1 -->
    <video id="1" src="http://vfx.mtime.cn/Video/2019/03/18/mp4/190318214226685784.mp4"></video>
    <button bindtap="handlePlay">播放/暂停视频1</button>
    </view>

    (1)创建视频上下文对象

    1
    2
    3
    4
    5
    // pages/videotest/videotest.js 
    onLoad(options) {
    //创建视频上下文对象,并绑定在当前页的实例对象上
    this.videoContext = wx.createVideoContext("1");
    },

    (2)控制视频的播放/暂停/跳转

    1
    2
    3
    4
    5
    6
    7
    8
    handlePlay() {
    //视频播放
    this.videoContext.play();
    //视频暂停
    // this.videoContext.pause();
    //视频跳转 - 传入的为s
    this.videoContext.seek(40);
    },

    补充:由于微信问题,视频播放下一个的时候不会自动暂停上一个视频,所以这里需要使用视频上下文进行处理

    • 演示内容,当播放视频1的时候,又跑去播放视频2,那么视频1会被暂停播放

    • 示例wxml基本结构

      • 记得绑定bindplay
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <!--pages/videotest/videotest.wxml-->
    <text>视频测试</text>
    <view>
    <!-- 视频1 -->
    <!-- video添加id,用于获取上下文对象-->
    <video id="1" src="http://vfx.mtime.cn/Video/2019/03/18/mp4/190318214226685784.mp4" bindplay="handlePlay"></video>
    </view>
    <view>
    <!-- 视频2 -->
    <!-- video添加id,用于获取上下文对象-->
    <video id="2" src="http://vfx.mtime.cn/Video/2019/03/19/mp4/190319104618910544.mp4" bindplay="handlePlay"></video>
    </view>

    js代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    handlePlay(event) {
    //1.先获取下视频的id
    let videoId = event.target.id;
    //1.5 判断之前是否创建过了,如果创建过了,则暂停播放
    if(this.videoContext){
    //暂停上一个视频
    this.videoContext.pause();
    }
    //2.创建视频的上下文对象
    this.videoContext = wx.createVideoContext(videoId);

    },

    小程序的video组件设置为100%出现黑色边框

    如图

    scroll-view滚动到指定位置并且有过渡效果

    • @官网API -scroll-view
    • 实现滚动到指定位置的二个属性
      • scroll-into-view: 值应为某子元素id(id不能以数字开头)。设置哪个方向可滚动,则在哪个方向滚动到该元素
      • scroll-with-animation : 在设置滚动条位置时使用动画过渡
    • 实现原理
      • 通过动态设置scroll-into-view的值来实现

    效果

    代码实现

    wxml

    1
    2
    3
    4
    5
    6
    7
    8
    <!--pages/bar/bar.wxml-->
    <scroll-view class="nav-tab" enable-flex scroll-x scroll-into-view="{{'scroll'+selectedId}}" scroll-with-animation>
    <view class="nav" wx:for="{{navList}}" wx:key="id" id="{{'scroll'+item.id}}">
    <view class="nav-item {{selectedId==item.id?'active':''}}" bindtap="changNavTab" id="{{item.id}}">
    {{item.name}}
    </view>
    </view>
    </scroll-view>

    js

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    // pages/bar/bar.js
    Page({

    /**
    * 页面的初始数据
    */
    data: {
    //数据列表
    navList: [{
    "id": 58100,
    "name": "现场",
    "url": null,
    "relatedVideoType": null,
    "selectTab": false,
    "abExtInfo": null
    },
    {
    "id": 60100,
    "name": "翻唱",
    "url": null,
    "relatedVideoType": null,
    "selectTab": false,
    "abExtInfo": null
    },
    {
    "id": 1101,
    "name": "舞蹈",
    "url": "",
    "relatedVideoType": null,
    "selectTab": false,
    "abExtInfo": null
    },
    {
    "id": 58101,
    "name": "听BGM",
    "url": null,
    "relatedVideoType": null,
    "selectTab": false,
    "abExtInfo": null
    },
    {
    "id": 262158,
    "name": "万有引力",
    "url": null,
    "relatedVideoType": null,
    "selectTab": false,
    "abExtInfo": null
    },
    {
    "id": 261121,
    "name": "告白",
    "url": null,
    "relatedVideoType": null,
    "selectTab": false,
    "abExtInfo": null
    },
    {
    "id": 259132,
    "name": "云村放映厅",
    "url": null,
    "relatedVideoType": null,
    "selectTab": false,
    "abExtInfo": null
    },
    {
    "id": 259129,
    "name": "超燃联盟",
    "url": null,
    "relatedVideoType": null,
    "selectTab": false,
    "abExtInfo": null
    },
    {
    "id": 264120,
    "name": "热歌看得见",
    "url": null,
    "relatedVideoType": null,
    "selectTab": false,
    "abExtInfo": null
    },
    {
    "id": 243125,
    "name": "#歌手#",
    "url": "",
    "relatedVideoType": null,
    "selectTab": false,
    "abExtInfo": null
    },
    {
    "id": 243123,
    "name": "致敬英雄",
    "url": "",
    "relatedVideoType": null,
    "selectTab": false,
    "abExtInfo": null
    },
    {
    "id": 254120,
    "name": "滚石唱片行",
    "url": "",
    "relatedVideoType": null,
    "selectTab": false,
    "abExtInfo": null
    },
    {
    "id": 249121,
    "name": "宫崎骏",
    "url": "",
    "relatedVideoType": null,
    "selectTab": false,
    "abExtInfo": null
    },
    ],
    //默认选中项
    selectedId:'58100'
    },
    //单击标签跳转到指定标签
    changNavTab(event){
    this.setData({
    selectedId:event.target.id
    })
    },

    })

    wxss

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /* pages/bar/bar.wxss */
    .nav-tab{
    display: flex;
    white-space: nowrap;
    margin-top: 15rpx;
    height: 60rpx;
    /* flex-wrap: nowrap; */
    }
    .nav-tab .nav {
    padding: 0 10rpx;
    margin: 0 10rpx;
    height: 60rpx;
    }
    .nav-tab .nav .nav-item{
    padding-bottom: 7rpx;
    font-size: 28rpx;
    }
    .nav-tab .nav .nav-item.active{
    border-bottom: 1rpx solid #ee5d44;
    }

    用户转发分享和自定义转发分享内容

    如果想要区分是当前页面调用了button触发的分享还是右上角转发菜单触发的分享,只需要在onShareAppMessage回调当中结构出from,通过from判断

    1
    2
    3
    4
    5
    6
    7
    8
    onShareAppMessage({from}) {
    if (from === 'menu') {
    //右上角转发菜单调用的分享
    } else {
    //为按钮调用的分享

    }
    }

    button实现

    • 通过设置button的open-type属性为share,即成为了一个分享功能的按钮,其实这样子已经可以使用了,但是我们可以自定义分享的图片和标题~
    • 想要自定义,就需要在对应页面的js文件当中添加onShareAppMessage回调
    • @官网API-onShareAppMessage

    wxml

    1
    2
    <!--pages/share/share.wxml-->
    <button open-type="share">单击我分享</button>

    js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // pages/share/share.js
    Page({
    /**
    * 用户点击右上角分享
    */
    onShareAppMessage() {
    //如果都留空,则都采取默认值
    return {
    title:"我来分享啦~~~我是自定义内容",
    imageUrl:"https://dreamlove.top/img/favicon.png"
    }
    }
    })

    自定义内容后的效果

    默认值效果,图片为自动截取的

    微信小程序背景音频的播放

    (1)获取全局唯一的背景音频管理对象,返回BackgroundAudioManager 实例

    • @官方API - BackgroundAudioManager

    • 注意的是

      • 从微信客户端6.7.2版本开始,若需要在小程序切后台后继续播放音频,需要在 app.json 中配置 requiredBackgroundModes 属性。开发版和体验版上可以直接生效,正式版还需通过审核。

        1
        2
        3
        4
        5
        {
        "requiredBackgroundModes": [
        "audio"
        ],
        }
    1
    2
    3
    4
    onLoad(options) {
    //获取全局唯一的背景音频管理对象
    this.musicManager = wx.getBackgroundAudioManager();
    },

    (2)操作BackgroundAudioManager 实例

    • @官方API - BackgroundAudioManager 实例
    • 比较常用的实例属性
      • startTime: 音频开始播放的位置(单位:s)。
      • src:音频的数据源(2.2.3 开始支持云文件ID)。默认为空字符串,当设置了新的 src 时,会自动开始播放,目前支持的格式有 m4a, aac, mp3, wav。
      • title:**(必填)**音频标题,用于原生音频播放器音频标题(必填)。原生音频播放器中的分享功能,分享出去的卡片标题,也将使用该值。
      • duration:当前音频的长度(单位:s),只有在有合法 src 时返回。(只读)
      • currentTime:当前音频的播放位置(单位:s),只有在有合法 src 时返回。(只读)
    • 比较常用的方法
      • play(): 播放音乐
      • pause(): 暂停音乐
      • seek(跳转到的位置): 跳转到指定位置(单位为s)
      • onTimeUpdate(callback):监听背景音频播放进度更新事件,只有小程序在前台时会回调。
      • onEnded():监听背景音频自然播放结束事件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    onLoad(options) {
    //获取全局唯一的背景音频管理对象
    this.musicManager = wx.getBackgroundAudioManager();
    //设置背景音频管理对象的一些属性
    //当设置了src的时候,音频会自动播放
    this.musicManager.src = " http://downsc.chinaz.net/Files/DownLoad/sound1/201906/11582.mp3";
    //还必须要设置title属性
    this.musicManager.title = "测试音频";
    },

    微信小程序的授权登录和获取openid

    wx.getUserProfile( 2022 年 10 月 25 日 24 时后 会失效)

    (1).button按钮绑定回调
    1
    2
    <!--pages/userprofile/userprofile.wxml-->
    <button bindtap="handleUserInfo">单击我申请授权</button>
    (2).自定义一个函数,这里取名 handleUserInfo 并且添加wx.getUserProfile方法 需要注意的是,事件名都是小写
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // pages/userprofile/userprofile.js
    handleUserInfo() {
    wx.getUserProfile({
    desc: '请求授权',
    success: (res) => {
    console.log("用户同意授权");
    console.log(res);
    },
    fail: (reason) => {
    console.log("用户拒绝授权");
    console.log(reason);
    }
    })
    },
    (3).处理授权结果
    • 成功结果输出

    • 失败结果输出
    1
    {errMsg: "getUserProfile:fail getUserProfile:fail auth deny"}

    获取openid

    • @官方API - wx.login

    • @官方API - auth.code2Session

    • wx.loginauth.code2Session结合使用

    • wx.login

      • 调用接口获取登录凭证(code)。通过凭证进而换取用户登录态信息,包括用户在当前小程序的唯一标识(openid)、微信开放平台帐号下的唯一标识(unionid,若当前小程序已绑定到微信开放平台帐号)及本次登录的会话密钥(session_key)等。用户数据的加解密通讯需要依赖会话密钥完成。
    • auth.code2Session

      • 登录凭证校验。通过 wx.login 接口获得临时登录凭证 code 后传到开发者服务器调用此接口完成登录流程。更多使用方法详见 小程序登录
    • 步骤如下

    (1).wx.login获取登录凭证
    1
    2
    3
    4
    5
    6
    7
    8
    handleOpenid() {
    wx.login({
    success: (res) => {
    //获取登录凭证
    let code = res.code;
    }
    })
    },
    (2).有了登录凭证,再通过auth.code2Session获取openid
    • 这里我没有自己后台服务器,就直接这样子用了,其实可以通过后台来调用,这样子就不会泄露自己的开发信息了
    1
    2
    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
    //获取openid
    handleOpenid() {
    wx.login({
    success: (res) => {
    //获取登录凭证
    let code = res.code;
    if (code) {
    //凭证存在,接着请求换取openid
    wx.request({
    url: 'https://api.weixin.qq.com/sns/jscode2session',
    data: {
    //登陆换取的凭证
    js_code: code,
    //小程序的appid
    appid: "",
    //小程序的APPSecret
    secret: "",
    //授权类型,固定值authorization_code
    grant_type: "authorization_code",
    },
    success: (res) => {
    console.log("获取openid成功");
    console.log(res);
    },
    fail: (error) => {
    console.log("获取openid失败", error);
    }
    })
    }
    }
    })
    },
    (3).处理结果
    • 授权成功输出

    • 授权失败输出这里错误的appid

    小程序更新数据状态的区别

    • 实时更新(每发一次请求就更新一部分数据)
      • 优点: 用户等待时间较短
      • 缺点: 多次更新页面
    1
    2
    3
    4
    5
    6
    7
    for(循环请求数据){
    let 请求的数据 = 发送请求;
    //实时更新数据
    this.setData({
    topList:resultArray
    });
    }
    • 统一更新
      • 优点: 减少更新的次数,只更新1次
      • 缺点: 网络较差的时候用户等待时间过长,可能会看到白屏
    1
    2
    3
    4
    5
    6
    7
    8
    9
    let 全部数据 ; 
    for(循环请求数据){
    let 请求的数据 = 发送请求;
    将请求的数据添加到全部数据
    }
    //统一更新数据
    this.setData({
    topList:全部数据
    });

    小程序的数据存储

    • @官方API - 操作数据存储

    • 不同小程序的数据存储相互独立,互不干扰. @官方解释网站

    • 同一个微信用户,同一个小程序 storage 上限为 10MB。storage 以用户维度隔离,同一台设备上,A 用户无法读取到 B 用户的数据;不同小程序之间也无法互相读写数据。

    • 插件隔离策略

      • 同一小程序使用不同插件:不同插件之间,插件与小程序之间 storage 不互通。
      • 不同小程序使用同一插件:同一插件 storage 不互通。
    • 与h5的storage不同的是,h5的需要转化为json格式的字符串后存储,而小程序可以直接存储对象,也可以存储json格式字符串

    wx.setStorageSync(存数据)

    • 将数据存储在本地缓存中指定的 key 中。会覆盖掉原来该 key 对应的内容。除非用户主动删除或因存储空间原因被系统清理,否则数据都一直可用。单个 key 允许存储的最大数据长度为 1MB,所有数据存储上限为 10MB。

    示例 - 直接存储对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    let data = {
    "objects": [{
    "ID": "1",
    "JobTitle": "Software Engineer",
    "EmailAddress": "Gil_West3007@elnee.tech",
    "FirstNameLastName": "Gil West"
    },
    {
    "ID": "2",
    "JobTitle": "Associate Professor",
    "EmailAddress": "Leslie_Little2936@naiker.biz",
    "FirstNameLastName": "Leslie Little"
    },
    ]
    }
    wx.setStorageSync('userInfo', data.objects);

    调试器信息 - Storage项

    wx.getStorageSync(取数据)

    1
    2
    let data1 = wx.getStorageSync('userInfo');
    console.log("获取的数据",data1);

    调试器输出信息

    小程序scroll-view的下拉刷新

    • @官方API-scroll-view组件
    • scroll-view需要开启和设置的属性
      • refresher-triggered: 设置当前下拉刷新状态,true 表示下拉刷新已经被触发,false 表示下拉刷新未被触发(会显示相应的效果)
      • refresher-enabled: 是否开启自定义下拉刷新,true开启,false关闭(默认)
    • scroll-view需要添加的回调或监听
      • bindrefresherrefresh:自定义下拉刷新被触发
    • scroll-view需要实现上拉加载更多只需要添加
      • bindscrolltolower 滚动到底部/右边时触发就可以
    • 顺带一提
      • 使用calc计算scroll-view的高度的时候,calc的计算符号和数字之间必须要分开来才可以,否者计算会无效!并且如果小程序有tabBar(底部导航),可以不用减去底部导航的高度(小程序会自动处理)

    示例

    • 模拟请求发送数据,1s后关闭下拉刷新并显示提示

    效果图

    wxml

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <!--pages/refreshscroll/refreshscroll.wxml-->
    <scroll-view class="item-container"
    scroll-y refresher-enabled
    refresher-triggered="{{isRefresh}}"
    bindrefresherrefresh="handlePullRefresh"
    >
    <view class="item">我是内容</view>
    <view class="item">我是内容</view>
    <view class="item">我是内容</view>
    <view class="item">我是内容</view>
    <view class="item">我是内容</view>
    </scroll-view>

    js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // pages/refreshscroll/refreshscroll.js
    Page({
    /**
    * 页面的初始数据
    */
    data: {
    isRefresh:false,//是否处于正在刷新的状态
    },
    handlePullRefresh(){
    // console.log("用户下拉刷新了");
    //当用户触发下拉刷新,data当中的'isRefresh'的值会自动被小程序更改为true
    //模拟请求发送数据,1s后关闭下拉刷新并显示提示
    setTimeout(()=>{
    //设置当前下拉刷新状态
    this.setData({
    isRefresh:false
    });
    //显示用户提示
    wx.showToast({
    title: '刷新成功!',
    });
    },1000);
    },
    })

    wxss

    1
    2
    3
    4
    5
    /* pages/refreshscroll/refreshscroll.wxss */
    .item-container{
    border: 1rpx solid red;
    height: 400rpx;
    }

    为什么小程序没有引入css样式都会生效?

    • 在html开发的时候,我们会通过@import或者link标签来引入css文件,但是微信小程序却不用我们去引入,那是为什么?

    • 我们在app.json的配置项pages已经书写了一条,比如下面代码,我们知道,所有页面都需要写在pages当中

      1
      2
      3
      4
      5
      6
      {
      "pages": [
      "pages/index/index",
      ],
      "sitemapLocation": "sitemap.json"
      }
    • 在我们进入页面,比如说index页面的时候,小程序回去寻找pages/index文件夹,然后依次访问,寻找与访问页面同名的文件,index.wxml, index.wxss ,index.js ,index.json

      • 所以你可以试试看,把index.wxss改为index11.wxss,然后再去访问index页面,就会丢失样式

    @import 和import 引入css区别和小程序引入css?

    • 一句话@import引入css样式在html文件,或者css文件当中,
    • 而import是用来引入模块的,当然,如果有webpack工具也是可以通过import引入css文件的

    @import的用法

    • (1) html当中的link标签中使用
    1
    <link rel="stylesheet" type="text/css" href="css文件路径"/>
    • (2) html当中的style标签中使用
    1
    2
    3
    4
    5
    <style type="text/css">

    @import url(css文件路径);

    </style>
    • (3) css文件当中引入
    1
    @import url(css文件路径);

    @import区别

    • link是XHTML标签,除了加载CSS外,还可以定义RRS等其他事务。@import属于CSS范畴,只能加载CSS
    • link引用CSS样式,是和页面加载同步进行加载,@import是等页面加载完后才开始加载。
    • link是XHTML标签,无兼容问题;@import是在CSS2.1提出的,低版本的浏览器不支持。
    • link支持使用Javascript控制DOM去改变样式;而@import不支持。

    小程序引入css

    • @ 官方API - wxss
    • 使用@import语句可以导入外联样式表,@import后跟需要导入的外联样式表的相对路径,用;表示语句结束

    示例

    1
    2
    3
    4
    5
    /** app.wxss **/
    @import "common.wxss";
    .middle-p {
    padding:15px;
    }

    微信小程序动态类(class)

    • vue的时候动态类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //vue代码
    <div :class="{ colorRed: isShow }">哈哈哈</div>

    //js代码
    data() {
    return {
    isShow: true
    }
    }

    //css 类名
    .colorRed {
    color:red;
    }
    .colorBlue{
    color:blue;
    }
    • 小程序的动态类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <!--pages/classdongtai/classdongtai.wxml-->
    <text class="item {{activeIndex === 0?'active':''}}">动感超人</text>

    // pages/classdongtai/classdongtai.js
    data: {
    activeIndex:0
    },

    /* pages/classdongtai/classdongtai.wxss */
    .item.active{
    color: red;
    }

    使用事件委托,children层存在嵌套时无法获取标识符id,非嵌套时可以如期获取,嵌套获取不到标识符id的解决办法

    • 有人提出这个问题,然后我自己也遇到了这个问题,就来记录下
    • 先说嵌套时候的解决办法
    • mark属性简介
      • 在基础库版本 2.7.1 以上,可以使用 mark 来识别具体触发事件的 target 节点。此外, mark 还可以用于承载一些自定义数据(类似于 dataset )。
      • 当事件触发时,事件冒泡路径上所有的 mark 会被合并,并返回给事件回调函数。(即使事件不是冒泡事件,也会 mark 。)

    委派子元素层存在嵌套获取不到标识符id的情况的演示

    • 本来想通过事件委派,然后通过event.target.id来获取是哪一个元素所触发的,这样子方便寻找数据,但是实际情况却是,当单击到了子元素嵌套的元素的时候,就获取不到id

    演示动画,可以看到,单击子元素内部的索引号或者邮箱地址的时候,出现获取不到id的情况

    委派子元素层存在嵌套获取id的办法-通过mark属性

    实现代码

    wxml

    1
    2
    3
    4
    5
    6
    7
    8
    <!--pages/weipaimark/weipaimark.wxml-->

    <view class="list-container" bindtap="handleClick" >
    <view class="item" wx:for="{{userInfo}}" wx:key="id" mark:index="{{index}}">
    <text>索引号{{index}}</text>
    <text>邮箱地址:{{item.EmailAddress}}</text>
    </view>
    </view>

    js

    1
    2
    3
    4
    5
    handleClick(event){
    //一旦单击到子元素内容,就会输出为空
    // console.log(event.target.id);
    console.log(event.mark.index);
    },

    wxss

    1
    2
    3
    4
    5
    6
    7
    8
    9
    /* pages/weipaimark/weipaimark.wxss */

    .list-container{
    border: 1rpx solid red;
    }
    .list-container .item{
    margin-bottom: 10rpx;
    border: 1px solid blue;
    }

    演示效果

    其他知识点

    js操作的键值为变量的时候,需要使用中括号

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // pages/refreshscroll/refreshscroll.js
    Page({
    data: {
    priority: {
    // 优先级
    'vip': 1111,
    'user': 1,
    },
    },
    onLoad(options) {
    let type = 'vip';
    this.setData({
    [type]:9999
    });
    console.log(this.data.priority);
    },

    })

    判断类型的时候,老师说最好是全等来判断

    位移运算符转化为数字

    • 这里使用无符号右移运算符转换为数字~
    • 如果转换过程中有字母,则返回0
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var a = '12';
    let afterA = a >>> 0;
    console.log(afterA); //12
    console.log(typeof afterA);//number

    var b = 'a12a';
    let afterB = b >>> 0;
    console.log(afterB); //0
    console.log(typeof afterB);//number

    var c = '12a';
    let afterC = c >>> 0;
    console.log(afterC); //0
    console.log(typeof afterC);//number
    • 补充
      • ! 一个感叹号表示取反
      • !! 二个感叹号转化为布尔值
      • !!! 三个感叹号表示转化为布尔值并取反

    object的toString和Array的toString和其他的toString

    • toString()函数的作用是返回object的字符串表示,JavaScript中object默认的toString()方法返回字符串[object Object]。定义类时可以实现新的toString()方法,从而返回更加具有可读性的结果。

    • JavaScript对于数组对象、函数对象、正则表达式对象以及Date日期对象均定义了更加具有可读性的toString()方法:

    object的toSting(经常用来判断数据类型)
    • 如果不是object,而是function,需要通过Object.prototype.toSting.call()来调用
    • 通过toString()返回类型后可以通过slice(8,-1)来获取具体类型,代码功能为从索引为8的开始取,直到倒数第二个
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    //object的toString
    let userInfo = {
    "ID": "1",
    "JobTitle": "Software Engineer",
    "EmailAddress": "Gil_West3007@elnee.tech",
    "FirstNameLastName": "Gil West"
    };
    //输出 [object Object]
    console.log(userInfo.toString());

    //输出 数据类型 Object
    console.log("数据类型取值", userInfo.toString().slice(8, -1));

    //其他的,比如说数组
    let userList = ['用户1', '用户2'];

    //输出 [object Array]
    console.log(Object.prototype.toString.call(userList));

    //输出 数据类型取值 Array
    console.log("数据类型取值", Object.prototype.toString.call(userList).slice(8, -1));
    array当中的toString
    • array当中的toString会返回以逗号分割的字符串
    1
    2
    3
    4
    5
    6
    7
    8
     //array的toString
    let userList = ['用户1','用户2','用户3'];
    console.log(userList.toString());
    console.log(typeof userList.toString());

    //输出结果
    用户1,用户2,用户3
    string
    函数的toString
    • function的toString()方法将返回函数的文本定义
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //函数当中的toString
    function sayName(){
    console.log("我叫将军大人");
    }
    console.log(sayName.toString());
    console.log(typeof sayName.toString());

    //输出结果
    这个应该是字符串
    function sayName(){
    console.log("我叫将军大人");
    }

    string
    Date的toString
    • Date的toString()方法将返回一个具有可读性的日期时间字符串
    1
    2
    3
    4
    5
    6
    7
    8
    // Date的toString()
    var time = new Date();
    console.log(time.toString());
    console.log(typeof time.toString());

    //输出
    Object的toStirng和Array的toString.html:33 Thu Jul 14 2022 09:57:49 GMT+0800 (中国标准时间)
    string
    正则的toString
    • RegExp的toString()方法与function的toString()方法类似,将返回正则表达式的文本定义
    1
    2
    3
    4
    5
    6
    7
    8
    // RegExp的toString()方法
    var reg = new RegExp(/\w{5,10}/);
    console.log(reg.toString());
    console.log(typeof reg.toString());

    //输出
    /\w{5,10}/
    string

    设计模式之单例模式,工厂模式简说

    单例模式
    • 创建多个对象的情况下,使用一个变量来保存,始终只有一个对象
    • 当创建新的对象的时候就会把之前的对象覆盖掉
    • 可以节省内存空间
    工厂模式
    • 根据不同的参数创建不同的对象

    如果遍历的是数组里面的对象,那么修改遍历时候的遍历项,会影响原数组

    • @在线演示

    • 代码示例如下,可以看到,更改了找到的item项目后,原来的也会改变

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    const array1 = [{
    name: '李白1',
    age: 18
    }, {
    name: '李白2',
    age: 19
    }, {
    name: '李白3',
    age: 20
    }, {
    name: '李白4',
    age: 21
    }];
    //寻找年龄大于20的项目,返回找到的对象
    const found = array1.find(element => element.age > 20);
    //对寻找到的对象的年龄进行修改
    found.age = 1888;
    //输出查看当前寻找到的对象
    console.log(found);//输出 item项目修改.html:31 {name: '李白4', age: 1888}
    //将原对象转化为字符串,避免干扰
    console.log(JSON.stringify(array1));//输出 [{"name":"李白1","age":18},{"name":"李白2","age":19},{"name":"李白3","age":20},{"name":"李白4","age":1888}]

    数组的includes,indexOf,find,findIndex ,concat ,slice,splice

    includes
    • includes() 方法用来判断一个数组是否包含一个指定的值,根据情况,如果包含则返回 true,否则返回 false
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    const array1 = [1, 2, 3];

    console.log(array1.includes(2));
    // expected output: true

    const pets = ['cat', 'dog', 'bat'];

    console.log(pets.includes('cat'));
    // expected output: true

    console.log(pets.includes('at'));
    // expected output: false

    indexOf
    • indexOf()方法返回在数组中可以找到一个给定元素的第一个索引,如果不存在,则返回-1。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const beasts = ['ant', 'bison', 'camel', 'duck', 'bison'];

    console.log(beasts.indexOf('bison'));
    // expected output: 1

    // start from index 2
    console.log(beasts.indexOf('bison', 2));
    // expected output: 4

    console.log(beasts.indexOf('giraffe'));
    // expected output: -1
    find
    • 方法返回数组中满足提供的测试函数的第一个元素的值,找到后就会停止查找,否则返回 undefined
    1
    2
    3
    4
    5
    6
    7
    8
    9
    const array1 = [5, 12, 8, 130, 44];
    //统计次数
    let index = 0
    const found = array1.find(element => {
    index++;
    return element > 10
    });
    console.log("查找的次数", index); //输出 2
    console.log('搜索结果', found); //输出 12
    findIndex
    • findIndex()方法返回数组中满足提供的测试函数的第一个元素的索引,找到后就会停止查找,若没有找到对应元素则返回-1。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    const array1 = [5, 12, 8, 130, 44];
    //统计次数
    let index = 0
    const found = array1.findIndex(element => {
    index++;
    return element > 10
    });
    console.log("查找的次数", index); //输出 2
    console.log('搜索结果', found); //输出 1
    concat
    • concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。
    1
    2
    3
    4
    5
    6
    7
    const array1 = ['a', 'b', 'c'];
    const array2 = ['d', 'e', 'f'];
    const array3 = array1.concat(array2);

    console.log(array3);
    // expected output: Array ["a", "b", "c", "d", "e", "f"]

    slice
    • slice() 方法返回一个新的数组对象,这一对象是一个由 beginend 决定的原数组的浅拷贝(包括 begin,不包括end)相当于左闭右开。原始数组不会被改变
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    const animals = ['ant', 'bison', 'camel', 'duck', 'elephant'];

    console.log(animals.slice(1,3));
    // expected output: Array ['bison', 'camel']

    console.log(animals.slice(2));
    // expected output: Array ["camel", "duck", "elephant"]

    console.log(animals.slice(2, 4));
    // expected output: Array ["camel", "duck"]

    console.log(animals.slice(1, 5));
    // expected output: Array ["bison", "camel", "duck", "elephant"]

    console.log(animals.slice(-2));
    // expected output: Array ["duck", "elephant"]

    console.log(animals.slice(2, -1));
    // expected output: Array ["camel", "duck"]

    console.log(animals.slice());
    // expected output: Array ["ant", "bison", "camel", "duck", "elephant"]

    splice
    • splice() 方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改的内容。此方法会改变原数组。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const months = ['Jan', 'March', 'April', 'June'];
    months.splice(1, 0, 'Feb');
    // inserts at index 1
    console.log(months);
    // expected output: Array ["Jan", "Feb", "March", "April", "June"]

    months.splice(4, 1, 'May');
    // replaces 1 element at index 4
    console.log(months);
    // expected output: Array ["Jan", "Feb", "March", "April", "May"]

    calc计算高度时候的一个坑,因为符号问题

    • 错误的写法,运算符和操作数之间没有空格分开
    1
    2
    3
    .video-scroll{
    height:calc(100vh-83rpx-60rpx-15rpx)
    }
    • 正确的写法,运算符和操作数之间用空格分开
    1
    2
    3
    .video-scroll{
    height:calc(100vh - 83rpx - 60rpx - 15rpx)
    }

    display:flex和float不能同时使用!!!因为display:flex开启后是内容撑开容器,float就无法浮动了,因为没有位置了

    开启定位后子元素相当于父元素水平垂直居中

    • 原理可以看看这@文章
    • 简单来说就是盒子计算的问题
    1
    2
    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
    35
    36
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
    .father{
    width: 300px;
    height: 300px;
    background-color: red;
    position: relative;
    }
    .son{
    width: 100px;
    height: 100px;
    background-color: blue;
    /* 水平垂直居中 */
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
    margin: auto;
    }
    </style>
    </head>
    <body>
    <div class="father">
    <div class="son">

    </div>
    </div>
    </body>
    </html>

    效果图

    设置旋转的中心点

    • 但是有时候我们需要设置旋转中心,就需要通过transform-origin来设置了

      • 比如transform-origin:50px 50px,意思就是将中心点移动到距离容器x轴50px的位置,y轴50px的位置

    • 也可以设置为右上角顶点 transform-origin:0 0

    • 旋转动画 transform-origin:0 0并设置transform:rotate(45deg)效果

    小程序生命周期的onLoad当中的参数options,用于路由传参

    • 原生小程序url有长度限制,如果传参内容过长会自动截取掉
    • options.id可以传入的id参数

    在组件 wxss 中不应使用 ID 选择器、属性选择器和标签名选择器。

    如题~

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/393ca5ba.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    avatar
    梦洁
    小小的字,有大大的梦想~分享我的前端学习过程,经历,错误,和一些其他折腾过程
    关注下我(* ̄▽ ̄*)
    公告
    不断更新中,有问题请留言回复(会通过邮箱提醒~)
    目录
    1. 1. 小程序项目想下载项目的可以下载看看~
    2. 2. 弹性盒布局和移动端适配的一些知识
    3. 3. 微信小程序的适配方案
    4. 4. 什么是响应式和自适应
      1. 4.1. 响应式
      2. 4.2. 自适应
    5. 5. 小程序配置
      1. 5.1. 全局配置
      2. 5.2. 页面配置
      3. 5.3. sitemap
    6. 6. 小程序框架接口
      1. 6.1. App.js
      2. 6.2. xxx.js(不同页面不同的xxx.js)
      3. 6.3. WXML语法
        1. 6.3.1. 数据绑定
        2. 6.3.2. 列表渲染
        3. 6.3.3. 条件渲染
      4. 6.4. WXS文件
      5. 6.5. 事件系统/事件
        1. 6.5.1. 标准事件流
        2. 6.5.2. 非标准事件流
        3. 6.5.3. 事件格式及示例
    7. 7. 数据绑定
    8. 8. 小程序获取,查询,元素/组件的信息
    9. 9. vue和小程序都是单向数据流
      1. 9.1. 什么是单向数据流
      2. 9.2. vue
      3. 9.3. 小程序
    10. 10. App.json当中配置路由,路由跳转及需要注意的点
      1. 10.1. 注意点
    11. 11. 小程序使用分包
      1. 11.1. 使用常规分包的具体步骤和示例
        1. 11.1.1. (1).为分包建立文件夹
        2. 11.1.2. (2).将除了启动页(tabBar)的页面文件夹放置在刚刚创建的文件夹当中
        3. 11.1.3. (3).app.json配置项修改
        4. 11.1.4. (4).更改跳转路径
        5. 11.1.5. (5).目录结构
      2. 11.2. 分包预下载
    12. 12. 微信小程序生命周期
    13. 13. 小程序使用npm包步骤
      1. 13.1. (1).初始化
      2. 13.2. (2).勾选使用npm(有的项目需要勾选,现在没有这个选项了)
      3. 13.3. (3).下载需要的包
      4. 13.4. (4).引入包
      5. 13.5. (5).引入包后构建npm
    14. 14. 小程序的ajax请求发送和二次封装
      1. 14.1. ajax请求发送
      2. 14.2. 二次封装
    15. 15. 小程序使用自定义组件
      1. 15.1. (1)建立文件夹
      2. 15.2. (2)右键新建Component
      3. 15.3. (3)构建NavHeader的结构
      4. 15.4. (4)其他组件注册使用自定义组件
      5. 15.5. (5使用并传值
    16. 16. tabBar底部导航的实现
    17. 17. js文件使用this.data.xxx和this.xxx和this.setData的区别
      1. 17.1. this.data.xxx
      2. 17.2. this.xxx
      3. 17.3. this.setData
    18. 18. 元素当中id和data-xxx在辨别数据的使用
    19. 19. 文本超出显示省略号或文本超出两行后显示省略号
      1. 19.1. 文本超出显示省略号
      2. 19.2. 文本超出两行后显示省略号
    20. 20. 小程序的轻提示(Toast)
    21. 21. 小程序当中视频的操作
      1. 21.1. (0)wxml基本结构
      2. 21.2. (1)创建视频上下文对象
      3. 21.3. (2)控制视频的播放/暂停/跳转
      4. 21.4. 补充:由于微信问题,视频播放下一个的时候不会自动暂停上一个视频,所以这里需要使用视频上下文进行处理
    22. 22. 小程序的video组件设置为100%出现黑色边框
    23. 23. scroll-view滚动到指定位置并且有过渡效果
      1. 23.1. 效果
      2. 23.2. 代码实现
    24. 24. 用户转发分享和自定义转发分享内容
      1. 24.1. button实现
    25. 25. 微信小程序背景音频的播放
      1. 25.1. (1)获取全局唯一的背景音频管理对象,返回BackgroundAudioManager 实例
      2. 25.2. (2)操作BackgroundAudioManager 实例
    26. 26. 微信小程序的授权登录和获取openid
      1. 26.1. wx.getUserProfile( 2022 年 10 月 25 日 24 时后 会失效)
        1. 26.1.1. (1).button按钮绑定回调
        2. 26.1.2. (2).自定义一个函数,这里取名 handleUserInfo 并且添加wx.getUserProfile方法 需要注意的是,事件名都是小写
        3. 26.1.3. (3).处理授权结果
      2. 26.2. 获取openid
        1. 26.2.1. (1).wx.login获取登录凭证
        2. 26.2.2. (2).有了登录凭证,再通过auth.code2Session获取openid
        3. 26.2.3. (3).处理结果
    27. 27. 小程序更新数据状态的区别
    28. 28. 小程序的数据存储
      1. 28.1. wx.setStorageSync(存数据)
      2. 28.2. wx.getStorageSync(取数据)
    29. 29. 小程序scroll-view的下拉刷新
      1. 29.1. 示例
    30. 30. 为什么小程序没有引入css样式都会生效?
    31. 31. @import 和import 引入css区别和小程序引入css?
      1. 31.1. @import的用法
      2. 31.2. @import区别
      3. 31.3. 小程序引入css
    32. 32. 微信小程序动态类(class)
    33. 33. 使用事件委托,children层存在嵌套时无法获取标识符id,非嵌套时可以如期获取,嵌套获取不到标识符id的解决办法
      1. 33.1. 委派子元素层存在嵌套获取不到标识符id的情况的演示
      2. 33.2. 委派子元素层存在嵌套获取id的办法-通过mark属性
    34. 34. 其他知识点
      1. 34.1. js操作的键值为变量的时候,需要使用中括号
      2. 34.2. 判断类型的时候,老师说最好是全等来判断
      3. 34.3. 位移运算符转化为数字
      4. 34.4. object的toString和Array的toString和其他的toString
        1. 34.4.1. object的toSting(经常用来判断数据类型)
        2. 34.4.2. array当中的toString
        3. 34.4.3. 函数的toString
        4. 34.4.4. Date的toString
        5. 34.4.5. 正则的toString
      5. 34.5. 设计模式之单例模式,工厂模式简说
        1. 34.5.1. 单例模式
        2. 34.5.2. 工厂模式
      6. 34.6. 如果遍历的是数组里面的对象,那么修改遍历时候的遍历项,会影响原数组
      7. 34.7. 数组的includes,indexOf,find,findIndex ,concat ,slice,splice
        1. 34.7.1. includes
        2. 34.7.2. indexOf
        3. 34.7.3. find
        4. 34.7.4. findIndex
        5. 34.7.5. concat
        6. 34.7.6. slice
        7. 34.7.7. splice
      8. 34.8. calc计算高度时候的一个坑,因为符号问题
      9. 34.9. display:flex和float不能同时使用!!!因为display:flex开启后是内容撑开容器,float就无法浮动了,因为没有位置了
      10. 34.10. 开启定位后子元素相当于父元素水平垂直居中
      11. 34.11. 设置旋转的中心点
      12. 34.12. 小程序生命周期的onLoad当中的参数options,用于路由传参
      13. 34.13. 在组件 wxss 中不应使用 ID 选择器、属性选择器和标签名选择器。
    最新文章
    \ No newline at end of file diff --git a/394d2f27.html b/394d2f27.html new file mode 100644 index 000000000..0f0e9d63b --- /dev/null +++ b/394d2f27.html @@ -0,0 +1 @@ +express+socket简易聊天室 | 梦洁小站-属于你我的小天地

    express+socket简易聊天室

    简易聊天室

    前置知识

    1. 在我们平常的时候,ajax发送的都是短连接,get完成或者post完成之后连接就断开,并且当服务器数据更新的时候,必须再次发送ajax请求才可以获取到最新的数据
    2. 在没有socket之前,要实时获取服务器的数据,必须要轮询,也就是每隔一段时间发送ajax
    3. 所以现在有了服务端主动向前端推送消息的东东——长连接socket
    4. 实现的二种方式
      1. socket.io
      2. websocket(H5新增)

    这里使用socket.io实现

    1. socket.io的具体使用并没有想象的难记,而是on和emit这二个关键字贯穿始终

    2. on就用来订阅服务器发送的消息(服务端当中的理解就是监听用户发送服务端的信息)

    3. emit就用来用户向服务器发送信息(服务端当中的理解就是向用户发送信息)

      socket.io大概过程

    前端设置

    1. 引入<script src="./socket.io.js"></script>

    2. 使用io.connect(url)连接socket服务器

    3. 使用on监听和emit发送

    前端代码

    index.html

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="./jquery-3.6.0.js"></script>
    <script src="./socket.io.js"></script>
    <style>
    * {
    margin: 0;
    padding: 0;
    }

    .wrap {
    width: 400px;
    height: 450px;
    margin: 50px auto;
    box-shadow: 0 0 4px rgba(0, 0, 0, 0.4);
    }

    .wrap>.content {
    width: 100%;
    height: 300px;
    box-sizing: border-box;
    padding: 20px 10px 8px 10px;
    background-color: #e2e1e2;
    overflow-x: auto;
    overflow-x: hidden;
    }

    .wrap>.content .left {
    font-size: 15px;
    padding: 5px 4px;
    border-radius: 4px;
    clear: both;
    margin: 5px 0;
    background-color: #c5cdd6;
    max-width: 300px;
    float: left;
    }

    .wrap>.content .right {
    font-size: 15px;
    padding: 5px 4px;
    border-radius: 4px;
    clear: both;
    margin: 5px 0;
    background-color: #8dcced;
    max-width: 300px;
    float: right;
    }

    .wrap>.input {
    width: 100%;
    height: 100px;
    }

    .wrap>.input #ipt {
    width: 100%;
    height: 100%;
    }

    .wrap>.ope {
    height: 50px;
    }

    .wrap>.ope #send {
    padding: 10px;
    float: right;
    }
    </style>
    </head>

    <body>
    <div class="wrap">
    <div class="content">
    <!-- <p class="left">你好,我叫</p> -->
    <!-- <p class="right">hello</p> -->
    </div>
    <div class="input">
    <textarea id="ipt"></textarea>
    </div>
    <div class="ope">
    <button id="send">发送</button>
    </div>
    </div>

    <script>
    var $inputs = $("#ipt"); //文本输入框
    var $send = $("#send"); //发送按钮
    var $content = $(".content").eq(0);
    // socket连接
    var socket = io.connect("http://localhost:8888");
    //监听自定义的serverInfo事件,data参数为服务器传送的数据
    //监听服务器的信息
    socket.on("serverInfo", (data) => {
    //收到从服务器发送的信息
    $('<p class="left">' + data + '</p>').appendTo($content);
    })
    //用户在输入框按下回车
    $inputs.keyup(function (e) {
    if ($inputs.val().trim() && e.keyCode == 13) {
    $send.click();
    }
    })
    //发送按钮被单击
    $send.click(function () {
    var value = $inputs.val();
    if (value) {
    // 不为空,添加
    //自己发送的消息,添加到右边
    $('<p class="right">' + value + '</p>').appendTo($content);
    //先服务端发送消息,key为userMessage
    socket.emit("userMessage", value);
    //清空输入框
    $inputs.val("");
    } else {
    alert("输入框为空!");
    }
    })
    </script>
    </body>

    </html>

    后端代码

    server.js

    1
    2
    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
    const express = require("express");
    const app = express();
    //这里是为了解决跨域
    app.use("/", express.static(__dirname));
    const server = require("http").Server(app);
    const websocket = require("socket.io")(server);
    //上面的可以说的固定的~

    var clientCache = []; //用户暂存列表,为了后面可以向所有用户发送数据
    //连接监听
    websocket.on("connection", (client) => {
    clientCache.push(client); //添加用户到缓存列表
    //监听用户请求 key为userMessage
    client.on("userMessage", (data) => {
    //收到用户发送的信息,批量发送给其他人
    clientCache.forEach((ele) => {
    if (ele != client) {
    //避免自己收到
    ele.emit("serverInfo", data);
    }
    })
    })

    })

    //服务启动
    server.listen("8888", () => {
    console.log("---------server start--------------");
    });

    效果图

    娱乐一刻

    舔狗日记

    "今天你约我去陪你买衣服,尽管我知道只是因为我的身形像你异地的男朋友,比较好试衣服而已。买完衣服,寄快递写他名字的时候,看见你嘴角翘起幸福的笑来。那一刻,我多希望我可以也叫那个名字,哪怕只有一分钟!!"

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/394d2f27.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/39cea9d6.html b/39cea9d6.html new file mode 100644 index 000000000..59c6f9407 --- /dev/null +++ b/39cea9d6.html @@ -0,0 +1 @@ +浅拷贝深拷贝和个人一些新理解(非普遍的理解) | 梦洁小站-属于你我的小天地

    浅拷贝深拷贝和个人一些新理解(非普遍的理解)

    预备知识

    • 引用数据类型

      • Object(在JS中除了基本数据类型以外的都是对象,数据是对象,函数是对象等等)
    • 基本数据类型

      • NumberStringBooleanNullUndefinedSymbol
    • 文章参考了牛客网CodeSheep

    赋值 vs 浅拷贝 vs 深拷贝

    赋值(不能算是拷贝,因为拷贝的仅仅只是引用关系,并没有生成新的实际对象)

    很常见的一种

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var a = 10;
    var b = 100;
    var obj = {
    name:"李白",
    sex:"男"
    }
    // 注意,这个严格来说是赋值,不是什么浅拷贝深拷贝!后面有原因
    var obj2 = obj;

    // 注意,这个严格来说是赋值,不是什么浅拷贝深拷贝
    Student codeSheep = new Student();
    Student codePig = codeSheep;

    浅拷贝

    一个误区

    很多人说var objOrigin = {name:'李白'}; var objAfter = objOrigin; 是浅拷贝,严格来说是错误的!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 严格来说是错误的!
    // 如果说下面这一行是浅拷贝
    var objOrigin = {name:'李白'}; var objAfter = objOrigin;是浅拷贝

    //那么这个是什么?
    var obj1 = { name: '李白', sex: '男' };

    var clonedObj = { ...obj1 };
    clonedObj.name="李黑";

    console.log(clonedObj); // {name: '李黑', sex: '男'}

    console.log(obj1);// {name: '李白', sex: '男'}

    //可以看到,克隆后的对象将名字修改为了李黑,原来的没有变化
    • 这里使用了展开运算符(扩展运算符),官方解释是浅拷贝

    • 如果按照之前有些人的说法,下面这个是浅拷贝

      1
      var objOrigin = {name:'李白'}; var objAfter = objOrigin;
      • 那么mdnWebDocs当中的展开运算符(扩展运算符)可以实现浅拷贝,那么按道理来说这个所谓的浅拷贝,修改了浅拷贝后的对象不会导致原来的对象改变吧?那为什么修改后会发生改变?

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        //var objOrigin = {name:'李白'}; var objAfter = obj;是浅拷贝

        var objOrigin = {name:'李白'};
        var objAfter = objOrigin;
        //修改拷贝后的对象当中的name(name为基本数据类型)
        objAfter.name="李黑";

        //输出为 {name: '李黑'}
        console.log(objAfter);

        //输出为 {name: '李黑'}
        console.log(objOrigin);

      • 所以觉得,赋值就是赋值,不是什么浅拷贝和深拷贝!不然你说var objAfter = objOrigin就是浅拷贝,那么为什么修改了objAfterobjOrigin也会变化?并且修改的还是对象当中的基本数据类型!(因为后面深拷贝还设计到对象当中的数据类型)

    真正的浅拷贝

    • 比如我们试图通过studen1实例,拷贝得到student2,如果是浅拷贝这种方式,大致模型可以示意成如下所示的样子:

    • 很明显,值类型的字段会复制一份,而引用类型的字段拷贝的仅仅是引用地址,而该引用地址指向的实际对象空间其实只有一份。
    • 所以浅拷贝是什么,就是赋值引用数据类型当中存储的基本数据类型,而引用数据类型当中的引用数据类型(比如数组当中嵌套对象的情况),仅仅只是将地址赋值给了另外一个对象!

    深拷贝

    • 深拷贝相较于上面所示的浅拷贝,除了值类型字段会复制一份,引用类型字段所指向的对象,会在内存中也创建一个副本,就像这个样子:

    • 所以深拷贝是什么,就是不管对象当中嵌套了多少层引用数据类型还是基本数据类型,都建立一个新的给自己

    一些常用的深浅拷贝方法

    浅拷贝

    展开运算符(扩展运算符)

    • 可以看到,除了基本数据类型拷贝了,里面的引用数据类型并未进行拷贝,修改一个拷贝后的引用数据类型会影响原来的
    1
    2
    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
    var objOrigin = {
    name: '李白',
    sex: '男',
    other: {
    hobby: "喝酒",
    food: "吃肉"
    }
    };
    //扩展运算符浅拷贝
    var objAfter = {
    ...objOrigin
    }
    //false
    console.log(objAfter === objOrigin);

    //true (浅拷贝的原因)
    console.log(objAfter.other === objOrigin.other);

    //修改
    objAfter.name = "动感超人";

    //修改2
    objAfter.other.food = "蔬菜"

    //{ name: '动感超人', sex: '男', other: { hobby: '喝酒', food: '蔬菜' } }
    console.log(objAfter);

    // { name: '李白', sex: '男', other: { hobby: '喝酒', food: '蔬菜' } }
    console.log(objOrigin);

    Object.assign

    • 可以看到,除了基本数据类型拷贝了,里面的引用数据类型并未进行拷贝,修改一个拷贝后的引用数据类型会影响原来的
    1
    2
    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
    var objOrigin = {
    name: '李白',
    sex: '男',
    other: {
    hobby: "喝酒",
    food: "吃肉"
    }
    };
    //Object.assign浅拷贝
    var objAfter = {};
    Object.assign(objAfter,objOrigin)
    //false
    console.log(objAfter === objOrigin);

    //true (浅拷贝的原因)
    console.log(objAfter.other === objOrigin.other);

    //修改
    objAfter.name = "动感超人";

    //修改2
    objAfter.other.food = "蔬菜"

    //{ name: '动感超人', sex: '男', other: { hobby: '喝酒', food: '蔬菜' } }
    console.log(objAfter);

    // { name: '李白', sex: '男', other: { hobby: '喝酒', food: '蔬菜' } }
    console.log(objOrigin);

    深拷贝

    JSON.stringify和JSON.parseInt实现

    • 可以看到,除了基本数据类型拷贝了,里面的引用数据类型也进行了拷贝,修改一个拷贝后的引用数据类型不影响原来的
    1
    2
    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
    var objOrigin = {
    name: '李白',
    sex: '男',
    other: {
    hobby: "喝酒",
    food: "吃肉"
    }
    };
    //JSON.parse和JSON.stringify 深拷贝
    var objAfter = JSON.parse(JSON.stringify(objOrigin));
    //false
    console.log(objAfter === objOrigin);

    //true (浅拷贝的原因)
    console.log(objAfter.other === objOrigin.other);

    //修改
    objAfter.name = "动感超人";

    //修改2
    objAfter.other.food = "蔬菜"

    //{ name: '动感超人', sex: '男', other: { hobby: '喝酒', food: '蔬菜' } }
    console.log(objAfter);

    // { name: '李白', sex: '男', other: { hobby: '喝酒', food: '吃肉' } }
    console.log(objOrigin);

    lodash当中的cloneDeep

    cloneDeep当中的API地址

    1
    2
    3
    4
    5
    6
    var objects = [{ 'a': 1 }, { 'b': 2 }];

    var deep = _.cloneDeep(objects);
    //比较是否相同 返回 false
    console.log(deep[0] === objects[0]);

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/39cea9d6.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/3a777744.html b/3a777744.html new file mode 100644 index 000000000..c4e288efc --- /dev/null +++ b/3a777744.html @@ -0,0 +1 @@ +微信小程序抓包-夜神模拟器结合BurpSuite抓包(可用于现在最新版本微信) | 梦洁小站-属于你我的小天地

    微信小程序抓包-夜神模拟器结合BurpSuite抓包(可用于现在最新版本微信)

    警告

    • 2023年3月23日18.37.46:很多人说网络错误,目前我再次自己试过了,都没有出现这个问题,如果害怕出现网络异常等情况,就不要看下去了

    前置

    https://blog.csdn.net/qq_34498872/article/details/122469329

    https://www.52pojie.cn/forum.php?mod=viewthread&tid=1617968&highlight=BurpSuite

    https://blog.csdn.net/qq_44029310/article/details/126017134

    开始教程

    1.安装夜神模拟器并下载安装微信

    • 官网下就可以

    • 记得安装微信

      • 截止发这篇博客的时候,微信版本为8.0.3
    • 安装好微信后登录自己微信

    2.查看电脑ip地址并在burp当中添加

    • 比如我的ip地址为192.168.200.65

    • 按照图片上步骤添加自己的ip,端口设置为8888

    3.夜神模拟器修改WiFi代理

    • 输入自己电脑的ip地址和更改burp设置的端口号

    4.pc,pc,pc浏览器下载证书

    • pc端浏览器输入证书网站192.168.200.65:8888(不同的ip不同,根据刚刚设置来决定)

    5.window安装OpenSSL

    5.1安装

    • 这一步好像可以跳过,直接把转换后的per证书名称改为9a5ba575.0即可,我看另外一个人的教程计算生成出来的结果和这个一样,可能是统一的吧

    • 下载网站

    • 单击这个EXE或者MSI,然后安装的时候一直下一步即可

    5.2配置环境

    1
    C:\Program Files\OpenSSL-Win64\bin
    • 我们就新建环境变量OPENSSL_HOME
    • 值为C:\Program Files\OpenSSL-Win64\bin
      • 注意,不要漏掉了bin

    • 向环境变量path添加%OPENSSL_HOME%

    • 测试安装是否成功

    6.der证书转换为pem证书

    1.可以用在线版本

    2.也可以使用openssl来转换

    1
    openssl x509 -inform der -in cacert.der -out cacert.pem

    转换效果是一样的

    7.查看hash表示并修改pem文件名字为 hash标识.0

    • 查看hash标识:pem证书所在目录下运行当前命令,文件名称需要改为自己对应的文件名称,记录下9a5ba575
    1
    openssl x509 -subject_hash_old -in cacert.pem

    • 将生成的pem格式的文件的文件名称改为9a5ba575.0

    就像这样

    8.打开模拟器,把开发者模式打开

    • 打开设置-关于平板电脑,找到版本号,单击五次版本号进入开发者模式

    进入开发者模式

    9.打开usb调试模式

    • 打开开发者模式后,然后点击返回键,回到上一级的设置界面,可以看到多出一个“开发者选项”了。

    • 点进去开启USB调试。
      • 下图为已开启usb调试

    10.移动证书

    • 打开文件资源管理器,进入夜神模拟器的安装目录,找到nox_adb.exe程序,或者直接everything搜索

    • 如果是雷电模拟器,可能就是这个名字了

    • 在ADB所在目录打开cmd命令行
    • 输入nox_adb.exe connect 127.0.0.1:62001即可以连接到adb,或者是adb connect 127.0.0.1:62001

    • nox_adb.exe connect 127.0.0.1:62001后面的62001为模拟器的端口号,不同的模拟器端口号不同,下面是常见的模拟器ABD端口
    模拟器端口号
    网易MuMu模拟器7555
    夜神安卓模拟器62001
    逍遥安卓模拟器21503
    蓝叠安卓模拟器5555
    雷电安卓模拟器5555
    天天安卓模拟器5037
    安卓模拟器大师54001
    腾讯手游助手5555
    • 此时直接adb devices 命令即可显示夜神模拟器设备信息了

    • 把证书放到nox_adb.exe所在目录下

    • 然后依次执行以下命令,在查看系统证书就会发现成功安装。
    1
    2
    3
    4
    5
    6
    adb root // 提升到root权限
    adb remount // 重新挂载system分区

    //将证书放到系统证书目录 注意文件名是自己的文件名
    adb push 9a5ba575.0 /system/etc/security/cacerts/

    注意把证书移动到当前目录

    11.完成

    • 抓包效果

    burp抓包结果中文乱码的问题

    • 修改下字体和编码类型即可

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/3a777744.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/3be58dac.html b/3be58dac.html new file mode 100644 index 000000000..f08d215c0 --- /dev/null +++ b/3be58dac.html @@ -0,0 +1 @@ +git添加代理,让github克隆的速度增加变快 | 梦洁小站-属于你我的小天地

    git添加代理,让github克隆的速度增加变快

    clash的配置

    注意Allow LAN要打上勾

    注意Allow LAN

    方法1:推荐!

    • 添加创建.gitconfig文件,内容如下
      • 注意,端口7890每个人可能不一样,需要看自己的clash代理是什么端口,可以在General项目当中的Port查看到
    1
    2
    3
    4
    5
    6
    [http]
    proxy = "socks5://127.0.0.1:7890"
    proxy = "http://127.0.0.1:7890"
    [https]
    proxy = "socks5://127.0.0.1:7890"
    proxy = "https://127.0.0.1:7890"
    • 创建这个文件的目录到git里面看下
      • 可以看到,我这里C:\Users\Administrator,说明我需要在这个文件下创建.gitconfig

    创建的文件

    • 添加后的文件内容为

    如果需要curl的代理,请看这里

    • 依旧是新建.curlrcC:\Users\Administrator

    • 添加下面内容
    1
    socks5 = "127.0.0.1:7890"
    • 测试连接
    1
    curl -I https://www.google.com

    成功图片

    方法2

    找到git的安装路径,找到\Git\etc\bash.bashrc文件

    每个人路径不同!

    添加如下内容

    1
    2
    3
    4
    5
    6
    # clash 
    export http_proxy=http://127.0.0.1:7890;export https_proxy=http://127.0.0.1:7890

    //下面这里可以不用添加,这里做个记录~
    # V2rayN
    # export http_proxy="socks5://127.0.0.1:7891" export https_proxy=socks5://127.0.0.1:7890

    添加完成

    添加后

    方法3:终极解决

    • 添加环境变量如下图所示
    1
    2
    3
    4
    5
    http_proxy
    http://127.0.0.1:7890

    https_proxy
    http://127.0.0.1:7890

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/3be58dac.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/3cb9e66e.html b/3cb9e66e.html new file mode 100644 index 000000000..f6a1f5d32 --- /dev/null +++ b/3cb9e66e.html @@ -0,0 +1 @@ +ES5当中var只有全局作用域和函数作用域,注意变量提升在var中的坑 | 梦洁小站-属于你我的小天地

    ES5当中var只有全局作用域和函数作用域,注意变量提升在var中的坑

    前置知识

    1. 在ES5当中,变量的作用域只有全局作用域和函数作用域之分的,也就是说,你使用 var 关键字创建的变量,要么是全局都可以使用的,要么就是函数内部可以使用的
    2. 在ES5当中,var定义的变量是会变量提升的
    3. 在ES6当中,let是块级作用域,不会变量提升

    于是乎产生了一次错误

    1
    2
    3
    4
    5
    6
    7
    <script>
    var data='动感超人'
    if(data){
    var flag=true;
    }
    console.log(flag);//输出结果为true
    </script>

    这一段代码一看就是true

    1. 在js当中,除了 null undefined “” NaN false 这四个转化为布尔值为false,其他均为true

    2. 所以data=”动感超人”转化为布尔值为true~所以执行var flag=true;

    3. 由于var只有全局作用域和函数作用域,所以var flag为全局变量

      代码改改

    1
    2
    3
    4
    5
    6
    7
    <script>
    var data=''
    if(data){
    var flag=true;
    }
    console.log(flag);
    </script>

    这一段代码呢?

    结果为undefined

    分析

    执行流程

    1.js引擎执行js代码前先看看有没有可以变量提升,函数提升),上面代码转换下实际上是这样子在执行的过程中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <script>
    var data;//变量提升
    var flag;//变量提升
    data=''//赋值
    if(data){//转布尔值为false
    flag=true;//条件不成立,不执行赋值
    }
    console.log(flag);//没有赋值,又为全局变量,不报错,但是输出为undefined
    </script>

    总结

    施主,要小心,小心,再小心~阿弥陀佛

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/3cb9e66e.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/3fc93d78.html b/3fc93d78.html new file mode 100644 index 000000000..ea95bb54a --- /dev/null +++ b/3fc93d78.html @@ -0,0 +1 @@ +无js实现拖拽边框改变div大小的笔记 | 梦洁小站-属于你我的小天地

    无js实现拖拽边框改变div大小的笔记

    前言

    • 最近刷抖音看到一款游戏”拣爱”,看到这个人手动拖动的很有意思,就想着能不能前端实现,来学习学习,虽然说最终的效果没有gif图片那么好,但是也算实现了,,,,吧….

    具体原理

    • 利用resize属性所出现的小拖拽条

    • 再配合::-webkit-scrollbar设置拖拽区域宽度,高度,结合opacity:0即可将可拖拽区域覆盖整个div

    具体效果和代码

    效果

    代码

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>拖拽照片</title>
    <style>
    *{
    margin: 0;
    padding: 0;
    }
    body,html{
    width: 100%;
    height: 100%;
    }
    .pic{
    width: 100%;
    height: 100%;
    box-sizing: border-box;
    padding: 16px 6px 0 6px;
    background: black;
    }
    /*内容区域*/
    .content{
    width: 100%;
    box-sizing: border-box;
    background: red;
    display: flex;
    flex-direction: row;
    flex-wrap: nowrap;
    /*超出内容隐藏*/
    overflow: hidden;
    }
    /*照片区域*/
    .right,
    .left
    {
    background-position: left;
    background-size: 100vw auto;
    background-repeat: no-repeat;
    }
    .left{
    background-image: url(https://dreamos.oss-cn-beijing.aliyuncs.com/gitblog/202302012011639.jpg);
    position: relative;/*为内容区提供定位*/
    }
    .right{
    background-image: url(https://dreamos.oss-cn-beijing.aliyuncs.com/gitblog/202302012011607.jpg);
    flex: 1;
    }

    /*拖拽条*/
    .drag{
    border-right: 1px solid blue;
    /*min-width: 10vw;*/
    width: 50vw;
    height: 80vh;/*设置百分比没有效果*/
    cursor: ew-resize;
    resize: horizontal;
    overflow: scroll;
    /*隐藏拖拽条*/
    opacity: 0;
    }
    /*重要*/
    .drag::-webkit-scrollbar{
    width: 50vw;
    height: 80vh;
    }
    /*底部内容*/
    .bottom{
    height: 10%;
    box-sizing: border-box;
    }
    </style>
    </head>
    <body>
    <div class="pic">
    <!-- 内容区 -->
    <div class="content">
    <!-- 拖动条 -->
    <div class="left">
    <div class="drag"></div>
    </div>
    <div class="right"></div>
    </div>
    <!-- 底部 -->
    <div class="bottom"></div>
    </div>
    </body>
    </html>

    参考链接和文章

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/3fc93d78.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/40766dbf.html b/40766dbf.html new file mode 100644 index 000000000..ef423a32e --- /dev/null +++ b/40766dbf.html @@ -0,0 +1 @@ +今日刷题-类型转换必须解决 | 梦洁小站-属于你我的小天地

    今日刷题-类型转换必须解决

    题目1

    1
    2
    3
    4
    5
    6
    7
    8
    下面结果为真的表达式是:()
    A: null instanceof Object

    B: null === undefined

    C: null == undefined

    D: NaN == NaN
    • 答案

      • C
    • 解析

      • A:

        • null instanceof Object ;// false
        • typeof null === ‘object’;// true
        • 同理
        • undefined instanceof Object ; //false
        • typeof undefined === ‘undefined’ //true
      • B,C

        • null 遇到二个等号( == ) 和 undefined 比较,无条件返回true
        • null 遇到三个等号( === ) 和undefined 比较,无条件返回false
      • D:

        • NaN 不与 任何值相等 包括自身

    题目2

    1
    2
    3
    4
    5
    6
    7
    8
    以下哪个语句打印出来的结果是false
    A: alert(3 == true)

    B: alert(2 == '2')

    C: alert(null == undefined)

    D: alert(isNaN('true'))
    • 答案

      • A
    • 解析

      • A: == 会隐式转换,二个都是基本数据类型比较,true转化为数字为1,然后3==1返回false

      • B: == 会隐式转换,二个都是基本数据类型比较,都转化为数字,字符串’2’ 转化为数字为2,所以 2 == 2返回为true

      • C: null 遇到2个等号(==) 和undefined 无条件返回true**(规定)**

      • D: isNaN的转换好像都是将参数通过**Number()**来进行转换,凡是Number不能转换的都返回NaN

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        isNaN(123) //false
        isNaN(-1.23) //false
        isNaN(5-2) //false
        isNaN(0) //false
        isNaN('123') //false
        isNaN('Hello') //true
        isNaN('2005/12/12') //true
        isNaN('') //false
        isNaN(true) //false
        isNaN(undefined) //true
        isNaN('NaN') //true
        isNaN(NaN) //true
        isNaN(0 / 0) //true

    总结下

    1. 只要是基本数据类型== 比较,都会转化为数值来进行比较

    2. 只要是引用数据类型基本数据类型的的比较,都会转化为字符串 然后 转化为 被比较的基本数据的类型再进行比较((除了引用数据类型和布尔值进行比较的时候是将二者转化为布尔值比较))

    3. (比如 对象和布尔值比较 )

      1
      2
      3
      // 输出结果为false
      // 过程: [ ]转换为字符串'',然后转换为数字0,true转换为数字1,所以为false
      [] == true;
    4. (比如 对象和数字比较)

      1
      2
      3
      // 输出结果为true
      // 过程: [1]转化为字符串'1' ,然后转化为数字1 然后数字1 和 1进行比较 ,返回true
      [1] == 1;
    5. 还有一些需要记住的,像

      1
      2
      3
      4
      5
      6
      7
      null == undefined // true  null和undefined二个等于号比较则返回true,二者和其他比较就返回false

      //举例子,下方的就是
      null == false //false

      null === null //为true
      undefined === undefined //为true
    6. 具体可以看这个博主写的js中的一些隐式转换和总结

    题目2扩展

    1
    2
    3
    4
    5
    //各自的返回结果是?
    1 == true
    "" == false
    false == null
    null == undefined
    • 答案
      • 1 == true ;// true (基本数据类型和布尔值比较,二者转化为数值 1 不变化 true变为了1,所以二者相等返回true)
      • "" == false; // true (基本数据类型和布尔值比较 , 二者转化为数值, ""转化为数值为0, false变为0,所以二者相等返回true)
      • false == null; // false (null除了和undefined二个等号比较返回为true,null和其他比较均返回为false(undefined也是))
      • null == undefined;// true (nullundefined 比较,返回为true,可以看上面第五条)

    扩展

    • 对象转换为数字的时候的时候,会依次调用valueOf方法和toString方法,我们可以进行重写这二个方法来完成一些看似不可能的操作
    • 当调用valueOf方法没有获取到基本数据类型的时候,就会再次调用toString方法

    证明1:当调用valueOf方法没有获取到基本数据类型的时候,就会再次调用toString方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var a = [];
    Array.prototype.valueOf = function () {
    console.log("你调用了我1");
    return [];
    };
    Array.prototype.toString = function () {
    console.log("你调用了我2");
    }
    console.log([] == false);

    //依次输出
    // 你调用了我1
    // 你调用了我2
    // false

    证明2:当调用valueOf方法没有获取到基本数据类型的时候,就会再次调用toString方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    var test = {
    name:"李白",
    valueOf(){
    console.log("你调用了我1");
    return {};
    },
    toString(){
    console.log("你调用了我2");
    }
    }
    console.log(test == false);
    //依次输出
    // 你调用了我1
    // 你调用了我2
    // false

    证明3:当调用valueOf方法没有获取到基本数据类型的时候,就会再次调用toString方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var test = {
    name:"李白",
    valueOf(){
    console.log("你调用了我1");
    return "777";
    },
    toString(){
    console.log("你调用了我2");
    }
    }
    console.log(test == false);
    //依次输出
    // 你调用了我1
    // false

    也许有人会说,为什么我重写了valueOf方法和toString方法还是只会调用valueOf啊

    • 那是因为你没有加return,你没加return在valueOf方法里面,那么相同会自动添加return undefined
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var test = {
    name:"李白",
    valueOf(){
    console.log("你调用了我1");
    },
    toString(){
    console.log("你调用了我2");
    }
    }
    console.log(test == false);
    //可能你们是这样子写的
    //然后发现只有 你调用了我1 和false输出
    // 你return下任意的引用数据类型就可以了

    有没有什么办法可以使得 a==1 && a==2 && a==3为真?

    1
    2
    3
    4
    5
    6
    7
    8
    var a = {
    num:1,
    valueOf(){
    //每次都是先返回在自增1
    return this.num++;
    }
    }
    console.log(a==1 && a==2 && a==3);//true
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/40766dbf.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/413722d6.html b/413722d6.html new file mode 100644 index 000000000..7046298ed --- /dev/null +++ b/413722d6.html @@ -0,0 +1 @@ +vue当中绑定回调函数的时候添加括号和不添加括号的区别 | 梦洁小站-属于你我的小天地

    vue当中绑定回调函数的时候添加括号和不添加括号的区别

    前置

    • 有时候我们会看到绑定回调函数的时候,就拿最普遍的超链接的@click事件来说,有时候我们会看到别人这样子写

    写法一

    1
    <a @click="handleClick">单击我</a>

    写法二

    1
    <a @click="handleClick()">单击我</a>
    • 那么写法一盒写法二有什么区别呢?(注意是有区别的)

    我们书写下代码(很简单的代码,对比输出不同而已)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <template>
    <a @click="handleClick">单击我1</a>
    <a @click="handleClick()">单击我2</a>
    </template>

    <script>
    export default {
    name: "App",
    setup(){
    function handleClick(testParam){
    console.log("输出查看",testParam);
    }
    return {
    handleClick,
    }
    }
    };
    </script>

    [单击我1]输出结果(就是不添加括号的)

    [单击我2]输出结果(就是添加括号的)

    结论

    • 相不添加括号 @click = “handleClick” 会默认传入一个event参数

    • 添加括号 @click = “handleClick()” 则不会传入参数

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/413722d6.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/415dc4d6.html b/415dc4d6.html new file mode 100644 index 000000000..0d30f3f04 --- /dev/null +++ b/415dc4d6.html @@ -0,0 +1 @@ +flex设置为1后为什么要设置width为0,和布局超出省略号为什么会超出容器,为什么会没有用和在苹果环境下获取屏幕高度需要注意的点 | 梦洁小站-属于你我的小天地

    flex设置为1后为什么要设置width为0,和布局超出省略号为什么会超出容器,为什么会没有用和在苹果环境下获取屏幕高度需要注意的点

    前言

    • 最近在做手机端的页面,制作过程出现了flex布局的一些问题,再次记录在解决办法
    • 关于在flex:1的情况下设置为width的效果
      • 如果没有设置width,当内部元素的内容大小超过平均分配的剩余空间时,元素的宽度等于内容大小,如果设置了width并且这个width的大小小于平均分配的剩余空间大小时,取平均分配的剩余空间;当flex设置为 1 时 相当于 剩余空间大小 = 父元素的宽度 因此平均的剩余空间大小等于 = 父元素的宽度 / 元素的个数,直接设置width为0可以保证元素宽度平分父元素宽度

      • 说简单点就是设置了flex:1的情况下不设置width:0,那么就会导致父元素宽度等于等元素的宽度,而不会等于剩下空间的长度,这样子就会超出空间了

      • 具体可看@地址

      • 顺带一提,在苹果机器上,可能会出现window.innerHeight或者window.innerWidth获取不到或者是获取到的不是自己想要的高度或宽度,那是因为这二个属性在苹果safari上获取的是视觉视口,而不是布局视口,尽管现在布局视口等于视觉视口了,所以保险起见,还是使用document.document.clientHeight来获取可视区域的高度

    最终效果

    • 可以看到,完成了基本布局和超出显示省略号的功能

    开始

    • 有时候我就感觉很奇怪,为什么有的用margin,有的用padding,后面才知道,margin是透明的,填充不会被填充到,padding则会被填充

    • 开启flex在主内容区域,并设置每一个item项目高度为80px

    • 稍加完善就成为了这样子,注意,此时还没有设置超出显示省略号的效果
    • 此时的代码
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    *{
    margin: 0;
    padding: 0;
    }
    :root{
    --self-top:50px;
    --self-bottom:40px;
    --self-leftTab:60px;
    }
    .wrapper{
    width: 100%;
    //顶部,一般是搜索栏
    .top{
    height: var(--self-top);
    background-color: red;
    }
    .main{
    display: flex;
    flex-flow: row nowrap;
    background-color: gray;
    //计算剩余高度,当然,后期计算可能需要通过js来手动计算
    height: calc(100% - var(--self-top) - var(--self-bottom));
    // 左侧,一般是分栏,用于切换不同栏目,一般是固定的宽度
    .main-left{
    width: var(--self-leftTab);
    background-color: aquamarine;
    }

    //右侧,一般是数据展示,才用flex布局一般,手机不用flex布局是否可用
    .main-right{
    display: flex;
    flex-flow: column wrap;
    flex: 1;//让其子元素占满
    //每一个的item项目
    &-item{
    height: 80px;//假设为80px
    background-color: brown;
    margin-bottom: 10px;
    /* 假设这是一个描述信息 */
    &_description{
    white-space: nowrap;
    text-overflow:ellipsis;
    overflow: hidden;
    }
    }
    }
    }
    /* 底部一般是底部导航,一般会固定高度 */
    .bottom{
    height:var(--self-bottom);
    background-color: hotpink;
    }
    }
    • 这样子写看上去没有什么问题,但是如果item项的描述信息过多的时候,就会导致下面动图演示的问题了
      • 可以看到,随着超人字段的增加,左侧tab栏被挤压了

    • 这个时候只需要设置类名为main-right的div宽度为0即可(也就是设置item项的外层div容器宽度为0),就恢复正常了
    1
    2
    3
    4
    5
    6
    7
    8
    9
    .main-right{
    display: flex;
    flex-flow: column wrap;
    flex: 1;
    width: 0; //重点,避免挤压其他的
    &-item{
    .....
    }
    }
    恢复正常了
    • 但是依旧没有省略号,那是因为没有约束每一个item项的宽度,导致item项的宽度会被子元素撑开,这里约束下就好
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     .main-right{
    display: flex;
    flex-flow: column wrap;
    flex: 1;
    width: 0;//避免挤压左侧tab栏
    &-item{
    //....
    //设置item的宽度为父元素100%
    //避免被挤压
    width: 100%;
    }
    }
    • 完成

    完整代码

    • html代码
    1
    2
    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
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="./1.css">
    </head>
    <body>
    <div class="wrapper">
    <div class="top">顶部</div>

    <div class="main">
    <div class="main-left">

    </div>
    <div class="main-right">
    <div class="main-right-item">
    <div class="main-right-item_description">我是描述我是描述我是描述我是描述我是描述我是描述我是描述我是描述我是描述我是描述我是描述我是描述我是描述我是描述我是描述</div>
    </div>
    <div class="main-right-item">
    <div class="main-right-item_description">我是描述我是描述我是描述我是描述我是描述</div>
    </div>
    <div class="main-right-item">
    <div class="main-right-item_description">我是描述我是描述我是描述我是描述我是描述</div>
    </div>
    </div>
    </div>

    <div class="bottom">底部</div>
    </div>
    </body>
    </html>
    • less代码
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    *{
    margin: 0;
    padding: 0;
    }
    :root{
    --self-top:50px;
    --self-bottom:40px;
    --self-leftTab:60px;
    }
    .wrapper{
    width: 100%;
    //顶部,一般是搜索栏
    .top{
    height: var(--self-top);
    background-color: red;
    }
    .main{
    display: flex;
    flex-flow: row nowrap;
    background-color: gray;
    //计算剩余高度,当然,后期计算可能需要通过js来手动计算
    height: calc(100% - var(--self-top) - var(--self-bottom));
    // 左侧,一般是分栏,用于切换不同栏目,一般是固定的宽度
    .main-left{
    width: var(--self-leftTab);
    background-color: aquamarine;
    }

    //右侧,一般是数据展示,才用flex布局一般,手机不用flex布局是否可用
    .main-right{
    display: flex;
    flex-flow: column wrap;
    flex: 1;//让其子元素占满
    width: 0;
    //每一个的item项目
    &-item{
    height: 80px;//假设为80px
    background-color: brown;
    margin-bottom: 10px;
    width: 100%;
    /* 假设这是一个描述信息 */
    &_description{
    white-space: nowrap;
    text-overflow:ellipsis;
    overflow: hidden;
    }
    }
    }
    }
    /* 底部一般是底部导航,一般会固定高度 */
    .bottom{
    height:var(--self-bottom);
    background-color: hotpink;
    }
    }
    • less编译后的css代码
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    * {
    margin: 0;
    padding: 0;
    }
    :root {
    --self-top: 50px;
    --self-bottom: 40px;
    --self-leftTab: 60px;
    }
    .wrapper {
    width: 100%;
    /* 底部一般是底部导航,一般会固定高度 */
    }
    .wrapper .top {
    height: var(--self-top);
    background-color: red;
    }
    .wrapper .main {
    display: flex;
    flex-flow: row nowrap;
    background-color: gray;
    height: calc(100% - var(--self-top) - var(--self-bottom));
    }
    .wrapper .main .main-left {
    width: var(--self-leftTab);
    background-color: aquamarine;
    }
    .wrapper .main .main-right {
    display: flex;
    flex-flow: column wrap;
    flex: 1;
    width: 0;
    }
    .wrapper .main .main-right-item {
    height: 80px;
    background-color: brown;
    margin-bottom: 10px;
    width: 100%;
    /* 假设这是一个描述信息 */
    }
    .wrapper .main .main-right-item_description {
    white-space: nowrap;
    text-overflow: ellipsis;
    overflow: hidden;
    }
    .wrapper .bottom {
    height: var(--self-bottom);
    background-color: hotpink;
    }
    /*# sourceMappingURL=./1.css.map */
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/415dc4d6.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/41635aab.html b/41635aab.html new file mode 100644 index 000000000..e4f4128a4 --- /dev/null +++ b/41635aab.html @@ -0,0 +1 @@ +Fiddler和Proxifier的配合使用抓取window应用的所有包 | 梦洁小站-属于你我的小天地

    Fiddler和Proxifier的配合使用抓取window应用的所有包

    没有使用Proxifier的情况下对网易有道词典的抓包不到的

    • 可以看到,输入玛利亚,fiddler抓包工具下,什么都没有抓到

    设置Proxifier抓包

    1.添加规则

    • 注意,这里填写的是fiddler设置的端口(我这里是8888,大部分也是8888)

    • 单击OK后会提示是否设置为默认的,这里就取消

    2.设置代理规则

    • 添加fiddler

    • 添加完成后的

    3.设置默认规则

    • 选择Proxy HTTPS 127.0.0.1

    4.开启HTTP代理服务支持

    • 打上勾勾

    5.配置好的总体配置

    也可以参考别人的

    • 不过我测试没有那个localhost会有问题

    实测效果

    • 可以看到,抓到了网易有道词典的请求

    工具下载

    推荐另外一个工具

    参考文章

    https://www.cnblogs.com/gancuimian/p/14010628.html

    http://www.51testing.com/html/42/15142342-4462193.html

    https://www.freebuf.com/articles/mobile/267647.html

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/41635aab.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/42c1e321.html b/42c1e321.html new file mode 100644 index 000000000..e493b6f37 --- /dev/null +++ b/42c1e321.html @@ -0,0 +1 @@ +今日刷题-原型链 | 梦洁小站-属于你我的小天地

    今日刷题-原型链

    题目1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    请问以下两次检测对象constructor是否拥有属性名1的结果分别是什么?
    1 in Object(1.0).constructor;
    Number[1] = 123;
    1 in Object(1.0).constructor;

    A: false,false

    B: false,true

    C: true,true

    D: true,false
    • 答案

      • B
    • 解析

      • in用于检测一个属性是否在指定对象上或者是其原型链上

      • Object 构造函数将给定的值包装为一个新对象。

        • 比如Object({name:’李白’}) 返回 { name : ‘李白’ }
        • Object([1,2,3,4]) 返回[1,2,3,4]
        • Object(1.0) 返回Number(1) 也就是Number对象
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        // 代码等同于
        var temp = Object(1.0);

        1 in temp.constructor;

        //Object[key] = value; 形式给 constructor 对象添加 key = 1 属性,对应的 value = 123
        // 因为key不能为数字开头,所以就通过Ojbect[key]方式赋值
        // 类似于 Object.name = "李白"这种
        Number[1] = 123;

        1 in temp.constructor;

    题目2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var F=function(){};
    Object.prototype.a=function(){};
    Function.prototype .b=function(){};
    var f=new F();
    A: f能取到a,但取不到b

    B: f能取到a,b

    C: F能取到b,不能取到a

    D: F能取到a,不能取到b

    • 答案

      • A
    • 解析

      1
      2
      3
      4
      5
      6
      7
      8
      // 看代码~
      var F = function () {};
      var f = new F();
      F.__proto__ === Function.prototype; //true
      f.__proto__ === F.prototype; //true
      F.prototype.__proto__ === Object.prototype; //true

      F.prototype.__proto__ == Function.prototype; //false

      图形演示

      原型链

    题目3

    1
    2
    3
    4
    5
    6
    7
    8
    9
    下面哪些语句可以 在JS里判断一个对象是否为String类型?
    A: oStringObject instanceof String

    B: typeof oStringObject == 'string'

    C: oStringObject is String (没见过这个语法~)

    D: 以上答案都不正确

    • 答案

      • A
    • 解析

      1
      2
      3
      //所以只有a
      typeof 'hello'; // 'string'
      typeof new String('hello'); // 'object'
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/42c1e321.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/43093808.html b/43093808.html new file mode 100644 index 000000000..80a29f74d --- /dev/null +++ b/43093808.html @@ -0,0 +1 @@ +pinia的基本创建和统一创建和解构失去响应式解决办法等知识点 | 梦洁小站-属于你我的小天地

    pinia的基本创建和统一创建和解构失去响应式解决办法等知识点

    在线代码演示

    使用注意点

    不能直接结构赋值

    • 如果直接结构赋值,就像下面一样,就会失去响应式效果(数据变了,视图依旧不会更新)
    1
    2
    3
    4
    5
    <template>
    {{ name }}
    </template>

    const { name } = useCounterStore();
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // 官网示例代码
    import { storeToRefs } from 'pinia'

    export default defineComponent({
    setup() {
    const store = useCounterStore()
    // `name` and `doubleCount` 都是响应式 refs
    // 这也将为由插件添加的属性创建 refs
    // 同时会跳过任何 action 或非响应式(非 ref/响应式)属性
    const { name, doubleCount } = storeToRefs(store)
    // 名为 increment 的 action 可以直接提取
    const { increment } = store

    return {
    name,
    doubleCount,
    increment,
    }
    },
    })

    多次使用依旧是相同的对象

    1
    2
    3
    4
    const store2 = useShopInfo();
    const store3 = useShopInfo();
    console.log(store2 === store3)
    //输出true

    创建方式(多种)

    • 官方示例的会导致重复打包
    1
    2
    3
    4
    使用store时要先把store的定义import进来,再执行定义函数使得实例化。
    但是,在项目逐渐庞大起来后,每个组件要使用时候都要实例化吗?
    在文中开头讲过,pinia的代码分割机制是把引用它的页面合并打包,
    那像下面的例子就会有问题,user被多个页面引用,最后user store被重复打包。

    方法0:官方示例写法

    • src/store/useShopInfo.ts
    1
    2
    3
    export default deinfStore('shopInfo',{
    ...
    })
    • main.ts
    1
    2
    3
    4
    5
    6
    7
    8
    import { createApp } from 'vue'
    import { createPinia } from "pinia";
    import './style.css'
    import App from './App.vue'
    const app = createApp(App);
    app.use(createPinia())
    app.mount('#app')

    • 使用
    1
    2
    3
    4
    5
    6
    7
    8
    import useShopInfo from "./store/useShopInfo.ts";

    const store = useShopInfo();

    store.addCar({
    name:name.value,
    price:price.value
    })

    先简单了解下app.use方法

    • 我们先来了解下vue的app.use方法,app.use用来注册插件,插件可以是一个带 install() 方法的对象,亦或直接是一个将被用作 install() 方法的函数。插件选项 (app.use() 的第二个参数) 将会传递给插件的 install() 方法。
      • app.use() 对同一个插件多次调用,该插件只会被安装一次
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import {createApp} from "vue";
    const app = createApp();
    import 插件带install方法 from "./test"
    import 插件不带install方法 from "./test"

    app.use(插件带install方法);//会自动调用install方法

    //或者手动执行,以一个自定义函数作为install方法
    app.use((app, options) => {
    // dosomething
    })
    • 基于app.use() 对同一个插件多次调用,该插件只会被安装一次。我们其实可以看到很多组件库都会叫我们这样使用组件库
    1
    2
    3
    4
    5
    6
    7
    8
    9
    import TDesign from 'tdesign-vue-next';
    import {createApp} from "vue";
    import App from './app.vue';

    const app = createApp(App);

    //重点
    app.use(TDesign);

    • 我们看看我们引入的TDesign是什么
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import {App} from 'vue'
    import {
    Button,
    Popup,
    Avatar,
    Icon,
    ...
    } from 'tdesign-vue-next';
    export default (app:App) => {
    app.use(Button)
    app.use(Popup)
    app.use(Avatar)
    app.use(Icon)
    app.use(....)
    }
    • 现在明白了吧,我们引入的TDesign就是一个主入口文件,他帮我们一个一个的使用了插件,从而达到了全局引入的效果,当然,你也可以自己去引入,然后按需使用插件

    方法1:store统一实例化并暴露使用

    • 我们可以使用此方法,我们在store文件夹下方的index.ts对其他store进行统一实例化然后暴露使用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //未统一实例化
    store
    a.ts
    b.ts
    //使用的时候(一般以useXXX命名,当然,也随你命名)
    import useA from "../store/a";
    const infoA = useA();
    console.log(infoA.name);//输出存储的名字信息

    //统一实例化并暴露使用
    store
    a.ts
    b.ts
    index.ts
    //使用的时候
    import appStore from "./store";
    console.log(appStore.infoA.name);//输出存储的名字信息

    创建总路口和其他仓库

    • store/index.ts
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    import shopInfo from "./useShopInfo"

    export interface AppStore {
    shopInfo:ReturnType<typeof shopInfo>,
    }

    const appStore = {} as AppStore;

    /* 注册store状态库 */
    export const registerStore = () => {
    appStore.shopInfo = shopInfo();//由于没有传入pinia对象,会自动去寻找pinia对象
    }

    export default appStore;

    • store/user.ts
    1
    2
    3
    4
    5
    6
    7
    8
    import {defineStore} from "pinia"
    export default defineStore('shopInfo',{
    //...
    state:{
    money:''
    }
    //...
    })

    全局注册

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import {createApp} from "vue";
    import App from "./App.vue";
    import {registerStore} from "./store";

    const app = createApp(App);
    app.use(createPinia);/* 使用pinia后就可以紧跟着注册了 */
    // 注册pinia状态管理库
    registerStore();

    app.mount('#app')

    组件使用

    • src/pages/A.vue
    1
    2
    3
    import appStore from "../store";

    <div> {{ appStore.shopInfo.money }} </div>

    方法2:store统一实例化并暴露使用

    • 这种方法不需要全局注册pinia,当然,你为了美观也可以全局注册下
    • 原理是因为defineStore返回值useStore函数,这个useStore函数第一个参数可以接收一个pinia对象,如果有传入则使用传入的pinia对象,如果没有传入pinia对象,那么会去全局寻找,否则就会报错,报错内容大概如下
    1
    2
    3
    4
    `[🍍]: getActivePinia was called with no active Pinia. Did you forget to install pinia?\n` +
    `\tconst pinia = createPinia()\n` +
    `\tapp.use(pinia)\n` +
    `This will fail in production.`

    创建总路口和其他仓库

    • store/index.ts
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import {createPinia} from "pinia";
    import UseInfo from "./user.ts";//引入其他仓库

    import type { App } from 'vue';//或者import {App} from "vue";

    //创建唯一的store
    const store = createPinia();

    //如果需要美观下,想要在main.js全局注册,可以添加下面方法
    export default (app:App) => {
    app.use(store);
    }


    //注意大小写和传参
    export const useInfo = UseInfo(store);
    //如果有多个也是这样子调用,名字什么的你随意
    //export useList = UseList(store);

    • store/user.ts
    1
    2
    3
    4
    5
    6
    7
    8
    import {defineStore} from "pinia"
    export default defineStore('shopInfo',{
    //...
    state:{
    money:''
    }
    //...
    })

    全局注册(方法2可以不全局注册)

    • main.js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import {createApp} from "vue";
    import App from "./App.vue";
    import pinia from "./store"

    const bootstrap = () => {
    const app = createApp(App)
    // ...
    // 注册TDesignUI()组件库
    app.use(TDesignUI)
    // 加载pinia(可选)
    app.use((app) => {
    app.use(pinia)
    })
    // ...
    }

    void bootstrap()

    组件使用

    • src/pages/A.vue
    1
    2
    3
    import { useInfo } from "../store";

    <div> {{ useInfo.money }} </div>

    参考文章

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/43093808.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/434010ba.html b/434010ba.html new file mode 100644 index 000000000..02f2c9be5 --- /dev/null +++ b/434010ba.html @@ -0,0 +1 @@ +记一次配置picgo错误的记录和解决办法 | 梦洁小站-属于你我的小天地

    记一次配置picgo错误的记录和解决办法

    1.错误提示StatusCodeError:403 -<?xml version=xxxxxxxx>

    2.原因

    没有设置管理对象存储服务器(OSS权限)

    3.解决

    4.成功解决再次上传测试可以上传

    2.错误提示RequestError: Error: tunneling socket could not be established, cause=getaddrinfo ENOTFOUND 7890

    解决

    • 可能是安装软件的时候设置了代理,删除电脑的环境变量 HTTPS_PROXY之类的
      • 在环境变量里面把 http_proxyhttps_proxy 两项删除即可

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/434010ba.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/447d3137.html b/447d3137.html new file mode 100644 index 000000000..9625ef141 --- /dev/null +++ b/447d3137.html @@ -0,0 +1 @@ +React的学习笔记-(Bilibili天禹老师) | 梦洁小站-属于你我的小天地

    React的学习笔记-(Bilibili天禹老师)

    React的特点

    • 采用组件化模式,声明式编码,提高开发效率和组件复用率
    • React Native中可以使用React语法进行移动端开发(IOS和Android)
    • 使用虚拟DOM+优秀的Diffing算法,尽量减少与真实DOM的交互

    babel用处

    • es6 => es5
    • jsx => js

    1.你好,react

    • 注意引入顺序
    1
    2
    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
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>首次React</title>
    </head>
    <body>
    <div id="test"></div>
    <!-- 引入react核心库-核心库必须要第一个引入 引入后会有一个全局对象React-->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react操作DOM的库 引入后会有一个全局对象ReactDom-->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!-- 引入babel,使其可以书写jsx语法(jsx语法转化为js语法) -->
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <!-- 书写jsx语法,注意type的值 -->
    <script type="text/babel">

    //1.创建一个虚拟的DOM
    //不需要添加引号
    const VDOM = <h1>你好,react</h1>
    //2.使用react语法将虚拟的DOM转化为真实的DOM,插入页面
    ReactDOM.render(VDOM,document.getElementById("test"));
    </script>
    </body>
    </html>

    效果

    二种创建虚拟DOM的方式

    方式1-使用jsx的形式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>虚拟DOM的二种创建方式</title>
    </head>
    <body>
    <div id="test"></div>
    <!-- 引入react核心库-核心库必须要第一个引入 引入后会有一个全局对象React-->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react操作DOM的库 引入后会有一个全局对象ReactDom-->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!-- 引入babel,使其可以书写jsx语法(jsx语法转化为js语法) -->
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <!-- 书写jsx语法,注意type的值 -->
    <script type="text/babel">

    //1.创建一个虚拟的DOM
    const VDOM = <h1 id='content'><span>你好,React,世界我来了</span></h1>
    //2.使用react语法将虚拟的DOM转化为真实的DOM,插入页面
    ReactDOM.render(VDOM,document.getElementById("test"));
    </script>
    </body>
    </html>

    使用jsx的形式

    方式2-使用javascript形式

    • 主要是利用引入React库所当中的React.createElement创建虚拟DOM
    • React.createElement语法参数
      • 第一个参数:为创建的标签名称
      • 第二个参数:为标签的属性
      • 第三个属性:为标签的内容
    1
    2
    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
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>虚拟DOM的二种创建方式</title>
    </head>
    <body>
    <div id="test"></div>
    <!-- 引入react核心库-核心库必须要第一个引入 引入后会有一个全局对象React-->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react操作DOM的库 引入后会有一个全局对象ReactDom-->
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <!-- 引入babel,使其可以书写jsx语法(jsx语法转化为js语法) -->
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <!-- 书写jsx语法,注意type的值 -->
    <script type="text/babel">
    // <h1 id='content'><span>你好,React,世界我来了</span></h1>
    //1.创建一个虚拟的DOM
    //第一个参数为创建的标签名称
    //第二个参数为标签的属性
    //第三个参数为标签的内容
    const VDOM = React.createElement('h1',{id:'content',className:'content-show'},React.createElement('span',{},'你好,React,世界我来了'));
    //2.使用react语法将虚拟的DOM转化为真实的DOM,插入页面
    ReactDOM.render(VDOM,document.getElementById("test"));
    </script>
    </body>
    </html>

    虚拟DOM和真实DOM

    • 虚拟DOM输出查看

    虚拟DOM

    • 真实DOM输出查看

    真实DOM

    • 总结
      • 虚拟DOM的本质其实就是Object类型的一般对象
      • 虚拟DOM比较’轻’,真实DOM比较’重’
        • 轻,重体现在这个对象的所具有的属性和方法上

    jsx的语法规则

    1. 若第一层存在多个标签,则必须要有一个统一的标签,否者就报错(也就是根标签只有一个)
    2. 书写js表达式代码,需要以{ }开头,用于区分普通的字符代码
    3. 如果需要书写内联样式,需要以{ {...} },第一个花括号是用来区分普通的字符代码的,代表要书写js代码,其中,{...}是对象,并且要以小驼峰的形式命名key
    4. 书写类名要以 className 书写
    5. 标签必须要闭合,否者会报错
    6. 创建虚拟DOM的时候,不需要引号
    7. 在jsx中,标签名是可以随意书写的
      1. 如果是[小写]的,react则会将当前的jsx标签对应为一个html标签,若对应成了,直接渲染展示,否则的话就报错
      2. 如果是[大写]的,react则会将当前的jsx标签对应为一个组件,去查找组件的定义位置,若找到了,直接渲染展示,否者就报错
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>JSX语法规则</title>
    </head>
    <body>
    <div id="test"></div>
    <!-- 引入react核心库-核心库必须要第一个引入 引入后会有一个全局对象React-->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react操作DOM的库 引入后会有一个全局对象ReactDom-->
    <script
    type="text/javascript"
    src="../js/react-dom.development.js"
    ></script>
    <!-- 引入babel,使其可以书写jsx语法(jsx语法转化为js语法) -->
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <!-- 书写jsx语法,注意type的值 -->
    <script type="text/babel">
    const id = 'cOnTEnt';
    const text = "hEllo,你好,I aM cOming!";
    //1.创建一个虚拟的DOM
    const VDOM = (
    <div>
    <h1 id={id.toLowerCase()} className='biubiu'>
    <span style={{backgroundColor:'red',color:'blue'}}>{text.toUpperCase()}</span>
    </h1>
    <h1>你好</h1>
    </div>
    );
    //语法规则
    /*
    1.若第一层存在多个标签,则必须要有一个统一的标签,否者就报错(也就是根标签只有一个)
    2.书写js表达式代码,需要以 { } 开头,用于区分普通的字符代码
    3.如果需要书写内联样式,需要以{ {...} } 其中,{...}是对象,并且要以小驼峰的形式命名
    4.书写类名要以 className 书写
    5.标签必须要闭合,否者会报错
    6.创建虚拟DOM的时候,不需要引号
    7.在jsx中,标签名是可以随意书写的
    7.1,如果是[小写]的,react则会将当前的jsx标签对应为一个html标签,若对应成了,直接渲染展示,否则的话就报错
    7.2,如果是[大写]的,react则会将当前的jsx标签对应为一个组件,去查找组件的定义位置,若找到了,直接渲染展示,否者就报错
    */
    //2.使用react语法将虚拟的DOM转化为真实的DOM,插入页面
    ReactDOM.render(VDOM, document.getElementById("test"));
    </script>
    </body>
    </html>

    表达式和语句(代码)的区别

    • 一句话概括就是表达式左侧是可以有值返回的,而语句是没有的
    • 所以不管是在React还是Vue当中,插值语法都是只能书写表达式的

    表达式

    • 表达式就是会产生结果的值,可以放在React任何一个需要值的地方
    • 下面这些都是表达式
      • a
      • a+b
      • demo(1) (函数返回值)
      • function test() { }

    语句(代码)

    • 语句是不会产生结果的
    • 下面这些都是语句(代码)
      • if( ){ }
      • for( ) { }
      • switch( ) {case: xxx}

    JSX小练习

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <body>
    <div id="test"></div>
    <!-- 引入react核心库-核心库必须要第一个引入 引入后会有一个全局对象React-->
    <script type="text/javascript" src="../js/react.development.js"></script>
    <!-- 引入react操作DOM的库 引入后会有一个全局对象ReactDom-->
    <script
    type="text/javascript"
    src="../js/react-dom.development.js"
    ></script>
    <!-- 引入babel,使其可以书写jsx语法(jsx语法转化为js语法) -->
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <!-- 书写jsx语法,注意type的值 -->
    <script type="text/babel">
    const arrayData = ['React','Vue','Angulay'];

    const VDOM = (
    <div>
    <h1>前端JS框架列表</h1>
    <ul>
    {arrayData.map((item,index) => (<li key={index}>{item}</li>))}
    </ul>
    </div>
    )
    ReactDOM.render(VDOM,document.getElementById('test'));
    </script>
    </body>

    React函数式组件

    • Reace有函数式组件,也有类似组件,这里做下函数式组件的笔记
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>1_函数式组件</title>
    </head>
    <body>
    <div id="test"></div>
    <script type="text/javascript" src="../js/react.development.js"></script>
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <script type="text/babel">
    //1.函数式组件
    function Demo(){
    //此处的this是undefined,是因为经过babel编译后,开启了严格模式
    console.log(this);
    return <h1>我是函数定义的组件,适用于简单组件的定义</h1>
    }

    ReactDOM.render(<Demo/>,document.getElementById('test'));

    </script>
    </body>
    </html>
    • 执行ReactDOM.render发生了什么?
      • 1.React发现<MyComponent/>标签,去寻找MyComponent组件定义的位置,发现是函数式组件
      • 2.是函数式组件,则react调用该函数
      • 3.react调用该函数,函数会返回一个虚拟的DOM,并由react转换为真实的DOM,并显示在页面上

    React类式组件

    • 什么是类式组件,就是通过类来创建组件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <div id="test"></div>
    <script type="text/javascript" src="../js/react.development.js"></script>
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <script type="text/babel">
    //1.类式组件
    //继承react内置的类
    class MyComponent extends React.Component {

    render(){
    console.log(this);//为创建的MyComponent实例对象
    return <h2>我是用类定义的组件</h2>
    }
    }

    ReactDOM.render(<MyComponent/>,document.getElementById('test'));

    </script>
    • render方法,是给react渲染时候调用的
    • 执行ReactDOM.render发生了什么?
      • 1.React发现<MyComponent/>标签,去寻找MyComponent组件定义的位置,发现是类式组件
      • 2.是类式组件,则react创建该类的实例对象
      • 3.react调用该类的实例对象身上的render方法,render方法返回一个虚拟的DOM,并由react转换为真实的DOM,并显示在页面上

    组件的三大属性之state

    在学之前,先来复习下this的指向

    • 严格模式,是禁止this指向window的
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function demo1(){
    console.log("this指向:",this);//window
    }
    demo1();

    function demo(){
    "use strict"
    console.log("this指向:",this);//undefined
    }
    demo();
    • 严格模式下,this禁止指向window,所以下面这个例子中,thisundefined
    1
    2
    3
    4
    5
    6
    7
    <script>
    "use strict";
    function DemoWatch() {
    console.log(this);
    }
    DemoWatch();//输出 'undefined'
    </script>
    • 普通模式下(除类外,并且非箭头),this指向执行者(刽子手~)
    1
    2
    3
    4
    5
    6
    <script>
    function DemoWatch(){
    console.log(this);
    }
    window.DemoWatch();//输出 'window'
    </script>
    • 普通模式下(除类外,箭头函数下),this指向上层作用域下的this
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var obj = {
    fn1:() => {
    console.log(this);
    },
    fn2:function(){
    console.log(this);
    }
    }
    obj.fn1(); //输出 window
    obj.fn2(); //输出 obj
      • 先来想一下这个this指向哪里

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        <script type="text/javascript">
        class Demo {
        show(){
        console.log("看看this在哪里",this);
        }
        }
        const example = new Demo();

        example.show(); //输出结果为?

        const x = example.show;

        x(); //输出结果为?

        </script>
      • 答案依次为

        • example实例对象
          • 因为此时的show方法是由example实例化对象所调用的
        • undefined
          • 此时show方法是由window调用的,如果没有开启严格模式,那么应该是输出window,但是类中的方法局部默认开启了严格模式,所以禁止指向window,所以就输出了undefined

    state

    • 未改变state当中的状态
    1
    2
    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
    35
    36
    37
    38
    <body>
    <div id="test"></div>
    <script type="text/javascript" src="../js/react.development.js"></script>
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <div id="test"></div>
    <script type="text/babel">

    class Weather extends React.Component {

    constructor(props){
    super(props);
    this.state = {
    isHot:false,
    }
    }

    changeWeather(){
    //这里的this不是Weather的实例对象,因为不是通过Weather的实例对象调用的
    //而是作为点击的回调调用的
    //类中定义在方法,在局部都开启了严格模式,所以this不敢指向window,当我们在回调点击了h2的时候,相当于直接从堆里面拉出这个函数来,然后直接执行,(并非通过实例调用)
    //由于开启了严格模式,本该指向window的对象的不被允许,所以被指向了undefined
    console.log("我被单击了",this);
    }

    render(){
    //在这里定义虽然也是可以的
    //但是不推荐,因为晚了一步,我们其实应该在创建好实例对象就定义,而不是创建好实例化对象后调用render方法后才定义
    // this.state = {isHot:false};
    return <h2 onClick={this.changeWeather}>今天的天气,真{this.state.isHot ? '热' : '凉快'}啊!</h2>
    }

    }

    ReactDOM.render(<Weather/>,document.getElementById('test'));

    </script>
    </body>
    • 改变state状态的写法
      • 通过继承的React.Component身上的方法setState来完成响应式操作(数据变了,视图也变)
        • setState是一个同步的方法,但是后续更新状态的动作是异步的
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>state属性</title>
    </head>
    <body>
    <div id="test"></div>
    <script type="text/javascript" src="../js/react.development.js"></script>
    <script type="text/javascript" src="../js/react-dom.development.js"></script>
    <script type="text/javascript" src="../js/babel.min.js"></script>
    <div id="test"></div>
    <script type="text/babel">

    class Weather extends React.Component {

    constructor(props){
    super(props);
    this.state = {
    isHot:false,
    wind:'微风',
    }
    // 解决 changeWeather 当中this指向的问题
    //this.changeWeather属性添加到了每一个实例身上
    this.changeWeather = this.changeWeather.bind(this);
    }

    //此方法在Weather原型链上
    changeWeather(){
    //改变state当中的属性
    this.setState({
    //取反
    isHot:!this.state.isHot,
    });
    //此操作为覆盖,不是替换,等同于下面这代码
    // this.setState({
    // ...this.state,
    // isHot:!this.state.isHot,
    // })
    }

    render(){
    //在这里定义虽然也是可以的
    //但是不推荐,因为晚了一步,我们其实应该在创建好实例对象就定义,而不是创建好实例化对象后调用render方法后才定义
    // this.state = {isHot:false};
    return <h2 onClick={this.changeWeather}>今天的天气,真{this.state.isHot ? '热' : '凉快'}啊!</h2>
    }

    }

    ReactDOM.render(<Weather/>,document.getElementById('test'));

    </script>
    </body>
    </html>
    • 调用次数
      • 构造器调用次数: 使用组件多少次,就调用多少次

    state的简写形式-解决this指向问题

    • 使用到了箭头函数
    • 使用到了class在原型链上定义属性
    1
    2
    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
    <body>
    <div id="app"></div>
    <script src="../js/react.development.js"></script>
    <script src="../js/react-dom.development.js"></script>
    <script src="../js/babel.min.js"></script>
    <script type="text/babel">
    class Weather extends React.Component{
    state = {
    hot:true,
    other:'多云'
    }
    constructor(props,name){
    super(props)
    this.name = name;
    }
    //相当于在原型链上创建一个属性(方法)
    changeHot = ()=>{
    this.setState({
    hot:!this.state.hot
    })
    }
    render(){
    return (<h2 onClick={this.changeHot}>今天的天气真{this.state.hot?'热':'凉快'},{this.state.other}</h2>)
    }
    }
    ReactDOM.render(<Weather/>,document.getElementById("app"));
    </script>
    </body>
    • 原理
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    class Weather extends React.Component{
    //之前的写法是在原型Weather身上添加方法(普通方法)
    changeHot1(){

    }

    //这种写法是在自身实例身上添加属性
    changeHot2: () => {

    }
    }
    • 复习
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    </head>
    <body>
    <script>
    /* 可以参考这个https://blog.csdn.net/momDIY/article/details/79997793 */
    class Demo {
    //Demo类原型链身上的,需要通过Demo.prototype.show才可以调用
    show1(){
    console.log('show1被调用了');
    }
    //Demo类原型链身上的,就表示该方法不会被实例继承,直接通过类来调用
    static show2(){
    console.log('show2被调用了');
    }
    }
    Demo.prototype.show1()
    Demo.show2();
    </script>
    </body>
    </html>

    组件的三大属性之props

    • 注意在组件当中的扩展运算符的使用
      • 这里的扩展运算符并不是js原生的{...info},而是babel+react环境就可以让展开运算符展开一个对象,但是仅仅适用于传递标签属性
    1
    2
    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
    <body>
    <div id="app"></div>
    <div id="app2"></div>
    <script src="../js/react.development.js"></script>
    <script src="../js/react-dom.development.js"></script>
    <script src="../js/babel.min.js"></script>
    <script type="text/babel">
    class UserInfo extends React.Component {

    render(){
    const {name,age,sex} = this.props;
    return (
    <ul>
    <li>姓名:{name}</li>
    <li>性别:{age}</li>
    <li>年龄:{sex}</li>
    </ul>
    )
    }
    }

    ReactDOM.render(<UserInfo name='李白' age='18' sex='男'/>,document.getElementById('app'));
    const info = {
    name:'李白',
    age:'18',
    sex:'男',
    }
    //这里的...info,并不是js原生的{...info},而是babel+react环境就可以让展开运算符展开一个对象,但是仅仅适用于传递标签属性
    ReactDOM.render(<UserInfo {...info}/>,document.getElementById('app2'));

    </script>
    </body>

    对props进行约束-类式组件

    1. 引入约束文件
    1
    <script src="../js/prop-types.js"></script>
    1. 使用约束
    • 通过在类身上添加属性propTypes,结合约束文件当中的PropTypes对象当中的类型来使用
    1
    2
    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
    <body>
    <div id="app"></div>
    <script src="../js/react.development.js"></script>
    <script src="../js/react-dom.development.js"></script>
    <script src="../js/babel.min.js"></script>
    <script src="../js/prop-types.js"></script>
    <script type="text/babel">
    class Person extends React.Component{
    //此为实例属性,添加在每一个实例化对象身上,通过实例对象.属性名调用
    state = {
    other:'大家好'
    }
    //此为静态属性,添加在类身上,通过类名.属性名调用
    static propTypes = {
    name:PropTypes.string,//限制name属性必须为字符串
    age:PropTypes.number,//限制age属性必须为数字
    sex:PropTypes.string,//限制sex属性必须为字符串
    address:PropTypes.string,//限制address属性必须为字符串
    }
    render(){
    const {name,age,sex,address } = this.props;
    return (
    <ul>
    <li>姓名:{name}</li>
    <li>年龄:{age+1}</li>
    <li>性别:{sex}</li>
    <li>住址:{address}</li>
    </ul>
    )
    }
    }
    ReactDOM.render(<Person name='李白' age={18} sex='男' address='地球村'/>,document.getElementById("app"));
    </script>
    </body>

    1. 当接收到一个不符合的值的时候,控制台会报警告,但是不会影响视图的渲染

    Warning: Failed prop type: Invalid prop 'age' of type 'string' supplied to 'Person', expected 'number'.

    对props进行约束-函数式组件

    • 函数式组件当中,props是唯一可以使用的三大属性
    • 添加约束的方法和类差不多,也是向函数添加属性propTypes即可
    1
    2
    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
    <body>
    <div id="app"></div>
    <script src="../js/react.development.js"></script>
    <script src="../js/react-dom.development.js"></script>
    <script src="../js/babel.min.js"></script>
    <script src="../js/prop-types.js"></script>
    <script type="text/babel">

    function Person(props){
    const {name,age,sex,address } = props;
    return (
    <ul>
    <li>姓名:{name}</li>
    <li>年龄:{age+1}</li>
    <li>性别:{sex}</li>
    <li>住址:{address}</li>
    </ul>
    )
    }
    //添加约束
    Person.propTypes = {
    name:PropTypes.string,
    age:PropTypes.number,
    sex:PropTypes.string,
    address:PropTypes.string,
    }

    //弹出警告
    // ReactDOM.render(<Person name='李白' age='18' sex='男' address='地球村'/>,document.getElementById("app"));
    ReactDOM.render(<Person name='李白' age={18} sex='男' address='地球村'/>,document.getElementById("app"));
    </script>
    </body>

    ref

    • 通过React当中的ref,可以获取指定节点,获取的节点为真实DOM

    字符串形式的ref

    • 17.x版本已经不推荐使用了,这里做一个学习记录

    • 原理就是通过为元素添加ref属性后,会在实例对象身上的ref属性上添加对应的节点,如下图,就是添加了二个refinput身上,输出this就可以看到ref属性下的二个节点

    image-20221009222127086

    • 具体例子
    1
    2
    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
    35
    36
    37
    38
    <body>
    <div id="app"></div>
    <script src="../js/react.development.js"></script>
    <script src="../js/react-dom.development.js"></script>
    <script src="../js/babel.min.js"></script>
    <script type="text/babel">

    class Demo extends React.Component {

    showData1 = () => {
    //之前的做法
    // const val = document.getElementById('input1').value;
    const {input1} = this.refs;
    alert(input1.value);
    }

    showData2 = () => {
    const {input2} = this.refs;
    alert(input2.value);
    }


    render(){
    return (
    <div>
    <input ref='input1' type='text' placeholder='点击按钮提示输入'/>

    <button onClick={this.showData1}>点我提示数据</button>

    <input ref='input2' onBlur={this.showData2} type='text' placeholder='失去焦点提示输入'/>

    </div>
    )
    }
    }
    ReactDOM.render(<Demo/>,document.getElementById('app'));
    </script>
    </body>

    回调形式的ref

    • 格式

      • <input ref={currentNode = this.input1 = currentNode}/>
      • 其中,ref里面的为一个函数,React调用后会传入一个参数(这个参数就是这个ref所指向的节点),由currentNode接收后,通过this.input1挂载到了当前实例对象身上
    • 不过这种方式的ref也不推荐了….

    • 示例

    1
    2
    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
    35
    36
    37
    38
    <body>
    <div id="app"></div>
    <script src="../js/react.development.js"></script>
    <script src="../js/react-dom.development.js"></script>
    <script src="../js/babel.min.js"></script>
    <script type="text/babel">

    class Demo extends React.Component {

    showData1 = () => {
    //之前的做法
    // const val = document.getElementById('input1').value;
    const {input1} = this;
    alert(input1.value);
    }

    showData2 = () => {
    const {input2} = this;
    alert(input2.value);
    }


    render(){
    return (
    <div>
    <input ref={currentNode => this.input1 = currentNode} type='text' placeholder='点击按钮提示输入'/>

    <button onClick={this.showData1}>点我提示数据</button>

    <input ref={currentNode => this.input2 = currentNode} onBlur={this.showData2} type='text' placeholder='失去焦点提示输入'/>

    </div>
    )
    }
    }
    ReactDOM.render(<Demo/>,document.getElementById('app'));
    </script>
    </body>

    createRef的使用

    • React为了避免this身上太多的属性,就推出了createRef
    • createRef的使用如下
      • 实例对象添加container1属性(只可存储一个节点数据),值设置为React.createRef()
      • 在需要获取节点的身上添加ref属性,值为{this.container1}
      • 输出查看获取的节点的值this.container1.current.value
    • 输出查看container1

    • 示例
    1
    2
    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
    35
    36
    37
    38
    39
    <body>
    <div id="app"></div>
    <script src="../js/react.development.js"></script>
    <script src="../js/react-dom.development.js"></script>
    <script src="../js/babel.min.js"></script>
    <script type="text/babel">

    class Demo extends React.Component {

    input1 = React.createRef();
    input2 = React.createRef();

    showData1 = () => {
    console.log(this.input1);
    console.log(this.input1.current.value);
    }

    showData2 = () => {
    console.log(this.input2);
    console.log(this.input2.current.value);
    }


    render(){
    return (
    <div>
    <input ref={this.input1} type='text' placeholder='点击按钮提示输入'/>

    <button onClick={this.showData1}>点我提示数据</button>

    <input ref={this.input2} onBlur={this.showData2} type='text' placeholder='失去焦点提示输入'/>

    </div>
    )
    }
    }
    ReactDOM.render(<Demo/>,document.getElementById('app'));
    </script>
    </body>

    React当中为什么要建立一个onClick,onXxx的事件

    • 我们知道,在原生js当中,是有事件onclick的,那么为什么React需要创建一个onClick来绑定事件呢?

    • 原因

      • React使用的自定义事件(onClick,onBlur等),而不是使用原生DOM事件,是为了更好的兼容性
      • React的事件是通过事件委派的方式处理的,委托给组件最外层的元素,是为了提高效率
        • 所以你不必担心在li等元素身上绑定自定义事件太多而会出现执行效率问题,React会自动将他们处理为事件委派的形式
    • 其他

      • 在自定义事件当中,可以通过event.target得到发生事件的DOM元素对象

      • 示例

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        <body>
        <div id="app"></div>
        <script src="../js/react.development.js"></script>
        <script src="../js/react-dom.development.js"></script>
        <script src="../js/babel.min.js"></script>
        <script type="text/babel">

        class Demo extends React.Component {

        sayHello = (event) => {
        console.log(event.target);
        }

        render(){
        return (
        <div>
        <button onClick={this.sayHello}>点我输出发生事件的DOM元素</button>
        </div>
        )
        }
        }
        ReactDOM.render(<Demo/>,document.getElementById('app'));
        </script>
        </body>

    React之受控组件和非受控组件

    非受控组件

    • 概念:现用现取(你不受我控制,我需要你的时候你再告诉我)

    示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <body>
    <div id="app"></div>
    <script src="../js/react.development.js"></script>
    <script src="../js/react-dom.development.js"></script>
    <script src="../js/babel.min.js"></script>
    <script type="text/babel">
    class Login extends React.Component {
    loginClick = (e) => {
    e && e.preventDefault();
    console.log(this.account.value,this.password.value);
    }
    render(){
    return (
    <form onSubmit={this.loginClick}>
    <span>账号</span>
    <input ref={(e) => this.account = e} type='text' placeholder='请输入账号'/>
    <span >密码</span>
    <input ref={(e) => this.password = e} type='password' placeholder='请输入密码'/>
    <button>登录</button>
    </form>
    )
    }
    }
    ReactDOM.render(<Login/>,document.getElementById("app"));
    </script>
    </body>

    受控组件

    • 概念:组件中输入类的DOM,随着用户的输入,将输入的值维护到state当中
      • 组件受到我控制,你随时都需要向我报告
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    <body>
    <div id="app"></div>
    <script src="../js/react.development.js"></script>
    <script src="../js/react-dom.development.js"></script>
    <script src="../js/babel.min.js"></script>
    <script type="text/babel">
    // 受控组件

    class Login extends React.Component {
    state = {
    account:'',//账号
    password:'',//密码
    }
    loginClick = (e) => {
    e && e.preventDefault();
    const {account,password} = this.state;
    console.log(account,password);
    };
    changAccount = (event) => {
    //event.target获取触发事件的DOM
    this.setState({
    account:event.target.value,
    })
    }
    changePwd = (event) => {
    //event.target获取触发事件的DOM
    this.setState({
    password:event.target.value,
    })
    }
    render() {
    return (
    <form onSubmit={this.loginClick}>
    <span>账号</span>
    <input type="text" onChange={this.changAccount} placeholder="请输入账号" />
    <span>密码</span>
    <input type="password" onChange = {this.changePwd} placeholder="请输入密码" />
    <button >登录</button>
    </form>
    );
    }
    }
    ReactDOM.render(<Login />, document.getElementById("app"));
    </script>
    </body>
    • 效果

    区别

    • 非受控组件就是现用现取,想获取什么值就去元素中获取
    • 而受控组件就是值统一存储在state的当中,需要的时候就去取
    • 说通俗点就是非受控组件组件不囤积’粮食’,没有食物了就去超时买而受控组件会**囤积’粮食’,**需要的时候就去仓库取

    高阶函数和函数的柯里化

    • **高阶函数:**符合下面任意一个条件即为高阶函数

      1. 本身是一个函数,又返回了一个函数
      2. 函数当中的参数接受到了一个函数
    • 高阶函数常见的有Promise,setTimeout,arr.map()

    • **函数的柯里化:**通过函数调用继续返回函数的方式,实现多次接受参数最后统一处理的函数编码形式,比如下面

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      <script type="text/javascript">
      //普通的函数
      function sum1(a,b,c){
      return a+b+c;
      }
      const result1 = sum1(1,2,3);
      console.log(result1);

      //函数的柯里化
      function sum2(a){
      return (b) => {
      return (c) => {
      return a+b+c;
      }
      }
      }
      const result2 = sum2(1)(2)(3);
      console.log(result2);
      </script>
    • 函数的柯里化在React的应用

      • 如下代码所示,通过一个函数就完成了二个参数的传入,达到了函数复用的效果
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    <body>
    <div id="app"></div>
    <script src="../js/react.development.js"></script>
    <script src="../js/react-dom.development.js"></script>
    <script src="../js/babel.min.js"></script>
    <script type="text/babel">
    // 受控组件

    class Login extends React.Component {
    state = {
    account:'',//账号
    password:'',//密码
    }
    loginClick = (e) => {
    e && e.preventDefault();
    const {account,password} = this.state;
    console.log(account,password);
    };

    collectInfo = (type) => {
    return (event) => {
    this.setState({
    [type]:event.target.value,
    })
    }
    }

    render() {
    return (
    <form onSubmit={this.loginClick}>
    <span>账号</span>
    <input type="text" onChange={this.collectInfo('account')} placeholder="请输入账号" />
    <span>密码</span>
    <input type="password" onChange = {this.collectInfo('password')} placeholder="请输入密码" />
    <button >登录</button>
    </form>
    );
    }
    }
    ReactDOM.render(<Login />, document.getElementById("app"));
    </script>
    </body>
    • 上面例子当中,不使用函数的柯里化在 collectInfo当中的实现
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    <body>
    <div id="app"></div>
    <script src="../js/react.development.js"></script>
    <script src="../js/react-dom.development.js"></script>
    <script src="../js/babel.min.js"></script>
    <script type="text/babel">
    // 受控组件

    class Login extends React.Component {
    state = {
    account:'',//账号
    password:'',//密码
    }
    loginClick = (e) => {
    e && e.preventDefault();
    const {account,password} = this.state;
    console.log(account,password);
    };

    collectInfo = (event,type) => {
    //event为触发的DOM
    //type为类型
    this.setState({
    [type]:event.target.value
    })
    }

    render() {
    return (
    <form onSubmit={this.loginClick}>
    <span>账号</span>
    <input type="text" onChange={(event) => this.collectInfo(event,'account')} placeholder="请输入账号" />
    <span>密码</span>
    <input type="password" onChange = {(event) => this.collectInfo(event,'password')} placeholder="请输入密码" />
    <button >登录</button>
    </form>
    );
    }
    }
    ReactDOM.render(<Login />, document.getElementById("app"));
    </script>
    </body>

    关于类式组件当中的构造器

    • 类式组件中的构造器,完全可以省略掉

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      <script type="text/babel">
      class Demo extends React.Component {
      //可以被省略
      //constructor(props){
      //必须要调用
      //super();
      //}
      render(){
      return ''
      }
      }
      ReactDOM.render(<Demo name='李白'/>,document.getElementById('app'));
      </script>
    • 若在类式组件当中写了构造器,那么就必须要调用super

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      <script type="text/babel">
      class Demo extends React.Component {
      constructor(props){
      //必须要调用
      super();
      }
      render(){
      return ''
      }
      }
      ReactDOM.render(<Demo name='李白'/>,document.getElementById('app'));
      </script>
    • 若调用super时,不传props,那么在构造器当中,通过this.props不可以访问props的,反之就可以访问

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      <script type="text/babel">
      class Demo extends React.Component {
      constructor(props){
      super();
      console.log(this.props); //输出undefined
      }
      render(){
      return ''
      }
      }
      ReactDOM.render(<Demo name='李白'/>,document.getElementById('app'));
      </script>

    React生命周期

    • 生命周期是啥子就是一个组件从创建到销毁过程当中React会在特定阶段执行特定函数
    • 在类式组件当中,render方法会被调用1+n次,1次创建的时候被执行,n次则是数据被更新,DOM被更新的次数

    生命周期16.x

    • 生命周期

      • constructor:构造器

      • componentWillMount:组件将要被挂载

      • render:组件初次渲染和后期更新(调1+n次,n为更新的次数)

      • componentDidMount:组件挂载完成后执行(仅在一次渲染时被调用一次,如果开启了严格模式,可能会调用多次)

        • 一般可以在这个钩子当中做一些初始的事情,如果说开启定时器,发送网络请求,订阅消息
      • componentWillUnmount:组件将要被卸载的时候执行(调1次)

        • 一般在这个钩子中做一些收尾的事情,比如关闭定时器,取消订阅消息
      • componentWillReceiveProps:props发生变化时候的回调

        • 初次渲染的时候不会执行此钩子
      • shouldComponentUpdate:组件是否可以更新,当返回值为true的时候允许更新,false禁止更新

      • componentWillUpdate:组件将要更新(调n次,n为更新的次数)

      • componentDidUpdate:组件更新完成(调n次,n为更新的次数)

    • 生命周期常用函数

      • fourceUpdate():不经过shouldComponentUpdate,直接更新组件
    • 卸载组件

      • ReactDOM.unmountComponentAtNode(要卸载的节点DOM)
      • 比如ReactDOM.unmountComponentAtNode(document.getElementById('app'));

    示例1-透明度改变

    • 组件挂载完成后执行定时器,之后通过按钮执行卸载,卸载的时候执行组件卸载前生命周期函数
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    <body>
    <div id="app"></div>
    <script src="../js/react.development.js"></script>
    <script src="../js/react-dom.development.js"></script>
    <script src="../js/babel.min.js"></script>
    <script type="text/babel">
    class Demo extends React.Component {
    state = {
    opacity:1,
    }
    //生命周期-挂载完成
    componentDidMount(){
    this.timer = setInterval(() => {
    console.log('@');//组件卸载完成后不会再被输出
    let {opacity} = this.state;
    opacity = opacity-0.2 < 0 ? 1 : opacity-0.2
    //设置透明度,每一次设置为上一次的0.2,当小于0的时候就重置
    this.setState({
    opacity,
    });
    }, 200);
    }
    //生命周期-组件将要被卸载
    componentWillUnmount(){
    //清除定时器
    clearInterval(this.timer);
    }
    dead = () => {
    //卸载组件
    ReactDOM.unmountComponentAtNode(document.getElementById('app'))
    }
    render(){
    //获取透明度
    const {opacity} = this.state;
    //造成render被多次调用,开启多个定时器
    // setInterval(() => {
    // this.setState({
    // opacity:opacity-0.2
    // })
    // }, 200);
    return (
    <div>
    <p style={{opacity}}>叫我将军大人</p>
    <button onClick={this.dead}>就不叫你</button>
    </div>
    )
    }
    }
    ReactDOM.render(<Demo/>,document.getElementById('app'));
    </script>
    </body>

    示例2-更新与强制更新

    • 每次单击按钮数字就会加一,同时React自动执行生命周期,通过另外一个按钮控制是否允许DOM更新
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    <body>
    <div id="app"></div>
    <script src="../js/react.development.js"></script>
    <script src="../js/react-dom.development.js"></script>
    <script src="../js/babel.min.js"></script>
    <script type="text/babel">
    class Demo extends React.Component {
    state = {
    number:1,
    allUpdate:true,
    }
    constructor(props){
    super(props);
    console.log('-- constructor --');
    }

    //单击数字+1
    addNumber = () => {
    const { number } = this.state;
    this.setState({
    number: number+1,
    })
    }

    //卸载组件
    xiezai = () => {
    ReactDOM.unmountComponentAtNode(document.getElementById('app'))
    }

    //控制是否允许DOM更新
    setShouldUpdate = () => {
    const {allUpdate} = this.state;
    this.setState({
    allUpdate:!allUpdate
    })
    }

    //强制DOM更新
    force = () => {
    this.forceUpdate();
    }

    /* 组件将要被更新 */
    componentWillUpdate(){
    console.log('-- componentWillUpdate --');
    }

    /* 组件是否允许被更新 */
    shouldComponentUpdate(){
    //返回为false,不允许
    //返回为true,允许 默认返回true
    return this.state.allUpdate;
    }

    /* 组件更新完毕 */
    componentDidUpdate(){
    console.log('-- componentDidUpdate --');
    }
    /* 组件将要被卸载 */
    componentWillUnmount(){
    console.log('-- componentWillUnmount --')
    }
    /* 组件初次渲染和后期更新 */
    render(){
    console.log('-- render --');
    return (
    <div>
    <span>{this.state.number}</span>
    <button onClick = {this.addNumber}>数字+1</button>
    <button onClick = {this.xiezai}>卸载组件</button>
    <button onClick = {this.setShouldUpdate}>更新是否允许更新</button>
    <button onClick = {this.force}>强制更新</button>
    </div>
    )
    }
    }
    ReactDOM.render(<Demo/>,document.getElementById('app'))
    </script>
    </body>

    示例3-props的接收监听

    • 父组件传递props给子组件,子组件接收,后期父组件更新了props值,子组件就会调用componentWillReceiveProps
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    <body>
    <div id="app"></div>
    <script src="../js/react.development.js"></script>
    <script src="../js/react-dom.development.js"></script>
    <script src="../js/babel.min.js"></script>
    <script type="text/babel">
    class A extends React.Component {
    state = {
    carName:'宝马',
    }

    updateValue = () => {
    this.setState({
    carName:'动感超人'
    })
    }

    /* props发生变化时候的回调 初次渲染的时候不会执行此钩子** */
    componentWillReceiveProps(){
    //不会触发
    console.log('-- componentWillReceiveProps --');
    }

    render(){
    const {carName} = this.state;
    return (
    <div>
    <button onClick={this.updateValue}>更新值</button>
    <B carName = {carName}/>
    </div>
    )
    }
    }

    class B extends React.Component {

    /* props发生变化时候的回调 初次渲染的时候不会执行此钩子** */
    componentWillReceiveProps(){
    //触发
    console.log('-- componentWillReceiveProps --');
    }
    render(){
    return (
    <div>
    <span>来自父组件当中的属性 - {this.props.carName}</span>
    </div>
    )
    }
    }

    ReactDOM.render(<A/>,document.getElementById('app'))
    </script>
    </body>

    生命周期17.x

    • 17.x开始,React就不推荐使用下列三种生命周期钩子了,如需使用,需要在前添加前缀UNSAFE_

      • componentWillMount = > UNSAFE_componentWillMount
      • componentWillReceiveProps => UNSAFE_componentWillReceiveProps
      • componentWillUpdate => UNSAFE_componentWillUpdate
    • 与此同时也添加了几个新的生命周期钩子

      • getDerivedStateFromProps:如果该组件所有的state都取决于父组件传递过来的(也就是props),可以使用这个,这个函数必须要返回一个对象或者是null
        • 使用static getDerivedStateFromProps(props,state)
      • getDerivedStateFromError:一旦后台组件报错,就会触发,这个函数必须要返回一个对象或者是null
        • 使用static getDerivedStateFromError(error)
      • getSnapshotBeforUpdate:这个函数必须要配合componentDidUpdate使用且getSnapshotBeforeUpdate必须要有返回值,返回值为componentDidUpdate当中第三个形参
        • 使用getSnapshotBeforeUpdate(preProps,preState)
        • componentDidUpdate(prevProps, prevState, snapshot)
      • componentDidCatch:统计页面的错误,可以发送请求到后台去
        • 使用componentDidCatch(error,info)
    • 新生命周期钩子图示

    示例1-使用不安全的生命周期钩子

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    <body>
    <div id="app"></div>
    <script src="../js/17.0.1/react.development.js"></script>
    <script src="../js/17.0.1/react-dom.development.js"></script>
    <script src="../js/17.0.1/babel.min.js"></script>
    <script type="text/babel">
    class A extends React.Component {
    state = {
    carName:'宝马',
    }

    updateValue = () => {
    this.setState({
    carName:'动感超人'
    })
    }

    /* props发生变化时候的回调 初次渲染的时候不会执行此钩子** */
    UNSAFE_componentWillReceiveProps(){
    //不会触发
    console.log('-- componentWillReceiveProps --');
    }

    UNSAFE_componentWillMount(){
    console.log('-- UNSAFE_componentWillMount --');
    }
    UNSAFE_componentWillUpdate(){
    console.log('-- UNSAFE_componentWillUpdate --');
    }
    render(){
    const {carName} = this.state;
    return (
    <div>
    <button onClick={this.updateValue}>更新值</button>
    <B carName = {carName}/>
    </div>
    )
    }
    }

    class B extends React.Component {

    /* props发生变化时候的回调 初次渲染的时候不会执行此钩子** */
    UNSAFE_componentWillReceiveProps(){
    //触发
    console.log('-- componentWillReceiveProps --');
    }
    render(){
    return (
    <div>
    <span>来自父组件当中的属性 - {this.props.carName}</span>
    </div>
    )
    }
    }

    ReactDOM.render(<A/>,document.getElementById('app'))
    </script>
    </body>

    如果不添加UNSAFE_前缀,后有如下图警告

    示例2-使用新钩子,体会聊天窗口有新消息出现时候的做法

    • 注意点如下

    • getSnapshotBeforeUpdate(preProps, preState):返回更新视图前的props,和state

    • componentDidUpdate(preProps,preState,fromsnapshot):第三个参数名称随意,接收的是来自getSnapshotBeforeUpdate返回的值

    • getSnapshotBeforeUpdatecomponentDidUpdate必须要连起来用,并且getSnapshotBeforeUpdate必须要返回一个值

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    <body>
    <div id="app"></div>
    <script src="../js/17.0.1/react.development.js"></script>
    <script src="../js/17.0.1/react-dom.development.js"></script>
    <script src="../js/17.0.1/babel.min.js"></script>
    <script type="text/babel">
    class Demo extends React.Component {
    state = {
    newsList: [],
    };

    getSnapshotBeforeUpdate(preProps, preState) {
    // console.log("snapshot", preProps, preState); //相同
    //更新前返回更新前的滚动条位置
    return this.wrapperNode.scrollHeight;
    }

    componentDidUpdate(preProps, preState, snapshot) {
    // console.log("componentDidUpdate", preProps, preState); //相同
    //设置滚动条位置
    //snapshot为更新前记录的值
    //而此时的this.wrapperNode.scrollTop 已经为更新后的距离了
    // this.wrapperNode.scollHeight - snapshot 就是新增加的内容所占据的高度
    this.wrapperNode.scrollTop += this.wrapperNode.scrollHeight - snapshot;
    }

    componentDidMount() {
    setInterval(() => {
    //添加定时器
    const { newsList } = this.state;
    const newsTemp = `新闻${newsList.length + 1}`;
    this.setState({
    newsList: [newsTemp,...newsList]
    });
    }, 1000);
    }

    render() {
    return (
    <div className="wrapper" ref={c => this.wrapperNode = c}>
    {
    this.state.newsList.map((item,index) => {
    return <div key={index} className='item'>{item}</div>
    })
    }
    </div>
    );
    }
    }

    ReactDOM.render(<Demo />, document.getElementById("app"));
    </script>
    </body>
    <style>
    .wrapper {
    width: 200px;
    height: 150px;
    overflow: auto;
    background-color: hotpink;
    }
    .item {
    line-height: 30px;
    }
    </style>

    没有使用getSnapshotBeforeUpdate的时候,效果如下,可以看到,每次有新消息出来,位置就会被改变,不会停留在一处,之前的为什么会被改变,那是因为浏览器会自动生成计算scrollTop

    没有使用getSnapshotBeforeUpdate的时候

    现在使用了getSnapshotBeforeUpdate,每一次更新前返回更新前的滚动条位置信息,通过componentDidUpdate手动设置scrollTop,从而每一次都在原处(为什么要减去的,因为每一次都增加了一部分,我想留在原地,当然要减去了)

    那么原理是什么呢? 首先知道,scrollTop是设置(获取)这个元素的内容顶部(卷起来的)到它的视口可见内容(的顶部)的距离,所以如果我们不去改变滚动条,那么scrollTop的值是永远不会改变的(不相信你可以输出看看),scrollTop值不变,内容逐渐增多,就会导致内部容器滚动高度增长,所以就会看到位置逐渐后移的效果(再说通俗点就是修路,你站在原地,别人一直修路,随着路增长,你距离也会离他们越来越远)

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    <body>
    <div id="app"></div>
    <script src="../js/17.0.1/react.development.js"></script>
    <script src="../js/17.0.1/react-dom.development.js"></script>
    <script src="../js/17.0.1/babel.min.js"></script>
    <script type="text/babel">
    class Demo extends React.Component {
    state = {
    newsList: [],
    };

    getSnapshotBeforeUpdate(preProps, preState) {
    // console.log("snapshot", preProps, preState); //相同
    //更新前返回更新前的滚动条位置
    return this.wrapperNode.scrollHeight;
    }

    componentDidUpdate(preProps, preState, snapshot) {
    // console.log("componentDidUpdate", preProps, preState); //相同
    //设置滚动条位置
    //snapshot为更新前记录的值
    //而此时的this.wrapperNode.scrollTop 已经为更新后的距离了
    // this.wrapperNode.scollHeight - snapshot 就是新增加的内容所占据的高度
    this.wrapperNode.scrollTop += this.wrapperNode.scrollHeight - snapshot;
    }

    componentDidMount() {
    setInterval(() => {
    //添加定时器
    const { newsList } = this.state;
    const newsTemp = `新闻${newsList.length + 1}`;
    this.setState({
    newsList: [newsTemp,...newsList]
    });
    }, 1000);
    }

    render() {
    return (
    <div className="wrapper" ref={c => this.wrapperNode = c}>
    {
    this.state.newsList.map((item,index) => {
    return <div key={index} className='item'>{item}</div>
    })
    }
    </div>
    );
    }
    }

    ReactDOM.render(<Demo />, document.getElementById("app"));
    </script>
    </body>

    使用getSnapshotBeforeUpdate的时候

    React的Differ算法

    React/vue中的key有什么作用(key的内部原理是什么?)

    • 作用

      • key用于标识「虚拟DOM」,当状态当中的数据发生改变的时候,React会根据新数据生成「新的虚拟DOM」,之后React会将「新虚拟DOM」「旧虚拟DOM」进行differ比较**(比较的最小力度是节点(标签))**,规则如下
      • 旧虚拟DOM中找到了和新虚拟DOM相同的key
        • 若内容没有发生变化,就直接使用之前的真实DOM
        • 若内容发生了变化,则生成新的真实DOM,随后替换掉页面中与之对应的真实DOM
      • 旧虚拟DOM中未找到与虚拟DOM相同的key
        • 根据数据创建新的真实DOM,随后渲染到页面
    • 大概流程

    为什么遍历列表的时候,key最好不要用index

    • 用index可能引发的问题
      • 若对数据进行:「逆序添加」,「逆序删除」等破坏顺序的操作
        • 会产生没有必须要的真实DOM更新 => 界面没有问题,但是效率低
      • 如果结构中包含输入类的DOM
        • 会产生错误的DOM更新 => 界面有问题
      • 如果不存在对数据的「逆序添加」,「逆序删除」等破坏顺序的操作,仅用于渲染列表用于展示,使用index作为key是没有问题的
    • 开发如何选择key
      • 最好使用每条数据的唯一标识作为key,比如说id,手机号,身份证号,学号等唯一值
      • 如果确定只是简单的展示数据,用index也是可以的

    React脚手架

    • 安装
    1
    npm install create-react-app -g
    • 创建一个名为react_01的react项目
    1
    create-react-app react_01
    • React脚手架创建的项目目录和文件分析

      • public:
        • favicon.ico => 网站图标
        • index.html => 主页面
        • logo192.png / logo512.png => logo图,不同分辨率的
        • manifest.json => 应用加壳的配置文件
        • robots.txt => 爬虫协议文件
      • src:
        • App.css => App组件的样式
        • App.js => App组件
        • App.test.js => 用于给App做测试
        • index.css => 样式
        • index.js => 入口文件
        • logo.svg => logo图,矢量图
        • reportWebVital.js => 页面性能分析文件(需要web-vitals库的支持)
        • setupTests.js => 组件单元测试文件(需要jest-dom库的支持)
    • 我们看下一个没见过的短命令在package.json当中

      • react-scripts eject:可以输出React当中的所有配置文件,过程不可逆
    • 一些小技巧

      • react脚手架当中**,可以省略.js ,.jsx后缀**,并且如果文件夹当中有index.js ,那么引入的时候就可以省略index.js
      • 比如import a from “/component/Person/index” => 可以省为 import a fro “/component/Person”
      • 引入css文件,只需要import "./XXXX.css" 即可
      • 如果组件需要传入全部的props值,可以使用<Item {...todos}/>,使用...进行展开

    React小案例之todoList

    • 重要

      • 操作state数据的时候,只能通过setState去操作,不可以通过其他方式去更改

      • 比如不推荐如下

        1
        2
        3
        let {todos} = this.state;
        todos.unshift(todoObj);
        this.setState({todos});
      • 推荐下面

        1
        2
        3
        4
        const {todos} = this.state;
        this.setState({
        todos:[todoObj,...todos],
        })
    • 知识点

      • 记住,状态驱动数据,数据驱动视图
      • 状态在哪里,操作状态的方法就在哪里
      • arr.reduce方法当中,如果没有传入initialValue,则第一次的preValue值就是数组中第一个元素的值
    • 技巧

      • 结构赋值重命名const {a:newName} = obj;
    • 工具库

      • nanoid:一个小巧、安全、URL友好、唯一的 JavaScript 字符串ID生成器。

    React脚手架配置代理

    方式1-在package.json当中配置

    • 步骤
      • package.json当中添加"proxy": "http://localhost:5000/students"即可,其中http://localhost:5000代表的是服务器的地址
      • 然后我们发送ajax请求的时候
        • 之前会产生跨域问题axios.get('http://localhost:5000/students'),
        • 在package配置好后我们更改为axios.get('http://localhost:3000/students')即可
      • 总结
        • package是实际的服务器ip地址,axios是本地的地址
        • 缺点就是只能配置一台代理服务器~
    • 其他
      • 不知道为什么,我添加proxy,关闭服务重新启动就启动不了,提示Invalid options object. Dev Server has been initialized using an options object that does not match the API schema.,删除后就可以启动了,所以这里就做一个学习吧,反正这方法也不太可能用到

    方式1-src创建配置文件xxxx当中配置(推荐)

    • 1.在src目录下建立setupProxy.js(规范为cjs,也就是nodejs使用的规范)

    • 2.写如下配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    const proxy = require("http-proxy-middleware")

    module.exports = function (app) {
    app.use(
    //api1是用于区分需要转发的请求,所有带有/api1前缀的请求都会转发给5000
    proxy('/api1',{
    target:'http://localhost:5000',//转换到的地址
    /*
    控制服务器接收到的请求头中的host字段的值
    如果配置了,在F12调试工具看到的依旧是本地3000端口,但是在服务器看到的却是5000端口
    */
    changeOrigin:true,
    pathRewrite:{
    '^/api1':'',//去除请求前缀,也就是去除 /api1
    }
    }),
    proxy('/api2',{
    target:'http://localhost:5001',
    changeOrigin:true,
    pathReWrite:{
    '^/api2':'',
    }
    })
    )
    }
    • 关于changeOrigin

    React小案例之github搜索

    fetch和axios

    • fetch:为原生函数,不用安装也可以使用,不使用XMLHttpRequest,但是老版本可能不支持fetch

      • 想获取服务器返回的信息,就不需要像axios一样多访问一层data了,直接就返回的是原始数据

      • 想获取错误信息,依旧是要再访问一层message

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      try {
      let response = await fetch(`https://api.github.com/search/users?q=${value}`)
      let result = await response.json();
      PubSub.publish('updateList', {
      userList:result?.items ?? [],
      isLoading:false,
      })
      }catch (e) {
      PubSub.publish('updateList',{
      isLoading: false,
      errMsg:e.message,
      })
      }
    • Promise之链式调用复习

      • 如果需要链式调用,需要可能返回一个promise,也可以返回一个非promise

        • 如果是返回promise,那么下一步的then的成功还是失败就取决于返回的promise
        • 如果是返回非promise,那么下一步的then状态一定是成功的
      • 链式调用示例

      1
      2
      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
      35
      36
      37
      38
      39
      40
      41
      const bbb = new Promise((resolve,reject)=>{
      let a = Math.random() * 100;
      if(a>50){
      resolve('第一次返回的resolve' + a);//标记为解决,并返回a - 第一次返回
      }else{
      reject('第一次返回的reject' + a);//标记为未解决,并返回a
      }
      });
      bbb.then(res=>{
      console.log(res);
      return new Promise((resolve, reject)=>{
      reject('第二次返回的reject')
      })
      })
      .then(res=>{
      console.log(res);//输出 第二次返回的reject
      })
      .catch(error=>{
      console.log(error);//统一处理错误,不在.then当中处理
      })

      //当然,你也可以写的更加简单点
      new Promise((resolve,reject)=>{
      let a = Math.random() * 100;
      if(a>50){
      resolve('第一次返回的resolve' + a);//标记为解决,并返回a - 第一次返回
      }else{
      reject('第一次返回的reject' + a);//标记为未解决,并返回a
      }
      }).then(res=>{
      console.log(res);
      return new Promise((resolve, reject)=>{
      reject('第二次返回的reject')
      })
      })
      .then(res=>{
      console.log(res);//输出 第二次返回的reject
      })
      .catch(error=>{
      console.log(error);//统一处理错误,不在.then当中处理
      })
      • 调用关系如下 相同颜色的代表为实际调用.then的promise

    React之路由前置

    预备,知道history

    • 知识点
      • 现实中的路由器-管理多个上网设备
      • 前端的路由器-管理多个页面
      • 现在主流的为SPA(也就是单页面,多组件)
      • 什么是哈希路由
        • 通俗点就是定位 #后面的都算的前端的东东,后端接收不到也不会将这些传递给后端
        • 比如 前端请求/abc#/a/b/c,那么向服务器请求的时候,后端收到的是/abc,而不会收到/a/b/c
      • replace操作
        • 原来从上到下是 /test1 ,/test2后面replace(‘/test3’) 那么从上到下就依次变成了/ test1, /test3原来的/test2被替换为了/test3
    • 示例
    1
    2
    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
    35
    36
    37
    38
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>测试history</title>
    <script src="https://cdn.bootcss.com/history/4.7.2/history.js"></script>
    </head>
    <body>
    <a href="http://www.baidu.com" onclick="return push('/test1')">添加一条历史记录</a>
    <button onclick="back()">回退</button>
    <button onclick="goNext()">前进</button>
    <button onclick="replace('/test3')">替换</button>
    <script>
    let history = History.createBrowserHistory();
    /*浏览器添加一条历史记录*/
    function push(path){
    history.push(path);
    return false;
    }

    function back(){
    history.goBack();
    }

    function goNext(){
    history.goForward();
    }
    function replace(path){
    history.replace(path);
    }
    //监听路径改变
    history.listen(location=>{
    console.log("请求路径发生了变化",location)
    })
    </script>
    </body>
    </html>

    React路由@5版本

    基本使用

    1. 安装react-router-dom库,这里以5.2.0版本为例子,6.x开始写法就和5.2.0不一样了

      1. 如果出现A <Route> is only ever to be used as the child of <Routes> element, never rendered directl就说明用5.2.0的版本的写法在6.x版本了,所以要么更改6.x的写法,要么更换react-router-dom为5.2.0版本
    2. index.js主入口文件最外层包裹一个<BrowserRouter></BrowserRouter>或者<HashRouter></HashRouter>,因为如果不这样子做,每次使用路由,都需要在使用的地点添加此标签,所以可以直接在主入口统一添加

      1
      2
      3
      4
      5
      6
      7
      8
      import React from "react";
      import ReactDOM from "react-dom";
      import App from "./App";
      import {BrowserRouter} from "react-router-dom";
      ReactDOM.render(
      (<BrowserRouter>
      <App/>
      </BrowserRouter>),document.getElementById('root'))
    3. 导航区的标签更换为Link标签或NavLink标签

      1. Link标签:点完要去一个地方,使用to来指明 <Link to="/about">前往About页面</Link>
      2. NavLink标签:点完要去一个地方,使用to来指明,和Link不同的是,多了activeClassName属性,可以指明选中路由的样式,如果不使用activeClassName指明活动类样式,那么会自动添加一个类名为active,使用和Link是一样的使用<NavLink activeClassName="activeStyle" to="/about">前往About页面</NavLink>
    4. 知道什么是一般组件(非路由组件)和路由组件

      1. 程序员没有直接写组件方式去使用组件,而是通过路由标签使用的形式,那么就是路由组件,否则的话就是非路由组件 还有就是路由组件不可以传递props属性.会收到固定的属性,而非路由组件(一般组件),可以接收到props
    5. 如果在路由组件输出this.props,会输出如下图内容

    1. 路由组件,接收到的三个固定的属性

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      //操作历史记录的的方法
      history:
      go: f go(n)
      goBack: f goBack()
      goForward: f goForward()
      push: f push(path,state)
      replace: f replace(path,state)

      location:
      pathname: "/about"
      //接收传递过来的search参数(也就是query参数)
      search: ""
      //接收传递过来的state参数
      state: undefined


      match:
      //接收传递过来的params参数
      params:{}
      path: "/about"
      url: "/about"

    基本使用示例代码

    index.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import React from "react"
    import ReactDOM from "react-dom"
    import App from "./App";
    import {BrowserRouter} from "react-router-dom";

    ReactDOM.render(
    <BrowserRouter>
    <App/>
    </BrowserRouter>
    ,document.getElementById('root'));

    App.jsx

    1
    2
    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
    35
    36
    37
    38
    39
    40
    import React, { Component } from 'react'
    import Home from './pages/Home'
    import About from './pages/About'
    import {Link,Route} from "react-router-dom"
    export default class App extends Component {
    render() {
    return (
    <div>
    <div className="row">
    <div className="col-xs-offset-2 col-xs-8">
    <div className="page-header"><h2>React Router Demo</h2></div>
    </div>
    </div>
    <div className="row">
    <div className="col-xs-2 col-xs-offset-2">
    <div className="list-group">
    {/*导航栏*/}
    {/*使用Link*/}
    {/*<Link to='/about'>跳转到About</Link>*/}
    {/*<Link to='/home'>跳转到HOME页面</Link>*/}

    {/*使用NavLink*/}
    <NavLink activeClassName="demo" to="/about">关于</NavLink>
    <NavLink activeClassName="demo" to="/home">首页</NavLink>
    </div>
    </div>
    <div className="col-xs-6">
    <div className="panel">
    <div className="panel-body">
    {/*导航的结果*/}
    <Route path='/about' component={About}></Route>
    <Route path='/home' component={Home}></Route>
    </div>
    </div>
    </div>
    </div>
    </div>
    )
    }
    }

    pages/About.jsx

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import React, {Component} from 'react';

    class About extends Component {
    render() {
    return (
    <div>
    我是About
    </div>
    );
    }
    }

    export default About;

    pages/Home.jsx

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import React, {Component} from 'react';

    class Home extends Component {
    render() {
    return (
    <div>
    我是HOME
    </div>
    );
    }
    }

    export default Home;
    1. 现在的组件基本上都是这样子<标签 属性A = "属性值" 属性B = "属性值">标签体</标签>,在react当中,标签体会放置在children属性当中,也就是可以通过this.props.children读取标签体内容
    2. 这样子就可以不用每次书写className了~,简化了我们操作
    3. MyNavLink示例如下代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import React, {Component} from 'react';
    import {NavLink,} from "react-router-dom";


    class MyNavLink extends Component {
    render() {
    return (
    <div>
    {/* 接收传递过来的数据,并不论多少,都结构并赋值给NavLink组件 */}
    <NavLink className="list-group-item" {...this.props}/>
    </div>
    );
    }
    }

    export default MyNavLink;

    Switch的在路由的使用-遇见就返回

    • 正常情况下,react通过匹配路径的方式去寻找对应路径,并渲染,但是找到后不会停止寻找,而是会继续寻找

      1
      2
      3
      4
      5
      6
      7
      8
      9
      //当我们切换到/about路径的时候

      //本意是想渲染 About组件,通过匹配路径的方式,然后匹配后react依旧会接着匹配
      //匹配成功,显示
      <Route path="/about" component={About}/>
      //匹配失败,不显示
      <Route path="/home" component={Home}/>
      //匹配成功,显示
      <Route path="/about" component={Other}/>
    • 这种找到后还接着寻找,很会影响效率,所以我们可以在外面嵌套一层Switch,(在vue当中就不用)

      1
      2
      3
      4
      5
      6
      7
      8
      import {Switch} from "react-router-dom";
      //当我们切换到/about路径的时候
      //匹配成功,显示,不会接着往下匹配
      <Route path="/about" component={About}/>

      <Route path="/home" component={Home}/>

      <Route path="/about" component={Other}/>

    解决路径丢失的问题

    • 当我们路径从/about => 改为 /gugu/about的时候
    1
    2
    3
    4
    5
    <NavLink className="list-group-item" to="/gugu/about">About</NavLink>
    <NavLink className="list-group-item" to="/gugu/home">HOME</NavLink>

    <Route path="/gugu/about" component={About}/>
    <Route path="/gugu/home" component={Home}/>
    • 切换路由后刷新页面,就会发现页面css丢失了

    • 可以看到,路由切换到/gugu/about后刷新页面,发现样式丢失了,原因如下
      • 在引入样式的时候,使用了 href="./css/bootstrap.css",也就是去寻找当前目录下的bootstrap,所以我们切换到/gugu/about,这个是一个多级路径,所以react会认为gugu300端口下的一个路径,所以就造成浏览器去访问http://localhost:3000/gugu/css/bootstrap.css了,正常的应该是寻找http://localhost:3000/css/bootstrap.css
    • 解决办法
      1. 引入样式的时候,去掉 . ,原来的 href="./css/bootstrap.css" => 改为 href="/css/bootstrap.css",这样子浏览器就会永远去3000端口下寻找东西
      2. 引入样式的时候, 原来的 href="./css/bootstrap.css" => 改为 href="%PUBLIC_URL%/css/bootstrap.css",让react永远将这个资源定位到public目录下的css目录
      3. 使用HashRouter(也就是哈希模式)
        • 因为之前我们说过,哈希模式,是不会将#后面的视为浏览器请求内容的,也就是说,即使你路径切换到了http://localhost:3000/#/gugu/home,#后面的/gugu/home浏览器都不会去使用的,查找资源的时候,只会使用#左侧指明的路径
          • 所以请求css的时候的请求地址始终为http://localhost:3000/css/xxxx
        • 考考你,当浏览器http://localhost:3000/abc/efg#/gugu/home请求一个css,那么url的请求地址会是什么?
          • 答案是http://localhost:3000/abc/efg/css

    BrowserRouter和HashRouter的区别

    • 底层原理不一样
      • BrowserRouter使用的是H5的history API,不兼容IE9及以下版
      • HashRouter使用的是URL的哈希值
    • path表现形式不一样
      • BrowserRouter路径中没有**#**,例如:localhost:3000/demo/test
      • HashRouter的路径中包含**#,例如:localhost:3000/#/demo/test,并且#**后面的内容服务端是不会接收到的
    • 刷新后对路由state参数的影响
      • BrowserRouter没有任何影响(但是清空缓存后会有),因为state保存在history对象中
      • HashRouter老师我看是可以使用的,但是我使用就报Warning: Hash history cannot replace state; it is ignored,并且state输出也是undefined,所以估计HashRouter不支持传递state了
    • 备注
      • HashRouter可以解决一些路径错误相关的问题,因为并且**#**后面的内容服务端是不会接收到的,所以不管怎么样请求资源,都是会以localhost:3000开头的,不会出现localhost:3000/about这种开头

    路由的匹配之模糊路由和精准(严格)匹配

    • 知道react的路由匹配是怎么匹配的 - 模糊匹配
      • path要的东西,to里面一个都不能少,其他你随意.并且path当中的顺序不能乱
        • 比如to = "/a/home/c" path = "/home/a/c" 那么react会将to拆分为 a 和 home 和 cpath拆分为 home 和 a 和 c ,path当中和to去匹配,发现第一个home 和 a 不对应,就不匹配下去了,就匹配失败了
        • 比如 to = "/home/c" path = "/home" 那么react会将to拆分为 home和cpath拆分为 home path当中和to去匹配,path当中的都与to都的匹配,所以就匹配成功了
    • 精准(严格)匹配开启
      • <Route exact={true}></Route> 或者 <Route exact ></Route> 即为开启严格匹配
    • 严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由

    Redirect标签 - 重定向

    • 路由的兜底
    • 当路由都匹配不到的时候,就会使用Redirect当中的to值,放的顺序我试了,放在最前面也没有事
    1
    2
    3
    4
    5
    6
    7
    <Switch>
    <Route path="/about" component={About}/>
    <Route path="/home" component={Home}/>
    //当上面的都匹配不到的时候,就会匹配Redirect组件当中的to值
    <Redirect to="/home"/>
    </Switch>

    嵌套路由

    • 什么是嵌套呢,就是一个展示区里面又出现了另外一个展示区
    • 必须要知道的知识点-路由匹配
      • React的路由匹配的从最开始注册的路由开始匹配的,一直匹配到最后注册的路由(也就是从一级路由开始,二级路由继续后,三级路由继续,…..一直继续下去)
      • 所以当我们路径切换到/home/news的时候,React会从一级路由开始(如果有)
        • 一级路由有 /home 和 /about 那么一级路由将会匹配 /home (因为是模糊匹配),所以会展示/home下的所有组件
        • 当我们来到/home的时候,就会开始二级路由匹配(如果有)
        • 二级路由有 /home/news 和 /home/message ,那么二级路由就会匹配/home/news,所以会展示/home/news下的组件
        • ….如果存在三级,四级等,就会按照这个规律匹配下去
    • 所以为什么要慎重开始严格(精准)匹配,因为如果开启了严格
      • 如果路径变为了/home/news,React会从一级路由开始,那么/home/news,不会和/home进行匹配,因为开启了严格(精准)匹配
    • 示例(伪代码)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <div>
    <h3>我是HOME</h3>
    <ul className="nav nav-tabs">
    <li>
    //自定义的NavLink,没封装什么
    <MyNavLink to="/home/news">News</MyNavLink>
    </li>
    <li>
    <MyNavLink to="/home/message">Message</MyNavLink>
    </li>
    </ul>
    <Switch>
    {/* 注册路由 */}
    <Route path="/home/news" component={News}></Route>
    <Route path="/home/message" component={Message}></Route>
    {/* 兜底 */}
    <Redirect to="/home/news"></Redirect>
    </Switch>
    </div>

    路由传参之params

    • params传参,在vue,axios,react之中,都是这种形式/a/b/c/?/?/?/....

    • react添加params

      • 路由导航(路由链接):
        • <Link to="/home/message/1">
      • 注册路由(路由当中需要声明):
        • <Route path = "/home/message/:id" component = "ABC"/>
        • 如果存在多个也是这种写法<Route path = "/home/message/:id/:name" component = "ABC"/>
    • 接收参数: 子组件可以通过this.props.match.params接收传递的params参数,如果没有传params参数,则会返回空对象

    • 重新来看下路由接收到的三个固定的属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    //操作历史记录的的方法
    history:
    go: f go(n)
    goBack: f goBack()
    goForward: f goForward()
    push: f push(path,state)
    replace: f replace(path,state)

    location:
    pathname: "/about"
    //接收传递过来的search参数(也就是query参数)
    search: ""
    //接收传递过来的state参数
    state: undefined


    match:
    //接收传递过来的params参数
    params:{}
    path: "/about"
    url: "/about"
    • 示例(伪代码)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    //Message组件
    render() {
    const { messageList } = this.state;
    return (
    <div>
    <div>
    <ul>
    {
    messageList.map(item => {
    return (
    <li key={item.id}><Link to={`/home/message/detail/${item.id}`}>{item.title}</Link></li>
    )
    })
    }
    </ul>
    </div>
    <div>
    <Route path="/home/message/detail/:id" component={Detail}/>
    </div>
    </div>
    );
    }

    1
    2
    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
    //Detail组件
    import React, {Component} from 'react';

    class Detail extends Component {
    state = {
    list:[
    {id:1,title:"天气",info:'今天的天气很好的'},
    {id:2,title:"吃饭",info:'你吃饭了没有呀'},
    {id:3,title:"喜欢",info:'我喜欢小梦奇呀'},
    ]
    }
    render() {
    //接收传递过来的id
    const {id} = this.props?.match?.params;//字符串的id
    const {list} = this.state;
    //寻找item 找不到就返回一个空对象,否者下面如果undefined.id 就会报错
    const result = list.find(item => item.id == id) ?? {};
    return (
    <ul>
    <li>ID:{result.id}</li>
    <li>TITLE:{result.title}</li>
    <li>CONTENT:{result.info}</li>
    </ul>
    );
    }
    }

    export default Detail;

    路由传参之search

    • search传参和vue,axios的query参数一样,也是通过?来区分,比如/a?name=abc&id=1这种形式

    • React添加search参数

      • **路由导航(路由链接):**当中<Link to="/demo/test?name=tom&age=18">信息</Link
      • 注册路由(search参数不需要声明):<Route path = "/demo/test" component = {Demo} />
    • 接收参数:

      • 子组件通过this.props.location.search进行获取,如果是没有传search参数,则会返回空字符串,否则就会返回字符串,比如?name=tom&age=18
    • 特别注意

      • 获取到的search是字符串(?name=tom&age=18) 所以需要通过querystring进行解析

      • querystring解析

        1
        2
        3
        4
        5
        import qs from "querystring";
        const demo = '?name=tom&age=18';
        const result = qs.parse(demo.slice(1));
        //输出对象 注意,value均为字符串!
        // { name:'tom',age:'18' }
      • querystring编码

        1
        2
        3
        4
        5
        import qs from "querystring";
        const demo = { name:'tom',age:'18' }
        const result = qs.stringify(demo)
        //输出字符串
        // "name=tom&age=18"
    • 伪代码示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
     //search传参
    <li key={item.id}><Link to={`/home/message/detail?id=${item.id}`}>{item.title}</Link></li>

    //search传参
    <Route path = "/home/message" component={Detail}/>

    /* search传参 */
    const searchData = this.props.location.search;
    const searchDataObj = qs.parse(searchData.slice(1));
    const id = searchDataObj.id;
    • 重新来看下路由接收到的三个固定的属性
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    //操作历史记录的的方法
    history:
    go: f go(n)
    goBack: f goBack()
    goForward: f goForward()
    push: f push(path,state)
    replace: f replace(path,state)

    location:
    pathname: "/about"
    //接收传递过来的search参数(也就是query参数)
    search: ""
    //接收传递过来的state参数
    state: undefined


    match:
    //接收传递过来的params参数
    params:{}
    path: "/about"
    url: "/about"

    路由传参之state

    • React添加state参数

      • **路由导航(路由链接):**当中<Link to={{pathname:'/home/message',state:{id:666}}}>信息</Link
      • 注册路由(search参数不需要声明):<Route path = "/home/message" component = {Demo} />
    • 接收参数:

      • 子组件通过this.props.location.state进行获取,如果是没有传state参数,则会返回undefined,否则就会返回对象,比如{ id:666} 注意,这个666是number类型,而不是string类型,因为你在路由导航中传入的是number
    • 特别注意

      • state传参只适用于BrowserRouter 使用在HashRouter会出现react_devtools_backend.js:4026 Warning: Hash history cannot PUSH the same path; a new entry will not be added to the history stack,并且没有效果在哈希模式下
      • 刷新可以保留住参数,但是清空缓存会没有
    • 示例(伪代码)

    1
    2
    3
    4
    5
    6
    7
    8
    //state传参
    <li key={item.id}><Link to={{pathname:'/home/message',state:{id:item.id}}}>{item.title}</Link></li>

    {/* state传参 */}
    <Route path = "/home/message" component={Detail}/>

    /* state传参 */
    const searchData = this.props.location.state ?? {};
    • 重新来看下路由接收到的三个固定的属性
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    //操作历史记录的的方法
    history:
    go: f go(n)
    goBack: f goBack()
    goForward: f goForward()
    push: f push(path,state)
    replace: f replace(path,state)

    location:
    pathname: "/about"
    //接收传递过来的search参数(也就是query参数)
    search: ""
    //接收传递过来的state参数
    state: undefined


    match:
    //接收传递过来的params参数
    params:{}
    path: "/about"
    url: "/about"

    路由的push 和 replace

    • 默认情况下是push
    • 如果需要开启replace,只需要在路由声明当中添加replace即可
    • <Link replace to="/demo">跳转到Demo</Link><Link replace={true} to="/demo">跳转到Demo</Link>
    • 当然,你也可以直接就写一个单词<Link replace to="/demo">跳转到Demo</Link>

    路由编程式导航

    • 编程式导航

      • 通过借助this.props.history对象上面的方法实现跳转,前进,后退,比如
        • this.props.history.push(path,state)
        • this.props.history.replace(path,state)
        • this.props.history.goBack()
        • this.props.history.goForward()
        • this.props.history.go(n)
      • push方式(默认)
        • 调用this.props.history.push(path,state),第一个path为路径,第二个state为state传参
      • replace
        • 调用this.props.history.replace(path,state),第一个path为路径,第二个state为state传参
    • 声明式导航

      • 在react当中就是通过Link 或者是 NavLink就是声明式导航
      • 在vue当中就是<router-link></router-link>
    • 示例(伪代码)

    1
    2
    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
    //编程式导航传递 - push
    //<button key={item.id} onClick={() => this.pushShow(item.id)}>编程式-pushShow{item.title}</button>

    //编程式导航传递 - params
    <button key={item.id} onClick={() => this.replaceShow(item.id)}>编程式-replaceShow{item.title}</button>


    /* 编程式导航 - replace */
    replaceShow = (id) => {
    //params传参
    this.props.history.replace(`/home/message/detail/${id}`);

    //search传参
    //this.props.history.replace('/home/message/detail?id='+id);

    //state传参
    //this.props.history.replace('/home/message/detail',{id});
    }

    /* 编程式导航 - push */
    pushShow = (id) => {
    //params传参
    //this.props.history.push(`/home/message/detail/${id}`);

    //search传参
    //this.props.history.push('/home/message/detail?id='+id);

    //state传参
    //this.props.history.push('/home/message/detail',{id});
    }
    • 重新来看下路由接收到的三个固定的属性
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    //操作历史记录的的方法
    history:
    go: f go(n)
    goBack: f goBack()
    goForward: f goForward()
    push: f push(path,state)
    replace: f replace(path,state)

    location:
    pathname: "/about"
    //接收传递过来的search参数(也就是query参数)
    search: ""
    //接收传递过来的state参数
    state: undefined


    match:
    //接收传递过来的params参数
    params:{}
    path: "/about"
    url: "/about"

    路由的withRouter

    • 作用

      • withRouter可以让一般组件(非路由组件),可以让一般组件使用路由组件的相关API
      • withRouter调用后返回是一个新组件
    • 使用

      • 返回的时候用这个包裹一下
    • 示例(伪代码-在一般组件当中使用)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    import {withRouter} from "react-router-dom";
    import React,{Component} from "react";
    import {withRouter} from "react-router-dom";
    class Header extends Component {
    render() {
    return (
    <div className="row">
    <div className="col-xs-offset-2 col-xs-8">
    <div className="page-header">
    <h2>React Router Demo</h2>
    {/* 如果是一般组件,那么this.props.history会是undefined */}
    {/* 使用需要通过 withRouter来让一般组件可以使用路由组件的API */}
    <button onClick={() => this.props.history.goBack()}>返回</button>
    <button onClick={() => this.props.history.goForward()}>前进</button>
    </div>
    </div>
    </div>
    )
    }
    }
    export default withRouter(Header)

    React路由@6版本

    概述

    1. React Router 以三个不同的包发布到 npm 上,它们分别为:

      1. react-router: 路由的核心库,提供了很多的:组件、钩子。
      2. react-router-dom: 包含react-router所有内容,并添加一些专门用于 DOM 的组件,例如 <BrowserRouter>
      3. react-router-native: 包括react-router所有内容,并添加一些专门用于ReactNative的API,例如:<NativeRouter>等。
    2. 与React Router 5.x 版本相比,改变了什么?

      1. 内置组件的变化:移除<Switch/> ,新增 <Routes/>等。

      2. 语法的变化:component={About} 变为 element={<About/>}等。

      3. 新增多个hook:useParamsuseNavigateuseMatch等。

      4. 官方明确推荐函数式组件了!!!

        ……

    一级路由

    • 在react路由@6版本当中,我们必须要在所有的Route外面包一层Routes,作用和Switch类型,Routes中的Route只有一个会被匹配
    • Route不再使用component属性,而是element属性
    • <Route caseSensitive> 属性用于指定:匹配时是否区分大小写(默认为 false)。
    • 伪代码示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import React from 'react'
    import {NavLink,Route,Routes,Navigate} from "react-router-dom";
    import About from "./views/About";
    import Home from "./views/Home";
    export default function App() {
    return (
    <div>
    <NavLink className="list-group-item" to="/home">HOME</NavLink>
    <NavLink className="list-group-item" to="/about">About</NavLink>
    <Routes>
    <Route path="/home" element={<Home/>}></Route>
    <Route path="/about" element={<About/>}></Route>
    </Routes>
    </div>
    )
    }
    • react路由@6当中,由于删除了Redirect,所以我们需要使用Navigate来达到重定向
    • Navigate只要被渲染出来,就会引起视图的切换
    • replace属性用于控制跳转模式(push 或 replace,默认是push)。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <Routes>
    <Route path="/home" element={<Home/>}></Route>
    <Route path="/about" element={<About/>}></Route>
    <Route path="/" element={<Navigate to="/home"/>}></Route>
    </Routes>

    import React,{useState} from 'react'
    import {Navigate} from 'react-router-dom'

    export default function Home() {
    const [sum,setSum] = useState(1)
    return (
    <div>
    <h3>我是Home的内容</h3>
    {/* 根据sum的值决定是否切换视图 */}
    {sum === 1 ? <h4>sum的值为{sum}</h4> : <Navigate to="/about" replace={true}/>}
    <button onClick={()=>setSum(2)}>点我将sum变为2</button>
    </div>
    )
    }
    • 在router@5版本当中,我们可以为NavLink添加activeClassName来指定激活状态下的样式

    • 但是在router@6版本当中,就没有这个了,取而代之的是我们需要通过函数来传递类名

    • 语法

      • className的方式
      1
      2
      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
      35
      //css内容
      .active_style{
      background-color: red;
      }


      //jsx内容
      import React from 'react';
      import {Link,NavLink} from "react-router-dom";
      import "./test.css";
      const Menu = () => {
      const activeStyle = ({isActive}) => {
      return isActive ? 'active_style' : null;
      }
      return (
      <div>
      <Link to={'/home'}>Home页面</Link>
      {/*<NavLink to='/home/page' className={activeStyle}>Home页面下的page</NavLink>*/}
      {/*或者*/}
      <NavLink to={'/home/page'} className={
      ({isActive}) => {
      return isActive ? 'active_style' : null;
      }
      }>Home页面下的page</NavLink>
      <Link to={'/about'}>关于页面</Link>
      </div>
      );
      };

      export default Menu;

      //激活的时候HTML状态
      <a href="/home/page" aria-current="page" class="active_style">Home页面下的page</a>
      //未激活的时候HTML状态
      <a href="/home/page">Home页面下的page</a>
      • 或者styleName的方式
      1
      2
      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
      import React from 'react';
      import {Link,NavLink} from "react-router-dom";
      const Menu = () => {
      const activeStyle = ({isActive}) => {
      return isActive ? {backgroundColor:'red'} : null;
      }
      return (
      <div>
      <Link to={'/home'}>Home页面</Link>
      {/*<NavLink to='/home/page' style={*/}
      {/* ({isActive}) => {*/}
      {/* return isActive ? {backgroundColor:'red'} : null*/}
      {/* }*/}
      {/*}>Home页面下的page</NavLink>*/}
      <NavLink to='/home/page' style={activeStyle}>Home页面下的page</NavLink>
      <Link to={'/about'}>关于页面</Link>
      </div>
      );
      };

      export default Menu;

      //激活的时候HTML状态
      <a aria-current="page" class="active" href="/home/page" style="background-color: red;">Home页面下的page</a>

      //未激活的时候HTML状态
      <a class="" href="/home/page" style="">Home页面下的page</a>
      • isActive代表该路由标签是否是活动状态
    • 使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
     <div className="list-group">
    <NavLink className={ ({isActive}) => isActive ? 'list-group-item demo' : 'list-group-item' } to="/home">HOME</NavLink>
    <NavLink className={ ({isActive}) => isActive ? 'list-group-item demo' : 'list-group-item' } to="/about">About</NavLink>
    <NavLink className={ ({isActive}) => isActive ? 'list-group-item demo' : 'list-group-item' } to="/demo">Demo</NavLink>
    </div>



    //你也可以写成一个函数的形式
    const getClassName = ({isActive}) => {
    return isActive ? 'list-group-item demo' : 'list-group-item'
    }
    <div className="list-group">
    <NavLink className={ getClassName } to="/home">HOME</NavLink>
    <NavLink className={ getClassName } to="/about">About</NavLink>
    <NavLink className={ getClassName } to="/demo">Demo</NavLink>
    </div>

    useRoutes(类似于vue的路由表)

    • 在之前,我们是通过下面这样子书写注册路由的
    1
    2
    3
    4
    5
    6
    <Routes>
    <Route path="/home" element={<Home/>}></Route>
    <Route path="/about" element={<About/>}></Route>
    <Route path="/demo" element={<Demo/>}></Route>
    <Route path="/" element={<Navigate to="/home"/>}></Route>
    </Routes>
    • 有了useRoutes后,我们就可以通过此来构成这种结构

    • 语法

      • const element = useRoutes([ {path:匹配的路径,element:要渲染的组件,children:子路由结构相同} ])
    • 示例

    1
    2
    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
    import {NavLink,useRoutes} from "react-router-dom";
    const element = useRoutes([
    {
    path:'/home',
    element:<Home/>
    },
    {
    path:'/about',
    element:<About/>,
    },
    {
    path:'/demo',
    element:<Demo/>
    }
    ])

    <div className="list-group">
    <NavLink className="list-group-item" to="/home">HOME</NavLink>
    <NavLink className="list-group-item" to="/about">About</NavLink>
    <NavLink className="list-group-item" to="/demo">Demo</NavLink>
    </div>

    <div className="panel-body">
    {element}
    {/*<Routes>*/}
    {/* <Route path="/home" element={<Home/>}></Route>*/}
    {/* <Route path="/about" element={<About/>}></Route>*/}
    {/* <Route path="/demo" element={<Demo/>}></Route>*/}
    {/* <Route path="/" element={<Navigate to="/home"/>}></Route>*/}
    {/*</Routes>*/}
    </div>

    不过routes都是统一管理

    • 所以在src下建立router/index.js

    • index.js内容如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import About from "../views/About";
    import Home from "../views/Home";
    import Demo from "../views/Demo"
    export default [
    {
    path:'/home',
    element:<Home/>
    },
    {
    path:'/about',
    element:<About/>,
    },
    {
    path:'/demo',
    element:<Demo/>
    }
    ]
    • 我们在使用的时候就需要引入并生成路由表
    1
    2
    3
    4
    5
    6
    7
    import {useRoutes} from "react-router-dom";
    const element = useRoutes(routes);

    <div className="panel-body">
    //使用路由表 注册路由
    {element}
    </div>

    嵌套路由和Outlet

    • 嵌套路由,就是本身自己是一个路由组件,里面又嵌套了另外一个路由组件

    • 路由表和路由的书写技巧

      • 不可以写斜杆,写斜杆代表不管你从哪里开始,我都是从/开始的,
        所以我们需要在不破坏当前路由的基础上把路由加进去,所以不能有/
      • /带了斜杆就是根了(也就url的路径从我开始算起)
      • ./在不破坏当前路径下载后面添加内容,也可以不写./
      • 于是乎下面三个效果都是一样的,最终匹配的都是/home/message
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      //路由表
      {
      path:'/home',
      element:<Home/>
      children:[
      path:"./message",
      path:'/home/message',
      path:'message',
      ]
      }
      //路由链接的to的书写也和这个相同,
      //下面二个均是在对应url后面添加相应的内容
      //比如之前url为/home,那么点击`message组件显示`,
      //那么url就会变为/home/message
      <NavLink to="message">Message组件显示</NavLink>
      <NavLink to="news">News组件显示</NavLink>
    • 示例需求,在Home组件下建立二个子路由组件,我们先建立好如下的路由表

    1
    2
    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
    35
    36
    37
    38
    import About from "../views/About";
    import Home from "../views/Home";
    import Demo from "../views/Demo"

    /*Home下方的二个子路由组件*/
    import Message from "../views/Message"
    import News from "../views/News";

    export default [
    {
    path:'/home',
    element:<Home/>,
    //添加children属性,写法和上面一样
    children:[
    {
    //path:"./message",
    //或者
    //path:'/home/message',
    //或者,效果都是一样的
    path:'message',
    element:<Message/>
    },
    {
    path:'news',
    element: <News/>
    }
    ]
    },
    {
    path:'/about',
    element:<About/>,
    },
    {
    path:'/demo',
    element:<Demo/>
    }
    ]

    • 然后我们在Home组件当中书写路由链接,并使用Outlet进行占位显示(表示组件要显示的位置)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import {Outlet,NavLink} from "react-router-dom";
    export default function Home() {
    return (
    <div>
    <h1>我是Home组件</h1>
    <div>
    <NavLink to="message">Message组件显示</NavLink>
    <NavLink to="news">News组件显示</NavLink>
    </div>
    <hr/>

    <div>需要展示的组件</div>
    <Outlet/>
    </div>
    )
    }

    效果

    路由params传参

    • 传参

      1
      2
      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
      1.在路由表当中使用占位
      import Detail from "../views/Detail"
      {
      path:"detail/:id/:title/:content",
      /*params传参*/
      element:<Detail/>
      }

      2.路由传入参数
      const [list] = useState([
      {id:'001',title:'消息1',content:'动感超人1'},
      {id:'002',title:'消息2',content:'动感超人2'},
      {id:'003',title:'消息3',content:'动感超人3'},
      {id:'004',title:'消息4',content:'动感超人4'},
      {id:'005',title:'消息5',content:'动感超人5'},
      ])
      <ul>

      {list.map(item => (
      <li key={item.id}>
      <Link to={`detail/${item.id}/${item.title}/${item.content}`}>
      {item.title}
      </Link>
      </li>
      ))}

      </ul>

    • 接收参数

      • 使用useParams获取params
        • 直接获取传入占位符所组成的对象
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      import React from 'react';
      import {useParams} from "react-router-dom";
      const Detail = () => {
      const data = useParams();
      //{id: '003', title: '消息3', content: '动感超人3'}
      return (
      <div>
      <p>{data.id}</p>
      <p>{data.title}</p>
      <p>{data.content}</p>
      </div>
      )
      };

      export default Detail;

      • 使用useMatch获取params参数
        • 调用此函数需要传入完整的,包含占位的url
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      import React from 'react';
      import {useParams,useMatch} from "react-router-dom";
      const Detail = () => {
      const data2 = useMatch('/home/message/detail/:id/:title/:content')
      const {params} = data2;
      return (
      <div>
      <p>{params.id}</p>
      <p>{params.title}</p>
      <p>{params.content}</p>
      </div>
      )
      };

      export default Detail;

      输出useMatch的返回值

    路由search传参

    • 传参

      1
      2
      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
      1.在路由表当中书写路由,search参数不需要任何占位
      import Detail from "../views/Detail"
      {
      path:"detail",
      element:<Detail/>
      }

      2.路由传入参数
      const [list] = useState([
      {id:'001',title:'消息1',content:'动感超人1'},
      {id:'002',title:'消息2',content:'动感超人2'},
      {id:'003',title:'消息3',content:'动感超人3'},
      {id:'004',title:'消息4',content:'动感超人4'},
      {id:'005',title:'消息5',content:'动感超人5'},
      ])
      <ul>

      {list.map(item => (
      <li key={item.id}>
      <Link to={`detail?id=${item.id}&title=${item.title}&content=${item.content}`}>
      {item.title}
      </Link>
      </li>
      ))}

      </ul>

    • 接收参数

      • 使用useSearchParams,用法有点类似useState
        • const [xxx,setXXX] = useSearchParams( )
        • xxx通过调用get方法,并传入要获取的key值,就可以得到对于search参数的值
        • setXXX传入search参数
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      import React from 'react';
      import {useSearchParams} from "react-router-dom";

      const Detail = () => {
      const [search,setSearch] = useSearchParams();
      const id = search.get('id');
      const title = search.get('title');
      const content = search.get('content');
      return (
      <div>
      <p>{id}</p>
      <p>{title}</p>
      <p>{content}</p>
      <button onClick={() => setSearch('id=888&title=设置的标题&content=设置的内容')}>点击我调用setSearch方法</button>
      </div>
      )
      };

      export default Detail;

      当然了,xxx不光只有search,还有其他方法,类型推断出来的

      search的其他方法

    调用setSearch

    • 当然,你也可以使用useLocation来获取参数
    1
    2
    import {useLocation} from "react-router-dom"
    console.log(useLocation())

    输出查看useLocation

    路由state传参

    • 传参
    1
    2
    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
    1.在路由表当中书写路由,search参数不需要任何占位
    import Detail from "../views/Detail"
    {
    path:"detail",
    element:<Detail/>
    }

    2.路由传入参数
    const [list] = useState([
    {id:'001',title:'消息1',content:'动感超人1'},
    {id:'002',title:'消息2',content:'动感超人2'},
    {id:'003',title:'消息3',content:'动感超人3'},
    {id:'004',title:'消息4',content:'动感超人4'},
    {id:'005',title:'消息5',content:'动感超人5'},
    ])


    <ul>
    {list.map(item => (
    <li key={item.id}>
    <Link to='detail' state = {{
    id:item.id,
    title:item.title,
    content:item.content,
    }}>
    {item.title}
    </Link>
    </li>
    ))}
    </ul>

    • 接收参数

      • 使用useLocation
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      import React from 'react';
      import {useLocation} from "react-router-dom";
      const Detail = () => {
      const { state:{id,title,content} } = useLocation()
      return (
      <div>
      <p>{id}</p>
      <p>{title}</p>
      <p>{content}</p>
      </div>
      )
      };

      export default Detail;

      输出查看useLocation返回值

    编程式路由导航

    • 使用useNavigate即可
    • 示例
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    import {useState} from "react";
    import {Link,Outlet,useNavigate} from "react-router-dom";
    export default function Message() {
    const navigate = useNavigate();
    const [list] = useState([
    {id:'001',title:'消息1',content:'动感超人1'},
    {id:'002',title:'消息2',content:'动感超人2'},
    {id:'003',title:'消息3',content:'动感超人3'},
    {id:'004',title:'消息4',content:'动感超人4'},
    {id:'005',title:'消息5',content:'动感超人5'},
    ])
    const jump = ({id,title,content}) => {
    navigate('detail',{
    replace:false,
    state:{
    id,
    title,
    content,
    }
    })
    }
    return (
    <div>
    <ul>
    {list.map(item => (
    <li key={item.id}>
    <Link to='detail' state = {{
    id:item.id,
    title:item.title,
    content:item.content,
    }}>
    {item.title}
    </Link>
    <button onClick={() => jump(item)}>点击我也可以跳转</button>
    </li>
    ))}
    </ul>
    <Outlet/>
    </div>
    )
    }

    • useNavigate支持的参数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    declare function useNavigate(): NavigateFunction;

    interface NavigateFunction {
    (
    to: To,
    options?: {
    replace?: boolean;
    state?: any;
    relative?: RelativeRoutingType;
    }
    ): void;
    (delta: number): void;
    }

    使用useNavigate进行页面后退,前进

    1
    Pass the delta you want to go in the history stack. For example, navigate(-1) is equivalent to hitting the back button.
    • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    import {useNavigate} from "react-router-dom";

    export default function Header() {
    const navigate = useNavigate();
    const back = () => {
    navigate(-1);
    }
    const forward = () => {
    navigate(1);
    }
    return (
    <div className="col-xs-offset-2 col-xs-8">
    <div className="page-header"><h2>React Router Demo</h2></div>
    <div>
    <button onClick={back}>后退</button>
    <button onClick={forward}>前进</button>
    </div>
    </div>
    )
    }

    useInRouterContext()

    • 作用:如果组件在 <Router> 的上下文中呈现,则 useInRouterContext 钩子返回 true,否则返回 false。
    • useInRouterContext 什么时候不为true,当组件脱离了路由器的管理
    • 有时候一些封装的组件可能需要判断是否处于路由环境

    useNavigationType()

    • 作用:返回当前的导航类型(用户是如何来到当前页面的)。

    • 返回值:POPPUSHREPLACE

    • 备注:POP是指在浏览器中直接打开了这个路由组件(刷新页面)。

    useOutlet()

    • 作用:用来呈现当前组件中渲染的嵌套路由。

    • 示例代码:

    1
    2
    3
    4
    const result = useOutlet()
    console.log(result)
    // 如果嵌套路由没有挂载,则result为null
    // 如果嵌套路由已经挂载,则展示嵌套的路由对象

    useResolvedPath()

    • 作用:给定一个 URL值,解析其中的:path、search、hash值。

    redux

    redux的核心概念

    action

    • 和reducer打交道,通过action来告诉reducer应该怎么去操作中心仓库的值

    store

    • 中心仓库,用于存储数据和接收reducer的初始化数据和改变后的数据

    reducer

    • 和中心仓库直接打交道的值,返回的状态会影响到store

    redux核心API

    • getState():获取状态

    • dispatch({type:xxxx,data:xxxx})操作reducer当中的方法,找到对应的case

    • store更新后视图更新:

      • 由于react中状态变了视图不一定会发生变化,也就是状态变了一定要重新调用render后视图才会变化
      • 所以这里需要监听store变化后改变,需要在index.js主入口文件填下如下内容
      1
      2
      3
      4
      5
      6
      7
      8
      9
      import App from "./App";
      import React from "react";
      import ReactDOM from "react-dom";
      ReactDOM.render(<App/>,document.getElementById('root'));

      //监听store的变化,进而重新渲染
      store.subscribe( () => {
      ReactDOM.render(<App/>,document.getElementById('root'));
      } )

    redux编写案例

    案例略,案例不是精髓,里面的笔记才是精髓~

    redux简版的笔记

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    1.安装redux
    npm install redux

    2.src下建立
    -redux
    -store.js
    -count_reducer.js
    3.编辑store.js文件
    //引入redux中的createStore函数,创建一个store
    import {createStore} from "redux";

    //引入为对应store服务的reducer 用于:初始化状态、加工状态
    import countReducer from "./countReducer";
    //暴露store对象 传入为store服务的reducer
    export default createStore(countReducer);

    4.创建对应store服务的reducer:countReducer.js
    4.1:reducer的本质是一个函数,接收preState,action 返回加工后的状态
    4.2:reducer有二个作用,一个是初始化状态,一个是加工状态
    4.3:reducer被第一次调用是store自动触发的
    传递的previousState是undefined
    传递的action是:{type:'@@REDUX/INIT_a.2.b.4'}
    export default function countReducer(preState = 0 ,action) {
    const {type,data} = action;
    switch (type){
    case 'increment':
    return preState + data;
    case 'reduce':
    return preState - data;
    default:
    return preState;
    }
    }


    5.在index.js(主入口文件检测store中状态的改变,一旦发生改变重新渲染<App/>)
    import React from "react";
    import ReactDOM from "react-dom";
    import App from "./App";
    import store from "./redux/store";
    ReactDOM.render(<App/>,document.getElementById('root'));

    //监听store的变化,重新渲染
    store.subscribe(()=>{
    ReactDOM.render(<App/>,document.getElementById('root'));
    })

    6.在count.js当中操作store

    import React, {Component} from 'react';
    import store from "../../redux/CountStore";
    class Count extends Component {
    /*增加*/
    add = () => {
    //todo 先增加1
    store.dispatch({type:'increment',data:100})
    }
    render() {
    const storeData = store.getState();
    console.log(storeData)
    return (
    <div>
    <h1>状态值{storeData}</h1>
    <select>
    <option value={1}>1</option>
    <option value={2}>2</option>
    <option value={3}>3</option>
    </select>
    <button onClick={this.add}>+</button>
    <button>-</button>
    <button>如果是奇数就增加</button>
    <button>异步加</button>
    </div>
    );
    }
    }

    export default Count;

    redux完整版的笔记

    • 新增:action对象由专门的文件进行管理,比如count_action.js,就是为count所创建的action,当然也可以别的文件夹

      • 这样子我们在调用store.dispatch(action)就不需要手动写action了,而是通过一个变量或者函数来输入action值
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      //比如原来让数字+1,我们在Count.jsx是这样子做的

      store.dispatch({type:'increment',data:1});

      //我们对action对象进行专门文件管理后就可以这样子

      1.创建一个action.js文件
      //当然,你也可以对字符串'increment'单独建立一个文件夹
      //便于统一管理action的type类型
      export function createIncrement(number){
      return {type:'increment',data:number};
      }

      2.调用的时候只需要引入action.js当中对应的函数

      import {createIncrement} from "action.js"

      store.dispatch(createIncrement(1));

    • 新增:action对象当中的type也建立专门的文件进行管理,比如constance.js,就可以放置action的type,避免编码疏忽

    redux-不包括异步的示意图

    redux异步版的笔记

    • 什么是异步的呢?有点像nodejs的一些操作,既有同步操作,也有异步操作,比如nodejs的readFile操作

    • 之前的时候,我们把等待的操作放在了React组件当中,现在我们讲这个等待过程放在action里面去等待,由action定时等待后再去执行reducer(也就是把延迟的动作转交给action)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //之前的做法
    <button onClick={this.addAsync}>异步加</button>

    /*异步加*/
    addAsync = () => {
    setTimeout(() => {
    const {selectDOM} = this.state;
    const {value} = selectDOM.current;
    store.dispatch(createIncrement(value));
    },1000)
    }
    • 什么时候需要异步action: 想要对状态进行操作,但是具体的数据靠异步任务返回(非必须)

    • 具体编码(由于redux不支持改写法,所以需要借助中间件来处理)

      • 1.安装redux-thunk,在store添加如下配置

        1
        2
        3
        4
        5
        import thunk from "redux-thunk";
        import {createStore,applyMiddleware} from "redux";
        import countReducer from "./countReducer";//自己的reducer

        export default createStore(countReducer,applyMiddlerWare(thunk));//使用中间件
      • 创建action函数不再返回一般对象,而是一个函数,在此函数中书写异步任务

      • 异步任务有结果后,分发一个同步的action去真正操作数据

        1
        2
        3
        4
        5
        6
        7
        8
        export const demo = (data,time) => {
        return (dispatch) => {
        setTimeout(() => {
        //有结果了,分发一个同步的action
        dispatch({type:'demo',data:data});
        },time)
        }
        }
      • 异步action不是必须要写的,完全可以自己等待异步任务的结果后再去分发同步action

    redux包括异步的示意图

    react-redux基本概念

    • 为什么需要使用react-redux
      • 在平时中,我们需要react-redux结合redux来使用
      • 单单使用redux操作过程事情太多了,不容易理解,操作和管理(面对大一点项目的时候就非常容易出现这种情况)
    • 先来学习下模型图吧,我感觉学习这个模型图,就是在学习设计思想,多学学总是没有错的
    • react-redux模型要求如下
      • 所有的UI组件都应该包裹一个容器组件,他们是父子关系(也就是src/component都是UI组件,src/container都是容器组件)
      • **容器组件是真正和redux打交道的,**容器组件可以随意的使用redux的api
      • UI组件不能使用任何redux的api(都需要借助于父亲(容器组件))
      • 容器组件会传给UI组件如下东东
        • redux中保存的状态
        • 用于操作状态的方法
      • 注意:容器给UI传递的状态方法都是通过props传递

    react-redux操作代码

    1. store不变,依旧是使用redux

    2. 安装react-redux

    3. 明确二个概念

      1. UI组件:不能使用任何redux的api,只负责页面的呈现,交互等
      2. 容器组件:负责和redux通信,将结果交给UI组件
    4. 如果创建一个容器组件–通过react-redux 的 connect函数

      1. connect函数调用:connect(mapStateToProps,mapDispatchToProps)(UI组件)
        1. mapStateToProps映射状态,返回值是一个对象
        2. mapDispatchToProps映射操作状态的方法,返回值是一个对象
    5. 备注1:容器组件中的store的靠props传进去的,而不是在容器组件中直接引入的

    6. 备注2:apDispatchToProps也可以是一个对象

    简化mapDispatch

    • 有几个需要注意的点在简写上,老师视频没有说,导致饶了很大弯路

      • 就是想简写mapDispatchToProps操作的时候,一定要直接传递对象,而不是通过一个函数返回对象
    • 之前的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import {connect} from "react-redux";
    import Count from "../../Component/Count";
    import {incrementAction,reduceAction,incrementActionAsync} from "../../Redux/Count/CountAction";
    const mapStateToProps = (state) => {
    return {
    count:state,
    }
    }

    const mapDispatchToProps = (dispatch) => {
    return {
    add:(data) => dispatch(incrementAction(data)),
    increment: (data) => dispatch(reduceAction(data)),
    incrementAsync: (data,time) => dispatch(incrementActionAsync(data,time)),
    }
    }
    export default connect(mapStateToProps,mapDispatchToProps)(Count);
    • 简化后的
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    import {connect} from "react-redux";
    import Count from "../../Component/Count";
    import {incrementAction,reduceAction,incrementActionAsync} from "../../Redux/Count/CountAction";
    const mapStateToProps = (state) => {
    return {
    count:state,
    }
    }

    //const mapDispatchToProps = () => ({
    // //incrementAction,
    // //reduceAction,
    // //incrementActionAsync
    //})
    export default connect(mapStateToProps,({
    incrementAction,
    reduceAction,
    incrementActionAsync
    }))(Count);

    • 千万要注意的!千万要注意的!千万要注意的!千万要注意的!千万要注意的!千万要注意的!千万要注意的!千万要注意的!,如果想像上面这种简化形式的写法(不需要dispatch的那种),就一定要直接传递对象,而不是通过一个函数返回对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //错误的,会导致state状态得不到更新,也不会自动调用dispatch,所以不可以这样子写
    const mapDispatchToProps = () => ({
    incrementAction,
    reduceAction,
    incrementActionAsync
    })
    export default connect(mapStateToProps,mapDispatchToProps)(Count);

    //下面这种写法才是正确的,直接在connect当中写对象
    export default connect(mapStateToProps,({
    incrementAction,
    reduceAction,
    incrementActionAsync
    }))(Count);

    • 还有值得一提的是,如果你是异步增加的时候,明明配置了中间件thunk,依旧提示报错Actions must be plain objects. Instead, the actual type was:,那么可能是你action写法的问题
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    import {INCREMENT,REDUCE} from "./CountConstance";

    export const incrementAction = data => ({type:INCREMENT,data});

    export const reduceAction = data => ({type:REDUCE,data});


    //可能看到,在使用简化操作之前,你可能这样子写的
    export const incrementActionAsync = (data,time) => {
    setTimeout((dispatch)=>{
    dispatch(incrementAction(data))
    },time)
    }

    //但是实际需要这样子写,否者会报错
    export const incrementActionAsync = (data,time) => {
    return (dispatch) => {
    setTimeout(()=>{
    dispatch({type:INCREMENT,data});
    },time)
    }
    }

    Provider

    • 为了避免出现下面这种多次传入store的情况
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    import React, {Component} from 'react';
    import Count from "./Container/Count";
    import {CountStore} from "./Redux";
    class App extends Component {
    render() {
    return (
    <div>
    <Count store={CountStore}/>
    <Count store={CountStore}/>
    <Count store={CountStore}/>
    <Count store={CountStore}/>
    <Count store={CountStore}/>
    <Count store={CountStore}/>
    <Count store={CountStore}/>
    <Count store={CountStore}/>
    <Count store={CountStore}/>
    </div>
    );
    }
    }

    export default App;

    • 我们就可以使用react-redux当中的Provider组件,只需要在外面传入一次,App当中所有的后代容器组件都能接收到store
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import React from "react";
    import ReactDOM from "react-dom";
    import App from "./App";
    import {Provider} from "react-redux";
    import {CountStore} from "./Redux";
    ReactDOM.render(
    <Provider store={CountStore}>
    <App />
    </Provider>, document.getElementById("root"));

    整合UI组件和容器组件

    • 说通俗点就是将UI组件和容器组件写在一个文件里面,因为容器组件最终是暴露一个通过connect加工处理后的UI组件,所以写在一起也可以的

    • 伪代码示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    import React, {Component} from 'react';
    import {connect} from "react-redux";

    const mapStateToProps = (state) => {
    return {}
    }

    const mapDispatchToProps = (dispatch) => {
    return {}
    }

    class Count extends Component {
    render() {
    return (
    <div>
    <button onClick={this.addIfOdd}>奇数就加</button>
    <button onClick={this.addAsync}>异步加</button>
    </div>
    );
    }
    }

    export default connect(mapStateToProps,mapDispatchToProps)(Count);

    数据共享(combineReducers)

    • 之前的store我们只在一个Count组件使用,这个时候我们可能不会这么简单,我们需要让这个redux可以被所有的组件所使用,并且可以区分数据是谁的
    1. 目录分级
      • 建立action文件夹:用于放置每一个数据所需的action
      • 建立reducer文件夹,用于放置每一个数据所需的reducer

    就像这个样子

    • 使用combineReducers合并,合并后是一个总的状态对象

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      import {createStore,applyMiddleware,combineReducers} from "redux";
      import thunk from "redux-thunk"
      import CountReducer from "./reducer/count";
      import PeopleReducer from "./reducer/people";

      const allReducer = combineReducers({
      count:CountReducer,
      people:PeopleReducer,
      })

      export default createStore(allReducer,applyMiddleware(thunk));

      • 使用combineReducers和没有使用的对比

    • 我们取值的时候就可以像下面这样子
    1
    2
    3
    4
    5
    6
    7
    8
    9
    import Count from "./Count";
    const mapStateToProps = (state) => {
    return {
    result:state.count,
    listLength:state.people.length,
    }
    }

    export default connect(mapStateToProps,{})(Count);
    • 如果出现Error: Objects are not valid as a React child (found: object with keys {count, people}). If you meant to render a collection of children, use an array instead.,因为react不能直接展示对象,所以你错误原因就是展示了一个对象数据

    image-20221126214350419

    • 下面这个代码用unshift为什么有问题,因为unshift没有返回值

      • 刚开始执行default,返回空数组,所以初始化的时候没有什么问题

        当type有值的时候,就会执行case ADDPEOPLE,然后return preInfo.unshift(data)运行后的结果,也就是一个null,和我们想象的返回添加后的数据是不一样的,或者你自己写一个代码块去处理也可以~

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import {ADDPEOPLE} from "../const";
    const initData = [];//初始化时候的值
    export default function (preInfo = initData,action){
    const{type,data} = action;
    switch (type){
    //case ADDPEOPLE: return preInfo.unshift(data);
    case ADDPEOPLE: return [data,...preInfo];
    default: return preInfo;
    }
    }
    • 再来看看这个为什么使用unshift有问题
      • 原理上:因为redux发现返回的地址值和之前是一样的,就不更新了
      • 其他:因为这个不是纯函数(这里导致传入的preInfo参数被修改了)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    import {ADDPEOPLE} from "../const";
    const initData = [];//初始化时候的值
    export default function (preInfo = initData,action){
    const{type,data} = action;
    switch (type){
    case ADDPEOPLE:
    preInfo.unshift(data);
    return preInfo;
    default: return preInfo;
    }
    }

    纯函数和高阶函数

    • 纯函数

      • 只要是输入同样的实参,就必定会得到相同的数据输出(返回)结果
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      //无论今天,明天还是将来某个时间调用 Math.cos(0) 都没关系,输出始终为1,这就是一个纯函数

      //我们来看一个新的例子,下面函数totalApples就是一个纯函数
      const numberOfApples = 5;
      const applesBought = 5;
      const add = (num1, num2) => num1 + num2;
      const totalApples = add(numberOfApples, applesBought) // 10
      const totalApples = add(numberOfApples, applesBought) // 10
      // ... 过了很久很久
      const totalApples = add(numberOfApples, applesBought) //10
      • 不得改写参数数据(也就是你传入的参数,在函数内部不可以去修改)
      • 不会产生副作用
        • 函数执行的过程中对外部产生了可观察的变化,我们就说函数产生了副作用。
        • 例如修改外部的变量、调用DOM API修改页面,发送Ajax请求、调用window.reload刷新浏览器甚至是console.log打印数据,都是副作用
        • 说通俗点就是:你这个函数不可以访问外部的作用域和不可以让调试工具知道你这个函数干了什么(当然,debugger肯定不算),因为你发送ajax浏览器会捕捉,window.reload会进行刷新,console.log会进行控制台输出
    • 高阶函数

      • 参数是函数或者返回值是函数
      • 常见的高阶函数,forEach/map/filter/reducer/find/bind/promise

    redux开发者工具的使用

    1. 安装

      • 谷歌浏览器插件市场搜索Redux DevTools即可
    2. 使用

      • 项目安装依赖(这个是已被废弃的插件),推荐用下一个
      1
      2
      3
      npm install --save-dev redux-devtools-extension
      或者简写
      npm install -D redux-devtools-extension
      • 项目安装依赖(推荐)
      1
      2
      yarn add @redux-devtools/extension -D 
      npm install @redux-devtools/extension -D
      • 项目使用
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      import { createStore, applyMiddleware } from 'redux';
      import { composeWithDevTools } from '@redux-devtools/extension';

      const store = createStore(
      reducer,
      composeWithDevTools(
      applyMiddleware(...middleware)
      // other store enhancers if any
      )
      );

    打包运行

    • 打包
    1
    2
    3
    yarn build
    或者
    npm run build
    • 打包后在build文件(注意,打包后的文件需要以服务端的形式运行)

    • 所以为了简单以服务端的形式运行,我们可以安装下面的插件
    1
    2
    3
    yarn add serve -g
    或者
    npm add serve -g
    • 安装后运行即可
    1
    serve 需要运行的东东

    React扩展

    setState更新状态的2种写法

    • setState(stateChange, [callback]) 对象式的setState

      • stateChange为状态改变对象(该对象可以体现出状态的更改)
      • callback是可选的回调函数,它在状态更新完毕,界面也更新后(render被调用)后才执行
        • 很有用其实,比如在状态更新后对DOM进行一些操作
    • 二. setState(updater, [callback]) 函数式的setState

      • updater为返回stateChange对象的函数
      • updater可以接收到state和props
      • callback是可选的回调函数,它在状态更新完毕,界面也更新后(render被调用)后才执行
        • 很有用其实,比如在状态更新后对DOM进行一些操作
    • setState是一个同步的方法,但是后续更新状态的动作是异步的

    • 示例

    1
    2
    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
    import React, {Component} from 'react';

    class Demo extends Component {
    state = {
    age:18,
    }
    add = () => {
    //const {age} = this.state;
    //this.setState({
    // age:age+1,
    //},() => {
    // console.log('我更新完成了')
    //})
    this.setState((state,props) => {
    return {
    age:state.age + 1
    }
    })
    }
    render() {
    return (
    <div>
    <h1>当前年龄{this.state.age}</h1>
    <button onClick={this.add}>+1</button>
    </div>
    );
    }
    }

    export default Demo;
    • 总结
      • 对象式的setState是函数式的setState的简写方式(语法糖)
      • 使用原则(可以忽略,哪一个喜欢就用哪一个)
        1. 如果新状态不依赖于原状态 => 使用对象方式
        2. 如果新状态依赖原状态 => 使用函数方式
        3. 如果需要在setState() 执行后获取最新的状态数据,要在第二个callback函数中读取

    lazy之路由的懒加载

    • 一句话,没有使用懒加载之前,你用到了几个路由组件,那么就会将这些路由组件全部加载过来,而现在我们做的就是使用到了的时候再去加载,加载的时候需要写一个加载等待的页面

    • 基本操作

      • 引入lazy,Suspense from “react”
      • 路由使用Suspense包括,并添加fallback属性
    • 使用示例如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    import React, {Component,lazy,Suspense} from 'react';
    import {NavLink,Route} from "react-router-dom";
    //import Home from "./Home";
    //import About from "./About";
    const Home = lazy(() => import('./Home'))
    const About = lazy(() => import('./About'))
    class Demo extends Component {
    render() {
    return (
    <div>
    <NavLink to='/home' activeClassName="demo">首页</NavLink>
    <NavLink to='/about' activeClassName="demo">关于</NavLink>
    <Suspense fallback={<h1>加载中...</h1>}>
    <Route path='/home' component={Home}></Route>
    <Route path='/about' component={About}></Route>
    </Suspense>
    </div>
    );
    }
    }

    export default Demo;

    • 报错Error: A React component suspended while rendering, but no fallback UI was specified. Add a <Suspense fallback=...> component higher in the tree to provide a loading indicator or placeholder to display.没有使用Suspense包裹或者没有书写fallback
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    //错误的
    const Home = lazy(() => import('./Home'))
    const About = lazy(() => import('./About'))
    <div>
    <NavLink to='/home' activeClassName="demo">首页</NavLink>
    <NavLink to='/about' activeClassName="demo">关于</NavLink>
    <Route path='/home' component={Home}></Route>
    <Route path='/about' component={About}></Route>
    </div>


    //正确的
    const Home = lazy(() => import('./Home'))
    const About = lazy(() => import('./About'))
    <div>
    <NavLink to='/home' activeClassName="demo">首页</NavLink>
    <NavLink to='/about' activeClassName="demo">关于</NavLink>
    <Suspense fallback={<h1>加载中...</h1>}>
    <Route path='/home' component={Home}></Route>
    <Route path='/about' component={About}></Route>
    </Suspense>
    </div>

    Hooks

    • Hook是React 16.8.0版本增加的新特性/新语法
    • 可以让我们在函数组件中使用state以及其他React特性

    useState

    • 在之前,我们使用函数组件只能定义一些简单的组件(也就是没有state的组件),但是有了setStateHook,我们就可以使用state了
    • 基本语法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    const [xxx,setXxx] = React.useState(initValue);

    useState()说明
    参数:第一次初始化指定的值在内部做缓存
    返回值:包含二个元素的数组,第一个为内部当前状态值,第二个为更新状态值的函数

    xxxx是初始值,只用来显示数据,直接修改不会触发组件的重新渲染
    通过setXxx的修改值(不改变则不会重新渲染 )才会重新渲染(重新调用render)


    //更新状态
    setXxx()二种写法:
    setXxx(newValue):参数为非函数值,直接指定新的状态值,内部用其覆盖原来的状态值
    setXxx(prevState => newValue);参数为函数,接收原来的状态值,返回新的状态值,内部用其覆盖原来的状态值
    • 示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    /*函数式组件*/
    import React from "react";

    export default function Demo(){
    /*使用setStateHooks*/
    const [result,setResult] = React.useState(0);
    //也可以创建多个
    const [resultB,setResultB] = React.useState(100);
    function add(){
    //第一种写法
    setResult(result + 1);
    //第二种写法
    //setResult(prevState => prevState + 1);
    }
    return (
    <div>
    <h1>计算结果是:{result}</h1>
    <button onClick={add}>结果+1</button>
    </div>
    )
    }

    • 报错
      • React Hook "React.useState" cannot be called at the top level. React Hooks must be called in a React function component or a custom React Hook function react-hooks/rules-of-hooks
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    //报错的写法,把这个React.useState写在了顶部

    /*函数式组件*/
    import React from "react";
    /*使用setStateHooks*/
    const [result,setResult] = React.useState(0);

    function add(){
    //第一种写法
    setResult(result + 1);
    //第二种写法
    //setResult(prevState => prevState + 1);
    }
    export default function Demo(){

    return (
    <div>
    <h1>计算结果是:{result}</h1>
    <button onClick={add}>结果+1</button>
    </div>
    )
    }
    • 疑问,不是说每一次渲染页面都会重新执行render吗?在函数式组件就是重新执行函数,那为什么状态值还是会更新,按照之前的理解不是应该都是0吗?
      • 因为react会在初次调用的时候保存这个状态值
    • 当state的值是一个对象时候,修改时是使用新的对象去替换原来的值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    const [user,setUser] = useState({
    value:'猪八戒',
    age:18,
    })

    setUser({
    value:'孙悟空'
    });//user被替换为了{value:孙悟空}


    //所以不想其他值被替换,就需要扩展运算符
    setUser({
    ...user,
    value:'孙悟空',
    })
    • 为什么使用新对象去替换呢?因为旧值和新值指向是同一个地址,react会默认为同一个,就不去更新了,所以set方法就需要整体替换才可以更新视图
    1
    2
    3
    4
    5
    6
    7
    const [user,setUser] = useState({
    value:'猪八戒',
    age:18,
    })

    user.value = '孙悟空';
    setUser(user);//不会触发视图更新
    • setXxx改变的是下一次渲染时state的值
    • setState()会触发组件的重新渲染,它是异步的(不是立刻更新的),相当于有一个队列,剩下代码执行完成后才去执行队列当中的更新操作
    1
    2
    3
    4
    5
    6
    7
    const [user,setUser] = useState(0);
    //调用一次函数则只执行一次视图更新
    const add = () => {
    setUser(0);
    setUser(1);
    setUser(2);
    }
    • 使用当setState需要使用旧值state,一定要注意有可能出现计算错误的情况,为了避免这种情况,可以通过为setState设置回调函数的形式来避免
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    const addHandler = () => {
    setTimeout(() => {
    setState(count + 1);//多次点击会出现问题
    },1000)
    }

    //改为回调形式的
    const addHandler = () => {
    setTimeout(() => {
    setState((prevNumber) => {
    return prevNumber + 1;
    });
    },1000)
    }
    const addHandler = () => {
    setState((prev) => prev + 1)
    }

    useEffect

    • Effect Hooks:可以让我们在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
    • React中的副作用操作
      • 发ajax请求获取数据
      • 设置订阅 / 启动定时器
      • 手动更改真实DOM
    • 语法和说明
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import React from "react";
    React.useEffect(() => {
    //do something
    // ...

    //返回的函数将在组件卸载前执行
    return () => {
    //在这里做一些收尾工作,会在下一次effect调用前执行
    //比如清除定时器,取消订阅
    }

    },[stateValue]) //如果指定的是 [], 那么回调函数只会在第一次render后执行
    //否者就会里面的值发生变化,就执行一次回调(因为render被重新执行了)
    • 我理解为:监听传入参数属性的变化,在组件加载后就立马执行一次回调,如果传入的参数为空数组,那么就不监听值的变化,只会在初次调用,如果不传入,那么会监听所有值的变化,发生变化就执行回调

    • 错误

      • Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.,你useEffect返回的是一个非函数了
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      import React from "react";
      React.useEffect(() => {
      //do something
      // ...

      //返回的函数将在组件卸载前执行
      return '我返回非函数';//报错,报上面的错误

      },[stateValue]) //如果指定的是 [], 那么回调函数只会在第一次render后执行
      //否者就会里面的值发生变化,就执行一次回调(因为render被重新执行了)

    useRef

    • 可以在函数组件中存储/查找组件内的标签或任意其他数据
    • 语法
    1
    2
    3
    4
    import React from "react";
    const myRef = React.useRef();

    <input ref={myRef} />
    • 作用:保存标签

    • 对象,功能与React.createRef()一样

    • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import React from "react";
    export default function Demo(){
    const myRef = React.useRef();
    function getValue(){
    const {value} = myRef.current;
    console.log(value)
    }
    return (
    <div>
    <input ref={myRef}/>
    <button onClick={getValue}>获取输入的值</button>
    </div>
    )
    }
    • 其实,直接创建一个对象,对象当中有current也可以和useRef一样的效果
    1
    2
    3
    const h1Ref = {current:null}

    <div ref={h1Ref}></div>
    • useRef()则可以确保每一次渲染获取的是同一个对象,这就是和自己直接创建的区别
    • 当需要一个对象不会因为组件的重新渲染而改变的时候,就可以使用useRef

    Fragment

    • 使用
    1
    2
    3
    4
    import {Fragment} from "react"

    <Fragment></Fragment>

    • 作用
      • 可以不用必须有一个真实的DOM根标签了
    • 要参与遍历就需要使用Fragment,这样子就可以设置key值,不参与直接空标签
    • 或者使用空标签,空标签不能设置key值,
    1
    2
    3
    <>
    <input type='text'/>
    </>
    • 不管是fragment还是空标签,都不可以设置css属性

    Context和useContext

    • 一种组件通信方式,常用于[祖组件][后代组件]间的通信

    • 一般用于插件

    • 遵循就近原则

      • 当我们通过context访问数据时候,他会读取离他最近的provider中的数据,比如下面,,B组件读取的是沙和尚类似于作用域,如果没有provider,则读取context中创建的时候提供的默认值

    • 使用
    1
    2
    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
    0.引入React
    import React from "react";

    1. 创建context容器对象
    const XxxContext = React.createContext(默认值);

    2.渲染子组件,外面包裹XxxContext.Provider,通过value属性给后代组件传递数据
    <XxxContext.Provider value={数据}>
    子组件
    </XxxContext.Provider>

    3.后代组件读取数据
    //第一种:仅适用于类式组件
    class Demo extends Component {
    static contextType = XxxContext;
    this.context;//读取context中的value数据
    render(){
    //...
    }
    }

    //第二种:函数组件与类式组件都可以
    function Demo(){
    <XxxContext.Consumer>
    value => (
    //value就是context中的value数据
    //..要渲染的内容
    )
    </XxxContext.Consumer>
    }


    示例-类式组件接收

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    import React, {Component} from 'react';
    import "./index.css";
    // 1.创建context容器对象
    const MyContext = React.createContext('未指定用户名称');

    // 2.渲染子组件时,外面包裹context容器对象,并通过value属性给后代组件传递数据
    //value作为key是固定的,不可以更改


    class A extends Component {
    state = {
    userName:'梦洁小站',
    }
    render() {
    return (
    <div className="A">
    <h1>我是A组件</h1>
    <div>我的用户名是:{this.state.userName}</div>
    <MyContext.Provider value={this.state.userName}>
    <B/>
    </MyContext.Provider>
    </div>
    );
    }
    }

    /*直接父子一般不用这种方式*/
    class B extends Component {
    static contextType = MyContext
    render() {
    return (
    <div className="B">
    <h1>我是B组件</h1>
    <div>从A接收的用户名是:{this.context}</div>
    <C/>
    </div>
    );
    }
    }


    //3.接收使用
    //static contextType = context容器对象
    class C extends Component {
    static contextType = MyContext
    render() {
    //4.输出查看接收的context
    console.log(this.context)
    return (
    <div className="C">
    <h1>我是C组件</h1>
    <div>从A接收的用户名是:{this.context}</div>
    </div>
    );
    }
    }

    export default A;

    示例-函数式组件接收

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    import React, {Component} from 'react';
    import "./index.css";
    // 1.创建context容器对象
    const MyContext = React.createContext('未指定用户名称');

    // 2.渲染子组件时,外面包裹context容器对象,并通过value属性给后代组件传递数据
    //value作为key是固定的,不可以更改


    class A extends Component {
    state = {
    userName:'梦洁小站',
    }
    render() {
    return (
    <div className="A">
    <h1>我是A组件</h1>
    <div>我的用户名是:{this.state.userName}</div>
    <MyContext.Provider value={this.state.userName}>
    <B/>
    </MyContext.Provider>
    </div>
    );
    }
    }

    /*直接父子一般不用这种方式*/
    class B extends Component {
    static contextType = MyContext
    render() {
    return (
    <div className="B">
    <h1>我是B组件</h1>
    <div>从A接收的用户名是:{this.context}</div>
    <C/>
    </div>
    );
    }
    }

    //3.函数式组件接收使用
    function C(){
    return (
    <div className="C">
    <h1>我是C组件</h1>
    <MyContext.Consumer>
    {
    value => (<div>从A接收的用户名是:{value}</div>)
    }

    </MyContext.Consumer>

    </div>
    );
    }

    export default A;

    有多个数据需要传递也可以,传入一个对象就可以

    1
    2
    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
    35
    36
    const MyContext = React.createContext({});

    class A extends Component {
    state = {
    userName:'梦洁小站',
    age:'18',
    }
    render() {
    return (
    <div className="A">
    <h1>我是A组件</h1>
    <div>我的用户名是:{this.state.userName}</div>
    <MyContext.Provider value={{...this.state}}>
    <B/>
    </MyContext.Provider>
    </div>
    );
    }
    }


    //3.接收使用
    //static contextType = context容器对象
    class C extends Component {
    static contextType = MyContext
    render() {
    //4.输出查看接收的context
    console.log(this.context)
    return (
    <div className="C">
    <h1>我是C组件</h1>
    <div>从A接收的用户名是:{this.context.userName} - {this.context.age}</div>
    </div>
    );
    }
    }

    useContext(函数式组件)

    1
    2
    3
    4
    5
    除了上面集中使用context,还可以通过context来
    //使用钩子函数(推荐)
    import {useContext} from "react"
    const ctx = useContext(创建Context的容器)
    ctx即为context容器内容

    组件优化

    缺点

    • 目前我们使用继承Component来完成类式组件的书写,但是存在下面二个问题
      • 只要执行setState(),即使不改变状态数据,组件也会重新render()
      • 只要当前组件render()被执行了,就会自动重新调用子组件的render()
    1
    2
    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
    import React, {Component} from 'react';

    class Demo extends Component {
    state = {
    name:'动感超人',
    }
    check = () => {
    this.setState({})
    }
    render() {
    console.log("父组件-render");
    return (
    <div>
    <div>{this.state.name}</div>
    <button onClick={this.check}>点击我</button>
    <Child/>
    </div>
    );
    }
    }
    class Child extends Component {
    render() {
    console.log('Child-render')
    return (
    <div>
    我是子组件
    </div>
    );
    }
    }
    export default Demo;

    可以看到,不管有没有数据,只要调用了setState,数据就会被更新

    效率高的做法

    • 只有当组件的state或props数据发生改变的时候才重新执行render()

    原因

    • Component中的shouldComponentUpdate()总是返回true

    解决

    • 方法1

      • 重写shouldComponentUpdate()方法,
      • 通过比较新旧state或props数据,如果有变化就返回true,没有就返回false
    • 方法2

      • 使用PureComponent

      • PureComponent重写了shouldComponentUpdate(),只有state或props数据发生变化了才返回true

      • 注意:

        • 只是进行stateprops数据的浅比较,如果知识数据对象内部数据变了,shouldComponentUpdate依旧会返回false,如下代码就是修改对象内部数据,就不会更新,也就是shouldComponentUpdate返回false了

          1
          2
          3
          4
          5
          const changeCart = () => {
          const obj = this.state;
          obj.carName = '动感超人',
          this.setState(obj);
          }
        • 所以需要产生新数据,不要直接修改state数据

      • 项目一般使用PureComponent来优化

    • PureComponent的例子

    1
    2
    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
    import React, {PureComponent} from 'react';

    class Demo extends PureComponent {
    state = {
    name:'动感超人',
    }
    check = () => {
    this.setState({})
    }
    render() {
    console.log("父组件-render");
    return (
    <div>
    <div>{this.state.name}</div>
    <button onClick={this.check}>点击我</button>
    <Child/>
    </div>
    );
    }
    }
    class Child extends PureComponent {
    render() {
    console.log('Child-render')
    return (
    <div>
    我是子组件
    </div>
    );
    }
    }
    export default Demo;

    render props(标签体)

    • 平时我们写的<div>中间内容</div>,这个”中间内容”我们叫他标签体内容
    • 标签体内容如果在react当中使用,比如<A>传入的值</A>,那么标签体内容再A组件当中就会出现在this.props.children当中

    那么如果我们需要像vue插槽一样可以实现动态传入带内容的结构,在react当中怎么实现呢

    • 使用children的props属性,通过组件标签体传入结果
    • 使用render props:通过组件标签属性传入结构,一般用render函数属性

    children props(通过组件标签体传入)

    1
    2
    3
    4
    <A>
    <B>XXXX</B>
    </A>
    A可以正常渲染B组件,但是如果B需要A组件的数据无法做到
    • 示例
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    import React, {PureComponent} from 'react';

    class Demo extends PureComponent {
    render() {
    return (
    <div>
    <A>
    <B></B>
    </A>
    </div>
    );
    }
    }

    class A extends PureComponent {
    render() {
    console.log(this.props)
    return (
    <div>
    我是A
    {/* 如果需要渲染B组件 就需要像下面一行代码一样*/}
    {this.props.children}
    </div>
    );
    }
    }


    class B extends PureComponent {

    render() {
    return (
    <div>
    我是B
    </div>
    );
    }
    }

    export default Demo;

    render props(通过组件标签属性传入)

    • 这样子我们就可以在A组件当中随意控制一个东西放置在里面了
    1
    2
    3
    4
    5
    <A render={ (data) => <C data={data}/> }></A>

    A组件: 渲染:this.props.render(要传入的state当中的data数据)

    C组件: 读取A组件传入的数据 this.props.data
    • 示例
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    import React, {PureComponent} from 'react';

    class Demo extends PureComponent {
    render() {
    return (
    <div>
    <A render={(name) => <B name={name}/>}></A>
    </div>
    );
    }
    }

    class A extends PureComponent {
    state = {
    name:'梦洁小站',
    }
    render() {
    console.log(this.props)
    return (
    <div>
    我是A
    {/* 如果需要渲染B组件 就需要像下面一行代码一样*/}
    {/*接收的是一个函数,所以需要去执行,这个函数呢又有参数*/}
    {this.props.render(this.state.name)}
    </div>
    );
    }
    }


    class B extends PureComponent {

    render() {
    return (
    <div>
    我是B
    <h1>渲染来自A组件的name:{this.props.name}</h1>
    </div>
    );
    }
    }

    export default Demo;

    错误边界

    • 错误边界(error boundary),用来捕获后代组件错误,渲染出备用页面
    • 特点
      • 只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件,定时器中产生的错误
    • 使用方式
      • getDerivedStateFromError配合componentDidCatch
      • 在生产环境下才可以看到效果,开发模式只会一闪而过
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //生命周期函数,一旦后台组件报错,就会触发
    static getDerivedStateFromError(error){
    console.log(error);
    //在render之前会触发,返回新的state
    return {
    hasError:true,
    }
    }

    //统计页面的错误,可以发送请求到后台去
    componentDidCatch(error,info){
    //...
    console.log(error,info)
    }
    • 示例
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    import React, {Component} from 'react';

    class Index extends Component {
    state = {
    hasError:false,
    }
    static getDerivedStateFromError(error){
    return {
    hasError: true,
    }
    }
    render() {
    return (
    <div>
    我是A
    {this.state.hasError ? '网络出现错误' : <Child/>}
    </div>
    );
    }
    }
    class Child extends Component {
    state = {
    //list:[
    // {name:'李白1',age:18},
    // {name:'李白2',age:19},
    // {name:'李白3',age:20},
    //]
    list:""
    }
    render() {
    return (
    <div>
    <h1>我是B</h1>
    {
    this.state.list.map(item=><div key={item.name}>{item.name}</div>)
    }
    </div>
    );
    }
    }
    export default Index;

    随记

    • 简称要全大写

    • reduce条件统计

    • 开启了严格模式,禁止函数的this指向window

      • 如果继承后想要有自己的属性,那么就需要在构造器当中去调用super,super必须要在this之前被调用

      • 如果继承后没有自己的属性,可以省略super,甚至构造器都可以省略不写

      • 如果继承后和父类有相同的名字函数,不是被覆盖了,而是在这一个函数之前,有同名的函数了,所以当依据原型链去寻找的时候,找到了一个,就不会接着找下去了,(就近原则),达到了重写的目的

      • 类中书写的属性

        • 在state

        • 静态属性

          • 只可以类名.属性名调用
          • static关键字修饰
        • 实例属性

          1
          2
          3
          4
          5
          6
          7
          8
          class Person {
          static num = 20;
          address = '深圳'
          constructor(n1,a1){
          this.name = n1;//实例属性
          this.age = a1;//实例属性
          }
          }
    • 如果有null,可能是暗示你书写一个对象

    • axios当中的then返回的参数是axios封装的一层对象,想要获取真正结果,需要多一层data才可以,也就是res.data才是获取真实结果

      • 同理,axios的catch也是,需要通过error.message才是获取错误信息
    • src目录为程序写的东东

    • 对象不能直接展示在react当中

    • kebab-case 烤肉串形式命名,全部都是小写的,通过’-‘穿在一起

    • Reactredux createStore 在 typescript 中被弃用

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/447d3137.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    avatar
    梦洁
    小小的字,有大大的梦想~分享我的前端学习过程,经历,错误,和一些其他折腾过程
    关注下我(* ̄▽ ̄*)
    公告
    不断更新中,有问题请留言回复(会通过邮箱提醒~)
    目录
    1. 1. React的特点
    2. 2. babel用处
    3. 3. 1.你好,react
    4. 4. 二种创建虚拟DOM的方式
      1. 4.1. 方式1-使用jsx的形式
      2. 4.2. 方式2-使用javascript形式
    5. 5. 虚拟DOM和真实DOM
    6. 6. jsx的语法规则
    7. 7. 表达式和语句(代码)的区别
      1. 7.1. 表达式
      2. 7.2. 语句(代码)
    8. 8. JSX小练习
    9. 9. React函数式组件
    10. 10. React类式组件
    11. 11. 组件的三大属性之state
      1. 11.1. 在学之前,先来复习下this的指向
      2. 11.2. state
      3. 11.3. state的简写形式-解决this指向问题
    12. 12. 组件的三大属性之props
      1. 12.1. 对props进行约束-类式组件
      2. 12.2. 对props进行约束-函数式组件
    13. 13. ref
      1. 13.1. 字符串形式的ref
      2. 13.2. 回调形式的ref
      3. 13.3. createRef的使用
    14. 14. React当中为什么要建立一个onClick,onXxx的事件
    15. 15. React之受控组件和非受控组件
      1. 15.1. 非受控组件
      2. 15.2. 受控组件
      3. 15.3. 区别
    16. 16. 高阶函数和函数的柯里化
    17. 17. 关于类式组件当中的构造器
    18. 18. React生命周期
      1. 18.1. 生命周期16.x
        1. 18.1.1. 示例1-透明度改变
        2. 18.1.2. 示例2-更新与强制更新
        3. 18.1.3. 示例3-props的接收监听
      2. 18.2. 生命周期17.x
        1. 18.2.1. 示例1-使用不安全的生命周期钩子
        2. 18.2.2. 示例2-使用新钩子,体会聊天窗口有新消息出现时候的做法
    19. 19. React的Differ算法
      1. 19.1. React/vue中的key有什么作用(key的内部原理是什么?)
      2. 19.2. 为什么遍历列表的时候,key最好不要用index
    20. 20. React脚手架
    21. 21. React小案例之todoList
    22. 22. React脚手架配置代理
      1. 22.1. 方式1-在package.json当中配置
      2. 22.2. 方式1-src创建配置文件xxxx当中配置(推荐)
    23. 23. React小案例之github搜索
      1. 23.1. fetch和axios
    24. 24. React之路由前置
      1. 24.1. 预备,知道history
    25. 25. React路由@5版本
      1. 25.1. 基本使用
      2. 25.2. 基本使用示例代码
      3. 25.3. 二次封装NavLink
      4. 25.4. Switch的在路由的使用-遇见就返回
      5. 25.5. 解决路径丢失的问题
      6. 25.6. BrowserRouter和HashRouter的区别
      7. 25.7. 路由的匹配之模糊路由和精准(严格)匹配
      8. 25.8. Redirect标签 - 重定向
      9. 25.9. 嵌套路由
      10. 25.10. 路由传参之params
      11. 25.11. 路由传参之search
      12. 25.12. 路由传参之state
      13. 25.13. 路由的push 和 replace
      14. 25.14. 路由编程式导航
      15. 25.15. 路由的withRouter
    26. 26. React路由@6版本
      1. 26.1. 概述
      2. 26.2. 一级路由
      3. 26.3. Navigate组件-路由重定向
      4. 26.4. NavLink组件
      5. 26.5. useRoutes(类似于vue的路由表)
        1. 26.5.1. 不过routes都是统一管理
      6. 26.6. 嵌套路由和Outlet
      7. 26.7. 路由params传参
      8. 26.8. 路由search传参
      9. 26.9. 路由state传参
      10. 26.10. 编程式路由导航
        1. 26.10.1. 使用useNavigate进行页面后退,前进
      11. 26.11. useInRouterContext()
      12. 26.12. useNavigationType()
      13. 26.13. useOutlet()
      14. 26.14. useResolvedPath()
    27. 27. redux
      1. 27.1. redux的核心概念
        1. 27.1.1. action
        2. 27.1.2. store
        3. 27.1.3. reducer
      2. 27.2. redux核心API
      3. 27.3. redux编写案例
        1. 27.3.1. redux简版的笔记
        2. 27.3.2. redux完整版的笔记
        3. 27.3.3. redux异步版的笔记
      4. 27.4. react-redux基本概念
      5. 27.5. react-redux操作代码
      6. 27.6. 简化mapDispatch
      7. 27.7. Provider
      8. 27.8. 整合UI组件和容器组件
      9. 27.9. 数据共享(combineReducers)
        1. 27.9.1. 纯函数和高阶函数
      10. 27.10. redux开发者工具的使用
      11. 27.11. 打包运行
    28. 28. React扩展
      1. 28.1. setState更新状态的2种写法
      2. 28.2. lazy之路由的懒加载
      3. 28.3. Hooks
        1. 28.3.1. useState
        2. 28.3.2. useEffect
        3. 28.3.3. useRef
      4. 28.4. Fragment
      5. 28.5. Context和useContext
        1. 28.5.1. 示例-类式组件接收
        2. 28.5.2. 示例-函数式组件接收
        3. 28.5.3. 有多个数据需要传递也可以,传入一个对象就可以
        4. 28.5.4. useContext(函数式组件)
      6. 28.6. 组件优化
        1. 28.6.1. 缺点
        2. 28.6.2. 效率高的做法
        3. 28.6.3. 原因
        4. 28.6.4. 解决
      7. 28.7. render props(标签体)
        1. 28.7.1. 那么如果我们需要像vue插槽一样可以实现动态传入带内容的结构,在react当中怎么实现呢
        2. 28.7.2. children props(通过组件标签体传入)
        3. 28.7.3. render props(通过组件标签属性传入)
      8. 28.8. 错误边界
    29. 29. 随记
    最新文章
    \ No newline at end of file diff --git a/451acffa.html b/451acffa.html new file mode 100644 index 000000000..ccd927210 --- /dev/null +++ b/451acffa.html @@ -0,0 +1 @@ +今日刷题-变量的回收和reduce的使用 | 梦洁小站-属于你我的小天地

    今日刷题-变量的回收和reduce的使用

    题目1

    1
    2
    3
    4
    5
    6
    7
    8
    以下哪些表达式的值为0?(多选)
    A: (()=>{}).length

    B: 1 & 2

    C: +[]

    D: [1,2,-3].reduce((a, b) => a - b, 0)
    • 答案

      • A,B,C,D
    • 解析

      • A: 获取的是形参的个数(不是实参)

        1
        2
        3
        4
        5
        如果是((a)=> {}).length,形参有一个所以函数的length为1
        如果是((a,b)=> {}).length,形参有两个所以函数的length为2
        假如:function aaa(){}
        aaa(1,2);
        console.log(aaa.length) ;//值为0,因为形参个数是0,所以函数的length为0
      • B: & 与运算, (二进制下)二者都是1最终结果才为1,否则就为0

        1
        2
        3
        1的二进制表示  0001
        2的二进制表示 0010
        1&2结果是 0000 ,转成10进制也就是0
      • C: +[ ] 隐式类型转换。 +会让 [ ] 隐式转换成Number,转换过程如下

        1
        因为 [ ] 是对象,所以 toPrimitive->valueOf->toString为  '' ,Number('')得0
      • D: reduce用于求和

        1
        2
        3
        4
        5
        6
        7
        // 代码简写
        [1,2,-3].reduce((a, b) => a - b, 0)

        // 代码详细
        [1,2,-3].reduce((prev, cur)=>{
        return a-b;
        },0)

        reduce的使用

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        【功能】: 求和
        【参数】: callback参数
        1. total(若为赋值,则默认为0)
        2. currentItem
        3. currentIndex
        4. arr(数组所属数组对象)
        【返回值】:根据callback的返回值决定total的值
        【示例】:
        var sum = result.reduce(function(prev, cur) {
        console.log(prev) // 0
        console.log(cur) // {name:'小明',score:88}
        return cur.score + prev;
        }, 0);
        console.log('总数是' + sum)

    题目2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    下列代码存在几个变量没有被回收?( )
    var i = 1;
    var i = 2;
    var add = function() {
    var i = 0;
    return function()
    {
    i++;
    console.log(i);
    }
    }();
    add();
    A: 0
    B: 1
    C: 2
    D: 3
    • 答案

      • D
    • 解析

      • 不会被回收的

        • 全局变量i不会被回收
        • 全局变量add不会被回收
        • 闭包引用的局部变量i不会被回收
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        var i = 1;
        var i = 2; //二个var ,一个被覆盖了,没有新建,只是覆盖,因此只有一个全局变量i不会被回收

        // add 也是全局变量,不回收
        var add = function() {
        // var 是函数作用域
        var i = 0;
        return function()
        {
        //闭包保持引用,因此这个也不会被回收
        i++;
        console.log(i);
        }
        }();

    题目3

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    以下代码执行后,array的结果是?
    var array=[-1,1,3,4,6,10];
    array.sort((a,b)=>Math.abs(a-3)-Math.abs(b-3));
    A: [10,-1,6,1,4,3]

    B: [10,6,4,3,1,-1]

    C: [3,4,1,6,-1,10]

    D: [-1,1,3,4,6,10]

    • 答案

      • C
    • 解析

      • (a,b)=>Math.abs(a-3)-Math.abs(b-3);

        箭头函数表示:当Math.abs(a-3)>Math.abs(b-3)时,a放在b后面,Math.abs(a-3)<Math.abs(b-3)时,不交换位置,也就是说数组中的每一项减去3的绝对值越大越靠后。这里主要考的是对sort()方法的掌握.

      • 我个人也不是很懂,只知道 array.sort((a,b)=>a-b)是升序 array.sort((a,b)=>b-a)是降序

      • 获取这个是一个技巧吧,真正理解sort还是挺复杂的

        • 先将数组每一个值 -3再取绝对值,然后得[4,2,0,1,3,7]
        • [4,2,0,1,3,7] 依次对应数组 [-1,1,3,4,6,10]
        • 又因为是 Math.abs(a-3) - Math.abs(b-3);所以数组中的每一项减去3的绝对值越大越靠后,排序后的为[0,1,2,3,4,7],所以最终结果就为**[3,4,1,6,-1,10]**
      • 具体可看

    题目4

    1
    2
    3
    4
    5
    6
    7
    8
    9
    el是一个id="id1"的div元素,以下哪行代码会执行失败
    A: el.className='aaa'

    B: el.tagName='p'

    C: el.innerHTML=''

    D: el.id='id2'

    • 答案
      • B
    • 解析
      • A: className 属性设置或返回元素的 class 属性。获取属性值:HTMLElementObject.className;设置属性值:HTMLElementObject.className=classname
      • B: tagName属性返回元素的标签名。HTML 返回 tagName 属性的值是大写的比如div标签返回tagName为 DIV
      • C: innerHTML 属性设置或返回表格行的开始和结束标签之间的 HTML。HTMLElementObject.innerHTML=text
      • id 属性设置或者返回元素的 id。HTMLElementObject.id=id
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/451acffa.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/45513c12.html b/45513c12.html new file mode 100644 index 000000000..9f83f40b4 --- /dev/null +++ b/45513c12.html @@ -0,0 +1 @@ +React多个echarts图表在一个页面的使用 | 梦洁小站-属于你我的小天地

    React多个echarts图表在一个页面的使用

    前景

    • 很多情况下图标都是一个,我们大概率会像下面代码一样的做法

      • 大概流程就是获取到数据后执行初始化,因为先初始化后异步请求再设置state里面的数据回导致无法正常显示echarts(除非再次调用setOption)
      • 下面就记录下自己解决过程
      • 源码

    根据ID获取DOM进行初始化

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    import React, { Component } from 'react';
    import * as echarts from 'echarts';
    import axios from 'axios';

    class TestEcharts extends Component {
    constructor(props) {
    super(props);
    this.state = {
    testData: '测试数据',
    list1: [],
    };
    }
    componentDidMount() {
    this.initEchartsData();
    }
    // 请求并初始化echarts数据
    initEchartsData() {
    axios.get('https://api.oick.cn/api/lishi').then((res) => {
    console.log(res);
    //处理数据
    this.setState(
    {
    list1: res.data.result.map((item) => Math.round(Math.random() * 100)),
    },
    );
    // 初始化echarts
    const myChart = echarts.init(document.getElementById('myChart'));
    myChart.setOption({
    xAxis: {
    type: 'category',
    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
    },
    yAxis: {
    type: 'value',
    },
    series: [
    {
    data: this.state.list1,
    type: 'line',
    },
    ],
    });
    });
    }


    render() {
    return (
    <div>
    <div id="myChart" style={{ width: '400px', height: '400px' }}></div>
    <div>{this.state.testData}</div>
    </div>
    );
    }
    }

    export default TestEcharts;

    根据ref获取DOM进行初始化

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    import React, { Component,createRef  } from 'react';
    import * as echarts from 'echarts';
    import axios from 'axios';

    class TestEcharts extends Component {
    constructor(props) {
    super(props);
    this.state = {
    testData: '测试数据',
    list1: [],
    };
    this.echart1 = createRef();
    }
    componentDidMount() {
    this.initEchartsData();
    }
    // 请求并初始化echarts数据
    initEchartsData() {
    axios.get('https://api.oick.cn/api/lishi').then((res) => {
    console.log(res);
    //处理数据
    this.setState(
    {
    list1: res.data.result.map((item) => Math.round(Math.random() * 100)),
    },
    );
    // 初始化echarts
    // const myChart = echarts.init(document.getElementById('myChart'));
    console.log(this.echart1.current);
    const myChart = echarts.init(this.echart1.current);
    myChart.setOption({
    xAxis: {
    type: 'category',
    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
    },
    yAxis: {
    type: 'value',
    },
    series: [
    {
    data: this.state.list1,
    type: 'line',
    },
    ],
    });
    });
    }


    render() {
    return (
    <div>
    {/* id="myChart" */}
    <div ref={this.echart1} style={{ width: '400px', height: '400px' }}></div>
    <div>{this.state.testData}</div>
    </div>
    );
    }
    }

    export default TestEcharts;

    如果有1个页面有多个echart图标怎么解决呢?

    方式1-一个图表就是一个组件

    数据获取到后再setOption

    • 主index.jsx
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import React, { Component } from 'react'
    import ShowData from "./component/showData/index";
    export default class index extends Component {
    render() {
    return (
    <div>
    <div>显示多个图表</div>
    <div style={{ display:'flex' }}>
    <ShowData/>
    <ShowData/>
    <ShowData/>
    <ShowData/>
    </div>
    </div>
    )
    }
    }

    • 图表ShowData.jsx
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    import React, { Component, createRef } from 'react';
    import * as echarts from 'echarts';
    import axios from 'axios';
    export default class index extends Component {
    constructor(props) {
    super(props);
    this.state = {
    data: [],
    };
    this.echartDOM = createRef();
    }
    // 请求数据
    initData() {
    axios.get('https://api.oick.cn/api/lishi').then((res) => {
    console.log(res);
    //处理数据
    this.setState({
    data: res.data.result.map((item) => Math.round(Math.random() * 100)),
    },() => {
    this.initEchartsData();
    });
    });
    }
    //初始化echart图表
    initEchartsData(){
    const myChart = echarts.init(this.echartDOM.current);
    myChart.setOption({
    xAxis: {
    type: 'category',
    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
    },
    yAxis: {
    type: 'value',
    },
    series: [
    {
    data: this.state.data,
    type: 'line',
    },
    ],
    });
    }
    componentDidMount() {
    this.initData(); //初始化数据
    }
    render() {
    return (
    <div
    ref={this.echartDOM}
    style={{ width: '400px', height: '400px' }}
    ></div>
    );
    }
    }

    先setOption后再更新

    • 通过createRef获取DOM再保存echarts实例方式
    • 主index.jsx
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import React, { Component } from 'react'
    import ShowData from "./component/showData/index";
    import ShowData2 from "./component/showData/index2";
    export default class index extends Component {
    render() {
    return (
    <div>
    <div>显示多个图表</div>
    <div style={{ display:'flex' }}>
    <ShowData2/>
    <ShowData2/>
    <ShowData2/>
    <ShowData2/>
    </div>
    </div>
    )
    }
    }

    • 图表ShowData2.jsx
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    import React, { Component, createRef } from 'react';
    import * as echarts from 'echarts';
    import axios from 'axios';
    export default class index extends Component {
    constructor(props) {
    super(props);
    this.state = {
    data: [],
    myEchart: '',
    };
    this.echartDOM = createRef();
    }
    // 请求数据
    initData() {
    axios.get('https://api.oick.cn/api/lishi').then((res) => {
    console.log(res);
    //处理数据
    this.setState({
    data: res.data.result.map((item) => Math.round(Math.random() * 100)),
    },() => {
    this.state.myEchart.setOption({
    xAxis: {
    type: 'category',
    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
    },
    yAxis: {
    type: 'value',
    },
    series: [
    {
    data: this.state.data,
    type: 'line',
    },
    ],
    });
    });
    });
    }
    //初始化echart图表
    initEchartsData(){
    const myEchart = echarts.init(this.echartDOM.current);
    this.setState({
    myEchart,
    })
    myEchart.setOption({
    xAxis: {
    type: 'category',
    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
    },
    yAxis: {
    type: 'value',
    },
    series: [
    {
    data: this.state.data,
    type: 'line',
    },
    ],
    });
    }
    componentDidMount() {
    this.initData(); //初始化数据
    this.initEchartsData();//初始化数据
    }
    render() {
    return (
    <div
    ref={this.echartDOM}
    style={{ width: '400px', height: '400px' }}
    ></div>
    );
    }
    }

    方式2-都写在一个jsx\tsx文件中

    • 比较难的地方在于怎么获取到这么多个的DOM再进行统一的初始化,并且还可能后续不同操作需要更新不同的DOM
    • 这里只是数组简单的存储,你也可以通过Map来进行存储

    • 点击更新

    • 主index.jsx
    1
    2
    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
    import React, { Component } from 'react'
    import ShowData from "./component/showData/index";
    import ShowData2 from "./component/showData/index2";
    import ShowData3 from "./component/showData/index3";
    export default class index extends Component {
    render() {
    return (
    <div>
    <div>显示多个图表</div>
    {/* 方式1 */}
    <h1>方式1</h1>
    <div style={{ display:'flex' }}>
    {/* <ShowData2/>
    <ShowData2/>
    <ShowData2/>
    <ShowData2/> */}
    </div>
    {/* 方式2 */}
    <h1>方式2</h1>
    <div >
    <ShowData3/>
    </div>
    </div>
    )
    }
    }

    • 图表ShowData.jsx
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    import React, { Component, createRef } from 'react';
    import * as echarts from 'echarts';
    import axios from 'axios';
    export default class index3 extends Component {
    constructor(props) {
    super(props);
    this.state = {
    myEchartList: [], //实例化echart列表
    data1: [],
    data2: [],
    data3: [],
    data4: [],
    };
    this.allRef = []; //存储所有的ref
    }
    componentDidMount() {
    this.setState({
    //实例化echart
    myEchartList: this.allRef.map((item) => echarts.init(item)),
    });
    //获取数据A
    this.initDataA();
    //获取数据B
    this.initDataB();
    }
    initDataA = () => {
    axios.get('https://api.oick.cn/api/lishi').then((res) => {
    console.log(res,'来自initDataA');
    let data1 = res.data.result.map(() => Math.round(Math.random() * 100));
    let data2 = res.data.result.map(() => Math.round(Math.random() * 100));
    //处理数据
    this.setState({
    data1,
    data2,
    });
    this.state.myEchartList[0].setOption({
    xAxis: {
    type: 'category',
    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
    },
    yAxis: {
    type: 'value',
    },
    series: [
    {
    data:data1,
    type: 'line',
    },
    ],
    });
    this.state.myEchartList[1].setOption({
    xAxis: {
    type: 'category',
    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
    },
    yAxis: {
    type: 'value',
    },
    series: [
    {
    data:data2,
    type: 'line',
    },
    ],
    });
    });
    };
    initDataB = () => {
    axios.get('https://api.oick.cn/api/lishi').then((res) => {
    console.log(res,'来自initDataB');
    let data3 = res.data.result.map(() => Math.round(Math.random() * 100));
    let data4 = res.data.result.map(() => Math.round(Math.random() * 100));
    //处理数据
    this.setState({
    data3,
    data4,
    });
    this.state.myEchartList[2].setOption({
    xAxis: {
    type: 'category',
    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
    },
    yAxis: {
    type: 'value',
    },
    series: [
    {
    data:data3,
    type: 'line',
    },
    ],
    });
    this.state.myEchartList[3].setOption({
    xAxis: {
    type: 'category',
    data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
    },
    yAxis: {
    type: 'value',
    },
    series: [
    {
    data:data4,
    type: 'line',
    },
    ],
    });
    });
    };
    handleUpdate = () => {
    this.initDataA();
    };
    render() {
    return (
    <>
    <button onClick={this.handleUpdate}>点击我更新2个图表数据</button>
    <div
    style={{ display: 'flex' }}
    >
    <div
    ref={(node) => {
    this.allRef.push(node);
    }}
    style={{ width: '401px', height: '400px' }}
    ></div>
    <div
    ref={(node) => {
    this.allRef.push(node);
    }}
    style={{ width: '402px', height: '400px' }}
    ></div>
    <div
    ref={(node) => {
    this.allRef.push(node);
    }}
    style={{ width: '403px', height: '400px' }}
    ></div>
    <div
    ref={(node) => {
    this.allRef.push(node);
    }}
    style={{ width: '404px', height: '400px' }}
    ></div>
    </div>
    </>
    );
    }
    }

    总结

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/45513c12.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/4561a0ef.html b/4561a0ef.html new file mode 100644 index 000000000..e7c5af0e6 --- /dev/null +++ b/4561a0ef.html @@ -0,0 +1 @@ +Echarts图表的基本使用 | 梦洁小站-属于你我的小天地

    Echarts图表的基本使用

    Echarts的初始化使用

    • 引入什么的就不多说了,普通的js脚本怎么引入echarts就怎么引入

    • 大概初始化的步骤

        1. 获取DOM(用于绘制表格)
        2. 依据获取的DOM对象初始化echarts(使得这个DOM对象用于展示图表数据)
        3. 书写echarts配置对象
        4. 第二步返回的echarts对象调用setOption方法并传入第三步书写的配置对象
    • 别忘记设置Dom容器的宽高了(css设置即可)

    • 好像如果是同一个echarts实例化对象,再次调用setOption方法,不会造成图表全部重新绘制,只会造成部分修改

      1
      2
      3
      4
      5
      6
      7
      //重新设置图表的标题,在这之前已经调用过setOption了
      //所以只会重新绘制图表标题
      this.table1.setOption({
      title:{
      text:this.showTitle+"趋势"
      },
      })

    示例代码

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
    * {
    margin: 0;
    padding: 0;
    }

    #table1{
    width: 500px;
    height: 500px;
    }
    </style>
    </head>

    <body>
    <div id="table1"></div>
    <div id="table2"></div>
    <script src="./js/echarts.js"></script>
    <script>
    //1.获取DOM容器
    var dom1 = document.querySelector("#table1")
    //2.根据DOM容器初始化echarts实例
    var table1 = echarts.init(dom1);
    //3.设置echarts展示的数据和设置图表
    var options1 = {
    //设置图表标题
    title: {
    //主标题文本
    text: "数据可视化",
    //副标题文本
    subtext: "echarts的基本使用",
    //设置主标题样式
    textStyle: {
    color: 'hotpink',
    fontStyle: 'italic'
    },
    //设置副标题样式
    subtextStyle:{
    color:'yellow'
    },
    //设置居中
    left: 'center'
    },
    //设置x坐标
    xAxis: {
    data: ["衣服", "直播", "游戏", "电影"],
    },
    //设置y坐标
    yAxis: {

    },
    //系列的设置,绘制怎么样类型的图表,数据的展示在这里
    series:[
    {
    type:'bar',
    data:[20,30,26,24],
    //设置颜色
    color:'red'
    }
    ]
    }
    //4.传入写好的配置项
    table1.setOption(options1);
    </script>
    </body>

    </html>

    效果图

    Echarts的setOption配置对象

    • grid设置echarts的对于容器的位置 官网API
      • left,right,top,bottom设置为0可以使得表格占满容器

    Echarts的series的配置对象

    • 设置渐变填充折线图

      • 关键配置参数areaStyle 官方API

      • 官方渐变参考代码

      • 代码

        1
        2
        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
        series:[
        {
        type:"line",
        data:[45,35,25,87,45,89],
        itemStyle:{
        // 图形透明度。支持从 0 到 1 的数字,为 0 时不绘制该图形。
        opacity:0,
        //颜色设置为紫色
        color:'purple'
        },
        //areaStyle区域填充样式。设置后显示成区域面积图。
        //https://echarts.apache.org/zh/option.html#series-line.areaStyle
        areaStyle:{
        //支持渐变 https://echarts.apache.org/zh/option.html#color
        color:{
        //线性渐变
        type: 'line',
        x: 0,
        y: 0,
        r: 0,
        colorStops: [{
        offset: 0, color: '#ce9dce' // 0% 处的颜色
        },

        {
        offset: 1, color: '#dcb9dc' // 100% 处的颜色
        }
        ],
        global: false // 缺省为 false
        }
        }
        }
        ],

    反向柱形图(也就是X轴和Y轴反过来显示)

    • 原来的x轴变为了y轴,原来的y轴变为了x轴

    • 只需要设置yAxis对象当中的type:"category"和x轴的数据放置在yAxis当中,然后把x部分删除

    • 代码

      1
      2
      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
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      <!DOCTYPE html>
      <html lang="en">

      <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>Document</title>
      <style>
      * {
      margin: 0;
      padding: 0;
      }

      #table1,#table2{
      width: 500px;
      height: 500px;
      }
      </style>
      </head>

      <body>
      <div id="table1"></div>
      <script src="https://cdn.bootcdn.net/ajax/libs/echarts/5.3.2/echarts.min.js"></script>
      <script>
      //1.获取DOM容器
      var dom1 = document.querySelector("#table1")
      //2.根据DOM容器初始化echarts实例
      var table1 = echarts.init(dom1);
      //3.设置echarts展示的数据和设置图表
      var options1 = {
      //设置图表标题
      title: {
      //主标题文本
      text: "数据可视化",
      //副标题文本
      subtext: "echarts的基本使用",
      //设置主标题样式
      textStyle: {
      color: 'hotpink',
      fontStyle: 'italic'
      },
      //设置副标题样式
      subtextStyle:{
      color:'yellow'
      },
      //设置居中
      left: 'center'
      },
      //设置x坐标
      xAxis: {
      // data: ["衣服", "直播", "游戏", "电影"],
      },
      //设置y坐标
      yAxis: {
      type:'category',
      data: ["衣服", "直播", "游戏", "电影"],
      },
      //系列的设置,绘制怎么样类型的图表,数据的展示在这里
      series:[
      {
      type:'bar',
      data:[20,30,26,24],
      //设置颜色
      color:'red'
      }
      ],

      }
      //4.传入写好的配置项
      table1.setOption(options1);
      </script>
      </body>

      </html>
    • 效果图

    反向柱形图实现防进度条的静态图

    效果

    代码

    1
    2
    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
    var table1 = echarts.init(this.$refs.dom);
    table1.setOption({
    //设置x轴,现在x轴在水平线上了
    //min最小值 范围
    //max:最大值 范围
    xAxis:{show:false,min:0,max:100},
    //设置y轴,现在y轴在垂直线上了
    yAxis:{show:false,type:"category"},
    //设置位置
    grid:{
    left:0,
    right:0,
    top:0,
    bottom:0
    },
    series:[
    {
    type:'bar',
    barWidth:10,
    //设置颜色
    color:"#92b532",
    data:[78],
    //图形上的文本标签,可用于说明图形的一些数据信息,比如值,名称等。
    //label:{
    // show:true,
    // //内容
    // formatter:"|",
    // //标签的位置。
    // position:'right'
    //}
    }
    ]
    })

    一个容器显示多个图表

    • 使用配置对象当中的series属性

    • 这就是为什么series是一个数组而不是一个对象了~ series图

      • series当中的格式

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        var options = {
        ...
        series:[
        //图表1
        {....},
        //图表2
        {....}
        ]
        ...
        }

    代码

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
    *{
    margin: 0;
    padding: 0;
    }
    #dom1{
    width: 500px;
    height: 500px;
    }
    </style>
    </head>

    <body>
    <div id="dom1"></div>
    <script src="./js/echarts.js"></script>
    <script>
    var table1 = echarts.init(document.querySelector("#dom1"))
    var options = {
    //设置标题
    title: {
    //标题文字
    text: "月销售数据",
    //副标题文字
    subtext: "销售人员必看!",
    left:'center',
    //主标题样式
    textStyle: {
    color: 'red'
    },
    //副标题样式
    subtextStyle: {
    color: 'yellow'
    },
    },
    //x轴
    xAxis:{
    data:['Mon','Tue','Wed','Thu','Fri','Sat','Sun']
    },
    // y轴
    yAxis:{},
    //一系列数据展示
    series:[
    //图表1-显示柱形图
    {
    type:"bar",
    data:[125,200,150,68,52,103,146],
    color:"#5470c6",
    },
    //图表2-显示折线图
    {
    type:'line',
    data:[125,200,150,68,52,103,146],
    color:'red',
    },
    //图表3-饼图
    {
    type:'pie',
    //设置饼图半径(像素值)
    radius:20,
    data:[
    {value:125,name:'Mon'},
    {value:200,name:'Tue'},
    {value:150,name:'Wed'},
    {value:68,name:"Thu"},
    {value:52,name:'Fri'},
    {value:103,name:'Sat'},
    {vaue:146,name:'Sun'}
    ],
    //设置宽度
    width:200,
    height:200,
    //设置位置
    left:200,
    top:50
    }
    ]
    }
    table1.setOption(options)
    </script>
    </body>

    </html>

    效果图

    dataset数据集的使用

    • 我理解的数据集就是series当中的每一个对象所对应的数据,方便我们统一处理数据

    • 主要注意的是:

      • 如果我们设置了dataset数据集,而没有在series当中的对象当中使用encode去指明映射那一列
      • ECharts 就按最常见的理解进行默认映射:X 坐标轴声明为类目轴,默认情况下会自动对应到 dataset.source 中的第一列;
      • 三个柱图系列,一一对应到 dataset.source 中后面每一列。(也就是按照series当中对象的顺序依次对应)
    • dataset数据集基本使用简略代码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      option = {
      dataset: {
      source: [
      // 每一列称为一个『维度』。
      // 这里分别是维度 0、1、2、3、4。
      [12, 44, 55, 66, 2],
      [23, 6, 16, 23, 1],
      ]
      },
      series: {
      type: 'xxx',
      encode: {
      //使用0维度数据
      x: 0
      }
      }
      }
    • 具体可参考Echarts官网数据集概念

    代码

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
    *{
    margin: 0;
    padding: 0;
    }
    #dom1{
    width: 500px;
    height: 500px;
    }
    </style>
    </head>

    <body>
    <div id="dom1"></div>
    <script src="./js/echarts.js"></script>
    <script>
    var table1 = echarts.init(document.querySelector("#dom1"))
    //设置数据集
    var data = [
    //分别对应柱状图,折线图,饼图
    // 如果后面没有设置encode映射
    //ECharts 就按最常见的理解进行默认映射:
    //X 坐标轴声明为类目轴,默认情况下会自动对应到 dataset.source 中的第一列;
    //三个柱图系列,一一对应到 dataset.source 中后面每一列。(也就是按照series当中对象的顺序依次对应)
    ['Mon','Mon',125,45,35],
    ['Tue','Tue',54,53,15],
    ['Wed','Wed',85,86,38],
    ['Thu','Thu',79,86,88],
    ['Fri','Fri',103,138,49],
    ['Sat','Sat',187,251,86],
    ['Sun','Sun',56,154,78],
    ]
    var options = {
    //设置数据集
    dataset:{
    source:data
    },
    //设置标题
    title: {
    //标题文字
    text: "月销售数据",
    //副标题文字
    subtext: "销售人员必看!",
    left:'center',
    //主标题样式
    textStyle: {
    color: 'red'
    },
    //副标题样式
    subtextStyle: {
    color: 'yellow'
    },
    },
    //x轴
    xAxis:{
    data:['Mon','Tue','Wed','Thu','Fri','Sat','Sun']
    },
    // y轴
    yAxis:{},
    //一系列数据展示
    series:[
    //图表1-显示柱形图
    {
    type:"bar",
    color:"#5470c6",
    encode:{
    // 把 数据集所有数组当中,下标为2的数组的值为映射为折线图当中的y值
    y:2
    }
    },
    //图表2-显示折线图
    {
    type:'line',
    color:'red',
    encode:{
    // 把 数据集所有数组当中,下标为3的数组的值为映射为折线图当中的y值
    y:3
    }
    },
    //图表3-饼图
    {
    type:'pie',
    //设置饼图半径(像素值)
    radius:20,
    // data:[],
    encode:{
    // 指定数据项的名称
    itemName:1,
    // 把 数据集所有数组当中,下标为5的数组的值为映射为饼图的值
    value:4
    },
    //设置宽度
    width:200,
    height:200,
    //设置位置
    left:100,
    top:50
    }
    ]
    }
    table1.setOption(options)
    </script>
    </body>

    </html>

    效果图

    Echarts内置组件的使用

    • 组件,可以理解为一块块的功能~ 组件图

    echarts组件

    • 使用也很简单,只需要在配置对象当中添加即可,比如toolbox component

      1
      2
      3
      4
      5
      var options = {
      ...
      toolbox:{},
      ...
      }

    toolbox

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    //toolBox工具箱
    toolbox:{
    show:true,
    feature:{
    //保存为图片
    saveAsImage:{title:"保存为图片"},
    //配置项还原(图表的一些配置项目会恢复默认)
    restore:{title:"配置项还原"},
    //数据视图工具,可以展现当前图表所用的数据,编辑后可以动态更新。
    dataView:{title:"数据视图"},
    // 数据区域缩放。目前只支持直角坐标系的缩放。
    dataZoom:{title:{zoom:"数据区域缩放",back:"还原数据区域"}},
    //动态类型切换
    magicType:{
    title:{line:"切换为折线图",bar:"切换为柱形图"},
    type:["line",'bar']
    }
    }
    }
    • 效果图

    dataZoom

    • 配置对象当中添加 dataZoom:{ } 即可

    • 效果图

    legend

    • 图例的数据数组。数组项通常为一个字符串,每一项代表一个系列的 name(如果是饼图,也可以是饼图单个数据的 name)

    • 示例

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      var options = {
      ...
      series:[
      {name:"柱形图",...},
      {name:"折线图",...},
      {name:"饼图",...},
      ],
      legend:{
      //每一项代码一个系列对象当中的name
      data:["柱形图","折线图","饼图"]
      },
      ...
      }
    • 效果图

    其他一些常用的配置项目

    设置提示 tooltip

    • tooltip:{ }

      1
      2
      3
      4
      5
      var options = {
      ...
      tooltip:{},
      ...
      }
    • 效果

    双坐标轴-Y轴

    • 如果需要设置多个Y轴,则配置对象当中yAxis从{ } 改为 [ {…},{…} ]
    • 系列当中的对象设置为哪一个y轴需要设置yAxisIndex属性
    代码
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
    * {
    margin: 0;
    padding: 0;
    }

    #dom1 {
    width: 800px;
    height: 400px;
    }
    </style>
    </head>

    <body>
    <div id="dom1"></div>
    <script src="./js/echarts.js"></script>
    <script>
    var table1 = echarts.init(document.querySelector("#dom1"))
    table1.setOption({
    //设置标题
    title: {
    text: "双坐标"
    },
    xAxis: {
    //适用于离散的类目数据 -- 散点图
    type: 'category',
    data: ['游戏', '直播', '农业', '服饰'],
    },
    yAxis: [
    //y坐标轴1 设置
    {
    //显示Y轴的线
    axisLine: {
    show: true
    },
    //显示Y轴的刻度
    axisTick: {
    show: true
    }
    },
    //y坐标轴2 设置
    {
    //显示Y轴的线
    axisLine: {
    show: true
    },
    //显示Y轴的刻度
    axisTick: {
    show: true
    }
    }
    ],
    series: [
    //柱形图
    {
    //指明使用那一条y轴索引
    yAxisIndex:0,
    type: 'bar',
    data: [20, 35, 48, 80],
    },
    //散点图
    {
    //指明使用那一条y轴索引
    yAxisIndex:1,
    type: 'scatter',
    data: [88, 99, 124, 200],
    color: 'red'
    }
    ]
    })
    </script>
    </body>

    </html>
    • 效果图

    圆滑折线图

    • series当中的对象添加smooth:true即可

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      table.setOption({
      ...
      series:[
      {
      type:"line",
      //设置圆滑
      smooth:true,
      }
      ]
      ...
      })
    • 效果图

      smooth=true的效果

    添加事件监控

    • 官方API中Event网站

    • echarts经过init后返回实例化对象,实例化对象调用on即可(方法和原生js的addEventListener一样)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    import echarts from "echarts";
    var table1 = echarts.init(this.$refs.dom1);
    //不适用节流阀
    table1.on('mousemove',()=>{
    let {name,value} = params;
    //重新绘制标题
    table1.setOption({
    title:{
    text:name,
    subtext:value
    }
    })
    });
    //使用节流阀
    //按需引入
    import {throttle} from "lodash/throttle";
    table1.on('mousemove',throttle((params)=>{
    let {name,value} = params;
    //重新绘制标题
    table1.setOption({
    title:{
    text:name,
    subtext:value
    }
    })
    },20));
    • 返回的参数params的值

      1
      2
      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
      {
      // 当前点击的图形元素所属的组件名称,
      // 其值如 'series'、'markLine'、'markPoint'、'timeLine' 等。
      componentType: string,
      // 系列类型。值可能为:'line'、'bar'、'pie' 等。当 componentType 为 'series' 时有意义。
      seriesType: string,
      // 系列在传入的 option.series 中的 index。当 componentType 为 'series' 时有意义。
      seriesIndex: number,
      // 系列名称。当 componentType 为 'series' 时有意义。
      seriesName: string,
      // 数据名,类目名
      name: string,
      // 数据在传入的 data 数组中的 index
      dataIndex: number,
      // 传入的原始数据项
      data: Object,
      // sankey、graph 等图表同时含有 nodeData 和 edgeData 两种 data,
      // dataType 的值会是 'node' 或者 'edge',表示当前点击在 node 还是 edge 上。
      // 其他大部分图表中只有一种 data,dataType 无意义。
      dataType: string,
      // 传入的数据值
      value: number|Array,
      // 数据图形的颜色。当 componentType 为 'series' 时有意义。
      color: string,
      // 用户自定义的数据。只在 graphic component 和自定义系列(custom series)
      // 中生效,如果节点定义上设置了如:{type: 'circle', info: {some: 123}}。
      info: *
      }

    其他图

    实例(Instance)

    • 实例是指在同一个容器所绘制所有的图表的统称。每一个图表实例中可以多含多种图类型,每一个图表实例必须单独占用一个DOM节点。

    系列(series)

    • 系列是指图表的类型(如柱形图、饼图、折线图等),在一个图表实例中可以存在多个图表系列。

    组件(component)

    • 组件是指图表的各个组件部分,如标题、X轴、Y轴,工具栏提示等。

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/4561a0ef.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/4687ae16.html b/4687ae16.html new file mode 100644 index 000000000..3b41d6735 --- /dev/null +++ b/4687ae16.html @@ -0,0 +1 @@ +canvas基本使用 | 梦洁小站-属于你我的小天地

    canvas基本使用

    canvas的基本了解

    • canvas仅仅只是一个画布,定义一个canvas标签,需要通过属性,属性,属性来设置宽度高度(不可以通过样式!),如果不设置默认300*150

      • 不通过属性设置画布的宽度和高度,会造成坐标不准确的问题!
    • canvas必须要通过js来操作

    • canvas的坐标是从左到右是x轴,从上到下是y轴

    • vscode书写canvas的时候没有提示,加上/** @type {HTMLCanvasElement} */

      1
      2
      3
      4
      <script>
      /** @type {HTMLCanvasElement} */
      canvas代码书写
      </script>

    canvas的基本步骤

    1. 获取canvas的节点
    2. 画布创建画笔并选择画笔的绘制类型
    3. 开始绘制
    4. store()方法绘制 —— stroke() 方法会实际地绘制出通过 moveTo() 和 lineTo() 方法定义的路径。默认颜色是黑色。
    1
    2
    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
    <canvas id="cav" height="400" width="400"></canvas>
    <script>
    /** @type {HTMLCanvasElement} */
    //1.获取canvas节点(获取画布)
    var canvas = document.getElementById("cav");
    //2.画布创建画笔并选择画笔的绘制类型
    var ctx = canvas.getContext("2d");
    //3.开始绘制
    //绘制的起点
    ctx.moveTo(100,100);
    //有了绘制的起点我们就拖动着笔移动到100,200的坐标点
    ctx.lineTo(100,200);
    //我们再次移动画笔的坐标
    ctx.lineTo(200,300);
    //第一个坐标(100,100)和最后一个坐标(200,300)点闭合
    ctx.closePath();
    //设置填充颜色 - 红色
    //ctx.fillStyle = "red";
    //开始填充颜色
    //ctx.fill();
    //设置绘制线条的颜色
    //ctx.strokeStyle = "yellow";
    //设置绘制线条的粗细
    //ctx.lineWidth = "20";
    //绘制线条
    ctx.stroke();
    </script>
    1. 绘制效果绘制效果

    2. 如果想要填充的颜色,画笔可以设置fillStyle指明颜色并且调用fill()方法进行填充

      1
      2
      3
      4
      //设置填充颜色 - 红色
      ctx.fillStyle = "red";
      //开始填充
      ctx.fill();
      绘制效果
    3. 设置线条的颜色

      1
      2
      3
      4
      //设置绘制线条的颜色
      ctx.strokeStyle = "yellow";
      //设置绘制线条的粗细
      ctx.lineWidth = "20";
      绘制效果

    canvas绘制矩形和圆

    绘制矩形

    • 画笔使用storeRect()方法
    • 画笔使用fillRect()方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    var cav = document.getElementById("cav");
    var ctx = cav.getContext("2d");
    ctx.beginPath();
    //绘制线框矩形四个参数分别为 x,y,width,height
    ctx.strokeRect(100,100,150,200);
    //绘制填充矩形
    //设置填充颜色
    ctx.fillStyle = 'red';
    ctx.fillRect(300,100,150,200);

    绘制圆

    • 画笔使用arc()方法

      • ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);

      • X,Y,radius分别是横坐标,纵坐标,半径

      • startAngle: 圆弧的起点,x轴方向开始计算,单位以弧度表示

        • (1弧度等于57度左右) 因为2PI弧度=360所以1弧度=57度
        • 所以绘制一个圆直接起始弧度为0,终点弧度为2*Math.PI即可
      • endAngle:圆弧的终点,单位以弧度表示

      • anticlockwise:可选的Boolean值 ,如果为 true,逆时针绘制圆弧,反之,顺时针绘制。

        • 注意,是绘制,绘制,绘制!,比如其他参数相同,唯独最后一个参数不同,那么图形是完全不同的

        • 如图 ctx.arc(50,50,25,0,1,false);顺时针和逆时针绘制效果

          绘制效果
    1
    2
    3
    4
    5
    6
    var cav2 = document.getElementById('cav2');
    var ctx = cav2.getContext("2d");
    ctx.beginPath();
    //绘制圆 ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);
    ctx.arc(50,50,25,0,2*Math.PI,false);
    ctx.stroke();

    绘制字体

    • 画笔设置font属性可以设置字体的格式
    • 画笔调用fillText可以绘制字体
      • ctx.fillText(text, x, y, [maxWidth]);
      • text: 绘制的文本
      • x,y: 坐标
      • maxWidth: 绘制的最大宽度。如果指定了值,并且经过计算字符串的值比最大宽度还要宽,字体为了适应会水平缩放(如果通过水平缩放当前字体,可以进行有效的或者合理可读的处理)或者使用小号的字体。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <canvas id="cav" height="400" width="400"></canvas>
    <script>
    /** @type {HTMLCanvasElement} */
    //1.获取canvas节点(获取画布)
    var canvas = document.getElementById("cav");
    //2.画布创建画笔并选择画笔的绘制类型
    var ctx = canvas.getContext("2d");
    //绘制文字
    //设置绘制文字字体
    ctx.font = "20px 黑体"
    ctx.fillText("叫我将军大人",10,20);
    </script>
    绘制效果

    清空画步和清除指定区域

    • 均使用画笔当中的clearRect方法,只是传入的值有所区别
    • ctx.clearRect(x, y, width, height);
      • X,Y: 开始清空的坐标
      • width: 清空的宽度
      • height: 清空的高度

    清空画布

    • ctx.clearRect(0,0,画布的宽度,画布的高度);

    清除指定区域

    • ctx.clearRect(0,0,40,50)

    • 效果

      绘制效果

    canvas绘制一个柱形图

    效果

    柱形图效果

    代码

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    </head>

    <body>
    <canvas id="cav" height="600" width="800"></canvas>
    <style>
    #cav{
    border: 1px solid red;
    }
    </style>
    <script>
    /** @type {HTMLCanvasElement} */
    //1.获取canvas节点(获取画布)
    var canvas = document.getElementById("cav");
    //2.画布创建画笔并选择画笔的绘制类型
    var ctx = canvas.getContext("2d");
    //设置线条粗细
    ctx.lineWidth = "2";
    //设置字体
    ctx.font = "20px 黑体"
    //绘制坐标轴先
    //y坐标
    ctx.fillText("数据可视化",0,40);
    ctx.moveTo(40,40);
    ctx.lineTo(40,480)
    //150字体
    ctx.moveTo(40,80);
    ctx.lineTo(20,80);
    ctx.fillText("150",10,80);
    //120字体
    ctx.moveTo(40,160);
    ctx.lineTo(20,160);
    ctx.fillText("120",10,160);
    //90字体
    ctx.moveTo(40,240);
    ctx.lineTo(20,240);
    ctx.fillText("90",20,240);
    //60字体
    ctx.moveTo(40,320);
    ctx.lineTo(20,320);
    ctx.fillText("60",20,320);
    //30字体
    ctx.moveTo(40,400);
    ctx.lineTo(20,400);
    ctx.fillText("30",20,400);
    //0字体
    ctx.moveTo(40,480);
    ctx.lineTo(20,480);
    ctx.fillText("0",20,480);

    //x坐标
    ctx.moveTo(40,480);
    ctx.lineTo(680,480);
    //竖线1
    ctx.moveTo(200,480)
    ctx.lineTo(200,500);
    ctx.fillText("食品",100,500);
    //竖线2
    ctx.moveTo(360,480)
    ctx.lineTo(360,500);
    ctx.fillText("数码",260,500);
    //竖线3
    ctx.moveTo(520,480)
    ctx.lineTo(520,500);
    ctx.fillText("服饰",420,500);
    ctx.fillText("箱包",580,500);
    ctx.stroke();

    //绘制矩形
    //设置填充矩形颜色
    ctx.fillStyle = 'hotpink';
    //食品矩形
    ctx.fillRect(80,320,80,160);
    //数码矩形
    ctx.fillRect(240,280,80,200);
    //服饰矩形
    ctx.fillRect(400,240,80,240);
    //箱包矩形
    ctx.fillRect(560,0,80,480);

    </script>
    </body>

    </html>

    过程图

    过程图

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/4687ae16.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/4809f4cc.html b/4809f4cc.html new file mode 100644 index 000000000..4b4ab0822 --- /dev/null +++ b/4809f4cc.html @@ -0,0 +1 @@ +element-ui分页器设置每一页显示数量(page-size)后页码没有发生变化原因与解决 | 梦洁小站-属于你我的小天地

    element-ui分页器设置每一页显示数量(page-size)后页码没有发生变化原因与解决

    问题

    element-ui当中分页器,设置每一页显示数量(page-size)后页码没有发生变化

    比如

    1. 原来是每一个显示数据量为3个数据

    1. 后面想每一页显示数据为5个,那么按照道理来说,页码也会变化的

      然而并没有发生变化

    原因

    • page-size要和page-sizes当中的数据对得上才可以,也就是说page-size如果数字没有与page-sizes这个数组里面的数组对应上,那么就会区page-sizes当中的第一个值来计算页码

    示例1

    • page-size有值但是page-sizes不填写

    示例1效果图

    示例1分析

    • page-sizes不填写**(注意是page-sizes)**,则取默认值为 :page-sizes = “ [10, 20, 30, 40, 50, 100] “
    • 这里填写的 :page-size = “12”,在 :page-sizes = “[10, 20, 30, 40, 50, 100] “ 找不到对应的,就忽略page-size设置的值,而去取page-sizes数组当中第一个值,也就是10
    • 最终计算页码: 10898 / 10 向上取整就为1090页

    示例2

    • page-size有值并且page-sizes填写但是page-sizes当中没有对应值

    示例2效果图

    示例2分析

    • page-size的值在page-sizes没有有对应的值,于是取page-sizes数组当第一个值,也就是3
    • 计算页码: 10898/3 向上取整 = 3633页

    示例3

    • page-size有值并且page-sizes填写并且page-sizes当中有对应值

    示例3效果图

    示例3分析

    • page-size的值在page-sizes有对应的值,所以可以正常按照我们设置的显示页码
    • 计算页码: 10898/6 向上取整 = 1817页
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/4809f4cc.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/4ab6d7aa.html b/4ab6d7aa.html new file mode 100644 index 000000000..53c95e7c6 --- /dev/null +++ b/4ab6d7aa.html @@ -0,0 +1 @@ +前端真题刷题-注意基础知识不要忘了基本原理 | 梦洁小站-属于你我的小天地

    前端真题刷题-注意基础知识不要忘了基本原理

    题目1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    SVG 有多少种图形对象类型?
    A: 2

    B: 3

    C: 5

    D: 7

    • 答案
    • 解析

    题目2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    以下哪个是 html5 中的 input 类型属性?
    (1) search
    (2) datetime
    (3) week
    (4) color
    (5) track
    (6) placeholder

    A: 1 , 2 , 3 , 4 , 6

    B: 1 , 2 , 3 , 4 , 5

    C: 2 , 3 , 4 , 5 , 6

    D: 1 , 3 , 4 , 5 , 6

    • 答案
    • 解析

    题目3

    1
    2
    3
    4
    5
    以下哪个不是 HTML5 中使用的媒体元素?
    A: <source>
    B: <audio>
    C: <track>
    D: <time>
    • 答案
    • 解析

    题目4

    1
    2
    3
    4
    5
    SVG 是什么意思?
    A: 二级矢量图形
    B: 可伸缩的垂直图形
    C: 可伸缩矢量图形
    D: 三级矢量图形
    • 答案
    • 解析

    题目5

    1
    2
    3
    4
    5
    input 元素的哪个属性将元素的值设置为表示一定范围内的数字?
    A: range
    B: email
    C: file
    D: data
    • 答案
    • 解析
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/4ab6d7aa.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/4b280335.html b/4b280335.html new file mode 100644 index 000000000..f289cd3f3 --- /dev/null +++ b/4b280335.html @@ -0,0 +1 @@ +java-lambda和练习之多线程下载工具 | 梦洁小站-属于你我的小天地

    java-lambda和练习之多线程下载工具

    多线程

    • 脱离了任务的线程是没有意义的

      • 但是不一定要去执行任务
    • 线程是通过Thread类来创建的

    • 任务是通过Runnable接口来实现的

    • 继承Thread类

    • 实现Runnable接口

      • 无返回值
    • 实现Callable接口

      • 有返回值

    Thread

    • Thread构造器:无参构造就是不需要指定任务,有参构造可以直接指定线程的任务
    1
    public Thread(Runnable target)	

    流程

    1. 创建线程对象,同时指定任务
    2. 启动线程,start后进入就绪状态,等待获取CPU资源
    3. 一旦拿到CPU资源,开始执行任务,调用Thread的run方法
    1
    2
    3
    4
    5
    public void run(){
    if(target != null){
    target.run();
    }
    }

    示例

    1
    2
    3
    4
    5
    6
    7
    8
    public class MyThread extends Thread{
    @Override
    public void run() {
    for (int i = 0; i < 100; i++){
    System.out.println(i);
    }
    }
    }
    1
    2
    3
    4
    5
    6
    7
    public class TestApplication {

    public static void main(String[] args) {
    new MyThread().start();
    }

    }

    缺点

    • 继承的缺点在于直接将任务的实现写到了线程当中,耦合度太高,想干其他的,必须要修改源代码
    • 解决办法使用类去实现Runnable接口,将任务和线程进行分开

    Runnable

    线程休眠

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public static void main(String[] args) throws InterruptedException {
    Thread thread1 = new Thread(() -> {
    for (int i = 0; i < 5; i++) {
    System.out.println("第一个 = " + i);
    }
    });
    Thread thread2 = new Thread(() -> {
    for (int i = 0; i < 5; i++) {
    System.out.println("第二个 = " + i);
    }
    });
    thread1.start();
    thread1.sleep(1000);
    thread2.start();
    }
    • sleep方法到底是让哪一个方法休眠?
      • 不在于谁调用sleep,而在与sleep写到哪,如上面的代码,先输出thread1,在输出thread2,休眠的是main方法
    • 下面的代码则是先执行thread2,再执行thread1,休眠的是thread1
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public static void main(String[] args) {
    Thread thread1 = new Thread(() -> {
    try {
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    throw new RuntimeException(e);
    }
    for (int i = 0; i < 5; i++) {
    System.out.println("第一个 = " + i);
    }
    });
    Thread thread2 = new Thread(() -> {
    for (int i = 0; i < 5; i++) {
    System.out.println("第二个 = " + i);
    }
    });
    thread1.start();
    thread2.start();
    }

    lambda表达式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    (parameters) -> expression

    (parameters) ->{ statements; }

    parameters 是参数列表,expression 或 { statements; } 是Lambda 表达式的主体。如果只有一个参数,可以省略括号;如果没有参数,也需要空括号。

    形参列表。形参列表允许省略形参类型。如果形参列表中只有一个参数,可以省略形参列表的圆括号。
    箭头(->)。英文短线和大于号。
    代码块。如果代码块只有一句,可以省略花括号。如果只有一条 return 语句,可以省略 return,lambda表达式会自动返回这条语句的值。
    1
    2
    3
    4
    5
    6
    Thread thread = new Thread(new Runnable(){
    @Override
    public void run(){

    }
    });
    1
    2
    3
    4
    5
    6
    7
    //一个类,不过没有名称而已
    {
    @Override
    public void run(){

    }
    }
    • 进一步使用lambda来进一步简化代码,只把方法的实现进行传值,而不关注其他内容
    • () -> {}括号实现
    1
    2
    3
    4
    5
    6
    Thread thread1 = new Thread(() -> {
    for (int i = 0; i < 10; i++) {
    System.out.println("i = " + i);
    }
    });
    thread1.start();
    • 普通方法下如果需要调用MathOperation接口下的operation方法,就需要这样做
      • 创建一个实现MathOperation接口的类,调用这个类的new方法创建对象后调用operation方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    public class One {
    interface MathOperation {
    /**
    * 计算和
    * @param a 数字A
    * @param b 数字B
    * @return 相加结果
    */
    int operation(int a, int b);
    }
    public static void main(String[] args) {
    MathOperationImpl mathOperation = new MathOperationImpl();
    int operation = mathOperation.operation(1, 2);
    System.out.println("operation = " + operation);
    }
    }

    class MathOperationImpl implements One.MathOperation {

    @Override
    public int operation(int a, int b) {
    return a + b;
    }
    }

    • 但是使用lambda表达式就很方便了,代码简化为下方
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    public class One {
    interface MathOperation {
    /**
    * 计算和
    * @param a 数字A
    * @param b 数字B
    * @return 相加结果
    */
    int operation(int a, int b);
    }
    public static void main(String[] args) {
    MathOperation mathOperation = new MathOperation() {
    @Override
    public int operation(int a, int b) {
    return a + b;
    }
    };
    int num = mathOperation.operation(1,2);
    System.out.println("num = " + num);
    }
    }
    //此时MathOperation接口只有一个抽象方法,还可以简化为下面的写法

    public class One {
    interface MathOperation {
    /**
    * 计算和
    * @param a 数字A
    * @param b 数字B
    * @return 相加结果
    */
    int operation(int a, int b);

    }
    public static void main(String[] args) {
    MathOperation mathOperation = (int a,int b) -> {
    return a + b;
    };
    int num = mathOperation.operation(1,2);
    System.out.println("num = " + num);
    }
    }

    //lambda也可以省略参数类型
    public static void main(String[] args) {
    MathOperation mathOperation = (a,b) -> {
    return a + b;
    };
    int num = mathOperation.operation(1,2);
    System.out.println("num = " + num);
    }

    //甚至,如果只有一行,且是返回值,{}都可以省略(类似于前端的es6箭头函数写法)
    public static void main(String[] args) {
    MathOperation mathOperation = (a,b) -> a + b;
    int num = mathOperation.operation(1,2);
    System.out.println("num = " + num);
    }
    //注意,二行代码就不能省略{}了

    public static void main(String[] args) {
    MathOperation mathOperation = (a,b) -> {
    System.out.println(112);
    return a + b;
    };
    int num = mathOperation.operation(1,2);
    System.out.println("num = " + num);
    }

    顺带一提

    类型推断

    • 有时候你经常看到List<Dog> dogs2 = new ArrayList<>();这种写法,是不是很好奇为啥不完整的写成List<Dog> dogs2 = new ArrayList<Dog>();,原因很简单,因为左边指明了列表类型为Dog,右边可以推断出来,写不写都无所谓的,这就是类型推断的作用
    • 但是类型推断也不是万能的,不是所有的都可以推断出来的,所以有时候,还是要显示的添加形参类型,例如:先不要管这个代码的具体作用
    1
    2
    3
    4
    5
    6
    7
    BinaryOperator b = (x, y)->x*y;
    //上面这句代码无法通过编译,下面是报错信息:无法将 * 运算符作用于 java.lang.Object 类型。
    The operator * is undefined for the argument type(s) java.lang.Object, java.lang.Object

    //添加参数类型,正确的代码。
    BinaryOperator<Integer> b = (x, y)->x*y;

    函数式接口

    • 具体可看
    • 满足下面规则就是函数式接口
      • 只能有一个抽象方法。
      • 可以有多个静态方法和默认方法。
      • 默认包含Object类的方法。
    • 可以很方便我们使用lambda表达式

    我们常用的Runnable就是函数式接口

    System.out::print

    练习项目-下载工具

    ScheduledExecutorService

    • scheduleAtFixedRate

      • 任务耗费的时间会和设定的period同步开始计时,比如说一个任务耗费6秒,但是设置的是每隔3秒执行,就会导致过了3秒再次执行任务
      • 倘若在执行任务的时候,耗时超过了间隔时间,则任务执行结束之后直接再次执行,而不是再等待间隔时间执
    • scheduleWithFixedDelay

      • 在执行任务的时候,无论耗时多久,任务执行结束之后都会等待间隔时间之后再继续下次任务。

    下载文件进度功能

    • 百分比 = 已下载的文件大小 / 要下载的文件大小
      • percent = downloadedSize / totalSize;
    • 下载速度 = 已下载的文件大小 - 上一次下载的文件大小
      • speed = downloadedSize - prevDownloadedSize;
    • 剩余时间 = (要下载的文件大小 - 已下载的文件大小) / 下载速度
      • remainTime = ( totalSize - downloadedSize ) / speed;

    线程池

    • execute

      • 任务提交给线程池
    • 线程满了,队列也慢了,就会执行拒绝策略

    • 创建线程池

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public ThreadPoolExecutor(int corePoolSize,
    int maximumPoolSize,
    long keepAliveTime,
    TimeUnit unit,
    BlockingQueue<Runnable> workQueue,
    RejectedExecutionHandler handler)
    corePoolSize:线程池核心线程数量
    maximumPoolSize:线程池最大线程数量
    keepAliverTime:当活跃线程数大于核心线程数时,空闲的多余线程最大存活时间
    unit:存活时间的单位
    workQueue:存放任务的队列
    handler:超出线程范围和队列容量的任务的处理程序

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    import java.util.concurrent.ArrayBlockingQueue;
    import java.util.concurrent.ThreadPoolExecutor;
    import java.util.concurrent.TimeUnit;

    public class Pool {
    public static void main(String[] args) {
    //设置核心线程为2个,总共线程为3个,非核心线程则为1个,设置等待队列为5
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 3, 1, TimeUnit.MINUTES, new ArrayBlockingQueue<>(5));

    Runnable r1 = () -> {
    System.out.println("当前执行线程" + Thread.currentThread().getName());
    };
    //提交单个线程到线程池
    // threadPoolExecutor.execute(r1);

    //如果8改为9,就会执行拒绝策略了
    for (int i = 0; i < 8; i++) {
    threadPoolExecutor.execute(r1);
    }
    }
    }

    • 关闭
    1
    2
    3
    4
    shutdown():在完成已提交的任务后关闭服务,不再接受新任;
    shutdownNow():停止所有正在执行的任务并关闭服务;
    isTerminated():测试是否所有任务都执行完毕了;
    isShutdown():测试是否该ExecutorService已被关闭。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public static void main(String[] args) {
    //设置核心线程为2个,总共线程为3个,非核心线程则为1个,设置等待队列为5
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 3, 1, TimeUnit.MINUTES, new ArrayBlockingQueue<>(5));

    Runnable r1 = () -> {
    System.out.println("当前执行线程" + Thread.currentThread().getName());
    };
    //提交单个线程到线程池
    // threadPoolExecutor.execute(r1);

    for (int i = 0; i < 8; i++) {
    threadPoolExecutor.execute(r1);
    }

    //线程池关闭
    threadPoolExecutor.shutdown();//温和,等待队列任务执行完成后才关闭
    threadPoolExecutor.shutdownNow();//暴力关闭,如果等待队列还有任务,都抛弃,不执行

    }
    • 你也可以使用来等待一会,如果还没有执行完成则强制关闭
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    public static void main(String[] args) throws InterruptedException {
    //设置核心线程为2个,总共线程为3个,非核心线程则为1个,设置等待队列为5
    ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 3, 1, TimeUnit.MINUTES, new ArrayBlockingQueue<>(5));

    Runnable r1 = () -> {
    System.out.println("当前执行线程" + Thread.currentThread().getName());
    };
    //提交单个线程到线程池
    // threadPoolExecutor.execute(r1);

    for (int i = 0; i < 8; i++) {
    threadPoolExecutor.execute(r1);
    }

    //线程池关闭
    //threadPoolExecutor.shutdown();//温和,等待队列任务执行完成后才关闭
    //threadPoolExecutor.shutdownNow();//暴力关闭,如果等待队列还有任务,都抛弃,不执行
    threadPoolExecutor.shutdown();
    //等待一分钟,如果线程池没有关闭则执行里面逻辑
    if(!threadPoolExecutor.awaitTermination(1,TimeUnit.MINUTES)){
    threadPoolExecutor.shutdownNow();
    }
    }

    切片下载

    • 这里用到的切片下载就是请求头的Range
      • 告知服务端,客户端下载该文件想要从指定的位置开始下载,至于 Range 字段属性值的格式有以下几种:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    语法格式
    Range: <unit>=<range-start>-<range-end>
    Range: <unit>=<range-start>-<range-end>, <range-start>-<range-end>, <range-start>-<range-end>

    <unit> 类型,一般来说是bytes;
    <range-start> 表示范围的起始值,一般是数字,如果不是数字就看服务端逻辑如何处理;
    <range-end> 表示范围的结束值。这个值是可选的,如果不存在,表示此范围一直延伸到文档结束,如果非数字,同上。

    常见的
    Range:bytes=0-500
    表示下载从0500字节的文件,即头500个字节 ,[0-500]前闭后闭。0<=range<=500

    Range:bytes=501-1000
    表示下载从5001000这部分的文件,单位字节

    Range:bytes=-500
    表示下载最后的500个字节

    Range:bytes=500-
    表示下载从500开始到文件结束这部分的内容

    Range:bytes=500-600,700-1000
    表示下载这两个区间的内容

    原子类

    volatile关键字

    Volatile关键字的作用主要有如下两个:

    1. 线程的可见性:当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。

      1. 当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值。

        而普通的共享变量不能保证可见性,因为普通共享变量被修改之后,什么时候被写入主存是不确定的,当其他线程去读取时,此时内存中可能还是原来的旧值,因此无法保证可见性。

    2. 顺序一致性:禁止指令重排序。

    CountDownLatch

    • CountDownLatch 是 Java 中的一个并发工具类,用于协调多个线程之间的同步。其作用是让某一个线程等待多个线程的操作完成之后再执行。它可以使一个或多个线程等待一组事件的发生,而其他的线程则可以触发这组事件。

    • 注意不要讲await写成了wait

    问题

    • 改为原子类后scheduleAtFixedRate执行不正常(只执行一次)
      • 在源码的Java doc中的发现了如下一句话:If any execution of the task encounters anexception, subsequent executions are suppressed.Otherwise, the task will onlyterminate via cancellation or termination of the executor.
      • 简单总结就是:如果定时任务执行过程中遇到发生异常,则后面的任务将不再执行。
    • 之前的
      • 可以看到downloadedSize不为空

    • 但是现在这个是null
      • 解决是延迟1秒调用

    • 可能你改为原子类后依旧是null
      • 可能你没有new
      • 应该是public static volatile DoubleAdder downloadedSize = new DoubleAdder();

    技巧

    • 快速100次for循环
      • 100.for后tab

    • 输出n次,只保留最新一次的输出结果(java 怎么替换上一次的输出)

      • \r作用 将光标定义到当前行行首

        • \r后有新内容时,会先删除之前以前存在过的文本,即只打印\r后面的内容
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        //将会输出 结果是0结果是1结果是2结果是3结果是4
        for (int i = 0; i < 5; i++) {

        System.out.print("结果是" + i);
        }

        //只会输出 结果是4
        for (int i = 0; i < 5; i++) {
        System.out.print("\r");
        System.out.print("结果是" + i);
        }
    • 常用的流分类

    分类字节输入流字节输出流字符输入流字符输出流
    抽象父类InputStreamOutputStreamReaderWriter
    访问文件FileInputStreamFileOutStreamFileReaderFileWriter
    访问数值ByteArrayInputStreamByteArrayOutStreamCharArrayReaderCharArrayWriter
    访问管道PipedInputStreamPipedOutStreamPipedReaderPipedWriter
    访问字符串StringReaderStringWriter
    缓冲流BufferedInputStreamBufferedOutputStreamBufferedReaderBufferedWriter
    转换流InputStreamReaderOutputStreamWriter
    对象流ObjectInputStreamObjectOutputStream
    装饰流FilterInputStreamFilterOutputStreamFilterReaderFilterWriter
    打印流PrintStreamPrintWriter
    数据过滤流DataInputStreamDataOutputStream

    疑问

    • lambda写的这么简略,怎么知道是传入的哪一个类?

    thread.run和start方法有什么区别?直接调用run有什么问题吗?

    • idea的建议对 ‘run()’ 的调用可能应当替换为 ‘start()’

    • 直接调用run就不是多线程了,而是执行里面的一个方法
      • run()方法当作普通方法的方式调用,程序还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码: 而如果直接用run方法,这只是调用一个方法而已,程序中依然只有主线程–这一个线程,其程序执行路径还是只有一条,这样就没有达到写线程的目的。
    • 具体可看
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/4b280335.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/4f2816f0.html b/4f2816f0.html new file mode 100644 index 000000000..6ad64f24e --- /dev/null +++ b/4f2816f0.html @@ -0,0 +1 @@ +前端canvas的学习和将网页生成canvas图片 | 梦洁小站-属于你我的小天地

    前端canvas的学习和将网页生成canvas图片

    目标

    第一个canvas

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <body>
    <canvas
    id="c"
    width="300"
    height="200"
    style="border:1px solid #ccc"
    ></canvas>
    <script>
    //获取canvas元素
    const cnv = document.querySelector('#c');
    //获取canvas上下文环境对象
    const cxt = cnv.getContext('2d');
    //绘制图形
    cxt.moveTo(100,100);//起始点坐标(x,y)
    cxt.lineTo(200,100);//重点坐标(x,y)
    cxt.stroke();//将起点和终点链接起来
    </script>
    </body>

    不能通过css设置画布的宽高

    1
    2
    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
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
    #c{
    width: 400px;
    height: 400px;
    }
    </style>
    </head>
    <body>
    <canvas
    id="c"

    style="border:1px solid #ccc"
    ></canvas>
    <script>
    //获取canvas元素
    const cnv = document.querySelector('#c');
    //获取canvas上下文环境对象
    const cxt = cnv.getContext('2d');
    //绘制图形
    cxt.moveTo(100,100);//起始点坐标(x,y)
    cxt.lineTo(200,100);//重点坐标(x,y)
    cxt.stroke();//将起点和终点链接起来
    console.log(cnv.width);//输出300
    console.log(cnv.height);//输出150
    </script>
    </body>
    </html>

    canvas 的默认宽度是300px,默认高度是150px。

    1. 如果使用 css 修改 canvas 的宽高(比如本例变成 400px * 400px),那宽度就由 300px 拉伸到 400px,高度由 150px 拉伸到 400px。
    2. 使用 js 获取 canvas 的宽高,此时返回的是 canvas 的默认值。

    坐标系

    • 这个很重要,不能弄混了

    • Canvas 使用的是 W3C 坐标系 ,也就是遵循我们屏幕、报纸的阅读习惯,从上往下,从左往右。

    W3C 坐标系数学直角坐标系X轴 是一样的,只是 Y轴 的反向相反。

    W3C 坐标系Y轴 正方向向下

    绘制直线

    • 使用moveTo,lineTo,stroke即可绘制出一条直线
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <body>
    <canvas id="c" style="border:1px solid red"></canvas>
    <script>
    const canvas = document.querySelector('#c');
    const cxt = canvas.getContext('2d');
    cxt.moveTo(100,100);//起始点坐标(x,y)
    cxt.lineTo(200,100);//下一个点的坐标(x,y)
    cxt.stroke();//链接起来
    </script>
    </body>

    • 绘制多条直线就调用多次方法即可

    设置样式

    • lineWidth:线的粗细
    • strokeStyle线的颜色
    • lineCap:线帽

    linecap

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <body>

    <canvas id="c" style="border:1px solid red"></canvas>
    <script>
    const canvas = document.querySelector('#c');
    const cxt = canvas.getContext('2d');//获取canvas上下文环境对象
    cxt.moveTo(10,10);//起始点坐标(x,y)
    cxt.lineTo(60,60);

    //设置线条的宽度
    cxt.lineWidth = 20;
    //更改线条的颜色
    cxt.strokeStyle = 'green';
    //修改线帽
    cxt.lineCap = 'round';

    cxt.stroke();//链接起来
    </script>
    </body>

    新开路径

    • 我说怎么画2条线另外一条也变粗了
    • 在绘制多条线段的同时,还要设置线段样式,通常需要开辟新路径。要不然样式之间会相互污染
    • 使用 beginPath() 方法,重新开一个路径
      • 设置新线段的样式(必做项)
        • 否则会出现前面影响后面,或者后面影响前面的情况出现
      • 比如前一个线设置了strokeWidth:20,那么即使开辟了新路径,不设置strokeWidth的话第二条路径还是依照strokeWidth为20进行绘制
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    <body>

    <canvas id="c" style="border:1px solid red"></canvas>
    <script>
    const canvas = document.querySelector('#c');
    const cxt = canvas.getContext('2d');//获取canvas上下文环境对象
    cxt.moveTo(10,10);//起始点坐标(x,y)
    cxt.lineTo(60,60);
    //设置线条的宽度
    cxt.lineWidth = 20;
    cxt.stroke();//链接起来

    //新开一个路径
    cxt.beginPath();
    //设置新线段的样式
    cxt.lineWidth = 1;
    //更改线条的颜色
    cxt.strokeStyle = 'green';
    //修改线帽
    cxt.lineCap = 'round';
    cxt.moveTo(100,100);//起始点坐标(x,y)
    cxt.lineTo(200,100);//下一个点的坐标(x,y)
    cxt.stroke();//链接起来
    </script>
    </body>

    • 在设置 beginPath() 的同时,也各自设置样式。这样就能做到相互不影响了。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

    <script>
    const cnv = document.getElementById('c')
    const cxt = cnv.getContext('2d')

    cxt.moveTo(20, 100)
    cxt.lineTo(200, 100)
    cxt.lineWidth = 10
    cxt.strokeStyle = 'pink'
    cxt.stroke()

    cxt.beginPath() // 重新开启一个路径
    cxt.moveTo(20, 120.5)
    cxt.lineTo(200, 120.5)
    cxt.lineWidth = 4
    cxt.strokeStyle = 'red'
    cxt.stroke()
    </script>

    折线(特殊的直线)

    • 也是使用方法moveTo,lineTo,stroke即可完成

    矩形(rect)

    • 点组成线,线组成面,面构成图形,你可以使用绘制直线的方式去绘制矩形,但是有现成的方法当然有现成的
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <body>
    <canvas id="c" style="border:1px solid red;height: 300px;width: 300px;"></canvas>
    <script>
    const canvas = document.querySelector('#c');
    const cxt = canvas.getContext('2d');//获取canvas上下文环境对象
    cxt.strokeStyle = "green";//必须要写在绘制前面
    cxt.strokeRect(10, 10, 120, 100);//起始点坐标(10,10) 宽120 高100
    </script>
    </body>

    • 大概这么理解,直接抄他的,嘻嘻

    填充矩形

    • 你可以理解为stroke都是在做描边效果的,真正要创建填充的效果还是需要使用fill开头的一些关键字
    • 需要注意的是,fillStyle 必须写在 fillRect() 之前,不然样式不生效。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <body>
    <canvas id="c" style="border:1px solid red;height: 300px;width: 300px;"></canvas>
    <script>
    const canvas = document.querySelector('#c');
    const cxt = canvas.getContext('2d');//获取canvas上下文环境对象

    cxt.fillStyle = "blue";//必须要写在绘制前面
    cxt.fillRect(10, 10, 120, 100);//起始点坐标(10,10) 宽120 高100

    </script>
    </body>

    • 同时使用strokeRect()fillRect(),则是描边+填充效果
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <body>
    <canvas id="c" style="border:1px solid red;height: 300px;width: 300px;"></canvas>
    <script>
    const canvas = document.querySelector('#c');
    const cxt = canvas.getContext('2d');//获取canvas上下文环境对象

    cxt.fillStyle = "blue";//必须要写在绘制前面
    cxt.fillRect(10, 10, 120, 100);//起始点坐标(10,10) 宽120 高100

    cxt.strokeStyle = "green";
    cxt.strokeRect(10, 10, 120, 100);//起始点坐标(10,10) 宽120 高100
    </script>
    </body>

    使用rect()

    • rect()fillRect() 、strokeRect() 的用法差不多,唯一的区别是:

    • strokeRect()fillRect() 这两个方法调用后会立即绘制;rect() 方法被调用后,不会立刻绘制矩形,而是需要调用 stroke()fill() 辅助渲染。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <body>
    <canvas id="c" style="border:1px solid red;height: 300px;width: 300px;"></canvas>
    <script>
    const canvas = document.querySelector('#c');
    const cxt = canvas.getContext('2d');

    cxt.strokeStyle = 'pink'
    cxt.fillStyle = 'blue';

    cxt.rect(10, 10, 120, 100);

    cxt.stroke();//进行描边操作
    cxt.fill();//进行填充炒作
    </script>
    </body>

    clearRect()

    • 清空指定区域
    1
    clearRect(x, y, width, height)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <body>
    <canvas id="c" style="border:1px solid red;height: 300px;width: 300px;"></canvas>
    <script>
    const canvas = document.querySelector('#c');
    const cxt = canvas.getContext('2d');

    cxt.strokeStyle = 'pink'
    cxt.fillStyle = 'blue';

    cxt.rect(10, 10, 120, 100);

    cxt.stroke();//进行描边操作
    cxt.fill();//进行填充炒作

    //清空矩形
    cxt.clearRect(20, 20, 100, 80);
    </script>
    </body>

    • 也可以使用clearRect来清空当前矩形
    1
    2
    3
    const cnv = document.querySelector('#c');
    const cxt = cnv.getContext('2d');
    cxt.clearRect(0, 0, cnv.width, cnv.height)

    多边形

    • Canvas 要画多边形,需要使用 moveTo()lineTo()closePath()
      • 需要真正闭合,使用 closePath() 方法。不要自己手动去连接2点

    三角形

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <body>
    <canvas id="canvas" width="400" height="300" style="border: 1px solid red;"></canvas>
    <script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    ctx.lineWidth=5;//线条粗细
    ctx.moveTo(10,10);
    ctx.lineTo(100,100);
    ctx.lineTo(300,100);
    ctx.closePath();//闭合路径
    ctx.stroke();//绘制路径
    </script>
    </body>

    arc圆

    1
    arc(x, y, r, sAngle, eAngle,counterclockwise)
    • xy: 圆心坐标
    • r: 半径
    • sAngle: 开始角度
    • eAngle: 结束角度
    • counterclockwise: 绘制方向(true: 逆时针; false: 顺时针),默认 false
    • 绘制圆形之前,必须先调用 beginPath() 方法!!! 在绘制完成之后,还需要调用 closePath() 方法!!!
    • 大佬的图也通俗易懂
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <body>
    <canvas id="canvas" width="400" height="300" style="border: 1px solid red;"></canvas>
    <script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    ctx.beginPath();
    ctx.arc(100,100,50,0,Math.PI * 2);
    ctx.stroke();
    ctx.closePath();
    </script>
    </body>

    • 在实际开发中,为了让自己或者别的开发者更容易看懂弧度的数值,1°应该写成 Math.PI / 180。(说的很好)

      • 100°: 100 * Math.PI / 180
      • 110°: 110 * Math.PI / 180
      • 241°: 241 * Math.PI / 180
    • 半圆

      • 结束角度为180度就是半圆了
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <body>
    <canvas id="canvas" width="400" height="300" style="border: 1px solid red;"></canvas>
    <script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    ctx.beginPath();
    ctx.arc(100,100,50,0,Math.PI );
    ctx.closePath();
    ctx.stroke();
    </script>
    </body>

    弧线

    • 调用arc()方法不调用closePath()方法所画出的图像就是一条弧线
    • 可用arc()或者arcTo()绘制弧线
    • arcTo语法
      • arcTo() 方法利用 开始点、控制点和结束点形成的夹角,绘制一段与夹角的两边相切并且半径为 radius 的圆弧
    1
    2
    3
    4
    5
    6
    arcTo(cx, cy, x2, y2, radius)
    cx: 两切线交点的横坐标
    cy: 两切线交点的纵坐标
    x2: 结束点的横坐标
    y2: 结束点的纵坐标
    radius: 半径
    • 其中,(cx, cy) 也叫控制点,(x2, y2) 也叫结束点。

    • 是不是有点奇怪,为什么没有 x1y1

      • (x1, y1)是开始点,通常是由moveTo()或者lineTo()` 提供。
    • 绘制30度的弧线

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <body>
    <canvas id="canvas" width="400" height="300" style="border: 1px solid red;"></canvas>
    <script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    ctx.beginPath();
    ctx.arc(100, 100, 50, 0, 30 * Math.PI / 180, false);
    ctx.stroke();
    </script>
    </body>
    • 下面用arcTo方法绘制的不知道多少度,可以用数学算算
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

    <script>
    const cnv = document.getElementById('c')
    const cxt = cnv.getContext('2d')

    cxt.moveTo(40, 40)
    cxt.arcTo(120, 40, 120, 120, 80)

    cxt.stroke()
    </script>
    • 开始点即为(40,40)

    样式设置

    stroke(描边)

    • 绘制描边线条

    lineWidth(设置线条宽度)

    • lineWidth = 值 + 单位
    • 设置绘制的线条宽度,默认单位为px,默认值为1

    strokeStyle(描边颜色)

    • strokeStyle = 颜色值

    lineCap(设置线帽)

    • lineCap = 值

    • butt: 默认值,无线帽

    • square: 方形线帽

    • round: 圆形线帽

    lineJoin(拐角样式)

    • lineJoin = 值
    • miter: 默认值,尖角
    • round: 圆角
    • bevel: 斜角

    setLineDash(设置描边虚线)

    • setLineDash([])传入数组,且元素是数值型
    • 只传1个值代表空白值(单位为px)
    • 有2个值代表线条值,空白值,
    • 有3个以上的值线条值,空白值,线条值依次轮的去
    1
    2
    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

    <body>
    <canvas id="canvas" width="400" height="300" style="border: 1px solid red;"></canvas>
    <script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    //基础样式
    ctx.strokeStyle = 'blue';
    ctx.lineWidth = 10;

    ctx.moveTo(10, 10);
    ctx.lineTo(290, 10);

    //设置空白值为10(也就是间隔10px)
    ctx.setLineDash([10])
    ctx.stroke();

    ctx.beginPath();
    //设置线条长度为10px,空白值为5px
    ctx.setLineDash([10, 5])
    ctx.moveTo(10, 40);
    ctx.lineTo(290, 40);
    ctx.stroke();

    ctx.beginPath();
    //设置线条长度为10px,空白值为5px,线条长度为20px,空白值为30px,线条长度为40px,空白值为50px
    ctx.setLineDash([10, 5, 20, 30, 40, 50]);
    ctx.moveTo(10, 70);
    ctx.lineTo(290, 70);
    ctx.stroke();
    </script>
    </body>

    fill(填充)

    • 使用 fill() 可以填充图形
    • 可以使用 fillStyle 设置填充颜色,默认是黑色。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <canvas id="c" width="300" height="300" style="border: 1px solid #ccc;"></canvas>

    <script>
    const cnv = document.getElementById('c')
    const cxt = cnv.getContext('2d')

    cxt.fillStyle = 'pink'

    cxt.rect(50, 50, 200, 100)

    cxt.fill()
    </script>

    非零环绕填充

    • 如果需要判断某一个区域是否需要填充颜色. 就从该区域中随机的选取一个点。从这个点拉一条直线出来, 一定要拉到图形的外面. 此时以该点为圆心。看穿过拉出的直线的线段. 如果是顺时针方向就记为 +1, 如果是 逆时针方向,就记为 -1. 最终看求和的结果. 如果是 0 就不填充. 如果是 非零 就填充(注意是非0,而不是负数)

    • 代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <body>
    <canvas id="canvas" width="300" height="300" style="border: 1px solid red;"></canvas>
    <script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    ctx.moveTo(100, 100)
    ctx.lineTo(300, 100)
    ctx.lineTo(300, 300)
    ctx.lineTo(100, 300)
    ctx.closePath()

    //内部的
    ctx.moveTo(150, 150)
    ctx.lineTo(150, 250)
    ctx.lineTo(250, 250)
    ctx.lineTo(250, 150)
    ctx.closePath()
    ctx.fill();
    </script>
    </body>

    • 大的正方形绘制的方向是顺时针,小的正发形绘制的方向是逆时针(因为没有调用beginPath())
    • 小的从内部出来一根线,自身为-1,外界为1相加为0,所以不填充,而大的从内部出来一根线,自身为1,无相交,相加为1,所以填充

    • 可以看下面图像
      • 1处:出来一条线,顺时针,没有相交,相加为1,所以填充了颜色
      • 2处:出来2条线,逆时针,相加-2,不为0,所以填充
      • 3处:出来2条线,-1+1等于0,为0,所以不填充

    文本

    strokeText()描边文本和设置文本样式

    • CSS 设置 font 差不多,Canvas 也可以通过 font 设置样式。
    1
    cxt.font = 'font-style font-variant font-weight font-size/line-height font-family'
    1
    2
    3
    如果需要设置字号 font-size,需要同时设置 font-family

    cxt.font = '30px 宋体'
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <body>
    <canvas id="canvas" width="300" height="300" style="border: 1px solid red;"></canvas>
    <script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    ctx.font = '60px 宋体';
    ctx.strokeText("你好,世界", 10, 100);
    </script>
    </body>

    • 当然,你也可以设置描边颜色strokeStyle
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <body>
    <canvas id="canvas" width="300" height="300" style="border: 1px solid red;"></canvas>
    <script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    ctx.font = '60px 宋体';
    ctx.strokeStyle = 'blue';
    ctx.strokeText("你好,世界", 10, 100);
    </script>
    </body>

    fillText-填充文本和fillStyle-填充颜色

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <body>
    <canvas id="canvas" width="300" height="300" style="border: 1px solid red;"></canvas>
    <script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    ctx.font = '60px 宋体';
    ctx.strokeStyle = 'blue';
    // ctx.strokeText("你好,世界", 10, 100);
    ctx.fillStyle = 'red';
    ctx.fillText('你好,世界', 10, 100);
    </script>
    </body>

    measureText() - 获取文本信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <body>
    <canvas id="canvas" width="300" height="300" style="border: 1px solid red;"></canvas>
    <script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    ctx.font = '60px 宋体';
    ctx.strokeStyle = 'blue';
    // ctx.strokeText("你好,世界", 10, 100);
    ctx.fillStyle = 'red';
    let text = '你好,世界';
    ctx.fillText(text, 10, 100);
    console.log(ctx.measureText(text));
    </script>
    </body>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    {
    "actualBoundingBoxAscent": 49,
    "actualBoundingBoxDescent": 7,
    "actualBoundingBoxLeft": -2,
    "actualBoundingBoxRight": 268,
    "alphabeticBaseline": 0,
    "fontBoundingBoxAscent": 52,
    "fontBoundingBoxDescent": 8,
    "hangingBaseline": 41.6,
    "ideographicBaseline": -8,
    "width": 270
    }

    textAlign 水平对齐方式

    使用 textAlign 属性可以设置文字的水平对齐方式,一共有5个值可选

    • start: 默认。在指定位置的横坐标开始。
    • end: 在指定坐标的横坐标结束。
    • left: 左对齐。
    • right: 右对齐。
    • center: 居中对齐。
    • 从上面的例子看,startleft 的效果好像是一样的,endright 也好像是一样的。
    • 在大多数情况下,它们的确一样。但在某些国家或者某些场合(比如阿拉伯),阅读文字的习惯是 从右往左 时,start 就和 right 一样了,endleft 也一样。这是需要注意的地方。

    textBaseline 垂直对齐方式

    • 使用 textBaseline 属性可以设置文字的垂直对齐方式。

    • 在使用 textBaseline 前,需要自行了解 css 的文本基线。

    • textBaseline 可选属性:
      • alphabetic: 默认。文本基线是普通的字母基线。
      • top: 文本基线是 em 方框的顶端。
      • bottom: 文本基线是 em 方框的底端。
      • middle: 文本基线是 em 方框的正中。
      • hanging: 文本基线是悬挂基线。

    drawImage-渲染图片

    • 渲染图片的方式有2中,一种是在JS里加载图片再渲染,另一种是把DOM里的图片拿到 canvas 里渲染
    1
    drawImage(image,dx,dy,dw,dh);
    • image: 要渲染的图片对象。
    • dx: image 的左上角在目标画布上 X 轴坐标
    • dy: image 的左上角在目标画布上 Y 轴坐标。
    • dw 用来定义图片的宽度。(不填则默认图片宽度)
    • dh 定义图片的高度。(不填则默认图片高度)

    js方式

    • JS 里加载图片并渲染,有以下几个步骤:
    1. 创建 Image 对象
    2. 引入图片
    3. 等待图片加载完成(必须)
    4. 使用 drawImage() 方法渲染图片
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <body>
    <canvas id="canvas" width="800" height="500" style="border: 1px solid red;"></canvas>
    <script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    const image = new Image();
    image.src = 'https://s2.loli.net/2024/03/08/FmvSfs5TeZh4Bcq.png';
    image.onload = () => {
    //等待图片加载完成
    ctx.drawImage(image,30,30)
    }
    </script>
    </body>

    DOM方式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <body>
    <img src="https://s2.loli.net/2024/03/08/FmvSfs5TeZh4Bcq.png" id="cimg"/>
    <canvas id="canvas" width="800" height="500" style="border: 1px solid red;"></canvas>
    <script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    const cimgDOM = document.getElementById('cimg');
    ctx.drawImage(cimgDOM,30,30)
    </script>
    </body>

    设置图片宽高

    1
    drawImage(image, dx, dy, dw, dh)

    image、 dx、 dy 的用法和前面一样。

    dw 用来定义图片的宽度,dh 定义图片的高度。

    截取图片

    • 又多了参数..
    1
    drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh)
    • image: 图片对象
    • dx: 开始截取的横坐标
    • dy: 开始截取的纵坐标
    • dw: 截取的宽度
    • dh: 截取的高度
    • sx: 图片左上角的横坐标位置
    • sy: 图片左上角的纵坐标位置
    • sw: 图片宽度
    • sh: 图片高度
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <body>
    <canvas id="canvas" width="400" height="400" style="border: 1px solid red;"></canvas>
    <script>
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    const image = new Image();
    image.src = 'https://s2.loli.net/2024/03/08/FmvSfs5TeZh4Bcq.png';
    image.onload = () => {
    //从图像的 (10,10) 位置开始剪切,剪切的大小为 120x300,然后在画布的 (20,30) 位置放置图像,缩放图像的大小为 100x200。
    ctx.drawImage(image, 10,10,120,300,20,30,100,200)
    }
    </script>
    </body>

    1
    2
    3
    4
    5
    6
    7
    const canvas = document.getElementById("canvas");
    const ctx = canvas.getContext("2d");
    const image = document.getElementById("source");

    image.addEventListener("load", (e) => {
    ctx.drawImage(image, 33, 71, 104, 124, 21, 20, 87, 104);
    });

    使用html2canvas

    • 很多情况下我们需要动态生成分享图片,很多情况下我们使用的是这个html2canvas的库

    图片为空白

    • 空白大部分情况是下面几种原因

      • 跨域
        • 解决,后端解决
      • 使用的是网络图片
        • 解决办法看下面
      • 图片未加载完成就调用了方法
        • 解决,等待图片加载完成后调用
      • 滚动条的一些问题啥的
        • 解决:略
    • 一般情况下,如果是本地引入的图片,不依赖于网络,是可以正常加载的

    • 但是大部分的时候,我们使用的图片都是网络图片,也就是http或者https开头的图片,会出现图片为空白的情况

    • 也就是将proxy设置为和图片一样的地址

    终极解决-后端设置允许跨域

    • 后端开启跨域,然后前端html2canvas配置参数中的useCORS设置为true,或者你可以试试看html2canvas配置项的proxy功能

      • 如果是需要使用html2canvas的话,必须要后端设置允许跨域

      • 下面代码图床设置为了跨域,所以canvas渲染没问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <body>
    <div id="main" style="width: 500px;height: 500px;display: flex;border: 1px solid red;">
    <img style="width: 90%;height: 90%;" src="https://oss.dreamlove.top/i/2024/03/09/hg7kn6.jpg" />
    <div style="font-size: 20px;">大家好,我是文字</div>
    </div>
    <button id="clickme">点击我</button>
    <script type="module">
    import html2canvas from 'https://cdn.bootcdn.net/ajax/libs/html2canvas/1.4.1/html2canvas.esm.min.js';
    document.getElementById('clickme').addEventListener('click', () => {
    html2canvas(document.querySelector('#main'),{
    useCORS: true // 【重要】开启跨域配置
    }).then(function (canvas) {
    document.body.append(canvas)
    });
    })
    </script>
    </body>

    练习

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69

    <body>
    <div id="wrapper"
    style="position: relative;width: 600px;height: 500px;background-color: red;background-image: url('./image/bg.jpg');">
    <span id="time"
    style="color: blue;position: absolute;bottom: 0;font-size: 30px;left: 50%;transform: translateX(-50%);"></span>
    </div>
    <button id="btnDown">下载</button>
    <script type="module">
    import html2canvas from "https://cdn.bootcdn.net/ajax/libs/html2canvas/1.4.1/html2canvas.esm.min.js";
    function dataURLtoBlob(dataurl) {
    let arr = dataurl.split(','),
    mime = arr[0].match(/:(.*?);/)[1],
    bstr = atob(arr[1]),
    n = bstr.length,
    u8arr = new Uint8Array(n)
    while (n--) {
    u8arr[n] = bstr.charCodeAt(n)
    }
    return new Blob([u8arr], { type: mime })
    }
    function downFile (url) {
    const a = document.createElement('a');
    a.style.display = 'none';
    a.download = 'xx';
    a.href = url;
    document.body.appendChild(a);
    a.click();
    document.body.removeChild(a);
    /*
    * download: HTML5新增的属性
    * url: 属性的地址必须是非跨域的地址
    */
    };
    window.onload = () => {
    const timeDOM = document.querySelector('#time');
    timeDOM.textContent = new Date().toLocaleString();
    }

    document.querySelector('#btnDown').addEventListener('click', () => {
    const shareContent = document.getElementById('wrapper');//需要截图的包裹的(原生的)DOM 对象
    const width = shareContent.offsetWidth; //获取dom 宽度
    const height = shareContent.offsetHeight; //获取dom 高度
    const canvas = document.createElement("canvas"); //创建一个canvas节点

    const scale = 1; //定义任意放大倍数 支持小数
    canvas.width = width * scale; //定义canvas 宽度 * 缩放
    canvas.height = height * scale; //定义canvas高度 *缩放
    canvas.getContext("2d").scale(scale, scale); //获取context,设置scale

    // var rect = shareContent.getBoundingClientRect();//获取元素相对于视察的偏移量
    // canvas.getContext("2d").translate(-rect.left,-rect.top);//设置context位置,值为相对于视窗的偏移量负值,让图片复位
    const opts = {
    scale: scale, // 添加的scale 参数
    canvas: canvas, //自定义 canvas
    logging: true, //日志开关
    width: width, //dom 原始宽度
    height: height, //dom 原始高度
    backgroundColor: 'transparent',
    };
    html2canvas(shareContent, opts).then((canvas) => {
    const base64 = canvas.toDataURL();
    const blob = dataURLtoBlob(base64)
    const href = window.URL.createObjectURL(blob)
    downFile(href,'test.png')
    })
    })
    </script>
    </body>

    动态生成分享图片

    微信小程序生成-使用snapshot绘制

    • wxml
    1
    2
    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
    <navigation-bar title="Weixin" back="{{false}}" color="black" background="#FFF"></navigation-bar>
    <van-popup show="{{ show }}" bind:close="onClose">
    <snapshot class="share" id="downloadWrapper">
    <!-- 用户基本信息 -->
    <view class="share_info">
    <image class="avatar" src="{{info.avatar}}" mode="aspectFill"></image>
    <view class="desc">
    <view class="name">{{info.name}}</view>
    <view class="text">{{info.description}}</view>
    </view>
    </view>
    <!-- 分享背景 -->
    <view class="share_bg">
    <image class="pic" src="{{info.bgURL}}" mode="aspectFill"></image>
    </view>
    <!-- 二维码和价格 -->
    <view class="share_code">
    <view class="price">{{'$' + info.price}}</view>
    <view class="code">
    <image class="pic" src="{{info.codeURL}}" mode="aspectFill"></image>
    </view>
    </view>
    </snapshot>
    <view style="text-align:center;">
    <van-button type="primary" bind:tap="handleDownload">点击下载</van-button>
    </view>
    </van-popup>
    <van-button type="primary" bind:click="showPopup">点击我生成海报</van-button>
    • index.js
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    const app = getApp()

    Page({
    data: {
    show:false,
    info:{
    name: "梦洁",//用户名称
    description: "给你推荐了一个好东西",//描述
    avatar: "https://s2.loli.net/2024/03/09/8tey3JKxCIpg4c7.png",//头像
    codeURL: "https://s2.loli.net/2024/03/09/924jbZViXRUngOh.png",//二维码
    bgURL: "https://s2.loli.net/2024/03/09/QhupvOzgwGmcY58.jpg",//背景图
    price: "29.99"
    }
    },
    onLoad() {
    console.log('代码片段是一种迷你、可分享的小程序或小游戏项目,可用于分享小程序和小游戏的开发经验、展示组件和 API 的使用、复现开发问题和 Bug 等。可点击以下链接查看代码片段的详细文档:')
    console.log('https://developers.weixin.qq.com/miniprogram/dev/devtools/minicode.html')
    },
    showPopup() {
    this.setData({ show: true });
    },

    onClose() {
    this.setData({ show: false });
    },
    //点击下载
    handleDownload(){
    this.createSelectorQuery().select('#downloadWrapper').node().exec(res => {
    const node = res[0].node;
    //保存海报
    node.takeSnapshot({
    type:'arraybuffer',
    format:"png",
    success:(res) => {
    //不让背景透明,就简单点改了下扩展名
    const filePath = `${wx.env.USER_DATA_PATH}/生成的图片${Math.random()}.jpg`
    const fs = wx.getFileSystemManager();

    //将海报数据写入本地文件
    fs.writeFileSync(filePath,res.data,'binary')

    //保存到本地
    wx.saveImageToPhotosAlbum({
    filePath,
    })
    },
    error:(e) => {
    console.log(`出错了${e}`);
    }
    })
    })
    }
    })

    • index.wxss
      • 样式就将就点吧
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    /* page {
    display: flex;
    flex-direction: column;
    height: 100vh;
    } */

    .scroll-area {
    flex: 1;
    overflow-y: hidden;
    }

    .intro {
    padding: 30rpx;
    text-align: center;
    }

    .share {
    border: 1rpx solid red;
    width: 100vw;
    height: 60vh;
    display: flex;
    flex-direction: column;
    }

    .share_info {
    display: flex;
    align-items: center;
    }

    .avatar {
    width: 80rpx;
    height: 80rpx;
    border-radius: 50%;
    }

    .desc {
    font-size: 32rpx;
    margin-left: 40rpx;
    flex: 1;
    }

    .name {
    font-weight: bold;
    }

    .text {
    color: gray;
    }

    .share_bg {
    flex: 1;
    }

    .share_code {
    display: flex;
    align-items: center;
    justify-content: space-between;
    }

    .code {
    text-align: right;
    }

    .code .pic {
    width: 160rpx;
    height: 160rpx;
    }

    使用html2canvas进行转图片

    • 先写好html代码结构
    • 必须要允许跨域
    • 以react函数式组件为例
    • 主入口
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import React, {  useState } from "react";
    import { Button } from "@mui/material";
    import Share from "./component/share";
    const Index = () => {
    const [open,setOpen] = useState(false);
    return (
    <div>
    <Button onClick={() => setOpen(true)}>点击我分享</Button>
    {/* 分享组件 */}
    { open && <Share close={() => setOpen(false)}/> }
    </div>
    );
    };

    export default Index;

    • share.jsx组件
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    import React, { useRef, useState } from "react";
    import { Button, Modal } from 'antd';
    import html2canvas from 'html2canvas';
    import "./share.less";
    const Share = ({ close }) => {
    const [info, setInfo] = useState({
    name: "梦洁",//用户名称
    description: "给你推荐了一个好东西",//描述
    avatar: "https://s2.loli.net/2024/03/09/8tey3JKxCIpg4c7.png",//头像
    codeURL: "https://s2.loli.net/2024/03/09/924jbZViXRUngOh.png",//二维码
    bgURL: "https://s2.loli.net/2024/03/09/QhupvOzgwGmcY58.jpg",//背景图
    price: "29.99"
    });
    const wrapperRef = useRef();
    //点击下载
    const handleDownload = () => {
    html2canvas(wrapperRef.current,{
    useCORS:true,//确保可以下载网络图片
    }).then((canvas) => {
    canvas.toBlob((data) => {
    const url = URL.createObjectURL(data);
    const ADOM = document.createElement("a");
    ADOM.href = url;
    ADOM.style.display = "none";
    ADOM.download = "";//避免在当前窗口打开
    document.body.appendChild(ADOM);
    ADOM.click();
    document.body.removeChild(ADOM);
    });

    })
    }
    return (
    <Modal width={"375px"} open={true} footer={null} onCancel={close}>
    <div ref={wrapperRef} className="share">
    {/* 用户基本信息 */}
    <div className="share_info">
    <img className="avatar" src={info.avatar} alt="" />
    <div className="desc">
    <div className="name">{info.name}</div>
    <div className="text">{info.description}</div>
    </div>
    </div>
    {/* 分享背景 */}
    <div className="share_bg">
    <img alt="" src={info.bgURL} />
    </div>
    {/* 二维码和价格 */}
    <div className="share_code">
    <div className="price">{ "$" + info.price }</div>
    <div className='code'>
    <img alt='' src={info.codeURL}/>
    </div>
    </div>
    </div>
    <div style={{textAlign:'center'}}>
    <Button type={'primary'} onClick={handleDownload}>点击下载</Button>
    </div>
    </Modal>
    );
    };

    export default Share;

    • 这里为了方便截图,就用手机端进行操作了

    基础知识点

    • 如果不在 canvas 上设置宽高,那 canvas 元素的默认宽度是300px,默认高度是150px。
    • 线条的默认宽度是 1px ,默认颜色是黑色。
      • 但由于默认情况下 canvas 会将线条的中心点和像素的底部对齐,所以会导致显示效果是 2px 和非纯黑色问题。
    • IE兼容问题
      • 暂时只有 IE 9 以上才支持 canvas 。但好消息是 IE 已经有自己的墓碑了。
      • 如需兼容 IE 7 和 8 ,可以使用 ExplorerCanvas 。但即使是使用了 ExplorerCanvas 仍然会有所限制,比如无法使用 fillText() 方法等。
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/4f2816f0.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    avatar
    梦洁
    小小的字,有大大的梦想~分享我的前端学习过程,经历,错误,和一些其他折腾过程
    关注下我(* ̄▽ ̄*)
    公告
    不断更新中,有问题请留言回复(会通过邮箱提醒~)
    \ No newline at end of file diff --git a/4f8d5179.html b/4f8d5179.html new file mode 100644 index 000000000..288073b38 --- /dev/null +++ b/4f8d5179.html @@ -0,0 +1 @@ +移动端适配vue小练习 | 梦洁小站-属于你我的小天地

    移动端适配vue小练习

    移动端Vue适配小练习

    • 为了练习,就没有使用自动转换rem的功能,想了解的可以看看这几位博主的
    • 想看源码的可以到github或者gitee上下载(后台也打包好了)

    项目遇到的问题记录

    解决方法:

    vue_project\src\router\index.js 路由主入口文件当中添加如下代码

    1
    2
    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

    const originalPush = VueRouter.prototype.push
    //解决重复提交相同链接报错
    VueRouter.prototype.push = function push(location, onResolve, onReject) {
    if (onResolve || onReject)
    return originalPush.call(this, location, onResolve, onReject)
    return originalPush.call(this, location).catch((err) => {
    if (VueRouter.isNavigationFailure(err)) {
    // resolve err
    return err
    }
    // rethrow error
    return Promise.reject(err)
    })
    }
    const originalReplace = VueRouter.prototype.replace
    VueRouter.prototype.replace = function replace(location, onResolve, onReject) {
    if (onResolve || onReject){
    //回调函数里面会用到this的指向,所以就要使用call
    return originalReplace.call(this, location, onResolve, onReject)
    }
    return originalReplace.call(this, location).catch((err) => {
    if (VueRouter.isNavigationFailure(err)) {
    //如果为相同链接引发的错误,返回错误原因,promise状态为resolve
    // resolve err
    return err
    }
    // rethrow error
    return Promise.reject(err)
    })
    }

    配置代理问题

    由于视频的是react,所以配置代理花了一点时间

    vue.config.js当中,主要是proxy的配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    const { defineConfig } = require('@vue/cli-service')
    module.exports = defineConfig({
    lintOnSave:false,
    transpileDependencies: true,
    devServer:{
    proxy:{
    "/dev":{
    //转发
    target:"http://localhost:5000",
    changeOrigin:true,
    //重写,删除/dev前缀
    pathRewrite:{
    "^/dev":"",
    }
    }
    }
    }
    })

    注意:这样子写的话所有api请求都需要戴上/dev前缀了,所以一般都是对axios进行二次封装,下面是我的二次封装

    1
    2
    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
    import axios from "axios";
    import nprogress from "nprogress"
    import "nprogress/nprogress.css"

    //创建一个axios的实例化对象
    //传入配置对象
    const service = axios.create({
    //每一个ajax请求都添加/dev前缀
    //比如http://localhost:8080/dev/login
    //http://localhost:8080/dev/home
    baseURL:"/dev",
    //请求超时时间
    timeout: 2000
    });
    //请求拦截器
    service.interceptors.request.use((config) => {
    //进度显示(当然,nprogress是假进度条)
    nprogress.start();
    return config;
    })

    //响应拦截器
    service.interceptors.response.use((response) => {
    nprogress.done();
    return response.data || response;
    }, (error) => {
    nprogress.done();
    console.log("未知错误!");
    return new Promise(() => {});
    })

    export default service;

    OAuth认证

    OAuth2.0

    • OAuth 2.0 是目前最流行的授权机制,用来授权第三方应用,获取用户数据。
    • 简单说,OAuth 就是一种授权机制。数据的所有者同意其他应用使用自己存储的用户信息。

    授权流程(以GitHub为例)

    • GitHub官方文档

    • 开发流程介绍

      • 从A 网站跳转到 GitHub授权页面。
      • GitHub 要求校验用户信息,引导用户登录。
      • GitHub 询问”A 网站要求获得你的xx数据,你是否同意?”
      • 用户同意,GitHub 就会重定向到A网站对应的服务器,同时发回一个授权码。
      • A网站服务器使用授权码,向 GitHub 请求令牌。
      • GitHub 返回令牌token. A网站服务器使用令牌,向 GitHub 请求用户数据。
    • 应用登记

      • 一个应用要 OAuth 授权,必须先到对方网站登记,让对方知道是谁在请求。

    使用GitHub授权

    1.GitHub登记应用

    登记地址:https://github.com/settings/applications/new

    登记

    2.获得client_id

    查看地址:https://github.com/settings/developers

    client_id

    3.获得Client secrets

    获得Client secrets

    4.配置

    4.1 前台项目准备好个人中心组件,供授权成功后查看

    4.2 将得到的 client_id 、clinet_secret配置到服务器config\index.js中,随后重启服务器。

    1
    2
    3
    // github oauth
    const CLIENT_ID = "xxxxxxxxxxxxxxx";
    const CLIENT_SECRET = "xxxxxxxxxxxxxxx";

    如图,server配置

    4.3 配置前端请求地址,这里就不写了

    4.4 项目中携带网站标识跳转到授权页

    1
    2
    3
    loginGithub = ()=>{
    window.location.href = AUTH_BASE_URL+'?client_id='+CLIENT_ID
    }

    项目启动

    1.电脑安装好MongoDB后启动

    2.server路径下cmd打开,输入npm start

    server启动

    3.项目启动 ,项目路径下cmd打开,输入npm run serve后浏览器进入~

    项目的展示

    主页登录

    主页登录

    获取验证码

    获取验证码

    个人中心

    个人中心

    github关联

    github关联

    关联后

    关联后

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/4f8d5179.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/50575b3e.html b/50575b3e.html new file mode 100644 index 000000000..70a2ca5f3 --- /dev/null +++ b/50575b3e.html @@ -0,0 +1 @@ +独角数卡,打开商品列表出现Undefined variable form的解决办法 | 梦洁小站-属于你我的小天地

    独角数卡,打开商品列表出现Undefined variable form的解决办法

    前言

    • 独角数卡,打开商品列表出现了ErrorException In GoodsController.php line 95 : Undefined variable: form
    1
    2
    3
    4
    5
    6
    7
    8
    9
    ErrorException In GoodsController.php line 95 : Undefined variable: form


    #0 app/Admin/Controllers/GoodsController.php(95): Illuminate/Foundation/Bootstrap/HandleExceptions->handleError()
    #1 [internal function]: App/Admin/Controllers/GoodsController->App/Admin/Controllers/{closure}()
    #2 vendor/dcat/laravel-admin/src/Show.php(673): call_user_func()
    #3 vendor/dcat/laravel-admin/src/Support/Helper.php(105): Dcat/Admin/Show->render()
    #4 vendor/dcat/laravel-admin/src/Layout/Column.php(100): Dcat/Admin/Support/Helper::render()
    #5 vendor/dcat/laravel-admin/src/Layout/Row.php(75): Dcat/Admin/Layout/Column->render()
    • 错误如下

    解决办法

    • 更改如下

    • 然后就解决了

    • 看来还是要多看看pull request啊,
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/50575b3e.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/50b2d4ac.html b/50b2d4ac.html new file mode 100644 index 000000000..4e6299c22 --- /dev/null +++ b/50b2d4ac.html @@ -0,0 +1 @@ +前端进度条和进度条流光效果 | 梦洁小站-属于你我的小天地

    前端进度条和进度条流光效果

    前言

    1
    2
    3
    4
    5
    6
    7
    8
    9
    80 <= --percent <= 100 绿色(green)

    60 <= --percent <80 蓝色(blue)

    40 <= --percent < 60 紫色(purple)

    20 <= --percent < 40 桔色(orange)

    0 <= --percent < 20 红色(red)
    • 效果差不多这样子

    有话要说

    • 差不多是这样子吧,设置多个background-imagebackground-size,并设置不同的宽度,

    • 我们挑选2个来看,设置宽度看看,一个设置1%,一个设置10%,可以看到,桔色在红色的后面,红色领先了

    • 我们设置桔色宽度高一点,看看会怎么样,桔色设置了15%,可以看到,红色被覆盖了

    那么为什么–percent:60的时候是紫色?

    • 我们先明白下,background-image出现了多个,并且也设置了每一个background-size的大小,并且每一个大小都乘了100%,那么就确保了当用户输入多少–percent,上面一层的值一定会覆盖掉下面一层
      • 比如,当中–percent:1的时候,上面计算浏览器渲染宽度都是为0,但是最底层红色的宽度为100%就会渲染为红色,只有但指定–percent达到设定的颜色阶度的时候,就才会覆盖掉与之对应颜色的下面一层
      • 精妙就在这里,犹豫图层不是被覆盖的关系,而是折叠的关系,这就需要我们上一层一定要大于下一层才会覆盖掉下一层的颜色,所以不管你取什么值,在对应阶层下才会出现正数(且比是大于1的)

    • 那么我们再来看看–percent:61的时候为什么是蓝色

    流光效果

    • 就像下面总而言之,有一个流光效果

    • 添加下面代码就可以了
      • 来自antd的keyframes
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    .bar_shiny {
    position: absolute;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    background-color: #fff;
    border-radius: 10px;
    opacity: 0;
    animation: abc 2.4s cubic-bezier(0.075, 0.82, 0.165, 1) infinite;
    }
    @keyframes abc {
    0% {
    transform: translateX(-100%) scaleX(0);
    opacity: 0.1;
    }
    20% {
    //你设置translateX(0)也可以,只要不是正数
    transform: translateX(-100%) scaleX(0);
    opacity: 0.5;
    }
    100% {
    transform: translateX(0) scaleX(1);
    opacity: 0;
    }
    }
    • 注意

      • 流光的div一层,不可以设置transformX的值为正数,否则会出现滚动条(横向)
    • 貌似这样子也可以

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    @keyframes abc {
    0% {
    transform: translateX(-100%) ;
    opacity: 0.5;
    }
    100% {
    transform: translateX(0) ;
    opacity: 0;
    }
    }

    注意点

    • background-size当中负数是不被浏览器支持的,出现了负数为什么还可以,那是浏览器自动解决了的
    • 如果一个关键帧规则没有指定动画的开始或结束状态(也就是,0%/from100%/to,浏览器将使用元素的现有样式作为起始/结束状态。这可以用来从初始状态开始元素动画,最终返回初始状态。
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/50b2d4ac.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/50c7a259.html b/50c7a259.html new file mode 100644 index 000000000..b31349473 --- /dev/null +++ b/50c7a259.html @@ -0,0 +1 @@ +我来图书馆实现用云函数cfc进行自动化抢位置 | 梦洁小站-属于你我的小天地

    我来图书馆实现用云函数cfc进行自动化抢位置

    window工具自动抢位置和签到

    前言

    • 抓包的话就不多说啦

    • 只实现四楼自动抢,其他楼自己改改就可以

    • 先下载下代码,单击我进入跳转下载

    • 下载代码后的操作

      下载zip格式

    • 下载完成后,将其解压后重新在目录内压缩

    下载完成后,将其解压后重新在目录内压缩

    具体操作步骤

    单击体验

    • 再单击创建函数

    单击创建函数

    • 选择空白函数后单击’下一步’

    选择空白函数后单击'下一步'

    • 按照如图选择

    按照如图选择

    • 单击提交,创建完成

    创建完成

    • 单击**’进入函数详情页’**

    单击进入函数详情页

    • 单击’函数代码’

    单击'函数代码'

    • 下载刚刚下载的代码
    • 单击’上传代码.zip’

    单击'上传代码.zip'

    • 选择刚刚下载的zip

    选择自己重新压缩过的

    • 选择好后单击开始上传

    选择好后单击开始上传

    • 切换回’在线编辑’

    切换回'在线编辑'

    • 单击编辑进入环境变量设置

    单击编辑进入环境变量设置

    • 添加这二个环境变量 ,单击保存
    1
    2
    3
    4
    5
    6
    runLibraryUser 自己抓包的用户代码	
    runLibraryUserSeatIdAndArea 座位号id&区域id 具体看github的文档

    比如
    runLibraryUser rjwiaorjawrijoawr
    runLibraryUserSeatIdAndArea 561&24

    添加环境变量

    • 单击’触发器’,增加触发器

    单击'触发器',增加触发器

    • 添加完成
      • cron(30 14 * * ?)

    添加完成

    添加完成

    • 后面就自动22:30预约了

    可以在这里看预约日志

    可以在这里看预约日志

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/50c7a259.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/53412998.html b/53412998.html new file mode 100644 index 000000000..6329a8f6a --- /dev/null +++ b/53412998.html @@ -0,0 +1 @@ +flex1和auto区别-好记性不如烂笔头 | 梦洁小站-属于你我的小天地

    flex1和auto区别-好记性不如烂笔头

    好记性不如烂笔头

    区别

    • 完全等分布局使用flex:1
    • 根据内容宽度动态分配宽度使用flex:auto

    示例

    • flex:1时候的样子

    • flex:auto时候的样子

    1
    2
    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
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>flex:1和flex:auto</title>
    <style>
    .content{
    font-size: 12px;
    display: flex;
    width: 300px;
    height: 50px;
    border: 1px solid red;
    }
    .content .item{
    box-sizing: border-box;
    /* flex: 1;等分布局 */
    flex: auto;/* 根据内容动态分配 */
    border-right: 2px solid blue;
    }
    </style>
    </head>
    <body>
    <div class="content">
    <div class="item">好好吃吃的零食</div>
    <div class="item">不好吃的</div>
    <div class="item">这个零食好好吃,绝绝子,吱吱吱</div>
    <div class="item">好吃</div>
    </div>
    </body>
    </html>

    参考文章

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/53412998.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/5391ecf8.html b/5391ecf8.html new file mode 100644 index 000000000..1825b4932 --- /dev/null +++ b/5391ecf8.html @@ -0,0 +1 @@ +js的promise的究竟是同步还是异步的问题和promise.all可以同时请求多个接口是错误的回答的原因 | 梦洁小站-属于你我的小天地

    js的promise的究竟是同步还是异步的问题和promise.all可以同时请求多个接口是错误的回答的原因

    • 个人理解

    前景-代码输出结果是什么

    我们都知道,循环队列的时候,同步任务大于异步任务(异步任务里面的微任务又大于宏任务),那么你猜猜这个代码输出结果是 什么呢

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <script>
    setTimeout(() => {
    console.log(1);
    }, 0);

    new Promise(function(resolve,reject){
    console.log(2);
    resolve();
    console.log(3);
    }).then(function(){
    console.log(4);
    }).finally(function(){
    console.log(5);
    });
    console.log(6);
    </script>
    • 做出来了吗?我公布下答
    • 答案
    1
    2 3 6 4 5 1 

    前景解析

    • 首先,你知道同步>异步,可是可能不太会区分同步和异步,其实记住下面的异步任务就好,其他就都是同步任务了

    • 异步任务:setTimeout,setInterval,setImmediate(非标准),I/O,UI rendering,Promise

    • 你们肯定也知道,promise的优先级大于其他异步任务,因为Promise是微任务嘛

    • 但是呢,这里说promise是异步任务其实不完全对,因为创建promise的时候是同步执行的,nextTick才是异步执行的(也就是.then,.catch这种方法),下面的例子和前景例子很好的说明了这个情况

      1
      2
      3
      4
      5
      6
      7
      8
      new Promise(resolve=>{
      console.log(1);
      resolve(3);
      }).then(num=>{
      console.log(num);
      });
      console.log(2);
      //依次为 123

    我们学习了上面的点,再来看看Promise.all方法

    • 在之前的时候,我一直想不明白,Promise.all方法为什么能做到将多个promise封装成为一个新的promise实例,并同步获取到结果,现在才知道原因,先看下面代码,后面再详细解释
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
      <script src="https://cdn.bootcdn.net/ajax/libs/axios/1.1.3/axios.min.js"></script>
    <script>
    let work1 = new Promise((resolve,reject)=>{
    resolve(axios.get('https://api.oick.cn/dutang/api.php'))
    });

    let work2 = new Promise((resolve,reject)=>{
    resolve(axios.get('https://api.oick.cn/yiyan/api.php'))
    });

    let work3 = new Promise((resolve,reject)=>{
    resolve(axios.get('https://api.oick.cn/dog/api.php'))
    });
    Promise.all([work1,work2,work3]).then(res=>{
    console.log(res);
    })
    </script>
    • 看着上面的代码,我一直在想,这为什么可以做到并发,我想想中的并发是这个请求同一时间发送,但是我搜来搜去,Promise.all 可以同时请求多个接口,这句话是错误的~我后面也调试发现并不是在同一时间发送ajax请求的,而是顺序发送,最后统一处理返回的结果

    • 我们知道,js是单线程的,所以分为同步和异步,我们创建promise的时候是同步执行的,nextTick才是异步执行的,所以Promise里面的函数是同步任务

    1
    2
    3
    4
    5
    6
    7
    8
    9
    let work1 = new Promise((resolve,reject)=>{
    //同步任务
    resolve(axios.get('https://api.oick.cn/dutang/api.php'))
    });

    let work2 = new Promise((resolve,reject)=>{
    //同步任务
    resolve(axios.get('https://api.oick.cn/yiyan/api.php'))
    });
    • 所以在没有调用promise.all之前,ajax请求已经被发送了,只不过在promise.all的.then才统一接收处理

    可以看到,如果是统一时间发送的,我在调试的时候应该说Promise.all才执行ajax发送请求,但是我在经过let work2的时候停顿一会后才执行let work3处的代码**,然后才准备执行**promise.all方法, 就会发现,前几天的数据请求状态为200了,也是在promise.all之前就发送了这个请求,而不是说执行promise.all这代码的时候才发送ajax请求

    • 等待了很久后,终止调试,发现正常输出返回的结果,而没有超时,所以就说明不是在promise.all才发送请求,而是我们在promise.all做最终处理
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/5391ecf8.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/53b29abd.html b/53b29abd.html new file mode 100644 index 000000000..d1d98b594 --- /dev/null +++ b/53b29abd.html @@ -0,0 +1 @@ +antd3和dva-自定义组件初始化值的操作演示和自定义组件校验 | 梦洁小站-属于你我的小天地

    antd3和dva-自定义组件初始化值的操作演示和自定义组件校验

    前言

    • 在antd3 (react)版和dva下,好像有的项目使用的是getFieldDecorator来获取表单的值的,现在就遇到了一个问题,getFieldDecorator针对antd自带的组件实现效果很好,除去一个form.item只能有一个getFieldDecorator的限制,其他都很好用,但是假如是自定义组件或者说在getFieldDecorator当中遇上了全选操作,就会有问题(目前我是这样子的)

    • 演示代码地址:

    • 演示效果

    实现

    • 实现的关键其实就是调用getFieldDecorator修饰过后自动向组件传递的onChange事情来传递值,如果需要初始化值操作,可以借助initialValue或者使用 setFieldsValue 来动态设置其他控件的值。
      • initialValue: 初始化值操作,但是如果传入的是一个变量,后期变量更新,组件收到的值也不会变化
        • 比如通过initialValue传入了变量a,值为100,那么后面a的值变为了200,那么组件收到的值依旧是100
      • setFieldsValue: 设置一组输入控件的值(注意:不要在 componentWillReceiveProps 内使用,否则会导致死循环,原因

    (可能会出现)自定义组件校验的问题

    • 触发自定义校验后,点击注册按钮,触发表单验证,

    • 问题原因:value存在值为undefind的时刻,会导致表单验证不正常
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    //之前的问题代码
    const validateLimit = (rule, value, callback) => {
    console.log('自定义检验', value)
    if (value.length > 2) {
    callback('最多选择2个选项!');
    } else {
    callback();
    }
    }


    //修改后自定义校验代码
    const validateLimit = (rule, value, callback) => {
    // 自定义函数if判断的条件中加入对value的兼容判断
    if (value && value.length > 2) {
    callback('最多选择2个选项!');
    } else {
    callback();
    }
    }

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/53b29abd.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/5419262f.html b/5419262f.html new file mode 100644 index 000000000..61fdf9403 --- /dev/null +++ b/5419262f.html @@ -0,0 +1 @@ +NAT服务器搭建rustdesk服务-docker方式 | 梦洁小站-属于你我的小天地

    NAT服务器搭建rustdesk服务-docker方式

    前言

    docker配置

    1
    2
    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

    networks:
    rustdesk-net:
    external: false

    services:
    hbbs:
    container_name: hbbs
    ports:
    - 26441:21115
    - 26442:21116 # 自定义 hbbs 映射端口 对应ID服务器端口
    - 26442:21116/udp # 自定义 hbbs 映射端口
    image: rustdesk/rustdesk-server
    command: hbbs -r 14141.xyz:26443 #填入个人域名或 IP + hbbr 暴露端口 对应中继服务器端口
    volumes:
    - ./data:/root # 自定义挂载目录
    networks:
    - rustdesk-net
    depends_on:
    - hbbr
    restart: unless-stopped

    hbbr:
    container_name: hbbr
    ports:
    - 26443:21117 # 自定义 hbbr 映射端口 #对应中继服务器端口
    image: rustdesk/rustdesk-server
    command: hbbr
    volumes:
    - ./data:/root # 自定义挂载目录
    networks:
    - rustdesk-net
    restart: unless-stopped

    • 然后rustdesk配置如下
    • 如果遇到10054错误,就不要设置id服务器

    • NAT配置如下

    • 注意

    推荐rustdesk-api-server

    • 记得关闭注册当第一个用户注册后ALLOW_REGISTRATION:false
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/5419262f.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/54e18b38.html b/54e18b38.html new file mode 100644 index 000000000..f736a9da3 --- /dev/null +++ b/54e18b38.html @@ -0,0 +1 @@ +网易严选,使用uni-app实现,包含后台数据文件 | 梦洁小站-属于你我的小天地

    网易严选,使用uni-app实现,包含后台数据文件

    介绍

    • 使用uni-app开发的

    • 数据为本地JSON数据,所以不用担心后台问题啦,但是需要手动运行后台

    • 下载地址

    • 运行项目

      • 先运行npm install后再hbuilderX中工具条选择运行 - 运行到小程序模拟器-微信开发者工具
      • 不过运行之前微信开发者工具开启下服务端口

    • 运行后台

      • 切换到目录/guiguShopServer后运行npm install
      • 然后node server.js
    • 后台服务器运行的默认地址

      • http://localhost:3001
    • 更改自己的后台地址

      • /utils/config.js打开更改host字段即可

    项目截图

    首页

    首页

    购物车

    分类

    个人中心

    登录

    商品详情

    填充内容

    舔狗日记 3月1日 雨

    今天你终于通过我好友了,打招呼的方式还是那么别致,一个阿玛尼包包的淘宝链接,我从兄弟那边借了3000,很快给你买了,你很开心,给我发了可爱的表情包,还对我说了谢谢,你开心,我也就开心了。

    舔狗日记 3月2日 阴

    今天是你的生日,我想送你礼物,但是我没有钱,我去偷东西的时候被抓了,我本来想反抗,警察说了一句老实点别动,我立刻就放弃了抵抗,因为我记得你说过你喜欢老实人。

    舔狗日记 3月3日 雨

    今天发工资了,我一个月工资800,你猜我会给你多少,是不是觉得我会给你1200,因为厂里全勤奖还有400。错了,我会再和工友借114凑够1314转给你。

    舔狗日记 3月4日 阴

    我给你打了一通电话,你终于接了。听到了你发出啊啊啊啊的声音,你说你脚痛,我想你一定是很难受吧。电话还有个男的对你说“来换个姿势”。你一定是在做理疗,好心疼,期待你早日康复。

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/54e18b38.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/55a3c0ce.html b/55a3c0ce.html new file mode 100644 index 000000000..d42592848 --- /dev/null +++ b/55a3c0ce.html @@ -0,0 +1 @@ +文心一言开通后吐槽下 | 梦洁小站-属于你我的小天地

    文心一言开通后吐槽下

    1
    2
    总的来说,不推荐使用和续费
    光是生成速度就落后一大截了.........亏我还开了会员,操蛋

    速度慢,还会出现火爆使用

    生成速度太慢了

    • 大概2~3分钟左右才处理好

    切出网页标签还不能生成了!

    • 这一点真无语,必须要一直看着……………….
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/55a3c0ce.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/565d1ba5.html b/565d1ba5.html new file mode 100644 index 000000000..0b906cb00 --- /dev/null +++ b/565d1ba5.html @@ -0,0 +1 @@ +尚品汇Vue项目 前台+后台完成品源码(含在线演示) | 梦洁小站-属于你我的小天地

    尚品汇Vue项目 前台+后台完成品源码(含在线演示)

    尚品汇前台练习

    重新添加了修改了下

    • 2022年7月30日16.13.13
    1. 登录界面添加了下自动登录
    2. detail购物车用户输入商品数量的优化,为合法值的时候才更新,否者还原原有的值
    3. 搜索结果页面的评价人数用随机数,这样子好看点,并且每一件商品的金额的样式调整了下
    4. 事件委派的方式为搜索结果添加’加入购物车’按钮
    5. 结算页面验证是否登录了
    6. 首页swiper的前进后退按钮移入显示移出隐藏
    7. login界面的label和input对齐了下
    8. 注册成功后自动登录
    9. 地址编辑框优化
    10. dialog自己添加遮罩层和dialog显示的时候禁用滚动(elementui在这里有bug)
    11. 购物车为空的时候没有什么提示添加进去了一个内容
    12. 购物车提交时候必须要有地址信息才可以
    13. 注册界面添加倒计时
    14. 提交订单页选择优化
    15. 购物车列表商品增加删除逻辑优化
    16. 一些样式的轻微调整
    17. 其他的请你们进入在线演示仔细查看吧~

    自己添加了下收货地址和删除收货地址

    有时候主页的一些数据老是获取不到,用了假数据(但是数据内容和接口地址是一样的)

    添加地址

    信息弹窗全部改为element-ui的message提示

    项目依赖安装

    1
    npm install

    项目运行

    1
    npm run serve

    项目打包编译

    1
    npm run build

    地址

    国外

    1
    https://github.com/superBiuBiuMan/gshop_project

    国内

    1
    https://gitee.com/superBiuBiu/gshop_project

    尚品汇后台练习

    首页登录

    登录界面

    首页图表

    首页图表

    品牌管理

    品牌管理

    权限管理

    权限管理-用户管理

    商品管理

    商品管理

    项目依赖安装

    1
    npm install

    项目运行

    1
    npm run dev

    项目打包编译

    1
    2
    3
    4
    //构建生产环境
    npm run build:prod
    //构成测试环境
    npm run build:stage

    如果想在nginx上线,注意配置转发和删除.env.production的里面的内容(以生产环境打包为例)

    • 需要删除Vue_APP_BASE_API里面的内容~

    .env.production文件

    • nginx转发设置

      nginx转发设置

    地址

    国外

    1
    https://github.com/superBiuBiuMan/gshop_project_after

    国内

    1
    https://gitee.com/superBiuBiu/gshop_project_after

    如果角色权限不能正常赋予请看这里

    • elementUI tree在收集数据的时候,并不会在上几级的数据放入,所以会导致用户权限不能正常赋予,这里给出了解决办法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    vue elementUI tree树形控件获取父节点ID的实例

    修改源码:
    情况1: element-ui没有实现按需引入打包
    node_modules\element-ui\lib\element-ui.common.js 25382行修改源码 去掉 'includeHalfChecked &&'
    // if ((child.checked || includeHalfChecked && child.indeterminate) && (!leafOnly || leafOnly && child.isLeaf)) {
    if ((child.checked || child.indeterminate) && (!leafOnly || leafOnly && child.isLeaf)) {


    情况2: element-ui实现了按需引入打包
    node_modules\element-ui\lib\tree.js 1051行修改源码 去掉 'includeHalfChecked &&'
    // if ((child.checked || includeHalfChecked && child.indeterminate) && (!leafOnly || leafOnly && child.isLeaf)) {
    if ((child.checked || child.indeterminate) && (!leafOnly || leafOnly && child.isLeaf)) {

    注意!!!!!!!!!

    • vue-admin管理模板npm安装依赖后npm run de提示依赖core-js,@babel等报错的解决办法

    安装别人做好的后台管理项目,npm run dev后报错

    • 都是这种core-js/modules/es.array.concat.js什么的错误

    报错项

    解决

    1. 找到项目下的babel.config.js 原来的presets改为下面

      1
      presets: [ [ "@vue/app", { useBuiltIns: "entry" } ] ],
    2. 改好之后

    原来的presets改为下面

    1. 重新运行npm run dev 成功!

      成功

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/565d1ba5.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    avatar
    梦洁
    小小的字,有大大的梦想~分享我的前端学习过程,经历,错误,和一些其他折腾过程
    关注下我(* ̄▽ ̄*)
    公告
    不断更新中,有问题请留言回复(会通过邮箱提醒~)
    \ No newline at end of file diff --git a/5744dcde.html b/5744dcde.html new file mode 100644 index 000000000..54e805505 --- /dev/null +++ b/5744dcde.html @@ -0,0 +1 @@ +今日刷题-JavaScript的身份证正则和内置可迭代对象 | 梦洁小站-属于你我的小天地

    今日刷题-JavaScript的身份证正则和内置可迭代对象

    题目1

    1
    2
    3
    4
    5
    以下哪些对象是Javascript内置的可迭代对象?(多选)
    A: Array
    B: Map
    C: String
    D: Object
    • 答案

      • A,B,C
    • 解析

      1
      2
      3
      4
      5
      6
      7
      8
      JavaScript当中可迭代的对象有
      Array
      Map
      Set
      String
      TypedArray
      arguments对象(注意:箭头函数是没有arguments和this的,所以只有普通函数才有arguments)
      NodeList对象(从document.getElementsByTagName之类获取的节点列表)

    题目2

    1
    2
    3
    4
    5
    6
    7
    8
    关于身份证号,以下正确的正则表达式为(多选)
    A: isIDCard=/^[1-9]\d{7}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}$/

    B: isIDCard=/^[1-9]\d{7}((9\d)|(1[0-2]))(([0|1|2]\d)|3[9-1])\d{3}$/

    C: isIDCard=/^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{4}$/

    D: isIDCard=/^[1-9]\d{5}[1-9]\d{3}((9\d)|(1[9-2]))(([0|1|2]\d)|3[9-1])\d{4}$/
    • 答案

      • A ,C
    • 解析

      1
      2
      3
      4
      身份证构成
      15位身份证的构成:六位出生地区码+六位出身日期码+三位顺序码
      并且15位的身份证号码显示的年份00-99,代表1900-1999年,其他的和18为基本一致
      18位身份证的构成:六位出生地区码+八位出生日期码+三位顺序码+一位校验码
      • A:

        1
        2
        3
        4
        5
        ^:以指定类型的开头,后面紧跟着什么就是以什么开头
        [1-9]\d{7}: 六位出生地区码+两位出生日期码的年份(00-99)
        ((0\d)|(1[0-2])): 匹配月份,不足二位的前面填充0,也就是01,05
        (([0|1|2]\d)|3[0-1]): 匹配日,匹配01,10等或者30,31
        \d{3}$: 三位顺序码,并以数字结尾
      • C:

        1
        2
        3
        4
        5
        6
        ^:以指定类型的开头,后面紧跟着什么就是以什么开头
        [1-9]\d{5}: 匹配六位出生地区码
        [1-9]\d{3}: 匹配年份,年份不能为0开头,所以第一位就以[1-9]的情况出现
        ((0\d)|(1[0-2])): 匹配月份,不足二位的前面填充0,也就是01,05
        (([0|1|2]\d)|3[0-1]): 匹配日,匹配01,10等或者30,31
        \d{4}$: 三位顺序码+一位校验码,并以数字结尾

    额外

    • ​ jQuery当中的$符号还可以传入一个jQuery对象,代表创建该jQuery对象的一个副本并返回,副本与传入的jQuery对象引用完全相同的元素.
    • ​ JavaScript是解释性语言
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/5744dcde.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/5978735e.html b/5978735e.html new file mode 100644 index 000000000..e0626aafd --- /dev/null +++ b/5978735e.html @@ -0,0 +1 @@ +Ubuntu系统安装基本Nginx和docker和一些其他的软件的基本操作 | 梦洁小站-属于你我的小天地

    Ubuntu系统安装基本Nginx和docker和一些其他的软件的基本操作

    前言

    • 系统使用Ubuntu20.4
    • 华为云

    安装nginx(如果需要NginxWebUI,就不这样子安装)

    1
    2
    3
    4
    5
    #安装
    apt install nginx

    #查看版本
    nginx -v

    安装Nginx Proxy Manager

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    version: '3.8'
    services:
    app:
    image: 'jc21/nginx-proxy-manager:latest'
    restart: unless-stopped
    ports:
    - '80:80'
    - '81:81'
    - '443:443'
    volumes:
    - ./data:/data
    - ./letsencrypt:/etc/letsencrypt
    1
    2
    Email:    admin@example.com
    Password: changeme

    安装docker和docker-compose

    安装docker

    • 官方的一键安装方式:
    1
    2
    3
    4
    5
    6
    7
    8
    curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun

    或者
    curl -fsSL https://get.docker.com | bash -s docker

    #查看版本
    docker -v
    #Docker version 23.0.1, build a5ee5b1
    • 国内 daocloud一键安装命令:
      • 官网都打不开了,不知道为啥,所以估计这个命名过不了多久就没有用
    1
    2
    3
    4
    5
    6
    #安装
    curl -sSL https://get.daocloud.io/docker | sh

    #查看版本
    docker -v
    #Docker version 23.0.1, build a5ee5b1
    • 设置docker镜像
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    sudo mkdir -p /etc/docker
    sudo tee /etc/docker/daemon.json <<-'EOF'
    {
    "registry-mirrors": ["https://49qfdqkg.mirror.aliyuncs.com"]
    }
    EOF
    sudo systemctl daemon-reload
    sudo systemctl restart docker

    #查看配置是否生效
    docker info

    • 如果出现这个问题Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?
    1
    2
    3
    4
    5
    6
    7

    #重新启动
    systemctl restart docker.service


    #开机启动
    systemctl enable docker

    安装docker-compose

    1
    2
    3
    4

    #2.17版本
    docker compose up -d

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    #直接下载(国外ip)
    curl -SL https://github.com/docker/compose/releases/download/v2.16.0/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose


    #使用代理加速下载(国内ip)
    curl -SL https://ghproxy.com/https://github.com/docker/compose/releases/download/v2.16.0/docker-compose-linux-x86_64 -o /usr/local/bin/docker-compose


    #查看版本
    docker-compose -v
    # Docker Compose version v2.16.0

    #如果出现docker-compose -v Docker Compose version v2.16.0
    #执行下面命令即可
    chmod +x /usr/local/bin/docker-compose

    #接着查看版本,可以正常查看
    docker-compose -v
    # Docker Compose version v2.16.0

    # 或者
    docker compose version
    # Docker Compose version v2.17.3

    安装NginxWebUI(Nginx可视化工具)

    • docker运行
    1
    2
    3
    4
    5
    6
    #安装java运行环境
    apt install openjdk-8-jdk

    #启动容器
    #端口可以自己更改
    docker run -itd -v /home/nginxWebUI:/home/nginxWebUI -e BOOT_OPTIONS="--server.port=9764" --privileged=true --net=host --restart=always cym1102/nginxwebui:latest

    安装nodejs(使用NVM管理)

    1
    2
    3
    4
    5
    #国外IP
    curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash

    #国内IP
    curl -o- https://ghproxy.com/https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash

    • 安装完成后,如果输入nvm回车后提示command not found,则关闭当前终端,打开一个新终端,然后再次尝试验证
    1
    2
    3
    #查看nvm版本
    nvm -v
    #0.39.3
    • 安装nvm后安装nodejs
    1
    2
    3
    4
    5
    6
    7
    8
    #安装指定版本
    nvm install 16.19.1

    #安装最新版
    nvm install node # "node" is an alias for the latest version

    #查看安装了哪些版本
    nvm ls
    • 更换淘宝镜像源地址
    1
    2
    3
    4
    5
    #设置地址
    npm config set registry https://registry.npm.taobao.org/

    #查看镜像源的地址
    npm config get registry npm

    安装自己常用docker

    学习强国

    拉取镜像(也可以直接去部署)

    2023年3月26日14.33.45 更换新方法

    • docker-compose 部署
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    version: "3.5"
    services:
    xuexi-auto:
    image: xlh001/study_xxqg:latest
    # 容器名
    container_name: xuexi-auto
    environment:
    # 时区
    - TZ=Asia/Shanghai
    # 配置文件路径
    volumes:
    - ./config:/opt/config
    # 映射端口
    ports:
    - 1234:8080
    restart: unless-stopped

    2023年3月24日14.32.45下面这种失效了

    • x86 处理器镜像
    1
    docker pull registry.cn-hangzhou.aliyuncs.com/wxyhgk/xxgq:2.0
    • arm处理器镜像
    1
    docker pull registry.cn-hangzhou.aliyuncs.com/wxyhgk/xxqg_arm:1.0

    部署

    • x86 处理器
      • -p 1234:8080表示内部8080端口映射到外部(服务器)1234端口
    1
    2
    3
    4
    5
    6
    7
    docker run \
    --name study_xxqg \
    -d \
    -p 1234:8080 \
    -v /etc/study_xxqg/:/opt/config/ \
    --restart unless-stopped \
    registry.cn-hangzhou.aliyuncs.com/wxyhgk/xxgq:2.0
    • arm处理器
    1
    2
    3
    4
    5
    6
    7
    docker run \
    --name study_xxqg \
    -d \
    -p 1234:8080 \
    -v /etc/study_xxqg/:/opt/config/ \
    --restart unless-stopped \
    registry.cn-hangzhou.aliyuncs.com/wxyhgk/xxqg_arm:1.0
    • 含义
    1
    2
    3
    4
    5
    6
    —name 表示名字 ,名字是 study_xxqg
    -d 表示后台运行
    -p 1234:8080 表示将 容器内部的 8080 端口映射到主机的 1234 端口
    -v /etc/study_xxqg/:/opt/config/ 表示将 本机的 /etc/study_xxqg/ 映射到 docker 镜像的 /opt/config/ 下
    registry.cn-hangzhou.aliyuncs.com/wxyhgk/xxgq:1.0 表示拉取这个镜像
    --restart unless-stopped \ 表示重启docker后会启动此容器

    完成和其他配置

    安装青龙(docker-compose方式)

    • 创建docker-compose.yml文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    version: '2'
    services:
    ql_web:
    image: whyour/qinglong:latest
    container_name: ql
    volumes:
    - ./data/config:/ql/config
    - ./data/log:/ql/log
    - ./data/db:/ql/db
    - ./data/scripts:/ql/scripts
    - ./data/repo:/ql/repo
    ports:
    - "0.0.0.0:5700:5700"
    environment:
    - ENABLE_HANGUP=true
    - ENABLE_WEB_PANEL=true
    restart: always
    • 所在目录下执行
    1
    docker-compose up -d 
    • 记得服务器防火墙开放端口5700
    • 浏览器访问服务器地址:5700

    安装好后安装下依赖(懂得都懂)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    #国内版
    docker exec -it ql bash -c "$(curl -fsSL https://ghproxy.com/https://raw.githubusercontent.com/FlechazoPh/QLDependency/main/Shell/QLOneKeyDependency.sh | sh)"

    #国外版:
    docker exec -it ql bash -c "$(curl -fsSL https://raw.githubusercontent.com/FlechazoPh/QLDependency/main/Shell/QLOneKeyDependency.sh | sh)"

    #【更新】 版本号 2.12+ 的新版本青龙安装失败请尝试:
    docker exec -it ql bash -c "$(curl -fsSL https://raw.githubusercontent.com/FlechazoPh/QLDependency/main/Shell/XinQLOneKey.sh | sh)"

    • 或者自己安装下
    1
    npm install -g png-js date-fns axios crypto-js ts-md5 tslib @types/node requests tough-cookie jsdom download tunnel fs ws form-data js-base64 qrcode-terminal silly-datetime

    安装qbittorrent

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    version: "2.1"
    services:
    qbittorrent:
    image: lscr.io/linuxserver/qbittorrent:latest
    container_name: qbittorrent
    environment:
    - PUID=1000
    - PGID=1000
    - TZ=Etc/UTC
    - WEBUI_PORT=8080
    volumes:
    - /path/to/appdata/config:/config
    - /path/to/downloads:/downloads
    ports:
    - 8080:8080
    - 6881:6881
    - 6881:6881/udp
    restart: unless-stopped

    如果需要更改端口,可以看看下方注释

    version: "2"
    services:
    qbittorrent:
    image: linuxserver/qbittorrent
    container_name: qbittorrent
    environment:
    - PUID=1000
    - PGID=1000
    - TZ=Asia/Shanghai # 你的时区
    - UMASK_SET=022
    - WEBUI_PORT=8081 # 将此处修改成你欲使用的 WEB 管理平台端口
    volumes:
    - /home/qb/config:/config # 绝对路径请修改为自己的config文件夹
    - /home/d:/downloads # 绝对路径请修改为自己的downloads文件夹
    ports:
    # 要使用的映射下载端口与内部下载端口,可保持默认,安装完成后在管理页面仍然可以改成其他端口。
    - 6881:6881
    - 6881:6881/udp
    # 此处WEB UI 目标端口与内部端口务必保证相同,见问题1
    - 8081:8081
    restart: unless-stopped

    • 所在目录下执行
    1
    docker-compose up -d 
    • 记得服务器防火墙放行80806881

    • 浏览器访问服务器地址:8080

    • 默认用户名/密码为 admin/adminadmin

    • 更换中文

    安装aria2-pro

    1
    2
    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
    docker run -d \
    --name aria2-pro \
    --restart unless-stopped \
    --log-opt max-size=1m \
    --network host \
    -e PUID=$UID \
    -e PGID=$GID \
    -e RPC_SECRET=<TOKEN> \
    -e RPC_PORT=6800 \
    -e LISTEN_PORT=6888 \
    -v $PWD/aria2-config:/config \
    -v $PWD/aria2-downloads:/downloads \
    p3terx/aria2-pro

    (自用)
    docker run -d \
    --name aria2-pro \
    --restart unless-stopped \
    --log-opt max-size=1m \
    --network host \
    -e PUID=$UID \
    -e PGID=$GID \
    -e RPC_SECRET=112233 \
    -e RPC_PORT=6800 \
    -e LISTEN_PORT=6888 \
    -v /onetb/qbitandair/aria2-config/:/config \
    -v /onetb/qbitandair/download/:/downloads \
    p3terx/aria2-pro
    • docker-compose
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    version: "3.8"

    services:

    Aria2-Pro:
    container_name: aria2-pro
    image: p3terx/aria2-pro
    environment:
    - PUID=65534
    - PGID=65534
    - UMASK_SET=022
    - RPC_SECRET=P3TERX
    - RPC_PORT=6800
    - LISTEN_PORT=6888
    - DISK_CACHE=64M
    - IPV6_MODE=false
    - UPDATE_TRACKERS=true
    - CUSTOM_TRACKER_URL=
    - TZ=Asia/Shanghai
    volumes:
    - /onetb/qbittorrent/config/aria2-config:/config
    - /onetb/qbittorrent/ariadownload:/downloads
    # If you use host network mode, then no port mapping is required.
    # This is the easiest way to use IPv6 networks.
    network_mode: host
    # network_mode: bridge
    # ports:
    # - 6800:6800
    # - 6888:6888
    # - 6888:6888/udp
    restart: unless-stopped
    # Since Aria2 will continue to generate logs, limit the log size to 1M to prevent your hard disk from running out of space.
    logging:
    driver: json-file
    options:
    max-size: 1m

    # AriaNg is just a static web page, usually you only need to deploy on a single host.
    AriaNg:
    container_name: ariang
    image: p3terx/ariang
    command: --port 6880 --ipv6
    network_mode: host
    # network_mode: bridge
    # ports:
    # - 6880:6880
    restart: unless-stopped
    logging:
    driver: json-file
    options:
    max-size: 1m

    安装alist

    • 使用docker-compose安装
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    version: '3.3'
    services:
    alist:
    restart: always
    volumes:
    - '/etc/alist:/opt/alist/data'
    ports:
    - '5244:5244'
    environment:
    - PUID=0
    - PGID=0
    - UMASK=022
    container_name: alist
    image: 'xhofe/alist:latest'

    或者

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    version: '3.3'
    services:
    alist:
    restart: always
    volumes:
    - './data:/opt/alist/data'
    ports:
    - '5244:5244'
    environment:
    - PUID=0
    - PGID=0
    - UMASK=022
    container_name: alist
    image: 'xhofe/alist:latest'
    • 所在目录下执行
    1
    docker-compose up -d 
    • 记得服务器防火墙放行5244

    • 浏览器访问服务器地址:5244

    • 获取alist默认密码命令

    1
    docker exec -it alist ./alist admin

    安装subconverter

    1
    docker run -d --restart=always -p 25500:25500 tindy2013/subconverter:latest
    • docker-compose安装
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    version: '3.3'
    services:
    subconverter:
    restart: always
    volumes:
    - './data:/opt/subconverter/data'
    ports:
    - '25500:25500'
    container_name: subconverter
    image: 'tindy2013/subconverter:latest'

    安装短链接Yourls

    • docker-compose方式
    1
    2
    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
    35
    36
    version: "3.5"
    services:

    mysql:
    image: mysql:5.7.22 # 如果遇到不正确的数据库配置,或无法连接到数据库PDOException: SQLSTATE[HY000] [1045] 用户'yourls'@'yourls_service.yourls_default'的访问被拒绝(使用密码:是) 可以把5.7.22 改为 5.7
    environment:
    - MYSQL_ROOT_PASSWORD=my-secret-pw
    - MYSQL_DATABASE=yourls
    - MYSQL_USER=yourls
    - MYSQL_PASSWORD=yourls
    volumes:
    - ./mysql/db/:/var/lib/mysql
    - ./mysql/conf/:/etc/mysql/conf.d
    restart: always
    container_name: mysql

    yourls:
    image: yourls
    restart: always
    ports:
    - "8200:80"
    environment:
    YOURLS_DB_HOST: mysql
    YOURLS_DB_USER: yourls
    YOURLS_DB_PASS: yourls
    YOURLS_DB_NAME: yourls
    YOURLS_USER: admin # 自己起一个名字
    YOURLS_PASS: admin # 自己换一个登陆密码
    YOURLS_SITE: https://gao.ee # 换成你自己的域名
    YOURLS_HOURS_OFFSET: 8
    volumes:
    - ./yourls_data/:/var/www/html
    container_name: yourls_service
    links:
    - mysql:mysql

    • docker-compose部署
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    version: '3.8'

    services:
    shlink:
    image: shlinkio/shlink:stable
    container_name: shlink
    ports:
    - '127.0.0.1:8200:8080'
    environment:
    - DEFAULT_DOMAIN=omgl.xyz
    - IS_HTTPS_ENABLED=true
    - GEOLITE_LICENSE_KEY=GEOLITE_LICENSE_KEY 需要在 Maxmind 注册帐号获取
    - DB_DRIVER=maria
    - DB_NAME=shlink
    - DB_USER=shlink
    - DB_PASSWORD=随机密码1
    - DB_HOST=db
    - DB_PORT=3306
    - TIMEZONE=UTC
    - REDIRECT_STATUS_CODE=301
    restart: always

    db:
    image: mariadb:10.6
    depends_on:
    - shlink
    container_name: db
    ports:
    - '127.0.0.1:3306:3306'
    environment:
    - MYSQL_ROOT_PASSWORD=随机 root 密码
    - MYSQL_DATABASE=shlink
    - MYSQL_USER=shlink
    - MYSQL_PASSWORD=随机密码1
    volumes:
    - /opt/shlink/data:/var/lib/mysql
    restart: always

    shlink-web-client:
    image: shlinkio/shlink-web-client:stable
    container_name: shlink-web-client
    ports:
    - '127.0.0.1:9765:80'
    restart: always
    • 然后获取一个 API Key:
    1
    docker exec -it shlink shlink api-key:generate

    • 配置Nginx,步骤略

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    通过环境变量为默认服务器提供配置。当容器启动时,它将servers.json基于它们动态构建文件。(自 shlink-web-client 3.2.0 起)。
    SHLINK_SERVER_URL:Shlink 服务器的完全限定 URL。
    SHLINK_SERVER_API_KEY:API 密钥。
    SHLINK_SERVER_NAME:要显示的名称。如果未提供,则默认为Shlink。
    docker run \
    --name shlink-web-client \
    -p 8000:80 \
    -e SHLINK_SERVER_URL=https://s.test \
    -e SHLINK_SERVER_API_KEY=6aeb82c6-e275-4538-a747-31f9abfba63c \
    shlinkio/shlink-web-client
    • 如果是docker-compose配置添加默认环境的话也可以,但是莫名其妙没有成功,这里就使用下面的配置,结合config文件来配置了
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    version: '3.8'

    services:
    shlink:
    image: shlinkio/shlink:stable
    container_name: shlink
    ports:
    - '127.0.0.1:8200:8080'
    environment:
    - DEFAULT_DOMAIN=omgl.xyz
    - IS_HTTPS_ENABLED=true
    - GEOLITE_LICENSE_KEY=GEOLITE_LICENSE_KEY 需要在 Maxmind 注册帐号获取
    - DB_DRIVER=maria
    - DB_NAME=shlink
    - DB_USER=shlink
    - DB_PASSWORD=随机密码1
    - DB_HOST=db
    - DB_PORT=3306
    - TIMEZONE=UTC
    - REDIRECT_STATUS_CODE=301
    restart: always

    db:
    image: mariadb:10.6
    depends_on:
    - shlink
    container_name: db
    ports:
    - '127.0.0.1:3306:3306'
    environment:
    - MYSQL_ROOT_PASSWORD=随机 root 密码
    - MYSQL_DATABASE=shlink
    - MYSQL_USER=shlink
    - MYSQL_PASSWORD=随机密码1
    volumes:
    - /opt/shlink/data:/var/lib/mysql
    restart: always

    shlink-web-client:
    image: shlinkio/shlink-web-client:stable
    container_name: shlink-web-client
    volumes:
    - ./config/:/usr/share/nginx/html/conf.d/
    ports:
    - '127.0.0.1:9765:80'
    restart: always
    • 在docker-compose.yml下的文件夹config建立servers.json文件,内容格式如下
    1
    2
    3
    4
    5
    6
    7
    [
    {
    "name": "服务器名称",
    "url": "https://xxxxx.xxxxxxxxxx",
    "apiKey": "执行获取的api"
    }
    ]

    image-20230402214930699

    • 成功添加默认服务器

    image-20230402214802003

    安装heimdall

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    version: "2.1"
    services:
    heimdall:
    image: lscr.io/linuxserver/heimdall:latest
    container_name: heimdall
    environment:
    - PUID=1000
    - PGID=1000
    - TZ=Etc/UTC
    volumes:
    - /path/to/appdata/config:/config
    ports:
    - 80:80
    - 443:443
    restart: unless-stopped
    • docker
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    docker run -d \
    --name=heimdall \
    -e PUID=1000 \
    -e PGID=1000 \
    -e TZ=Etc/UTC \
    -p 80:80 \
    -p 443:443 \
    -v /path/to/appdata/config:/config \
    --restart unless-stopped \
    lscr.io/linuxserver/heimdall:latest
    • 自己用的
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    docker run -d \
    --name=heimdall \
    -e PUID=1000 \
    -e PGID=1000 \
    -e TZ=Etc/UTC \
    -p 84:80 \
    -p 5243:443 \
    -v /path/to/appdata/config:/config \
    --restart unless-stopped \
    lscr.io/linuxserver/heimdall:latest

    安装nastool

    • docker
    1
    docker run -d --name nas-tools --hostname nas-tools --network=host -p 3000:3000 -v /nastool/config:/config -v /nastools:/nastools -e PUID=0 -e PGID=0 -e UMASK=000 -e NASTOOL_AUTO_UPDATE=false nastools/nas-tools:2.9.1
    • docker-compose
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    version: '3.3'
    services:
    nas-tools:
    container_name: nas-tools
    hostname: nas-tools
    network_mode: host
    volumes:
    - '/nastool/config:/config'
    - '/nastools:/nastools'
    environment:
    - PUID=0
    - PGID=0
    - UMASK=000
    - NASTOOL_AUTO_UPDATE=false
    image: 'nastools/nas-tools:2.9.1'
    restart: unless-stopped

    • 自用配置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    version: '3.3'
    services:
    nas-tools:
    container_name: nas-tools
    hostname: nas-tools
    ports:
    - '9703:3000'
    volumes:
    - './config:/config'
    - './nastools:/nastools'
    environment:
    - PUID=0
    - PGID=0
    - UMASK=000
    - NASTOOL_AUTO_UPDATE=false
    image: 'nastools/nas-tools:2.9.1'
    restart: unless-stopped
    • 浏览器访问
      • 通过 http://ip:3000 访问,默认账号 admin 默认密码 password,初次使用会强制更改密码

    安装vertex

    1
    2
    3
    4
    5
    6
    7
    docker run -d \
    --name vertex \
    -v /root/vertex:/vertex \
    -p 3000:3000 \
    -e TZ=Asia/Shanghai \
    --restart unless-stopped \
    lswl/vertex:stable
    • docker-compose.yml
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    version: '3.3'
    services:
    vertex:
    container_name: vertex
    volumes:
    - './vertex:/vertex'
    ports:
    - '9704:3000'
    environment:
    - TZ=Asia/Shanghai
    restart: unless-stopped
    image: 'lswl/vertex:stable'
    • 默认账号为admin
      • 密码是下图所示的内容

    安装screen

    安装

    1
    apt-install screen

    使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    screen -ls 				# 浏览当前有哪些后台作业 类似ls命令,列举出所有对话作业。
    screen -S <作业名称>   # 新建一个screen作业的名称。
    screen -r <作业名称>   # 恢复之前的一个screen作业。
    screen -d <作业名称> # 挂起该作业
    screen -S <作业名称> -X quit # 删除该作业

    #关闭退出
    exit
    #不关闭退出窗口(这样子就可以一直在后台执行) 快捷键
    CTRL+A+D

    安装cloudreve

    安装Kavita

    • 官方wiki
    • 安装按照教程来就好了,主要是开机启动的问题,kavita.service
      • 注意:需要将文件夹权限改为777
    • 使用官方的配置项莫名其妙启动失败,删除掉用户相关的莫名其妙就好了
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    [Unit]
    Description=Kavita Server
    After=network.target

    [Service]
    Type=simple
    WorkingDirectory=/mnt/hdd/Kavita
    ExecStart=/mnt/hdd/Kavita/Kavita
    TimeoutStopSec=20
    KillMode=process
    Restart=on-failure

    [Install]
    WantedBy=multi-user.target

    安装 Web File Browser

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    docker run --restart=always --name filebrowser -d -v /onetb/qbitandair/download:/srv -v /onetb/filebrowser/filebrowserconfig.json:/etc/config.json -v /onetb/filebrowser/database.db:/etc/database.db -p 2334:80 filebrowser/filebrowser



    或者
    version: '3.3'
    services:
    filebrowser:
    restart: always
    container_name: filebrowser
    volumes:
    - '/onetb/qbitandair/download:/srv'
    - '/onetb/filebrowser/filebrowserconfig.json:/etc/config.json'
    - '/onetb/filebrowser/database.db:/etc/database.db'
    ports:
    - '2334:80'
    image: filebrowser/filebrowser
    • 浏览器访问
      • localhost:2334
      • 默认账号和密码admin admin

    使用root用户在AWS

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    #设置password密码
    sudo passwd root

    #切换root
    su root

    #编辑
    vim /root/.ssh/authorized_keys 修改内容,删除ssh-rsa以前的内容如下图,保存退出。
    这样fianalshell就可以使用root直接登录了。

    一键设置虚拟内存

    1
    2
    3
    4
    5
    #国外IP
    wget -O box.sh https://raw.githubusercontent.com/BlueSkyXN/SKY-BOX/main/box.sh && chmod +x box.sh && clear && ./box.sh

    #国内IP
    wget -O box.sh https://ghproxy.com/https://raw.githubusercontent.com/BlueSkyXN/SKY-BOX/main/box.sh && chmod +x box.sh && clear && ./box.sh

    • 选择1.添加SWAP

    image-20230402205400924

    • 输入数字,单位为MB

    输入数字,单位为MB

    短链接的一个介绍博客

    Just-Moh-it/Pckd: The most ⚡️ analytics-intensive 💪 self-hostable 🔗 URL shortener, with an amazing UI 😍
    地址:https://github.com/Just-Moh-it/Pckd

    Hello from Pckd - Docs | Pckd - Docs
    地址:https://docs.pckd.me/

    Prebuilt Docker image · Issue #32 · PckdHQ/Pckd
    地址:https://github.com/PckdHQ/Pckd/issues/32

    Pckd - The most ⚡️ analytics-intensive 💪 self-hostable 🔗 URL shortener, with an amazing UI 😍 : selfhosted
    地址:https://www.reddit.com/r/selfhosted/comments/srn4wo/pckd_the_most_analyticsintensive_selfhostable_url/

    AboutAn open-source link shortener with built-in analytics + free custom domains.

    地址:https://github.com/steven-tey/dub

    Free Modern URL Shortener.

    地址:https://github.com/thedevs-network/kutt

    服务器测试

    1
    2
    3
    4
    5
    6
    wget -qO- bench.sh | bash

    或者

    curl -sL bench.sh | bash

    参考文章

    小知识点

    • docker-compose指定运行文件
    1
    2
    # -f 指定使用的 Compose 模板文件,默认为 docker-compose.yml,可以多次指定,指定多个 yml
    docker-compose -f docker-compose.yml up -d
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/5978735e.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    avatar
    梦洁
    小小的字,有大大的梦想~分享我的前端学习过程,经历,错误,和一些其他折腾过程
    关注下我(* ̄▽ ̄*)
    公告
    不断更新中,有问题请留言回复(会通过邮箱提醒~)
    \ No newline at end of file diff --git a/5c543e29.html b/5c543e29.html new file mode 100644 index 000000000..0984c3fc8 --- /dev/null +++ b/5c543e29.html @@ -0,0 +1 @@ +问卷星问卷抓包分析 | 梦洁小站-属于你我的小天地

    问卷星问卷抓包分析

    谷歌调试分析

    提交的包数据

    如图,可以看到提交是数据,我们先以jqsign来进行全局搜索

    这次全局搜索注意点

    在没有提交之前进行全局搜索ctrl+shift+F搜索才可以搜索到,不然提交完成后全局搜索什么都搜索不到

    如图,没有提交之前搜索,可以看到jqsin关键字

    没有提交之前搜索

    网页源码分析

    jqnonce(后期jqsign加密需要用到)

    就在网页源代码里面~所以每次请求网页都会发生变化

    jqnonce

    rndnum(后期提交请求需要用到)

    • 依旧网页当中有
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    //网页提供了很多参数
    var isYdb=0;
    var isPub=0;
    var isQywx =0;
    var isinterview =0;
    var isQywxAnswerChangeUrl = '';
    var cqType=1;
    var ddcorpid="";
    var sojumpParm='';
    var parmsign='';
    var casign='';
    var cats='';
    var endTs='';
    var isKaoShi=0;
    var lastTopic=0;
    var Password = window.Password ? window.Password : "";
    var PasswordExt = window.PasswordExt ? window.PasswordExt : "";
    var pwdExt="";
    var emailName="";
    var displayExt="";
    var phoneName="";
    var wxNickName="";
    var cepingName="";
    var guid = "";
    var udsid=0;var fromsour="";
    var langVer=0;
    var cProvince="";
    var cCity="";
    var cIp="";
    var divTip=document.getElementById("divTip");
    var displayPrevPage="none";
    var inviteid='';var jbkid='';
    var access_token="";
    var openid = "";
    var unionId = "";
    var wxUserId = "";
    var isQQLogin=0;
    var isDingTalkLogin = 0;
    var wxthird=0;
    var parterts="";
    var parterjoiner="";
    var partersign="";
    var parterrealname="";
    var parterextf="";
    var parterdept="";
    var parterpuser="";
    var relusername="";
    var relts="";
    var relsign="";
    var relrealname="";
    var reldept="";
    var relext = "";
    var writeuser = "";
    var formopen="";
    var formts="";
    var formsign="";
    var formnick="";
    var nbk=0;
    var corpId="";
    var flist=0;
    var isPvw=0;
    var user_token="";
    var IsSampleService=0;
    var hashb=0;
    var sjUser='';
    var sjts='';
    var sjsign='';
    var outuser='';
    var sourcelink='';
    var outsign='';
    var sourceurl = '';
    var sourcename="";
    var isSimple='';
    var jiFenBao=0;
    var isRunning=1;
    var SJBack='';var jiFen="0";
    var FromSj=0;
    var ItemDicData="";
    //需要用到
    var rndnum="1792470105.05000187";
    var totalPage=1;
    var totalCut=0;
    var cepingCandidate="";
    var allowPart =0;
    var showTotalScore =0;
    var OneaTime =0;
    var oneDept =0;
    var oneneedcontcp =0
    var cpid="";
    var needSaveJoin=0;
    var isChuangGuan=0;
    //需要用到
    var jqnonce="61348c0e-ff49-4ffb-b2a7-f60ae3106771";
    var maxCgTime=0;
    var maxOpTime=0;
    //需要用到,后期提交只不过转换了下
    var qBeginDate="1656339402157";
    var randomMode=0;
    var fisrtLoadTime=new Date().getTime();
    var canAward=1;
    var allowAward=1;
    var isVip =0;
    var emUserName = "V30SQaxJ9+XY5Hw0HBxVmINnib19XMvSDwMRfIUgpMA=";
    var LogStoreLocal=0;
    var needAddList=0;

    var needLogCompanyId=0;
    var needHBAlert=0;
    var isPromoteing=0;
    var prsjts = "";
    var prsjsign = "";
    var cityPeiEQues = "";

    var ishydj = 0;
    var canEditAnswer = 0;
    var forbidEditStr = "";
    var markerText = '';
    var jumpOrgMinPro =1;
    var progressBarType = 1;
    var isdingtalkFreeUser = 0
    var themeId = "";
    var fengmainId = "";
    var backgroundId = "";
    var useNewAppearance = "1";
    var hasTouPiao =0;
    var compressPictures =0;
    var completeResultType = 1;

    jqsign参数加密分析

    加密关键js和代码,如图

    加密关键js和代码

    代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    /*a为网页的window.jqnonce的参数
    ktimes 单击网页的次数
    */
    function dataenc(a,ktimes) {
    var c, d, e, b = ktimes % 10;
    for (0 == b && (b = 1),
    c = [],
    d = 0; d < a.length; d++)
    e = a.charCodeAt(d) ^ b,
    c.push(String.fromCharCode(e));
    return encodeURIComponent(c.join(""));
    }

    dataenc("fcabefd7-5003-480f-a256-6f0e24f813be",2);
    //返回内容
    //dac%60gdf5%2F7221%2F6%3A2d%2Fc074%2F4d2g06d%3A31%60g

    参数

    get参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //这里以https://www.wjx.cn/vm/tuf9t53.aspx问卷为例
    shortid: tuf9t53 //问卷后缀
    starttime: 2022/6/27 22:31:23 //开始时间
    submittype: 1 //提交类型好像(不太清楚)
    ktimes: 49 //好像是统计单击了网页多少次
    hlv: 1
    rn: 1792470105.85906191
    jqpram: hyKUJxeSJ //网页加载后有
    nw: 1
    t: 1656340310465 //时间戳
    jqnonce: 2b1b7383-4cc2-4b14-a29d-4b53af74a1fc
    jqsign: ;k8k>:1:$=jj;$=k8=$h;0m$=k<:ho>=h8oj //具体查看jqsign参数加密分析部分

    post参数

    • 这里以这个问卷全部是单选为例https://www.wjx.cn/vm/tuf9t53.aspx
    • 这个问卷规律很简单
      • }分割题目
      • 单选:1$1 前面的代表题目,后面的代表题目号
    1
    2
    3
    4
    //我这里全选A了,会发现规律了
    } 分割题目
    1$1 前面的代表题目,后面的代表题目号
    submitdata: 1$1}2$1}3$1}4$1}5$1
    • 后面看了下多选的,以这个问卷分析https://www.wjx.cn/vm/mpPVSKK.aspx
    • 这个问卷规律也还挺简单的
      • }分割题目
      • 单选:1$1 前面的代表题目,后面的代表题目号
      • 多选:6$1|2 前面代表题目,后面代表选项,以|分割
    1
    2
    3
    //第六题为多选,我选了1,2,所以这里就出现了
    // 6$1|2
    submitdata: 1$1}2$1}3$1}4$1}5$1}6$1|2}7$1
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/5c543e29.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/5dcd009d.html b/5dcd009d.html new file mode 100644 index 000000000..1a6a77c5e --- /dev/null +++ b/5dcd009d.html @@ -0,0 +1 @@ +使用backdrop-filter实现elementui官网的模糊滤镜效果的和毛玻璃效果 | 梦洁小站-属于你我的小天地

    使用backdrop-filter实现elementui官网的模糊滤镜效果的和毛玻璃效果

    前置

    • element-ui官网有一个属性很好看,可以看到,当滚动的时候,文字会被显示白色带阴影背景

    演示

    • 你可能有点印象,因为公交车的时候也是类似于这种效果

    • 他们是怎么做的呢?我看到源码使用到了
      • backdrop-filter
      • background-size
      • background-image
      • 就实现了,很少的属性,达到了不错的效果,值得学习

    element-ui开始

    了解backdrop-filter和filter属性

    • backdrop-filter属性

      • 可以让你为一个元素后面区域添加图形效果(如模糊或颜色偏移)。因为它适用于元素背后的所有元素,为了看到效果,必须使元素或其背景至少部分透明。
      • 说通俗点就是通过设置A上面的B元素来达到对A模糊或颜色偏移的效果
    • filter属性

      • 将模糊或颜色偏移等图形效果应用于元素。滤镜通常用于调整图像、背景和边框的渲染。
    • 二者区别

      • backdrop-filter作用于元素的背景(不直接作用于元素,而是通过另外一层元素来达到效果)
        • 作用于元素背后的所有元素
      • filter直接设置在元素身上
        • 作用于当前元素,并且它的后代元素也会继承这个属性
    • 二者支持的滤镜对比(其实filter可以用的,backdrop-filter都可以用)

    filterbackdrop-filter备注
    url获取指向SVG过滤器的URI
    blur(模糊)高斯模糊滤镜
    brightness(亮度)图像明亮度的滤镜
    contrast(对比度)图像的对比度滤镜
    drop-shadow(阴影)图像的阴影滤镜
    grayscale(灰度)图像灰度滤镜
    hue-rotate(色相旋转)图像色相滤镜
    invert(反色)反转滤镜
    opacity(透明度)透明度滤镜
    sepia深褐色滤镜
    saturate(褐色)图像饱和度滤镜
    • element-ui头部栏就是通过设置blursaturate属性来达到的

    一步步分解

    • 找到官网,复制粘贴代码(以将变量全部转化为具体的值)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    .navbar-wrapper {
    position: relative;
    border-bottom: 1px solid #dcdfe6;
    height: 55px;
    background-image: radial-gradient(transparent 1px,#ffffff 1px);
    background-size: 4px 4px;
    backdrop-filter: saturate(50%) blur(4px);
    top: 0;
    }
    • 我们重点关注下面几个属性
      • background-image: radial-gradient(transparent 1px,#ffffff 1px);
      • background-size: 4px 4px;
      • backdrop-filter: saturate(50%) blur(4px);

    为什么设置background-size和background-image

    • background-size:设置背景图片大小,这个不用多说

    • background-image设置背景图片的大小

    • 所以我们如果只设置这二个值会发生什么?为了便于观看,我将background-image换为了一张具体图片,便于查看

    1
    2
    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
    <html lang="en">
    <head>
    <meta charset="UTF-8" />
    <title>Document</title>
    <style>
    .wrapper {
    width: 500px;
    height: 300px;
    background: url("../image/26.jpg")
    }
    .content {
    width: 300px;
    height: 150px;
    /* background-image: radial-gradient(transparent 1px, #ffffff 1px); */
    /* background-size: 4px 4px; */
    /* backdrop-filter: saturate(50%) blur(4px); */
    background-image: url('../image/27.jpg');
    background-size: 4px 4px;
    }
    </style>
    </head>
    <body>
    <div class="wrapper">
    <div class="content"></div>
    </div>
    </body>
    </html>

    初次的时候你可能看到这个效果

    是不是觉得这个点点是什么,我们放大看看

    • 原来是一个一个的背景图,因为我们设置了background-size尺寸,然后默认背景会铺满全部的,所以就导致这效果
    • 现在我们讲background-sizebackground-image换为element-ui上的属性,看看会发生什么
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    .content {
    width: 300px;
    height: 150px;

    /* 换为element-ui官网的 */
    background-image: radial-gradient(transparent 1px, #ffffff 1px);
    background-size: 4px 4px;

    /*backdrop-filter:saturate(50%) blur(4px); */
    /* background-image: url('../image/27.jpg');
    background-size: 4px 4px; */
    }
    • 可以看到,只不过背景被替换为了一个一个的小渐变圆点
    效果-未设置backdrop-filter

    最终

    • 我们最后添加上backdrop-filter样式
    1
    2
    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
    <html lang="en">
    <head>
    <meta charset="UTF-8" />
    <title>Document</title>
    <style>
    .wrapper {
    width: 500px;
    height: 300px;
    background: url("../image/26.jpg")
    }
    .content {
    width: 300px;
    height: 150px;

    /* 换为element-ui官网的 */
    background-image: radial-gradient(transparent 1px, #ffffff 1px);
    background-size: 4px 4px;
    backdrop-filter:saturate(50%) blur(4px);

    /* background-image: url('../image/27.jpg');
    background-size: 4px 4px; */
    }
    </style>
    </head>
    <body>
    <div class="wrapper">
    <div class="content"></div>
    </div>
    </body>
    </html>
    效果-设置backdrop-filter
    • 你可以对比看看,添加backdrop-filter在这里也就多了一层朦胧的效果

    • 你也可以扩大一点,使得A身上的B元素和A一样宽度,就成了下面这种效果

    1
    2
    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
    <html lang="en">
    <head>
    <meta charset="UTF-8" />
    <title>Document</title>
    <style>
    .wrapper {
    width: 500px;
    height: 300px;
    background: url("../image/26.jpg")
    }
    .content {
    width: 500px;
    height: 300px;
    /* 换为element-ui官网的 */
    background-image: radial-gradient(transparent 1px, #ffffff 1px);
    background-size: 4px 4px;
    backdrop-filter:saturate(50%) blur(4px);

    /* background-image: url('../image/27.jpg');
    background-size: 4px 4px; */
    }
    </style>
    </head>
    <body>
    <div class="wrapper">
    <div class="content"></div>
    </div>
    </body>
    </html>

    毛玻璃效果

    • 可以看到,下面这张gif实现了一个很漂亮的gif,那么做的呢?也是通过backdrop-filter来实现的,由于找不到这张图就源代码,就自己写了下~
    • 在线地址@地址

    最终完成效果

    先编写一个静态页面

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
    *{
    margin: 0;
    padding: 0;
    background-color: #000000;
    }
    /* 外层-实现居中 */
    #wrapper{
    width: 40%;
    height: 40%;
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
    margin: auto;
    }
    /* 圆点形状 */
    .dot{
    width: 120px;
    height: 120px;
    border-radius: 50%;
    background-color: #00ffc6;
    }
    .dot-wrapper{
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    }
    .dot-wrapper div:nth-of-type(1){
    animation: dotLeft linear infinite 5s;
    }
    .dot-wrapper div:nth-of-type(2){
    align-self: flex-end;
    }
    /* 模糊背景div */
    .mask-wrapper{
    width: 90%;
    height: 70%;
    position: absolute;
    bottom: 0;
    top: 0;
    left: 0;
    right: 0;
    margin: auto;
    border-radius: 10px;
    background-color: rgba(255, 255, 255, .1);/* 透明背景 */
    backdrop-filter: blur(5px);
    box-shadow: -1px 0px rgba(255, 255, 255, .4);
    color: #e9e9e9;
    font-size: 26px;
    }
    </style>
    </head>
    <body>
    <div id="wrapper">
    <!-- 圆点的 -->
    <div class="dot-wrapper">
    <div class="dot"></div>
    <div class="dot"></div>
    </div>
    <!-- 模糊背景 -->
    <div class="mask-wrapper">
    <div style="background-color:transparent;height: 100%;text-align: center;transform: translateY(40%);">
    梦洁小站
    </div>
    </div>
    </div>
    </body>
    </html>

    添加一点动画

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
    *{
    margin: 0;
    padding: 0;
    background-color: #000000;
    }
    /* 外层-实现居中 */
    #wrapper{
    width: 40%;
    height: 40%;
    position: absolute;
    left: 0;
    right: 0;
    top: 0;
    bottom: 0;
    margin: auto;
    }
    /* 圆点形状 */
    .dot{
    width: 120px;
    height: 120px;
    border-radius: 50%;
    background-color: #00ffc6;
    }
    .dot-wrapper{
    width: 100%;
    height: 100%;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    }
    @keyframes dotLeft {
    0%{
    transform: translate(0,0);
    }
    50%{
    transform: translate(16px,-16px);
    }
    100%{
    transform: translate(0,0);
    }
    }
    .dot-wrapper div:nth-of-type(1){
    animation: dotLeft linear infinite 5s;
    }
    @keyframes dotRight {
    0%{
    transform: translate(0,0);
    }
    50%{
    transform: translate(-20px,20px);
    }
    100%{
    transform: translate(0,0);
    }
    }
    .dot-wrapper div:nth-of-type(2){
    align-self: flex-end;
    animation: dotRight linear infinite 5s;
    }
    @keyframes maskAni{
    0%{
    transform: translate(0,0);
    }
    25%{
    transform: translate(-4px,6px);
    }
    50%{
    transform: translate(4px,0);
    }
    100%{
    transform: translate(0,0);
    }
    }
    /* 模糊背景div */
    .mask-wrapper{
    width: 90%;
    height: 70%;
    position: absolute;
    bottom: 0;
    top: 0;
    left: 0;
    right: 0;
    margin: auto;
    border-radius: 10px;
    background-color: rgba(255, 255, 255, .1);/* 透明背景 */
    backdrop-filter: blur(5px);
    box-shadow: -1px 0px rgba(255, 255, 255, .4);
    color: #e9e9e9;
    font-size: 26px;
    animation: maskAni linear infinite 5s;
    }
    </style>
    </head>
    <body>
    <div id="wrapper">
    <!-- 圆点的 -->
    <div class="dot-wrapper">
    <div class="dot"></div>
    <div class="dot"></div>
    </div>
    <!-- 模糊背景 -->
    <div class="mask-wrapper">
    <div style="background-color:transparent;height: 100%;text-align: center;transform: translateY(40%);">
    梦洁小站
    </div>
    </div>
    </div>
    </body>
    </html>

    完成效果

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/5dcd009d.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/607299df.html b/607299df.html new file mode 100644 index 000000000..f485eea66 --- /dev/null +++ b/607299df.html @@ -0,0 +1 @@ +使用输入法自定义短语一步创建Hexo front-matter格式 | 梦洁小站-属于你我的小天地

    使用输入法自定义短语一步创建Hexo front-matter格式

    使用输入法自定义短语一步创建Hexo front-matter格式

    • 有时候书写博客,总是要输入front-matter,很麻烦,费事,输入法的自定义短语可以很好解决这个麻烦
    • 也欢迎大家来我博客看看

    使用教程

    1.找到高级-自定义短语设置

    2.粘贴对应输入法的语法格式然后确定即可

    3.效果

    搜狗输入法的自定义短语创建front-matter

    1
    2
    3
    4
    5
    6
    7
    8
    #---
    title:
    tags:[]
    categories:[]
    date: $year-$month-$day $fullhour:$minute:$second
    updated: $year-$month-$day $fullhour:$minute:$second
    index_img:
    ---

    百度输入法的自定义短语创建front-matter

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    1,ifm=#{Hexo front-matter}#---
    \title:
    \id: $(month_mm)$(day_dd)$(fullhour)$(minute)$(second)
    \date: $(year)-$(month_mm)-$(day_dd) $(fullhour):$(minute):$(second)
    \updated: $(year)-$(month_mm)-$(day_dd) $(fullhour):$(minute):$(second)
    \categories:
    \tags:
    \permalink: https://likianta.coding.me/2018/category/$(month_mm)$(day_dd)$(fullhour)$(minute)$(second)/
    \---
    \
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/607299df.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/60af1bf8.html b/60af1bf8.html new file mode 100644 index 000000000..0a1fe73a5 --- /dev/null +++ b/60af1bf8.html @@ -0,0 +1 @@ +vue当中script setup语法糖 | 梦洁小站-属于你我的小天地

    vue当中script setup语法糖

    前置

    • 发现vue3的<script setup>很好用,就学习了下

    https://cn.vuejs.org/guide/typescript/composition-api.html#typing-component-props

    https://cn.vuejs.org/api/sfc-script-setup.html

    https://blog.csdn.net/weixin_45203607/article/details/123130829

    Vue3-script-setup的作用

    引入组件后可以不用手动去components当中手动在输入名称注册了,会自动注册组件

    • 之前的(需要引入并注册)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <template>
    <UserInfo></UserInfo>
    </template>
    <script>
    import UserInfo from './components/UserInfo.vue';
    export default {
    name: "App",
    components:{
    UserInfo,
    },
    setup(){
    }
    };
    </script>
    • 之后的(使用了<script setup>)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <style scoped></style>

    <template>
    <UserInfo></UserInfo>
    </template>

    <script setup lang="ts">
    // This starter template is using Vue 3 <script setup> SFCs
    // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
    import UserInfo from "./components/UserInfo.vue";
    </script>

    在setup或者data当中写好的数据可以不用return了

    • 之前的(需要return)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <template>
    <UserInfo></UserInfo>
    {{name}}
    </template>

    <script>
    import { ref } from 'vue';
    import UserInfo from './components/UserInfo.vue';
    export default {
    name: "App",
    components:{
    UserInfo,
    },
    setup(){
    const name = ref('梦洁小站');
    return {
    name,
    }
    }
    };
    </script>
    • 之后的(不需要return)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <style scoped></style>

    <template>
    <UserInfo></UserInfo>
    {{ name }}
    </template>

    <script setup lang="ts">
    import { ref } from "vue";
    import UserInfo from "./components/UserInfo.vue";
    const name = ref("梦洁小站");
    </script>
    • 等等等作用~

    使用script setup语法糖的时候要怎么使用props,emits呢?

    defineProps-使用props接收数据

    • 之前的使用(没有使用语法糖获取props的写法)

    App.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <template>
    <UserInfo :sex="sex"></UserInfo>
    </template>

    <script>
    import { ref } from 'vue';
    import UserInfo from './components/UserInfo.vue';
    export default {
    name: "App",
    components:{
    UserInfo,
    },
    setup(){
    const name = ref('梦洁小站');
    const sex = ref('男-我是男孩子!');
    return {
    name,
    sex,
    }
    }
    };
    </script>

    UserInfo.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <template>
    您好
    <p>性别:{{sex}}</p>
    </template>

    <script>
    export default {
    name: 'UserInfo',
    props:{
    sex:String,
    },
    setup(props,{emit}){
    return {
    sex:props.sex,
    }
    }
    }
    </script>
    • 可以看到,还是比较麻烦的,接下来我们看下使用了<script setup>的写法吧

    App.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <style scoped></style>

    <template>
    <UserInfo :sex="sex"></UserInfo>
    </template>

    <script setup lang="ts">
    import { ref } from "vue";
    import UserInfo from "./components/UserInfo.vue";
    const name = ref("梦洁小站");
    const sex = ref("男-我是男孩子");
    </script>

    UserInfo.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <style scoped>

    </style>
    <template>
    你好
    <p>性别:{{sex}}</p>
    </template>
    <script setup lang="ts">

    const props = defineProps({
    sex:String,
    })
    </script>

    defineEmits-使用emit触发自定义事件

    • 之前的emit写法

    App.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <template>
    <UserInfo @sayHello="sayHello"></UserInfo>
    </template>

    <script>
    import UserInfo from "./components/UserInfo.vue";
    export default {
    name: "App",
    components: {
    UserInfo,
    },
    setup() {
    const sayHello = (value) => {
    console.log("我会说sayHello哦", value);
    };
    return {
    sayHello,
    };
    },
    };
    </script>

    <style scoped></style>

    UserInfo.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <template>
    <button @click="handleClick">单击我-userInfo</button>
    </template>

    <script>
    export default {
    name: "UserInfo",
    emits:['sayHello'],
    setup(props, { emit }) {
    const handleClick = () => {
    console.log("111");
    emit("sayHello", "我是UserInfo传的消息");
    };
    return {
    handleClick,
    };
    },
    };
    </script>

    <style scoped></style>

    • 使用了<script setup>语法糖的写法

    App.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <style scoped></style>

    <template>
    <UserInfo @sayHello="sayHello"></UserInfo>
    </template>

    <script setup lang="ts">
    import UserInfo from "./components/UserInfo.vue";
    const sayHello = (value: string): void => {
    console.log("我会说sayHello哦", value);
    };
    </script>

    UserInfo.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <style scoped></style>
    <template>
    <button @click="handleClick">单击我-userInfo</button>
    </template>
    <script setup lang="ts">
    const emit = defineEmits(["sayHello"]);
    const handleClick = () => {
    emit("sayHello", "我是UserInfo传的消息");
    };
    </script>

    使用顶层 await结果代码会被编译成 async setup()

    App.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <style scoped></style>

    <template>
    <Suspense>
    <UserInfo></UserInfo>
    </Suspense>
    </template>

    <script setup lang="ts">
    import UserInfo from "./components/UserInfo.vue";
    </script>

    • async setup() 必须与 Suspense 内置组件组合使用,Suspense 目前还是处于实验阶段的特性,会在将来的版本中稳定

    UserInfo.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <style scoped></style>
    <template>
    </template>
    <script setup lang="ts">
    import axios from "axios";
    //请求数据
    const data = await axios("https://api.oick.cn/lishi/api.php");
    console.log(data);
    </script>

    注意点

    • 如果在vue中使用ts,那么引入组件的时候,最好加上去,否者可能会导致报错!!

    正确的

    1
    import UserInfoVue from './components/UserInfo.vue';

    错误的

    错误提示:找不到模块“./components/UserInfo”或其相应的类型声明。ts(2307)

    1
    import UserInfoVue from './components/UserInfo';

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/60af1bf8.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/63116b94.html b/63116b94.html new file mode 100644 index 000000000..f10622211 --- /dev/null +++ b/63116b94.html @@ -0,0 +1 @@ +vue静态资源的引用(相对路径,绝对路径,@,~的一些笔记,以图片引入为例,含在线演示) | 梦洁小站-属于你我的小天地

    vue静态资源的引用(相对路径,绝对路径,@,~的一些笔记,以图片引入为例,含在线演示)

    前置

    引入的方式

    相对路径的引用

    webpack的处理

    • 路径以.开头,会被 webpack 处理
    • 在其编译过程中,所有诸如
    • <img src="...">background: url(...) 和 CSS @import 的资源 URL 都会被解析为一个模块依赖

    例如,url(./image.png)会被编译为require("./image.png")而:

    1
    <img src="./image.png">

    将会被编译到:

    1
    h('img', { attrs: { src: require('./image.png') }})
    • 并且,会根据图片的大小来决定是否转化为base64编码图片

    相对路径下的URL转换规则

    • 如果 URL 以 . 开头,它会作为一个相对模块请求被解释且基于你的文件系统中的目录结构进行解析。
      • 大白话就是哪里引入的,就相当与哪一个文件的路径
    • <img src="./img/28.jpg" alt="" />就表示引入当前组件下img目录下的28.jpg文件

    示例如下

    src/components/HelloWorld.vue

    1
    2
    3
    4
    5
    6
    <template>
    <div class="hello">
    <!-- 相对路径的引入静态资源 -->
    <img src="./img/28.jpg" alt="" />
    </div>
    </template>

    引入的就是src/components/img/28.jpg,参照于HelloWord.vue所在的路径,也就是以src/components为起始点,去寻找img目录下的28.jpg文件

    绝对路径的引用

    • 需要知道的是在vue当中,绝对路径的表示是以URL为/开头的,或是省略了/或者是.
      • 比如/images/foo.png就表示是一个绝对路径在vue当中
      • images/foo.png也表示一个绝对路径在vue当中

    webpack的处理

    • 依旧会根据图片的大小来决定是否转化为base64编码图片

    绝对路径下的URL转换规则

    • 绝对路径的表示是以URL为/开头的,或是省略了/或者是.

      • 比如/images/foo.png就表示是一个绝对路径在vue当中

      • images/foo.png也表示一个绝对路径在vue当中

    • 转换规则很简单,绝对路径的转换规则就是以public为起始点

      • <img src="/resource/img/pic1.png" alt="" />
      • 就表示引入public/img/pic1.png文件

    示例代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <template>
    <div class="hello">
    <!-- 相对路径的引入静态资源 -->
    <img src="./img/28.jpg" alt="" title="相对路径的引入静态资源" />
    <!-- url路径含有@ -->
    <img src="@/components/img/28.jpg" alt="" title="url路径含有@" />
    <!-- 绝对路径的引用 -->
    <img src="/resource/img/pic1.png" alt="" />
    <img src="resource/img/pic1.png" alt="" />
    </div>
    </template>
    • 在绝对路径的引用当中
      • src="/resource/img/pic1.png"src="resource/img/pic1.png"
      • 引入的都是public/img/pic1.png文件

    含有‘@’路径URL的转换规则

    • 如有URL以@开头,它也会作为一个模块请求被解析,它的用户在于Vue Cli默认会设置一个执行<projectRoot>/src的别名@(仅作用于模板中)
      • 大白话说:就是看到路径有@了,就说明是项目路径/src路径为起始点了

    src/components/HelloWorld.vue

    1
    2
    3
    4
    5
    6
    7
    8
    <template>
    <div class="hello">
    <!-- 相对路径的引入静态资源 -->
    <img src="./img/28.jpg" alt="" />
    <!-- url路径含有@ 二个写法都是一样的-->
    <img src="@/components/img/28.jpg" alt="" />
    </div>
    </template>
    • 上面代码的示例当中
      • src = "./img/28.jpg"src="@/components/img/28.jpg"都是指向src/components/img/28.jpg的图片文件

    • 并且图片也都可以这样子引入
      • <img :src="require('@/assets/46.jpg')" alt="">

    含有’~’路径URL的转换规则(主要使用好像是css里面)

    • 含有~路径URL多用于css当中
    • vue的解释
      • 如果 URL 以 ~ 开头会作为一个模块请求被解析从node_modules中引用资源。
      • 反正我是看不太懂vue的解释是什么意思….所以这里就记录下使用情况,多在css中使用

    css里面,如果使用相对路径去引入图片,那没有什么区别,都会引入src/assets/26.jpg文件

    1
    2
    3
    4
    5
    6
    <style>
    .show-bg {
    /* 使用相对路径引入图片在css中 */
    background-image: url("../assets/26.jpg");
    }
    </style>

    如果使用了绝对路径去引入图片,那么不添加~,就会报错

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <style>
    .show-bg {
    /* 使用相对路径引入图片在css中 */
    /* background-image: url("../assets/26.jpg"); */
    /* 使用绝对路径引入图片在css中 */
    background-image: url("@/assets/26.jpg");

    }
    </style>

    报错信息

    所以需要添加下~就可以

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <style>
    .show-bg {
    /* 使用相对路径引入图片在css中 */
    /* background-image: url("../assets/26.jpg"); */
    /* 使用绝对路径引入图片在css中 */
    background-image: url("~@/assets/26.jpg");

    }
    </style>

    data当中图片数据的引入

    • 一开始我以为直接在data数据当中写图片路径就可以,然后循环,后面发现不可以
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <template>
    <div>
    <div class="show-pic">
    <!-- :src="picPath" 还是require(picPath) 都不可以 -->
    <img
    v-for="(picPath, index) in imgList"
    :key="index"
    :src="picPath"
    alt="123"
    />
    </div>
    </div>
    </template>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    data() {
    return {
    name: "李白",
    imgList: [
    "./assets/46.jpg",
    "./assets/47.jpg",
    "./assets/48.jpg"],
    };
    },
    • 后面发现需要先在data当中引入才可以
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <template>
    <div>
    <div class="show-pic">
    <img
    v-for="(picPath, index) in imgList"
    :key="index"
    :src="picPath"
    alt="123"
    />
    </div>
    </div>
    </template>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    data() {
    return {
    name: "李白",
    imgList: [
    require("./assets/46.jpg"),
    require("./assets/47.jpg"),
    require("./assets/48.jpg")],
    };
    },

    总结

    vue引入静态资源有相对路径绝对路径的方式

    相对路径是URL开头有.

    绝对路径是URL开头为/或者省略不写

    相对路径相对的是当前文件

    绝对路径相对的是public目录

    并且引入的时候URL开头为~的使用在css里面引入静态资源的时候

    如果 URL 以 @ 开头会作为一个模块请求被解析。Vue CLI 默认会设置一个指向 src 的别名 @ 。

    参考文章

    https://segmentfault.com/a/1190000021485662

    https://segmentfault.com/a/1190000019495695

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/63116b94.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/638af414.html b/638af414.html new file mode 100644 index 000000000..20cee2ccd --- /dev/null +++ b/638af414.html @@ -0,0 +1 @@ +vue的h渲染函数和customRender在ant design vue的table组件的使用 | 梦洁小站-属于你我的小天地

    vue的h渲染函数和customRender在ant design vue的table组件的使用

    需求和解决

    • 使用ant design vue 的table组件,没有使用插槽的情况下,我想你给我使用tooltip,这样子我就不用又写插槽又写html结构了
    • 因为我们使用table组件,想自定义结构,一般是先使用插槽,然后插槽填写内容,比如下面做法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    const columns = [
    {
    title:'演示',
    dataIndex:'demo',
    slots:{customRender:'demo'},
    ellipsis:true,
    align:'center',

    },
    ]
    <a-table :columns="columns">
    <template #demo={record,text}>
    <!--要书写的结构-->
    <a-tooltip>
    <template #title>我是内容</template>
    我是内容
    </a-tooltip>
    </template>
    </a-table>
    • 这个时候我们可以使用组件列表columns说明的customRender属性来简化这一个操作
    1
    2
    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
    import {h} from "vue";
    import {Tooltip} from "ant-design-vue";
    columns = [
    {
    title:'演示',
    dataIndex:'demo',
    slots:{customRender:'demo'},
    ellipsis:true,
    align:'center',
    customRender:({record})=>{
    return h(Tooltip,{title:record.corpName,placement:'top'},{ default: () => record.corpName })
    }
    }
    ]

    <a-table :columns="columns"></a-table>

    <!--这上面一行代码就等同于下面的代码-->

    <a-table :columns="columns">
    <template #demo={record,text}>
    <!--要书写的结构-->
    <a-tooltip>
    <template #title>我是内容</template>
    我是内容
    </a-tooltip>
    </template>
    </a-table>

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/638af414.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/64b8ea48.html b/64b8ea48.html new file mode 100644 index 000000000..6cfe11797 --- /dev/null +++ b/64b8ea48.html @@ -0,0 +1 @@ +win11安装系统提示virtualBox不兼容需要卸载virtual的解决办法,但是卸载列表找不到virtual的解决办法 | 梦洁小站-属于你我的小天地

    win11安装系统提示virtualBox不兼容需要卸载virtual的解决办法,但是卸载列表找不到virtual的解决办法

    win11安装系统提示virtualBox不兼容需要卸载的解决办法

    • 提示如图,立即卸载此应用,因为他与window 10不兼容

    解决办法

    • 找到文件夹C:\Program Files\ldplayerbox,然后把VirtualBox.exe这一个文件夹删除即可解决

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/64b8ea48.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/65204d30.html b/65204d30.html new file mode 100644 index 000000000..c43a444f4 --- /dev/null +++ b/65204d30.html @@ -0,0 +1 @@ +vue-router当中内置component标签(组件)的使用 | 梦洁小站-属于你我的小天地

    vue-router当中内置component标签(组件)的使用

    说明

    • component为vue内置特殊元素,一个用于渲染动态组件或元素的’元组件’
    • 具体可以看@官网

    使用

    • 大概使用就是使用component当中的属性is,这个is可以传入字符串,也可以传入组件

      • <component :is="showChildA"/>其中 showChildA为一个组件
    • 示例

      • 根据不同的参数来决定渲染哪一个组件

    App.vue

    1
    2
    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
    <template>
    <div>
    我是主页
    <component :is="showComponent"></component>
    </div>
    </template>

    <script setup>
    import {ref,computed} from "vue";
    import ChildA from "./pages/ChildA.vue"
    import ChildB from "./pages/ChildB.vue"
    const componentAll = {
    son1:ChildA,
    son2:ChildB,
    }

    const receiveTitle = ref('son1');//假设从别的地方接收到一个属性

    const showComponent = computed(()=>{
    return componentAll[receiveTitle.value]
    })
    console.log(showComponent);
    </script>

    <style scoped>
    </style>

    ChildA.vue

    1
    2
    3
    4
    5
    6
    7
    8
    <template>
    <div>
    <h1>我是ChildA</h1>
    </div>
    </template>

    <script setup>
    </script>

    ChildB.vue

    1
    2
    3
    4
    5
    6
    7
    8
    <template>
    <div>
    <h1>我是ChildB</h1>
    </div>
    </template>

    <script setup>
    </script>

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/65204d30.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/66bcb49.html b/66bcb49.html new file mode 100644 index 000000000..af7f63c4f --- /dev/null +++ b/66bcb49.html @@ -0,0 +1 @@ +defineAsync-Suspense学习 | 梦洁小站-属于你我的小天地

    defineAsync-Suspense学习

    前言

    defineAsyncComponent

    1
    2
    import Home from "./components/Home/index.vue"
    import Center from "./components/Center/index.vue"
    • 这样子就算你使用了v-show还是v-if,也会加载资源,不相信可以看控制台~

    • 所以可以通过defineAsyncComponent进行优化

    • 参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    const AsyncFooWithOptions = defineAsyncComponent({
    loader: () => import("./demo.vue"),
    //加载过程中的组件
    loadingComponent: LoadingComponent,
    //加载失败的组件
    errorComponent: ErrorComponent,
    // 在显示loadingComponent组件之前, 等待多长时间,在加载组件显示之前有一个默认的 200ms 延迟——这是因为在网络状况较好时,加载完成得很快,加载组件和最终组件之间的替换太快可能产生闪烁,反而影响用户感受。
    delay: 200,
    //加载组件的超时时间,如果超过这个值,则显示错误组件, 默认Infinity永不超时, 单位ms
    timeout: 3000
    //定义组件是否可以挂起, 默认true
    suspensible:true,
    /** 异步组件加载失败的回调函数
    * err: 错误信息,
    * retry: 函数, 调用retry尝试重新加载
    * fail: 函数, 指示加载程序结束退出
    * attempts: 记录尝试的次数
    */
    onError: function(err, retry, fail, attempts) {
    }
    })

    • 对应ts
    1
    2
    3
    4
    5
    6
    7
    8
    9
    export interface AsyncComponentOptions<T = any> {
    loader: AsyncComponentLoader<T>;
    loadingComponent?: Component;
    errorComponent?: Component;
    delay?: number;
    timeout?: number;
    suspensible?: boolean;
    onError?: (error: Error, retry: () => void, fail: () => void, attempts: number) => any;
    }
    • 当我们使用了defineAsyncComponent的时候,就会发现,我们只有在需要的时候才会被导入
      • 注意,如果用v-show逻辑判断,依旧会被提前导入,v-if才会在需要的时候导入
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    <style scoped>

    </style>
    <template>
    <button @click="handleHome">切换到首页</button>
    <button @click="handleCenter">切换到个人中心页面</button>
    <AsyncHome v-if="isShowHome"/>
    <AsyncCenter v-if="!isShowHome"/>
    </template>
    <script setup lang="ts">
    import {defineAsyncComponent,ref} from "vue";
    import Loading from "./components/Loading/index.vue"

    const AsyncHome = defineAsyncComponent({
    loader: () => import("./components/Home/index.vue"),
    loadingComponent:Loading,
    delay:200,
    })

    const AsyncCenter = defineAsyncComponent({
    loader: async () => {
    await new Promise(resolve => {
    setTimeout(() => {resolve()},2000);
    //加载5秒后返回
    })
    return import("./components/Center/index.vue")
    },
    loadingComponent:Loading,
    delay:200,
    })

    const isShowHome = ref<boolean>(true);

    const handleHome = () => {
    isShowHome.value = true;
    }

    const handleCenter = () => {
    isShowHome.value = false;
    }
    </script>

    • 效果,点击个人中心后,延迟200毫秒后加载AsyncCenter,模拟等待2秒后组件加载完毕

    Suspense

    • vue官方文档:https://cn.vuejs.org/guide/built-ins/suspense.html

    • 作用是什么呢?我理解的就是统一异步加载状态

      • 这意味着如果一个组件的父链中有Suspense,它将被视为该Suspense的一个异步依赖。我们的组件的加载、错误、延迟和超时选项将被忽略,而是由Suspense来处理。
    • <Suspense> 组件有两个插槽:#default#fallback。两个插槽都只允许一个直接子节点。在可能的时候都将显示默认槽中的节点。否则将显示后备槽中的节点。

      • 两个插槽都只允许一个直接子节点意思是
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      //下面是正确的示例
      <Suspense>
      <template #default>
      <div>
      <button @click="handleHome">切换到首页</button>
      <button @click="handleCenter">切换到个人中心页面</button>
      </div>
      </template>
      </Suspense>
      //下面是错误的示例 vue不会渲染
      <Suspense>
      <button @click="handleHome">切换到首页</button>
      <button @click="handleCenter">切换到个人中心页面</button>
      </Suspense>
    • 代码示例

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    <style scoped>

    </style>
    <template>
    <Suspense>
    <template #default>
    <div>
    <button @click="handleHome">切换到首页</button>
    <button @click="handleCenter">切换到个人中心页面</button>
    </div>
    </template>
    <template #fallback>
    <Loading/>
    </template>
    </Suspense>
    <AsyncHome v-if="isShowHome"/>
    <AsyncCenter v-if="!isShowHome"/>
    </template>
    <script setup lang="ts">
    import {defineAsyncComponent,ref} from "vue";
    import Loading from "./components/Loading/index.vue"
    import Loading2 from "./components/Loading/index.vue"

    const AsyncHome = defineAsyncComponent({
    loader: () => import("./components/Home/index.vue"),
    loadingComponent:Loading2,
    delay:200,
    })

    const AsyncCenter = defineAsyncComponent({
    loader: async () => {
    await new Promise(resolve => {
    setTimeout(() => {resolve()},2000);
    //加载5秒后返回
    })
    return import("./components/Center/index.vue")
    },
    loadingComponent:Loading2,
    delay:200,
    })

    const isShowHome = ref<boolean>(true);

    const handleHome = () => {
    isShowHome.value = true;
    }

    const handleCenter = () => {
    isShowHome.value = false;
    }
    </script>

    • 说明在,异步组件的Loading提示文本应该是Loading2(加载中),而通过Suspense,自动忽略异步组件Loading状态,采用Suspense当中的加载说明,这一点上面提到过

    参考学习文章

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/66bcb49.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/6708b1a2.html b/6708b1a2.html new file mode 100644 index 000000000..7d191add6 --- /dev/null +++ b/6708b1a2.html @@ -0,0 +1 @@ +我来图书馆小程序一键签到和一键抢位置 | 梦洁小站-属于你我的小天地

    我来图书馆小程序一键签到和一键抢位置

    注意

    • openid需要自己抓包,这个没有办法~
    • 还有,抢位置目前只做了4楼,3楼没必要~
    • 签到位置4楼,3楼都可以
    • 区域代码的话使用下面的获取区域代码工具就可以
    • 你怕有毒就别用,我没心思做什么病毒
    • 软件中的openId可能弄错了(今天发现应该是返回的userId,程序说是openId,实际上应该填写抓包的userId,二个字段都试试看吧)

    下载地址

    使用

    1.获取openid((今天发现应该是返回的userId,程序说是openId,实际上应该填写抓包的userId,二个字段都试试看吧)

    • 这个需要抓包,或者要会的人帮你抓包下

    2.通过下面工具找到对应的座位id和区域id

    • 工具有获取区域的,自己找下并格式化下就可以

    3.然后填入对应的位置就可以

    • 就可以使用啦

    截图

    抢位置(4楼)

    签到位置(4楼,3楼都可以)

    区域代码获取(4楼位置)

    • 获取的一般是这种,自己格式化下就好

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/6708b1a2.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/67b77f3c.html b/67b77f3c.html new file mode 100644 index 000000000..ae2f3b6c3 --- /dev/null +++ b/67b77f3c.html @@ -0,0 +1 @@ +vue源码分析-快速版(DMQ的MVVM为例) | 梦洁小站-属于你我的小天地

    vue源码分析-快速版(DMQ的MVVM为例)

    感兴趣的可以下载这位老师的例子

    前置知识

    知道节点

    文档节点

    nodeName(节点名称)nodeType(节点类型)nodeValue(节点值)
    文档节点#document9null
    元素节点标签名1null
    属性节点属性名2属性值
    文本节点#text3文本内容

    知道一些方法或者属性

    • dom元素.childNodes属性,获取dom元素下的所有节点(包括文本节点和其他(换行符也是文本节点))
      • dom元素.children是获取dom元素下的所有的元素节点
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
    <title>Title</title>
    </head>

    <body>
    <div id="d">
    <p>1</p>
    <p>12</p>
    <p>123</p>
    <p>1234</p>
    <p>12345</p>
    </div>
    <script>
    var d = document.getElementById("d");
    console.log(d.childNodes);
    console.log(d.children);
    </script>
    </body>

    </html>

    使用children输出结果

    使用children输出结果

    使用childNodes输出结果

    使用childNodes输出结果

    • 特别注意,如果使用node.appendChild(被添加的节点) 如果被添加的节点存在于原有的文档上面,那么被添加的节点就会被移动到新的位置!

    • node.attributes获取元素节点身上的所有属性(返回一个对象)

      1
      <div id="id" attr="123"></div>

    调用attributes返回截图

    调用attributes

    所以我们可以通过其里面的name,和value等获取属性名称和值

    • node.removeAttribute()从指定的元素中删除一个属性。具体用法

    数据代理理解与原理

    什么是数据代理

    本来我们需要通过this._data.属性名才可以读取到data上的属性值的,但是我们通过数据代理,可以直接通过vm.属性名就可以读取到了

    数据代理干什么的,有什么用

    简化了流程操作,可以看看vuex当中的mapGetters和mapState就是简化了我们的流程

    数据代理原理是什么

    原理就是通过defineProperty给vm添加data当中所有的属性和属性值

    并且通过getter返回所要读取的属性值,比如读取this.msg.那么就自动调用getter当中的方法

    并且通过setter设置要设置的属性值,比如要设置this.msg = "新信息",那么就会自动调用setter方法

    模板解析

    • 模板解析只发生在初始化节点,当初始化完成后,模板代码就会被删除
    • 解析二种模板语法
      • 插值语法: ({{ xxx }}) 操作的是文本节点,通过textContent=动态值和正则来进行匹配
      • 指令语法: 操作元素节点
        • 事件指令(比如说v-on:xxx=xxxx),给元素绑定直接的事件名和回调函数,(通过addEventListener(事件类型,事件回调.bind(vm)))来进行绑定(注意通过bind来改变this的指向了)
        • 非事件指令
          • v-text: element.textContent = 动态值
          • v-html: element.innerHTML = 动态值
          • v-bind:class: element.className = 动态值 + 静态class
      • 除了事件指令外,解析其他模板语法的时候,都会创建一个对应的watcher对象,用于将来更新对应的节点

    数据绑定

    实现数据绑定需要解决的二个问题

    • 如何知道data的属性发生了变化的 (重要)
      • 通过observerdata当中的每一个属性通过数据劫持添加gettersetter
      • 原来data当中的数据是没有gettersetter的,通过数据劫持去添加了gettersetter
    • 那么要如何知道当前这个数据变化要更新哪些节点呢?
      • 通过订阅者/发布者模式

    1.发布者observer

    • 给data中的所有层次的属性都添加settergetter(也就是数据劫持)
    • 同时为data当中的每一个属性创建一个对应的dep对象
    • dep对象data当中的每一个属性为一一对应的对象

    1.5中间还需要一个dep(订阅器),去通知订阅者(watcher)

    • dep和watcher的关系是初始化的时候就建立起来的

    2.订阅者(watcher)

    • 解析每一个模板语法都创建一个watcher(比如模板当中有一个标签使用了插值语法,那就会创建一个watcher,v-bind也会触发创建watcher)
    • 并且创建watcher的时候最后一个参数为用于更新节点的回调函数

    原理分析图如下面二张图

    数据绑定原理图-再次

    数据绑定原理图-再次-再次

    数据双向绑定

    • 理解

      • 以input中的v-model来说,也就是<input v-model=”msg”/>
      • 从data到页面的绑定:输入框是根据data中的msg做初始化显示和更新显示
      • 从页面到data绑定:当输入发生改变时,会自动的将输入的最新值自动保存到datamsg
    • 原理

      • 从vm配置对象当中的data到页面的绑定:内部给input指定了动态valuemsg的值,因为有单向数据绑定的存在,一旦更新msg, 输入框就会自动更新显示
      • **从页面到data绑定:**内部给input绑定了input事件监听(input事件监听是输入的内容和原来内容不同的时候才会执行,而change是改变就触发),在回调函数中读取input最新的value值保存到datamsg属性上
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/67b77f3c.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/6ace453b.html b/6ace453b.html new file mode 100644 index 000000000..612d56537 --- /dev/null +++ b/6ace453b.html @@ -0,0 +1 @@ +前端常用npm库大全-vue,react,通用(持续更新) | 梦洁小站-属于你我的小天地

    前端常用npm库大全-vue,react,通用(持续更新)

    构建工具

    Name/GitHub/NPM描述演示地址
    Vite下一代的前端工具链
    Create React App通过运行一个命令来设置现代 Web 应用程序。
    Create React App中文文档通过运行一个命令来设置现代 Web 应用程序。
    Webpackjs强大的静态模块打包工具,主要用于现代JavaScript应用的构建和打包
    Rollup将点滴代码编织成错综复杂的程序。
    vite-plugin-mock-dev-server在vite 开发环境中注入 mock-dev-server, 模拟请求和数据响应

    小程序/Uniapp

    Name/GitHub/NPM描述演示地址
    微信小程序官方文档入口小程序,小游戏,等文档
    taro
    DonutDonut 平台覆盖开发、部署、产品体验分析全产品开发周期的各种需求。开发者可以专注于代码逻辑,其他的都交给我们。
    uni-helper旨在增强 uni-app 系列产品的开发体验

    基础Vue

    Name/GitHub/NPM描述演示地址
    Vue-RouterVue Router 是 Vue 官方的客户端路由解决方案。
    Vue-Cli🛠️ Vue.js 开发的标准工具
    Vue2渐进式 JavaScript 框架
    Vue3渐进式 JavaScript 框架
    VuexVuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。
    Pinia符合直觉的 Vue.js 状态管理库
    Umijs用 Umi 构建你的下一个Vue应用
    NuxtNuxtJS 让你构建你的下一个 Vue.js 应用程序变得更有信心。这是一个 开源 的框架,让 web 开发变得简单而强大。

    基础React

    Name/GitHub描述演示地址
    preactjsReact 的轻量级替代方案,体积仅有 3kB,并且拥有与 React 相同的 API
    React 文档The library for web and native user interfaces
    cracoCreate React App 配置覆盖,这是一个用于 Create React App 的简单易懂的配置层。
    react-reduxRedux 的官方 React 绑定
    redux-toolkitThe official, opinionated, batteries-included toolset for efficient Redux development
    redux中文文档
    React-router
    NextjsThe React Framework for the Web
    Umijs用 Umi 构建你的下一个 React 应用
    飞冰 (ICE)基于 React 的应用研发框架,开箱即用,同时支持移动端和桌面端

    NPM库

    VUE专用库

    Name/GitHub描述演示地址
    vueuseVue 组合实用程序集合
    vuefireVueFire Official Firebase bindings for Vue.js Idiomatic composables for realtime data and other Firebase services
    vue-grid-layout
    VantUI组件库-Mobile
    Antd-vueUI组件库-PC
    Datav-vueVue 大屏数据展示组件库
    Tdesign-VueUI组件库-PC
    varletUI组件库-PC
    vue-awesome-swiper🏆 Swiper component for @vuejs
    vee-validate
    vue-transitionsReusable interface transitions for Vue 2 and Vue 3 with no CSS needed ❤️
    vue-office支持多种文件(docx、excel、pdf)预览的vue组件库,支持vue2/3。也支持非Vue框架的预览。文档https://501351981.github.io/vue-office/examples/docs/
    vue-plugin-hiprintvue-plugin-hiprint(基于hiprint 2.5.4) 当时只是为了方便我(并非hiprint原作者) 在vue项目中引入使用,所以以此命名。打印模板
    vue-macrosVue Macros 体验超现代 Vue 探索更多的宏和语法糖到 Vue 中。
    pinia-plugin-persistedstatepinia持久化

    React专用库

    Name/GitHub描述演示地址
    AHooks一套高质量可靠的 React Hooks 库
    zustand一个小型、快速、可扩展的熊骨状态管理解决方案,采用简化的通量原理。它有一个基于钩子的舒适应用程序接口,没有模板化,也没有主观臆断。
    dvajsReact and redux based, lightweight and elm-style framework.
    Datav-ReactReact 大屏数据展示组件库
    Tdesign-ReactUI组件库
    Antd-MobileUI组件库-Mobile
    uiwUI组件库-A Component Library for React 16+.
    muiUI组件库-Move faster with intuitive React UI tools
    fusion企业级的中后台设计系统解决方案
    MobX简单、可扩展的状态管理
    emoji-picker-reactEmoji 选择器 React
    ImmerImmer(德语为:always)是一个小型包,可让您以更方便的方式使用不可变状态。

    通用库

    Name/GitHub描述演示地址
    Echarts图表
    Bootcss样式
    Swiper工具-轮播图
    JQueryjQuery 是一个快速、小巧且功能丰富的 JavaScript 库。
    lodashLodash 是一个一致性、模块化、高性能的 JavaScript 实用工具库。
    lottiefiles为您的网站和应用程序提供轻量级、可扩展的动画
    Mock生成随机数据,拦截 Ajax 请求
    Numeral-js用于格式化和处理数字的 javascript 库。
    clipboardjs将文本复制到剪贴板的现代方法 没有 Flash。没有框架。压缩后只有 3kb
    browserslist共享浏览器兼容性配置,适用于 Autoprefixer、Babel、ESLint、PostCSS 和 Webpack 等流行 JavaScript 工具
    Socket.IO支持及时、双向与基于事件的交流。它可以在每个平台、每个浏览器和每个设备上工作,可靠性和速度同样稳定。
    mswMock Service Worker是一个 API 模拟库,允许您编写与客户端无关的模拟并在任何框架、工具和环境中重复使用它们。
    jestjsJest 是一款优雅、简洁的 JavaScript 测试框架。
    animejs是一个轻量级 JavaScript 动画库,具有简单但功能强大的 API。 它可与 CSS 属性、SVG、DOM 属性和 JavaScript 对象配合使用。
    smooth-scrol一个轻量级脚本,用于为滚动到锚点链接制作动画。https://codepen.io/cferdinandi/pen/wQzrdM
    isotope-layout过滤和排序神奇的布局(排序的时候有一个动画效果)
    nodemailerNodemailer是 Node.js 应用程序的一个模块,可让您轻松发送电子邮件。
    cheerio用于解析和操作 HTML 和 XML 的快速、灵活且优雅的库。
    relationship中国亲戚关系计算器 - 家庭称谓/亲戚称呼/称呼计算/辈分计算/亲戚关系算法/親戚稱呼計算機_Chinese kinship system
    mandeSimple, light and extensible wrapper around fetch with smart defaults
    vestjsVest 是一个功能强大且易于使用的 JavaScript 验证框架,可让您编写和运行代码验证
    pinyinpīnyīn, 汉字拼音转换工具。
    aplayer🍭 Wow, such a beautiful HTML5 music player
    fastclickFastClick 是一个简单易用的库,用于消除click移动浏览器上物理点击和触发事件之间的 300 毫秒延迟
    autofit.js迄今为止最易用的自适应工具(描述是这样的)
    howlerhowler.js是一个适用于现代网络的音频库。它默认使用Web Audio API,并可回退到HTML5 Audio。这使得使用 JavaScript 处理音频在所有平台上都变得简单而可靠。
    opentype.js使用 JavaScript 读取和写入 OpenType 字体。
    print-js一个小型的 JavaScript 库,帮助从网络打印。
    Fuse.js强大、轻量级的模糊搜索库,没有任何依赖关系。
    Typr.jsTypr.js - process fonts in Javascripthttps://photopea.github.io/Typr.js/
    midnight.jsMidnight.js 可让你即时切换固定标题https://aerolab.github.io/midnight.js/
    ZeptoZepto是一个适用于现代浏览器的极简 JavaScript 库,具有与jQuery高度 兼容的 API 。 如果您使用 jQuery,那么您已经知道如何使用 Zepto。
    NodemonNodemon 是一个大约有 300 万个项目依赖的实用程序,它将监视源代码中的任何更改并自动重启服务器。非常适合开发。
    alova一行代码完成各种复杂场景的网络请求,别再花时间在请求这件小事上了,交给我们
    magika使用深度学习检测文件内容类型
    JQuery插件库
    帝国CMS源码收费,做来参考吧
    zindexz-index 管理器
    terser适用于 ES6+ 的 JavaScript 压缩器/压缩工具包。
    tippy.js适用于 Web 的完整工具提示、弹出窗口、下拉菜单和菜单解决方案
    YupYup 是一个用于运行时值解析和验证的模式构建器。定义架构、转换值以匹配、断言现有值的形状,或两者兼而有之。是的,模式非常具有表现力,并且允许对复杂的、相互依赖的验证或值转换进行建模。
    jsdomjsdom 是许多 Web 标准(尤其是 WHATWG DOMHTML标准)的纯 JavaScript 实现,用于 Node.js。总体而言,该项目的目标是模拟足够多的 Web 浏览器子集,以便用于测试和抓取真实的 Web 应用程序。
    npm-check-updatesnpm-check-updates (ncu)将您的 package.json 依赖项升级到最新版本,忽略指定的版本。
    highlightjs高亮
    prismjs高亮
    prettier配置生成生成配置
    es-toolkit一个现代 JavaScript 实用程序库,速度提高 2-3 倍,体积缩小 97% — — 这是 lodash 的重大升级。
    xe-utilsjavascript 函数库、工具类 文档地址https://vxetable.cn/xe-utils/#/
    StreamSaver.jsStreamSaver.js 采用了不同的方法。现在,您实际上可以直接在文件系统中创建可写流(我不是在谈论 Chrome 沙盒文件系统或任何其他 Web 存储),而不是将数据保存在客户端存储或内存中。这是通过模拟服务器如何使用某些响应标头 + 服务工作线程指示浏览器保存文件来实现的

    组件开发

    地址描述
    dumidumi,中文发音嘟米,是一款为组件开发场景而生的静态站点框架,与 father 一起为开发者提供一站式的组件开发体验,father 负责组件源码构建,而 dumi 负责组件开发及组件文档生成
    fatherfather 是一款 NPM 包研发工具,能够帮助开发者更高效、高质量地研发 NPM 包、生成构建产物、再完成发布。它主要具备以下特性:
    histoire
    vitepress由 Vite 和 Vue 驱动的静态站点生成器
    VerdaccioVerdaccio 是一个 Node.js创建的轻量的私有npm proxy registry
    rollup-plugin-visualizer一个强大且直观的工具,用于帮助开发者在使用 Rollup 打包时,生成详细的模块依赖图谱。通过可视化的方式,你可以清晰地理解代码的打包结构和优化潜在点,从而更高效地管理和优化你的 JavaScript 应用程序。

    CSS样式

    Name/GitHub描述演示地址
    tailwindcss一个实用优先的 CSS 框架,其中包含诸如、和之类的类,这些类可直接在您的标记中组合以构建任何设计。 flex pt-4 text-center rotate-90
    daisyuiThe most popular component library for Tailwind CSS
    enjoycssEnjoyCSS 是一个先进的 CSS3 生成器,可以让您摆脱常规编码。
    neumorphism盒子阴影样式
    meshgradient渐变
    noiseandgradien渐变
    meshgradient渐变毛玻璃
    navnav一些基础的样式
    oulu一个集合180种免费的线性渐变网站,可在任何网站使用(ps:网站还禁止调试..)
    webgradients线性渐变(比上面的更好)
    coolors调色板生成器!(也就是颜色)
    dribbble从世界各地数百万顶级设计师和机构的作品中获得灵感。
    dribbble下的css_loader
    uiverse使用 CSS 或 Tailwind 制作的自定义元素。UI Verse 是一个开发资源整合类的网站,内部提供了 4500+ 种不同的 css 特效
    加载动画css加载动画
    sliderrevolution不仅仅是一个 WordPress 滑块
    cssloaders很多加载效果css动画
    css-loaders几个加载效果css动画
    loadingAjax 加载器、动画图标、实时背景
    css-box-shadowBeautiful CSS box-shadow examples
    uiverse.ioOpen-Source loaders made with CSS or Tailwind(使用 CSS 或 Tailwind 制作的开源加载器)

    字体和素材库

    Name/GitHub描述演示地址
    iconpark字体图标-丰富多彩的资源库免费使用
    iconfont字体图标
    illust下載免費矢量圖和剪貼畫
    transfonter现代而简单的 css @font-face 生成器
    fontawesomeFont Awesome 是互联网的图标库和工具包,被数百万设计师、开发人员和内容创建者使用。
    iconshock-渐变svg图专为渐变爱好者制作的 免费交互式 SVG 图标包 !
    lexicaAI生成的图片大全
    unsplash也是一个图片网站
    稿定国产的…可能要收费
    创客贴国产的…可能要收费
    Canva可画使用Canva可画,轻松创建并分享专业设计。(可能要收费)
    爱给网爱给网_音效配乐_3D模型_视频素材_免费下载

    文档和工具

    地址描述
    MDN文档
    CanIuse用于前端开发者查询各种Web技术在不同浏览器中的兼容性。其主要功能和用途包括:
    npmTrends用于比较NPM(Node Package Manager)包下载趋势和受欢迎度的工具
    贝赛尔曲线 cubic-bezierjs代码压缩混淆
    jsnice旨在美化和反混淆JavaScript代码。其主要功能和用途包括:
    babel用于将现代JavaScript代码转换为兼容性更好的旧版JavaScript代码。
    less给 CSS 加点料。
    jquery之家自由分享jQuery、html5和css3的插件库
    less编译less编译
    stackoverflow问答交流
    思否问答交流
    文档速查十分推荐
    油猴文档油猴必备
    JSDOCJSDoc 3 是一个用于 JavaScript 的API文档生成器,类似于 Javadoc 或 phpDocumentor
    W3C前端最最最标准文档
    ES6入门-阮一峰ES6入门-阮一峰
    javascript-guidebook📚JavaScript 知识图谱:ECMAScript、DOM、BOM、HTML5、计算机网络
    React中文文档React中文文档
    w3schools也是比较好的文档
    bundlephobia包大小查看

    在线部署/在线运行/第三方服务提供

    地址描述
    netlify
    vercel
    codepen
    codesandbox
    stackblitz
    4everland
    upstash
    planetscaleThe ultimate MySQL database platform
    algolia
    cyclic
    render
    jsfiddle
    jsbin强大的在线编辑
    国内-runjs在线运行
    runjs探索和试验 JavaScript 和 TypeScript,将您的想法形象化并在打字时获得即时反馈。

    跨平台的桌面应用程序

    地址描述
    tauri
    electron-vite
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/6ace453b.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/6b274c12.html b/6b274c12.html new file mode 100644 index 000000000..8ea46f359 --- /dev/null +++ b/6b274c12.html @@ -0,0 +1 @@ +QQ农场-phpYeFarm添加数据教程 | 梦洁小站-属于你我的小天地

    QQ农场-phpYeFarm添加数据教程

    前置知识

    plugin\qqfarm\core\data

    • D:\study-project\testweb\upload\source\plugin\qqfarm\core\data
      • 也就是plugin\qqfarm\core\data
      • 是一个缓存文件,如果更新农场数据后,必须要删除才可以

    解决种子限制(必须要做才可以添加成功)

    • 你不更改加入了id大于2000直接删除种子

    • D:\study-project\testweb\upload\source\plugin\qqfarm\core\source\nc\mod\repertory_getuserseed.php

    • 也就是core\source\nc\mod\repertory_getuserseed.php

    1
    2
    3
    4
    5
    $value > 0  && $key > 0 && array_key_exists($key, $cropstype)  && $key < 2000

    更改为

    $value > 0 && $key > 0 && array_key_exists($key, $cropstype)
    • 也就是如下所示

    农场-添加种子

    • 以添加6097数据为例子

    • 快捷操作(改改变只添加了种子)

      • 截止到2024年4月08日,种子文件下载完成,你可以直接将下面文件,解压后,放置在对应位置,即可更新完成,swf农场id开始为2578

      • 下载地址(任选其一下载)

      • nc_data.php

        • 放置在插件目录下的core\source\xml\mod\nc_data.php
        • 比如D:\study-project\testweb\upload\source\plugin\qqfarm\core\source\xml\mod\nc_data.php
      • cropstime.php

        • 放置在插件目录下的core\config\nc\cropstime.php
        • 比如D:\study-project\testweb\upload\source\plugin\qqfarm\core\config\nc\cropstime.php
      • cropstype.php

        • 放置在插件目录下的core\config\nc\cropstype.php
        • 比如D:\study-project\testweb\upload\source\plugin\qqfarm\core\config\nc\cropstype.php
      • 农场swf文件-crops-swf-20240408-dreamlove

        • 放置在插件目录下的core\module\ui\allcrops
        • 比如D:\study-project\testweb\upload\source\plugin\qqfarm\core\module\ui\allcrops

    1.添加模型

    • 素材放置在路径下
      • 网站目录下的\plugins\qqfarm\core\module\ui\allcrops
      • 比如D:\study-project\farm.dreamlove.top\plugins\qqfarm\core\module\ui\allcrops

    2.添加数据

    • 修改目录
      • 网站目录下plugins\qqfarm\core\config\nc
      • 比如D:\study-project\farm.dreamlove.top\plugins\qqfarm\core\config\nc

    2.1 修改-cropstime.php

    • data_zh_CN_v_1736.xml元素的crops
    • 搜索id为6097的数据,里面的cropGrow则为成长信息

    • 我们添加进去plugins\qqfarm\core\config\nc\cropstime.php
    1
    "6097"=>array(10800,21600,32400,46800,68400,2000000000)

    2.2-修改cropstype.php

    • 打开进去plugins\qqfarm\core\config\nc\cropstype.php
    • 比如之前添加的6081和一个草莓作物
    1
    2
    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
    35
    36
    37
    38
    "6081"=> array("cId"=>6081,"cLevel"=>0,"cName"=>"瑞雪-君子兰","cType"=>1,"cropExp"=>100,"expect"=>19500,"growthCycle"=>68400,"maturingTime"=>2,"output"=>30,"price"=>1000,"sale"=>650)

    //对应的json字段注释
    {
    "6081": {
    "cId": 6081, // 作物ID
    "cLevel": 0, // 作物种植等级(作物等级)
    "cName": "瑞雪-君子兰", // 作物名称
    "cType": 1, // 作物类型
    "cropExp": 100, // 每季种植收获经验值
    "expect": 19500, // 预期金币价值
    "growthCycle": 68400, // 成熟时间(秒)
    "maturingTime": 2, // 多少季作物
    "output": 30, // 预计产量
    "price": 1000, // 种子价格
    "sale": 650 // 单个果实销售价格
    }
    }

    //再看下
    "1"=> array("cId"=>1,"cLevel"=>10,"cName"=>"草莓","cType"=>1,"cropExp"=>20,"expect"=>1296,"growthCycle"=>86400,"maturingTime"=>2,"output"=>24,"price"=>605,"sale"=>27),

    {
    "1": {
    "cId": 1, // 作物ID
    "cLevel": 10, // 作物种植等级(作物等级)
    "cName": "草莓", // 作物名称
    "cType": 1, // 作物类型
    "cropExp": 20, // 每季种植收获经验值
    "expect": 1296, // 预期金币价值
    "growthCycle": 86400, // 成熟时间(秒)
    "maturingTime": 2, // 多少季作物
    "output": 24, // 预计产量
    "price": 605, // 种子价格
    "sale": 27 // 单个果实销售价格
    }
    }

    • 我们现在要添加6097的数据,我们就搜索data_zh_CN_v_1736.xml,找到子元素cropsParames,找到对应的id

    • 找到的数据如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <crop id="6097" show="0" score="0" parent_id="0" star="0" output="30" sale="1000" coupon_price="0" price="6500" qdprice="500" exp="100" g_opt="0" />

    字段含义
    show:是否显示,0隐藏,1显示
    score:
    parent_id
    star
    output产量
    sale果实售价
    coupon_price
    price种子价格
    qdprice种子售卖价格??
    exp收获经验
    g_opt
    • 再从crops元素找到数据如下

    1
    2
    {"asset_id": {"m": "83207", "m0": "83208", "m1": "83209", "m2": "83210", "m3": "83211", "m4": "83212", "s": "83213"}, "cropGrow": "10800,21600,32400,46800,68400,2000000000", "eo": 30, "gossip": "", "harvestNum": 2, "id": 6097, "insect": "0,0,0,0,0,0|71,-4,81,21,105,4|54,7,69,-33,110,-1|0,0,0,0,0,0", "lv": 0, "name": "春分-含笑花", "nextText": "种子,发芽,小叶子,大叶子,初熟,成熟", "offsetX": 0, "offsetY": 0, "qd": 500, "sr": 310, "tip": "小贴士:", "type": 1, "vipDesc": "黄钻简单描述"},

    • 有了这二个数据,就可以将其转换为下面的格式
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    "6097"=> array("cId"=>6097,"cLevel"=>0,"cName"=>"春分-含笑花","cType"=>1,"cropExp"=>100,"expect"=>60000,"growthCycle"=>68400,"maturingTime"=>2,"output"=>30,"price"=>1000,"sale"=>1000),

    //字段对应关系如下
    {
    "6097": {
    "cId": 6097, // 作物ID => 对应 cropsParames的id 字段
    "cLevel": 0, // 作物种植等级(作物等级) 对应 crops的 lv字段
    "cName": "春分-含笑花", // 作物名称 对应 crops的 name字段
    "cType": 1, // 作物类型 对应 crops的 type字段
    "cropExp": 100, // 每季种植收获经验值 对应 cropsParames的 exp 字段
    "expect": 60000, // 预期金币价值 cropsParames的 sale字段 * cropsParames的output字段* crops的harvestNum
    "growthCycle": 68400, // 成熟时间(秒) 对应 crops的 cropGrow字段的倒数第二个值
    "maturingTime": 2, // 多少季作物 对应 crops的 harvestNum字段
    "output": 30, // 预计产量 对应 cropsParames的 output字段
    "price": 1000, // 种子价格 对应 cropsParames的 price
    "sale": 1000 // 单个果实销售价格 对应 cropsParames的sale字段
    }
    }

    2.3.修改-nc_data.php

    • 找到data_zh_CN_v_1736.xml文件,assets元素下的对应素材id的swf文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <asset id="83207" src="http://appimg1.qq.com/happyfarm/module/ui/allcrops/Crop_6097.swf" />
    <asset id="83208" src="http://appimg1.qq.com/happyfarm/module/ui/allcrops/Crop_6097_0.swf" />
    <asset id="83209" src="http://appimg1.qq.com/happyfarm/module/ui/allcrops/Crop_6097_1.swf" />
    <asset id="83210" src="http://appimg1.qq.com/happyfarm/module/ui/allcrops/Crop_6097_2.swf" />
    <asset id="83211" src="http://appimg1.qq.com/happyfarm/module/ui/allcrops/Crop_6097_3.swf" />
    <asset id="83212" src="http://appimg1.qq.com/happyfarm/module/ui/allcrops/Crop_6097_4.swf" />
    <asset id="83213" src="http://appimg1.qq.com/happyfarm/module/ui/allcrops/Crop_6097_Seed.swf" />

    并将http://appimg1.qq.com/happyfarm替换为$url
    <asset id="83207" src="$url/module/ui/allcrops/Crop_6097.swf" />
    <asset id="83208" src="$url/module/ui/allcrops/Crop_6097_0.swf" />
    <asset id="83209" src="$url/module/ui/allcrops/Crop_6097_1.swf" />
    <asset id="83210" src="$url/module/ui/allcrops/Crop_6097_2.swf" />
    <asset id="83211" src="$url/module/ui/allcrops/Crop_6097_3.swf" />
    <asset id="83212" src="$url/module/ui/allcrops/Crop_6097_4.swf" />
    <asset id="83213" src="$url/module/ui/allcrops/Crop_6097_Seed.swf" />
    • 定位到目录D:\study-project\farm.dreamlove.top\plugins\qqfarm\core\source\xml\mod\nc_data.php

      • plugins\qqfarm\core\source\xml\mod\nc_data.php
    • 将内容添加进此项元素里面plugins\qqfarm\core\source\xml\mod\nc_data.phpassets元素里面

    • 再从data_zh_CN_v_1736.xmlcrops元素找到id为6097的数据

    1
    {"asset_id": {"m": "83207", "m0": "83208", "m1": "83209", "m2": "83210", "m3": "83211", "m4": "83212", "s": "83213"}, "cropGrow": "10800,21600,32400,46800,68400,2000000000", "eo": 30, "gossip": "", "harvestNum": 2, "id": 6097, "insect": "0,0,0,0,0,0|71,-4,81,21,105,4|54,7,69,-33,110,-1|0,0,0,0,0,0", "lv": 0, "name": "春分-含笑花", "nextText": "种子,发芽,小叶子,大叶子,初熟,成熟", "offsetX": 0, "offsetY": 0, "qd": 500, "sr": 310, "tip": "小贴士:", "type": 1, "vipDesc": "黄钻简单描述"},
    • 将内容添加进此项元素里面plugins\qqfarm\core\source\xml\mod\nc_data.phpcrops元素里面

    农场-添加鱼苗

    1.添加模型模型数据

    • 鱼的模型添加到D:\study-project\testweb\upload\source\plugin\qqfarm\core\module\ui\farm\fish

    2.添加数据

    2.1 修改nc_data.php

    • D:\study-project\farm.dreamlove.top\plugins\qqfarm\core\source\xml\mod\nc_data.php
    • 对应core\source\xml\mod\nc_data.php
    • nc_data.php找到<assets>标签

    • others_v_1189.xml里面的将数据添加进去
    1
    2
    3
    4
    5
    6
    7
    8
    <asset id="29422" src="$url/module/ui/farm/fish/Fish_Seed_27.swf"/>
    <asset id="29423" src="$url/module/ui/farm/fish/Fish_27.swf"/>
    <asset id="29887" src="$url/module/ui/farm/fish/fish_seed_19.swf"/>
    <asset id="29888" src="$url/module/ui/farm/fish/fish_19.swf"/>
    <asset id="30023" src="$url/module/ui/farm/fish/fish_seed_28.swf"/>
    <asset id="30024" src="$url/module/ui/farm/fish/fish_28.swf"/>
    <asset id="33929" src="$url/module/ui/farm/fish/fish_seed_20.swf"/>
    <asset id="33930" src="$url/module/ui/farm/fish/fish_20.swf"/>

    • nc_data.php找到<waterPool type="json">

    • 再复制others_v_1189.xml里面的数据

    • 粘贴到nc_data.php

    2.2 修改fishtype.php

    • D:\study-project\farm.dreamlove.top\plugins\qqfarm\core\config\nc\fishtype.php

    • 对应qqfarm\core\config\nc\fishtype.php

    • 我们有了上面的池塘鱼的数据,就可以计算生成数据了

    1
    2
    3
    最终要生成下面这种数据
    "17"=> array("crop_name"=> "金鱼", "cycle"=> array(28800,57600,90000), "exp"=> 45, "id"=> 17, "isMill"=> 1, "isRestaurant"=> 1, "lock_crystal"=> array(0,0), "lock_money"=> 0, "mature"=> 25, "output"=> 30, "pool_size"=> 1, "price"=> 800, "sale"=> 100, "show"=> 0, "tip"=> "小贴士:可爱的小金鱼。")

    • 添加进去

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/6b274c12.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/739d241.html b/739d241.html new file mode 100644 index 000000000..e223e949b --- /dev/null +++ b/739d241.html @@ -0,0 +1 @@ +今日刷题-js的call,apply为null,undefined的情况和日期的注意点 | 梦洁小站-属于你我的小天地

    今日刷题-js的call,apply为null,undefined的情况和日期的注意点

    题目1

    1
    2
    3
    4
    function a(){
    console.log(this);
    }
    a.call(null);//输出结果为?
    • 答案
      • window
    • 解析
      • call,apply用于改变this的指向,并且可以让任意函数成为某一对象的方法进行调用
      • 特别的是

    题目2

    1
    2
    3
    4
    5
    var d = new Date('2018-05-09'),可以设置为6月份的操作是?()(多选)
    A: d.setMonth(7);
    B: d.setMonth(6);
    C: d.setMonth(5);
    D: d.setDate(40);
    • 答案

      • C与D
    • 解析

      • C : js当中的月份是从0开始算的,0代表1月,11代表12月,所以设置的时候需要减少1,所以d.setMonth(5)为设置6月份

      • D : 看看菜鸟教程当中的setDate(day)解释

        参数描述
        day必需。
        表示一个月中的一天的一个数值(1 ~ 31
        0 为上一个月的最后一天
        -1 为上一个月最后一天之前的一天
        如果当月有 31 天
        32 为下个月的第一天
        如果当月有 30 天
        32 为下一个月的第二天
    1
    2
    3
    4
    5
    //五月有31天,所以只需要大于31就是设置下一个月了(其实设置大于31的数字都可以设置到下一个月,只不过下一个是记号的区别而已~)
    var time = new Date("2018-05-09");
    console.log(`${time.getFullYear()}${time.getMonth()+1}${time.getDate()}日`);//2018年5月9日
    time.setDate(42);
    console.log(`${time.getFullYear()}${time.getMonth()+1}${time.getDate()}日`);//2018年6月12日

    题目3

    1
    2
    3
    4
    5
    //说出A,B,C,D输出结果
    A: console.log(1+ "2"+"2");
    B: console.log(1+ +"2"+"2");
    C: console.log("A"- "B"+"2");
    D: console.log("A"- "B"+2);
    • 答案

      • 122 => ( 如果typeof (1+ “2”+”2”) 那么输出结果为”string” )
      • 32 => ( 如果typeof (1+ +”2”+”2”) 那么输出结果为”string” )
      • NaN2
      • NaN
    • 解析

      • A : 做加法时要注意双引号,当使用双引号时,JavaScript认为是字符串,字符串相加等于字符串合并。
        因此,这里相当于字符串的合并,即为122.
      • B : 第一个+”2”中的加号是一元加操作符,+”2”会变成数值2,因此1+ +”2”相当于1+2=3.
        然后和后面的字符串“2”相合并,变成了字符串”32”.
      • C : 第一个+”2”中的加号是一元加操作符,+”2”会变成数值2,因此1+ +”2”相当于1+2=3.
        然后和后面的字符串“2”相合并,变成了字符串”32”.
        • 同样的操作还有 * “1” => 相当于把字符串1转化为数字1
      • “A”-“B”的运算中,需要先把”A”和”B”用Number函数转换为数值,其结果为NaN,在减发操作中,如果有一个是NaN,则结果是NaN,因此”A”-“B”结果为NaN。
        然后和”2”进行字符串合并(这里的+为什么是合并那是因为加数为字符串类型),变成了NaN2.
      • D :”A”-“B”结果为NaN,然后和数值2进行加法操作,在加法操作中,如果有一个操作数是NaN,则结果为NaN。
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/739d241.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/73c88c12.html b/73c88c12.html new file mode 100644 index 000000000..e09e494dd --- /dev/null +++ b/73c88c12.html @@ -0,0 +1 @@ +前端取图片相同颜色作为遮罩或者背景 | 梦洁小站-属于你我的小天地

    前端取图片相同颜色作为遮罩或者背景

    需求

    • 遮罩层取图片相同/相似的颜色作为遮罩

    效果

    做法

    npm库

    grade.js

    COLOR THIEF

    rgbaster.js

    算法

    简单做法

    做法1

    • 效果

    • 代码
    1
    2
    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
    35
    <html lang="en">
    <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    </head>
    <body>
    <!-- 原始图 -->
    <div class="origin"></div>
    <!-- 模糊 -->
    <div class="mask"></div>
    <style>
    div.origin {
    position: relative;
    width: 320px;
    height: 200px;
    overflow: hidden;
    background-repeat: no-repeat;
    background-image: url("https://oss.dreamlove.top/i/2024/02/20/xqsvvg.png");
    }
    div.mask{
    position: relative;
    width: 320px;
    height: 200px;
    overflow: hidden;
    /* 关键代码 */
    background-repeat: no-repeat;
    background-image: url("https://oss.dreamlove.top/i/2024/02/20/xqsvvg.png");
    background-size: 20000%;
    background-position: center;
    }
    </style>
    </body>
    </html>

    参考文章

    后话

    • 其实简单做法里面的思路,也可以根据背景来设置字体颜色,大概思路就是字体颜色设置为透明,并利用background-clip进行设置
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/73c88c12.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/7429306f.html b/7429306f.html new file mode 100644 index 000000000..7013ae70b --- /dev/null +++ b/7429306f.html @@ -0,0 +1 @@ +js正则匹配获取分组和正向反向的区别 | 梦洁小站-属于你我的小天地

    js正则匹配获取分组和正向反向的区别

    前言

    正向和反向

    很多人都不清楚,什么是正向,什么是方向啊,为什么后面是正向,前面是方向啊

    • 简单点说

      • 后面为什么是正向: 你识字是从左到右对吧,字在右边,右边当然是正向(顺着你阅读方向)
      • 前面为什么是反向: 你识字是从左到右对吧,字在左边,左边当然是反向了(反着你阅读方向)
    • 明白了上面这些,看几个案例熟悉下吧(图+文字)

    示例转载自@思否-柏拉图的理想国

    example(?:pattern)

    • ?=pattern的区别就是?:pattern会将其匹配进内容里面

    • 匹配pattern但不获取匹配结果,也就是说这是一个**非获取匹配(也就是不会获取这个括号里面的分组)**,不进行存储供以后使用。这在使用或字符(|)来组合一个模式的各个部分是很有用。例如factor(?:y|ies)就是一个比factory|factories更简略的表达式

    • /factor(?:y|ies)/:匹配字符串factor后面是y为ies的完整字符串,等同于 factory|factories

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var ary = 'factorywork'
    const reg = /factor(?:y|ies)/
    //匹配正则并替换
    const str = ary.replace(reg, '');
    console.log('str:', str)
    // str: work

    //如果正则更换为
    const reg = /factor(?=:y|ies)/
    //之后执行
    const str = ary.replace(reg, '');
    console.log('str:', str)
    //输出 str: ywork

    example(?=pattern) 正向肯定

    • 正向肯定预查,在任何匹配pattern的example开始处匹配查找字符串。这是一个非获取匹配,该匹配不需要获取供以后使用。
    • /factory(?=\d)/g:匹配字符串factory后面是数字的内容
    1
    2
    3
    4
    5
    var ary = 'factory123work123factory866'
    const reg = /factory(?=\d)/g
    const str = ary.replace(reg, '')
    console.log('str:', str)
    // str: 123work123866

    example(?!pattern) 正向否定

    • 正向肯定预查,在任何匹配pattern的example开始处匹配查找字符串。这是一个非获取匹配,该匹配不需要获取供以后使用。
    • /factory(?!\d)/g:匹配字符串factory后面不是数字的字符串
    1
    2
    3
    4
    5
    var ary = 'factorywork123factory866'
    const reg = /factory(?!\d)/g
    const str = ary.replace(reg, '')
    console.log('str:', str)
    // str: work123factory866

    (?<=pattern)example 反向肯定

    • 反向肯定预查,与正向肯定预查类似,只是方向相反。查找符合表达式pattern的example。

    • /(?<=\d)factory/g:匹配字符串factory前面是数字的字符串

    1
    2
    3
    4
    5
    var ary = 'factorywork123factory866'
    const reg = /(?<=\d)factory/g
    const str = ary.replace(reg, '') // 去掉数字后面factory部分
    console.log('str:', str)
    // str: factorywork123866

    (?<!pattern)example 反向否定

    • 反向否定预查,与正向否定预查类似,只是方向相反。
    • /(?<!\d)factory/g:匹配字符串factory前面不是数字的字符串
    1
    2
    3
    4
    5
    var ary = 'thisfactorywork123factory866'
    const reg = /(?<!\d)factory/g
    const str = ary.replace(reg, '') // 去掉非数字后的factory
    console.log('str:', str)
    // str: thiswork123factory866

    匹配获取分组

    • 有时候啊,就想获取那个分组匹配的结果,就像regx101颜色标记的那样子,后面发现没几个人分析这方面知识
    • 举个简单例子
    1
    2
    3
    4
    5
    6
    7
    const temp = "factory123work123factory866";

    //我就想获取里面的factory
    //要怎么获取通过正则?正则都会

    const reg = /factory/gi;

    • 那么要怎么获取结果呢?当然,你可以反向思考通过replace方法,使用第二个参数传入空字符串去替换,你也可以将第二个参数传入函数供正则使用,这样子就可以获取到匹配的分组结果了,当然,你也可以自己做处理汇聚为一个数组
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    const temp = "factory123work123factory866";
    const reg = /factory/gi;

    /**
    *
    * @param matchStr 匹配的分组
    * @param groups 从哪里匹配到的
    * @param index 匹配的开始索引
    * @param sourceStr 匹配源
    */
    temp.replace(reg,(matchStr,groups,index,sourceStr) => {
    //console.log(matchStr,groups,index,sourceStr);
    //输出 matchStr,groups,index,sourceStr undefined
    //输出 factory 17 factory123work123factory866 undefined
    });
    • 当然,你也可以在replace当中返回值用作替换的值
    1
    2
    3
    4
    5
    6
    7
    const temp = "factory123work123factory866";
    const reg = /factory/gi;

    const end = temp.replace(reg,(matchStr,groups,index,sourceStr) => {
    return '杰';
    });
    console.log(end);//输出 杰123work123杰866

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/7429306f.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/75bf0d7.html b/75bf0d7.html new file mode 100644 index 000000000..4381eea19 --- /dev/null +++ b/75bf0d7.html @@ -0,0 +1 @@ +webpack构建工具的学习 | 梦洁小站-属于你我的小天地

    webpack构建工具的学习

    什么是webpack

    • 是一个模块打包工具
    • webpack将前端的所有资源文件(js/json/css/img/less…)都看做模块,来进行处理
    • 会根据模块的依赖关系进行分析,生成对应的资源
    • 原生的webpack只可以完成js/json的打包编译,而转换什么的需要其他插件进行完成,比如说es6转es5,箭头函数转换,都需要依赖其他loader或者插件(plugins)

    五个核心概念

    1. 入口(entry): 告诉webpack应该使用哪个模块,作为构建内部依赖图的开始,比如vue中我们经常在一个文件夹当中建立index.js或者main.js作为入口文件
    2. 出口(output): 在哪里输出文件,以及如何命名这些文件
      • 比如经常可以看到有些目录有dist build等这些,里面只有js,css,img等一些文件,就是打包输出后的东西
    3. loader:
      • 我们需要注意,原生webpack只支持解析jsjson,其他的需要安装对应的loader,比如处理less就需要less-loader
      • 本身是一个函数,接收源文件作为参数,返回转换结果
      • loader异步以xxx-loader方式命名,xxx代表了这个loader要做的转换功能
    4. 插件(plugins):
      • 执行访问更广的任务,从打包到优化都可以实现
      • 完成一些loader不能完成的功能
    5. 模式(model)
      • 有生产模式production , 开发模式development,主要区别就是是否会将代码进行压缩

    安装webpack

    • 当然,离不开npm,所以npm初始化就不多说了,直接npm init -y
    • 安装webpack
      • npm install webpack webpack-cli -g //全局安装
      • npm install webpack webpack-cli -D//局部安装
      • 查看版本webpack --version
      • 还有,除了全局安装,记得也在项目局部也安装下,不然后面可能有莫名其妙的问题引发

    处理js和json文件

    目录结构如下:

    目录结构

    代码如下:

    index.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /**index.js**/

    import say from "./module1.js"
    import * as other from "./module2.js"
    //引入json
    import jsonFile from "../json/user.json"

    console.log(say());
    console.log(other.a);
    console.log(other.b);
    console.log(jsonFile);

    module1.js

    1
    2
    3
    4
    5
    6
    /** module1.js **/
    // 默认暴露
    function sayHello() {
    console.log("你好");
    };
    export default sayHello

    module2.js

    1
    2
    3
    4
    /** module2.js **/
    // 分别暴露
    export var a = 100;
    export var b = 200;

    user.json

    1
    2
    3
    4
    5
    /** user.json **/
    {
    "name":"李白",
    "age":"2000"
    }

    index.html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    /** index.html **/
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    </head>
    <body>
    hello
    <script src="./dist/js/main.js"></script>
    <script>
    </script>
    </body>
    </html>

    运行命令打包后的文件:

    运行:

    1
    2
    3
    4
    //生产模式打包
    webpack ./js/index.js -o ./dist/js --mode=development
    //开发模式打包
    webpack ./js/index.js -o ./dist/js --mode=development

    输出:

    编译输出

    注意:

    • es6情况下,如果引入的是一个json文件,那么引入这个json文件的时候,这个json文件是默认暴露
    1
    2
    3
    4
    5
    6
    7
    8
    9
    相当于你在user.json写的是
    {
    "name":"李白",
    "age":"2000"
    }
    那么es6就将这个作为默认暴露,类似于 export default {
    "name":"李白",
    "age":"2000"
    }

    webpack 和webpack-cli的关系

    • vue中有vue-cli,webpack有webpack-cli , 有时候我就在想,这个没有cli和有cli有什么区别呢?不带cli的可以理解为一个工具,是死的,就如果程序一样,不双击运行永远不会运行,而cli就可以帮助我们去使用这个工具,比如是帮助我们使用vue,帮助我们去使用webpack

    • cli说通俗点就是cli是一个让你在命令行中使用webpack/vue一些工具的辅助程序。

    webpack的使用

    基本输出操作

    • webpack 要编译的文件 -o 输出的文件 –mode=development或者production
    • 比如 webpack ./js/index.js -o ./dist/js --mode=development 就将 js目录下的index.js文件打包输出到dist/js目录下(开发者模式)

    运行后输出

    输出

    • 但是原生的webpack只支持js/json,并且不支持es6转换es5,箭头函数转换等等,这些都需要依赖loader或者插件,所以我们后面就来看看怎么使用这些loader或者插件来帮助我们完成

    配置文件的使用

    前置了解

    • src为程序员写的源码,建立配置文件的时候不要建立在src当中!
    • 为什么需要,每一次输入这么一长段的代码很不方便,我们不可能每次都敲这个命令来完成打包吧?webpack ./js/index.js -o ./dist/js,所以我们可以使用配置文件来帮助我们完成
    • 还有就是,webpack需要对文件进行操作,所以依赖于nodejs运行,所以肯定是CMJ模块化规范!,所以你会看到,一会儿在webpack配置文件中使用require引入模块,一会在src目录下使用import引入模块的操作,一个是CommonJs模块,一个是es6模块规范~
    • 还有就是基本上要用const来接收引入的模块,是防止有人再次定义了被覆盖而导致的错误,这种错误很难找到!
    • 开发依赖和生产依赖
      • 开发依赖,帮助程序员加工处理代码的,比如说less
      • 生产依赖,帮助程序员完成某些功能的代码,比如说lodash,jQuery之类的

    建立配置文件并使用

    在src外面建立一个文件名字叫 webpack.config.js 的文件,官网详细配置地址

    webpack.config.js配置代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    /**
    * ComJs的模块化规范
    */
    const path = require("path");
    module.exports = {
    //设置模式 development开发模式 production生产模式
    mode:"development",
    //webpack入口文件
    entry:"./src/js/index.js",
    //webpack出口(输出)文件
    output:{
    //设置输出文件输出在哪里 需要设置一个绝对路径!
    path:path.resolve(__dirname,"dist"),
    //设置出口文件的名称
    //如果是 index.js,那么会输出到path目录下,不会创建文件夹
    //如果是 js/index.js 就会输出到dist目录下的js目录下
    filename:"js/index.js"
    }
    }
    • 这样子我们就不用输出很长的一段代码
    • 只需要输入webpack就可,会自动寻找当前运行目录下的webpack.config.js配置文件

    运行后输出到dist文件的js目录下

    解析css文件 css-loader和style-loader

    • 由于webpack不能解析css文件,只能解析js或者是json文件,但是我们想解析css,那么我们需要安装解析css的插件 - css-loader

    没有安装css-loader之前,会提示You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders

    如图

    • 安装插件
      • npm install css-loader style-loader -D

    1.首先在主入口当中添加引入css import "./css/index.css"

    1
    2
    3
    4
    import "../css/index.css";//引入css文件

    //当然,也可以自己命令下接收
    import css from "../css/index.css";

    2.然后配置webpack.config.js

    • use当中的顺序是从右到左的!

    • style-loader作用: 将css-loader解析的内容处理,生成style并挂载到head

      如图,生成style标签,并挂载到head上

    • css-loader作用: css-loader帮我们解析css文件里面的css代码

      • 比如这是在只使用了css-loader的情况下输出接收到的内容,可以看到,css-loader帮我们读取了css的内容

        只使用了css-loader的情况下输出接收到的内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //loader的配置项
    module: {
    rules: [
    //解析css
    {
    test: /\.css$/,
    // <= 执行顺序是从右到左的,先css-loader ,再执行style-loader
    use: ['style-loader', 'css-loader']
    },
    ]
    }

    3.就可以直接使用了可以运行webpack命令来看看

    解析less文件 less-loader

    • 安装插件
      • npm install less-loader -D

    1.首先在主入口当中添加引入less import "./css/index.css"

    1
    2
    3
    4
    import "../css/index.less";//引入less文件

    //当然,也可以自己命令下接收
    import cssLess from "../css/index.less";

    2.然后修改下webpack.config.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    module: {
    rules: [
    //解析less
    {
    test:/\.less$/,
    use:['style-loader',"css-loader","less-loader"]
    },
    ]
    }

    如果想实现复用的话也可以使用 扩展运算符 ...
    const styleLoader = ['style-loader',"css-loader"];
    {
    test: /\.less$/,
    use: [...styleLoader, "less-loader"]
    },

    打包html文件 html-webpack-plugin(插件)

    1.首先配置下webpack.config.js文件

    • 注意这里是插件的使用,所以新增加了一个配置项plugins
    • 注意有模板和没有模板的区别!
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var HtmlWebpackPlugin = require('html-webpack-plugin');
    var path = require('path');

    var webpackConfig = {
    ...
    //这个是没有模板的,也就是如果自己的html文件写了内容,是会被覆盖的!
    //plugins: [new HtmlWebpackPlugin()]
    //这个是有模板的,以自己的html为模板来进行打包html!!!!!!!!!!!!!
    // 注意这个要带 . 不然路径不对!
    plugins: [new HtmlWebpackPlugin({template:"./src/index.html"})]
    ...
    };

    html-loader和html-webpack-plugin冲突导致的报错

    前置

    之前学习的时候是webpack4为基本,现在到webpack5了,有些问题了,当我们即使用html-webpack-plugin 又使用html-loader的时候,就会发生这个报错(至少在我现在的版本的情况下会报错!)

    • “html-loader”: “^3.1.0”,
    • “html-webpack-plugin”: “^5.5.0”
    • “webpack”: “^5.72.1”,
    • “webpack-cli”: “^4.9.2”
    1
    2
    3
    4
    ERROR in   Error: Child compilation failed:
    Module not found: Error: Can't resolve '../dist/js/index.js' in 'D:\develop\phpstudy_pro\WWW\webpackstudyagain\src':
    Error: Can't resolve '../dist/js/index.js' in 'D:\develop\phpstudy_pro\WWW\webpackstudyagain\src'
    ModuleNotFoundError: Module not found: Error: Can't resolve '../dist/js/index.js' in 'D:\develop\phpstudy_pro\WWW\webpackstudyagain\src'

    冲突报错解决

    • 原来的index.html改名为index.ejs然后webpack.config.js修改下即可
    1
    2
    3
    4
    5
    6
    7
    8
    9
    //loader的配置项
    module: {
    ...
    //原来的改为即可
    //plugins:[new HtmlWebpackPlugin({template:"./src/index.html"}),]
    plugins:[new HtmlWebpackPlugin({template:"./src/index.ejs"}),]
    ...
    },

    • 如果想在ejs引入图片文件并实现后期打包的话,就需要使用<%= require('图片位置') %>
    1
    2
    3
    4
    5
    6
    <body>
    <!-- <img src="./imgs/lh.jpg"> 不用这个-->
    <!-- 使用这个代替 -->
    <img src=" <%= require('./imgs/lh.jpg')%> ">

    </body>

    webpack5使用asset module

    • 在webpack5之前,我们url-loader、file-loader、raw-loader来完成操作
    • 现在webpack5自带的asset-module就可以实现以前的功能
    • 之前通过use来使用这些loader,asset module通过type来完成
    • 官网(英文)的介绍

    功能替换

    • asset/resource
      • 将资源分割为单独的文件,并导出url,就是之前的 file-loader的功能
      • 官网关于此的api
    • asset/inline
      • 将资源导出为dataURL(url(data:))的形式,之前的 url-loader的功能
    • asset/source
      • 将资源导出为源码(source code). 之前的 raw-loader 功能
    • asset
      • 自动选择导出为单独文件或者 dataURL形式(默认为8KB)

    打包样式中的文件-方法1-asset/resource

    类似于file-loader相当于只是对文件进行了重命名,不具有转base64功能

    设置type为asset/resource

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    module: {
    rules: [
    //配置解析样式中图片
    {
    test:/\.(png|jpg|gif|bmp|jpeg|webp)$/,
    type:'asset/resource',
    //配置项目
    generator:{
    //设置处理输出的路径为 dist下的imags (也就是dist/imags)
    //并且设置图片名称为 选取hash值前五位
    filename:"imgs/[hash:5][ext]"
    }
    }
    ]
    },

    打包样式中的文件-方法2-asste

    类似于url-loader,可以设置图片小于设定的大小后进行base64编码

    设置type为asset

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    module: {
    rules: [
    //配置解析样式中图片
    {
    test:/\.(png|jpg|gif|bmp|jpeg|webp)$/,
    type:'asset',
    // 转base64的条件
    parser: {
    // 转base64的条件,当小于8kb的时候就转化为base64
    dataUrlCondition: {
    maxSize: 8 * 1024, // 8kb
    }
    },
    //配置项目
    generator:{
    //设置处理输出的路径为 dist下的imags (也就是dist/imags)
    //并且设置图片名称为 选取hash值前五位
    filename:"imgs/[hash:5][ext]"
    }
    }
    ]
    },

    使用file-loader url-loader的时候会出现多出来一部分

    • 将type设置为javascript/auto
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    {
    test: /\.(jpg|png|gif)$/,
    loader: 'url-loader',
    options: {
    esModule: false, // webpack5默认开启esModule 手动关闭
    // 图片大小小于8kb,就会被base64处理
    // 优点:减少请求数量(减轻服务器压力)
    // 缺点:图片体积会更大(文件请求速度更慢)
    limit: 8 * 1024,
    },
    type: 'javascript/auto' // 阻止webpack5中asset
    }

    处理html文件的图片 html-loader

    不处理html文件图片时候打包后的效果

    发现并没有将html当中的图片进行打包

    没有处理html文件图片

    处理步骤和效果

    1.首先安装loader: npm install html-loader -D

    2.然后配置webpack.config.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //loader的配置项
    module: {
    rules: [
    //配置解析html中的图片<img>标签资源
    {
    test:/\.(html)$/,
    use:{
    loader:"html-loader",
    }
    }
    ]
    },

    3.然后就可以测试下是否正常了

    配置完成后被正确的打包和引入了

    打包其他资源(比如说字体图标,mp4等资源)

    • 有时候我们使用了阿里图标字体库,就必须要使用到ttf字体资源,所以我们就要考虑这些资源的打包

    webpack5的使用 asset/resource

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    module: {
    rules: [
    //配置解析其他资源文件,比如说图标字体
    //与其说是解析,不如说是搬运下
    {
    //设置排除列表
    exclude:/\.(html|less|css|png|jpg|bmp|js|gif|json|jped|webp|ejs)$/,
    type:"asset/resource",
    generator:{
    filename:"media/[hash][ext]"
    }
    }
    ]
    }

    注意

    ​ 使用这个asset/resource的时候,不知道是不是版本问题还是我配置的问题,老是出现__webpack_public_path__ = __webpack_base_uri__ = htmlWebpackPluginPublicPath; 这个没有用的文件和文件内容,很奇怪,,,,,,

    ​ 之前用webpack4的时候就没有,可能是因为是我写这个的时候使用的都是asset或者asset/resource来处理,而没有使用file-loaderurl-loader来处理的原因吧

    多出来的

    webpack使用webpack-dev-server

    • 安装,最好是全局和局部都安装!!!! (带指令集的最好全局安装)
      • npm install webpack-dev-server -D
      • npm install webpack-dev-server -g

    1.首先 在配置对象当中添加 devServer 配置选项

    • 热更新(热模替换): 当内容发生变化的时候,只重新编译变化了的部分,而不会引发整体的编译更新
    • 不开启热更新的话,当内容发生变化的时候,就会全部重新编译全部更新

    webpack.config.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    module.exports = {
    ...
    devServer:{
    port:5000,//开启服务器的端口号,
    open:true,//编译完成是否自动打开浏览器
    hot:true,//开启模块热更新(热模替换)
    },
    ...
    }

    2.使用

    后面就可以了,要使用这个webpack-dev-server,就只需要把webpack 改为webpack-dev-server即可

    使用webpack-dev-server命名

    生产环境

    • 之前如果已经配置完成了,那么开发环境就可以用了,开发我们不讲究什么兼容性啊,什么压缩啊,而生产环境就需要
    • 所以我们可以准备二套webpack的配置文件,一套用于生产,一套用于开发
    • 将之前写的配置文件配置为生产环境

    准备二套配置文件

    • 建立config文件夹,里面包括二个文件
    • webpack.dev.js文件 用于开发环境
    • webpack.prod.js文件 用于生产环境
    wepack.dev.js文件
    webpack4的时候(参考下)
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    const path = require("path");
    const HtmlWebpackPlugin = require("html-webpack-plugin");

    const styleLoader = ['style-loader', 'css-loader'];

    module.exports = {
    //五大核心概念在这里都会被陆续配置
    mode: "development", //开发模式还是生产模式
    entry: "./src/js/index.js", //入口文件
    //出口文件
    output: {
    //设置出口文件的输出路径
    path: path.resolve(__dirname, "dist"),
    //设置出口文件名称
    // filename:"index.js"
    filename: "js/index.js", //这样子写js文件就会被放入到js当中,注意不能/js/index.js!
    },
    //里面配置一个个的loader
    module: {
    rules: [
    //css-loader的配置
    {
    test: /\.css$/, //指明要处理的文件类型
    //执行顺序的从右到左的!
    // 简写
    use: [...styleLoader]
    },
    // less-loader配置
    {
    test: /\.less$/,
    use: [...styleLoader, "less-loader"]
    },
    // 配置解析样式中的图片 file-loader
    // {
    // test:/\.(png|jpg|gif|bmp)$/,
    // use:[
    // {
    // loader:"file-loader",
    // options:{
    // outputPath:"imgs",//配置图片加工后存放的位置
    // name:'[hash:5].[ext]',//配置生成图片的名字 + 后缀,
    // }
    // }
    // ]
    // },
    // 配置解析样式中的图片 url-loader
    {
    test: /\.(png|jpg|gif|bmp)$/,
    use:[
    {
    loader:'url-loader',
    options:{
    outputPath:"imgs",
    name:'[hash:5].[ext]',
    limit:9*1024,
    }
    }
    ]
    },
    //配置解析html中的图片<img>资源
    {
    test:/\.(html)$/,
    use:{
    loader:"html-loader",
    }
    },
    //配置处理其他资源
    {
    exclude:/\.(html|less|css|png|jpg|jpeg|bmp|js|gif|json)$/,
    use:[
    {
    loader:"file-loader",
    options:{
    outputPath:"media",
    name:"[hash:5].[ext]"
    }
    }
    ]
    }
    ]
    },
    //使用插件
    plugins: [
    new HtmlWebpackPlugin({
    template: "./src/index.html"
    }),
    ],
    devServer:{
    port:5000,//开启服务器的端口号
    open:true,//编译完成自动打开浏览器
    hot:true,//开启模块热更新(热模替换)
    }
    }
    webpack5的时候(这里以这套为例)
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    /**
    * ComJs的模块化规范
    */
    const path = require("path");
    //html打包插件
    var HtmlWebpackPlugin = require('html-webpack-plugin');

    //复用
    const styleLoader = ['style-loader', "css-loader"];
    module.exports = {
    //设置模式 development开发模式 production生产模式
    mode: "development",
    //webpack入口文件
    entry: "./src/js/index.js",
    //webpack出口(输出)文件
    output: {
    //设置输出文件输出在哪里
    path: path.resolve(__dirname, "dist"),
    //设置出口文件的名称
    //如果是 index.js,那么会输出到path目录下,不会创建文件夹
    //如果是 js/index.js 就会输出到dist目录下的js目录下
    filename: "js/index.js"
    },
    //loader的配置项
    module: {
    rules: [
    //解析css
    {
    test: /\.css$/,

    // <= 执行顺序是从右到左的,先css-loader ,再style-loader
    use: [...styleLoader],
    },
    //解析less
    {
    test: /\.less$/,
    use: [...styleLoader, "less-loader"],

    },
    //配置解析样式中图片
    {
    test: /\.(png|jpg|gif|bmp|jpeg|webp)$/,
    type: 'asset',
    // 转base64的条件
    parser: {
    // 转base64的条件
    dataUrlCondition: {
    maxSize: 8 * 1024, // 8kb
    }
    },
    //配置项目
    generator: {
    //设置处理输出的路径为 dist下的imags (也就是dist/imags)
    //并且设置图片名称为 选取hash值前五位
    filename: "imgs/[hash:5][ext]"
    }
    },
    //配置解析html中的图片<img>标签资源
    {
    test: /\.(html)$/,
    use: {
    loader: "html-loader",
    }
    },
    //配置解析其他资源文件,比如说图标字体
    //与其说是解析,不如说是搬运下
    {
    //设置排除列表
    exclude: /\.(html|less|css|png|jpg|bmp|js|gif|json|jped|webp|ejs)$/,
    type: "asset/resource",
    generator: {
    filename: "media/[hash][ext]"
    }
    }
    ]
    },
    //插件的配置项
    plugins: [
    new HtmlWebpackPlugin({
    template: "./src/index.ejs"
    }),
    ],
    devServer:{
    port:5000,//开启服务器的端口号,
    open:true,//编译完成是否自动打开浏览器
    hot:true,//开启模块热更新(热模替换)
    }
    }

    使用package.json当中运行脚本 这样子就不用每次都输入很长一段代码了

    • 在webpack中,当运行webpack的时候,可以指明配置文件在哪里,使用 --config就可以指明,否者的话就去当前运行目录下寻找是否有webpack.config.js文件!

      • 比如 webpack --config ./config/webpack.dev.js 就指明webpack的配置文件在哪里
    • 在package.json当中添加script配置项

    1
    2
    3
    4
    "scripts": {
    "start": "webpack-dev-server --config ./config/webpack.dev.js",
    "build": "webpack --config ./config/webpack.prod.js"
    }

    ​ 后期即可通过npm run start来进行start当中的命令,通过npm run build来运行build命令!

    ​ 提醒下:~如果script当中是以start的key值,那么可以简写就可以运行命令,直接npm start就可以,而其他的不可以!

    提取css为单独的文件 mini-css-extract-plugin

    webpack4测试可以css可以结合less使用,webpack5莫名其妙less无法和css结合…我这有问题!!!!!!!!!!!!

    webpack4测试可以css可以结合less使用,webpack5莫名其妙less无法和css结合…我这有问题!!!!!!!!!!!!

    webpack4测试可以css可以结合less使用,webpack5莫名其妙less无法和css结合…我这有问题!!!!!!!!!!!!

    • 之前的css,都是包含在js了,通过style-loader css-loader来进行解析使用,但是我们后期打包想分离,css就是css,js就是js
    • 就需要使用插件mini-css-extract-plugin
    • 复习下 style-loader的用处, 就是将js当中的css创建生成标签 ,然后插入到head
    • 关于此插件的api

    1.安装

    1
    npm install --save-dev mini-css-extract-plugin

    2.配置loader

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    //引入mini-css-extract-plugin,用于提取css为单独文件
    const MiniCssExtractPlugin = require('mini-css-extract-plugin');
    {
    // 处理less资源
    test: /\.less$/,
    exclude:/node_modules/,
    use: [
    MiniCssExtractPlugin.loader,
    'css-loader',
    'less-loader',
    ]
    },

    {
    // 处理css资源
    test: /\.css$/,
    use: [
    MiniCssExtractPlugin.loader,
    'css-loader',
    ]
    }

    3.配置插件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    module.exports = {
    ...
    //插件的配置项
    plugins: [
    //提取css文件
    new MiniCssExtractPlugin({
    filename: "./css/index.css",
    }),
    ],
    ...
    }

    3.使用 在配置了package.json的情况下,直接npm run build即可

    测试正常,没有出现异常

    css兼容性处理

    • 注意,修改下打包的配置项目为开发模式 mode = production ,否者不会处理兼容性问题(因为开发模式要什么兼容性,生产环境才要兼容性)

    1.安装loader

    1
    npm install postcss postcss-loader postcss-preset-env -D

    **2.定义通用配置 **因为css和less样式文件都要进行兼容性处理,所以我们定义好一个通用的配置:~ (相当于告诉这个工具,要兼容到哪些浏览器) ~!!!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // 配置一个styleLoader,处理less和css时都会使用
    const styleLoader = [MiniCssExtractPlugin.loader, 'css-loader', {
    loader: "postcss-loader",
    options: {
    postcssOptions: {
    plugins: [
    [
    "postcss-preset-env",
    {
    // Options
    },
    ],
    ],
    },
    },
    },
    ];

    3.修改css-loader和less-loader配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    {
    // 处理css资源
    test: /\.css$/,
    use: [...styleLoader]
    },
    {
    // 处理less资源
    test: /\.less$/,
    use: [...styleLoader, 'less-loader']
    },

    4.配置package.json,在其中追加browserslist配置,通过配置加载指定的css兼容性样式

    • browserslist 是一套描述产品目标运行环境的工具,它被广泛用在各种涉及浏览器/移动端的兼容性支持工具中

    • 若出现版本不兼容,或配置不正确的错误,那么需更换依赖包版本: npm i less-loader@5 postcss-loader@3

    代码: vue脚手架用的就是这一套~

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //注意,package.json不可以有注释,记得删除!
    "browserslist": {
    // 开发环境
    "development": [
    "last 1 chrome version",
    "last 1 firefox version",
    "last 1 safari version"
    ],
    // 生产环境:默认是生产环境
    "production": [
    ">0.2%", //兼容市面上99.8%的浏览器
    "not dead", //"死去"的浏览器不做兼容,例如IE8
    "not op_mini all",//不做opera浏览器mini版的兼容
    "ie 10" //兼容IE10
    ]
    }

    如图配置完成在package.json中

    5.然后重新运行 npm run build 即可

    可以看到,这里的display:flex自动添加了前置,就是做了下兼容性处理

    js语法转换

    • 概述:将浏览器不能识别的新语法转换成原来识别的旧语法,做浏览器兼容性处理,比如说箭头函数

    1.安装loader

    1
    npm install babel-loader @babel/core @babel/preset-env  -D

    2.配置loader

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    module: {
    rules: [
    {
    test: /\.js$/,
    exclude: /node_modules/,
    use: {
    loader: "babel-loader",
    options: {
    presets: ['@babel/preset-env']
    }
    }
    }
    ]
    }

    3.就可以啦,然后直接 npm run build就可以了

    如图示例转换了箭头函数

    js兼容性处理

    • 处理babel无法处理的~,比如说js语法转换无法处理 promise,而如果做了js语法兼容性处理是可以处理promise的,(注:ie11不使用兼容性处理的无法使用promise的!)

    • 注意从 7.4 开始,就不推荐使用 @babel/polyfill 了,但是这里依旧记录下这个库的使用教程和官方推荐

    babel7.4+版本的js兼容性处理
    1.安装
    1
    npm install babel-loader @babel/core @babel/preset-env core-js -D
    2.使用

    代码:

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    module.exports = {
    mode: "production",
    //loader
    module: {
    rules: [
    //处理js文件
    {
    test: /\.js$/,
    exclude: /node_modules/, //排除node_modules
    use: [
    //js兼容性处理
    {
    loader: "babel-loader",
    options: {
    presets: [
    //别漏掉了这个括号
    ["@babel/preset-env",
    {
    // 按需加载
    useBuiltIns: 'usage',
    // 指定core-js版本 ,安装的时候我这里为"core-js": "^3.22.8",
    corejs: {
    version: 3
    },
    // 指定兼容性做到哪个版本浏览器
    targets: {
    chrome: '60',
    firefox: '60',
    ie: '9',
    safari: '10',
    edge: '17'
    }
    }
    ]
    ]
    }
    },
    ]
    },
    ]
    },
    }
    3.注意

    如果出现提示To be a valid preset, its name and options should be wrapped in a pair of brackets 或者RROR in ./src/app.ts Module build failed (from ./node_modules/babel-loader/lib/index.js):Error: [BABEL] D:\develop\phpstudy_pro\WWW\tpyescriptStudy\05_snake\src\app.ts: Unknown option: .useBuiltIns. Check out https://babeljs.io/docs/en/babel-core/#options for more information about options.

    可能是因为少了括号,这位老哥一语道破

    一图道破

    我的ts结合babel

    ts结合babel

    4.效果

    打包后相比@babel/poplyfill库明显减少

    打包后相比@babel/poplyfill库明显减少

    promise语法也正常输出在ie浏览器

    promise语法也正常输出

    babel7.4以下 js兼容性处理-@babel/polyfill库的使用

    **缺点:!**这个插件会将所有的兼容性代码全部引入,会导致打包后文件体积变大!!!!

    1.安装
    1
    npm install @babel/polyfill -D

    安装的时候会提示这些,表示@babel/polyfill不推荐使用

    1
    2
    3
    npm WARN deprecated @babel/polyfill@7.12.1: 🚨 This package has been deprecated in favor of separate inclusion of a polyfill and regenerator-runtime (when needed). See the @babel/polyfill docs (https://babeljs.io/docs/en/babel-polyfill) for more information.

    npm WARN deprecated core-js@2.6.12: core-js@<3.4 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Please, upgrade your dependencies to the actual version of core-js.

    官网对于此的介绍: 从 7.4 开始,就不推荐使用 @babel/polyfill 了,但是这里依旧记录下这个库的使用教程

    从 7.4 开始,就不推荐使用 @babel/polyfill 了

    2.webpack入口文件处引入
    • 比如我webpack的入口是src\js\index.js,那么我就在这文件夹添加import '@babel/polyfill'; // 包含ES6的高级语法的转换 就可以
    3.使用

    没有js兼容性处理前IE11的输出(promise测试)

    没有js兼容性处理前IE11的输出(promise测试)

    使用js兼容性处理后IE11的输出(promise测试)

    使用js兼容性处理后IE11的输出(promise测试)

    4.效果

    测试代码

    1
    2
    3
    4
    5
    6
    7
    setTimeout(() => {
    new Promise((resolve, reject) => {
    resolve("动感超人" + 900)
    }).then((data) => {
    console.log(data);
    })
    }, 900);

    不过体积有点大

    打包后体积

    js语法检查

    1.安装loader

    1
    npm install eslint-loader eslint -D

    2.安装检查规则库

    备注:eslint-config-airbnb-base定制了一套标准的、常用的js语法检查规则,推荐使用

    1
    npm install eslint-config-airbnb-base eslint-plugin-import -D

    3.配置webpack

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    module.exports = {
    //loader的配置项
    module: {
    rules: [
    //语法检查功能
    {
    // 对js进行语法检查
    test: /\.js$/,
    exclude: /node_modules/,//排除这个文件
    // 优先执行
    enforce: 'pre',//优先执行 只要webpack启动时 尽可能先执行
    loader: 'eslint-loader',
    options: {
    fix: true //若有问题自动修复,重要!!!!
    }
    }
    ]
    },
    }

    4.配置package.json

    注意安装语法规则库

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    "eslintConfig": {
    "extends": "airbnb-base",
    "env": {
    "browser": true
    }
    }

    //说明
    "eslintConfig": {
    "extends": "airbnb-base", //直接使用airbnb-base提供的规则 需要下载的
    "env": {
    "browser": true//如果运行环境不是浏览器 则运行环境为node 此时需要将这个改为false
    }
    }

    5.使用

    打包的时候自动运行

    如图,我这里检测到的不符合语法规范的问题

    检测到的错误

    压缩css(对于html,js,webpack4都已经默认压缩了)

    1.安装

    webpack4的时候使用这个 官网api

    1
    npm install optimize-css-assets-webpack-plugin -D

    webpack5的时候使用这个 官网api

    1
    npm install css-minimizer-webpack-plugin --save-dev

    2.引入插件

    webpack4的时候使用这个

    1
    const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');	

    webpack5的时候使用这个

    1
    const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

    3.配置插件

    webpack4的optimize-css-assets-webpack-plugin配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    module.exports = {
    //插件的配置项
    plugins: [
    //压缩css插件
    new OptimizeCssAssetsPlugin({
    cssProcessorPluginOptions: {
    preset: ['default', { discardComments: { removeAll: true } }],//移出所有注释
    },
    }),
    ]
    }

    webpack5的css-minimizer-webpack-plugin配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    const MiniCssExtractPlugin = require("mini-css-extract-plugin");
    const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");

    module.exports = {
    optimization: {
    minimizer: [
    new CssMinimizerPlugin(),
    ],
    },
    //使用压缩css插件
    plugins: [new MiniCssExtractPlugin()],
    };

    注意!注意!注意!注意!注意!

    ​ 在webpack5中,不需要手动引入uglify插件,只需配置modeproduction就可以压缩js代码。但是,如果用了css-minimizer-webpack-plugin插件去压缩css文件,js的压缩就会失效,所以使用terser-webpack-plugin插件去压缩js代码

    ​ (我这边反正就是二个一起使用是报错``,这里记录下)

    我使用的时候报错记录,可能是提取css的时候遗留下来的问题吧,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    ERROR in main.css
    main.css from Css Minimizer plugin
    Error: Unexpected '/'. Escaping special characters with \ may help.
    at D:\develop\phpstudy_pro\WWW\webpackstudyagain\main.css:28:1
    at Root._error (D:\develop\phpstudy_pro\WWW\webpackstudyagain\node_modules\postcss-selector-parser\dist\parser.js:174:16)
    at Root.error (D:\develop\phpstudy_pro\WWW\webpackstudyagain\node_modules\postcss-selector-parser\dist\selectors\root.js:43:19)
    at Parser.error (D:\develop\phpstudy_pro\WWW\webpackstudyagain\node_modules\postcss-selector-parser\dist\parser.js:740:21)
    at Parser.unexpected (D:\develop\phpstudy_pro\WWW\webpackstudyagain\node_modules\postcss-selector-parser\dist\parser.js:758:17)
    at Parser.combinator (D:\develop\phpstudy_pro\WWW\webpackstudyagain\node_modules\postcss-selector-parser\dist\parser.js:656:12)
    at Parser.parse (D:\develop\phpstudy_pro\WWW\webpackstudyagain\node_modules\postcss-selector-parser\dist\parser.js:1101:14)
    at Parser.loop (D:\develop\phpstudy_pro\WWW\webpackstudyagain\node_modules\postcss-selector-parser\dist\parser.js:1043:12)
    at new Parser (D:\develop\phpstudy_pro\WWW\webpackstudyagain\node_modules\postcss-selector-parser\dist\parser.js:164:10)
    at Processor._root (D:\develop\phpstudy_pro\WWW\webpackstudyagain\node_modules\postcss-selector-parser\dist\processor.js:53:18)
    at Processor._runSync (D:\develop\phpstudy_pro\WWW\webpackstudyagain\node_modules\postcss-selector-parser\dist\processor.js:100:21)

    ERROR in ./css/index.css
    ./css/index.css from Css Minimizer plugin
    Error: Unexpected '/'. Escaping special characters with \ may help.
    at D:\develop\phpstudy_pro\WWW\webpackstudyagain\css\index.css:28:1
    at Root._error (D:\develop\phpstudy_pro\WWW\webpackstudyagain\node_modules\postcs

    打包和运行进度条功能

    1.安装

    1
    npm install webpackbar -D

    2.引入插件

    1
    const WebpackBar = require('webpackbar');

    3.配置添加到webpack配置文件

    1
    2
    3
    4
    5
    6
    7
    module.exports = {
    //插件的配置项
    plugins: [
    //webpack打包和运行的时候进度条
    new WebpackBar(),
    ]
    }

    4.然后就有效果了

    进度条效果

    webpack当中use的几种形式

    1. use为一个数组

      • 里面可以为一个个的字符串,比如 use:["style-loader","css-loader"]

      • 也可以对象掺杂着字符串,比如 use:[{...},"css-loader"]

      • 也可以全部为对象,比如use:[{...},{...},{...}]

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        module.exports = {
        module:{
        rules:[
        //分割下 use全部为对象
        {
        test: /\.less$/,
        use: [{
        loader: "style-loader"
        }, {
        loader: "css-loader"
        }, {
        loader: "less-loader"
        }]
        },
        ]
        }
        }
    2. use为一个对象,代表使用这一个loader

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      module: {
      rules: [
      //use为一个对象的情况
      {
      test: /\.m?js$/,
      exclude: /node_modules/,
      use: {
      loader: 'babel-loader',
      options: {
      presets: [
      ['@babel/preset-env', { targets: "defaults" }]
      ]
      }
      }
      },
      //其他loader
      ]
      }

    webpack配置对象当中的resolve

    resolve.extensions

    • 使用 import 引入其他模块时,如果不加上后缀扩展名(extension)且找不到对应的文件时,webpack 就会尝试根据 extensions 数组,依次加上扩展名看看能不能匹配到一个文件,直到找到为止。如果找不到则会报错。

    • 默认值为: ['.js','.json','wasm']

    • 功能就是,不加后缀名的情况下,依次匹配,直到匹配所有的还匹配不到就报错

    • 所以你经常可以看到一些人导入模块不添加后缀名,直接 import xxx from “./abc/a”,就是resolve.extensions起到的作用!

    如图,当导入模块import * as all from "./food"的时候,发现模块不存在,会依次去寻找,然后后缀都匹配完了还是找不到就报错

    推荐几款好用的npm

    rimraf —- 快速删除node_modules文件夹

    官网地址

    1.安装

    1
    npm install rimraf -g

    2.使用

    在所属文件夹下使用cmd指令下,进入所需删除的node_modules文件夹的位置,再输入指令

    1
    rimraf node_modules

    clena-webpack-plugin — 每次打包删除dist目录

    官网地址

    1.安装

    1
    npm install --save-dev clean-webpack-plugin

    2.使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    const { CleanWebpackPlugin } = require('clean-webpack-plugin');

    const webpackConfig = {
    plugins: [
    /**
    * All files inside webpack's output.path directory will be removed once, but the
    * directory itself will not be. If using webpack 4+'s default configuration,
    * everything under <PROJECT_DIR>/dist/ will be removed.
    * Use cleanOnceBeforeBuildPatterns to override this behavior.
    *
    * During rebuilds, all webpack assets that are not used anymore
    * will be removed automatically.
    *
    * See `Options and Defaults` for information
    */
    new CleanWebpackPlugin(),
    ],
    };

    module.exports = webpackConfig;

    webpack箭头函数导致第一行xx位置报错

    • 解决 : 添加 environment:{ arrowFunction:false }

    示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    module.exports = {
    mode: "production",
    entry: "./src/app.ts",
    output: {
    path: path.resolve(__dirname, "dist"),
    filename: "js/index.js", //文件名
    environment: {
    //不使用箭头函数
    arrowFunction: false,
    //不使用const
    const: false
    }
    },

    }

    设置前编译

    设置前

    设置后编译

    设置后

    loader和plugin作用和区别

    • loader: 是文件加载器,可以加载资源文件,并对这些文件进行一些处理,比如:编译,压缩等,
    • plugin:webpack在运行的生命周期会广播出许多事件,plugin可以监听这些事件,在合适的时机中通过webpack提供的api改变输出结果

    区别:

    • loader是将A文件进行编译形成B文件,这里操作的是文件,A.less => A.css
    • plugin是用于在webpack打包编译过程里,在对应的事件节点里执行自定义操作,比如资源管理、bundle文件优化等操作
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/75bf0d7.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    avatar
    梦洁
    小小的字,有大大的梦想~分享我的前端学习过程,经历,错误,和一些其他折腾过程
    关注下我(* ̄▽ ̄*)
    公告
    不断更新中,有问题请留言回复(会通过邮箱提醒~)
    目录
    1. 1. 什么是webpack
      1. 1.1. 五个核心概念
      2. 1.2. 安装webpack
      3. 1.3. 处理js和json文件
      4. 1.4. webpack 和webpack-cli的关系
    2. 2. webpack的使用
      1. 2.1. 基本输出操作
      2. 2.2. 配置文件的使用
        1. 2.2.1. 前置了解
        2. 2.2.2. 建立配置文件并使用
      3. 2.3. 解析css文件 css-loader和style-loader
      4. 2.4. 解析less文件 less-loader
      5. 2.5. 打包html文件 html-webpack-plugin(插件)
      6. 2.6. html-loader和html-webpack-plugin冲突导致的报错
        1. 2.6.1. 前置
        2. 2.6.2. 冲突报错解决
      7. 2.7. webpack5使用asset module
        1. 2.7.1. 功能替换
        2. 2.7.2. 打包样式中的文件-方法1-asset/resource
        3. 2.7.3. 打包样式中的文件-方法2-asste
      8. 2.8. 使用file-loader url-loader的时候会出现多出来一部分
      9. 2.9. 处理html文件的图片 html-loader
        1. 2.9.1. 不处理html文件图片时候打包后的效果
        2. 2.9.2. 处理步骤和效果
      10. 2.10. 打包其他资源(比如说字体图标,mp4等资源)
        1. 2.10.1. webpack5的使用 asset/resource
        2. 2.10.2. 注意
      11. 2.11. webpack使用webpack-dev-server
      12. 2.12. 生产环境
        1. 2.12.1. 准备二套配置文件
          1. 2.12.1.1. wepack.dev.js文件
            1. 2.12.1.1.1. webpack4的时候(参考下)
            2. 2.12.1.1.2. webpack5的时候(这里以这套为例)
        2. 2.12.2. 使用package.json当中运行脚本 这样子就不用每次都输入很长一段代码了
        3. 2.12.3. 提取css为单独的文件 mini-css-extract-plugin
        4. 2.12.4. css兼容性处理
        5. 2.12.5. js语法转换
        6. 2.12.6. js兼容性处理
          1. 2.12.6.1. babel7.4+版本的js兼容性处理
            1. 2.12.6.1.1. 1.安装
            2. 2.12.6.1.2. 2.使用
            3. 2.12.6.1.3. 3.注意
            4. 2.12.6.1.4. 4.效果
          2. 2.12.6.2. babel7.4以下 js兼容性处理-@babel/polyfill库的使用
            1. 2.12.6.2.1. 1.安装
            2. 2.12.6.2.2. 2.webpack入口文件处引入
            3. 2.12.6.2.3. 3.使用
            4. 2.12.6.2.4. 4.效果
        7. 2.12.7. js语法检查
        8. 2.12.8. 压缩css(对于html,js,webpack4都已经默认压缩了)
        9. 2.12.9. 打包和运行进度条功能
      13. 2.13. webpack当中use的几种形式
      14. 2.14. webpack配置对象当中的resolve
        1. 2.14.1. resolve.extensions
      15. 2.15. 推荐几款好用的npm
        1. 2.15.1. rimraf —- 快速删除node_modules文件夹
        2. 2.15.2. clena-webpack-plugin — 每次打包删除dist目录
      16. 2.16. webpack箭头函数导致第一行xx位置报错
    3. 3. loader和plugin作用和区别
    最新文章
    \ No newline at end of file diff --git a/7613e319.html b/7613e319.html new file mode 100644 index 000000000..746f67531 --- /dev/null +++ b/7613e319.html @@ -0,0 +1 @@ +微信小程序解密并拆包获取源码教程 | 梦洁小站-属于你我的小天地

    微信小程序解密并拆包获取源码教程

    第一步:电脑端提取微信小程序包

    • 一般在微信安装目录下的,比如我微信安装在d盘当中,那么下载的wxapkg包就在下方

      1
      D:\qq\wechatfile\WeChat Files\Applet
    • 那么微信小程序加载的wxapkg包都在这里

      • 比如下方的一个微信小程序的包就在这里

    第二步:解密wxapkg包

    1
    2
    3
    pc_wxapkg_decrypt.exe -wxid 微信小程序id -in 要解密的wxapkg路径 -out 解密后的路径
    //示例如下
    pc_wxapkg_decrypt.exe -wxid wxa50577e42c3c2a48 -in D:\360Downloads\pc_wxapkg_decrypt\__APP__.wxapkg

    第三步:解包

    1
    2
    3
    4
    5
    6
    切换到./nodejs目录下,使用cmd命令打开
    输入下面命令

    node .\wuWxapkg.js C:\AMD\operation\2\wxapkg\666.wxapkg&exit

    第二个参数为操作的项目,这里操作的是666.wxapkg 记得改为自己的

    示例

    • 拆包666.wxapkg

    舔狗日志

    舔狗日记 3.19 ☀️周四

    今天发工资了,我一个月工资1500,你猜我会给你多少?是不是觉得我会给你1200,自己留300吃饭?哈哈,我1500都给你,因为厂里包吃包住。

    舔狗日记 3.19 ☀️周四

    昨天你把我删了,我看着红色感叹号陷入了久久的沉思,我想这其中一定有什么含义。红色红色,我明白了,红色代表热情,你对我很热情,你想和我结婚,我愿意。

    舔狗日记 3.19 ☀️周四

    昨天我还是照常给你发了好多消息 你回了我五个字“烦不烦啊你” 你开始关心我觉不觉得烦了 我太感动了 受宠若惊的 不烦不烦 你天天骂我我都不会觉得你烦

    舔狗反击日记 3月20日 ☀️

    我还是很喜欢你,就像,我如果有一百块钱,我愿意花30打车去找你,然后花60去找狗胖子买两张惊奇队长,然后花八块钱给你买一杯冰阔乐。看完电影,我会用最后两块钱,去坐公交,去银行取两万自己去吃螃蟹龙虾三文鱼蛋糕松塔章鱼丸子酸菜鱼香辣鸡翅麦旋风芒果布丁金丝面羊肉串火锅

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/7613e319.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/76cdd9a1.html b/76cdd9a1.html new file mode 100644 index 000000000..874e0a1f3 --- /dev/null +++ b/76cdd9a1.html @@ -0,0 +1 @@ +antd vue 多选框的全选功能实现和选中时候传入值的改变 | 梦洁小站-属于你我的小天地

    antd vue 多选框的全选功能实现和选中时候传入值的改变

    需求

    • ant design vue 多选框的全选功能实现和选中时候传入值的改变,官网的实例不太好,没有仔细说明怎么更改传入的值,因为很多情况下都是显示的是中文,传入的是数字,也就是id

    做法

    • 添加下面
      • 多选按钮和选项项
    1
    2
    3
    4
    5
    <a-checkbox v-model:checked="checkInfoData.checkAllInfo" :indeterminate="checkInfoData.indeterminate"     @change="onCheckAllChange">
    <span>选择所有</span>
    </a-checkbox>

    <a-checkbox-group v-model:value="checkInfoData.userSelectList" :options="checkInfoData.checkInfoDataList"></a-checkbox-group>
    • 数据项目
    1
    2
    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
    const checkInfoData = ref({
    indeterminate:false, //当选择项有一部分还没有选择的时候,就会显示这个,否者就是完整的勾勾了
    checkAllInfo:true,//全选 要默认全选
    //多选按钮的项目,必须要设置为这种格式的,label对应展示项,value对应存储的值
    checkInfoDataList:[
    {
    label:'选项A',
    value:1,
    },
    {
    label:'选项B',
    value:2,
    },
    {
    label:'选项C',
    value:3,
    },
    {
    label:'选项D',
    value:4,
    },
    {
    label:'选项E',
    value:5,
    },

    ],//可选的多选项
    userSelectList:[],//用户选中的项
    });
    • 回调
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    /* 全选单选框点击回调 */
    const onCheckAllChange = (e) => {
    //e.target.checked 获取全选按钮的选中状态
    if(e.target.checked){
    //当为全选的时候
    //将多选按钮的项目的value值赋值为用户选择,也就是变为这种形式 [1,2,3,4,5]
    checkInfoData.value.userSelectList = checkInfoData.value.checkInfoDataList.map(item => item.value);
    }else{
    //当为全不选的时候
    //清空选择的列表
    checkInfoData.value.userSelectList = []
    }
    //明确全选了,所以设置不明确全选的状态为false,也就是展示完整的勾勾
    checkInfoData.value.indeterminate = false;
    }

    /* 监听导出列表选中情况 */
    watch(()=>checkInfoData.value.userSelectList,(val)=>{
    //val 等同于 checkInfoData.value.userSelectList
    //判断是展示不确定的选择状态还是展示完整的勾勾
    checkInfoData.value.indeterminate = !!val.length && val.length < exportSyncData.value.checkInfoDataList.length;

    //判断全选的状态 当用户选择的长度会等于多选项列表的长度的时候即为全选
    checkInfoData.value.checkAllInfo = val.length === checkInfoData.value.checkInfoDataList.length;
    })

    原理

    • 原理就是监听全选按钮的选中状态,设置相应的回调就可以

    • indeterminate是什么?

      • indeterminate = true的时候

      indeterminate = true

      • indeterminate = false的时候

    indeterminate = false

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/76cdd9a1.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/77ecdc83.html b/77ecdc83.html new file mode 100644 index 000000000..9f8c50613 --- /dev/null +++ b/77ecdc83.html @@ -0,0 +1 @@ +记录下bilibili(b站)小火箭页面上划动画效果的实现 | 梦洁小站-属于你我的小天地

    记录下bilibili(b站)小火箭页面上划动画效果的实现

    突发

    效果

    哔哩哔哩小火箭效果

    素材

    rocket_top

    rocket_frame

    代码

    • 基础动画效果
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>bilibili小火箭</title>
    <style>
    * {
    margin: 0;
    padding: 0;
    }
    .rock {
    position: fixed;
    right: 0;
    bottom: 0;
    width: 150px;
    height: 174px;
    background-image: url("img/rocket_top.png");
    overflow: hidden;
    }

    .rock:hover {
    background-image: url("img/rocket_frame.png");
    animation: rockMove steps(4) .4s infinite;
    }
    @keyframes rockMove {
    0% {
    background-position-x: 0;
    }
    /* 25% {
    background-position-x: -150px;
    }

    50% {
    background-position-x: -300px;
    }

    75% {
    background-position-x: -450px;
    } */
    100% {
    background-position-x: -600px;
    }
    }
    </style>
    </head>

    <body>
    <div class="rock fly">

    </div>
    </body>

    </html>
    • 带js滚动到顶部效果
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>bilibili小火箭</title>
    <style>
    * {
    margin: 0;
    padding: 0;
    }

    body,html {
    width: 100%;
    height: 1500px;
    }

    .rock {
    position: fixed;
    right: 0;
    bottom: 0;
    width: 150px;
    height: 174px;
    background-image: url("img/rocket_top.png");
    overflow: hidden;
    display: none;
    }

    .rock:hover {
    background-image: url("img/rocket_frame.png");
    animation: rockMove steps(4) .4s infinite;
    }

    @keyframes rockMove {
    0% {
    background-position-x: 0;
    }

    /* 25% {
    background-position-x: -150px;
    }

    50% {
    background-position-x: -300px;
    }

    75% {
    background-position-x: -450px;
    } */
    100% {
    background-position-x: -600px;
    }
    }
    </style>
    </head>

    <body>
    <div class="rock fly">

    </div>
    <script>
    // 获取当前视口的大小
    var viewHeight = document.documentElement.clientHeight;
    //小火箭添加单击事件
    document.querySelector(".rock").addEventListener("click", () => {
    //小火箭被单击,回到顶部
    // document.documentElement.scrollTop = 0;
    //当然,也可以慢慢回到顶部
    slowToTop();
    })
    //当然,这里使用节流会更好
    window.onscroll = function () {
    if (document.documentElement.scrollTop >= viewHeight) {
    //显示小火箭元素
    document.querySelector(".rock").style.display = "block";
    } else {
    //隐藏小火箭
    document.querySelector(".rock").style.display = "none";
    }
    }
    window.onresize = function () {
    viewHeight = document.documentElement.clientHeight;
    }
    /* 缓慢回到顶部 */
    function slowToTop() {
    setTimeout(() => {
    let value = document.documentElement.scrollTop;
    if(value<=0){
    document.documentElement.scrollTop = 0;
    }else{
    document.documentElement.scrollTop -= 80;
    slowToTop();
    }
    }, 1000/50);
    }
    </script>
    </body>

    </html>
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/77ecdc83.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/7afd4786.html b/7afd4786.html new file mode 100644 index 000000000..81e8cd8f8 --- /dev/null +++ b/7afd4786.html @@ -0,0 +1 @@ +今日刷题-注意优先级 | 梦洁小站-属于你我的小天地

    今日刷题-注意优先级

    题目1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    执行以下代码,其实现的效果为()
    <div>
    <input type="button" id ="button1" value="1" onclick="moveBtn(this);">
    <input type="button" id ="button2" value="2" />
    </div>
    <script type="text/javascript">
    function moveBtn(obj) {
    var clone = obj.cloneNode(true);
    var parent = obj.parentNode;
    parent.appendChild(clone);
    parent.removeChild(obj);
    }
    </script>
    A: 鼠标单击button1后将button1链接到button2的后面

    B: 鼠标单击button1后将button1移动到button2的后面

    C: 鼠标单击button1后将button2移动到button1的后面

    D: 鼠标单击button1后将button2链接到button1的后面

    • 答案
      • B
    • 解析
      • **Node.cloneNode([deep])**方法返回调用该方法的节点的一个副本.
        • Node: 将要被克隆的节点
        • deep: 是否采用深度克隆,如果为true,则该节点的所有后代节点也都会被克隆,如果为false,则只克隆该节点本身.
      • 所以obj.cloneNode(true);深度克隆一个自己的节点,克隆之后删除原体后在移动克隆体到button1的后面

    题目2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    假设val已经声明,可定义为任何值。则下面js代码有可能输出的结果为:
    console.log('Value is ' + (val != '0') ? 'define' : 'undefine');
    A: Value is define

    B: Value is undefine

    C: define

    D: undefine

    E: Value is define 或者 Value isundefine

    D: define 或者 undefine

    E: 其他选项都有可能

    • 答案
      • C
    • 解析
      • 算术运算符优先级大于三元运算符,所以先计算 ‘Value is ‘ + (val != ‘0’) 后进行三元运算,但是这个计算结果不管怎么样都会为真(因为在js当中,除了 “” null undefined NaN false 0 这六个转化为布尔值为假,其他均为真,空数组,空对象转化为布尔值也是真!)所以这个三元运算符结果永远为 ‘define’
      • JavaScript当中运算符优先级文档

    题目3

    1
    已知数组arr=[1,69,4,6,8,10],对数组进行升序排列,下列选项中,不符合要求的是()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    A:
    for (var i = 0;i<arr.length-1;i++){

    for (var j = 0;j < arr.length-1-i;j++){

    if(arr[j]>arr[j+1]){

    var temp = arr[j];

    arr[j] = arr[j+1];

    arr[j+1] = temp;

    }

    }

    }

    console.log(arr);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    B:
    var minIndex;

    var temp;

    for(let i = 1; i < arr.length; i++) {

    minIndex = i - 1;

    for(let j = i; j <arr.length; j++) {

    if(arr[j] < arr[minIndex]) minIndex = j;

    }

    if(minIndex != i-1) {

    temp = arr[i-1];

    arr[i-1] = arr[minIndex];

    arr[minIndex] = temp;

    }
    }
    console.log(arr);
    1
    2
    3
    4
    C:
    arr.sort((a,b)=>b-a);

    console.log(arr);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    D:
    for(let i = 1; i < arr.length; i ++) {

    for(let j = i - 1; j>=0 && arr[j] > arr[j+1]; j --) {

    let temp = arr[j];

    arr[j] = arr[j+1];

    arr[j+1] = temp;

    }
    }
    console.log(arr);
    • 答案
      • C选项
    • 解析
      • sort当中arr.sort( ( a , b ) ) => b - a ) ;为降序, arr.sort( ( a , b ) ) => a-b ) 为升序
      • A是冒泡排序
      • B是选择排序
      • D是插入排序
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/7afd4786.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/7c53a2bd.html b/7c53a2bd.html new file mode 100644 index 000000000..276002afe --- /dev/null +++ b/7c53a2bd.html @@ -0,0 +1 @@ +防抖节流的使用和封装成函数 | 梦洁小站-属于你我的小天地

    防抖节流的使用和封装成函数

    前置知识

    关于this指向问题,防抖函数中的fn.apply(this,arguments)作用

    防抖函数中的fn.apply(this,arguments)作用

    this指向问题

    节流

    1. 一定时间内只执行一项任务

    节流原理

    1. 执行一个函数
    2. 执行这个函数的时候看看前面有没有执行过
    3. 如果前面有执行过这个函数并且没有完成,那么本次任务就不执行

    节流前

    可以看到"move"疯狂输出

    节流后

    程序输出move的次数明显减少

    节流函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    /**
    * 节流
    * @param {function} fn 要节流的函数
    * @param {number} delay 延迟(类似于fps一样~)
    * return 执行函数
    */
    function throttleMy(fn, delay) {
    //这样子建立了一个闭包,timer始终存在
    var timer=null;
    return function (...args) {
    if (timer) {
    return;
    }
    timer = setTimeout(function () {
    //传入的为[30,40,50....这种形式],所以不能单纯fn(args)
    //不确定参数,加上参数形式为[....],就使用fn.apply(this.args);
    fn.apply(this, args);
    timer=null;
    }, delay);
    }
    }

    节流函数使用示例代码

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title></title>
    <style>
    .box {
    width: 100px;
    height: 100px;
    background-color: red;
    position: relative;
    top: 100px;
    }
    </style>
    </head>
    <body>
    <div class="box">
    </div>
    <script>
    var box = document.querySelector(".box");
    window.onmousemove=throttleMy(function(e){
    box.style.left = e.pageX - (box.clientWidth) / 2 + "px";
    box.style.top = e.pageY - (box.clientHeight) / 2 + "px";
    },40)

    /**
    * @param {function} fn 要防抖的函数
    * @param {number} delay 延迟(类似于fps一样~)
    * return 执行函数
    */
    function throttleMy(fn, delay) {
    //这样子建立了一个闭包,timer始终存在
    var timer=null;
    return function (...args) {
    if (timer) {
    return;
    }
    timer = setTimeout(function () {
    //传入的为[30,40,50....这种形式],所以不能单纯fn(args)
    //不确定参数,加上参数形式为[....],就使用fn.apply(this.args);
    fn.apply(this, args);
    timer=null;
    }, delay);
    }
    }
    </script>
    </body>
    </html>

    防抖

    防抖原理

    1. 执行一个函数
    2. 执行这个函数的时候一段时间后如果有东东再次执行这个函数,则重新计时后在次调用

    防抖前

    输入框输入"你好"

    防抖后

    输入框再次输入"你好"

    可以看到少了很多次

    防抖函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    /**
    * 防抖
    * @param {function} fn 要防抖的函数
    * @param {number} delay 延迟(类似于fps一样~)
    * return 执行函数
    */
    function debounce(fn, delay) {
    //这样子建立了一个闭包,timer始终存在
    var timer = null;
    return function (...args) {
    if (timer) {
    clearTimeout(timer); //清除上一次的
    }
    timer = setTimeout(function () {
    //传入的为[30,40,50....这种形式],所以不能单纯fn(args)
    //不确定参数,加上参数形式为[....],就使用fn.apply(this.args);
    fn.apply(this, args);
    timer = null;
    }, delay);
    }
    }

    防抖函数使用示例代码

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    </head>

    <body>
    账户:<input type="text" id="useript">
    <script>
    var ipt = document.getElementById("useript");
    ipt.onkeyup=debounce(function(e){
    console.log(e);
    },40);
    /**
    * 防抖
    * @param {function} fn 要防抖的函数
    * @param {number} delay 延迟(类似于fps一样~)
    * return 执行函数
    */
    function debounce(fn, delay) {
    //这样子建立了一个闭包,timer始终存在
    var timer = null;
    return function (...args) {
    if (timer) {
    clearTimeout(timer); //清除上一次的
    }
    timer = setTimeout(function () {
    //传入的为[30,40,50....这种形式],所以不能单纯fn(args)
    //不确定参数,加上参数形式为[....],就使用fn.apply(this.args);
    fn.apply(this, args);
    timer = null;
    }, delay);
    }
    }
    </script>
    </body>

    </html>

    娱乐一刻

    "昨晚你终于回我信息了,你回了一句谢谢还加了一个爱心。当时我在工地上激动的差点把隔壁的吊塔阿姨给亲了。不过我想了想你笑起来的样子我还是忍住了。你给我发爱心,一定是已经爱上我了吧,放心,我连咱们的孩子名字都想好了。XX等我,我一定会继续努力挣钱,给你买更多的化妆品,发更多的红包!!"

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/7c53a2bd.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/7c9666d2.html b/7c9666d2.html new file mode 100644 index 000000000..57d075ecc --- /dev/null +++ b/7c9666d2.html @@ -0,0 +1 @@ +ant design vue时间范围(range-picker)自定义时间段范围 | 梦洁小站-属于你我的小天地

    ant design vue时间范围(range-picker)自定义时间段范围

    需求

    • 最近有这么一个要求
    • 选取时间的时候,禁止选择今天和之后的日期(因为可能没有数据嘛)

    选取时间的时候,禁止选择今天和之后的日期

    • 选取的时间区间端不能超过30天

    选取的时间区间端不能超过30天

    做法

    • 使用a-range-picker

    • 添加以下属性

      • v-model:value="searchForm.dataTime"选取的值记录
      • value-format="YYYY-MM-DD"可选,值的格式
      • :disabledDate="disabledDateHandler"不可选择的日期(日期当中的每一天都会调用这个函数)
      • @calendarChange="dates => dateCalc.selectDateTime = dates[0]"待选日期发生变化的回调也就是我们每一次点击选择日期,就会调用一下这一个函数,这里作用是记录下第一个选择的日期时间(也就是日期开始的时间)
      • @openChange="_ => dateCalc.selectDateTime = ''"弹出日历和关闭日历的回调,这里弹出或者关闭日期选择的时候清空下之前记录日期开始值
      • @change="dateCalc.selectDateTime = ''"时间发生变化的回调,也就是开始时间,结束时间都选取完成后执行的回调
      • :ranges="timeScopedPre"选取预设,可选(比如点击一下就可以选取7天,14天,30天的预设)
      1
      2
      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
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      import moment from "moment";
      <a-range-picker
      v-model:value="searchForm.dataTime"
      value-format="YYYY-MM-DD"
      :disabledDate="disabledDateHandler"
      @calendarChange="dates => dateCalc.selectDateTime = dates[0]"
      @change="dateCalc.selectDateTime = ''"
      @openChange="_ => dateCalc.selectDateTime = ''"
      :ranges="timeScopedPre"
      />


      const searchForm = ref<SearchFormTypes>({
      dataTime:[moment().subtract(7,'days').format('YYYY-MM-DD'),moment().subtract(1,'days').format('YYYY-MM-DD')] //数据时间 默认前七天
      });

      //计算日期相关数据
      const dateCalc = ref<any>({
      selectDateTime:"",//计算日期数据-限制30天
      })

      const timeScopedPre = shallowRef<any>({
      '近七日':[moment().subtract(7,'d'),moment().subtract(1,'d')],
      '近14日':[moment().subtract(14,'d'),moment().subtract(1,'d')],
      '近30日':[moment().subtract(30,'d'),moment().subtract(1,'d')],
      })

      /* 禁用时间 */
      const disabledDateHandler = (current) => {
      if(dateCalc.value.selectDateTime){
      //这里实现下面功能
      //只能选取30天,这里需要填写29,不然会出现多出来了一天的情况
      //和禁止选择今天和之后的日期
      return current > moment(dateCalc.value.selectDateTime).add(29,'days') ||
      current < moment(dateCalc.value.selectDateTime).subtract(29,'days') ||
      current > moment().subtract(1,'days').endOf('day')
      }else {
      //这里实现下面功能
      //禁止选择今天和之后的日期
      return current > moment().subtract(1,'days').endOf('day');
      }
      }


    • 属性值一些说明

      • timeScopedPre:时间预设
      1
      2
      3
      4
      5
      6
      7
      import moment from "moment";
      //时间范围预设
      const timeScopedPre = shallowRef<any>({
      '近七日':[moment().subtract(7,'d'),moment().subtract(1,'d')],
      '近14日':[moment().subtract(14,'d'),moment().subtract(1,'d')],
      '近30日':[moment().subtract(30,'d'),moment().subtract(1,'d')],
      })
      • disabledDateHandler函数
        • 你如果需要禁用n天,这里只需要传入n-1就可以,比如禁用180天,这里把29改为179即可
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      /* 禁用时间 */
      const disabledDateHandler = (current) => {
      if(dateCalc.value.selectDateTime){
      //这里实现下面功能
      //只能选取30天,这里需要填写29,不然会出现多出来了一天的情况
      //和禁止选择今天和之后的日期
      return current > moment(dateCalc.value.selectDateTime).add(29,'days') ||
      current < moment(dateCalc.value.selectDateTime).subtract(29,'days') ||
      current > moment().subtract(1,'days').endOf('day')
      }else {
      //这里实现下面功能
      //禁止选择今天和之后的日期
      return current > moment().subtract(1,'days').endOf('day');
      }
      }
      • searchForm
      1
      2
      3
      4
      import moment from "moment";
      const searchForm = ref<SearchFormTypes>({
      dataTime:[moment().subtract(7,'days').format('YYYY-MM-DD'),moment().subtract(1,'days').format('YYYY-MM-DD')] //数据时间 默认前七天
      });
      • dateCalc
      1
      2
      3
      4
      //计算日期相关数据
      const dateCalc = ref<any>({
      selectDateTime:"",//计算日期数据-限制30天
      })

    disabledDateHandler函数禁用原理讲解

    • 禁用30天原理,当然,你如果需要禁用n天,这里只需要传入n-1就可以

    禁用30天原理

    • 禁止选择今天和之后的日期

    禁止选择今天和之后的日期

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/7c9666d2.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/7e386358.html b/7e386358.html new file mode 100644 index 000000000..1ae6d8c37 --- /dev/null +++ b/7e386358.html @@ -0,0 +1 @@ +2020年用JAVA制作的一个小项目图标快捷启动管理的 | 梦洁小站-属于你我的小天地

    2020年用JAVA制作的一个小项目图标快捷启动管理的

    1
    2020年的了,本来想用这个参加学校的作品大赛的获取经验的,想参加试试看,因为疫情没弄成,取消了

    用JAVA写的,支持库都放在里面了

    功能展示

    默认主界面

    托盘菜单

    右键空白菜单

    添加网站快捷方式

    添加其他图标

    分类栏重命名

    右键图标菜单栏

    顶部导航栏

    搜索功能

    还有其他的就不展示图片了~

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/7e386358.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/7ec112ae.html b/7ec112ae.html new file mode 100644 index 000000000..e4da1d598 --- /dev/null +++ b/7ec112ae.html @@ -0,0 +1 @@ +docker环境下的verdaccio设置权限并配置域名 | 梦洁小站-属于你我的小天地

    docker环境下的verdaccio设置权限并配置域名

    权限配置

    • 一个管理员叫admin,可以读也可以发布
    • 一个普通用户叫qiuye,只可以读,不可以发布
    • 添加账号就自行创建添加即可,只需要更改config文件的配置项即可
    1
    2
    3
    4
    5
    6
    7
    8
    9
    packages:
    '@*/*':
    access: admin qiuye
    publish: admin
    unpublish: admin
    '**':
    access: admin qiuye
    publish: admin
    unpublish: admin

    域名配置

    • 这里是docker配置的,只需要docker配置的yml文件添加变量,然后使用nginx设置代理转发即可

      • 必须要环境变量配置,否则会出现Refused to connect to 'http://my.site/verdaccio/-/verdaccio/packages' because it violates the following Content Security Policy
    • yml如下

    • 随后配置nginx即可

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/7ec112ae.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/8009c290.html b/8009c290.html new file mode 100644 index 000000000..1d5e26df2 --- /dev/null +++ b/8009c290.html @@ -0,0 +1 @@ +今日刷题-try...catch...finally | 梦洁小站-属于你我的小天地

    今日刷题-try...catch...finally

    题目1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var i = 100;
    function foo() {
    bbb: try {
    console.log("position1");
    return i++;
    }
    finally {
    break bbb;
    }
    console.log("position2");
    return i;
    }
    foo();
    • 答案

      • position1 position2
    • 解析,上面代码转换下,对齐下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var i = 100;
    function foo() {
    bbb: try {
    console.log("position1");
    return i++;//i此时从100变为101
    }
    finally {
    break bbb;
    }
    console.log("position2");
    return i;//i此时的值为101
    }
    foo();
    • 在try…..catch ….finally 当中,不管try里面的结果怎么样,finally都会被执行,至于 finally的break bbb;是跳出bbb标签代码块

    题目2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <SCRIPT LANGUAGE="JavaScript">
    var a="undefined";
    var b="false";
    var c="";
    function assert(aVar){
    if(aVar)
    alert(true);
    else
    alert(false);
    }
    assert(a);
    assert(b);
    assert(c);
    </SCRIPT>
    • 答案
      • true true false
    • 解析
      • 大意了,除了 “” null undefined false 0 NaN 这六种转布尔值为false外,其他转化为布尔值都为true
      • var a = “undefined” =>字符串,转布尔值为true
      • var b = “false” = > 字符串 ,转布尔值为true
      • var c = “” = > 空字符串, 转布尔值为false

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/8009c290.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/81611d8.html b/81611d8.html new file mode 100644 index 000000000..1fbb99c3a --- /dev/null +++ b/81611d8.html @@ -0,0 +1 @@ +我来图书馆小程序签到流程分析 | 梦洁小站-属于你我的小天地

    我来图书馆小程序签到流程分析

    先抓包看看提交的哪些参数

    再来看看源代码的

    • 可以看到,我们单击签到,是跳转到了iBeacon页面

    • 我们再去iBeacon页面看看,

    可以看到关键的签到代码

    1
    2
    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
    35
    36
    37
    38
    signAppoint: function() {
    wx.showLoading({
    title: "签到座位中",
    mask: !0
    });
    var o = getApp(), e = wx.getStorageSync("colleageId"), a = wx.getStorageSync("userId"), n = t.getKeyPair(o.globalData.rsa.exponent, "", o.globalData.rsa.modulus), s = t.encryptedString(n, this.data.urlOptions.appoint_id + "");
    wx.request({
    url: o.globalData.httpAddress + "/wx/sign",
    data: {
    colleageId: e,
    userId: a,
    appointId: s,
    x: this.data.latitude,
    y: this.data.longitude
    },
    header: {
    "content-type": "application/json"
    },
    success: function(t) {
    console.log(t.data), t.data.success, wx.showToast({
    title: t.data.message,
    mask: !0,
    icon: "none",
    duration: 2e3
    }), setTimeout(function() {
    wx.hideLoading(), wx.navigateBack({
    delta: 1
    });
    }, 2100);
    },
    fail: function(t) {
    console.log(t), wx.showToast({
    icon: "none",
    title: "服务器错误"
    });
    }
    });
    },
    • 然后我们通过找寻appointId的来源,后面发现可以通过https://wxcourse.jxufe.cn/wxlib/wx/mineCurrentAppoint?userId=个人学号当中的id字段来获取
    • 如图,图中的id就是appoint_id字段

    签到大概流程就这样子

    顺便附上代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //蓝牙签到
    const ttt = require("./security.js");
    var modulus = "00ae1d6d965af848bc5814af9073dce0b03ee18f5f2448f922549826c7ae54601ea7c09ef026c8997343833160298849a9b73483f324100b7095b4bd10afabed447ea1b0871ca613aeb391f1e7361f3ae0a147d1431ddd1c1c080ba46a51d70dc93508a9fc4dcc683ed64d429e026d1335ab01020cfee00e788d78dced6fe5199b";
    var exponent = "010001";
    var n = ttt.getKeyPair(exponent, "", modulus);

    //预约位置的id信息为每一次预约都变化
    var appoint_id = "id信息填写";
    var check_result = ttt.encryptedString(n, appoint_id + "");
    console.log("签到预约代码",check_result);

    security.js文件内容

    • 我是以nodejs运行的,security.js也可以浏览器运行,改改就OK
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    var i;

    !function(t) {
    void 0 === t.RSAUtils && (i = t.RSAUtils = {});
    var r, e, s, g = t.BigInt = function(i) {
    this.digits = "boolean" == typeof i && 1 == i ? null : r.slice(0), this.isNeg = !1;
    };
    i.setMaxDigits = function(i) {
    r = new Array(i);
    for (var t = 0; t < r.length; t++) r[t] = 0;
    e = new g(), (s = new g()).digits[0] = 1;
    }, i.setMaxDigits(20);
    i.biFromNumber = function(i) {
    var t = new g();
    t.isNeg = i < 0, i = Math.abs(i);
    for (var r = 0; i > 0; ) t.digits[r++] = 65535 & i, i = Math.floor(i / 65536);
    return t;
    };
    var n = i.biFromNumber(1e15);
    i.biFromDecimal = function(t) {
    for (var r, e = "-" == t.charAt(0), s = e ? 1 : 0; s < t.length && "0" == t.charAt(s); ) ++s;
    if (s == t.length) r = new g(); else {
    var d = (t.length - s) % 15;
    for (0 == d && (d = 15), r = i.biFromNumber(Number(t.substr(s, d))), s += d; s < t.length; ) r = i.biAdd(i.biMultiply(r, n), i.biFromNumber(Number(t.substr(s, 15)))),
    s += 15;
    r.isNeg = e;
    }
    return r;
    }, i.biCopy = function(i) {
    var t = new g(!0);
    return t.digits = i.digits.slice(0), t.isNeg = i.isNeg, t;
    }, i.reverseStr = function(i) {
    for (var t = "", r = i.length - 1; r > -1; --r) t += i.charAt(r);
    return t;
    };
    var d = [ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z" ];
    i.biToString = function(t, r) {
    var s = new g();
    s.digits[0] = r;
    for (var n = i.biDivideModulo(t, s), o = d[n[1].digits[0]]; 1 == i.biCompare(n[0], e); ) n = i.biDivideModulo(n[0], s),
    digit = n[1].digits[0], o += d[n[1].digits[0]];
    return (t.isNeg ? "-" : "") + i.reverseStr(o);
    }, i.biToDecimal = function(t) {
    var r = new g();
    r.digits[0] = 10;
    for (var s = i.biDivideModulo(t, r), n = String(s[1].digits[0]); 1 == i.biCompare(s[0], e); ) s = i.biDivideModulo(s[0], r),
    n += String(s[1].digits[0]);
    return (t.isNeg ? "-" : "") + i.reverseStr(n);
    };
    var o = [ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" ];
    i.digitToHex = function(t) {
    for (var r = "", e = 0; e < 4; ++e) r += o[15 & t], t >>>= 4;
    return i.reverseStr(r);
    }, i.biToHex = function(t) {
    for (var r = "", e = (i.biHighIndex(t), i.biHighIndex(t)); e > -1; --e) r += i.digitToHex(t.digits[e]);
    return r;
    }, i.charToHex = function(i) {
    return i >= 48 && i <= 57 ? i - 48 : i >= 65 && i <= 90 ? 10 + i - 65 : i >= 97 && i <= 122 ? 10 + i - 97 : 0;
    }, i.hexToDigit = function(t) {
    for (var r = 0, e = Math.min(t.length, 4), s = 0; s < e; ++s) r <<= 4, r |= i.charToHex(t.charCodeAt(s));
    return r;
    }, i.biFromHex = function(t) {
    for (var r = new g(), e = t.length, s = 0; e > 0; e -= 4, ++s) r.digits[s] = i.hexToDigit(t.substr(Math.max(e - 4, 0), Math.min(e, 4)));
    return r;
    }, i.biFromString = function(t, r) {
    var e = "-" == t.charAt(0), s = e ? 1 : 0, n = new g(), d = new g();
    d.digits[0] = 1;
    for (var o = t.length - 1; o >= s; o--) {
    var u = t.charCodeAt(o), a = i.charToHex(u), b = i.biMultiplyDigit(d, a);
    n = i.biAdd(n, b), d = i.biMultiplyDigit(d, r);
    }
    return n.isNeg = e, n;
    }, i.biDump = function(i) {
    return (i.isNeg ? "-" : "") + i.digits.join(" ");
    }, i.biAdd = function(t, r) {
    var e;
    if (t.isNeg != r.isNeg) r.isNeg = !r.isNeg, e = i.biSubtract(t, r), r.isNeg = !r.isNeg; else {
    e = new g();
    for (var s, n = 0, d = 0; d < t.digits.length; ++d) s = t.digits[d] + r.digits[d] + n,
    e.digits[d] = s % 65536, n = Number(s >= 65536);
    e.isNeg = t.isNeg;
    }
    return e;
    }, i.biSubtract = function(t, r) {
    var e;
    if (t.isNeg != r.isNeg) r.isNeg = !r.isNeg, e = i.biAdd(t, r), r.isNeg = !r.isNeg; else {
    var s, n;
    e = new g(), n = 0;
    for (var d = 0; d < t.digits.length; ++d) s = t.digits[d] - r.digits[d] + n, e.digits[d] = s % 65536,
    e.digits[d] < 0 && (e.digits[d] += 65536), n = 0 - Number(s < 0);
    if (-1 == n) {
    n = 0;
    for (d = 0; d < t.digits.length; ++d) s = 0 - e.digits[d] + n, e.digits[d] = s % 65536,
    e.digits[d] < 0 && (e.digits[d] += 65536), n = 0 - Number(s < 0);
    e.isNeg = !t.isNeg;
    } else e.isNeg = t.isNeg;
    }
    return e;
    }, i.biHighIndex = function(i) {
    for (var t = i.digits.length - 1; t > 0 && 0 == i.digits[t]; ) --t;
    return t;
    }, i.biNumBits = function(t) {
    var r, e = i.biHighIndex(t), s = t.digits[e], g = 16 * (e + 1);
    for (r = g; r > g - 16 && 0 == (32768 & s); --r) s <<= 1;
    return r;
    }, i.biMultiply = function(t, r) {
    for (var e, s, n, d = new g(), o = i.biHighIndex(t), u = i.biHighIndex(r), a = 0; a <= u; ++a) {
    e = 0, n = a;
    for (var b = 0; b <= o; ++b, ++n) s = d.digits[n] + t.digits[b] * r.digits[a] + e,
    d.digits[n] = 65535 & s, e = s >>> 16;
    d.digits[a + o + 1] = e;
    }
    return d.isNeg = t.isNeg != r.isNeg, d;
    }, i.biMultiplyDigit = function(t, r) {
    var e, s, n, d = new g();
    e = i.biHighIndex(t), s = 0;
    for (var o = 0; o <= e; ++o) n = d.digits[o] + t.digits[o] * r + s, d.digits[o] = 65535 & n,
    s = n >>> 16;
    return d.digits[1 + e] = s, d;
    }, i.arrayCopy = function(i, t, r, e, s) {
    for (var g = Math.min(t + s, i.length), n = t, d = e; n < g; ++n, ++d) r[d] = i[n];
    };
    var u = [ 0, 32768, 49152, 57344, 61440, 63488, 64512, 65024, 65280, 65408, 65472, 65504, 65520, 65528, 65532, 65534, 65535 ];
    i.biShiftLeft = function(t, r) {
    var e = Math.floor(r / 16), s = new g();
    i.arrayCopy(t.digits, 0, s.digits, e, s.digits.length - e);
    for (var n = r % 16, d = 16 - n, o = s.digits.length - 1, a = o - 1; o > 0; --o,
    --a) s.digits[o] = s.digits[o] << n & 65535 | (s.digits[a] & u[n]) >>> d;
    return s.digits[0] = s.digits[o] << n & 65535, s.isNeg = t.isNeg, s;
    };
    var a = [ 0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767, 65535 ];
    function b(t) {
    var r = i, e = r.biDivideByRadixPower(t, this.k - 1), s = r.biMultiply(e, this.mu), g = r.biDivideByRadixPower(s, this.k + 1), n = r.biModuloByRadixPower(t, this.k + 1), d = r.biMultiply(g, this.modulus), o = r.biModuloByRadixPower(d, this.k + 1), u = r.biSubtract(n, o);
    u.isNeg && (u = r.biAdd(u, this.bkplus1));
    for (var a = r.biCompare(u, this.modulus) >= 0; a; ) u = r.biSubtract(u, this.modulus),
    a = r.biCompare(u, this.modulus) >= 0;
    return u;
    }
    function h(t, r) {
    var e = i.biMultiply(t, r);
    return this.modulo(e);
    }
    function l(t, r) {
    var e = new g();
    e.digits[0] = 1;
    for (var s = t, n = r; 0 != (1 & n.digits[0]) && (e = this.multiplyMod(e, s)), 0 != (n = i.biShiftRight(n, 1)).digits[0] || 0 != i.biHighIndex(n); ) s = this.multiplyMod(s, s);
    return e;
    }
    i.biShiftRight = function(t, r) {
    var e = Math.floor(r / 16), s = new g();
    i.arrayCopy(t.digits, e, s.digits, 0, t.digits.length - e);
    for (var n = r % 16, d = 16 - n, o = 0, u = o + 1; o < s.digits.length - 1; ++o,
    ++u) s.digits[o] = s.digits[o] >>> n | (s.digits[u] & a[n]) << d;
    return s.digits[s.digits.length - 1] >>>= n, s.isNeg = t.isNeg, s;
    }, i.biMultiplyByRadixPower = function(t, r) {
    var e = new g();
    return i.arrayCopy(t.digits, 0, e.digits, r, e.digits.length - r), e;
    }, i.biDivideByRadixPower = function(t, r) {
    var e = new g();
    return i.arrayCopy(t.digits, r, e.digits, 0, e.digits.length - r), e;
    }, i.biModuloByRadixPower = function(t, r) {
    var e = new g();
    return i.arrayCopy(t.digits, 0, e.digits, 0, r), e;
    }, i.biCompare = function(i, t) {
    if (i.isNeg != t.isNeg) return 1 - 2 * Number(i.isNeg);
    for (var r = i.digits.length - 1; r >= 0; --r) if (i.digits[r] != t.digits[r]) return i.isNeg ? 1 - 2 * Number(i.digits[r] > t.digits[r]) : 1 - 2 * Number(i.digits[r] < t.digits[r]);
    return 0;
    }, i.biDivideModulo = function(t, r) {
    var e, n, d = i.biNumBits(t), o = i.biNumBits(r), u = r.isNeg;
    if (d < o) return t.isNeg ? ((e = i.biCopy(s)).isNeg = !r.isNeg, t.isNeg = !1, r.isNeg = !1,
    n = biSubtract(r, t), t.isNeg = !0, r.isNeg = u) : (e = new g(), n = i.biCopy(t)),
    [ e, n ];
    e = new g(), n = t;
    for (var a = Math.ceil(o / 16) - 1, b = 0; r.digits[a] < 32768; ) r = i.biShiftLeft(r, 1),
    ++b, ++o, a = Math.ceil(o / 16) - 1;
    n = i.biShiftLeft(n, b), d += b;
    for (var h = Math.ceil(d / 16) - 1, l = i.biMultiplyByRadixPower(r, h - a); -1 != i.biCompare(n, l); ) ++e.digits[h - a],
    n = i.biSubtract(n, l);
    for (var f = h; f > a; --f) {
    var v = f >= n.digits.length ? 0 : n.digits[f], c = f - 1 >= n.digits.length ? 0 : n.digits[f - 1], N = f - 2 >= n.digits.length ? 0 : n.digits[f - 2], m = a >= r.digits.length ? 0 : r.digits[a], M = a - 1 >= r.digits.length ? 0 : r.digits[a - 1];
    e.digits[f - a - 1] = v == m ? 65535 : Math.floor((65536 * v + c) / m);
    for (var y = e.digits[f - a - 1] * (65536 * m + M), p = 4294967296 * v + (65536 * c + N); y > p; ) --e.digits[f - a - 1],
    y = e.digits[f - a - 1] * (65536 * m | M), p = 65536 * v * 65536 + (65536 * c + N);
    l = i.biMultiplyByRadixPower(r, f - a - 1), (n = i.biSubtract(n, i.biMultiplyDigit(l, e.digits[f - a - 1]))).isNeg && (n = i.biAdd(n, l),
    --e.digits[f - a - 1]);
    }
    return n = i.biShiftRight(n, b), e.isNeg = t.isNeg != u, t.isNeg && (e = u ? i.biAdd(e, s) : i.biSubtract(e, s),
    r = i.biShiftRight(r, b), n = i.biSubtract(r, n)), 0 == n.digits[0] && 0 == i.biHighIndex(n) && (n.isNeg = !1),
    [ e, n ];
    }, i.biDivide = function(t, r) {
    return i.biDivideModulo(t, r)[0];
    }, i.biModulo = function(t, r) {
    return i.biDivideModulo(t, r)[1];
    }, i.biMultiplyMod = function(t, r, e) {
    return i.biModulo(i.biMultiply(t, r), e);
    }, i.biPow = function(t, r) {
    for (var e = s, g = t; 0 != (1 & r) && (e = i.biMultiply(e, g)), 0 != (r >>= 1); ) g = i.biMultiply(g, g);
    return e;
    }, i.biPowMod = function(t, r, e) {
    for (var g = s, n = t, d = r; 0 != (1 & d.digits[0]) && (g = i.biMultiplyMod(g, n, e)),
    0 != (d = i.biShiftRight(d, 1)).digits[0] || 0 != i.biHighIndex(d); ) n = i.biMultiplyMod(n, n, e);
    return g;
    }, t.BarrettMu = function(t) {
    this.modulus = i.biCopy(t), this.k = i.biHighIndex(this.modulus) + 1;
    var r = new g();
    r.digits[2 * this.k] = 1, this.mu = i.biDivide(r, this.modulus), this.bkplus1 = new g(),
    this.bkplus1.digits[this.k + 1] = 1, this.modulo = b, this.multiplyMod = h, this.powMod = l;
    };
    var f = function(r, e, s) {
    var g = i;
    this.e = g.biFromHex(r), this.d = g.biFromHex(e), this.m = g.biFromHex(s), this.chunkSize = 2 * g.biHighIndex(this.m),
    this.radix = 16, this.barrett = new t.BarrettMu(this.m);
    };
    i.getKeyPair = function(i, t, r) {
    return new f(i, t, r);
    }, void 0 === t.twoDigit && (t.twoDigit = function(i) {
    return (i < 10 ? "0" : "") + String(i);
    }), i.encryptedString = function(t, r) {
    for (var e = [], s = r.length, n = 0; n < s; ) e[n] = r.charCodeAt(n), n++;
    for (;e.length % t.chunkSize != 0; ) e[n++] = 0;
    var d, o, u, a = e.length, b = "";
    for (n = 0; n < a; n += t.chunkSize) {
    for (u = new g(), d = 0, o = n; o < n + t.chunkSize; ++d) u.digits[d] = e[o++],
    u.digits[d] += e[o++] << 8;
    var h = t.barrett.powMod(u, t.e);
    b += (16 == t.radix ? i.biToHex(h) : i.biToString(h, t.radix)) + " ";
    }
    return b.substring(0, b.length - 1);
    }, i.decryptedString = function(t, r) {
    var e, s, g, n = r.split(" "), d = "";
    for (e = 0; e < n.length; ++e) {
    var o;
    for (o = 16 == t.radix ? i.biFromHex(n[e]) : i.biFromString(n[e], t.radix), g = t.barrett.powMod(o, t.d),
    s = 0; s <= i.biHighIndex(g); ++s) d += String.fromCharCode(255 & g.digits[s], g.digits[s] >> 8);
    }
    return 0 == d.charCodeAt(d.length - 1) && (d = d.substring(0, d.length - 1)), d;
    }, i.setMaxDigits(130);
    }({}), module.exports = i;

    舔狗日志

    舔狗日记 6月19日 晴

    今天你终于通过我好友了,打招呼的方式还是那么别致,一个阿玛尼包包的淘宝链接,我从兄弟那边借了3000,很快给你买了,你很开心,给我发了可爱的表情包,还对我说了谢谢,你开心,我也就开心了


    舔狗日记 6月20日 晴

    今天发工资了,我一个月工资800,你猜我会给你多少,是不是觉得我会给你1200,因为厂里全勤奖还有400。错了,我会再和工友借114凑够1314转给你。

    舔狗日记 6月21日 阴

    我给你打了一通电话,你终于接了。听到了你发出啊啊啊啊的声音,你说你脚痛,我想你一定是很难受吧。电话还有个男的对你说“来换个姿势”。你一定是在做理疗,好心疼,期待你早日康复。

    舔狗日记 10月26日 晴

    今天约喜欢的女孩子一起出去喝奶茶,她回我一句“有病”,我想着,有病啊,那等她病好了,再约她吧,嘿嘿


    舔狗日记 10月30日 晴

    今天你破天荒的给我发了个早,我开心极了,难道这就是恋爱的感觉吗?我一看时间,十二点整,你一醒来就在想我,我流下了激动的泪水,又想到你现在都没有吃饭,我给你发了二百块钱的红包。你快速的领取了,却迟迟没有回我消息。我想你可能也沉浸在感动当中吧,我给你发了句吃点东西吧。回复我的确实一个红色感叹号!红色代表爱情,你一定是不好意思说出口,采用这么委婉的方式表达你对我的爱,我也爱你。

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/81611d8.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/83d071ca.html b/83d071ca.html new file mode 100644 index 000000000..5b18afa3b --- /dev/null +++ b/83d071ca.html @@ -0,0 +1 @@ +vue2项目之明日科技51购物商店官网-本地项目版本 | 梦洁小站-属于你我的小天地

    vue2项目之明日科技51购物商店官网-本地项目版本

    介绍

    • 全部都用localStorage,没有与服务器的交互

    • 视频详情数据没有~但是你可以自己做

    • 添加了导航守卫

    • 地址编辑页没有写~,你可以自己写

    • 其他都OK,巴拉巴拉(应付应付最后的作业~~)

    • 下载地址

    • 在线演示地址

    • 使用

      • 第一步: 当前目录下命令行运行npm install
      • 第二步: 当前目录下命名行运行npm run serve
      • 第三步: 浏览器输入http://localhost:8080/进入

    图片展示

    首页

    购物车页

    结算页

    登录页

    • 密码使用aes加密了

    注册页

    • 会检索本地数据,如果有的话就会注册失败!

    商品详情

    • 有一个放大镜效果~

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/83d071ca.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/84b2339c.html b/84b2339c.html new file mode 100644 index 000000000..9ae35dcca --- /dev/null +++ b/84b2339c.html @@ -0,0 +1 @@ +x64dbg反汇编技术入门学习笔记 | 梦洁小站-属于你我的小天地

    x64dbg反汇编技术入门学习笔记

    EIP

    • EIP是程序下一次要运行地方

    寄存器

    • 临时存放数据,按照Intel规定去存放

    window API

    • 微软提供的,用户可以操作系统的一些接口,以函数的形式体现

    杀软是如何查杀恶意的

    • 镜像地址 + 实际地址 = 实际运行后代码的地址

    image-20240523204648774

    • 查外部调用段就可以定位到.rdata

    call的调用

    • 我们现在需要手动调用这个删除的

    • 我们找一片空白的内存区域

    • 填入内容
      • 我们改为删除123.txt文件

    • 替换为另一端内存地址

    if语句、eax寄存器与函数返回值的关系、流程图

    • eax存放函数的返回值

    • if在汇编中被编译为jne,可以通过右键流程图来查看流程

    内存布局和补丁

    • 我们可以通过内存布局来查找标题名称

    • 填入”把我改了”

      • 注意,有时候utf-8或者ascii无法搜索到,就需要点击代码页进行切换

    • 在引用当中可以看到我们查找的结果

    • 我们在内存窗口中查看看看

    • 我们在内存窗口切换为GBK,软件估计是用GBK写的,这里我用UTF-8乱码了

    • 修改内容

    • 选择”修补文件”

    • 这边随便弄了个名字

    jmp指令

    • 强制更改代码的执行流程

    标志寄存器

    • ZF
    • SF
    • OF
    • 等等

    常用无条件跳转

    • jmp

    • call

      • 执行函数
    • ret

      • 函数体不可或缺的一部分
      • 无条件从函数体内部跳出来

    无条件干掉一些不顺眼的函数

    按钮关闭

    • 我们找下按钮的提示字符串

    • 找到了

    • 进入此函数

    • 将第一行代码修改为ret即可

    植物大战僵尸特殊

    • 我们附加下窗口

    • 我们切换下

    1
    2
    3
    4
    5
    6
    ret8这个数字和堆栈相关,和堆栈平衡相关,函数相关,跟call相关
    如果你直接ret崩溃了,那就进入函数体内部,向下拉,看retx,把retx复制到函数头部就可以了

    效果:刚进入函数内部,函数就结束了,
    函数头部:push ebp
    函数尾部:retx

    完整的call

    • 有头
      • push ebp
    • 有尾
      • pop ebp

    EBP和ESP

    • EBP指针寄存器(extended stack pointer)

      • 内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶
      • 始终指向堆栈最上面的数据
    • ESP基址指针寄存器(extended base pointer)

      • 内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部
    • 只要经过函数头,ESP就会等于EBP

      1
      2
      push ebp
      mov ebp,esp
    1
    2
    3
    一个程序实际上是由很多个函数组成的,主函数去调用A函数,A函数调用B函数,B函数调用完是一定要返回的,
    不返回就说明没调用完,没调用完就没法继续执行别处的代码,没法恢复到函数调用前程序的状态
    调用前程序的状态由 ebp控制,基址指针寄存器,一开始就 push 了,有 push(压入),就得有 pop(弹出),万事万物成对出现

    植物大战僵尸跳过暂停

    • 我们先按照正常条件来走下,我们选择步过而不是步进,会发现会进入执行

    • 所以我们不让他进入执行,改为jmp即可

    • 发现游戏不暂停了

    函数调用约定与参数顺序

    • __cdecl是C/C++的默认调用约定,也就是说,游戏/软件(包括我们自己写的)都是用这个约定

    • __stdcall是WindowsAPI默认调用约定,微软的WINAPI都是这个调用约定

    • 如何理解?这种约定是为了配合函数调用出现的,函数调用必须按照这个规则,就像万事万物都有它的规则一样:太阳东升西落,夜晚繁星闪烁

    • 函数在这种规则下造成的现象:参数顺序

    • __cdecl入栈顺序:从右到左

    • __stdcall入栈顺序:也是从右到左

    1
    2
    3
    4
    5
    6
    7
    8
    9
    比如有一个函数
    void myFun(int a,int b){}

    myFun(10,20);

    //那么汇编可能就是
    push 20
    push 10
    call 函数

    函数调用约定与堆栈平衡、参数个数计算

    • 堆栈平衡
      • 简单理解为吃了多少,给我吐出来多少,
    • 注意下: 用的都是十六进制,可以用过堆栈平衡来计算参数的个数

    EBP寻址

    • 只要是单参数函数,都是最先push ecx

    MOV指令

    加减乘除

    • 其他略
    • cmp eax,3等同于sub eax 3,然后再根据结果去改变标志位
      • 下面图片我随便截图的

    push pop指令常用操作

    条件转移

    • 图片关系也就是条件上方的语句是否满足关系所示
    1
    2
    3
    4
    cmp eax,3
    je xxx

    比如上面的,意思就是判断eax === 3,如果是就跳转,不是就不跳

    浮点寄存器、浮点单参数函数

    • Push ecx
    • Xmm反汇编没有办法直接地址向地址传递,必须得通过寄存器

    image-20240602153001582

    浮点寄存器局部变量

    FPU寄存器与常用浮点运算、浮点栈-108

    知识点

    已到达系统断点

    • 系统的断点,系统领空

    • 程序会分段,可以通过PEID来查看分段的

      • 代码段

    PEID查看的段

    • 查外部调用段就可以定位到.rdata

    回到最初的点

    • 减号’-‘

    带参数的call与远程调用

    • 观察,特点,push,总之,有参数,就需要push,换句话说,call的参数需要push
    • 或者说,函数的参数需要push指令
    • push:压入,压了就能用了
    • 格式:
    1
    2
    push 参数
    cal1函数地址

    远程调用

    • 通过注入的方式调用call

    但凡是push地址的,都是已经赋值了的

    但凡是push寄存器的,都是在前面赋值的,就得找前面赋值了什么

    更改断点

    跳转颜色

    • 一般情况下
      • 蓝色为跳转成立,红色为跳转不成立

    • 对于无条件跳转,蓝色线条意味着一定会跳转。
    • 对于条件跳转,需查看跳转条件是否满足。
    • 对于函数调用,蓝色线条表示会跳转到函数并返回。

    回车可以临时进入函数体内部

    只要经过函数头,ESP就会等于EBP

    • EBP指针寄存器(extended stack pointer)
      • 内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的栈顶
      • 始终指向堆栈最上面的数据
    • ESP基址指针寄存器(extended base pointer)
      • 内存放着一个指针,该指针永远指向系统栈最上面一个栈帧的底部

    单双局部变量的函数分析,局部变量的生命周期

    • 函数内部的局部变量在汇编中怎么看呢?
    • 一个局部变量创建方法如下图
      • 看到push ecx move就是一个参数
    • 二个或多个局部变量方法sub esp,8,8为十六进制,这句话代表要开辟的新空间大小

    al,ah

    • al最右边的数
    • ah

    在线汇编网站

    https://godbolt.org/

    多个参数不一定有多个寄存器

    • 可能一定参数对应一个寄存器,也可能多个参数对应一个寄存器,总之不是一一对应的关系

    nop是什么都不干

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/84b2339c.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    avatar
    梦洁
    小小的字,有大大的梦想~分享我的前端学习过程,经历,错误,和一些其他折腾过程
    关注下我(* ̄▽ ̄*)
    公告
    不断更新中,有问题请留言回复(会通过邮箱提醒~)
    最新文章
    \ No newline at end of file diff --git a/881e4206.html b/881e4206.html new file mode 100644 index 000000000..486817614 --- /dev/null +++ b/881e4206.html @@ -0,0 +1 @@ +vue3中使用混入mixins在setup当中 | 梦洁小站-属于你我的小天地

    vue3中使用混入mixins在setup当中

    前置

    使用

    mixins/query.js文件

    1
    2
    3
    4
    5
    6
    7
    export default {
    methods: {
    sayHello() {
    alert("你好,我说hello");
    }
    }
    };

    App.vue(这里简单演示就直接在App.vue使用混入了)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <script>
    import HelloWorldVue from "./components/HelloWorld.vue";
    import query from "./mixins/query.js";//引入混入
    import { getCurrentInstance, onMounted } from "vue";
    export default {
    name: "App",
    components: {
    HelloWorld: HelloWorldVue,
    },
    mixins: [query],//使用混入
    setup() {
    const { proxy } = getCurrentInstance();
    onMounted(() => {
    proxy.sayHello();
    });
    },
    };
    </script>

    效果

    需要注意的是使用时机

    • 使用混入,最好是在onMounted之后,而不要直接在setup里面去使用(但是回调函数情况去使用除外)
      • 如果在setup当中直接使用,会输出undefined,而在setup函数的回调当中却不会
    1
    2
    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
    <template>
    <img alt="Vue logo" src="./assets/logo.png" />
    <HelloWorld msg="Welcome to Your Vue.js App" />
    <button @click="onClick">我我我</button>
    </template>
    <script>
    import HelloWorld from "./components/HelloWorld.vue";
    import query from "./mixins/query"; //引入混入
    import { getCurrentInstance } from "vue";
    export default {
    name: "App",
    components: {
    HelloWorld,
    },
    mixins: [query],
    setup() {
    const { proxy } = getCurrentInstance();
    console.log(proxy.sayHello);//输出 'undefined'
    const onClick = () => {
    console.log('组件内方法调用mixin使用')
    proxy.sayHello();//调用方法
    }
    return {
    onClick,
    }
    },
    };
    </script>

    参考文章

    https://www.pudn.com/news/6285bc152927237e3ff15257.html

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/881e4206.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/8a2682cd.html b/8a2682cd.html new file mode 100644 index 000000000..c4dc29214 --- /dev/null +++ b/8a2682cd.html @@ -0,0 +1 @@ +前端div水平居中的几种实现方式 | 梦洁小站-属于你我的小天地

    前端div水平居中的几种实现方式

    借助display布局

    • 父元素开启display:flex布局,并设置justify-content:center主轴的空隙分布
      • 因为是单行,所以使用align-items:center设置侧轴上的对其方式
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <body>
    <style>
    .a{
    width: 200px;
    height: 200px;
    background-color: red;
    display: flex;
    justify-content: center;
    align-items: center;
    }
    .a .a1{
    width: 50px;
    height: 50px;
    background-color: green;
    }
    </style>
    <div class="a">
    <div class="a1"></div>
    </div>
    </body>

    借助flex布局

    • 啊啊啊
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <body>
    <style>
    .a{
    width: 200px;
    height: 200px;
    background-color: red;
    display: grid;
    }
    .a .a1{
    width: 50px;
    height: 50px;
    background-color: green;
    justify-self: center;
    align-self: center;
    }
    </style>
    <div class="a">
    <div class="a1"></div>
    </div>
    </body>

    借助绝对定位和盒子模型计算规则

    • 借助这一条规则
    1
    2
    3
    margin-left + border-left + padding-lef + width + padding-right + border-right + margin-right = 父元素内容区的宽度

    高度和是一样的
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <body>
    <style>
    .a{
    width: 200px;
    height: 200px;
    background-color: red;
    position: relative;
    }
    .a .a1{
    width: 50px;
    height: 50px;
    background-color: green;
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    margin: auto ;
    }
    </style>
    <div class="a">
    <div class="a1"></div>
    </div>
    </body>

    借助绝对定位和transform

    • top、left、right、bottom设置百分比基于父元素
    • translate的百分比参照自身
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    <body>
    <style>
    .a{
    width: 200px;
    height: 200px;
    background-color: red;
    position: relative;
    }
    .a .a1{
    width: 50px;
    height: 50px;
    background-color: green;
    position: absolute;
    top: 50%;
    right: 50%;
    left: 50%;
    bottom: 50%;
    transform: translate(-50%,-50%);
    }
    </style>
    <div class="a">
    <div class="a1"></div>
    </div>
    </body>

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/8a2682cd.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/8b5e5cf7.html b/8b5e5cf7.html new file mode 100644 index 000000000..2ac522e01 --- /dev/null +++ b/8b5e5cf7.html @@ -0,0 +1 @@ +盒子模型及块元素水平垂直定位和绝对元素的定位布局和弹性盒 | 梦洁小站-属于你我的小天地

    盒子模型及块元素水平垂直定位和绝对元素的定位布局和弹性盒

    盒子模型

    组成:

    • content(内容区)
    • border(边框)
    • padding(内边距)
    • margin(外边距)

    盒子大小

    盒子的大小值得就是盒子在容器中实际所占据的高度和宽度(默认情况下我们通过css设置的widthheight只是设置了content(内容区)的高度和宽度,实际上盒子模型所占据的高度和宽度是按照下面来计算的)

    实际的宽度:

    实际宽度 = margin-left + border-left + padding-left + width(content-width) + padding-right + border-right + margin-right

    实际的高度:

    实际的高度 = margin-top + border-top + padding-top + height(content-height) + padding-bottom + border-bottom + margin-bottom

    比如下面图片的实际宽度和实际高度是多少呢?

    实际宽度和实际高度是多少呢

    使用上述公式,就能算出示例中的高宽值

    实际宽度 = 20+6+20+400+20+6+20 = 492

    实际的高度 = 20+6+20+100+20+6+20 = 192

    更改盒子模型的计算方式

    一般情况下,css设置的width,height设置的都是content-width,content-height,但是我们也可以通过css,改变css里面的width,和height设置的是什么

    box-sizing:content-box(默认情况)

    • 一开始的css显示效果和代码,可以看到,Child container刚刚好等于父元素的宽度
      • 也就是实际的宽度 = 父元素的宽度
    1
    2
    box-sizing: content-box;
    width: 100%;

    显示效果

    • 添加了border为10px,并且设置了padding:5px
      • 可以看到内容超过了父元素,溢出了
      • 实际的宽度 = 父元素的宽度 + border-left + border-right + padding-left + padding-right
    1
    2
    3
    4
    box-sizing: content-box;
    width: 100%;
    border: solid #5B6DCD 10px;
    padding: 5px;

    添加了border为10px,并且设置了padding:5px后的效果

    box-sizing:border-box

    • 只将box-sizing修改为border-box,效果就完全不同
    • 实际的宽度 = 父元素的宽度
    • 因为box-sizing设置的为border-box所以css当中设置的width属性包括了border的宽度和padding的宽度(注意,不包括margin!)
    1
    2
    3
    4
    box-sizing: border-box;
    width: 100%;
    border: solid #5B6DCD 10px;
    padding: 5px;

    只将box-sizing修改为border-box

    div(块元素)水平方向布局

    • 看过上面,我们知道水平方向上的位置必须要满足
      • margin-left + border-left + padding-lef + width + padding-right + border-right + margin-right = 父元素内容区的宽度
    • 如果不满足上方等式,等式就会自动去调整

    这七个值中有三个值可以设置为auto

    1
    2
    3
    4
    5
    width

    margin-left

    margin-right
    • 调整的情况 (等式当中含有auto值没有auto的情况)
    widthmargin-leftmargin-right结果
    auto/auto**宽度(width)自动调整为最大值,**选取的margin-right自动为0
    autoauto/**宽度(width)自动调整为最大值,**选取的margin-left自动为0
    autoautoauto**宽度(width)自动调整为最大值,**二个外边距自动设置为0
    auto//**宽度(width)自动调整为最大值,**二个外边距自动设置为0
    固定值autoauto宽度(width)不变,margin-left和margin-right自动调整(也就是水平居中)
    固定值auto/宽度(width)不变,自动调整margin-left的值
    固定值/auto宽度(width)不变,自动调整margin-right的值
    固定值//宽度(width)不变,自动调整margin-right的值

    总结:

    • 三者当中含有auto值的情况

      • width为auto的时候,总是保证width为最大值而其他设置为auto的自动设置为0
      • width为固定值的时候,自动调整已经设置为auto的值使其满足等式(margin-left + border-left + padding-lef + width + padding-right + border-right + margin-right = 父元素内容区的宽度)
    • 三者当中没有auto的情况

      • width为固定值发现其他值都没有写,没有一个auto,那么就自动调整margin-right的值(见得最多的)

    例如下方例子:

    • 下方的例子说明auto的计算

    • 注意,没有写代表没有,如果填写了0,或者auto,就代表在css里面设置了margin-left或者其他的为0或者auto!

    margin-leftborder-leftpadding-leftwidthpadding-rightborder-rightmargin-right父元素宽度结果
    000auto000800pxwidth当中的auto设置为800px
    auto00500px000800pxmargin-left的auto被设置为300px
    000400px00auto800pxmargin-right的auto被设置为500px
    没有写00700px00没有写800pxmargin-right的auto被设置为100px
    auto105400px510auto700pxmargin-left和margin-right的auto被设置为(700-(10+5+400+5+10))/2=135px

    宽度设置了下,其他都没有写

    • 所以为什么你经常看到设置了宽度后,在设置margin:0 auto就可以实现水平居中,就是利用上述原理
      • width固定,margin-left,margin-right为auto,那么浏览器会自动跳转auto的值,所以就实现了水平居中
    div(块元素)垂直方向布局

    块元素7个值没有auto的话就会自动调整margin-bottom其他和水平基本一样的,也是下面三个值可以设置auto,这里就不多说了

    1
    2
    3
    4
    5
    margin-top

    height

    margin-bottom

    开启绝对定位元素的布局

    先来大概了解下开启绝对定位后元素的参考父元素(也就是我们常说的包含块)
    • 正常情况下,包含块就是最近的开启了相对定位的块元素(当然,如果都没有开启,则参考html)

      1
      2
      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
      <style>
      .box1 {
      width: 400px;
      height: 400px;
      background-color: red;
      position: relative;
      }
      .box2 {
      width: 200px;
      height: 200px;
      background-color: green;
      }
      .box3 {
      width: 100px;
      height: 100px;
      background-color: blue;
      position: absolute;
      right: 0;
      }
      </style>
      </head>
      <body>
      <div class="box1">
      <div class="box2">
      <div class="box3"></div>
      </div>
      </div>
      </body>

      可以看到,box3是参考box1的,因为box1开启了相对定位

    开启决定定位后,开启绝对定位后的元素也是需要满足一定条件约束的
    • 水平方向来说:(和平常没有开启定位的多了一个left,right)

    left + margin-left + border-left + padding-left + width + padding-right + border-right + margin-right + right = 包含块的宽度

    • 垂直方向来说:(和平常没有开启定位的多了一个top,bottom)

    top + margin-top+ border-top+ padding-top+ height+ padding-bottom+ border-bottom+ margin-bottom+ bottom = 包含块的宽度

    水平方向布局

    可以调整为auto的

    1
    2
    3
    4
    5
    margin-left
    width
    left
    right
    margin-right

    需要满足

    left + margin-left + border-left + padding-left + width + padding-right + border-right + margin-right + right = 包含块的宽度

    如果9个值当中没有设置auto,则会自动调整right的值来满足(有点像未开启定位的块元素的水平布局一样,块元素其他7个值没有auto的话就会自动调整margin-right)

    所以如果等式不满足的时候,就会自动调整leftright这两个值。

    • 例子:下面的例子就会自动调整right为-200px,使得等式成立
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <style>
    .box1{
    position: relative;
    width: 200px;
    height: 200px;
    background-color: red;
    }

    .box2{
    position:absolute;
    width: 100px;
    height: 100px;
    background-color: green;
    left: 300px;
    }
    </style>
    </head>
    <body>
    <div class="box1">
    <div class="box2"></div>
    </div>

    </body>

    浏览器查看box2的computed

    浏览器computed查看

    当然,你如果皮,把box2的right改为0

    1
    2
    3
    4
    5
    6
    7
    8
    9
    .box2{
    position:absolute;
    width: 100px;
    height: 100px;
    background-color: green;
    left: 300px;
    //新添加
    right:0;
    }

    那么效果依旧不会变化~不过浏览器查看box2的computed的时候变成了这样子

    把box2的right改为0后浏览器查看

    • 需要注意的是: 要设置left = 0; right = 0; 才能调整margin-left和margin-right,否则会自动调整left和right的值(不管computed有没有看到效果) 从而导致给margin-left、margin-right设置值会失效
    垂直方向布局

    可以调整为auto的

    1
    2
    3
    4
    5
    margin-top
    height
    left
    right
    margin-bottom

    需要满足

    top + margin-top+ border-top+ padding-top+ height + padding-bottom+ border-bottom+ margin-bottom+ bottom = 包含块的高度

    如果9个值当中没有设置auto,则会自动调整bottom的值来满足(有点像块元素的垂直布局一样,块元素7个值没有auto的话就会自动调整margin-bottom)

    所以如果等式不满足的时候,就会自动调整topbottom这两个值。

    常用的一些效果
    • 开启定位后相对于包含块水平居中
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    //设置代码
    .box2{
    ...
    position:absolute;
    left:0;
    right:0;
    margin:0 auto;
    /* 或者 */
    /* margin-left: auto; */
    /* margin-right: auto; */
    }

    //例子全部
    <style>
    .box1 {
    position: relative;
    width: 200px;
    height: 200px;
    background-color: red;
    }

    .box2 {
    position: absolute;
    width: 100px;
    height: 100px;
    background-color: green;
    left: 0;
    right: 0;
    margin: 0 auto;
    /* 或者 */
    /* margin-left: auto; */
    /* margin-right: auto; */
    }
    </style>
    </head>

    <body>
    <div class="box1">
    <div class="box2"></div>
    </div>

    </body>

    效果

    开启定位后相对于包含块水平居中

    浏览器computed效果

    浏览器computed查看效果

    • 开启定位后相对于包含块垂直居中
    1
    2
    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
    35
    36
    37
    38
    .box2{
    ...
    position:absolute;
    top:0;
    top:0;
    margin:auto 0;
    /* 或者 */
    /* margin-top: auto; */
    /* margin-bottom: auto; */
    }

    //完整代码
    <style>
    .box1 {
    position: relative;
    width: 200px;
    height: 200px;
    background-color: red;
    }

    .box2 {
    position: absolute;
    width: 100px;
    height: 100px;
    background-color: green;
    top: 0;
    bottom: 0;
    margin: auto 0;
    /* margin-top: auto;
    margin-bottom: auto; */
    }
    </style>
    </head>
    <body>
    <div class="box1">
    <div class="box2"></div>
    </div>
    </body>

    效果

    开启定位后相对于包含块垂直居中

    浏览器computed查看

    浏览器computed查看效果

    • 开启定位后相对于包含块居中
    1
    2
    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
    35
    36
    37
    38
     .box2 {
    ...
    position: absolute;
    left:0;
    right: 0;
    top: 0;
    bottom: 0;
    margin: auto;
    ...
    }

    //完整代码
    <style>
    .box1 {
    position: relative;
    width: 200px;
    height: 200px;
    background-color: red;
    }

    .box2 {
    position: absolute;
    width: 100px;
    height: 100px;
    background-color: green;
    left:0;
    right: 0;
    top: 0;
    bottom: 0;
    margin: auto;
    }
    </style>
    </head>
    <body>
    <div class="box1">
    <div class="box2"></div>
    </div>
    </body>

    效果

    开启定位后相对于包含块居中

    浏览器computed查看效果

    浏览器computed查看效果

    弹性盒

    基本概念

    • 想看看也可以,这里说的简单的,也可以看看这位博主写的

    • 设置

      • display:flex开启弹性容器,将当前元素设置为块级的弹性容器
      • display:inline-flex开启弹性容器 设置为行内的弹性容器
    • 基本概念

      • 主轴
        • 弹性元素的排列方向(默认水平方向为主轴)
      • 侧轴
        • 和主轴垂直的叫侧轴

    默认情况下的主轴侧轴方向

    • 特征
      • 弹性容器的子元素是弹性元素
      • 弹性元素可以同时是弹性容器

    如下代码开启了弹性布局,div即为弹性容器,div下方的span em等即为弹性元素

    1
    2
    3
    4
    5
    6
    7
    <body>
    <div style="display:flex">
    <span>听我说</span>
    <em>动感超人</em>
    </div>
    </body>
    </html>

    设置主轴

    顺带一提记忆方法: 你知道display:flex是开启定位,那么就联想记忆,就认为flex就代表水平,然后flex-direction就是告诉容器水平是哪一个方向的 (row代表行,行当然是水平的,column代表列,列肯定是垂直的) 感兴趣的可以看看MDN的主轴API

    方向 flex-direction
    • flex-direction:row(默认值)|row-reverse|column|column-reverse

    flex-direction:row设置弹性容器当中的弹性元素在水平方向排列,并且弹性元素是从左到右排序(同时设置主轴的方向为水平方向) (默认值)

    flex-direction:row

    flex-direction:row-reverse设置弹性容器的弹性元素在水平方向排列并且是从右到左排列(同时设置主轴的方向为水平方向)

    flex-direction:row-reverse

    flex-direction:column设置弹性容器的弹性元素在垂直方向,并且是从上到下依次排列(同时设置主轴的方向为垂直方向)

    flex-direction:column

    flex-direction:column-reverse设置弹性容器的弹性元素在垂直方向,并且是从下到上排列(同时设置主轴的方向为垂直方向)

    flex-direction:column-reverse

    设置弹性元素在弹性容器上是否自动换行(重要) flex-wrap
    • flex-wrap:nowrap(默认值不换行) | wrap(换行) | wrap-reverse

    flex-wrap:nowrap的情况

    flex-wrap:nowrap

    如果没有设置弹性元素:flex-shrink:0的话,那么弹性元素如果超过了弹性容器的宽度,那么会自动跳转宽度使其等于弹性容器的宽度!

    flex-wrap:wrap换行

    flex-wrap:wrap

    flex-wrap:wrap-reverse 主轴相反的方向进行换行 不太好描述,感觉像叠叠乐一样

    flex-wrap:wrap-reverse-效果1

    flex-wrap:wrap-reverse-效果2

    对齐方式 justify-content

    大家都知道word当中有什么左对齐,居中对其,右对齐,二端对其,css也有~

    • justify-content:flex-start | flex-end | center | space-between | space-around
    • 设置如何顺着弹性容器主轴的方向分配弹性元素之间及其周围的空间(大白话就是主轴在哪里,我就按照你方向来分配其他没有被占据的空间)
    • 正确的理解应该是设置弹性元素在主轴上的对其方式
    • justify-content说明文档
    • 特别注意,这里的弹性元素会出现放不下被换行和垂直方向排布端正是因为设置了flex-wrap: wrap;align-content: flex-start;

    justify-content:flex-start(可以说是默认值) 设置元素每一行在主轴开头(起始位置)开始排布

    justify-content:flex-start

    justify-content:flex-end设置元素沿着主轴终点开始排列

    justify-content:flex-end

    justify-content:space-between 设置每一行的空白元素平均分配到元素中间

    justify-content:space-between

    justify-content:space-around 设置每一行的空白元素分配到元素四周(注意:二个元素之间分配到的空白间距并不会取消,而是会叠加在一起)

    justify-content:space-around

    设置侧轴

    元素对其方式 align-items
    • align-items:stretch(默认) |center| flex-start | flex-end | baseline

    align-items:stretch 元素在侧轴上拉伸(如果没有设置height属性或者设置为auto就会拉伸) ,如果设置了width为固定值,那么这个效果就不会生效

    align-items:stretch并且不设置width

    align-items:center 设置侧轴元素居中排列

    align-items:center

    align-items:flex-start元素向侧轴起点对齐(以每一行为参考,可以想象下每一行有自己的主轴侧轴)

    align-items:flex-start

    align-items:flex-end 元素向侧轴终点对齐(以每一行为参考,可以想象下每一行有自己的主轴侧轴)

    align-items:flex-end

    空隙排布 align-content
    • align-content: flex-start | flex-end | space-between | space-around | center

    图我就不自己截了,拿了其他的,原来作者找不到了..只找到一个转载的

    align-content:flex-start

    align-content:flex-start

    align-content:flex-end

    align-content:flex-end

    align-content:space-between

    align-content:space-between

    align-content:space-around

    align-content:space-around

    align-content:center

    align-content:center

    一个小小的简写(一次性设置主轴方向和flex-wrap)

    • flex-flow: flex-direction flex-wrap
      • 比如flex-flow:row nowrap
    针对弹性元素的属性(注意区分弹性容器和弹性元素)

    再次强调下,弹性容器是外壳,弹性元素是内部~

    flex-grow:number
    • 指定弹性元素的伸展系数,也就是当父元素(弹性容器)有空余的元素的时候如何分配(伸展)
      • 比如 flex-grow : 1 ; flex-grow : 2; flex-grow : 3 ;

    原来的空隙

    原来的空隙

    设置 flex-grow : 1 ; flex-grow : 2; flex-grow : 3; [这些数字就是所占据的比例,比如1,就占1/(1+2+3)=1/6 也就是六分之一]

    设置 `flex-grow : 1 ;  flex-grow : 2; flex-grow : 3 `

    flex-shrink:number
    • 指定弹性元素的收缩系数,也就是如果当父元素(弹性容器)容纳不下子元素(弹性元素)的时候,如何减少

    未设置flex-shrink,即为0

    未设置flex-shrink,即为0

    设置flex-shrink : 1 ; flex-shrink : 2 ; flex-shrink : 3 ;后的效果

    设置flex-shrink : 1 ; flex-shrink : 2 ; flex-shrink : 3

    flex-basis
    • flex-basis具体长度|auto —-设置弹性元素在主轴上的基础长度
    • flex-basis:100px;
      • 如果主轴为横向的,则此元素设置宽度
      • 如果主轴是纵向的,则此元素设置高度
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/8b5e5cf7.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    avatar
    梦洁
    小小的字,有大大的梦想~分享我的前端学习过程,经历,错误,和一些其他折腾过程
    关注下我(* ̄▽ ̄*)
    公告
    不断更新中,有问题请留言回复(会通过邮箱提醒~)
    \ No newline at end of file diff --git a/8d55d49a.html b/8d55d49a.html new file mode 100644 index 000000000..4f3d48e46 --- /dev/null +++ b/8d55d49a.html @@ -0,0 +1,17 @@ +JAVA学习笔记 | 梦洁小站-属于你我的小天地

    JAVA学习笔记

    day01

    代码的结构

    • 主类名,包含main方法的类名
    1
    2
    3
    4
    5
    类{
    方法{
    语句;
    }
    }
    • 编译javac 要编辑的文件名.java(包含扩展名),生成字节码.class文件
      • cmd需要在HelloWorld.jav文件夹目录下
      • javac ./HelloWorld.java
    1
    2
    3
    4
    5
    6
    7
    public class HelloWorld {
    public static void main(String[] args){
    System.out.println("helloWorld");
    }
    }

    //String[] args 不要漏掉了
    • 运行java 编辑后的文件
    1
    2
    3
    4
    5
    6
    7
    java 主类名 (也就是 java HelloWorld)

    java HelloWorld 正确的

    java HelloWorld.class 错误的
    java ./HelloWorld.class 错误的
    java ./HelloWorld 错误的
    • 更改编码格式
    1
    javac -encoding UTF-8 HelloWorld.java

    否者容易出现编码 GBK 的不可映射字符

    大小写问题

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    1.java的代码是严格区分大小写的
    2.java的文件名是否区分大小写
    window操作系统来说
    2.1 文件名大小写都可以(即javac 命名后面,文件名大小写都可以)
    2.2 比如有一个文件名Problem2.java 可以javac problem2.java 也可以javac Problem2.java

    3.编译后的xxx.class文件的文件名是否区分大小写? 区分
    因为xxx.class文件的文件名代表的是类目
    比如,编译后生成字节码文件 Problem2.class,并且主类名称也为Problem2
    运行的命令,就必须是 java Problem2

    建议都区分
    • 特别注意的是,class的类名需要和文件夹名称一致,否则会报下面的错误

    java注释

    • 单行注释//
    • 多行注释/* 注释内容 */
    • 文档注释/** 内容 */

    类型

    • 基本数据类型

      • 整数: byte , short ,int ,long
      • 小数:float , double
      • 单字符: char
      • 布尔型: boolean
    • 引用数据类型

      • 类:class
      • 接口:interface
      • 枚举:enum
      • 注解:@interface
      • 数组:[]
    • java的常量是用final来表示的,常量名通常所有字母都大写

    1
    final int FULL_MARK = 100;

    基本数据类型的转换之自动转换

    • 自动转换之隐式转换
      • 当把存储范围小的值(常量值,变量值,表达式的结果值)赋值给存储范围大的变量的时候,就会发生自动类型转换
      • 存储范围从小到大排序如下
      • byte -> short -> int -> long -> float -> double
      • chartshort同级
      • boolean不参与
    1
    2
    double d = 1;
    System.out.println(d);//输出1.0
    • 当多种数据类型的数据混合运算时,会自动提升为他们之中最大的
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    double d = 1;
    System.out.println(d);//输出1.0

    int a = 1;
    byte b = 12;
    char c = 'a';

    //int ff = a + b + c + d;//错误: 不兼容的类型: 从double转换到int可能会有损失

    double dd = a + b + c + d;//正确的
    • bytebyte,shortshort,charchar进行运算或者他们三个混合运算,会自动提升为int类型
      • 顺带一提:字节(byte)数据类型是8位有符号Java原始整数数据类型。其范围是-128至127(-2^7至2^7-1)。
    1
    2
    3
    byte a = 1;
    byte b = 2;
    int c = a + b;//正确
    • 当小类型和大类型运算的时候,小类型会优先转化为大类型后参与运算(除开字符串)
      • 可能有误,这一条个人总结!
    1
    2
    3
    4
    5
    char和double之间相加,char型会转换为double类型
    char one = 'a'; //97
    double two = 12.15;
    System.out.println(one + two);//输出109.15

    基本数据类型的转换之强制转换

    • 当把存储范围大的值(常量值,变量值,表达式的结果值)赋值给存储范围小的变量的时候,就需要强制类型转换
      • 格式(存储范围小的值)值
    1
    2
    3
    byte a = 1;
    byte b = 2;
    byte c = (byte)(a + b);//本来是int的,强制转换为byte
    • 注意的是,可能会导致溢出或损失精度

    String类型与基本数据类型转换的问题

    • 任何数据与String进行+拼接,结果都是String
    • 其他数据类型进行+是求和
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    char c1 = 'a';
    char c2 = 'b';
    //求和,所以输出int类型
    System.out.println(c1 + c2);//197 (类型为整形)

    //
    System.out.println("C1 + C2 = " + c1 + c2);//C1 + C2 = ab (类型为字符串)

    System.out.println(c1 + c2 + "");// 197 (类型为字符串)

    System.out.println(c1 + "" + c2);// ab (类型为字符串)

    特别注意

    1
    2
    3
    4
    5
    6
    7
    8
    9
    float f = 1.2;//错误的

    要么
    double f = 1.2;
    或者
    float f = 1.2F;

    long j = 120;//自动类型提升
    double d = 34;//自动类型提升

    赋值运算符

    • =左边必须是变量,不能是常量值,不能是表达式
    1
    2
    3
    4
    5
    int a = 1;
    int b = 1;
    b = a + b;//正确

    b + 1 = a;//错误
    • =右边的值(常量,变量,表达式)的类型必须要<=左边变量的类型

      • 存储范围从小到大排序如下
      • byte -> short -> int -> long -> float -> double
      • chartshort同级
    • =永远是最后算的

    • 扩展的赋值运算符,当最后的赋值结果类型大于左边的变量类型时,会发生自动类型转换

    1
    2
    3
    4
    5
    6
    byte b1 = 10;
    byte b2 = 2;
    b1 = b1 + b2;//错误的

    b1 += b2;//相对于b1 = (int)(b1 + b2);
    System.out.println(b1);

    day02

    • 编码,同一个模块下,所有文件保持同一个编码,否则文件可以不同编码,使用System.out.println输出中文会出现乱码问题

    • 自动导包Alt + Enter

    • 表达式有返回值,语句没有返回值~

    几种输出语句

    1
    2
    3
    4
    5
    6
    System.out.println(输出内容);//输出内容之后,紧接着换行,如果()中什么都没写,表示空号
    System.out.println();//输出空行

    System.out.print(内容);//如果()中什么都没写,编译报错
    //()中也只能写一个值,如果有多个值,必须用"+"拼接起来

    格式化输出

    • 使用System.out.printf(内容,变量列表)
    • 内容需要使用占位符,占位符如下
    • %d 整形
    • %f小数
    • %.nf小数点留n位(四舍五入)
    • %c单个字符
    • %s字符串
    • %bboolean
    1
    2
    3
    4
    5
    6
    7
    8
    int a = 10;
    double b = 12.5885;
    char c = 'd';
    boolean f = true;

    System.out.printf("a=%d,b=%f,b=%.2f,c=%c,f=%b",a,b,b,c,f);
    //输出结果如下
    a=10,b=12.588500,b=12.59,c=d,f=true

    输入

    next

    • 遇到空白或者其他空白字符的时候,就会认为输入结束,后面的数据就不会接收了
    • 比如输入张 空格 三,那么只会接收
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //全名称使用法
    java.util.Scanner input = new java.util.Scanner(System.in);

    //简写用法
    import java.util.Scanner;
    Scanner input = new Scanner(System.in);

    System.out.print("请输入一个整数");
    int num = input.nextInt();//接收键盘输入
    System.out.println("num = " + num);

    //最后最好关闭
    input.close()
    • 注意,如果要接收数据的变量类型和用户输入数据的数据类型不符合,会报错

    • input.nextDouble()输入小数

    • input.nextBoolean()输入布尔值

    • input.nextLong();输入大整形

    • input.next();输入字符串

    • input.next().chartAt(0):输入单个字符(从多个字符串截取第一个字符)

    • 注意,最后最好关闭input.close()

    • 示例

    1
    2
    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
    import java.util.Scanner;

    public class input {
    public static void main(String[] args) {
    //用户输入
    Scanner input = new Scanner(System.in);

    //输入字符串
    System.out.printf("请输入字符串:");
    String a = input.next();
    System.out.println("a = " + a);
    //输出小数
    System.out.printf("请输入小数:");
    double b = input.nextDouble();
    System.out.println("b = " + b);
    //输入布尔值
    System.out.printf("请输布尔值:");
    boolean c = input.nextBoolean();
    System.out.println("c = " + c);
    //输入大整形
    System.out.printf("请输入大整形:");
    long d = input.nextLong();
    System.out.println("d = " + d);
    //输入单个字符
    System.out.printf("请输入单个字符:");
    char e = input.next().charAt(0);
    System.out.println("e = " + e);
    input.close();
    }
    }

    nextLine

    • 在读取用户输入的数据时,遇到回车换行符合才会认为输入结束

    需要注意

    • 当二者结合使用的时候,需要注意下面这种情况
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import java.util.Scanner;

    public class input_attention {
    public static void main(String[] args) {
    Scanner input = new Scanner(System.in);
    System.out.printf("请输入姓名");
    String name = input.next();//next是以空格为结束
    System.out.println("name = " + name);

    String other = input.nextLine();//nextLine是以回车结束
    System.out.printf("请输入其他信息");
    System.out.println("other = " + other);
    }
    }

    • 其实就是输入流当中的信息被nextnextLine进行补货,然后特定符号结束捕获,就会发生这种情况

    • 建议

      • 如果字符串中不需要包含空格,请使用next()更简单
      • 如果字符串中需要包含空格,那么nextLine()前面有其他的非nextLine()的输入语句,请在前面加一句xx.nextLine()解决(也就是提前捕获下然后不接收)

    day03

    数组

    • 命名如下
    1
    2
    3
    4
    5
    6
    7
    8
    //推荐的
    元素的数据类型[] 数组的名称
    //比如
    char[] temp;

    //不推荐
    元素的数据类型 数组的名称[];
    int temp[]

    初始化

    一维数组静态初始化

    • 静态初始化一般适用于一组数据的已知的
    1
    2
    3
    4
    5
    6
    7
    8
    9
    //支持的格式如下
    //如果声明和静态初始化是一起的,支持写法如下
    元素数据类型[] 变量名 = { 数据A,数据B,数据C };

    元素数据类型[] 变量名 = new 元素数据类型[]{ 数据A,数据B,数据C };

    //如果声明和静态初始化是分开的
    元素数据类型[] 变量名;
    变量名 = new 元素数据类型[]{ 数据A,数据B,数据C }
    1
    2
    3
    4
    5
    6
    7
    //如果声明和静态初始化是一起的,支持写法如下
    int[] achievement1 = {1,2,3};
    int[] achievement2 = new int[]{1,2,3,};

    //如果声明和静态初始化是分开的
    int [] achievement3;
    achievement3 = new int[]{1,2,3};

    一维数组动态初始化

    • 适用于一组数据是未知的,或需要通过计算得到,或者通过键盘输入得到
    1
    元素数据类型[] 变量名 = new 元素数据类型[长度]
    1
    2
    //动态初始化
    int[] achievement4 = new int[10];

    二维数组动态初始化

    • 静态初始化不多说,参考一维数组初始化

    • 动态初始化

      • 规则的矩阵,每一行的列数是相同的
      • 不规则的二维表(每一行的列数不相同,也就是一行有长有短)
      1
      2
      3
      4
      int[][] arr = new int[5][];//二维数组一共有5行,但是每一行的元素个数不确定

      //把二维数组看成一维数组的话,元素类型是int[]类型,int[]数组是引用数据类型,默认值就是null
      System.out.println(arr[0]);//输出null

    初始化的值

    1
    2
    3
    4
    整数类型数组: 里面填充为0
    小数类型数组: 里面填充为0.0
    boolean类型数组: 里面填充false
    char类型数组: 里面填充\u000

    return new int[0]; 的意义

    • 这个 return new int[0]; 就是防止编译器报错,返回一个垃圾值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    /*
    给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 的那 两个 整数, 并返回它们的数组下标。
    你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
    你可以按任意顺序返回答案。
    */
    class Solution {
    public int[] twoSum(int[] nums, int target) {
    //用数组中的一个元素值,加上其它的元素值,看看是否等于target,已经使用过的组合就不再使用了
    for(int i = 0;i < nums.length;i++){
    for(int j = i + 1;j < nums.length;j++){
    //用一个元素值和它后面的所有元素值相加,以此类推
    if(target == (nums[i] + nums[j])){
    return new int[]{i,j};
    }
    }
    }
    return new int[0];
    }
    }

    面向对象

    • 以类和对象为核心
    • 代码的结构以类为单位,程序是由一个一个的类组成的
    • 数据是在类里面的,数据分为在类中方法(函数)外,类中方法(函数)内
    • 数据在类中的方法外,称为 成员变量/成员数据.要么属于某个类共享,要么是每一个对象独立的.
    1
    2
    3
    4
    5
    6
    public class class_study {
    int a = 100;//成员变量
    public static void main(String[] args) {
    int b = 100;//不是成员变量,为局部变量
    }
    }
    • 数据在类中的方法内,称为局部变量/局部数据.局部变量无法共享,每一个方法独立

    类的定义

    • 类的定义/声明格式
      • 类的名称尽量见名知意,每一个单词的首字母大写
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    [修饰符] class 类名{

    }

    [修饰符] 可以缺省
    public和缺省public有什么区别?
    (1)
    如果class前面有public,要求.java文件名称必须要和class后面的类名相同(包括单词和大小写)
    一个.java文件只能有一个public的类

    如果class前面没有public,则不要求类名与.java文件名相同
    建议大家一个.java只写一个类,类名和.java文件名相同,方便维护

    (2)如果class是public,可以跨包使用,
    如果class没有public,那么不能跨包使用

    对象的创建

    1
    2
    3
    4
    5
    6
    7
    8
    new 类名();

    new 类名(实参列表)

    匿名对象,如果没有把对象赋值给一个变量,那么这样子的对象称为匿名对象,
    如果希望这个对象反复使用,那么最好把这个对象给一个变量,就像下面这样子

    类名 变量名 = new 类名();

    类的成员

    • 1223
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    类的成员有:
    成员变量
    成员方法
    构造器
    代码块
    成员内部类

    成员变量
    1.成员变量声明的位置,必须要在类中方法外
    2.成员变量声明的格式
    [修饰符] class 类名{
    //下面就是成员变量
    [修饰符] 数据类型 变量名
    }
    成员变量修饰符:public,protected,private,static,final,transient,volatile等
    数据类型:可以是8种基本数据类型,也可以是引用数据类型

    • 必须在源文件的代码首行
    • 一个包名对应的是一个目录
    • 一个源文件只能有一个声明包的package语句
    • 关键字为package
    • package语句只要不是一模一样的,就不是同一个包
    1
    package 包名
    • 包的命名规范和习惯:
      • 所有的单词都小写,每一个单词之间使用.分割
      • 习惯用公司的域名倒置开头和具体功能模块进行命名,比如com.atguigu.xxx;
        • 因为尚硅谷官网是atguigui.com,倒过来就是com.atguigu

    建议大家取包名时不要使用java.xx包

    包的作用域

    • 位于同一个包的类,可以访问作用域的字段和方法
    • 不用public,protected,private修饰的字段和方法就是包的作用域
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    不用public,protected,private修饰的字段和方法就是包的作用域,具有代码目录结构如下
    均在默认包下(也就是缺省包)
    Student.java
    TestStudent.java

    //Student.java文件内容
    public class Student {
    String name;
    }

    //TestStudent.java文件内容
    public class TestStudent {

    public static void main(String[] args) {
    //可以正常使用,没有问题
    Student stu1 = new Student();
    stu1.name = "李白";
    System.out.println("stu1 = " + stu1.name);
    }
    }
    • 如果文件目录更改,也就是将Student.java移动到top.dreamlove包下
      • Student的成员变量添加了Public,才可以被其他类所访问,否者只是在top.dreamlove包下才可以使用,也就是包的作用域下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    //文件目录结构
    -top.dreamlove
    --Student.java
    -TestStudent.java

    //Student.java文件内容
    public class Student {
    //添加Public修饰词
    Public String name;
    }

    //TestStudent.java文件内容
    import top.dreamlove.Student;

    public class TestStudent {

    public static void main(String[] args) {
    Student stu1 = new Student();
    stu1.name = "李白";
    System.out.println("stu1 = " + stu1.name);
    }
    }

    如何跨包使用类

    • 第一种方法是直接使用类型的全名称

      例如:java.util.Scanner input = new java.util.Scanner(System.in);

    • 第二种方法是通过import关键字来引入包

      • import语句告诉编译器到哪里去寻找类。
      • import语句的语法格式
      • import语句需要编写到package语句之下,class语句之上
      1
      2
      3
      4
      5
      //引入部分包
      import 包名.类名

      //一次性引入指定包名下的所有类
      import 包名.* //这里*代表的是省略的类名,不能省略子包名

    注意:

    使用java.lang包下的类,不需要import语句,就直接可以使用简名称

    import语句必须在package下面,class的上面

    当使用两个不同包的同名类时,例如:java.util.Date和java.sql.Date。一个使用全名称,一个使用简名称

    • 只有public的类才能被跨包使用

    方法

    • 方法必须要先声明后使用,不调用不执行,调用一次执行一次。
    • 声明的正确示范如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    //正确示范
    类{
    方法1(){

    }
    方法2(){

    }
    }
    • 错误示范
    1
    2
    3
    4
    5
    6
    7
    8
    //错误示范
    类{
    方法1(){
    方法2(){ //位置错误

    }
    }
    }
    • 格式
      • 修饰符也很多,public,protected,private,static,final,native,如果需要跨包使用,需要使用public修饰符
    1
    2
    3
    【修饰符】 返回值类型 方法名(【形参列表 】)【throws 异常列表】{
    方法体的功能代码
    }
    • 如果类中的方法没有使用public修饰符,那么这个方法只能在本包的其他类使用,不能跨包使用(同理,属性也是一样的)

    都是在不同包中

    实例变量与局部变量的区别

    1、声明位置和方式
    (1)实例变量:在类中方法外
    (2)局部变量:在方法体{}中或方法的形参列表、代码块中

    2、在内存中存储的位置不同
    (1)实例变量:堆
    (2)局部变量:栈

    3、生命周期
    (1)实例变量:和对象的生命周期一样,随着对象的创建而存在,随着对象被GC回收而消亡,
    而且每一个对象的实例变量是独立的。
    (2)局部变量:和方法调用的生命周期一样,每一次方法被调用而在存在,随着方法执行的结束而消亡,
    而且每一次方法调用都是独立。

    4、作用域
    (1)实例变量:通过对象就可以使用,本类中“this.,没有歧义还可以省略this.”,其他类中“对象.”
    (2)局部变量:出了作用域就不能使用

    5、修饰符(后面来讲)
    (1)实例变量:public,protected,private,final,volatile,transient等
    (2)局部变量:final

    6、默认值
    (1)实例变量:有默认值
    (2)局部变量:没有,必须手动初始化。其中的形参比较特殊,靠实参给它初始化。

    参数

    形参和实参

    • 形参:在声明方法时,()中声明的变量,每调用这个方法之前,它的值是不确定的
    1
    2
    3
    4
    //int a , int b是形参,它就是一个占位符,形式上存在的
    public int max(int a,int b){
    return a > b ? a : b;
    }
    • 实参,在”调用”方法时,()中传入的数据,这个数据可能是常量值,也可能是变量,还可以是表达式

    可变参数

    • 当定义一个方法时,形参的类型可以确定,但是形参的个数不确定,那么可以考虑使用可变参数。可变参数的格式:
    1
    2
    3
    4
    【修饰符】 返回值类型 方法名(【非可变参数部分的形参列表,】参数类型... 形参名){  }

    //貌似这样子也可以
    【修饰符】 返回值类型 方法名(【非可变参数部分的形参列表,】参数类型 ...形参名){ }
    • 示例,自定义Sum类,有一个计算总值方法
    1
    2
    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
    package top.dreamlove.param;

    public class Sum {
    public double getAll(int... all){
    int sum = 0;
    for (int i = 0; i < all.length; i++) {
    int i1 = all[i];
    sum+=i1;
    }
    return sum;
    }
    }

    //调用

    package top.dreamlove.param;

    public class Test1 {
    public static void main(String[] args) {
    Sum s1 = new Sum();
    //可以这样子
    double temp1 = s1.getAll(new int[]{1, 2, 3, 4, 5});
    //也可以这样子
    double temp2 = s1.getAll(1,2,3,4,5);
    System.out.println("temp1 = " + temp1);//输出15
    System.out.println("temp2 = " + temp2);//输出15
    }
    }

    方法的重载(Overload)

    • 一个类中出现了方法名相同,形参列表不同的二个或多个方法,称为方法的重载
    • 方法名必须要相同
    • 形参列表必须不同
    • 返回值类型:无关紧要(可相同,可不同)

    对象数组

    • 数组是用来存储一组数据的容器,一组基本数据类型的数据可以用数组装,那么一组对象也可以使用数组来装。即数组的元素可以是基本数据类型,也可以是引用数据类型。当元素是引用数据类型是,我们称为对象数组。

    注意:对象数组,首先要创建数组对象本身,即确定数组的长度,然后再创建每一个元素对象,如果不创建,数组的元素的默认值就是null,所以很容易出现空指针异常NullPointerException。

    • 示例
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    public class Rectangle {
    double length;
    double width;

    double area(){//面积
    return length * width;
    }

    double perimeter(){//周长
    return 2 * (length + width);
    }

    String getInfo(){
    return "长:" + length +
    ",宽:" + width +
    ",面积:" + area() +
    ",周长:" + perimeter();
    }
    }

    public class ObjectArrayTest {
    public static void main(String[] args) {
    //声明并创建一个长度为3的矩形对象数组
    Rectangle[] array = new Rectangle[3];

    //创建3个矩形对象,并为对象的实例变量赋值,
    //3个矩形对象的长分别是10,20,30
    //3个矩形对象的宽分别是5,15,25
    //调用矩形对象的getInfo()返回对象信息后输出
    for (int i = 0; i < array.length; i++) {
    //创建矩形对象
    array[i] = new Rectangle();

    //为矩形对象的成员变量赋值
    array[i].length = (i+1) * 10;
    array[i].width = (2*i+1) * 5;

    //获取并输出对象对象的信息
    System.out.println(array[i].getInfo());
    }
    }
    }

    面向对象的基本特征

    • 面向对象这个编程思想,有三个基本特征
      • 封装
      • 继承
      • 多态

    封装

    • 隐藏对象内部的复杂性,只对外公开简单和可控的访问方式,从而提高系统的可扩展性、可维护性。通俗的讲,把该隐藏的隐藏起来,该暴露的暴露出来。这就是封装性的设计思想。(只关心使用,不关心如何实现,就和手机一个道理,你不会去关心手机如何实现的,只会在意手机卡不卡)

    实现封装

    • 依赖于权限修饰符,或者又称为访问控制符,修饰符如下,可用与成员方法或者成员属性
    修饰符本类本包其他包子类其他包非子类
    private×××
    缺省××
    protected×
    public
    1
    2
    3
    4
    5
    6
    7
    private只能被本类访问

    public 子类和其他包非子类都可以访问

    protected 子类才可以访问,其他包非子类不可以访问

    缺省 本包和本类才可以访问
    • 成员变量选择哪种权限修饰符?
      • 实际上,习惯上,先声明为private,如果这个成员变量需要扩大它的可见性访问,那么可以把private修改为其他合适的修饰符
      • 扩大到本包,可以使用缺省
      • 扩大到其他包的子类,可以使用protected
      • 扩大到任意位置,可以使用public
    • 为什么?
      • 对象的数据要可控,不应全部暴露
    • 其他包子类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package test1;

    public class Person {
    protected String name;

    }

    package test1.son;

    import test1.Person;

    public class Test2 extends Person {
    public void method(){
    //成功访问
    System.out.println(name);
    }
    }

    • 其他包非子类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    package test1;

    public class Person {
    protected String name;

    }

    //其他包非子类
    package test1.noson;

    import test1.Person;

    public class Test3 {
    public static void main(String[] args) {
    Person s1 = new Person();
    //无法访问
    System.out.println(s1.name);
    }
    }

    如何使用私有化的属性?

    • 如果这个属性确实要被外部使用,需要提供get或者set方法

      • get方法:供调用者获取属性值的方法
      • set方法:供调用者修改属性值的方法
    • 生成的get/set的方法名,通常都是

      • get + 属性名,并且属性名的首字母大写
      • set + 属性名,并且属性名的首字母大写
    • 但是如果实例变量是boolean类型

      • 那么它对应的get方法,就会把get单词替换为is,而set命名不变
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      public class Test{
      private marry;

      public boolean isMarry(){

      }
      public boolean setMarry(){

      }
      }

    继承

    • 为什么要继承
      • 提高代码的复用性
      • 提高代码的扩展性
    • 继承要满足is - a的关系
    1
    2
    3
    4
    Student is a Person
    Teacher is a Person
    //注意,下面的就不是is - a的关系
    Car is not a Person
    • 关键词extends
    1
    2
    3
    [修饰符] class 子类名 extends 父类名{

    }
    • 子类表示的事物范围大还是父类表示事物的范围大?
      • Person类是父类
      • Student类是子类
      • 子类的事物范围 < 父类的事物范围
      • 子类更具体,里面的成员描述更多了
      • 父类更抽象,笼统的描述信息更少

    继承有什么特点

    • 子类会继承父类的实例变量和实现方法
    • 当子类对象被创建时,在堆中给对象申请内存时,就要看子类和父类都声明了什么实例变量,这些实例变量都要分配内存(不管能不能访问的到)。
    • Java只支持单继承,不支持多重继承
    1
    2
    3
    4
    5
    6
    public class A{}
    class B extends A{}

    //一个类只能有一个父类,不可以有多个直接父类。
    class C extends B{} //ok
    class C extends A,B... //error
    • Java支持多层继承(继承体系)
    1
    2
    3
    class A{}
    class B extends A{}
    class C extends B{}
    • 一个父类可以同时拥有多个子类
    1
    2
    3
    4
    class A{}
    class B extends A{}
    class D extends A{}
    class E extends A{}
    • 查看继承关系快捷键

    例如:选择A类名,按Ctrl + H就会显示A类的继承树。

    :A类的父类和子类

    :A类的父类

    :A类的所有子类

    例如:在类继承目录树中选中某个类,比如C类,按Ctrl+ Alt+U就会用图形化方式显示C类的继承祖宗

    权限修饰符问题

    权限修饰符:public,protected,缺省,private

    修饰符本类本包其他包子类其他包非子类
    private×××
    缺省√(本包子类非子类都可见)××
    protected√(本包子类非子类都可见)√(其他包仅限于子类中可见)×
    public

    外部类:public和缺省

    成员变量、成员方法等:public,protected,缺省,private

    1.外部类要跨包使用必须是public,否则仅限于本包使用

    (1)外部类的权限修饰符如果缺省,本包使用没问题

    (2)外部类的权限修饰符如果缺省,跨包使用有问题

    2.成员的权限修饰符问题

    (1)本包下使用:成员的权限修饰符可以是public、protected、缺省

    (2)跨包下使用:要求严格

    (3)跨包使用时,如果类的权限修饰符缺省,成员权限修饰符>类的权限修饰符也没有意义

    3.父类成员变量私有化(private)

    • 子类虽会继承父类私有(private)的成员变量,但子类不能对继承的私有成员变量直接进行访问,可通过继承的get/set方法进行访问。如图所示:

    父类代码:

    1
    2
    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
    package com.atguigu.inherited.modifier;

    public class Person {
    private String name;
    private int age;

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    public int getAge() {
    return age;
    }

    public void setAge(int age) {
    this.age = age;
    }

    public String getInfo(){
    return "姓名:" + name + ",年龄:" + age;
    }
    }

    子类代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    package com.atguigu.inherited.modifier;

    public class Student extends Person {
    private int score;

    public int getScore() {
    return score;
    }

    public void setScore(int score) {
    this.score = score;
    }

    public String getInfo(){
    // return "姓名:" + name + ",年龄:" + age;
    //在子类中不能直接使用父类私有的name和age
    return "姓名:" + getName() + ",年龄:" + getAge();
    }
    }

    测试类代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package com.atguigu.inherited.modifier;

    public class TestStudent {
    public static void main(String[] args) {
    Student student = new Student();

    student.setName("张三");
    student.setAge(23);
    student.setScore(89);

    System.out.println(student.getInfo());
    }
    }

    IDEA在Debug模式下查看学生对象信息:

    重写(Override)

    • 我们说父类的所有方法子类都会继承,但是当某个方法被继承到子类之后,子类觉得父类原来的实现不适合于子类,该怎么办呢?我们可以进行方法重写 (Override)

    • 重写过程中,如果需要调用父类的方法,需要使用super关键字

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //父类
    package com.atguigu.inherited.method;

    public class Phone {
    public void sendMessage(){
    System.out.println("发短信");
    }
    public void call(){
    System.out.println("打电话");
    }
    public void showNum(){
    System.out.println("来电显示号码");
    }
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    package com.atguigu.inherited.method;

    //smartphone:智能手机
    public class Smartphone extends Phone{
    //重写父类的来电显示功能的方法
    public void showNum(){
    //来电显示姓名和图片功能
    System.out.println("显示来电姓名");
    System.out.println("显示头像");

    //保留父类来电显示号码的功能
    super.showNum();//此处必须加super.,否则就是无限递归,那么就会栈内存溢出
    }

    @Override
    public void call() {
    super.call();
    System.out.println("视频通话");
    }
    }

    重写的要求

    • 父类和子类之间,重写方法的名称相同

    • 父类和子类之间,参数列表也要完全相同

    • 返回值类型

      • 如果是void和基本数据类型,返回值必须要相同
      • 如果是引用数据类型
        • 子类重写方法的返回值类型必须要小于等于父类方法的返回值类型
          • 父类的菜,你子类重写就应该小于等于菜,不能说重写返回值返回一个人类
      • 子类方法的权限必须【大于等于】父类方法的权限修饰符。

      注意:public > protected > 缺省 > private

      父类私有方法不能重写

      跨包的父类缺省的方法也不能重写

    EMS项目结构

    • 一般的java项目目录结构如下

    • 也就是
      • 视图层(view)包
      • 业务逻辑层(service)包
      • 数据访问层(dao)包
      • bean包或者domain包

    多态

    1
    2
    父类类型 变量名 = 子类对象;
    且在调用的时候只能调用父类声明的方法,不能调用子类扩展的方法
    • 编译类型取决于定义对象时 =号的左边,运行类型取决于 =号的右边

    向上转型

    • 自动类型转换

    • 当把子类对象赋值给父类的变量时,在”编译时”会自动类型提升为父类的类型

      • 也就是从小的范围自动转化为了大的范围
    • 怎么才能成功?

    1
    2
    3
    只要满足对象是这个要赋值变量的子类型即可
    Person p1 = new Man();
    //Man继承了Person类

    向下转型

    • 如果需要调用子类扩展的方法的时候,必须要向下转型,通过强制类型转换完成,这样才能通过编译,对象的本质类型从头到尾都没有变化,只是骗编译器的
    1
    2
    父类类型 变量名 = 子类对象;
    且在调用的时候只能调用父类声明的方法,不能调用子类扩展的方法,但是在堆当中已经有对应的方法了,只是编译器不让你调用
    • 怎么才能成功向下转型?
    • 运行类型类型怎么看?就是new单词后面的类型
    • 大小可以理解为范围的大小,比如人类的范围肯定比男人要大,所以男人<人类
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    对象的运行类型必须要 <= ()中向下转的类型
    // Class Man extends Person
    //p1对象的运行时类型为Man,会等于要转换为Man的类型,所以可以向下转型
    Person p1 = new Man();
    Man temp1 = (Man) p1;

    // Class Man extends Person
    // Class ChineseMan extends Man
    // p2运行时候的类型为ChineseMan,和Man相比,小于Man,所以可以向下转型
    Person p2 = new ChineseMan();
    Man temp2 = (Man)p2;

    // Class Man extends Person
    // Class Woman extends Person
    // p3对象运行时候的类型为Man,和Woman相比,没啥子关系,所以不能比较,所以不可以向下转型
    // 向下转型失败,报ClassCastException(类强制转换异常)
    Person p3 = new Man();
    Man temp3 = (Woman)p3;


    // Class Man extends Person
    // p4运行时的类型为Person,和Man相比,比Man更大,不满足<=关系,所以不可以向下转型
    // 向下转型失败,报ClassCastException(类强制转换异常)
    Person p4 = new Person();
    Man temp4 = (Man)p4;

    如何避免向下转型编译通过,运行发生ClassCastException?

    • 添加instanceof判断
    1
    2
    3
    4
    语法格式
    变量/对象 instanceof 类型
    instanceof的作用是判断某个变量/对象的运行类型是否<=instanceof后面写的类型
    (前端作用是:instanceof 运算符用于检测构造函数的 prototype 属性是否出现在某个实例对象的原型链上。)

    注意

    • 在成员变量:没有多态的概念,变量的寻找只看编译时类型,没有编译时类型和运行时类型不一致这个说法
    • 在成员方法:有多态的概念,编译时看父类,运行时看子类,如果子类重写了,一定是执行子类重写的方法体;

    构造器

    • 构造器的修饰符只能是public,protected,缺省,private,不能有static,final

    • 构造器的名称不能随意乱写,只能也一定要和类名完全一致,包括大小写

    • 如果没有创建任何构造器,那么编译器会添加一个和class类前面的权限修饰符一致的默认构造器(如果手动写了任意一个构造器,则不会添加默认构造器)

    同一个类中构造器相互调用

    • this()调用本类的无参构造器
    • this(实参列表)调用本类的有参构造器
    • this()和this(实参列表)只能出现在构造器首行
    • 不能出现递归调用

    构造器在继承时的要求

    • 父类构造器不会继承到子类中

    • 父类的构造器和子类有没有关系?

      • 子类在继承父类的时,默认会在子类的构造器首行加一句代码super(),表示调用父类的无参构造器
    • 父类中只有无参构造或者没有写构造函数(默认构造函数)

      • 子类首行可以添加super(),也可以省略
    • 父类中只有有参构造

      • 子类首行必须要添加super(父类所有实参列表),否则会调用(也就是要调用父类的构造函数)
    • 父类中既有无参构造函数,又有有参构造函数

      • 写了super(),就表示调用父类的无参构造
      • 写了super(实参列表),就表示调用父类的有参构造
    • 为什么子类的构造器一定要调用父类的构造器呢?

      • 因为子类会继承父类所有的成员变量,那么在new子类对象的时候,必须要为这些继承成员变量”初始化”
    • super()表示调用父类的无参构造,可以省略

    • super(实参列表),表示调用父类的有参构造,不能省略

    • 如果要写他们,都必须要在构造器的首行,而且不能与this(),this(实参列表)在同一个构造器中出现

    非静态代码块

    • 作用:用来给实例变量初始化的

    • 意义:把多个构造器的代码抽离出来,写到代码块中,减少代码冗余

    • 特点:

      • 代码块中的代码会自动执行
      • 当new对象时,会自动执行,不new对象不会执行
      • 每new一个对象,执行一次
      • 无论它写在哪里,都是比构造器先执行
    • 比如需要n个构造器都执行一段代码,怎么办?难不成n个构造器都写入代码吗?太不方便了,所以可以这样子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class Student{
    //非静态代码块
    {
    System.out.println("新用户注册");
    this.currentTime = System.currentTimeMillis();
    }

    public Student(){

    }
    public Student(String name){

    }
    }
    • 格式
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    【修饰符】 class 类{
    {
    非静态代码块
    }
    【修饰符】 构造器名(){
    // 实例初始化代码
    }
    【修饰符】 构造器名(参数列表){
    // 实例初始化代码
    }
    }

    实例初始化过程

    • new调用构造器,本质上是执行它对应的<init>方法
    • 每一个构造器都会有自己对应的<init>方法,它由下面这些代码组成:
      • A:super()或者super(实参列表)(已经不仅仅代表父类的构造器,而且还代码父类构造器对应的init方法)
      • B:当前类的 实例变量声明后的显式赋值表达式语句和非静态代码块 (这二个按照代码块编写的顺序依次组装)
      • C:构造器剩下的代码(除了super()或者super(实参列表)的代码)

    final

    • 在类前,代表这个类都不能被继承
    • 在方法名前,代表方法只能被继承,不能被重写
    • 在变量名前,代表这个变量不能被修改,也就是常量
    1
    2
    3
    4
    class MyDate {
    //没有set方法(你生成的时候也不显示)
    private final int year;
    }

    Object根父类

    • 既然Object是所有类的父类,那么Object类型的变量,可以和任意类型的对象构成”多态引用”
    1
    2
    3
    Object obj1 = "hello";//正确
    Object obj2 = new Student();//正确
    Object obj3 = new Scanner(System.in);//正确
    • java规定,Object[]数组,可以接收任意类型的对象数组
    • java规定,int[],char[]等,它们之间是不能互相转换,它们和Object[]之间也不能互相转换
    1
    2
    3
    4
    5
    6
    7
    Object[] arr1 = new String[5];//正确

    Object[] arr2 = new int[10];//错误

    Object[] arr3 = new char[10];//错误

    Object arr4 = new int[5];//正确
    • 所有类都可以调用Object当中的方法

    Object类中方法

    public String toString();

    • 用法

      • 通过对象.toString()进行调用

      • 在打印对象时自动调用(System.out.print)

      • 在对象与字符串进行”+”拼接时自动调用

    • 说明

      • 如果子类没有重写,继承的Object类的toString默认返回的是
      • 对象的运行时类型@对象的hashCode值的十六进制值
    • 重写toString方法

      • 这样子我们就不用去创建getInfo这种方法了

    public final Class<?> getClass()

    • 返回此对象的运行时类
    1
    2
    3
    4
    5
    6
    7
    //编译时类型是Object,运行时类型是Student
    Object o1 = new Student("李白","男");
    System.out.println(o1.getClass());//class constructor.Student

    //编译时类型是Student,运行时类型是Student
    Student temp1 = new Student("李黑","男");
    System.out.println(temp1.getClass());//class constructor.Student

    public boolean equals(Object obj);

    • 用于判断当前对象this和指定对象obj是否”相等”
    • 默认情况下,equals方法的实现等价于与“==”,比较的是对象的地址值
    • 我们可以选择重写,重写有些要求:
      • 自反性
        • 自己和自己比较一定返回true
        • x.equals(x)一定返回true
      • 对程序
        • x.equals(y)如果返回true,那么y.equals(x)也要返回true
      • 传递性
        • a等于b,b等于c,那么a肯定会等于c
      • 一致性:
        • x.equals(y)如果在前面调用时返回true,这2个对象参与equals比较的属性没有修改的话,那么在后面调用结果也要返回true
      • 非空对象与null比较,永远是false
        • x.equals(null)一定是false
        • null.equals(x)错误,会报空指针异常

    public native int hashCode()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    public native int hashCode():返回该对象的哈希码值。支持此方法是为了提高哈希表(例如 java.util.Hashtable 提供的哈希表)的性能。
    哈希表示一个数组+链表或数组+链表/红黑树的结构。

    数组的优点:
    根据[下标]可以快速的定位到某个元素。

    哈希表是一个容器,用来装对象。当哈希表中的对象有很多的时候,要查询到某个对象是否存在,工作会很大。
    如何提高查询的效率?希望能够充分利用数组的优点。

    但是,对于任意一个对象来说,它在查找之前,并不知道它的[下标]。
    问题就转换为,如何找到快速的计算下标的方式。

    [下标】 = 对象的hashCode值 & (数组的长度 - 1)。

    哈希表存储对象就是这个公式来定位存储位置。
    hashCode值 & (数组的长度 - 1) 计算的结果范围[0, 数组的长度-1]


    因为Java中hashCode值是通过某个“算法”计算出来的一个int值,那么这个算法,可能是某个散列函数,可能是某个JVM地址值等。
    本类Java希望,不同的对象,它的hashCode值是不同的,但是现实中,可能出现,两个不同的Java对象,它的hashCode相等了。

    (1)如果两个对象equals返回true,那么这两个的hashCode一定要相同。
    (2)如果两个对象hashCode值不相同,那么这两个对象equals也一定要不相等。
    (3)如果两个对象的hashCode相同的,那么这个两个对象equals不一定相同

    在重写equals方法时,一定要一起重写hashCode方法,保持它俩的上述规定。
    • public int hashCode():返回每个对象的hash值。

    如果重写equals,那么通常会一起重写hashCode()方法,hashCode()方法主要是为了当对象存储到哈希表(后面集合章节学习)等容器中时提高存储和查询性能用的,这是因为关于hashCode有两个常规协定:

    • ①如果两个对象的hash值是不同的,那么这两个对象一定不相等;
    • ②如果两个对象的hash值是相同的,那么这两个对象不一定相等。

    重写equals和hashCode方法时,要保证满足如下要求:

    • ①如果两个对象调用equals返回true,那么要求这两个对象的hashCode值一定是相等的;

    • ②如果两个对象的hashCode值不同的,那么要求这个两个对象调用equals方法一定是false;

    • ③如果两个对象的hashCode值相同的,那么这个两个对象调用equals可能是true,也可能是false

    1
    2
    3
    4
    public static void main(String[] args) {
    System.out.println("Aa".hashCode());//2112
    System.out.println("BB".hashCode());//2112
    }

    finalize

    protected void finalize():用于最终清理内存的方法

    演示finalize()方法被调用:

    1
    2
    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
    35
    36
    package com.atguigu.api;

    public class TestFinalize {
    public static void main(String[] args) throws Throwable{
    for (int i=1; i <=10; i++){
    MyDemo my = new MyDemo(i);
    //每一次循环my就会指向新的对象,那么上次的对象就没有变量引用它了,就成垃圾对象
    }

    //为了看到垃圾回收器工作,我要加下面的代码,让main方法不那么快结束,因为main结束就会导致JVM退出,GC也会跟着结束。
    System.gc();//如果不调用这句代码,GC可能不工作,因为当前内存很充足,GC就觉得不着急回收垃圾对象。
    //调用这句代码,会让GC尽快来工作。
    Thread.sleep(5000);//单位是毫秒,让当前程序休眠5秒再结束
    }
    }

    class MyDemo{
    private int value;

    public MyDemo(int value) {
    this.value = value;
    }

    @Override
    public String toString() {
    return "MyDemo{" + "value=" + value + '}';
    }

    //重写finalize方法,让大家看一下它的调用效果
    @Override
    protected void finalize() throws Throwable {
    // 正常重写,这里是编写清理系统内存的代码
    // 这里写输出语句是为了看到finalize()方法被调用的效果
    System.out.println(this+ "轻轻的走了,不带走一段代码....");
    }
    }

    每一个对象的finalize()只会被调用一次,哪怕它多次被标记为垃圾对象。当一个对象没有有效的引用/变量指向它,那么这个对象就是垃圾对象。GC(垃圾回收器)通常会在第一次回收某个垃圾对象之前,先调用一下它的finalize()方法,然后再彻底回收它。但是如果在finalize()方法,这个垃圾对象“复活”了(即在finalize()方法中意外的又有某个引用指向了当前对象,这是要避免的),被“复活”的对象如果再次称为垃圾对象,GC就不再调用它的finalize方法了,避免这个对象称为“僵尸”。

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    package com.atguigu.api;

    public class TestFinalize {
    private static MyDemo[] arr = new MyDemo[10];
    private static int total;

    public static void add(MyDemo demo){
    arr[total++] = demo;
    }

    public static void main(String[] args) throws Throwable{
    for (int i=1; i <=10; i++){
    MyDemo my = new MyDemo(i);
    //每一次循环my就会指向新的对象,那么上次的对象就没有变量引用它了,就成垃圾对象
    }

    //为了看到垃圾回收器工作,我要加下面的代码,让main方法不那么快结束,因为main结束就会导致JVM退出,GC也会跟着结束。
    System.gc();//如果不调用这句代码,GC可能不工作,因为当前内存很充足,GC就觉得不着急回收垃圾对象。
    //调用这句代码,会让GC尽快来工作。
    Thread.sleep(5000);//单位是毫秒,让当前程序休眠5秒再结束


    for (int i = 0; i < arr.length; i++) {
    System.out.println(arr[i]);//MyDemo的对象还在,没有被回收掉,因为在回收过程中被复活了
    }

    for (int i = 0; i < arr.length; i++) {
    arr[i] = null;//让这些元素不引用MyDemo的对象,这些对象再次称为垃圾对象
    System.out.println(arr[i]);
    }
    arr = null;

    System.gc();//再次让GC工作,使得MyDemo的对象再次被回收
    Thread.sleep(5000);//单位是毫秒,让当前程序休眠5秒再结束
    }
    }

    class MyDemo{
    private int value;

    public MyDemo(int value) {
    this.value = value;
    }

    @Override
    public String toString() {
    return "MyDemo{" + "value=" + value + '}';
    }

    //重写finalize方法,让大家看一下它的调用效果
    @Override
    protected void finalize() throws Throwable {
    // 正常重写,这里是编写清理系统内存的代码
    // 这里写输出语句是为了看到finalize()方法被调用的效果
    System.out.println("我轻轻的走了,不带走一段代码....");

    TestFinalize.add(this);
    //把当前对象this放到一个数组中,这样就有变量引用它,当前对象就不能被回收了
    //当下次this对象再次称为垃圾对象之后,GC就不会调用它的finalize()方法了
    }
    }

    面试题:对finalize()的理解?

    • 当对象被GC确定为要被回收的垃圾,在回收之前由GC帮你调用这个方法,不是由程序员手动调用。

    • 这个方法与C语言的析构函数不同,C语言的析构函数被调用,那么对象一定被销毁,内存被回收,而finalize方法的调用不一定会销毁当前对象,因为可能在finalize()中出现了让当前对象“复活”的代码

    • 每一个对象的finalize方法只会被调用一次,就算对象在finalize方法中被复活了,下次GC就不调用它的finalize方法了。

    • 子类可以选择重写,一般用于彻底释放一些资源对象,而且这些资源对象往往时通过C/C++等代码申请的资源内存

    静态

    • 静态变量存储在方法区

    7.1.1 静态关键字(static)

    在类中声明的实例变量,其值是每一个对象独立的。但是有些成员变量的值不需要或不能每一个对象单独存储一份,即有些成员变量和当前类的对象无关。

    在类中声明的实例方法,在类的外面必须要先创建对象,才能调用。但是有些方法的调用和当前类的对象无关,那么创建对象就有点麻烦了。

    此时,就需要将和当前类的对象无关的成员变量、成员方法声明为静态的(static)。

    7.1.2 静态变量

    1、语法格式

    有static修饰的成员变量就是静态变量。

    1
    2
    3
    【修饰符】 class 类{
    【其他修饰符】 static 数据类型 静态变量名;
    }

    2、静态变量的特点

    • 静态变量的默认值规则和实例变量一样。

    • 静态变量值是所有对象共享。

    • 静态变量的值存储在方法区

    • 静态变量在本类中,可以在任意方法、代码块、构造器中直接使用。

    • 如果权限修饰符允许,在其他类中可以通过“类名.静态变量”直接访问,也可以通过“对象.静态变量”的方式访问(但是更推荐使用类名.静态变量的方式)。

    • 静态变量的get/set方法也静态的,当局部变量与静态变量重名时,使用“类名.静态变量”进行区分。

    分类数据类型默认值
    基本类型整数(byte,short,int,long)0
    浮点数(float,double)0.0
    字符(char)‘\u0000’
    布尔(boolean)false
    数据类型默认值
    引用类型数组,类,接口null

    演示:

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    package com.atguigu.keyword;

    public class Employee {
    private static int total;//这里私有化,在类的外面必须使用get/set方法的方式来访问静态变量
    static String company; //这里缺省权限修饰符,是为了演示在类外面演示“类名.静态变量”的方式访问
    private int id;
    private String name;

    {
    //两个构造器的公共代码可以提前到非静态代码块
    total++;
    id = total; //这里使用total静态变量的值为id属性赋值
    }

    public Employee() {
    }

    public Employee(String name) {
    this.name = name;
    }

    public void setId(int id) {
    this.id = id;
    }

    public int getId() {
    return id;
    }

    public String getName() {
    return name;
    }

    public void setName(String name) {
    this.name = name;
    }

    public static int getTotal() {
    return total;
    }

    public static void setTotal(int total) {
    Employee.total = total;
    }

    @Override
    public String toString() {
    return "Employee{company = " + company + ",id = " + id + " ,name=" + name +"}";
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    package com.atguigu.keyword;

    public class TestStaticVariable {
    public static void main(String[] args) {
    //静态变量total的默认值是0
    System.out.println("Employee.total = " + Employee.getTotal());

    Employee c1 = new Employee("张三");
    Employee c2 = new Employee();
    System.out.println(c1);//静态变量company的默认值是null
    System.out.println(c2);//静态变量company的默认值是null
    System.out.println("Employee.total = " + Employee.getTotal());//静态变量total值是2

    Employee.company = "尚硅谷";
    System.out.println(c1);//静态变量company的值是尚硅谷
    System.out.println(c2);//静态变量company的值是尚硅谷

    //只要权限修饰符允许,虽然不推荐,但是也可以通过“对象.静态变量”的形式来访问
    c1.company = "超级尚硅谷";

    System.out.println(c1);//静态变量company的值是超级尚硅谷
    System.out.println(c2);//静态变量company的值是超级尚硅谷
    }
    }

    3、静态变量内存分析

    4、静态类变量和非静态实例变量、局部变量

    • 静态类变量(简称静态变量):存储在方法区,有默认值,所有对象共享,生命周期和类相同,还可以有权限修饰符、final等其他修饰符
    • 非静态实例变量(简称实例变量):存储在堆中,有默认值,每一个对象独立,生命周期每一个对象也独立,还可以有权限修饰符、final等其他修饰符
    • 局部变量:存储在栈中,没有默认值,每一次方法调用都是独立的,有作用域,只能有final修饰,没有其他修饰符
    • 注意下,局部变量是在{}中的,形参,代码块{}中,而成员变量是类中方法外

    7.1.3 静态方法

    1、语法格式

    有static修饰的成员方法就是静态方法。

    1
    2
    3
    4
    5
    【修饰符】 class 类{
    【其他修饰符】 static 返回值类型 方法名(形参列表){
    方法体
    }
    }

    2、静态方法的特点

    • 静态方法在本类的任意方法、代码块、构造器中都可以直接被调用。
    • 只要权限修饰符允许,静态方法在其他类中可以通过“类名.静态方法“的方式调用。也可以通过”对象.静态方法“的方式调用(但是更推荐使用类名.静态方法的方式)。
    • 静态方法可以被子类继承,但不能被子类重写
    • 静态方法的调用都只看编译时类型。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package com.atguigu.keyword;

    public class Father {
    public static void method(){
    System.out.println("Father.method");
    }

    public static void fun(){
    System.out.println("Father.fun");
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    package com.atguigu.keyword;

    public class Son extends Father{
    // @Override //尝试重写静态方法,加上@Override编译报错,去掉Override不报错,但是也不是重写
    public static void fun(){
    System.out.println("Son.fun");
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package com.atguigu.keyword;

    public class TestStaticMethod {
    public static void main(String[] args) {
    Father.method();
    Son.method();//继承静态方法

    Father f = new Son();
    f.method();//执行Father类中的method
    }
    }

    7.1.4 静态代码块

    如果想要为静态变量初始化,可以直接在静态变量的声明后面直接赋值,也可以使用静态代码块。

    1、语法格式

    代码块的前面加static,就是静态代码块

    1
    2
    3
    4
    5
    【修饰符】 class 类{
    static{
    静态代码块
    }
    }

    2、静态代码块的特点

    每一个类的静态代码块只会执行一次。(注意区分普通的代码块)

    静态代码块的执行优先于非静态代码块和构造器。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    package com.atguigu.keyword;

    public class Chinese {
    // private static String country = "中国";

    private static String country;
    private String name;

    {
    System.out.println("非静态代码块,country = " + country);
    }

    static {
    country = "中国";
    System.out.println("静态代码块");
    }

    public Chinese(String name) {
    this.name = name;
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    package com.atguigu.keyword;

    public class TestStaticBlock {
    public static void main(String[] args) {
    Chinese c1 = new Chinese("张三");
    Chinese c2 = new Chinese("李四");
    }
    }

    3、静态代码块和非静态代码块

    静态代码块在类初始化时执行,只执行一次

    非静态代码块在实例初始化时执行,每次new对象都会执行

    7.1.5 类初始化

    (1)类的初始化就是为静态变量初始化。实际上,类初始化的过程时在调用一个()方法,而这个方法是编译器自动生成的。编译器会将如下两部分的所有代码,按顺序合并到类初始化()方法体中。

    • 静态类成员变量的显式赋值语句

    • 静态代码块中的语句

    (2)每个类初始化只会进行一次,如果子类初始化时,发现父类没有初始化,那么会先初始化父类。

    (3)类的初始化一定优先于实例初始化。

    1、类初始化代码只执行一次

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    package com.atguigu.keyword;

    public class Fu{
    static{
    System.out.println("Fu静态代码块1,a = " + Fu.a);
    }
    private static int a = 1;
    static{
    System.out.println("Fu静态代码块2,a = " + a);
    }

    public static void method(){
    System.out.println("Fu.method");
    }

    }
    1
    2
    3
    4
    5
    6
    7
    package com.atguigu.keyword;

    public class TestClassInit {
    public static void main(String[] args) {
    Fu.method();
    }
    }

    2、父类优先于子类初始化

    1
    2
    3
    4
    5
    6
    7
    package com.atguigu.keyword;

    public class Zi extends Fu{
    static{
    System.out.println("Zi静态代码块");
    }
    }
    1
    2
    3
    4
    5
    6
    7
    package com.atguigu.keyword;

    public class TestZiInit {
    public static void main(String[] args) {
    Zi z = new Zi();
    }
    }

    3、类初始化优先于实例初始化

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package com.atguigu.keyword;

    public class Fu{
    static{
    System.out.println("Fu静态代码块1,a = " + Fu.a);
    }
    private static int a = 1;
    static{
    System.out.println("Fu静态代码块2,a = " + a);
    }

    {
    System.out.println("Fu非静态代码块");
    }
    public Fu(){
    System.out.println("Fu构造器");
    }

    public static void method(){
    System.out.println("Fu.method");
    }
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package com.atguigu.keyword;

    public class Zi extends Fu{
    static{
    System.out.println("Zi静态代码块");
    }
    {
    System.out.println("Zi非静态代码块");
    }
    public Zi(){
    System.out.println("Zi构造器");
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    package com.atguigu.keyword;

    public class TestZiInit {
    public static void main(String[] args) {
    Zi z1 = new Zi();
    Zi z2 = new Zi();
    }
    }

    7.1.6 静态和非静态的区别

    1、本类中的访问限制区别

    静态的类变量和静态的方法可以在本类的任意方法、代码块、构造器中直接访问。

    非静态的实例变量和非静态的方法==只能==在本类的非静态的方法、非静态代码块、构造器中直接访问。

    即:

    • 静态直接访问静态,可以
    • 非静态直接访问非静态,可以
    • 非静态直接访问静态,可以
    • 静态直接访问非静态,不可以
      • 比如在main方法无法调用非静态的方法,只能调用实例身上的方法或者是静态方法

    2、在其他类的访问方式区别

    静态的类变量和静态的方法可以通过“类名.”的方式直接访问;也可以通过“对象.”的方式访问。(但是更推荐使用==”类名.”==的方式)

    非静态的实例变量和非静态的方法==只能==通过“对象.”方式访问。

    3、this和super的使用

    静态的方法和静态的代码块中,==不允许==出现this和super关键字,如果有重名问题,使用“类名.”进行区别。

    非静态的方法和非静态的代码块中,可以使用this和super关键字。

    7.1.7 静态导入

    如果大量使用另一个类的静态成员,可以使用静态导入,简化代码。

    1
    2
    import static 包.类名.静态成员名;
    import static 包.类名.*;

    演示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package com.atguigu.keyword;

    import static java.lang.Math.*;

    public class TestStaticImport {
    public static void main(String[] args) {
    //使用Math类的静态成员
    System.out.println(Math.PI);
    System.out.println(Math.sqrt(9));
    System.out.println(Math.random());

    System.out.println("----------------------------");
    System.out.println(PI);
    System.out.println(sqrt(9));
    System.out.println(random());
    }
    }

    枚举

    7.2.1 概述

    某些类型的对象是有限的几个,这样的例子举不胜举:

    • 星期:Monday(星期一)……Sunday(星期天)
    • 性别:Man(男)、Woman(女)
    • 月份:January(1月)……December(12月)
    • 季节:Spring(春节)……Winter(冬天)
    • 支付方式:Cash(现金)、WeChatPay(微信)、Alipay(支付宝)、BankCard(银行卡)、CreditCard(信用卡)
    • 员工工作状态:Busy(忙)、Free(闲)、Vocation(休假)
    • 订单状态:Nonpayment(未付款)、Paid(已付款)、Fulfilled(已配货)、Delivered(已发货)、Checked(已确认收货)、Return(退货)、Exchange(换货)、Cancel(取消)

    枚举类型本质上也是一种类,只不过是这个类的对象是固定的几个,而不能随意让用户创建。

    在JDK1.5之前,需要程序员自己通过特殊的方式来定义枚举类型。

    在JDK1.5之后,Java支持enum关键字来快速的定义枚举类型。

    7.2.2 JDK1.5之前

    在JDK1.5之前如何声明枚举类呢?

    • 构造器加private私有化
    • 本类内部创建一组常量对象,并添加public static修饰符,对外暴露这些常量对象

    示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    public class Season{
    public static final Season SPRING = new Season();
    public static final Season SUMMER = new Season();
    public static final Season AUTUMN = new Season();
    public static final Season WINTER = new Season();

    private Season(){

    }

    public String toString(){
    if(this == SPRING){
    return "春";
    }else if(this == SUMMER){
    return "夏";
    }else if(this == AUTUMN){
    return "秋";
    }else{
    return "冬";
    }
    }
    }
    1
    2
    3
    4
    5
    6
    public class TestSeason {
    public static void main(String[] args) {
    Season spring = Season.SPRING;
    System.out.println(spring);
    }
    }

    7.2.3 JDK1.5之后

    1、enum关键字声明枚举

    1
    2
    3
    4
    5
    6
    7
    8
    9
    【修饰符】 enum 枚举类名{
    常量对象列表
    }

    【修饰符】 enum 枚举类名{
    常量对象列表;

    其他成员列表;
    }

    示例代码:

    1
    2
    3
    4
    5
    package com.atguigu.enumeration;

    public enum Week {
    MONDAY,TUESDAY,WEDNESDAY,THURSDAY,FRIDAY,SATURDAY,SUNDAY
    }
    1
    2
    3
    4
    5
    6
    public class TestEnum {
    public static void main(String[] args) {
    Season spring = Season.SPRING;
    System.out.println(spring);
    }
    }

    2、枚举类的要求和特点

    枚举类的要求和特点:

    • 枚举类的常量对象列表必须在枚举类的首行,因为是常量,所以建议大写。
    • 如果常量对象列表后面没有其他代码,那么“;”可以省略,否则不可以省略“;”。
    • 编译器给枚举类默认提供的是private的无参构造,如果枚举类需要的是无参构造,就不需要声明,写常量对象列表时也不用加参数,
    • 如果枚举类需要的是有参构造,需要手动定义,有参构造的private可以省略,调用有参构造的方法就是在常量对象名后面加(实参列表)就可以。
    • 枚举类默认继承的是java.lang.Enum类,因此不能再继承其他的类型。
    • JDK1.5之后switch,提供支持枚举类型,case后面可以写枚举常量名。
    • 枚举类型如有其它属性,建议(不是必须)这些属性也声明为final的,因为常量对象在逻辑意义上应该不可变。

    示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    package com.atguigu.enumeration;

    public enum Week {
    MONDAY("星期一"),
    TUESDAY("星期二"),
    WEDNESDAY("星期三"),
    THURSDAY("星期四"),
    FRIDAY("星期五"),
    SATURDAY("星期六"),
    SUNDAY("星期日");

    private final String description;

    private Week(String description){
    this.description = description;
    }

    @Override
    public String toString() {
    return super.toString() +":"+ description;
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    package com.atguigu.enumeration;

    public class TestWeek {
    public static void main(String[] args) {
    Week week = Week.MONDAY;
    System.out.println(week);

    switch (week){
    case MONDAY:
    System.out.println("怀念周末,困意很浓");break;
    case TUESDAY:
    System.out.println("进入学习状态");break;
    case WEDNESDAY:
    System.out.println("死撑");break;
    case THURSDAY:
    System.out.println("小放松");break;
    case FRIDAY:
    System.out.println("又信心满满");break;
    case SATURDAY:
    System.out.println("开始盼周末,无心学习");break;
    case SUNDAY:
    System.out.println("一觉到下午");break;
    }
    }
    }

    3、枚举类型常用方法

    1
    2
    3
    4
    5
    1.String toString(): 默认返回的是常量名(对象名),可以继续手动重写该方法!
    2.String name():返回的是常量名(对象名)
    3.int ordinal():返回常量的次序号,默认从0开始
    4.枚举类型[] values():返回该枚举类的所有的常量对象,返回类型是当前枚举的数组类型,是一个静态方法
    5.枚举类型 valueOf(String name):根据枚举常量对象名称获取枚举对象

    示例代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    package com.atguigu.enumeration;

    import java.util.Scanner;

    public class TestEnumMethod {
    public static void main(String[] args) {
    Week[] values = Week.values();
    for (int i = 0; i < values.length; i++) {
    System.out.println((values[i].ordinal()+1) + "->" + values[i].name());
    }
    System.out.println("------------------------");

    Scanner input = new Scanner(System.in);
    System.out.print("请输入星期值:");
    int weekValue = input.nextInt();
    Week week = values[weekValue-1];
    System.out.println(week);

    System.out.print("请输入星期名:");
    String weekName = input.next();
    week = Week.valueOf(weekName);
    System.out.println(week);

    input.close();
    }
    }

    技巧

    • 快速输出println
      • sout
    • 快速输出printf
      • souf
    • 上一个值打印输出
    1
    2
    3
    4
    5
    String a = input.next();

    soutv //后回车
    //之后生成
    System.out.println("a = " + a);
    • 遍历数组

      • itar
      1
      2
      3
      for (int i = 0; i < nums.length; i++) {
      int num = nums[i];
      }
    • 迭代可迭代的对象或数组

      • iter
      1
      2
      3
      for (int num : nums) {

      }
    • for循环当中可以定义多个变量~

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public int[] getAllPrimeNumber(){
    if(value <=0) return new int[0];
    int[] tempArray = new int[approximateNumberCount()];
    for(int i =1,index = 0; i <= value; i++){
    if(value % i == 0){
    tempArray[index++] = i;
    }
    }
    return tempArray;
    }
    • 查看调用方法的形参可以输入哪些,快捷键Ctrl + p

    • 让当前程序歇一会
      • Thread.sleep(5000);传入的为毫秒
    • System.gc()通知gc来回收一下垃圾对象
    • 如果不用构造器有相同代码,可以使用代码块功能
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    public abc(){
    this.a = 100;
    this.b = 10;
    }

    public abc(String a){
    this.a = 100;
    this.b = 666
    }
    //简写为
    {
    this.a = 100;
    }
    public abc(){
    this.b = 10;
    }

    public abc(String a){
    this.b = 666
    }

    包装类

    • 将基本数据类型转化为包装类,这样子就可以调用对象里面的方法了

    抽象类

    • 描述对象应具有的行为

    接口

    • 规定标准
    • 接口是具有多态的

    注解

    • Junit
      • 可以帮助我们更好测试目前来的用处

    异常

    • 可以用try…catch捕捉运行
    1
    2
    3
    4
    5
    6
    7
    8
    try{
    可能发生xx异常的代码
    }catch(异常类型1 e){
    处理异常的代码1
    }catch(异常类型2 e){
    处理异常的代码2
    }
    ....

    img

    • 如果一个方法可能会产生异常,但是没有能力或者不愿意去处理这个异常,可以在方法声明处用throws来声明抛出异常

      • 如果进行了声明,但调用者没有进行异常处理,是无法编译通过的
    • 顺带一提,不是所有的方法抛出的异常都会处理的

      • IndexOutOfBoundsException,NullPointerException等都在编译时不会强制要求捕获异常,可以选择捕获处理异常,也可以选择不处理
    • 练习

      • 从键盘输入两个整数,求它们的商。尽量考虑和避免异常,无法避免的使用try…catch处理。

        (1)如果用户输入的非整数,使用try…catch处理,并且让用户重新输入

        ​ 可以使用InputMismatchException或者Scanner实例对象的hasNextInt

        (2)如果用户输入的除数为0,请用户重新输入。

        可以使用return或者break;跳出循环

    多线程

    • 并行:同一个时刻,同时处理多项任务

    • 并发:同一个时刻,只能处理一个任务,其他任务交替执行

    • 如果需要停止运行线程,有下面几种方法

      • https://www.jb51.net/article/212591.htm
      • interrupted(): 测试当前线程是否已经中断。该方法为静态方法,调用后会返回boolean值。不过调用之后会改变线程的状态,如果是中断状态调用的,调用之后会清除线程的中断状态。
      • interrupt(): 标记线程为中断状态,不过不会中断正在运行的线程。
    • wait() 必须搭配 synchronized 来使用,wait()必须写到synchronized代码块里面(notify 方法也必须在synchronized代码块中使用)。脱离 synchronized 使用 wait() 会直接抛出异常

    • 同步方法:synchronized 关键字直接修饰方法,表示同一时刻只有一个线程能进入这个方法,其他线程在外面等着。

    1
    2
    3
    public synchronized void method(){
        可能会产生线程安全问题的代码
    }
    • 同步代码块:synchronized 关键字可以用于某个区块前面,表示只对这个区块的资源实行互斥访问。
      格式:
    1
    2
    3
    synchronized(同步锁){
         需要同步操作的代码
    }
    • wait和notify
      • wait释放占用的资源,并进入阻塞等待阶段
      • notify通知阻塞等待阶段的线程,可以过来竞争资源

    1
    2
    3
    4
    5
    6
    7
    OutputStream out = socket.getOutputStream();
    PrintStream ps = new PrintStream(out);
    为什么有了OutputStream还需要new一个PrintStream ?
    回答:
    在给服务器发送数据时,确实可以直接使用 OutputStream 来进行写操作。但是,在这个例子中使用了 PrintStream 的主要原因可能是为了方便输出字符串。

    PrintStream 是 OutputStream 的子类,它提供了一些方便的方法来处理字符数据。使用 PrintStream,你可以使用 println() 方法直接输出字符串,并且它会自动添加换行符。这在处理文本数据时比较方便,特别是在网络通信中。

    注意

    • 如果类中有引用数据类型,记得new一下,否则会报java.lang.NullPointerException

    • 访问本类方法(非main)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    package top.dreamlove.circle;

    public class Circle {
    double r;//圆半径

    //求圆的面积
    public double area(){

    return r * r * Math.PI;
    }
    //周长
    public double perimeter(){
    return 2 * r * Math.PI;
    }
    //返回圆对象信息
    public String getInfo(){

    return "半径:" + r + "周长:" + perimeter() + "面积:" + area();
    }
    }

    • main方法中的String [] args其实写成String... args也是一样的,因为可变参数是jdk1.5才有的,而main方法jdk1.0就有了
    • main方法当中可以获取系统编码

    JDBC

    • 引入包
    1
    2
    MySQL5.7:mysql-connector-java-5.1.36-bin.jar
    MySQL8.0:mysql-connector-java-8.0.19.jar

    实现增删改查

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    package top.dreamlove.jdbc;

    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.PreparedStatement;
    import java.sql.SQLException;

    public class TestJDBC {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
    //把驱动类加载到内存中
    Class.forName("com.mysql.cj.jdbc.Driver");
    //获取数据库连接对象
    String url = "jdbc:mysql://127.0.0.1:3306/atguigu?serverTimezone=UTC";
    Connection connection = DriverManager.getConnection(url,"root","root");//网络编程的Socket


    String sql = "insert into t_department values(null,'测试部门数据','测试数据部门简介')";

    PreparedStatement PreparedStatement = connection.prepareStatement(sql);//准备发送
    int len = PreparedStatement.executeUpdate();//发送数据
    System.out.println("影响的条数" + len);
    PreparedStatement.close();
    connection.close();//关闭连接
    }
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    package top.dreamlove.jdbc;

    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.PreparedStatement;
    import java.sql.SQLException;

    public class TESTJDBCDelete {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
    Class.forName("com.mysql.cj.jdbc.Driver");
    String url = "jdbc:mysql://127.0.0.1:3306/atguigu?serverTimezone=UTC";
    Connection connection = DriverManager.getConnection(url,"root","root");

    String sql = "delete from t_department where did = 7";
    PreparedStatement pst = connection.prepareStatement(sql);

    int len = pst.executeUpdate();
    System.out.println("执行成功" + len);

    pst.close();
    connection.close();
    }
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    package top.dreamlove.jdbc;

    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.PreparedStatement;
    import java.sql.SQLException;

    public class TestJDBCUpdate {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
    Class.forName("com.mysql.cj.jdbc.Driver");
    String url = "jdbc:mysql://127.0.0.1:3306/atguigu?serverTimezone=UTC";
    Connection connection = DriverManager.getConnection(url,"root","root");

    String sql = "update t_department set description = '我是修改后的' where did = 7";
    PreparedStatement pst = connection.prepareStatement(sql);

    int len = pst.executeUpdate();
    System.out.println("执行成功" + len);

    pst.close();
    connection.close();
    }
    }

    • 结果值有一个元数据,对数据进行描述的信息,比如数据列有多少列,数据的列名称等
    1
    2
    ResultSetMetaData metaData = rs.getMetaData();
    int columnCount = metaData.getColumnCount();//获取结果集有几列
    1
    2
    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
    package top.dreamlove.jdbc;

    import java.sql.*;

    public class TestJDBCQuery {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
    Class.forName("com.mysql.cj.jdbc.Driver");
    String url = "jdbc:mysql://127.0.0.1:3306/atguigu?serverTimezone=UTC";
    Connection connection = DriverManager.getConnection(url,"root","root");

    String sql = "select * from t_department";
    PreparedStatement pst = connection.prepareStatement(sql);

    ResultSet rst = pst.executeQuery();
    //遍历
    while (rst.next()){
    int did = rst.getInt("did");
    String dname = rst.getString("dname");
    String desc = rst.getString("description");
    System.out.println(did + dname + " 描述:" + desc);

    //或者
    Object did1 = rst.getInt(1);
    Object dname1 = rst.getString(2);
    Object desc1 = rst.getString(3);
    System.out.println("did1 = " + did1);
    System.out.println("dname1 = " + dname1);
    System.out.println("desc1 = " + desc1);
    }
    rst.close();
    connection.close();
    }
    }

    sql拼接-使用?代替值

    • 如果不适用?代替值,如果出现了很多个参数,很不方便
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    package top.dreamlove.problem;

    import java.sql.*;
    import java.util.Date;
    import java.util.Scanner;

    public class Demo1 {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
    Scanner input = new Scanner(System.in);
    System.out.print("请输入姓名:");
    String ename = input.next();//李四

    System.out.print("请输入薪资:");
    double salary = input.nextDouble();//15000

    System.out.print("请输入出生日期:");
    String birthday = input.next();//1990-1-1

    System.out.print("请输入性别:");
    char gender = input.next().charAt(0);//男

    System.out.print("请输入手机号码:");
    String tel = input.next();//13578595685

    System.out.print("请输入邮箱:");
    String email = input.next();//zhangsan@atguigu.com

    input.close();
    Class.forName("com.mysql.cj.jdbc.Driver");
    String url = "jdbc:mysql://127.0.0.1:3306/atguigu?serverTimezone=UTC";
    Connection connection = DriverManager.getConnection(url,"root","root");

    String sql = "INSERT INTO t_employee(ename,salary,birthday,gender,tel,email,hiredate)VALUES(?,?,?,?,?,?,?);";
    PreparedStatement pst = connection.prepareStatement(sql);
    //如果知道确切的类型可以指定set类型
    // pst.setString(1,ename);//这里的1代表是第一个问号

    //如果不知道可以通过setObject
    pst.setObject(1,ename);//这里的1代表是第一个问号
    pst.setObject(2,salary);
    pst.setObject(3,birthday);
    pst.setObject(4,gender + "");//Mysql中的char类型实际上是数据库
    pst.setObject(5,tel);
    pst.setObject(6,email);
    pst.setObject(7, new Date());

    int len = pst.executeUpdate();
    System.out.println("执行成功" + len);

    pst.close();
    connection.close();
    }
    }

    sql注入-防sql注入

    • 如果我们采用拼接的写法,很容易sql注入,比如我们使用SELECT * FROMt_employeeWHERE eid = + 用户输入的用户id,如果用户输入1 or 1=1,那么就会返回所有的数据
    1
    2
    -- 将会返回所有的数据
    SELECT * FROM `t_employee` WHERE eid = 1 or 1=1;
    • 所以解决办法就是采用?代替
    1
    2
    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
    35
    36
    37
    package top.dreamlove.jdbc;

    import java.sql.*;
    import java.util.Scanner;

    public class TestJDBCQuery1 {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
    Class.forName("com.mysql.cj.jdbc.Driver");
    String url = "jdbc:mysql://127.0.0.1:3306/atguigu?serverTimezone=UTC";
    Connection connection = DriverManager.getConnection(url,"root","root");

    Scanner input = new Scanner(System.in);
    System.out.print("请输入你要查询的员工的编号:");
    String id = input.nextLine();
    input.close();
    String sql = "select * from t_employee where eid=?";
    PreparedStatement pst = connection.prepareStatement(sql);

    //通过?设置
    pst.setString(1,id);

    ResultSet rst = pst.executeQuery();
    ResultSetMetaData metaData = rst.getMetaData();
    int columnLength = metaData.getColumnCount();//获取数据有多少列
    //遍历
    while (rst.next()){
    for(int i = 1;i<=columnLength;i++){
    //输出所有数据
    System.out.print(rst.getObject(i) + "\t");
    }
    System.out.println();
    }
    rst.close();
    connection.close();
    }
    }

    • 不适用?代替就会出现sql注入
    1
    2
    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
    package top.dreamlove.jdbc;

    import java.sql.*;
    import java.util.Scanner;

    public class TestJDBCQuery1 {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
    Class.forName("com.mysql.cj.jdbc.Driver");
    String url = "jdbc:mysql://127.0.0.1:3306/atguigu?serverTimezone=UTC";
    Connection connection = DriverManager.getConnection(url,"root","root");

    Scanner input = new Scanner(System.in);
    System.out.print("请输入你要查询的员工的编号:");
    String id = input.nextLine();
    input.close();
    String sql = "select * from t_employee where eid = " + id;
    PreparedStatement pst = connection.prepareStatement(sql);

    ResultSet rst = pst.executeQuery();
    ResultSetMetaData metaData = rst.getMetaData();
    int columnLength = metaData.getColumnCount();//获取数据有多少列
    //遍历
    while (rst.next()){
    for(int i = 1;i<=columnLength;i++){
    //输出所有数据
    System.out.print(rst.getObject(i) + "\t");
    }
    System.out.println();
    }
    rst.close();
    connection.close();
    }
    }

    图片上传

    • 如果图片过大,限制了,需要到Mysql配置文件修改max_allowed_packet变量的值
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    package top.dreamlove.problem;

    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.FileReader;
    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.PreparedStatement;
    import java.sql.SQLException;
    import java.util.Scanner;

    public class Demo2 {
    public static void main(String[] args) throws ClassNotFoundException, SQLException, FileNotFoundException {
    Scanner input = new Scanner(System.in);
    System.out.print("请输入用户名:");
    String username = input.next();

    System.out.print("请选择照片:");
    String path = input.next();//这里没有图形化界面,只能输入路径,通过IO流读取图片的内容

    System.out.print("请输入密码:");
    String password = input.next();



    //把驱动类加载到内存中
    Class.forName("com.mysql.cj.jdbc.Driver");
    //获取数据库连接对象
    String url = "jdbc:mysql://127.0.0.1:3306/atguigu?serverTimezone=UTC";
    Connection connection = DriverManager.getConnection(url,"root","root");//网络编程的Socket


    String sql = "insert into t_user values(null,?,?,?)";

    PreparedStatement pst = connection.prepareStatement(sql);//准备发送

    pst.setObject(1,username);
    pst.setObject(2,new FileInputStream(path));//字节IO流表示二进制
    pst.setObject(3,password);

    int len = pst.executeUpdate();//发送数据
    System.out.println("影响的条数" + len);
    pst.close();
    connection.close();//关闭连接
    }
    }

    获取自增键值

    • 传入一个枚举值为Statement.RETURN_GENERATED_KEYS
    1
    2
    import java.sql.*;
    PreparedStatement pst = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);//准备发送
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    package top.dreamlove.problem;

    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.sql.*;
    import java.util.Date;
    import java.util.Scanner;

    public class Demo3 {
    public static void main(String[] args) throws ClassNotFoundException, SQLException, FileNotFoundException {
    Scanner input = new Scanner(System.in);
    System.out.print("请输入姓名:");
    String ename = input.next();//李四

    System.out.print("请输入薪资:");
    double salary = input.nextDouble();//15000

    System.out.print("请输入出生日期:");
    String birthday = input.next();//1990-1-1

    System.out.print("请输入性别:");
    String gender = input.next();//男 mysql的gender是枚举类型,这里用String处理

    System.out.print("请输入手机号码:");
    String tel = input.next();//13578595685

    System.out.print("请输入邮箱:");
    String email = input.next();//zhangsan@atguigu.com

    input.close();



    //把驱动类加载到内存中
    Class.forName("com.mysql.cj.jdbc.Driver");
    //获取数据库连接对象
    String url = "jdbc:mysql://127.0.0.1:3306/atguigu?serverTimezone=UTC";
    Connection connection = DriverManager.getConnection(url,"root","root");//网络编程的Socket


    String sql = "INSERT INTO t_employee(ename,salary,birthday,gender,tel,email,hiredate)VALUES(?,?,?,?,?,?,?)";

    PreparedStatement pst = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);//准备发送

    pst.setObject(1,ename); //这里的1,表示第1个?
    pst.setObject(2,salary); //这里的2,表示第2个?
    pst.setObject(3,birthday); //这里的3,表示第3个?
    pst.setObject(4,gender); //这里的4,表示第4个?
    pst.setObject(5,tel); //这里的5,表示第5个?
    pst.setObject(6,email); //这里的6,表示第6个?
    pst.setObject(7, new Date()); //这里的7,表示第7个?

    int len = pst.executeUpdate();//发送数据
    ResultSet generatedKeys = pst.getGeneratedKeys();
    if(generatedKeys.next()){
    //输出自增第一个的结果
    System.out.println(generatedKeys.getObject(1));
    }
    System.out.println("影响的条数" + len);
    pst.close();
    connection.close();//关闭连接
    }
    }

    批处理

    • 注意不要把values写成了value
    • 如何实现批处理?
    • url中加rewriteBatchedStatements=true
      jdbc:mysql://localhost:3306/atguigu?serverTimezone=UTC&rewriteBatchedStatements=true
    • PreparedStatement对象调用
      • addBatch()先赞着这些数据,设置后后sql会重新编译下,生成一条完整的sql
      • executeBatch()设置一次,执行一次
    1
    2
    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
    35
    36
    37
    package top.dreamlove.problem;

    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.PreparedStatement;
    import java.sql.SQLException;

    public class Demo4 {
    public static void main(String[] args) throws ClassNotFoundException, SQLException {
    long start = System.currentTimeMillis();
    //把驱动类加载到内存中
    Class.forName("com.mysql.cj.jdbc.Driver");
    //获取数据库连接对象
    String url = "jdbc:mysql://127.0.0.1:3306/atguigu?serverTimezone=UTC&rewriteBatchedStatements=true";
    Connection connection = DriverManager.getConnection(url,"root","root");//网络编程的Socket


    String sql = "insert into t_department values(null,?,?)";

    PreparedStatement pst = connection.prepareStatement(sql);//准备发送

    //批处理
    for(int i = 1 ; i <= 1000;i++){
    pst.setObject(1,"测试" + i);
    pst.setObject(2,"测试简介" + i);
    pst.addBatch();
    }

    int[] lenList = pst.executeBatch();//发送数据
    long end = System.currentTimeMillis();
    System.out.println("耗费时间" + (end - start));
    System.out.println("影响的条数" + lenList.length);
    pst.close();
    connection.close();//关闭连接
    }
    }

    事物处理

    • JDBC如何管理事务?
      • mysql默认是自动提交事务,每执行一条语句成功后,自动提交。需要开启手动提交模式。
    • Connection连接对象.setAutoCommit(false);//取消自动提交模式,开始手动提交模式
      • sql执行成功,别忘了提交事务Connection连接对象.commit();
      • sql执行失败,回滚事务 Connection连接对象.rollback();
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    package top.dreamlove.problem;

    import java.sql.Connection;
    import java.sql.DriverManager;
    import java.sql.PreparedStatement;
    import java.sql.SQLException;

    public class Demo5 {
    public static void main(String[] args) throws Exception {
    //把驱动类加载到内存中
    Class.forName("com.mysql.cj.jdbc.Driver");
    //获取数据库连接对象
    String url = "jdbc:mysql://127.0.0.1:3306/atguigu?serverTimezone=UTC";
    Connection connection = DriverManager.getConnection(url,"root","root");//网络编程的Socket

    connection.setAutoCommit(false);

    String sql1 = "update t_department set description = 'xxx' where did = 2";
    String sql2 = "update t_department set1 description = 'xxx' where did = 3";

    PreparedStatement pst1 = connection.prepareStatement(sql1);//准备发送
    PreparedStatement pst2 = connection.prepareStatement(sql2);//准备发送
    try{
    pst1.executeUpdate();
    pst2.executeUpdate();

    connection.commit();//没有问题才提交
    }catch (Exception e){
    //执行失败
    connection.rollback();//回滚操作
    System.out.println("执行回滚操作");
    }

    pst1.close();
    pst2.close();
    //这里习惯上,在cLose之前,,会把连接重新设置为自动提交模式
    connection.setAutoCommit(true);
    connection.close();//关闭连接
    }
    }

    数据库连接池

    • 连接对象的缓冲区。负责申请,分配管理,释放连接的操作。

    • 为什么要用

      • 如果不使用数据库连接池,每次都通过DriverManager获取新连接,用完直接抛弃断开,连接的利用率太低,太浪费。
      • 对于数据库服务器来说,压力太大了。我们数据库服务器和Java程序对连接数也无法控制,很容易导致数据库服务器崩溃。

    使用阿里的德鲁伊

    • 引入jar

    • 编写配置文件

      • src下加一个druid.properties文件
      • 或者在模块根目录下,再建立一个文件夹叫config,把config文件夹设置为源代码文件夹,再在config文件夹建一个druid.properties文件

    • 填写内容

    1
    2
    3
    4
    5
    6
    7
    8
    #key=value
    driverClassName=com.mysql.cj.jdbc.Driver
    url=jdbc:mysql://localhost:3306/atguigu?serverTimezone=UTC&rewriteBatchedStatements=true
    username=root
    password=123456
    initialSize=5
    maxActive=10
    maxWait=1000
    • 从数据库连接池中获取连接
      • 通过德鲁伊的数据库连接的工厂类创建数据库连接池,再从池中获取连接
      • 加载器加载资源文件
    1
    2
    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
    package top.dreamlove.pool;

    import com.alibaba.druid.pool.DruidDataSourceFactory;

    import javax.sql.DataSource;
    import java.io.IOException;
    import java.sql.Connection;
    import java.util.Properties;

    public class TestDruid {
    public static void main(String[] args) throws Exception {
    //加载配置文件
    Properties properties = new Properties();
    properties.load(TestDruid.class.getClassLoader().getResourceAsStream("druid.properties"));
    DataSource ds = DruidDataSourceFactory.createDataSource(properties);//创建数据库连接池

    for(int i = 1 ; i<= 11;i++){
    try{
    //获取数据库连接池
    Connection con = ds.getConnection();
    System.out.println(i + "连接池" + con);
    }catch (Exception e){
    e.printStackTrace();
    }
    }
    }
    }

    配置缺省说明
    name配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:”DataSource-” + System.identityHashCode(this)
    jdbcUrl连接数据库的url,不同数据库不一样。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
    username连接数据库的用户名
    password连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/%E4%BD%BF%E7%94%A8ConfigFilter
    driverClassName根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下)
    initialSize0初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
    maxActive8最大连接池数量
    maxIdle8已经不再使用,配置了也没效果
    minIdle最小连接池数量
    maxWait获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
    poolPreparedStatementsfalse是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
    maxOpenPreparedStatements-1要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
    validationQuery用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。
    testOnBorrowtrue申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
    testOnReturnfalse归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
    testWhileIdlefalse建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
    timeBetweenEvictionRunsMillis有两个含义: 1)Destroy线程会检测连接的间隔时间2)testWhileIdle的判断依据,详细看testWhileIdle属性的说明
    numTestsPerEvictionRun不再使用,一个DruidDataSource只支持一个EvictionRun
    minEvictableIdleTimeMillis
    connectionInitSqls物理连接初始化的时候执行的sql
    exceptionSorter根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接
    filters属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
    proxyFilters类型是List,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系

    DAO层

    • 把访问数据库的代码封装起来,这些类称为DAO,相当与是一个数据访问接口,夹在业务逻辑与数据库资源中间

    建立bean包

    • 里面的内容是数据库表中的字段,具有构造方法,和getter和setter为的是返回对应的对象(还可以有toString方法,方便输出查看)

    建立dao包

    • 里面为impl和对应的DAO包

    • DAO包为接口,为应该具有的方法,比如增加,修改,删除

    • impl为实现对应DAO包的方法

      • 除了每一个DAO包的实现外,还需要有一个基础的DAOBaseDAOImpl,用做共同的impl父类,提供基础的方法
    • 使用Dbutils

    commons-dbutils 是 Apache 组织提供的一个开源 JDBC工具类库,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,同时也不会影响程序的性能。

    其中QueryRunner类封装了SQL的执行,是线程安全的。

    (1)可以实现增、删、改、查、批处理、

    (2)考虑了事务处理需要共用Connection。

    (3)该类最主要的就是简单化了SQL查询,它与ResultSetHandler组合在一起使用可以完成大部分的数据库操作,能够大大减少编码量。

    (4)不需要手动关闭连接,runner会自动关闭连接,释放到连接池中

    (1)更新

    public int update(Connection conn, String sql, Object… params) throws SQLException:用来执行一个更新(插入、更新或删除)操作。

    ……

    (2)插入

    publicT insert(Connection conn,String sql,ResultSetHandlerrsh, Object… params) throws SQLException:只支持INSERT语句,其中 rsh - The handler used to create the result object from the ResultSet of auto-generated keys. 返回值: An object generated by the handler.即自动生成的键值

    ….

    (3)批处理

    public int[] batch(Connection conn,String sql,Object[][] params)throws SQLException: INSERT, UPDATE, or DELETE语句

    publicT insertBatch(Connection conn,String sql,ResultSetHandlerrsh,Object[][] params)throws SQLException:只支持INSERT语句

    …..

    (4)使用QueryRunner类实现查询

    public Object query(Connection conn, String sql, ResultSetHandler rsh,Object… params) throws SQLException:执行一个查询操作,在这个查询中,对象数组中的每个元素值被用来作为查询语句的置换参数。该方法会自行处理 PreparedStatement 和 ResultSet 的创建和关闭。

    ….

    ResultSetHandler接口用于处理 java.sql.ResultSet,将数据按要求转换为另一种形式。ResultSetHandler 接口提供了一个单独的方法:Object handle (java.sql.ResultSet rs)该方法的返回值将作为QueryRunner类的query()方法的返回值。

    该接口有如下实现类可以使用:

    • BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
    • BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。
    • ScalarHandler:查询单个值对象
    • MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
    • MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
    • ColumnListHandler:将结果集中某一列的数据存放到List中。
    • KeyedHandler(name):将结果集中的每一行数据都封装到一个Map里,再把这些map再存到一个map里,其key为指定的key。
    • ArrayHandler:把结果集中的第一行数据转成对象数组。
    • ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。

    使用Dbutils组件封装BaseDAOImpl

    • JDBCTools.java
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    package top.dreamlove.tools;

    import com.alibaba.druid.pool.DruidDataSourceFactory;

    import javax.sql.DataSource;
    import java.sql.Connection;
    import java.sql.SQLException;
    import java.util.Properties;

    public class JDBCTools {
    private static DataSource ds;

    static {
    Properties pro = new Properties();
    try{
    pro.load(JDBCTools.class.getClassLoader().getResourceAsStream("druid.properties"));
    ds = DruidDataSourceFactory.createDataSource(pro);

    }catch (Exception e){
    e.printStackTrace();
    }

    }
    private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
    //获取连接
    public static Connection getConnection() throws SQLException {
    Connection connection = threadLocal.get();
    //每一个线程调用这句代码,都会到自己的ThreadLocalMap中,以threadLocal对象为key,找到value
    //如果value为空,说明当前线程还未获取过Connection对象,那么就从连接池中拿一个数据库连接对象给你
    //并且通过threadLocal的set方法把Connection对象放到当前线程ThreadLocalMap中
    if(connection == null){
    connection = ds.getConnection();
    //通过threadLocal的set方法把Connection对象放到当前线程ThreadLocalMap中
    threadLocal.set(connection);
    }
    return connection;
    }
    public static void freeConnection() throws SQLException {
    Connection connection = threadLocal.get();
    if(connection != null){
    connection.setAutoCommit(false);
    threadLocal.remove();
    connection.close();
    }
    }
    }

    • BaseDAOImpl.java
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    package top.dreamlove.dao;

    import org.apache.commons.dbutils.QueryRunner;
    import org.apache.commons.dbutils.handlers.BeanListHandler;
    import top.dreamlove.tools.JDBCTools;

    import java.sql.SQLException;
    import java.util.List;
    public class BaseDAOImpl {
    private QueryRunner queryRunner = new QueryRunner();

    /**
    * 通用的增删改的方法
    * @param sql String 要执行的sql
    * @param args Object... 如果sql中有?,就传入对应个数的?要设置值
    * @return int 执行的结果
    */
    protected int update(String sql,Object... args) {
    try {
    return queryRunner.update(JDBCTools.getConnection(),sql,args);
    } catch (SQLException e) {
    throw new RuntimeException(e);
    }
    }

    /**
    * 查询单个对象的方法
    * @param clazz Class 记录对应的类类型
    * @param sql String 查询语句
    * @param args Object... 如果sql中有?,即根据条件查询,可以设置?的值
    * @param <T> 泛型方法声明的泛型类型
    * @return T 一个对象
    */
    protected <T> T getBean(Class<T> clazz, String sql, Object... args){
    return getList(clazz,sql,args).get(0);
    }

    /**
    * 通用查询多个对象的方法
    * @param clazz Class 记录对应的类类型
    * @param sql String 查询语句
    * @param args Object... 如果sql中有?,即根据条件查询,可以设置?的值
    * @param <T> 泛型方法声明的泛型类型
    * @return List<T> 把多个对象放到了List集合
    */
    protected <T> List<T> getList(Class<T> clazz, String sql, Object... args){
    try {
    return queryRunner.query(JDBCTools.getConnection(),sql,new BeanListHandler<T>(clazz),args);
    } catch (SQLException e) {
    throw new RuntimeException(e);
    }
    }

    }

    Servlet

    • 和服务端进行交互,完成客户的请求沟通

    HelloServlet

    Servlet(Server Applet)作为服务器端的一个组件,它的本意是“服务器端的小程序”。

    • Servlet的实例对象由Servlet容器负责创建;
    • Servlet的方法由容器在特定情况下调用;
    • Servlet容器会在Web应用卸载时销毁Servlet对象的实例。
    • 步骤

      • 新建一个普通类

      • 实现接口Servlet

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      <html>
      <head>
      <meta charset="UTF-8">
      <meta name="viewport"
      content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <title>管理员页面</title>
      </head>
      <body>
      <a href="hello">点击我跳转发送请求</a>
      </body>
      </html>
      • 实现接口中的所有抽象方法
      • 为HelloServlet设置访问路径
        • 注意:web.xml因为有约束文件,所以不可以乱写了,并且还约束了顺序
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      <?xml version="1.0" encoding="UTF-8"?>
      <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
      version="4.0">
      <servlet>
      <servlet-name>abc</servlet-name>
      <servlet-class>top.dreamlove.servlet.HelloServlet</servlet-class>
      </servlet>
      <servlet-mapping>
      <servlet-name>abc</servlet-name>
      <url-pattern>/hello</url-pattern>
      </servlet-mapping>
      </web-app>
    • 注意点

      • 网页必须要在web目录下(不可以放在WEB-INF下),暂时也不可以放置在目录下(后面就可以)
      • web.xml中的url-pattern的值必须要以/开头
      • 请求url中暂时不能以/开头

    找不到servlet解决办法

    作用

    • 接收请求 【解析请求报文中的数据:请求参数】

    • 处理请求 【DAO和数据库交互】

    • 完成响应 【设置响应报文】

    servlet生命周期

    • 怎么知道创建了?创建一个构造器可以知道

    • servlet可以设置为服务器启动的时候就创建其对象

      • 在当前servlet的设置一个标签load-on-startup,值越小越先启动(值为非0整数)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      <?xml version="1.0" encoding="UTF-8"?>
      <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
      version="4.0">
      <servlet>
      <servlet-name>abc</servlet-name>
      <servlet-class>top.dreamlove.servlet.HelloServlet</servlet-class>
      <!--设置自启动-->
      <load-on-startup>1</load-on-startup>
      </servlet>
      <servlet-mapping>
      <servlet-name>abc</servlet-name>
      <url-pattern>/hello</url-pattern>
      </servlet-mapping>
      </web-app>

    init

    • 只在创建对象时候执行一次,以后再接收到请求,就不会执行了

    service

    • 接口被调用的时候执行

    destroy

    • 在web应用被卸载的时候,会被执行改方法

    第二种创建servlet的方法-GenericServlet

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package top.dreamlove.servlet;

    import javax.servlet.GenericServlet;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import java.io.IOException;

    public class MyFirstServlet extends GenericServlet {
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
    System.out.println("这是我第一个Servlet");
    }
    }

    第三种创建servlet-HttpServlet

    • HttpServlet
      • 主要功能是实现service方法,然后对请求进行分发的操作(不同的请求方式调用不同的方法)
      • 如get请求调用doGet方法
      • post请求调用doPost方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    package top.dreamlove.servlet;

    import javax.servlet.*;
    import javax.servlet.http.*;
    import javax.servlet.annotation.WebServlet;
    import java.io.IOException;

    public class LoginServlet extends HttpServlet {

    //get请求
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    System.out.println("你好,世界,我是LoginServlet");
    }

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

    }
    }

    ServletConfig

    • 一个Servlet对象对应唯一的一个ServletConfig配置对象
    • ServletConfig对象如何获得?
      • 在init方法的形参位置
      • ServletConfig是在当前Servlet进行初始化的时候,传递给init方法的
    • 功能
      • 获取Servlet名称:web.xml中配置servlet-name的值
      • 获取全局上下文ServletContext对象
      • 获取Servlet初始化参数
        • 从web.xml设置的初始化参数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public void init(ServletConfig servletConfig) throws ServletException {
    System.out.println("执行了初始化操作");
    //获取Servletname
    String name = servletConfig.getServletName();
    System.out.println("name = " + name);
    //获取ServletContext
    ServletContext context = servletConfig.getServletContext();
    System.out.println("context = " + context);
    //获取web.xml的局部配置参数
    String path = servletConfig.getInitParameter("path");
    System.out.println(path);
    }

    ServletContext

    • 全局上下文对象:一个web项目只有一个ServletContext对象
    • 功能
      • 获取项目的上下文路径

        • getContextPath()
      • 获取虚拟路径所映射的本地真实路径(根据相对路径获取绝对路径)

        • getRealPath()
      • 获取WEB应用程序的全局初始化参数(基本不用)

        • 也就是在web.xml配置文件中的以<content-param>的标签
        • getInitParameter(String key)
      • 作为域对象共享数据

        • 什么是域对象
          • 在一定的作用域范围内共享的对象(A对某一个对象设置了数据,在B中也可以获取到)
          • 设置:setAttribute(String key,Object value)
          • 获取:getAttribute(key)

    getContextPath()-获取项目的上下文路径

    1
    2
     String contextPath = servletContext.getContextPath();// 获取项目的上下文路径
    System.out.println("contextPath = " + contextPath);

    getRealPath()-(根据相对路径获取绝对路径)

    • 不管有没有这个文件或者文件夹,都会返回路径,只是路径而已
    1
    2
    String upload = servletContext.getRealPath("upload");
    System.out.println("upload = " + upload);

    获取WEB应用程序的全局初始化参数

    • 顾名思义,也就是全部Servlet可以访问的初始化参数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    web.xml
    <web-app>
    <!-- Web应用初始化参数 -->
    <context-param>
    <param-name>ParamName</param-name>
    <param-value>ParamValue</param-value>
    </context-param>
    </web-app>

    java代码
    String paramKey = servletContext.getInitParameter("ParamKey");
    System.out.println("paramKey = " + paramKey);

    作为域对象共享数据

    1
    2
    3
    servletContext.setAttribute("globalParams","这是全局的参数");
    Object globalParams = servletContext.getAttribute("globalParams");
    System.out.println("globalParams = " + globalParams);

    HttpServletRequest

    获取请求头的信息

    • request.getHeader(String key)
    • 传入指定的请求头,返回请求头字符串
    1
    2
    3
    4
    5
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    //获取请求头信息
    String header1 = request.getHeader("Referer");
    System.out.println("header1 = " + header1);
    }

    获取URL地址信息

    • request.getRequestContext()//获取上下文路径
    • request.getServerName()
    • request.getServePort()
    • request.getMethod()
    1
    2
    3
    4
    5
    6
    7
    8
    9
    //获取url地址参数
    String path = request.getContextPath();//获取上下文路径
    System.out.println("path = " + path);
    int serverPort = request.getServerPort();//获取请求的端口号
    System.out.println("serverPort = " + serverPort);
    String serverName = request.getServerName();//获取请求的获取主机名
    System.out.println("serverName = " + serverName);
    String scheme = request.getScheme();//获取请求的协议
    System.out.println("scheme = " + scheme);

    获取请求头信息

    获取请求的参数

    • request.getParameter(String key)//根据key值返回一个value
    • request.getParameterValues(String key)//根据key值返回多个value
      • 原来get请求是支持获取多个传参的~
    • request.getParameterMap()//将整个表单的所有数据都放在map集合内
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    String username = request.getParameter("username");
    System.out.println("username = " + username);
    String password = request.getParameter("password");
    System.out.println("password = " + password);
    String gender = request.getParameter("gender");
    System.out.println("gender = " + gender);
    String[] soccerTeams = request.getParameterValues("soccerTeam");
    for(String soccerTeam : soccerTeams){
    System.out.println("soccerTeam = " + soccerTeam);
    }

    使用BeanUtils

    • 要求Map字段要和bean包的相同
    • 需要这几个包

    • 代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    Map<String, String[]> parameterMap = request.getParameterMap();
    Users users = new Users();
    try {
    BeanUtils.populate(users,parameterMap);
    } catch (IllegalAccessException e) {
    throw new RuntimeException(e);
    } catch (InvocationTargetException e) {
    throw new RuntimeException(e);
    }

    请求的转发

    • 转发
      • 交给另外一个Servlet处理
    • 重定向
      • 网页跳转

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    //读取参数
    String name = req.getParameter("name");
    System.out.println("第一个name = " + name);
    String sex = req.getParameter("sex");
    System.out.println("第一个sex = " + sex);

    //添加请求域参数
    req.setAttribute("hobby","吃饭");

    //转发给第二个 需要在web.xml注册
    req.getRequestDispatcher("forwardSecond").forward(req,resp);
    }

    重定向

    1
    2
    3
    4
    5
    protected void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException {
    //1.调用HttpServletResponse对象的sendRedirect()方法
    //2.传入的参数是目标资源的虚拟路径
    response.sendRedirect("index.html");
    }

    response

    1
    2
    3
    4
    //设置HttpServletResponse使用utf-8编码
    resp.setCharacterEncoding("utf-8");
    //通知浏览器使用utf-8编码
    resp.setHeader("Content-Type","text/html;character=utf-8");

    web项目的路径问题

    Thymeleaf

    • 添加这些包

    • 配置全局变量
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
    version="4.0">
    <!-- 在上下文参数中配置视图前缀和视图后缀 -->
    <context-param>
    <param-name>view-prefix</param-name>
    <param-value>/pages</param-value>
    </context-param>
    <context-param>
    <param-name>view-suffix</param-name>
    <param-value>.html</param-value>
    </context-param>
    </web-app>
    • 编写ViewBaseServlet
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    package top.dreamlove;

    import org.thymeleaf.TemplateEngine;
    import org.thymeleaf.context.WebContext;
    import org.thymeleaf.templatemode.TemplateMode;
    import org.thymeleaf.templateresolver.ServletContextTemplateResolver;

    import javax.servlet.ServletContext;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;

    public class ViewBaseServlet extends HttpServlet {
    private TemplateEngine templateEngine;

    @Override
    public void init() throws ServletException {

    // 1.获取ServletContext对象
    ServletContext servletContext = this.getServletContext();

    // 2.创建Thymeleaf解析器对象
    ServletContextTemplateResolver templateResolver = new ServletContextTemplateResolver(servletContext);

    // 3.给解析器对象设置参数
    // ①HTML是默认模式,明确设置是为了代码更容易理解
    templateResolver.setTemplateMode(TemplateMode.HTML);

    // ②设置前缀
    String viewPrefix = servletContext.getInitParameter("view-prefix");

    templateResolver.setPrefix(viewPrefix);

    // ③设置后缀
    String viewSuffix = servletContext.getInitParameter("view-suffix");

    templateResolver.setSuffix(viewSuffix);

    // ④设置缓存过期时间(毫秒)
    templateResolver.setCacheTTLMs(60000L);

    // ⑤设置是否缓存
    templateResolver.setCacheable(true);

    // ⑥设置服务器端编码方式
    templateResolver.setCharacterEncoding("utf-8");

    // 4.创建模板引擎对象
    templateEngine = new TemplateEngine();

    // 5.给模板引擎对象设置模板解析器
    templateEngine.setTemplateResolver(templateResolver);

    }

    protected void processTemplate(String templateName, HttpServletRequest req, HttpServletResponse resp) throws IOException {
    // 1.设置响应体内容类型和字符集
    resp.setContentType("text/html;charset=UTF-8");

    // 2.创建WebContext对象
    WebContext webContext = new WebContext(req, resp, getServletContext());

    // 3.处理模板数据
    templateEngine.process(templateName, webContext, resp.getWriter());
    }
    }

    • html设置为如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <html xmlns:th="http://www.thymeleaf.org">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport"
    content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    </head>
    <body>
    <h2 th:text="${msg}">放服务器传递过来的字段为msg的数据</h2>
    </body>
    </html>
    • 如果出现了org.apache.catalina.LifecycleException: Failed to start component,看看是不是lib包没有放在WEB-INF目录下的问题

    基本语法

    • html页面需要首先设置
    1
    <html xmlns:th="http://www.thymeleaf.org">
    • 拿到URL上下文路径@{/}
    • 设置上下文的值
      • 默认的从请求域中获取
    1
    <p th:属性名="${服务器设置的属性变量}">原始网页内容</p>
    • 设置服务端传递过来参数
      • 默认的从请求域中获取
    1
    <p th:属性名="${服务器设置的属性变量}">原始网页内容</p>
    1
    <a th:href="@{/hello(id=100,name='李白',age=100)}">跳转2</a>

    操作请求域

    Servlet中代码:

    1
    2
    3
    4
    String requestAttrName = "helloRequestAttr";
    String requestAttrValue = "helloRequestAttr-VALUE";

    request.setAttribute(requestAttrName, requestAttrValue);

    Thymeleaf表达式:

    1
    <p th:text="${helloRequestAttr}">request field value</p>

    操作应用域

    Servlet中代码:

    1
    2
    3
    4
    5
    String requestAttrName = "helloRequestAttr";
    String requestAttrValue = "helloRequestAttr-VALUE";
    ServletContext application = request.getServletContext();
    application.setAttribute(requestAttrName,requestAttrValue)

    Thymeleaf表达式:

    1
    <p th:text="${application.helloRequestAttr}">request field value</p>

    获取请求参数

    • 语法
    1
    ${param.参数名}
    • 既可以获取多个,也可以获取单个

    • 单个

    1
    2
    3
    4
    5
    <h1 th:text="${param.id + param.name + param.age}"></h1>

    //访问http://localhost:8080/day03_thymeleaf_war_exploded/params?id=100&name=李白&age=100

    //输出100李白100
    • 多个
    1
    2
    3
    4
    5
    6
    7
    8
    <h1 th:text="${param.hobby}"></h1>

    //访问http://localhost:8080/day03_thymeleaf_war_exploded/params?hobby=吃饭&hobby=睡觉

    //输出 [吃饭, 睡觉]

    如果需要精准获取,就用
    <h1 th:text="${params.hobby[0]}"></h1>

    内置对象

    • 所谓内置对象其实就是在Thymeleaf的表达式中可以直接使用的对象

    基本内置对象

    • #request就是Servlet中的HttpServletRequest对象
    • #response就是Servlet中的HttpServletResponse对象

    1
    2
    3
    <h3>表达式的基本内置对象</h3>
    <p th:text="${#request.getContextPath()}">调用#request对象的getContextPath()方法</p>
    <p th:text="${#request.getAttribute('helloRequestAttr')}">调用#request对象的getAttribute()方法,读取属性域</p>

    公共内置对象

    ognl-分支

    • if
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <table>
    <tr>
    <th>员工编号</th>
    <th>员工姓名</th>
    <th>员工工资</th>
    </tr>
    <tr th:if="${#lists.isEmpty(employeeList)}">
    <td colspan="3">抱歉!没有查询到你搜索的数据!</td>
    </tr>
    <tr th:if="${not #lists.isEmpty(employeeList)}">
    <td colspan="3">有数据!</td>
    </tr>
    <tr th:unless="${#lists.isEmpty(employeeList)}">
    <td colspan="3">有数据!</td>
    </tr>
    </table>
    • switch
    1
    2
    3
    4
    5
    6
    7
    <h3>测试switch</h3>
    <div th:switch="${user.memberLevel}">
    <p th:case="level-1">银牌会员</p>
    <p th:case="level-2">金牌会员</p>
    <p th:case="level-3">白金会员</p>
    <p th:case="level-4">钻石会员</p>
    </div>
    • 迭代(遍历)
    1
    2
    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
    <!--遍历显示请求域中的teacherList-->
    <table border="1" cellspacing="0" width="500">
    <tr>
    <th>编号</th>
    <th>姓名</th>
    </tr>
    <tbody th:if="${#lists.isEmpty(teacherList)}">
    <tr>
    <td colspan="2">教师的集合是空的!!!</td>
    </tr>
    </tbody>

    <!--
    集合不为空,遍历展示数据
    -->
    <tbody th:unless="${#lists.isEmpty(teacherList)}">
    <!--
    使用th:each遍历
    用法:
    1. th:each写在什么标签上? 每次遍历出来一条数据就要添加一个什么标签,那么th:each就写在这个标签上
    2. th:each的语法 th:each="遍历出来的数据,数据的状态 : 要遍历的数据"
    3. status表示遍历的状态,它包含如下属性:
    index 遍历出来的每一个元素的下标
    count 遍历出来的每一个元素的计数

    -->
    <tr th:each="teacher,status : ${teacherList}">
    <td th:text="${status.count}">这里显示编号</td>
    <td th:text="${teacher.teacherName}">这里显示老师的名字</td>
    </tr>
    </tbody>
    </table>

    小练习

    • 反射
    1
    2
    3
    4
    5
    Class c = this.getClass();

    Method method = c.getDeclaredMethod('要调用的方法',Class参数1,Class参数2,Class参数3,....);
    method.setAccessible(true);//暴力访问
    method.invoke(由谁调用:this,参数1,参数2)
    • 解决乱码问题
    1
    request.setCharacterEncoding("uft-8")

    Cookie和Session

    • Cookie(客户端的会话技术)
    • Session(服务端的存储技术)

    如何将数据保存到Cookie中

    • 一旦cookie被保存到客户端,在以后的每次请求中都会带着所有的cookie
    • 此时添加cookie被称为瞬时cookie,浏览器关闭cookie就消失
    1
    2
    3
    4
    5
    6
    7
    8
    //添加Cookie
    Cookie testCooke = new Cookie("adminKey","adminValue");
    Cookie testCooke1 = new Cookie("adminKey1","adminValue3");
    Cookie testCooke2 = new Cookie("adminKey2","adminValue4");
    //设置Cookie
    resp.addCookie(testCooke1);
    resp.addCookie(testCooke2);
    resp.addCookie(testCooke);

    如何将数据从Cookie中取出来

    1
    2
    //获取Cookie
    Cookie[] reqCookies = req.getCookies();

    Cookie中的数据的有效时间

    • 在添加到响应之前,可以设置有效时间
    1
    2
    3
    4
    //添加Cookie
    Cookie testCooke = new Cookie("adminKey","adminValue");
    //设置Cookie时间
    testCooke.setMaxAge(60);//单位为秒

    设置Cookie的携带条件

    • 比如为user设置了20个Cookie,为book设置了20个Cookie,我们不希望访问任意的时候都携带上,就可以设置携带条件
    • 比如只希望xxxx/user下的URI可以访问,就需要设置下方数据
    1
    testCooke.setPath(req.getContextPath() + "/user");

    Session

    • 数据存储在服务器端
    • 服务器端的会话从第一次获得HttpSession对象开始的,直到HttpSession对象销毁结束
    • 服务器会为每一个客户端创建对应的Session
    • 服务器是如何办到客户端和session对应关系?
      • 是通过cookie办到的!session是依赖于cookie
      • 当客户端第一次访问服务器,调用getSession(),新建一个session对象,并且设置一个cookie给浏览器
      • 当客户端第二次访问服务器,调用getSession(),就去获得请求中的jsessionid这个Cookie,通过Cookie判断
    • 会话什么时候结束?
      • 客户端关闭(jsessionid这个Cookie消息)
      • 强制失效
        • invalidate
      • 自动失效(达到最大空闲时间)
        • 默认是半小时,可以通过setMaxInactiveInterval
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //获取session对象
    HttpSession session = req.getSession();
    //设置
    session.setAttribute("sessionMsg","value");
    //获取
    Object sessionMsg = session.getAttribute("sessionMsg");
    //移除
    session.removeAttribute("sessionMsg");
    //销毁session对象(强制失效)
    session.invalidate()
    //设置空闲失效 60秒
    session.setMaxInactiveInterval(60);

    后台响应JSON

    • 使用JSON数据作为响应数据(JSON格式的字符串)
    • 可以借助gson帮助我们将JAVA对象转换为json字符串
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    Gson gson = new Gson();
    Books books = new Books("a","b",20.0,10,30,"f");
    String s1 = gson.toJson(books);
    System.out.println("s1 = " + s1);// { title:'xxx',author:'xxx' }

    //将Map集合作为响应数据
    Map<String,Books> map = new HashMap<>();
    map.put("one1",new Books("a","b",20.0,10,30,"f"));
    map.put("one2",new Books("a","b",20.0,10,30,"f"));
    map.put("one3",new Books("a","b",20.0,10,30,"f"));
    String s2 = gson.toJson(map);
    System.out.println("s2 = " + s2);// { one1:{},one2:{},one3:{}, }

    //List集合
    List<Books> booksList = new ArrayList<>();
    booksList.add(new Books("a","b",20.0,10,30,"f"));
    booksList.add(new Books("a","b",20.0,10,30,"f"));
    booksList.add(new Books("a","b",20.0,10,30,"f"));
    String s3 = gson.toJson(booksList);
    System.out.println("s3 = " + s3);//[{ title:'xxx',author:'xxx' },{},{},]
    PrintWriter writer = resp.getWriter();
    writer.write(s2);

    CommonResult

    • 设置异步请求响应结果的格式

    Maven

    Maven之Helloworld

    • Maven工程目录结构约束
    • 项目名
      • src(书写源代码)
        • main【书写主程序代码】
          • java【书写java源代码】
          • resources【书写配置文件代码】
        • test(书写测试代码)
          • java(书写测试代码)
      • pom.xml(书写Maven配置)

    Maven的坐标【重要】

    • 作用:使用坐标引入jar包

      • 一对多的关系
    • 坐标由g-a-v组成

      [1]groupId:公司或组织的域名倒序+当前项目名称

      [2]artifactId:当前项目的模块名称

      [3]version:当前模块的版本

    • 注意

      • g-a-v:本地仓库jar包位置
      • a-v:jar包全名

    坐标应用

    Maven的依赖管理

    • 依赖语法<scope>

      • compile
        • 【默认值】:在main、test、Tomcat【服务器】下均有效。
      • test
        • 只能在test目录下有效
          • 比如junit
      1
      2
      3
      4
      5
      6
      7
      8
      <dependencies>
      <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.2</version>
      <scope>test</scope>
      </dependency>
      </dependencies>
      • provided
        • 在main、test下均有效,Tomcat【服务器】无效。
        • servlet-api(因为Tomcat服务器有了,如果再提供就冲突了)
    • 依赖传递性

      • 路径最短者有先【就近原则】

      • 先声明者优先

      • 注意:Maven可以自动解决jar包之间的依赖问题

    • 统一管理版本号

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <properties>
    <spring-version>5.3.17</spring-version>
    </properties>
    <dependencies>
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>${spring-version}</version>
    </dependency>
    </dependencies>

    Maven继承和聚合

    继承

    • 为什么需要继承?

      • 如果子工程大部分都共同使用jar包,可以提取父工程中,使用继承原理在子工程中使用
      • 父工程打包方式,必须是pom方式
    • 第一种:在父工程中的pom.xml中导入jar包,在子工程中统一使用。【不足:所有子工程强制引入父工程jar包

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <packaging>pom</packaging>
    <dependencies>
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
    </dependency>
    </dependencies>
    • 第二种:在父工程中导入jar包【pom.xml】
      • 注意标签使用dependencyManagement
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <packaging>pom</packaging>
    <dependencyManagement>
    <dependencies>
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
    </dependency>
    </dependencies>
    </dependencyManagement>
    • 在子工程引入父工程的相关jar包<relativePath>../pom.xml</relativePath>
      • 注意:在子工程中,不能指定版本号
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <parent>
    <artifactId>maven_demo</artifactId>
    <groupId>com.atguigu</groupId>
    <version>1.0-SNAPSHOT</version>
    <relativePath>../pom.xml</relativePath>
    </parent>
    <dependencies>
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    </dependency>
    </dependencies>

    聚合

    为什么使用Maven的聚合

    • 优势:只要将子工程聚合到父工程中,就可以实现效果:安装或清除父工程时,子工程会进行同步操作。
    • 注意:Maven会按照依赖顺序自动安装子工程
    1
    2
    3
    4
    5
    <modules>
    <module>maven_helloworld</module>
    <module>HelloFriend</module>
    <module>MakeFriend</module>
    </modules>

    Maven在IDEA创建

    • 目录结构如下

    • 设置项目结构

    • 修改为

    • 修改这个

    • 设置

    • 添加Tomcat

    Mybatis

    • Mybatis是一个半自动化持久化层ORM框架
    • ORM:Object Relational Mapping [对象 关系 映射]
      • 将JAVA中的对象与数据库中的表建立关系
    • Mybatis与Hibernate对比

      • Mybatis是一个半自动化【需要手写SQL】
      • Hibernate是全自动化【无需手写SQL】
    • Mybatis与JDBC对比

      • JDBC中的SQL与Java代码耦合度高
      • Mybatis将SQL与Java代码解耦
    • Java POJO(Plain Old Java Objects,普通老式 Java 对象)

      • JavaBean 等同于 POJO

    搭建Mybatis框架

    1.导入jar包

    1
    2
    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
    35
    36
    37
    38
    39
    40
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
    <groupId>top.dreamlove</groupId>
    <artifactId>day03_maven</artifactId>
    <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>qiuye_01_mybatis</artifactId>

    <properties>
    <maven.compiler.source>8</maven.compiler.source>
    <maven.compiler.target>8</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
    <!--mysql的驱动包-->
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.26</version>
    </dependency>
    <!--Mybatis的jar包-->
    <dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.6</version>
    </dependency>
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>3.8.2</version>
    <scope>test</scope>
    </dependency>
    </dependencies>
    </project>

    2.编写核心配置文件【mybatis-config.xml】

    • 位置:resources目标下

    • 名称:推荐使用mybatis-config.xml

    • 示例代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">

    <configuration>
    <environments default="development">
    <environment id="development">
    <transactionManager type="JDBC"/>
    <dataSource type="POOLED">
    <!-- mysql8版本-->
    <!-- <property name="driver" value="com.mysql.cj.jdbc.Driver"/>-->
    <!-- <property name="url" value="jdbc:mysql://localhost:3306/db220106?serverTimezone=UTC"/>-->
    <!-- mysql5版本-->
    <property name="driver" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/db220106"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
    </dataSource>
    </environment>
    </environments>
    <!-- 设置映射文件路径-->
    <mappers>
    <mapper resource="mapper/EmployeeMapper.xml"/>
    </mappers>
    </configuration>

    3.书写相关接口及映射文件

    • 映射文件位置:resources/mapper

    • 映射文件名称:XXXMapper.xml

    • 映射文件作用:主要作用为Mapper接口书写Sql语句

      • 映射文件名与接口名一致
      • 映射文件namespace与接口全类名一致
      • 映射文件SQL的Id与接口的方法名一致
    • 1.书写pojo(也就是bean层)

    • 2.书写mapper层(接口文件)

    • 3.resources下书写xml映射文件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!--namespace对应书写的mapper下的JAVA文件-->
    <mapper namespace="top.dreamlove.mapper.EmployeeMapper">
    <!--
    id: 方法名要和接口对应的方法相同
    resultType: 返回类型,和pojo相同
    -->

    <select id="selectEmpById" resultType="top.dreamlove.pojo.Employee">
    select id,
    last_name lastName,
    email,
    salary
    from
    tbl_employee
    where
    id = #{id}
    </select>
    </mapper>
    • 4.测试[sqlSession]
    1
    2
    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
    35
    36
    37
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    import org.junit.Test;
    import org.apache.ibatis.io.Resources;
    import top.dreamlove.mapper.EmployeeMapper;
    import top.dreamlove.pojo.Employee;

    import java.io.IOException;
    import java.io.InputStream;

    public class TestMybatis {

    @Test
    public void testMyBatis() {
    String resource = "mybatis-config.xml";
    try {
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    //通过sqlSessionFactory获取sqlSession对象
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //获取EmployeeMapper的代理对象
    EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);

    System.out.println("employeeMapper = " + employeeMapper);

    //执行
    Employee employee = employeeMapper.selectEmpById(1);
    System.out.println("employee = " + employee.toString());

    } catch (IOException e) {
    throw new RuntimeException(e);
    }

    }
    }

    4.添加log4j日志jar包

    • 添加jar包
    1
    2
    3
    4
    5
    6
    7
    <!-- https://mvnrepository.com/artifact/log4j/log4j -->
    <dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
    </dependency>

    • 配置文件resources
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">

    <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

    <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
    <param name="Encoding" value="UTF-8" />
    <layout class="org.apache.log4j.PatternLayout">
    <param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n" />
    </layout>
    </appender>
    <logger name="java.sql">
    <level value="debug" />
    </logger>
    <logger name="org.apache.ibatis">
    <level value="info" />
    </logger>
    <root>
    <level value="debug" />
    <appender-ref ref="STDOUT" />
    </root>
    </log4j:configuration>

    Mybatis配置

    properties

    • 可以通过resources来引入外部文件(基于类路径)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
    PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-config.dtd">

    <configuration>

    <properties resource="db.properties"></properties>

    <environments default="development">
    <environment id="development">
    <transactionManager type="JDBC"/>

    <dataSource type="POOLED">
    <property name="driver" value="${db.driver}"/>
    <property name="url" value="${db.url}"/>
    <property name="username" value="${db.username}"/>
    <property name="password" value="${db.password}"/>
    </dataSource>
    </environment>
    </environments>
    <!-- 设置映射文件路径-->
    <mappers>
    <mapper resource="mapper/EmployeeMapper.xml"/>
    </mappers>
    </configuration>
    • 也可以通过url来引入(基于系统路径,盘符啥的)
    • 也可以直接书写
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <properties resource="org/mybatis/example/config.properties">
    <property name="username" value="dev_user"/>
    <property name="password" value="F2Fa3!33TYyg"/>
    </properties>

    使用
    <dataSource type="POOLED">
    <property name="driver" value="${driver}"/>
    <property name="url" value="${url}"/>
    <property name="username" value="${username}"/>
    <property name="password" value="${password}"/>
    </dataSource>

    setting

    • 作用:这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。

    • mapUnderscoreToCamelCase属性:是否开启驼峰命名自动映射,默认值false,如设置true会自动将

      字段a_col与aCol属性自动映射

      • 注意:只能将字母相同的字段与属性自动映射
      1
      2
      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
      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE configuration
      PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
      "http://mybatis.org/dtd/mybatis-3-config.dtd">

      <configuration>
      <!--加载属性文件-->
      <properties resource="db.properties"></properties>

      <settings>
      <!--开启驼峰命名(数据库字段自动映射为驼峰命名)-->
      <setting name="mapUnderscoreToCamelCase" value="true"/>
      </settings>

      <environments default="development">
      <environment id="development">
      <transactionManager type="JDBC"/>

      <dataSource type="POOLED">
      <property name="driver" value="${db.driver}"/>
      <property name="url" value="${db.url}"/>
      <property name="username" value="${db.username}"/>
      <property name="password" value="${db.password}"/>
      </dataSource>
      </environment>
      </environments>
      <!-- 设置映射文件路径-->
      <mappers>
      <mapper resource="mapper/EmployeeMapper.xml"/>
      </mappers>
      </configuration>

    typeAliases

    • 作用:类型别名可为 Java 类型设置一个缩写名字。

    • 语法及特点

    1
    2
    3
    4
    5
    6
    7
    8
    <typeAliases>
    <!-- 为指定类型定义别名-->
    <!-- <typeAlias type="com.atguigu.mybatis.pojo.Employee" alias="employee"></typeAlias>-->
    <!-- 为指定包下所有的类定义别名
    默认将改包下的每一个类名作为别名,不区分大小写【推荐使用小写字母】
    -->
    <package name="com.atguigu.mybatis.pojo"/>
    </typeAliases>

    environments

    • 作用:设置数据库连接环境

    • 示例代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <!--    设置数据库连接环境-->
    <environments default="development">
    <environment id="development">
    <transactionManager type="JDBC"/>
    <dataSource type="POOLED">
    <!-- mysql8版本-->
    <!-- <property name="driver" value="com.mysql.cj.jdbc.Driver"/>-->
    <!-- <property name="url" value="jdbc:mysql://localhost:3306/db220106?serverTimezone=UTC"/>-->
    <!-- mysql5版本-->
    <property name="driver" value="${db.driver}"/>
    <property name="url" value="${db.url}"/>
    <property name="username" value="${db.username}"/>
    <property name="password" value="${db.password}"/>
    </dataSource>
    </environment>
    </environments>

    mappers子标签

    • 作用:设置映射文件路径

    • 示例代码

    1
    2
    3
    4
    5
    6
    <!--    设置映射文件路径-->
    <mappers>
    <mapper resource="mapper/EmployeeMapper.xml"/>
    <!-- 要求:接口的包名与映射文件的包名需要一致-->
    <!-- <package name="com.atguigu.mybatis.mapper"/>-->
    </mappers>
    • 注意:核心配置中的子标签,是有顺序要求的。

    Mybatis映射

    映射文件概述

    • MyBatis 的真正强大在于它的语句映射,这是它的魔力所在。
    • 如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。

    映射文件根标签

    • mapper标签
    • mapper中的namespace要求与接口的全类名一致

    映射文件子标签

    子标签共有9个,注意学习其中8大子标签

    • insert标签:定义添加SQL
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!--namespace对应书写的mapper下的JAVA文件-->
    <mapper namespace="top.dreamlove.mapper.EmployeeMapper">
    <!--
    id: 方法名要和接口对应的方法相同
    resultType: 返回类型,和pojo相同
    -->

    <insert id="addEmployee">
    insert into tbl_employee(last_name,email,salary) values(#{lastName},#{email},#{salary})
    </insert>

    </mapper>
    • delete标签:定义删除SQL
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!--namespace对应书写的mapper下的JAVA文件-->
    <mapper namespace="top.dreamlove.mapper.EmployeeMapper">
    <!--
    id: 方法名要和接口对应的方法相同
    resultType: 返回类型,和pojo相同
    -->

    <delete id="deleteEmployee">
    delete from tbl_employee where id = #{empId}
    </delete>
    </mapper>
    • update标签:定义修改SQL
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!--namespace对应书写的mapper下的JAVA文件-->
    <mapper namespace="top.dreamlove.mapper.EmployeeMapper">
    <!--
    id: 方法名要和接口对应的方法相同
    resultType: 返回类型,和pojo相同
    -->

    <!--更新员工-->
    <update id="updateEmployee">
    update tbl_employee set last_name=#{lastName},email=#{email},salary=#{salary} where id = #{id};
    </update>
    </mapper>
    • select标签:定义查询SQL
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!--namespace对应书写的mapper下的JAVA文件-->
    <mapper namespace="top.dreamlove.mapper.EmployeeMapper">
    <!--
    id: 方法名要和接口对应的方法相同
    resultType: 返回类型,和pojo相同
    -->
    <!--查询所有员工,当然,你也可以取一个别名在resultType-->
    <select id="selectAllEmployee" resultType="top.dreamlove.pojo.Employee">
    select id,last_name,email,salary from tbl_employee
    </select>
    </mapper>
    • sql标签:定义可重用的SQL语句块
    • cache标签:设置当前命名空间的缓存配置
    • cache-ref标签:设置其他命名空间的缓存配置
    • resultMap标签:描述如何从数据库结果集中加载对象
      • resultType解决不了的问题,交个resultMap。

    获取主键自增数据

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!--namespace对应书写的mapper下的JAVA文件-->
    <mapper namespace="top.dreamlove.mapper.EmployeeMapper">
    <!--
    id: 方法名要和接口对应的方法相同
    resultType: 返回类型,和pojo相同
    -->

    <!--添加员工-->
    <insert id="addEmployee" useGeneratedKeys="true" keyProperty="id" >
    insert into tbl_employee(last_name,email,salary) values(#{lastName},#{email},#{salary})
    </insert>

    </mapper>
    1
    2
    3
    4
    Employee employee1 = new Employee(null,"李白","zmqdream@qq.com",1000.0);
    employeeMapper.addEmployee(employee1);
    //获取返回的自增主键
    System.out.println(employee1.getId());

    获取数据库受影响行数

    直接将接口中方法的返回值设置为int或boolean即可

    • int:代表受影响行数
    • boolean
      • true:表示对数据库有影响
      • false:表示对数据库无影响

    Mybatis参数传递问题

    单个参数 单个普通参数

    • 可以任意使用:参数数据类型、参数名称不用考虑
      • 当然,不建议这样,接口的参数名称是什么名称,一般映射当中就要什么名称
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Employee selectEmpById(int empId);

    <!--根据ID查询用户信息-->
    <select id="selectEmpById" resultType="top.dreamlove.pojo.Employee">
    select id,
    last_name,
    email,
    salary
    from
    tbl_employee
    where
    id = #{abcdefff}
    </select>

    多个普通参数

    • Mybatis底层封装Map结构,封装key为param1、param2….【支持:arg0、arg1、…】
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!--namespace对应书写的mapper下的JAVA文件-->
    <mapper namespace="top.dreamlove.mapper.EmployeeMapper">
    <!--
    id: 方法名要和接口对应的方法相同
    resultType: 返回类型,和pojo相同
    -->


    <!-- 根据员工名称,邮箱查询员工信息 -->
    <select id="selectEmployName" resultType="top.dreamlove.pojo.Employee">
    select * from tbl_employee where last_name = #{param1} and email = #{param2}
    </select>
    </mapper>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package top.dreamlove.mapper;

    import top.dreamlove.pojo.Employee;

    import java.util.List;

    public interface EmployeeMapper {


    /**
    * 员工名称,邮箱查询员工信息
    */
    Employee selectEmployName(String name,String email);

    }

    POJO参数

    • Mybatis支持POJO【JavaBean】入参,参数key是POJO中属性
    • 也就是这个对象里面的属性可以直接拿来用(有点类似于前端的自动帮你解构赋值了)

    多个普通参数(命名参数)

    • 支持param,但是不支持arg0
    1
    2
    3
    4
    5
    6
    /**
    * 通过员工姓名及薪资查询员工信息【命名参数】
    * @return
    */
    public List<Employee> selectEmpByNamed(@Param("lName")String lastName,
    @Param("salary") double salary);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <select id="selectEmpByNamed" resultType="employee">
    SELECT
    id,
    last_name,
    email,
    salary
    FROM
    tbl_employee
    WHERE
    last_name=#{param1}
    AND
    salary=#{param2}
    </select>

    或者不适用param1
    <select id="selectEmployName" resultType="top.dreamlove.pojo.Employee">
    select * from tbl_employee where last_name = #{lName} and email = #{salary}
    </select>

    Map参数

    • Mybatis支持直接Map入参,map的key=参数key
    1
    Employee selectEmployName(Map<String,Object> map);
    1
    2
    3
    Map<String,Object> map = new HashMap<>();
    map.put("Name","李白");
    map.put("Email","zmqdream@qq.com");
    1
    2
    3
    <select id="selectEmployName" resultType="top.dreamlove.pojo.Employee">
    select * from tbl_employee where last_name = #{Name} and email = #{Email}
    </select>

    #与$使用场景

    • #使用场景,sql占位符位置均可以使用#

    • $使用场景,#解决不了的参数传递问题,均可以交给$处理【如:form 动态化表名】

    1
    2
    3
    4
    /**
    * 测试$使用场景
    */
    public List<Employee> selectEmpByDynamitTable(@Param("tblName") String tblName);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <select id="selectEmpByDynamitTable" resultType="employee">
    SELECT
    id,
    last_name,
    email,
    salary
    FROM
    ${tblName}
    </select>

    select查询返回值情况

    • 查询单行数据返回Map集合
      • resultType写一个map就可以,因为底层有别名的存在

      • Map<String key,Object value>

        • 字段作为Map的key,查询结果作为Map的Value
    1
    2
    3
    4
    5
    /**
    * 查询单行数据返回Map集合
    * @return
    */
    public Map<String,Object> selectEmpReturnMap(int empId);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <!--    查询单行数据返回Map集合-->
    <select id="selectEmpReturnMap" resultType="map">
    SELECT
    id,
    last_name,
    email,
    salary
    FROM
    tbl_employee
    WHERE
    id=#{empId}
    </select>
    • 查询多行数据返回Map集合
      • Map<Integer key,Employee value>

        • 对象的id作为key
        • 对象作为value
      • @MapKey("id")id为Employee唯一标识

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /**
    * 查询多行数据返回Map
    * Map<Integer,Object>
    * Map<Integer,Employee>
    * 对象Id作为:key
    * 对象作为:value
    * @return
    */
    @MapKey("id")
    public Map<Integer,Employee> selectEmpsReturnMap();
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <select id="selectEmpsReturnMap" resultType="map">
    SELECT
    id,
    last_name,
    email,
    salary
    FROM
    tbl_employee
    </select>

    Mybatis中自动映射与自定义映射

    自动映射【resultType】

    自定义映射【resultMap】

    8.1 自动映射与自定义映射

    • 自动映射【resultType】:指的是自动将表中的字段与类中的属性进行关联映射
      • 自动映射解决不了两类问题
        • 多表连接查询时,需要返回多张表的结果集
        • 单表查询时,不支持驼峰式自动映射【不想为字段定义别名】
    • 自定义映射【resultMap】:自动映射解决不了问题,交给自定义映射
    • 注意:resultType与resultMap只能同时使用一个

    8.2 自定义映射-级联映射

    1
    2
    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
    <!--    自定义映射 【员工与部门关系】-->
    <resultMap id="empAndDeptResultMap" type="employee">
    <!-- 定义主键字段与属性关联关系 -->
    <id column="id" property="id"></id>
    <!-- 定义非主键字段与属性关联关系-->
    <result column="last_name" property="lastName"></result>
    <result column="email" property="email"></result>
    <result column="salary" property="salary"></result>
    <!-- 为员工中所属部门,自定义关联关系-->
    <result column="dept_id" property="dept.deptId"></result>
    <result column="dept_name" property="dept.deptName"></result>
    </resultMap>
    <select id="selectEmpAndDeptByEmpId" resultMap="empAndDeptResultMap">
    SELECT
    e.`id`,
    e.`email`,
    e.`last_name`,
    e.`salary`,
    d.`dept_id`,
    d.`dept_name`
    FROM
    tbl_employee e,
    tbl_dept d
    WHERE
    e.`dept_id` = d.`dept_id`
    AND
    e.`id` = #{empId}
    </select>

    8.3 自定义映射-association映射

    • 特点:解决一对一映射关系【一对多】

    • 示例代码

      • <!--    自定义映射 【员工与部门关系】-->
        +<resultMap id="empAndDeptResultMapAssociation" type="employee">
        +    <!--  定义主键字段与属性关联关系 -->
        +    <id column="id" property="id"></id>
        +    <!--  定义非主键字段与属性关联关系-->
        +    <result column="last_name" property="lastName"></result>
        +    <result column="email" property="email"></result>
        +    <result column="salary" property="salary"></result>
        +    <!--        为员工中所属部门,自定义关联关系-->
        +    <association property="dept"
        +                javaType="com.atguigu.mybatis.pojo.Dept">
        +        <id column="dept_id" property="deptId"></id>
        +        <result column="dept_name" property="deptName"></result>
        +    </association>
        +</resultMap>
        +
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10

        #### 8.4 自定义映射-collection映射

        - 示例代码

        ```java
        /**
        * 通过部门id获取部门信息,及部门所属员工信息
        */
        public Dept selectDeptAndEmpByDeptId(int deptId);
        +
      1
      2
      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
      <resultMap id="deptAndempResultMap" type="dept">
      <id property="deptId" column="dept_id"></id>
      <result property="deptName" column="dept_name"></result>
      <collection property="empList"
      ofType="com.atguigu.mybatis.pojo.Employee">
      <id column="id" property="id"></id>
      <result column="last_name" property="lastName"></result>
      <result column="email" property="email"></result>
      <result column="salary" property="salary"></result>
      </collection>
      </resultMap>
      <select id="selectDeptAndEmpByDeptId" resultMap="deptAndempResultMap">
      SELECT
      e.`id`,
      e.`email`,
      e.`last_name`,
      e.`salary`,
      d.`dept_id`,
      d.`dept_name`
      FROM
      tbl_employee e,
      tbl_dept d
      WHERE
      e.`dept_id` = d.`dept_id`
      AND
      d.dept_id = #{deptId}
      </select>

    8.5 ResultMap相关标签及属性

    • resultMap标签:自定义映射标签

      • id属性:定义唯一标识
      • type属性:设置映射类型
    • resultMap子标签

      • id标签:定义主键字段与属性关联关系
      • result标签:定义非主键字段与属性关联关系
        • column属性:定义表中字段名称
        • property属性:定义类中属性名称
      • association标签:定义一对一的关联关系
        • property:定义关联关系属性
        • javaType:定义关联关系属性的类型
        • select:设置分步查询SQL全路径
        • colunm:设置分步查询SQL中需要参数
      • collection标签:定义一对多的关联关系
        • property:定义一对一关联关系属性
        • ofType:定义一对一关联关系属性类型

    8.6 Mybatis中分步查询

    • 为什么使用分步查询【分步查询优势】?
      • 将多表连接查询,改为【分步单表查询】,从而提高程序运行效率

    MBG

    MyBatisGenerator:简称MBG

    是一个专门为MyBatis框架使用者定制的代码生成器

    可以快速的根据表生成对应的映射文件,接口,以及bean类。

    只可以生成单表CRUD,但是表连接、存储过程等这些复杂sgl的定义需要我们手工编写

    Mybatis中分页插件

    • PageHelper

    Spring

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public void test1(){
    //使用Spring之前
    // Student student = new Student();

    //使用Spring之后
    ApplicationContext iocObj = new ClassPathXmlApplicationContext("applicationContext.xml");
    Student student = (Student) iocObj.getBean("qiuyeStu");
    System.out.println("student = " + student);
    //输出student = Student{id=100, name='libai'}
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--对象装配到容器中,之后再用-->
    <bean id="qiuyeStu" class="top.dreamlove.pojo.Student">
    <property name="id" value="100"/>
    <property name="name" value="libai"/>
    </bean>

    </beans>

    Spring中getBean()三种方式

    • getBean(String beanId):通过beanId获取对象

      • 不足:需要强制类型转换,不灵活
    • getBean(Class clazz):通过Class方式获取对象

      • 不足:容器中有多个相同类型bean的时候,会报如下错误:

        expected single matching bean but found 2: stuZhenzhong,stuZhouxu

    • getBean(String beanId,Clazz clazz):通过beanId和Class获取对象

      • 推荐使用

    Spring依赖注入数值问题

    • 比如我想取名叫<西游记>,但是不允许这样写
    1
    2
    3
    <bean id="qiuyeStu1" class="top.dreamlove.pojo.Student">
    <property name="name" value="<西游记>"/>
    </bean>

    • 所以就需要写<![CDATA[]]>
    1
    2
    3
    4
    5
    <bean id="qiuyeStu1" class="top.dreamlove.pojo.Student">
    <property name="name">
    <value> <![CDATA[<红楼梦>]]> </value>
    </property>
    </bean>

    • 语法:<![CDATA[]]>
    • 作用:在xml中定义特殊字符时,使用CDATA区

    SpringBoot

    HelloWorld

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package top.dreamlove.boot;

    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;

    @SpringBootApplication
    public class MainController {
    public static void main(String[] args) {
    SpringApplication.run(MainController.class,args);
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package top.dreamlove.boot.controller;

    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;

    @RestController
    public class HelloController {
    @RequestMapping("/hello")
    public String handle01(){
    return "hello,wordl";
    }
    }
    1
    2
    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
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>myproject</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.18</version>
    </parent>

    <!-- Additional lines to be added here... -->
    <dependencies>
    <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    </dependencies>
    <build>
    <plugins>
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
    </plugins>
    </build>

    </project>

    • 可以修改默认版本号
    1
    2
    3
    4
    5
    1、查看spring-boot-dependencies里面规定当前依赖的版本 用的 key。
    2、在当前项目里面重写配置
    <properties>
    <mysql.version>5.1.43</mysql.version>
    </properties>
    • 默认的包结构
    • 主程序所在包及其下面的所有子包里面的组件都会被默认扫描进来
    • 无需以前的包扫描配置

    @Configuration

    @Conditional

    • 可以对类使用,当满足条件,则类里面的所有会被注入,
    • 也可以对某一个方法使用,当满足,这个方法才会被注入

    @ImportResource

    • 原生配置文件引入

    @PathVariable

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @RestController
    public class ParamController {

    @GetMapping("/car/{id}/owner/{userName}")
    public Map<String,Object> getInfo(@PathVariable("id") String id,
    @PathVariable("userName") String username
    ){
    Map<String,Object> map = new HashMap<>();
    map.put("id",id);
    map.put("userName",username);
    return map;
    }
    }

    1
    2
    3
    4
    {
    "id": "3",
    "userName": "libai"
    }

    @RequestParam

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @RestController
    public class ParamController {

    @GetMapping("/car/{id}/owner/{userName}")
    public Map<String,Object> getInfo(@PathVariable("id") String id,
    @PathVariable("userName") String username,
    @RequestParam("age") Integer age,
    @RequestParam("hobby") List<String> hobby
    ){
    Map<String,Object> map = new HashMap<>();
    map.put("id",id);
    map.put("userName",username);
    map.put("age",age);
    map.put("hobby",hobby);
    return map;
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    {
    "id": "3",
    "userName": "libai",
    "age": 18,
    "hobby": [
    "吃饭",
    "睡觉"
    ]
    }

    @RequestHeader

    • 获取请求头数据
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    @RestController
    public class ParamController {

    @GetMapping("/car/{id}/owner/{userName}")
    public Map<String,Object> getInfo(@PathVariable("id") String id,
    @PathVariable("userName") String username,
    @RequestParam("age") Integer age,
    @RequestParam("hobby") List<String> hobby,
    @RequestHeader("User-Agent") String agent,
    @RequestHeader Map<String,String> header
    ){
    Map<String,Object> map = new HashMap<>();
    map.put("id",id);
    map.put("userName",username);
    map.put("age",age);
    map.put("hobby",hobby);
    map.put("agent",agent);
    map.put("所有请求头",header);
    return map;
    }
    }

    1
    2
    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
    {
    "agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
    "所有请求头": {
    "host": "localhost:9909",
    "connection": "keep-alive",
    "cache-control": "max-age=0",
    "sec-ch-ua": "\"Google Chrome\";v=\"123\", \"Not:A-Brand\";v=\"8\", \"Chromium\";v=\"123\"",
    "sec-ch-ua-mobile": "?0",
    "sec-ch-ua-platform": "\"Windows\"",
    "upgrade-insecure-requests": "1",
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
    "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
    "sec-fetch-site": "none",
    "sec-fetch-mode": "navigate",
    "sec-fetch-user": "?1",
    "sec-fetch-dest": "document",
    "accept-encoding": "gzip, deflate, br, zstd",
    "accept-language": "zh-CN,zh;q=0.9,en;q=0.8",
    "cookie": "Idea-8296f2b3=4f2c3186-368f-495a-91d3-c900f7982eb4"
    },
    "id": "3",
    "userName": "libai",
    "age": 18,
    "hobby": [
    "吃饭",
    "睡觉"
    ]
    }

    @CookieValue

    • 获取Cookie数据
    1
    2
    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

    @RestController
    public class ParamController {

    @GetMapping("/car/{id}/owner/{userName}")
    public Map<String,Object> getInfo(@PathVariable("id") String id,
    @PathVariable("userName") String username,
    @RequestParam("age") Integer age,
    @RequestParam("hobby") List<String> hobby,
    @RequestHeader("User-Agent") String agent,
    @RequestHeader Map<String,String> header,
    @CookieValue("Idea-8296f2b3") String Cookie1,
    @CookieValue("Idea-8296f2b3") Cookie cookie2
    ){
    Map<String,Object> map = new HashMap<>();
    map.put("id",id);
    map.put("userName",username);
    map.put("age",age);
    map.put("hobby",hobby);
    map.put("agent",agent);
    map.put("所有请求头",header);

    map.put("cookie1",Cookie1);
    map.put("cookie2",cookie2.getValue());
    return map;
    }
    }

    1
    2
    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
    {
    "agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
    "所有请求头": {
    "host": "localhost:9909",
    "connection": "keep-alive",
    "pragma": "no-cache",
    "cache-control": "no-cache",
    "sec-ch-ua": "\"Google Chrome\";v=\"123\", \"Not:A-Brand\";v=\"8\", \"Chromium\";v=\"123\"",
    "sec-ch-ua-mobile": "?0",
    "sec-ch-ua-platform": "\"Windows\"",
    "upgrade-insecure-requests": "1",
    "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36",
    "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
    "sec-fetch-site": "none",
    "sec-fetch-mode": "navigate",
    "sec-fetch-user": "?1",
    "sec-fetch-dest": "document",
    "accept-encoding": "gzip, deflate, br, zstd",
    "accept-language": "zh-CN,zh;q=0.9,en;q=0.8",
    "cookie": "Idea-8296f2b3=4f2c3186-368f-495a-91d3-c900f7982eb4"
    },
    "id": "3",
    "userName": "libai",
    "cookie1": "4f2c3186-368f-495a-91d3-c900f7982eb4",
    "age": 18,
    "hobby": [
    "吃饭",
    "睡觉"
    ],
    "cookie2": "4f2c3186-368f-495a-91d3-c900f7982eb4"
    }

    @RequestAttribute

    • 获取request域属性
    • 既可以通过注解获取,也可以通过request.getAttribute()方法获取

    拦截器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    package top.dreamlove.admin.config;

    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    import top.dreamlove.admin.interceptor.LoginInterceptor;


    @Configuration
    public class AdminWebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
    //使用拦截器
    registry.addInterceptor(new LoginInterceptor())
    // 所有的页面添加拦截
    .addPathPatterns("/**")
    // 排除静态资源和登录页面
    .excludePathPatterns("/", "/login", "/css/**", "/fonts/**", "/images/**", "/js/**");

    }
    }

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    package top.dreamlove.admin.interceptor;

    import lombok.extern.slf4j.Slf4j;
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;

    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;

    @Slf4j
    public class LoginInterceptor implements HandlerInterceptor {

    /**
    * 请求之前
    *
    * @param request
    * @param response
    * @param handler
    * @return
    * @throws Exception
    */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    //判断是否登录
    HttpSession session = request.getSession();
    String requestURI = request.getRequestURI();//获取请求地址
    if(session.getAttribute("user") == null){
    log.info("拦截" + session.getAttribute("user") + "地址:" + requestURI);
    request.setAttribute("msg","请登录");
    //跳转到登录页面
    request.getRequestDispatcher("/").forward(request,response);
    return false;//拦截,未登录
    }else{
    return true;//登录,放行
    }
    // return HandlerInterceptor.super.preHandle(request, response, handler);
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
    HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
    }

    MyBatis-plus

    • 默认情况下名称和表名对应,可以通过@TableName("user_tabl")来指定表名

    基本

    字符串

    • get key

    • set key

    • setnx key value

      • 当key不存在的时候才设置value
    • del key

      • 成功1,失败0
    • exists key

      • 是否存在,存在1,不存在0
    • keys pattern

      • 获取所有符合pattern的键
    • flushall

      • 清空所有
    • ttl key

      • 查看一个键的过期时间,-1表示未设置过期时间
    • expire key second

      • 设置key过期时间 second单位为秒

    列表(list)-命令l开头

    • 元素是可以重复的

    • lpush key value1,value2,value3,…valueN

      • 列表头部添加内容
    • rpush key value1,value2,value3,…valueN

      • 列表尾部添加内容
    • lrange key start stop

      • start为起始位置,stop为结束位置(以0开始)
      • lrange letter 0 -1从开始获取到最后(获取所有)
    • lpop key count

      • 列表头部删除元素
      • count为删除元素个数
    • rpop key count

      • 列表尾部删除元素
      • count为删除元素个数
    • llen key

      • 查看列表的元素个数
    • LTRIM key start stop

      • 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。

    集合(set)-命令s开头

    • 无序的,且元素不可以重复

    • sadd key member1,member2,…,memberN,

      • 添加元素,一个或多个
    • smembers key

    • sismember key member

      • 判断member是否在key的集合中
    • srem key member

      • 删除key当中的member
    • 集合运算

    有序集合(sortedset或zset)-命令以z开头

    • 有序集合的每个元素,都会关联一个浮点类型的分数,然后按照这个分数,来对集合中的元素进行从小到大的排序,有序集合的成员是唯一的,但是分数是可以重复的,

    • zadd key 分数 成员,分数 成员,分数 成员

      • 返回添加的集合数量
      • 分数在前,成员在后
    • zrange key start stop

      • 技巧:zrange key 0 -1 获取所有元素
      • 如果需要输出分数,需要zrange key 0 -1 withscores
    • zscore key member

      • 查看某个成员分数的
    • zrank key member

      • 查看某个成员分数的排名(从小到大的顺序排列的排名),0开始排
    • zrevrank key member

      • 查看某个成员分数的排名(从大到小的顺序排列的排名) 0开始排
    • zrem key member

      • 删除成员

    哈希(hash)-命令以h开头

    • 哈希是一个字符类型的字段和值的映射表,简单来说就是一个键值对的集合,特别适合用来存储对象

    • hset key field value [field value…]

    • hgetall key

      • 获取所有键值对(以键值对成对出现)
    • hdel key field

      • 删除
    • hexist key field

      • 判断是否存在
    • hkeys key

      • 获取所有field
    • hlens key

    发布订阅模式

    • publish命令来将消息发送到指定的频道

    • subscribe来订阅这个频道

    • subscribe channel 订阅

    • publish channel message

      • channel频道发布消息

    消息队列(Stream)-命令以x开头

    • redis5.0版本引入的一个新的数据结构

    • 可以解决发布订阅消息无法持久化,无法记录历史记录等

    • XADD key ID field value [field value …]

      • key :队列名称,如果不存在就创建
      • ID :消息 id,我们使用 * 表示由 redis 生成,可以自定义,但是要自己保证递增性。
      • field value : 记录。
    • XLEN key

      • 使用 XLEN 获取流包含的元素数量,即消息长度,语法格式:
    • XRANGE key start end [COUNT count]

      • 使用 XRANGE 获取消息列表,会自动过滤已经删除的消息 ,语法格式:
      • 开始和结束可以分别使用-+
    • xdel

      • 删除消息
    • xtrim

    • xgroup

    技巧

    • GetMapping(“/“),GetMapping( value = “/“),多个值GetMapping( value = {“/“,”/login”} )

    • Controller和RestController的区别

    • thymeleaf如果是行内,则需要使用[[${变量}]]

    • Arrays.asList()

    • slf4j可以用占位符

      1
      2
      3
      4
      Log.info"上传的信息:email={},username={},headerImg={},photos={}",email,username,headerImg.getSize(),photos.length);

      //输出信息
      上传的信息:emai1=534096094@qq.c0m,username=admin,headerImg=43535,phot0s=2
    • C:\Users\Administrator\AppData\Local\Temp\tomcat.8080.3393410858019601440\work\Tomcat\localhost\ROOT\upload_52f25625_bd8b_400f_a069_c10bd66bdb8f_00000002.tmp (系统找不到指定的文件。)

    • 可以直接使用MapperScan这样就不需要我们手动为每一个Mapper添加@Mapper注解了

    • Ctrl + f12做什么用的?

    技巧

    • 重新部署可以按这2个按钮

    1
    2
    3
    4
    req.getRequestDispatcher("/list").forward(req,resp);
    //RequestDispatcher把request和response复制并转给“dispatchUrl”,需要注意的是,转发前后地址栏不变(均为转发前的地址

    resp.sendRedirect(req.getContextPath() + "/list");
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/8d55d49a.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    avatar
    梦洁
    小小的字,有大大的梦想~分享我的前端学习过程,经历,错误,和一些其他折腾过程
    关注下我(* ̄▽ ̄*)
    公告
    不断更新中,有问题请留言回复(会通过邮箱提醒~)
    目录
    1. 1. day01
      1. 1.1. 代码的结构
      2. 1.2. 大小写问题
      3. 1.3. java注释
      4. 1.4. 类型
      5. 1.5. 基本数据类型的转换之自动转换
      6. 1.6. 基本数据类型的转换之强制转换
      7. 1.7. String类型与基本数据类型转换的问题
      8. 1.8. 特别注意
      9. 1.9. 赋值运算符
    2. 2. day02
      1. 2.1. 几种输出语句
        1. 2.1.1. 格式化输出
      2. 2.2. 输入
        1. 2.2.1. next
        2. 2.2.2. nextLine
        3. 2.2.3. 需要注意
    3. 3. day03
      1. 3.1. 数组
      2. 3.2. 初始化
        1. 3.2.1. 一维数组静态初始化
        2. 3.2.2. 一维数组动态初始化
        3. 3.2.3. 二维数组动态初始化
        4. 3.2.4. 初始化的值
      3. 3.3. return new int[0]; 的意义
    4. 4. 面向对象
      1. 4.1. 类的定义
      2. 4.2. 对象的创建
      3. 4.3. 类的成员
    5. 5.
      1. 5.1. 包的作用域
      2. 5.2. 如何跨包使用类
    6. 6. 方法
    7. 7. 实例变量与局部变量的区别
    8. 8. 参数
      1. 8.1. 形参和实参
      2. 8.2. 可变参数
    9. 9. 方法的重载(Overload)
    10. 10. 对象数组
    11. 11. 面向对象的基本特征
      1. 11.1. 封装
        1. 11.1.1. 实现封装
        2. 11.1.2. 如何使用私有化的属性?
    12. 12. 继承
      1. 12.1. 继承有什么特点
      2. 12.2. 权限修饰符问题
        1. 12.2.1. 1.外部类要跨包使用必须是public,否则仅限于本包使用
        2. 12.2.2. 2.成员的权限修饰符问题
        3. 12.2.3. 3.父类成员变量私有化(private)
    13. 13. 重写(Override)
      1. 13.1. 重写的要求
    14. 14. EMS项目结构
    15. 15. 多态
      1. 15.1. 向上转型
      2. 15.2. 向下转型
      3. 15.3. 如何避免向下转型编译通过,运行发生ClassCastException?
      4. 15.4. 注意
    16. 16. 构造器
      1. 16.1. 同一个类中构造器相互调用
      2. 16.2. 构造器在继承时的要求
    17. 17. 非静态代码块
    18. 18. 实例初始化过程
    19. 19. final
    20. 20. Object根父类
      1. 20.1. Object类中方法
        1. 20.1.1. public String toString();
        2. 20.1.2. public final Class<?> getClass()
        3. 20.1.3. public boolean equals(Object obj);
        4. 20.1.4. public native int hashCode()
        5. 20.1.5. finalize
    21. 21. 静态
      1. 21.0.1. 7.1.1 静态关键字(static)
      2. 21.0.2. 7.1.2 静态变量
        1. 21.0.2.1. 1、语法格式
        2. 21.0.2.2. 2、静态变量的特点
        3. 21.0.2.3. 3、静态变量内存分析
        4. 21.0.2.4. 4、静态类变量和非静态实例变量、局部变量
      3. 21.0.3. 7.1.3 静态方法
        1. 21.0.3.1. 1、语法格式
        2. 21.0.3.2. 2、静态方法的特点
      4. 21.0.4. 7.1.4 静态代码块
        1. 21.0.4.1. 1、语法格式
        2. 21.0.4.2. 2、静态代码块的特点
        3. 21.0.4.3. 3、静态代码块和非静态代码块
      5. 21.0.5. 7.1.5 类初始化
        1. 21.0.5.1. 1、类初始化代码只执行一次
        2. 21.0.5.2. 2、父类优先于子类初始化
        3. 21.0.5.3. 3、类初始化优先于实例初始化
      6. 21.0.6. 7.1.6 静态和非静态的区别
        1. 21.0.6.1. 1、本类中的访问限制区别
        2. 21.0.6.2. 2、在其他类的访问方式区别
        3. 21.0.6.3. 3、this和super的使用
      7. 21.0.7. 7.1.7 静态导入
  • 22. 枚举
    1. 22.0.1. 7.2.1 概述
    2. 22.0.2. 7.2.2 JDK1.5之前
    3. 22.0.3. 7.2.3 JDK1.5之后
      1. 22.0.3.1. 1、enum关键字声明枚举
      2. 22.0.3.2. 2、枚举类的要求和特点
      3. 22.0.3.3. 3、枚举类型常用方法
  • 23. 技巧
  • 24. 包装类
  • 25. 抽象类
  • 26. 接口
  • 27. 注解
  • 28. 异常
  • 29. 多线程
  • 30.
  • 31. 注意
  • 32. JDBC
    1. 32.1. 实现增删改查
    2. 32.2.
    3. 32.3.
    4. 32.4.
    5. 32.5.
    6. 32.6. sql拼接-使用?代替值
    7. 32.7. sql注入-防sql注入
    8. 32.8. 图片上传
    9. 32.9. 获取自增键值
    10. 32.10. 批处理
    11. 32.11. 事物处理
    12. 32.12. 数据库连接池
      1. 32.12.1. 使用阿里的德鲁伊
  • 33. DAO层
    1. 33.1. 建立bean包
    2. 33.2. 建立dao包
      1. 33.2.1. 使用Dbutils组件封装BaseDAOImpl
  • 34. Servlet
    1. 34.1. HelloServlet
      1. 34.1.1. 找不到servlet解决办法
    2. 34.2. 作用
    3. 34.3. servlet生命周期
      1. 34.3.1. init
      2. 34.3.2. service
      3. 34.3.3. destroy
      4. 34.3.4. 第二种创建servlet的方法-GenericServlet
      5. 34.3.5. 第三种创建servlet-HttpServlet
    4. 34.4. ServletConfig
    5. 34.5. ServletContext
      1. 34.5.1. getContextPath()-获取项目的上下文路径
      2. 34.5.2. getRealPath()-(根据相对路径获取绝对路径)
      3. 34.5.3. 获取WEB应用程序的全局初始化参数
      4. 34.5.4. 作为域对象共享数据
    6. 34.6. HttpServletRequest
      1. 34.6.1. 获取请求头的信息
      2. 34.6.2. 获取URL地址信息
      3. 34.6.3. 获取请求头信息
      4. 34.6.4. 获取请求的参数
      5. 34.6.5. 使用BeanUtils
      6. 34.6.6. 请求的转发
      7. 34.6.7. 重定向
      8. 34.6.8. response
      9. 34.6.9. web项目的路径问题
  • 35. Thymeleaf
    1. 35.1. 基本语法
    2. 35.2. 操作请求域
    3. 35.3. 操作应用域
    4. 35.4. 获取请求参数
    5. 35.5. 内置对象
      1. 35.5.1. 基本内置对象
      2. 35.5.2. 公共内置对象
    6. 35.6. ognl-分支
    7. 35.7. 小练习
  • 36. Cookie和Session
    1. 36.1. Cookie
      1. 36.1.1. 如何将数据保存到Cookie中
      2. 36.1.2. 如何将数据从Cookie中取出来
      3. 36.1.3. Cookie中的数据的有效时间
      4. 36.1.4. 设置Cookie的携带条件
    2. 36.2. Session
  • 37. 后台响应JSON
  • 38. CommonResult
  • 39. Maven
    1. 39.1. Maven之Helloworld
    2. 39.2. Maven的坐标【重要】
    3. 39.3. Maven的依赖管理
    4. 39.4. Maven继承和聚合
      1. 39.4.1. 继承
      2. 39.4.2. 聚合
    5. 39.5. Maven在IDEA创建
  • 40. Mybatis
    1. 40.1. 搭建Mybatis框架
    2. 40.2. 1.导入jar包
    3. 40.3. 2.编写核心配置文件【mybatis-config.xml】
    4. 40.4. 3.书写相关接口及映射文件
    5. 40.5. 4.添加log4j日志jar包
  • 41. Mybatis配置
    1. 41.1. properties
    2. 41.2. setting
    3. 41.3. typeAliases
    4. 41.4. environments
    5. 41.5. mappers子标签
  • 42. Mybatis映射
    1. 42.1. 映射文件概述
    2. 42.2. 映射文件根标签
    3. 42.3. 映射文件子标签
    4. 42.4. 获取主键自增数据
    5. 42.5. 获取数据库受影响行数
  • 43. Mybatis参数传递问题
    1. 43.1. 单个参数 单个普通参数
    2. 43.2. 多个普通参数
    3. 43.3. POJO参数
    4. 43.4. 多个普通参数(命名参数)
    5. 43.5. Map参数
    6. 43.6. #与$使用场景
    7. 43.7. select查询返回值情况
  • 44. Mybatis中自动映射与自定义映射
    1. 44.0.0.1. 8.1 自动映射与自定义映射
    2. 44.0.0.2. 8.2 自定义映射-级联映射
    3. 44.0.0.3. 8.3 自定义映射-association映射
    4. 44.0.0.4. 8.5 ResultMap相关标签及属性
    5. 44.0.0.5. 8.6 Mybatis中分步查询
  • 45. MBG
  • 46. Mybatis中分页插件
  • 47. Spring
    1. 47.1. Spring中getBean()三种方式
    2. 47.2. Spring依赖注入数值问题
  • 48. SpringBoot
    1. 48.1. HelloWorld
    2. 48.2. @Configuration
    3. 48.3. @Conditional
    4. 48.4. @ImportResource
    5. 48.5. @PathVariable
    6. 48.6. @RequestParam
    7. 48.7. @RequestHeader
    8. 48.8. @CookieValue
    9. 48.9. @RequestAttribute
  • 49. 拦截器
  • 50. MyBatis-plus
  • 51. 基本
  • 52. 字符串
  • 53. 列表(list)-命令l开头
  • 54. 集合(set)-命令s开头
  • 55. 有序集合(sortedset或zset)-命令以z开头
  • 56. 哈希(hash)-命令以h开头
  • 57. 发布订阅模式
  • 58. 消息队列(Stream)-命令以x开头
  • 59. 技巧
  • 60. 技巧
  • 最新文章
    \ No newline at end of file diff --git a/8eb2cbd0.html b/8eb2cbd0.html new file mode 100644 index 000000000..cfa8c77d0 --- /dev/null +++ b/8eb2cbd0.html @@ -0,0 +1 @@ +java练习-简易博客的搭建 | 梦洁小站-属于你我的小天地

    java练习-简易博客的搭建

    前言

    注意

    • 项目为了练习,没有使用mybatisplus的简化写法

    统一错误(异常)处理

    • 还是这位博主的博客

    • 使用统一结果处理时,有些异常我们可以提前预知并处理,但是一个运行时异常,我们不一定能预知并处理,这时可以使用统一异常处理,当异常发生时,触发该处理操作,从而保证程序的健壮性。

    • 使用 @ControllerAdvice 或者 @RestControllerAdvice 注解作为统一异常处理的核心。

      • 这两个注解都是 Spring MVC 提供的。作用于 控制层 的一种切面通知。
    1
    2
    3
    4
    【@ControllerAdvice 与 @RestControllerAdvice 区别:】
    @RestControllerAdvice 注解包含了 @ControllerAdvice 与 @ResponseBody 注解。
    类似于 @Controller 与 @RestController 的区别。
    @RestControllerAdvice 返回 json 数据时不需要添加 @ResponseBody 注解。

    自定义一个异常类,用于处理项目中的异常,并收集异常信息。

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    package com.lyh.common.exception;

    import lombok.Data;
    import org.apache.http.HttpStatus;

    /**
    * 自定义异常,
    * 可以自定义 异常信息 message 以及 响应状态码 code(默认为 500)。
    *
    * 依赖信息说明:
    * 此处使用 @Data 注解,需导入 lombok 相关依赖文件。
    * 使用 HttpStatus 的常量表示 响应状态码,需导入 httpcore 相关依赖文件。
    */
    @Data
    public class GlobalException extends RuntimeException {
    /**
    * 保存异常信息
    */
    private String message;

    /**
    * 保存响应状态码
    */
    private Integer code = HttpStatus.SC_INTERNAL_SERVER_ERROR;

    /**
    * 默认构造方法,根据异常信息 构建一个异常实例对象
    * @param message 异常信息
    */
    public GlobalException(String message) {
    super(message);
    this.message = message;
    }

    /**
    * 根据异常信息、响应状态码构建 一个异常实例对象
    * @param message 异常信息
    * @param code 响应状态码
    */
    public GlobalException(String message, Integer code) {
    super(message);
    this.message = message;
    this.code = code;
    }

    /**
    * 根据异常信息,异常对象构建 一个异常实例对象
    * @param message 异常信息
    * @param e 异常对象
    */
    public GlobalException(String message, Throwable e) {
    super(message, e);
    this.message = message;
    }

    /**
    * 根据异常信息,响应状态码,异常对象构建 一个异常实例对象
    * @param message 异常信息
    * @param code 响应状态码
    * @param e 异常对象
    */
    public GlobalException(String message, Integer code, Throwable e) {
    super(message, e);
    this.message = message;
    this.code = code;
    }
    }

    再定义一个全局的异常处理类 GlobalExceptionHandler。

    • 使用 @RestControllerAdvice 注解标记这个类。
    • 内部使用 @ExceptionHandler 注解去捕获异常。
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    package com.lyh.common.exception;

    import com.lyh.common.util.Result;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    import org.springframework.web.bind.annotation.RestControllerAdvice;

    /**
    * 全局异常处理类。
    * 使用 slf4j 保存日志信息。
    * 此处使用了 统一结果处理 类 Result 用于包装异常信息。
    */
    @RestControllerAdvice
    public class GlobalExceptionHandler {
    private Logger logger = LoggerFactory.getLogger(getClass());

    /**
    * 处理 Exception 异常
    * @param e 异常
    * @return 处理结果
    */
    @ExceptionHandler(Exception.class)
    public Result handlerException(Exception e) {
    logger.error(e.getMessage(), e);
    return Result.error().message("系统异常");
    }

    /**
    * 处理空指针异常
    * @param e 异常
    * @return 处理结果
    */
    @ExceptionHandler(NullPointerException.class)
    public Result handlerNullPointerException(NullPointerException e) {
    logger.error(e.getMessage(), e);
    return Result.error().message("空指针异常");
    }

    /**
    * 处理自定义异常
    * @param e 异常
    * @return 处理结果
    */
    @ExceptionHandler(GlobalException.class)
    public Result handlerGlobalException(GlobalException e) {
    logger.error(e.getMessage(), e);
    return Result.error().message(e.getMessage()).code(e.getCode());
    }
    }

    使用

    1
    2
    3
    4
    5
    6
    使用?
    修改某个 controller 如下所示:
    参数不存在时,抛出 空指针异常。
    参数为 -1 时,抛出自定义异常并处理。
    查询结果为 null 时,抛出自定义异常并处理。
    查询成功时,正确处理并返回。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    @GetMapping("selectOne")
    public Result selectOne(Integer id) {
    Emp emp = this.empService.queryById(id);
    if (id == null) {
    throw new NullPointerException();
    }
    if (id == -1) {
    throw new GlobalException("参数异常", 400);
    }
    if (emp == null) {
    throw new GlobalException("未查询到结果,请确认输入是否正确");
    }
    return Result.ok().data("items", emp).message("查询成功");
    }

    统一结果处理

    数据格式?

    • 是否响应成功(success: true / false)
    • 响应状态码(code:200 / 400 / 500 等)
    • 状态码描述(message:访问成功 / 系统异常等)
    • 响应数据(data:处理的数据)

    如何处理

    • success 设置成 Boolean 类型。

    • code 设置成 Integer类型。  

    • message 设置成 String类型。

    • data 设置成 HashMap 类型。

    • 构造器私有,且使用静态方法返回类对象。

    • 采用链式调用(即方法返回对象为其本身,return thi

    • 所以打算直接拿这位博主的来~

      1
      2
      3
      4
      5
      <dependency>
      <groupId>org.apache.httpcomponents</groupId>
      <artifactId>httpcore</artifactId>
      <version>4.4.16</version>
      </dependency>
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    package com.lyh.common.util;

    import lombok.Data;
    import org.apache.http.HttpStatus;

    import java.util.HashMap;
    import java.util.Map;

    /**
    * 统一结果返回类。方法采用链式调用的写法(即返回类本身 return this)。
    * 构造器私有,不允许进行实例化,但提供静态方法 ok、error 返回一个实例。
    * 静态方法说明:
    * ok 返回一个 成功操作 的结果(实例对象)。
    * error 返回一个 失败操作 的结果(实例对象)。
    *
    * 普通方法说明:
    * success 用于自定义响应是否成功
    * code 用于自定义响应状态码
    * message 用于自定义响应消息
    * data 用于自定义响应数据
    *
    * 依赖信息说明:
    * 此处使用 @Data 注解,需导入 lombok 相关依赖文件。
    * 使用 HttpStatus 的常量表示 响应状态码,需导入 httpcore 相关依赖文件。
    */
    @Data
    public class Result {
    /**
    * 响应是否成功,true 为成功,false 为失败
    */
    private Boolean success;

    /**
    * 响应状态码, 200 成功,500 系统异常
    */
    private Integer code;

    /**
    * 响应消息
    */
    private String message;

    /**
    * 响应数据
    */
    private Map<String, Object> data = new HashMap<>();

    /**
    * 默认私有构造器
    */
    private Result(){}

    /**
    * 私有自定义构造器
    * @param success 响应是否成功
    * @param code 响应状态码
    * @param message 响应消息
    */
    private Result(Boolean success, Integer code, String message){
    this.success = success;
    this.code = code;
    this.message = message;
    }

    /**
    * 返回一个默认的 成功操作 的结果,默认响应状态码 200
    * @return 成功操作的实例对象
    */
    public static Result ok() {
    return new Result(true, HttpStatus.SC_OK, "success");
    }

    /**
    * 返回一个自定义 成功操作 的结果
    * @param success 响应是否成功
    * @param code 响应状态码
    * @param message 响应消息
    * @return 成功操作的实例对象
    */
    public static Result ok(Boolean success, Integer code, String message) {
    return new Result(success, code, message);
    }

    /**
    * 返回一个默认的 失败操作 的结果,默认响应状态码为 500
    * @return 失败操作的实例对象
    */
    public static Result error() {
    return new Result(false, HttpStatus.SC_INTERNAL_SERVER_ERROR, "error");
    }

    /**
    * 返回一个自定义 失败操作 的结果
    * @param success 响应是否成功
    * @param code 响应状态码
    * @param message 相应消息
    * @return 失败操作的实例对象
    */
    public static Result error(Boolean success, Integer code, String message) {
    return new Result(success, code, message);
    }

    /**
    * 自定义响应是否成功
    * @param success 响应是否成功
    * @return 当前实例对象
    */
    public Result success(Boolean success) {
    this.setSuccess(success);
    return this;
    }

    /**
    * 自定义响应状态码
    * @param code 响应状态码
    * @return 当前实例对象
    */
    public Result code(Integer code) {
    this.setCode(code);
    return this;
    }

    /**
    * 自定义响应消息
    * @param message 响应消息
    * @return 当前实例对象
    */
    public Result message(String message) {
    this.setMessage(message);
    return this;
    }

    /**
    * 自定义响应数据,一次设置一个 map 集合
    * @param map 响应数据
    * @return 当前实例对象
    */
    public Result data(Map<String, Object> map) {
    this.data.putAll(map);
    return this;
    }

    /**
    * 通用设置响应数据,一次设置一个 key - value 键值对
    * @param key 键
    * @param value 数据
    * @return 当前实例对象
    */
    public Result data(String key, Object value) {
    this.data.put(key, value);
    return this;
    }
    }

    前端传参校验

    Post用法

    • 在bean层添加对应注解
    1
    2
    3
    4
    5
    6
    7
    8
    public class demo{
    @NotNull(message="用户id不能为空")
    private Long userId;
    @NotBlank(message="用户名不能为空")
    private String userName;
    @NotBlank(message="年龄不能为空")
    private String age;
    }
    • 在controller层添加

      • @Valid 注解用于告诉 Spring Boot 对 MyRequest 对象进行验证
      1
      2
      3
      4
      5
      6
      @PostMapping("/xxx")
      public String createDemo(@RequestBody @Valid Demo demo, BindingResult result){
      if(result.hasErrors())
      return result.getFieldError().getDefaultMessage();
      return "sucess";
      }

    Get用法

    • 注意,如果需要使用校验,那么就需要关闭必填项@RequestParam(value = "username",required = false),否则就会出现org.springframework.web.bind.MissingServletRequestParameterException: Required request parameter 'username' for method parameter type String is not present,也就是字段未填写的报错

    • controller代码

    1
    2
    3
    4
    5
    6
    @GetMapping("/test2")
    public String getUserStr(
    @RequestParam(value = "username",required = false) @NotNull(message = "名字不能为空") String name) {

    return "success";
    }
    • 测试

    登录态

    • 这里就用jwt来生成一个token并添加在token里面
    • jwt依赖
    1
    2
    3
    4
    5
    <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.12.5</version>
    </dependency>
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    /**
    * jwt工具类
    */
    public class JwtUtil {

    private static final Logger logger = LoggerFactory.getLogger(JwtUtil.class);
    private static final String SECRET = "S/4AN9IsSRUC~{0c4]y#$F2XbV8^`#a14vawn<~Kr@(D%3TF-p1s/h{Y9k7y((rR";
    private static final long defaultExpire = 1000 * 60 * 60 * 24 * 7L;//7天
    //创建一个jwt密钥 加密和解密都需要用这个玩意
    private static final SecretKey key = Jwts.SIG.HS256.key()
    .random(new SecureRandom(SECRET.getBytes(StandardCharsets.UTF_8)))
    .build();

    private JwtUtil() {
    }

    /**
    * 使用默认过期时间(7天),生成一个JWT
    *
    * @param username 用户名
    * @param claims JWT中的数据
    * @return
    */
    public static String createToken(String username, Map<String, Object> claims) {
    return createToken(username, claims, defaultExpire);
    }

    /**
    * 生成token
    *
    * @param username 用户名
    * @param claims 请求体数据
    * @param expire 过期时间 单位:毫秒
    * @return token
    */
    public static String createToken(String username, Map<String, Object> claims, Long expire) {
    JwtBuilder builder = Jwts.builder();
    Date now = new Date();
    // 生成token
    builder.id("rQRk$yN:7%*Bw}A_A-]M~4#;yGa:a_F{") //id 这个可以不填,但是建议填
    .issuer("Galaxy") //签发者
    .claims(claims) //数据
    .subject(username) //主题
    .issuedAt(now) //签发时间
    .expiration(new Date(now.getTime() + expire)) //过期时间
    .signWith(key); //签名方式
    builder.header().add("JWT", "JSpWdhuPGblNZApVclmX");
    return builder.compact();
    }

    /**
    * 解析token
    *
    * @param token jwt token
    * @return Claims
    */
    public static Claims claims(String token) {
    try {
    return Jwts.parser()
    .verifyWith(key)
    .build()
    .parseSignedClaims(token)
    .getPayload();
    } catch (Exception e) {
    if (e instanceof ExpiredJwtException) {
    //现在不需要使用 claims.getExpiration().before(new Date());
    // 判断JWT是否过期了 如果过期会抛出ExpiredJwtException异常
    throw new RunException("token已过期");
    }
    if (e instanceof JwtException) {
    throw new RunException("token已失效");
    }
    logger.error("jwt解析失败" + e);
    throw new RunException("token解析失败");
    }
    }


    public static void main(String[] args) {
    Map<String, Object> claims = Map.of("name", "张三");
    String token = createToken("mysterious", claims, 3L);
    System.out.println(token);
    Claims claims1 = claims(token);
    System.out.println(claims1);
    }
    }

    token续签?到时续期?

    redis

    jwt过期判断

    1
    2
    3
    4
    5
    6
    7
    8
    void testJWT1(){
    String token = "eyJhbGciOiJIUzI1NiIsInR5cGUiOiJKV1QiLCJ0eXAiOiJKV1QifQ.eyJpYXQiOjE3MTE1Mjg2NjYsImV4cCI6MTcxMTUyODY3NiwianRpIjoiM2RkN2IzNmUtNjQxMS00OGEzLTkwMDMtZDVhZGVlZWQ1OTBhIiwic3ViIjoiYXV0aCIsInVzZXJOYW1lIjoicWl1eWUiLCJpZCI6MTIzfQ.lPbcvtgbI4VEhQZrkhmws-zmZLXlQrysRlAmFnNsVeU";
    try {
    JWTValidator jwtValidator = JWTValidator.of(token).validateDate(DateUtil.date());
    } catch (ValidateException exception) {
    throw new JWTException("token已过期");
    }
    }

    登录注册

    用户注册(需返回主键)

    分页功能

    • 参考文章
    1
    2
    3
    4
    5
    6
    7
    @Test
    //测试分页
    void testPage(){
    PageHelper.startPage(1,10);
    List<ArticleInfo> articleInfos = articleInfoMapper.articleList();
    PageInfo<ArticleInfo> articleInfoPageInfo = new PageInfo<>(articleInfos);
    }
    • 下图展示的是articleInfoPageInfo的信息

    问题

    • cn.hutool.core.convert.NumberWithFormat cannot be cast to java.lang.Integer

      • 使用Integer.parseInt方法转换
    • 需要的类型:Supplier<java.lang.String>提供的类型:String(Required type: Supplier <java.lang.String> Provided: String)

      • 导错误包了,应该是slf4j的
      1
      2
      3
      4
      5
      // import org.mybatis.logging.Logger;
      // import org.mybatis.logging.LoggerFactory;
      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;

    • mybatis中的@param什么时候加什么时候不加呢

    • @RequestBody通常,在处理 HTTP POST 请求时,客户端将请求的数据作为请求体发送到服务器端。服务器端可以使用@RequestBody 注解将请求体的内容绑定到方法的参数上,以便进行处理。

    在上述示例中,@RequestBody 注解应用于 User 对象的参数,表示将请求体的内容绑定到 User 对象上。这样,当客户端发送一个包含用户信息的 JSON 请求体时,Spring 框架会自动将该 JSON 数据转换为 User 对象,并将其作为参数传递给 createUser 方法。

    需要注意的是,使用 @RequestBody 注解时,Spring 框架会使用消息转换器(MessageConverter)来处理请求体的数据转换。默认情况下,Spring 支持多种消息转换器,包括处理 JSON、XML、Form 表单等数据格式。

    总结来说,@RequestBody 注解用于将 HTTP 请求的内容绑定到方法的参数上,方便在 Spring 控制器中处理请求体的数据。

    1
    2
    3
    4
    @PostMapping("/users")
    public ResponseEntity createUser(@RequestBody User user) {
    // 处理创建用户的逻辑
    }
    1
    2
    3
    4
    5
    6
    总结
    @requestParam
    1.用来获取URL后面追加的参数
    2.POST请求,content-type:application/json 的body中的参数
    @requestBoby
    1.接收POST ,content-type:application/json的body参数 (后端一般封装成javaBean对象处理)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    问题代码
    @PostMapping( "/register")
    public Result register(@RequestBody UserInfo userInfo){
    log.info(userInfo.toString());
    return Result.ok();
    }
    前端接口Content-Type:application/x-www-form-urlencoded

    原因
    前端请求传Json对象的字符串则后端才使用@RequestBody。而我前端采用的表单提交的数据,是不能采用@RequestBody注解的。

    解决办法:
    第一种解决方式就是修改后端代码,去掉@RequestBody注解,也可以直接获取到表单提交的POST数据。
    //修改后
    @PostMapping( "/register")
    public Result register(UserInfo userInfo){
    log.info(userInfo.toString());
    return Result.ok();
    }

    第二种解决方式就是修改前端代码,前端传递JSON对象的字符串,这里我采用jQuery来发送ajax请求传递JSON对象的字符串数据。
    指定contentType: "application/json",

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/8eb2cbd0.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/9131599a.html b/9131599a.html new file mode 100644 index 000000000..1cdc91187 --- /dev/null +++ b/9131599a.html @@ -0,0 +1 @@ +vue-admin-template里面的异步路由,常量路由,任意路由的添加,记录笔记 | 梦洁小站-属于你我的小天地

    vue-admin-template里面的异步路由,常量路由,任意路由的添加,记录笔记

    路由模式

    • 常量路由(比如首页)
    • 异步路由(不同用户不同路由,根据权限来定的)
    • 任意路由(比如404)

    vueAdmin当中使用路由权限

    1.在路由注册的时候,分段注册 (默认只注册常量路由)

    • src\router\index.js

    默认只注册常量路由

    2.处理路由信息,并添加到route当中

    • 调用router当中的addRouter方法,传入的参数为数组(数组里面也就是路由信息)

    • src\store\modules\user.js当中,根据token获取到用户信息后(比如用户权限,可以用的路由有哪些),来对数据进行处理

      设置路由信息

    • 处理路由信息的函数

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      /**
      *
      * @param {array} all 所有的异步路由信息
      * @param {array} selfRoutes 服务器获取到的当前账户所具有的异步路由信息
      */
      function getOwnAsyncRoutes(allAsyncRoutes,selfRoutes){
      return allAsyncRoutes.filter(item => {
      //当前账户异步路由信息包含了正在遍历的路由的name,则存入
      if(selfRoutes.includes(item.name)){
      //含有二级路由
      if(item.children && item.children.length){
      getOwnAsyncRoutes(item.children,selfRoutes);
      }
      return true;
      }
      });
      }

    3.使用路由生成导航条的时候, 用处理后的路由信息(借此生成导航窗格)

    • \src\layout\components\Sidebar\index.vue当中修改(注意这里用mapGetters)

    处理后的路由信息注册路由

    • 这里用的是mapGetters,所以这里也需要修改\src\store\getters.js

    处理addRoutes动态添加路由后白屏的情况

    • 文件路径: src\permission.js

      1
      2
      3
      4
      next();//改为下面一行即可

      next({...to,replace:true});

      处理白屏

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/9131599a.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/914c2cae.html b/914c2cae.html new file mode 100644 index 000000000..a86328319 --- /dev/null +++ b/914c2cae.html @@ -0,0 +1 @@ +artalk设置进程守护和普通方式部署下创建密码 | 梦洁小站-属于你我的小天地

    artalk设置进程守护和普通方式部署下创建密码

    • 以宝塔的进程守护管理器为例子
    • 对应的artalk版本为2.8.7

    普通方式部署下创建密码

    • 普通方式管理员密码需要通过该方式创建,不然没有没办法登录
    1
    2
    3
    4
    5
    // 切换到artalk所在目录,命令行执行
    //之后按照提示输入账号密码
    ./artalk admin -c artalk.yml


    设置进程守护

    • 格式如下
    1
    2
    3
    4
    5
    6
    7
    /<artalk所在文件路径>/artalk server



    示例
    /root/artalk_plus/artalk server

    • 示例图如下

    • artalk的目录结构

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/914c2cae.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/9151d943.html b/9151d943.html new file mode 100644 index 000000000..93e8c3bba --- /dev/null +++ b/9151d943.html @@ -0,0 +1 @@ +jQuery中API,post上传文件到阿里云OSS记录 | 梦洁小站-属于你我的小天地

    jQuery中API,post上传文件到阿里云OSS记录

    前景提示

    1. 在利用FormData上传文件的时候,新建实例化对象如果有参数,需要的是DOM元素,而不是其他的!
    2. FormData的实例化对象调用get()方法获取属性的时候,返回值(FormDataEntryValue)包括下面二种
      1. string数据


      2. ### File对象
      1. File对象当中的属性
      1. lastModified:格林威治时间
      2. lastModifiedDate:可识别时间
      3. name:文件名
      4. size :文件大小(字节)
      5. type:类型
      6. webkitRelativePath(非标准: 该特性是非标准的,请尽量不要在生产环境中使用它!)

      3. ### FormDataEntryValue数组
      1. FormData的实例化对象调用getAll(name)方法返回
      4. ### API测试使用的网站
      1. 果创云
      2. 阿里云OSS存储

    代码块

    ajax请求使用jQuery插件

    html代码块

    1
    2
    3
    4
    5
    <form method="post" enctype="multipart/form-data" id='form'>
    上传文件:
    <input type="file" name="file">
    <input type="button" value="提交" id="ss">
    </form>

    javascript代码块

    1
    2
    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
    var url =
    "http://hn216.api.yesapi.cn/?s=App.OSS_Aliyun.UploadFile&app_key=填写你自己的";
    $("#ss").on('click', function (e) {
    var formData = new FormData(document.getElementById('form'));
    var nameFile = formData.get('file').name;
    //含有文件
    if (nameFile.length != 0) {
    //文件名
    formData.append('object', nameFile);
    //做base64处理
    var reader = new FileReader();
    //从formData当中调用get方法,传入字符串(也就是name属性的值),这里用文件框输入用的name属性为file
    reader.readAsDataURL(formData.get('file'));
    //文件加载完成
    reader.onload = function () {
    //添加文件数据到formData当中
    formData.append('content', reader.result);
    //移除多余的,这一步可有可无
    formData.delete('file');
    //ajax发送数据
    $.ajax({
    'url': url,
    'type': 'post',
    'data': formData,
    'processData': false,
    'contentType': false,
    'success': function (data) {
    console.log(data);
    }
    });

    }
    }
    });

    执行结果

    成功上传

    测试过程中遇到的问题

    总说缺少content参数但data里面有了

    原因

    将processData设置为false的时候,表示不将data参数中的数据进行序列化,传输的是blob对象,DOM树,文件等数据的时候就是不需要将传输的数据序列化

    解决

    将processData设置为true即可,即默认情况下会将发送的数据序列化(也就是data当中的数据,以适应默认的内容类型application/x-www-form-urlencoded

    1. 解决之后传送的数据


    2. ##### 解决之前传送的数据

    区分下

    processData参数在jQuery当中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //测试代码
    $.ajax({
    type: "POST",
    url: "some.php",
    data: {
    name: "John",
    location: "Boston"
    },
    success: function (msg) {
    alert("Data Saved: " + msg);
    }
    });
    1. 当processData=true;(默认值)

    2. 当processData=false;

    总结

    1. processData:本地要怎么样处理当前的数据

      1. processData:true;默认值将发送的数据序列化以适应默认的内容类型application/x-www-form-urlencoded
      2. processData:false;
    2. contentType:告诉服务器发送数据的格式

      1. contentType:true;默认值,值为application/x-www-form-urlencoded
      2. contentType:false;值为multipart/form-data
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/9151d943.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/91ff580f.html b/91ff580f.html new file mode 100644 index 000000000..955c12910 --- /dev/null +++ b/91ff580f.html @@ -0,0 +1 @@ +Pycharm搭配miniConda(Anaconda的轻量版本)建立虚拟环境管理不同项目不同依赖 | 梦洁小站-属于你我的小天地

    Pycharm搭配miniConda(Anaconda的轻量版本)建立虚拟环境管理不同项目不同依赖

    需求

    • pip安装的版本会在全局环境下生效,导致不用的项目使用的是同一套环境,容易出现别人的项目跑的好好的,到了自己电脑就不能跑的问题
    • 需求
      • 1.不同的项目可以设置不同的环境,同时支持自动切换环境
      • 2.便捷,轻量化管理当前的环境
        • 如果可以,最好有一个可视化的平台,anaconda倒是支持图形化管理,但是不轻量,mini
      • 3.目前是window,就写了window平台使用,其他平台操作也应该大同小异

    调研

    • Anaconda
      • Anaconda是一个包含了大量数据科学和机器学习相关包的 Python 发行版。它附带了 conda包和环境管理工具、许多预安装的库和工具,以及图形界面的 Anaconda Navigator。
    • miniconda
      • Miniconda 是一个较小的 Python 发行版,它包括 conda 工具和Python 解释器,所以更像是Anaconda 的阉割版。相较于Anaconda,Miniconda 体积小了很多,占用磁盘空间少了很多,对于我们一般的开发者足够用了。

    miniconda安装并在pycharm配置

    • 如果没有打钩,自己在环境变量配置下即可

    • 安装完成,查看有的环境

    • 我们添加下环境

    • 可以看到,目前这个项目使用的是全局的环境,也就是base环境

    • 我们更换为conda环境,这里通过pycharm创建一个环境名称为project20241012RunEnv的运行环境

    • 等待一会,在下载一些基础库,为什么知道,因为下载失败了开着代理,哈哈哈

    • 创建完成

    • 我们cmd输入conda env list ,查看下环境
      • 可以看到我们建立的环境在下面

    • 对比之前,我们可以看到,新建立的环境库很少对比之前
    • 之前

    • 现在

    • 我们在终端也可以看到环境前缀

    • cmd中也可以使用conda activate 环境名 切换环境

    安装依赖在新环境

    • miniconda中默认自带一个名为conda的包管理工具,和pip工具有异曲同工之处,并且miniconda中也自带pip。conda主要用于管理虚拟环境的,虽然也能安装依赖,但还是推荐pip。
    • 我们看下新环境依赖

    • 我们随便安装一个
    • 出现**WARNING: Retrying (Retry(total=4, connect=None, read=None, redirect=None, status=None)) after connection broken by ‘SSLError(SSLZeroReturnError(6, ‘TLS/SSL connection has been closed (EOF) (_ssl.c:1149)’))’: /simple/torch/ **把代理关闭下

    • 随便安装一个库,看看有没有隔离
      • pip install test-test

    • 安装成功

    • 我们在project20241012安装了依赖库test-test,我们切换到其他环境或者不在任何一个环境下(最原始的)看看能不能查找到,可以看到,并没有出现依赖test-test

    特别注意

    • 如果在安装conda之前就安装了一些依赖,好像是会影响现有的环境的,比如我在原始环境安装了scrapy,然后切换到新环境输入scrapy也会识别,原始环境卸载scrapy后,新环境也无法运行scrapy了

    参考文章

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/91ff580f.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/92310ed8.html b/92310ed8.html new file mode 100644 index 000000000..ee048fa59 --- /dev/null +++ b/92310ed8.html @@ -0,0 +1 @@ +微信小程序渐进式骨架屏的写法 | 梦洁小站-属于你我的小天地

    微信小程序渐进式骨架屏的写法

    前置

    知道hidden属性

    • 为true显示,为false隐藏,类似于display:none
    1
    2
    3
    4
    <view>
    <text hidden="{{true}}">我会被隐藏</text>
    <text hidden="{{false}}">我会被显示</text>
    </view>

    第一步:写好基本的html

    • 写好的静态页面
      • 分为三个部分,上,中,下

    • 代码

      • wxml
      1
      2
      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
      <view style="display: flex;flex-direction: column;height: 100vh;">
      <!-- 前置了解下hidden -->
      <!-- <text hidden="{{true}}">我会被隐藏</text>
      <text hidden="{{false}}">我会被显示</text> -->

      <!-- 轮播图部分 -->
      <view class="swiper-part">
      <swiper indicatorDots="{{true}}">
      <swiper-item><image style="width: 100%;" src="https://tdesign.gtimg.com/miniprogram/images/swiper1.png"/></swiper-item>
      <swiper-item><image style="width: 100%;" src="https://tdesign.gtimg.com/miniprogram/images/swiper2.png"/></swiper-item>
      <swiper-item><image style="width: 100%;" src="https://tdesign.gtimg.com/miniprogram/images/swiper1.png"/></swiper-item>
      <swiper-item><image style="width: 100%;" src="https://tdesign.gtimg.com/miniprogram/images/swiper2.png"/></swiper-item>
      </swiper>
      </view>

      <!-- 文字说明部分 -->
      <view class="text-part">
      <view>大家好,我是梦洁和秋叶</view>
      <view>大家好,我是梦洁和秋叶</view>
      <view>大家好,我是梦洁和秋叶</view>
      <view>大家好,我是梦洁和秋叶</view>
      <view>大家好,我是梦洁和秋叶</view>
      <view>大家好,我是梦洁和秋叶</view>
      <view>大家好,我是梦洁和秋叶</view>
      <view>大家好,我是梦洁和秋叶</view>
      </view>

      <!-- 底部部分 -->
      <view class="bottom-part">
      <image style="width: 100%;" src="https://dreamlove.top/img/favicon.png"/>
      </view>
      </view>
      • wxss
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      .swiper-part{

      }

      .text-part{
      /* 简单的设置下文字居中显示 */
      flex: 1;
      text-align: center;
      display: flex;
      flex-flow: column nowrap;
      align-self: center;
      justify-content: center;
      }

      .bottom-part{

      }

    第二步:使用data-skeleton-hide指明骨架区域

    • 由于我们分为了三个部分"swiper-part""text-part"bottom-part,我们这里就指明三个部分就可以
    1
    2
    3
    4
    5
    6
    7
    8
    <!-- 轮播图部分 -->
    <view class="swiper-part" data-skeleton-hide="swiperData"> ... </view>

    <!-- 文字说明部分 -->
    <view class="text-part" data-skeleton-hide="textData"> ... </view>

    <!-- 底部部分 -->
    <view class="bottom-part" data-skeleton-hide="bottomData"> ... </view>
    • 代码(wxss未变动,所以不列出)
      • wxml
    1
    2
    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
    <view style="display: flex;flex-direction: column;height: 100vh;">
    <!-- 前置了解下hidden -->
    <!-- <text hidden="{{true}}">我会被隐藏</text>
    <text hidden="{{false}}">我会被显示</text> -->

    <!-- 轮播图部分 -->
    <view class="swiper-part" data-skeleton-hide="swiperData">
    <swiper indicatorDots="{{true}}">
    <swiper-item><image style="width: 100%;" src="https://tdesign.gtimg.com/miniprogram/images/swiper1.png"/></swiper-item>
    <swiper-item><image style="width: 100%;" src="https://tdesign.gtimg.com/miniprogram/images/swiper2.png"/></swiper-item>
    <swiper-item><image style="width: 100%;" src="https://tdesign.gtimg.com/miniprogram/images/swiper1.png"/></swiper-item>
    <swiper-item><image style="width: 100%;" src="https://tdesign.gtimg.com/miniprogram/images/swiper2.png"/></swiper-item>
    </swiper>
    </view>

    <!-- 文字说明部分 -->
    <view class="text-part" data-skeleton-hide="textData">
    <view>大家好,我是梦洁和秋叶</view>
    <view>大家好,我是梦洁和秋叶</view>
    <view>大家好,我是梦洁和秋叶</view>
    <view>大家好,我是梦洁和秋叶</view>
    <view>大家好,我是梦洁和秋叶</view>
    <view>大家好,我是梦洁和秋叶</view>
    <view>大家好,我是梦洁和秋叶</view>
    <view>大家好,我是梦洁和秋叶</view>
    </view>

    <!-- 底部部分 -->
    <view class="bottom-part" data-skeleton-hide="bottomData">
    <image style="width: 100%;" src="https://dreamlove.top/img/favicon.png"/>
    </view>
    </view>
    • 这样子我们生成的骨架屏就有了区域,我们在data-skeleton-hide传入的是字符串,不是变量

    • 生成骨架屏代码长这个样子

      • 所以我们传入的变量是控制骨架屏显示和隐藏的(注意不要弄混了)
    1
    2
    3
    4
    5
    6
    7
    <!-- 原 wxml 内容 -->
    <view data-skeleton-hide="hideBlock1"></view>
    <view data-skeleton-hide="hideBlock2"></view>

    <!-- 骨架屏 wxml 内容 -->
    <view hidden="{{hideBlock1}}"></view>
    <view hidden="{{hideBlock2}}"></view>

    第三步:生成骨架屏并创建各个部分显示/隐藏的变量

    生成骨架屏

    • 确认

    • 生成后会默认展示下效果,这里就不纠结怎么样了

    创建各个部分显示/隐藏的变量和一个总体loading变量

    • 代码
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // pages/skeletonDemo/skeletonDemo.js
    Page({
    data: {
    swiperData:false,//顶部轮播图数据是否加载完成-默认false
    textData:false,//中间文本数据是否加载完成-默认false
    bottomData:false,//底部数据是否加载完成-默认false
    loading:true,//数据是否还有没有加载的-默认true
    },
    })

    第四步:使用骨架屏

    引入骨架屏并在template当中使用data属性传入控制显示/隐藏的变量和总体loading变量

    1
    2
    3
    4
    5
    6
    7
    <!-- 引入骨架屏 -->
    <import src="./skeletonDemo.skeleton" />
    <!-- 传入创建好的控制骨架屏的变量名和总体loading -->
    <template is="skeleton" wx:if="{{loading}}" data="{{swiperData,textData,bottomData}}"/>


    //...下面是wxml内容部分,就隐藏不显示了

    引入样式

    • 添加@import "./skeletonDemo.skeleton.wxss";即可引入
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    @import "./skeletonDemo.skeleton.wxss";
    .swiper-part{

    }

    .text-part{
    /* 简单的设置下文字居中显示 */
    flex: 1;
    text-align: center;
    display: flex;
    flex-flow: column nowrap;
    align-self: center;
    justify-content: center;
    }

    .bottom-part{

    }

    第五步:添加控制显示/隐藏的变量到页面内容

    • 因为我们是默认显示骨架屏的,所以如果想要展示原本的内容,就取反就可以
    1
    2
    3
    4
    5
    6
    7
    8
    <!-- 轮播图部分 -->
    <view class="swiper-part" data-skeleton-hide="swiperData" hidden="{{!swiperData}}"> ... </view>

    <!-- 文字说明部分 -->
    <view class="text-part" data-skeleton-hide="textData" hidden="{{!textData}}"> ... </view>

    <!-- 底部部分 -->
    <view class="bottom-part" data-skeleton-hide="bottomData" hidden="{{!bottomData}}"> ... </view>

    最后和演示效果

    • 其实已经完成了这样子,我们最后自己添加下延迟吧
    • js文件
    1
    2
    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
    // pages/skeletonDemo/skeletonDemo.js
    Page({
    data: {
    swiperData:false,//顶部轮播图数据是否加载完成-默认false
    textData:false,//中间文本数据是否加载完成-默认false
    bottomData:false,//底部数据是否加载完成-默认false
    loading:true,//数据是否还有没有加载的-默认true
    },
    onLoad(){
    setTimeout(() => {
    this.setData({
    swiperData:true,//顶部轮播图设置加载完成
    })
    },1000);

    setTimeout(() => {
    this.setData({
    textData:true,//中间文本设置加载完成
    })
    },3000);

    setTimeout(() => {
    this.setData({
    bottomData:true,//底部图片设置加载完成
    loading:false,//数据全部加载完成,这样子骨架屏就可以隐藏了
    })
    },6000);

    }
    })
    • 这样子就实现了渐进加载骨架屏效果

    注意

    • 更改骨架屏配置后需要重新生成骨架屏

    • 如果需要闪亮的效果,可以全局添加下方配置

    1
    2
    3
    4
    5
    "skeletonConfig": {
    "global": {
    "loading": "shine"
    }
    }

    参考资料

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/92310ed8.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/928aeeef.html b/928aeeef.html new file mode 100644 index 000000000..0c40a7382 --- /dev/null +++ b/928aeeef.html @@ -0,0 +1,2 @@ +今日刷题-请求头和响应头有必要了解 | 梦洁小站-属于你我的小天地

    今日刷题-请求头和响应头有必要了解

    题目1

    1
    2
    3
    4
    5
    以下哪一项不属于浏览器Response Headers字段:
    A: Referer
    B: Connection
    C: Content-Type
    D: Server
    +
    • 答案 A

      • A: 请求头 ,用于告诉服务器请求来自哪里
      • B: 请求头 ,用于维护客户端和服务端的连接关系
      • C: 请求头 POST可用,响应头也可以使用
      • D: 响应头 web服务器软件名称
    • 解析

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      常见的请求头   客户端 -> 服务端[request]  
      Accept: */*(客户端能接收的资源类型)
      Accept-Language: en-us(客户端接收的语言类型)
      Connection: Keep-Alive(维护客户端和服务端的连接关系)
      Host: localhost:8080(连接的目标主机和端口号)
      Referer: http://localhost/links.asp(告诉服务器我来自于哪里)
      User-Agent: Mozilla/4.0(客户端版本号的名字)
      Accept-Encoding: gzip, deflate(客户端能接收的压缩数据的类型)
      If-Modified-Since: Tue, 11 Jul 2000 18:23:51 GMT(缓存时间)
      Cookie: (客户端暂存服务端的信息)
      Date: Tue, 11 Jul 2000 18:23:51 GMT(客户端请求服务端的时间)
      Content-Type: 请求的与实体对应的MIME信息。(POST方式的请求头携带)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      常见的响应头   服务端[response] -> 客户端  
      Location: http://www.baidu.com(服务端需要客户端访问的页面路径)
      Server: apache tomcat(服务端的Web服务端名)
      Content-Encoding: gzip(服务端能够发送压缩编码类型)
      Content-Length: 80(服务端发送的压缩数据的长度)
      Content-Language: zh-cn(服务端发送的语言类型)
      Content-Type: text/html; charset=GB2312(服务端发送的类型及采用的编码方式)
      Last-Modified: Tue, 11 Jul 2000 18:23:51 GMT(服务端对该资源最后修改的时间)
      Refresh: 1;url=http://www.it315.org(服务端要求客户端1秒钟后,刷新,然后访问指定的页面路径)
      Content-Disposition: attachment; filename=aaa.zip(服务端要求客户端以下载文件的方式打开该文件)
      Transfer-Encoding: chunked(分块传递数据到客户端)
      Set-Cookie: SS=Q0=5Lb_nQ; path=/search(服务端发送到客户端的暂存数据)
      Expires: -1//3种(服务端禁止客户端缓存页面数据)
      Cache-Control: no-***(服务端禁止客户端缓存页面数据)
      Pragma: no-***(服务端禁止客户端缓存页面数据)
      Connection: close(1.0)/(1.1)Keep-Alive(维护客户端和服务端的连接关系)
      Date: Tue, 11 Jul 2000 18:23:51 GMT(服务端响应客户端的时间)

      HTTP响应头和请求头信息对照表

    题目2

    1
    2
    3
    4
    5
    下面哪种方法可以实现匹配包含给定文本的元素?(jQuery知识点)
    A: text()
    B: contains(text)
    C: input()
    D: attr(name)
    • 答案

      • B
    • 解析

      • A: 用于获取或者设置指定元素的文本内容

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        //示例,这里演示的是获取
        <div class="demo-container">
        <div class="demo-box">内容1</div>
        <ul>
        <li>内容2</li>
        <li>内容3
        <strong>小弟1</strong>小弟2
        </li>
        </ul>
        </div>
        <script>
        var a = $(".demo-container").text();//结果为 内容1 内容2 内容3小弟1小弟2
        </script>
      • B: 选择所有包含指定文本的元素

        1
        2
        3
        4
        5
        6
        7
        //获取"内容1"的元素
        <div>内容1</div>
        <div>内容2</div>
        <div>内容3</div>
        <div>内容3</div>
        //js代码
        $("div:contains('内容3')");//返回<div>内容3</div>,当然,是封装成jQuery的DOM元素了,这里为了方便说明而直接说是DOM元素
      • C: input() ,其实题目不应该这样子,应该是 :input

        1
        2
        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
        35
        36
        37
        38
        39
        40
        41
        选择所有 input, textarea, select 和 button 元素. 
        <form>
        <input type="button" value="Input Button"/>
        <input type="checkbox" />

        <input type="file" />
        <input type="hidden" />
        <input type="image" />

        <input type="password" />
        <input type="radio" />
        <input type="reset" />

        <input type="submit" />
        <input type="text" />
        <select><option>Option</option></select>

        <textarea></textarea>
        <button>Button</button>
        </form>
        运行$(":input")
        返回:
        [
        <input type="button" value="Input Button"/>,
        <input type="checkbox" />,

        <input type="file" />,
        <input type="hidden" />,
        <input type="image" />,

        <input type="password" />,
        <input type="radio" />,
        <input type="reset" />,

        <input type="submit" />,
        <input type="text" />,
        <select><option>Option</option></select>,

        <textarea></textarea>,
        <button>Button</button>,
        ]
      • D: attr(name) 获取指定属性名称的值

    题目3

    1
    2
    3
    4
    5
    下列哪些函数是JavaScript的全局函数?(多选)
    A: encodeURI
    B: parseFloat
    C: setTimeout
    D: eval
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/928aeeef.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/933f6d7f.html b/933f6d7f.html new file mode 100644 index 000000000..cc6dd2151 --- /dev/null +++ b/933f6d7f.html @@ -0,0 +1 @@ +nvm安装版本后设置默认镜像地址和nvm list available出现空白解决办法 | 梦洁小站-属于你我的小天地

    nvm安装版本后设置默认镜像地址和nvm list available出现空白解决办法

    如题

    1
    2
    npm config get registry
    https://registry.npmjs.org/
    • 发现镜像地址依旧是默认的

    解决

    • 运行输入npm config list
    • 查看这个选项对应的目录

    • 修改为的内容如下
    1
    2
    home=https://npmmirror.com
    registry=https://registry.npmmirror.com/
    • 或者也可以
    1
    2
    home=https://npmmirror.com
    registry=https://npmmirror.com/

    • 再次查看,修改成功

    nvm list available出现空白解决办法

    • 找到nvm的安装路径,建立settings.txt文件

    • 添加下面2条,如果有就替换掉
    1
    2
    node_mirror: https://npmmirror.com/mirrors/node/
    npm_mirror: https://npmmirror.com/mirrors/npm/
    • 最终内容如下

    • 即可解决
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/933f6d7f.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/93d99268.html b/93d99268.html new file mode 100644 index 000000000..e579fd66a --- /dev/null +++ b/93d99268.html @@ -0,0 +1 @@ +今日刷题-重写的ValueOf | 梦洁小站-属于你我的小天地

    今日刷题-重写的ValueOf

    题目1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    以下关于Histroy对象的属性或方法描述正确的是()(多选)
    A: back回到浏览器载入历史URL地址列表的当前URL的前一个URL

    B: go表示刷新当前页面

    C: length保存历史URL地址列表的长度信息

    D: forward转到浏览器载入历史URL地址列表的当前URL的下一个URL

    • 答案
      • A,C,D
    • 解析
      • A: back回到浏览器载入历史URL地址列表的当前URL的前一个URL
      • B: go() 加载history列表中的某个具体页面(可以自由跳转)。所以B的表述刷新当前页面是错误的。
        • history.go(2)向前移动两页
        • history.go(-2)向后移动两页
      • C: 具体可看MdnWebDocs
      • D: 在会话历史中向前移动一页。它与使用delta参数为1时调用 history.go(delta)的效果相同。

    题目2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    以下哪些代码执行后i的值为10:(多选)
    A:
    let i =1 + {
    valueOf() { return 9; }
    };

    B:
    let i = 0;
    new Array(10).forEach(() => {
    i++;
    });

    C:
    let i = parseInt('0xA');

    D:
    let i = 5;
    function a(i) {
    i *= 2;
    }
    a(i)

    • 答案

      • A,C
    • 解析

      • A: 如果在需要使用原始值的上下文中使用了对象,就会自动调用valueOf方法.这里重写了默认的valueOf 方法。所以相当于 let i = 1+9

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        // 基本数据类型的类型和引用数据类型的比较,这里会先将引用数据类型转换为数字在进行比较
        // 引用数据类型转化为基本数据类型过程如下
        // 前置知识
        //1.valueOf 返回对象的字符串、数值或布尔值表示。
        //2.toString 返回对象的字符串表示。
        //1. 先调用ValueOf方法,如果valueOf就已经将数据转化为了基本数据类型就不进行下一步转换了

        //2. 如果valueOf没有转化为字符串,那么就会调用toString方法进行再次转换
        let i =1 + {
        valueOf() { return 9; }
        };
        // 第一步: 调用valueOf
        //发现重写了valueOf,所以就返回9
        {
        valueOf() { return 9; }
        };
        // 第一步执行完成后相当于
        let i =1 + 9;
        // 所以 i = 10
      • B: new Array(10); 建立长度为10的数组,forEach方法按升序为数组中含有效值的每一项执行一次 callback函数,那些未初始化的项将被跳过

      • C: 在没有指定基数的情况下,如果字符串以”0x”或者”0X”开头, 则基数是16 (16进制)。相当于let i = parseInt(‘0xA’,16)

      • D: i是形参,属于局部变量,不影响全局变量i的值

    题目3

    1
    2
    3
    4
    5
    6
    7
    8
    按照CommonJS规范,在任何模块代码的作用域下内置了以下哪些变量?
    A: module

    B: context

    C: require

    D: exports
    • 答案

      • A,C,D
    • 解析

      • 如 Nodejs当中调用arguments.callee.toString()即可查看内置变量

    题目5

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var a =[1,4,5,2,9];
    下面求a中最大值的代码正确的是
    A: Math.max(a)

    B: Array.max(a)

    C: Math.max.call(null,a)

    D: Math.max.apply(null,a)

    E: 以上均不是
    • 答案

      • D
    • 解析

      • Math.max() 函数返回一组数中的最大值。(不支持数组)

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        console.log(Math.max(1, 3, 2));
        // expected output: 3

        console.log(Math.max(-1, -3, -2));
        // expected output: -1

        const array1 = [1, 3, 2];

        console.log(Math.max(...array1));
        // expected output: 3
      • apply,call都是使某一个方法成为一个对象的属性来调用,第一个参数如果是null或者undefined,则会变为window(非严格模式下)

        • 所以这里只能用apply,传入多个参数给Math.max 类似于Math.max(…a)
        • 不可以使用call 因为call不能将数组展开为多个参数

    题目6

    1
    2
    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
    已知数组arr = [2,20,3,12,9],现在要对数组进行遍历,只要数组存在大于10的元素,则输出true,否则输出false,则下列选项中,符合要求的是()
    A:
    var res = arr.filter((val1,val2)=>{

    return val1 > 10;

    })

    console.log(res);

    B:
    var res = arr.some((val1,val2)=>{

    return val1 > 10;
    })
    console.log(res);

    C:
    var res = arr.every((val1,val2)=>{

    return val1 > 10;

    })

    console.log(res);

    D:
    var res = arr.map((val1,val2)=>{

    return val1 > 10;
    })
    console.log(res);
    • 答案
    • 解析

    题目7

    1
    2
    3
    4
    5
    +new Array(017)   这段代码输出为?()
    A: 17
    B: NaN
    C: 15
    D: Error
    • 答案

      • B
    • 解析

      • 首先来看 new Array(017) 数字0开头,代表8进制,所以转化八进制17为十进制为15,也就是返回一个空数组,长度为15

      • +作为一元运算符时,会将参数转换为数字返回

      • 这里相当于对于一个未赋值但是长度为15的数组进行number类型转化,其结果为NaN

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/93d99268.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/9435f721.html b/9435f721.html new file mode 100644 index 000000000..14fe1d482 --- /dev/null +++ b/9435f721.html @@ -0,0 +1 @@ +今日刷题-CMD和AMD的模块化 | 梦洁小站-属于你我的小天地

    今日刷题-CMD和AMD的模块化

    题目1

    1
    2
    3
    4
    5
    下列关于 JavaScript 模块化的描述,错误的是()
    A: AMD推崇依赖前置,CMD推崇依赖就近
    B: Sea.js遵循AMD规范,RequireJS遵循CMD规范
    C: 主流的模块化包括CommonJS,AMD,CMD等
    D: 模块化有利于管理模块间的依赖,更依赖模块的维护
    • 答案
      • B
    • 解析
      • AMD(也就是Async Module definition)异步模块加载机制,比如说Require.js使用的就是AMD规范,依赖前置,所有的依赖必须要放在最前面(一次性引入)
      • CMD是由sea.js实现的(依赖就近,所有依赖需要了再引入,用到了再引入)
      • CommonJS,比如说NodeJs
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/9435f721.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/95c05373.html b/95c05373.html new file mode 100644 index 000000000..04afe4c14 --- /dev/null +++ b/95c05373.html @@ -0,0 +1 @@ +默认暴露,分别暴露,整体暴露的再次学习及常用知识 | 梦洁小站-属于你我的小天地

    默认暴露,分别暴露,整体暴露的再次学习及常用知识

    三个暴露相同点

    1. 可以理解为都是暴露出一个对象给其他使用!

    2. 无论是哪一个暴露方式,想要直接获取到暴露出去的对象,可以使用

      1
      2
      3
      import * as 名称 from "xxx.js"
      //比如
      import * as $API from "xxx.js"

    默认暴露

    1. 默认暴露js代码里面,**只可以有一个默认暴露(也就是只能由一个export default出现)**否则出现Uncaught SyntaxError: Identifier '.default' has already been declared (at 1.js:2:8)报错
    2. 暴露出一个对象,直接就可以拿来使用

    1.js内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    export default {
    a:10,
    b:100
    }
    //相当于向外暴露一个对象,对象当中只有一个default属性,值为暴露对象的值
    // 上面暴露相当于是暴露下方对象给其他使用
    {
    default:{
    a:10,
    b:100
    }
    }

    1.html内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <body>
    <script type="module">
    // 默认暴露
    // 代码等同于 import {default as content} from "./1.js"
    import content from "./1.js"

    console.log(content);//输出{a: 10, b: 100}
    </script>
    </body>

    部分(分别)暴露

    1. 将暴露的内容汇聚成为一个对象给其他使用

    1.js内容

    1
    2
    3
    4
    5
    6
    7
    export let a = 10;
    export var b = function() { };
    // 代码等同于暴露下方对象
    {
    a: 10,
    b: function(){ }
    }

    1.html内容

    1
    2
    3
    4
    5
    6
    7
    <body>
    <script type="module">
    //部分(分别)暴露 并且进行解构赋值
    import { a, b } from "./1.js";
    console.log(a,b);//10 ƒ () { }
    </script>
    </body>

    整体(统一)暴露

    1.js内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    //整体暴露
    const a = 10;
    let b = 10;
    var c = {
    age:"18"
    }
    // 整体(统一)暴露只可以写成es6简写模式!
    export {
    a,
    b,
    c
    }
    //整体(统一)暴露相当于暴露下方对象给其他使用
    {
    a:10,
    b:10,
    c:{
    age:18
    }
    }

    1.html内容

    1
    2
    3
    4
    5
    6
    7
    <body>
    <script type="module">
    //整体暴露
    import * as content from "./1.js";
    console.log(content);//输出结果看图
    </script>
    </body>

    点开后

    三种暴露获取暴露出去的对象

    1
    2
    3
    import * as 名称 from "xxx.js"
    //比如
    import * as $API from "xxx.js"

    默认暴露

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //js代码
    //默认暴露
    export default {
    a:10,
    b:100
    }
    //html代码
    <script type="module">
    // 默认暴露
    import * as content from "./1.js"
    console.log(content);//如图
    </script>
    输出结果如图

    部分(分别)暴露

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //js代码
    //部分(分别)暴露
    export var a = 10;
    export var b = function() { };

    //html代码
    <script type="module">
    // 部分(分别)暴露
    import * as content from "./1.js"
    console.log(content);//如图
    </script>
    输出结果如图

    整体(统一)暴露

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    //js代码
    //整体暴露
    const a = 10;
    let b = 10;
    var c = {
    age:"18"
    }
    export {
    a,
    b,
    c
    }

    //html代码
    <script type="module">
    // 整体暴露
    import * as content from "./1.js"
    console.log(content);//如图
    </script>
    输出结果如图

    默认暴露的引入并暴露一次性完成

    1. 在做项目的时候,有时候需要把所有的默认暴露的模块整合到一个模块当中去以便后期使用

      • 也就是 A.js ,B.js ,C.js ,D.js 模块统一在 All.js 当中引入
    2. 用法格式: import { default as 别名名称 } from "模块路径" 针对默认暴露

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      //代码等同于

      // module1.js
      export default {
      a(){},
      b(){},
      C(){},
      }

      // All.js
      export {default as module1} from "./module1.js";
      //上面一行代码等同于下面二行代码的代码效果
      //1
      import {default as module1} from "./module1.js"
      //2
      export module1 {
      a(){},
      b(){},
      C(){}
      }

    3. 或者export * as xxx from "./1.js" 针对分别暴露

    4. 后期如果需要使用到 All.js 整合暴露的东西

      1. 比如绑定在Vue原型上: import * as $API from “./All.js”; Vue.prototype.$API = $API; 后期module1就这样子this.$API.module1.a(); this.$API.module1.b(); this.$API.module1.c();

    总结

    • 默认暴露是一个对象default为属性defau1t后面值为值的一个对象只能写一次
    • 部分(分别)暴露是一个对象它是最终暴露出去的时候把所有暴露的变量自动封装到对象当中
    • 整体(统一)暴露是一个对象这个对象是我们自己需要写的,把所有的需要暴露的变量写到我们的对象当中
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/95c05373.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/96254c31.html b/96254c31.html new file mode 100644 index 000000000..f1192e96b --- /dev/null +++ b/96254c31.html @@ -0,0 +1 @@ +江西财经大学智慧江财登录分析 | 梦洁小站-属于你我的小天地

    江西财经大学智慧江财登录分析

    先抓包看看

    • 发现提交登录的参数

    如图,提交的参数

    • 然后试着搜索这些参数,看哪里用到了,关键的是password是怎么加密的,全局搜索ctrl+shift+f来进行代码搜索,定位在如下图

    • 然后我们就知道了加密方式

    逻辑总结

    1. 加密入口可以在 index 首页找到,用到了 rsa.js 里面的三个加密函数 RSAKey()setPublic()encrypt()
    2. rsa.js 里的 BigInteger() 函数依赖 jsbn.js,SecureRandom() 函数依赖 rng.js;
    3. rng.js 里的变量 rng_psize 在 prng4.js 中定义,prng_newstate() 函数也依赖 prng4.js

    提交方式

    前置知识

    • 表单的name作用:name 属性用于对提交到服务器后的表单数据进行标识

    • 注意:只有设置了 name 属性的表单元素才能在提交表单时传递它们的值。

    • 简单来说,name就是提交到后台的索引,比如在复选框中都要设置成name=”hobby”说明几个复选框都在爱好下。

    • 查看网页源代码发现,是在网页通过form表单提交的,并不是通过axios或者加jQuery进行js提交,可以看看下面的代码,通过form表单提交到/cas/login

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    <form id="fm1" action="`" method="post" autocomplete="off">
    <div id="errorMessage" class="error" style="display:none;">
    <div class="error-massage"></div>
    </div>
    <table cellpadding="0" cellspacing="0">

    <tr>
    <td colspan="2">
    <input id="username" name="username" type="text" class="user" placeholder="校园卡号/首次登录请激活" value="" />
    <input type="text" class="user" style="display: none;" />
    </td>
    </tr>
    <tr>
    <td colspan="2">
    <input id="passwordEnc" name="password" type="hidden" value="" />
    <input id="password" type="password" class="pw" placeholder="统一身份认证密码" />
    <input type="text" class="pw" style="display: none;" />
    </td>
    </tr>
    <tr id="imageCode" style="display:none;">
    <td colspan="2" style="position:relative;">
    <input id="errors" name="errors" type="hidden" value="0" />
    <input id="imageCodeName" name="imageCodeName" type="text" size="4" class="yzm" placeholder="验证码" /><input type="text" class="yzm" style="display: none;" />
    <div style="position:absolute; top:5px; right:0;">
    <img width="100" style="height:2.5rem;" src='/cas/codeimage' style="cursor: pointer;" onclick="this.src='/cas/codeimage?'+Math.floor(Math.random()*100)" />
    </div>
    </td>
    </tr>
    <tr style="display:none;" id="rememberPwd">
    <td colspan="2" style="height: 15px;"><input id="ckbRememberPP" name="rememberMe" type="checkbox" value="true"/><input type="hidden" name="_rememberMe" value="on"/><label for="ckbRememberPP" style="vertical-align: middle;height: 13px;">记住密码,2周内自动登录</label></td>
    </tr>
    <tr>
    <td colspan="2"><input type="submit" value="登 录" onclick="javascript:return checkInput();" /></td>
    </tr>
    <tr>
    <td colspan="2">
    <a href='https://ssl.jxufe.edu.cn/uid/activateAuth?t=20161208120000' class="zhjh-a" target="_blank">账号激活</a><a href='https://ssl.jxufe.edu.cn/uid/forget?t=20161208120000' class="wjmm-a" target="_blank">忘记密码</a>
    </td>
    </tr>
    <tr>
    <td class="line" width="60%">
    <a href="pages/account_activate.html?t=20161208120000" class="zhjh">统一认证账号激活攻略</a>
    <div class="clear-1"></div>
    <a href="pages/password_forget.html?t=20161208120000" class="mmzh">信息门户密码找回攻略</a>
    </td>
    <td class="line" align="right" width="40%">
    <div align="center" class="ewm">
    <img src="images/ewm.jpg" width="70"><br>移动门户二维码
    </div>
    </td>
    </tr>
    </table>
    <input type="hidden" name="cryptoType" value="1" />
    <input type="hidden" name="lt" value="_c4ECACA77-B52F-B3C8-EF78-7F2DE9C616E0_k8175C76F-F064-D984-F521-22035A30AD09" />
    <input type="hidden" name="_eventId" value="submit" />
    </form>

    加密示例代码

    关键代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //RSA加密
    const {
    RSAKey
    } = require("./tools/rsa");

    var rsa = new RSAKey();
    //如果下面二个参数变动了,就去官网看看源代码就可以
    var n = "5598e3b75d21a2989274e222fa59ab07d829faa29b544e3a920c4dd287aed9302a657280c23220a35ae985ba157400e0502ce8e44570a1513bf7146f372e9c842115fb1b86def80e2ecf9f8e7a586656d12b27529f487e55052e5c31d0836b2e8c01c011bca911d983b1541f20b7466c325b4e30b4a79652470e88135113c9d9";
    var e = "10001";

    //密码加密
    rsa.setPublic(n, e);
    var encodedPassword = rsa.encrypt('123456789');
    //输出加密的密码
    console.log(encodedPassword);

    注意

    二个参数可能会发生变动,就去官网看看源代码就可以

    附带下登录的js

    • 本来想写一个查询成绩的,后面发现不会,抓包分析也不会…跳来跳去的那个认证….

    ts版本

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102

    const axios = require('axios');
    const qs = require('qs');
    const { RSAKey } = require("./tools/rsa");
    const cheerio = require("cheerio");

    const nDefault = '5598e3b75d21a2989274e222fa59ab07d829faa29b544e3a920c4dd287aed9302a657280c23220a35ae985ba157400e0502ce8e44570a1513bf7146f372e9c842115fb1b86def80e2ecf9f8e7a586656d12b27529f487e55052e5c31d0836b2e8c01c011bca911d983b1541f20b7466c325b4e30b4a79652470e88135113c9d9';
    const eDefault = '10001';

    class LoginAndGet {
    n: string;
    e: string;
    userName: string;
    passWord: string;
    encodedPassword: string;//加密过后的密码
    constructor(userName: string, passWord: string, n: string = nDefault, e: string = eDefault) {
    this.n = n;
    this.e = e;
    this.userName = userName;
    this.passWord = passWord;
    this.getRSAPassword(this.passWord);
    }
    /* 获取加密后的密码 */
    getRSAPassword(originPass: string): void {
    let rsa = new RSAKey();
    //设置加密
    rsa.setPublic(this.n, this.e);
    //进行加密
    this.encodedPassword = rsa.encrypt(originPass);
    }

    /* 开始 */
    start() {
    this.getIt();
    }

    /* 获取it参数 */
    getIt(): void {
    axios({
    method: 'get',
    url: 'https://ssl.jxufe.edu.cn/cas/login',
    headers: {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36',
    }
    }).then((data) => {
    var Data = [];
    Data.push(data.headers['set-cookie'][0].toString().match(/\w*(?=\.)/m).join()); //获取JSESSIONID
    Data.push(data.headers['set-cookie'][1].toString().match(/\w*(?=;)/m).join()); //获取sessoinMapKey
    Data.push(cheerio.load(data.data)(":input[name='lt']").attr('value')); //获取It
    // )
    console.log("载入页面获取的东西", Data);
    return Data;
    }).then(data => {
    // console.log(data);
    // JSESSIONID 0索引和 sessoinMapKey 1 和It 2
    this.loginF(data[2], data[1], data[0], this.userName, this.encodedPassword);
    });
    }
    /* 登录 */
    loginF(lt: string, sess: string, jsess: string, userName: string, encodedPassword: string) {
    //传送的数据
    var data = qs.stringify({
    'username': userName,
    'password': encodedPassword,
    'errors': '0',
    '_rememberMe': 'on',
    'cryptoType': '1',
    '_eventId': 'submit',
    'imageCodeName': '',
    'lt': lt
    });
    //axios配置
    var config = {
    method: 'post',
    url: 'https://ssl.jxufe.edu.cn/cas/login?jsessionid=' + jsess + '.cas_app_2',
    // url: 'https://ssl.jxufe.edu.cn/cas/login',
    headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
    'User-Agent': ' Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36',
    'Cookie': 'JSESSIONID=' + jsess + '.cas_app_2;sessoinMapKey=' + sess + '; jxufe_username=' + userName,
    'Referer': 'https://ssl.jxufe.edu.cn/cas/login',
    'Origin': 'https://ssl.jxufe.edu.cn',
    },
    data: data
    };
    //发送请求
    axios(config)
    .then(function (response) {
    var result = cheerio.load(response.data)(".error-massage span").text();
    console.log("登录结果为:" + result);
    console.log('cookie1为',response.headers);
    })
    .catch(function (error) {
    console.log(error);
    });
    }

    }

    //测试代码
    var test = new LoginAndGet('2202140875', 'superBiuBB');
    test.start();
    • 转换后的js
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    var axios = require('axios');
    var qs = require('qs');
    var RSAKey = require("./tools/rsa").RSAKey;
    var cheerio = require("cheerio");
    var nDefault = '5598e3b75d21a2989274e222fa59ab07d829faa29b544e3a920c4dd287aed9302a657280c23220a35ae985ba157400e0502ce8e44570a1513bf7146f372e9c842115fb1b86def80e2ecf9f8e7a586656d12b27529f487e55052e5c31d0836b2e8c01c011bca911d983b1541f20b7466c325b4e30b4a79652470e88135113c9d9';
    var eDefault = '10001';
    var LoginAndGet = /** @class */ (function () {
    function LoginAndGet(userName, passWord, n, e) {
    if (n === void 0) { n = nDefault; }
    if (e === void 0) { e = eDefault; }
    this.n = n;
    this.e = e;
    this.userName = userName;
    this.passWord = passWord;
    this.getRSAPassword(this.passWord);
    }
    /* 获取加密后的密码 */
    LoginAndGet.prototype.getRSAPassword = function (originPass) {
    var rsa = new RSAKey();
    //设置加密
    rsa.setPublic(this.n, this.e);
    //进行加密
    this.encodedPassword = rsa.encrypt(originPass);
    };
    /* 开始 */
    LoginAndGet.prototype.start = function () {
    this.getIt();
    };
    /* 获取it参数 */
    LoginAndGet.prototype.getIt = function () {
    var _this = this;
    axios({
    method: 'get',
    url: 'https://ssl.jxufe.edu.cn/cas/login',
    headers: {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36'
    }
    }).then(function (data) {
    var Data = [];
    Data.push(data.headers['set-cookie'][0].toString().match(/\w*(?=\.)/m).join()); //获取JSESSIONID
    Data.push(data.headers['set-cookie'][1].toString().match(/\w*(?=;)/m).join()); //获取sessoinMapKey
    Data.push(cheerio.load(data.data)(":input[name='lt']").attr('value')); //获取It
    // )
    console.log("载入页面获取的东西", Data);
    return Data;
    }).then(function (data) {
    // console.log(data);
    // JSESSIONID 0索引和 sessoinMapKey 1 和It 2
    _this.loginF(data[2], data[1], data[0], _this.userName, _this.encodedPassword);
    });
    };
    /* 登录 */
    LoginAndGet.prototype.loginF = function (lt, sess, jsess, userName, encodedPassword) {
    //传送的数据
    var data = qs.stringify({
    'username': userName,
    'password': encodedPassword,
    'errors': '0',
    '_rememberMe': 'on',
    'cryptoType': '1',
    '_eventId': 'submit',
    'imageCodeName': '',
    'lt': lt
    });
    //axios配置
    var config = {
    method: 'post',
    url: 'https://ssl.jxufe.edu.cn/cas/login?jsessionid=' + jsess + '.cas_app_2',
    // url: 'https://ssl.jxufe.edu.cn/cas/login',
    headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
    'User-Agent': ' Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36',
    'Cookie': 'JSESSIONID=' + jsess + '.cas_app_2;sessoinMapKey=' + sess + '; jxufe_username=' + userName,
    'Referer': 'https://ssl.jxufe.edu.cn/cas/login',
    'Origin': 'https://ssl.jxufe.edu.cn'
    },
    data: data
    };
    //发送请求
    axios(config)
    .then(function (response) {
    var result = cheerio.load(response.data)(".error-massage span").text();
    console.log("登录结果为:" + result);
    console.log('cookie1为', response.headers);
    })["catch"](function (error) {
    console.log(error);
    });
    };
    return LoginAndGet;
    }());
    //测试代码
    var test = new LoginAndGet('2202140875', 'superBiuBB');
    test.start();

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/96254c31.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/9df9c857.html b/9df9c857.html new file mode 100644 index 000000000..c996f1f7e --- /dev/null +++ b/9df9c857.html @@ -0,0 +1 @@ +MATLAB课程设计(非库函数实现高斯模糊,边缘检测,傅里叶等操作-基础) | 梦洁小站-属于你我的小天地

    MATLAB课程设计(非库函数实现高斯模糊,边缘检测,傅里叶等操作-基础)

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/9df9c857.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/9e915fa8.html b/9e915fa8.html new file mode 100644 index 000000000..6bb25b278 --- /dev/null +++ b/9e915fa8.html @@ -0,0 +1 @@ +2022年11月07日刷题-CSS 如何使用服务端的字体 | 梦洁小站-属于你我的小天地

    2022年11月07日刷题-CSS 如何使用服务端的字体

    CSS 如何使用服务端的字体?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    CSS 如何使用服务端的字体?

    A:@font-face

    B:font-family

    C:font

    D:@font-family
    • 答案

      • @font-face
    • 解析

      • 使用@font-face定义一个字体名称,然后使用font-family使用,就有点类似于@keyframs一样的使用
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      <html>
      <head>
      <title>Web Font Sample</title>
      <style type="text/css" media="screen, print">
      @font-face {
      font-family: "Bitstream Vera Serif Bold";
      src: url("https://developer.mozilla.org/@api/deki/files/2934/=VeraSeBd.ttf");
      }

      body { font-family: "Bitstream Vera Serif Bold", serif }
      </style>
      </head>
      <body>
      This is Bitstream Vera Serif Bold.
      </body>
      </html>

      文本「Hello, world.」显示的颜色是?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    文本「Hello, world.」显示的颜色是?

    <style>
    #content .text {text-color:red;}
    #content>.title {color:green;}
    #content div.title {font-color:blue;}
    strong {font-color:yellow;}
    * {color:black;}
    </style>

    <div id="content">
    <span class="text"><strong class="title">Hello, world.</strong></span>
    </div>

    A: red

    B: yellow

    C: blue

    D: black

    E: green
    • 答案

      • E
    • 解析

      1
      2
      3
      4
      5
      6
      仔细看这一段 css,其中 text-color:red 和 font-color:blue 以及 font-color:yellow 都是无效的
      也就是说,只剩下了 #content>.title {color:green;} 和 * {color:black;}
      其中 > 选择器是直系后代,在 DOM 中 <strong> 标签和 <div id="content"></div> 中间还隔了一层 <span> 标签,所以 #content>.title {color:green;} 也是无法作用于无效 <strong> 标签

      只剩下了 * {color:black;} 所以答案为 D
      同学们(敲黑板!),这道题考的是视力啊!

      CSS 中可继承的属性有哪些

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    CSS 中可继承的属性有哪些
    A:height

    B:font-size

    C:border

    D:width

    E:color
    • 答案

      • height,color
    • 解析

      • css继承,代表子元素可以使用父元素的样式属性
      • 颜色,字体,行高,间距,对其方式和列表的样式,都可以继承
      1
      2
      3
      4
      所有元素可继承:visibility和cursor。
      内联元素可继承:letter-spacing、word-spacing、white-space、line-height、color、font、font-family、font-size、font-style、font-variant、font-weight、text-decoration、text-transform、direction。
      终端块状元素可继承:text-indent和text-align。
      列表元素可继承:list-style、list-style-type、list-style-position、list-style-image。

      基于以下 HTML 结构,以下关于 main1.css 和 main2.css 的描述有哪些是正确的?

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    基于以下 HTML 结构,以下关于 main1.css 和 main2.css 的描述有哪些是正确的?
    <head>
    <link href="main1.css" rel="stylesheet"/>
    <link href="main2.css" ref="stylesheet"/>
    </head>
    A:main1.css 和 main2.css 同时开始加载,先加载完成的优先解析

    B:如果 main1.css 和 main2.css 中有相同的选择器规则,那么 main2.css 中的规则将合并 main1.css 的规则


    C:main2.css 只有在 main1.css 加载并解析后,才开始加载


    D:如果 main1.css 和 main2.css 中有相同的选择器规则,那么 main2.css 中的规则将被忽略

    • 答案

      • A,B
    • 解析

      • link标签是同时加载的,script标签才会加载完一个再加载另外一个
      • 其实也很好记忆,你想想看到的网页,即使一个css样式没有加载,后面的样式也不会受到影响,所以你只能看到网页某一块地方缺了样式
      • 而js加载的话设计到网页交互规则,必须要加载完后加载另外一个js,否者前一个js文件的规则可能会被后一个js文件所使用,不按顺序加载就会出问题

      以下关于跨域的描述哪些是正确的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    以下关于跨域的描述哪些是正确的:

    A:Web 字体、图片等资源文件加载都不受浏览器跨域限制

    B:CSS 文件的加载不受跨域限制

    C:window.onerror 方法默认情况下无法获取跨域脚本的报错详情

    D:canvas 中使用 drawImage 贴图会受跨域限制

    • 答案
      • A,B,C
    • 解析
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      css文件的加载肯定不收跨域限制,a站点能引用B站点的样式d
      canvas的drawImage使用跨域图片,会报错
      解决方案1、
      如果图片不大不多可以使用base64
      解决方案2、
      实例的image对象的设置img.crossOrigin = ' ';并且在服务器端设置Access-Control-Allow-Origin:*(或运行的域名

      如果想通过onerror函数收集不同域的js错误,我们需要做两件事:

      相关的js文件上加上Access-Control-Allow-Origin:*的response header
      引用相关的js文件时加上crossorigin属性
      链接:https://www.jianshu.com/p/315ffe6797b8
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/9e915fa8.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/9ff8515e.html b/9ff8515e.html new file mode 100644 index 000000000..52c224b89 --- /dev/null +++ b/9ff8515e.html @@ -0,0 +1 @@ +vue3.x项目图书兄弟项目上遇到的问题及解决办法的记录 | 梦洁小站-属于你我的小天地

    vue3.x项目图书兄弟项目上遇到的问题及解决办法的记录

    1.vue3中reactive定义的引用类型直接赋值导致数据失去响应式

    • 这里写的很详细,想看可以看看@地址,我这里说下结果就可以~
    • reactive定义的数组或者对象,不能直接对整体进行赋值修改,否则定义的数据将失去响应性。

    失去响应式示例

    • 当经过一秒后,页面显示的内容并没有被改变!
    1
    2
    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
    <template>
    <div style="border: 1px solid red" class="content">
    {{ banner }}
    </div>
    </template>

    <script>
    import { reactive, onMounted } from "vue";
    export default {
    name: "HomeView",
    components: {},
    setup() {
    //预先定义一个数据填充占位
    var banner = reactive({});
    onMounted(() => {
    setTimeout(() => {
    banner = { name: "李白", age: "18" };
    console.log("修改值完成");
    }, 2000);
    });

    return {
    banner,
    };
    },
    };
    </script>

    2.vue使用插槽的简写和使用默认插槽

    使用插槽简写v-slot直接用字符’#’代替

    • 使用Navbar组件当中的默认插槽 v-slot:default等同于#default
    1
    2
    3
    <Navbar>
    <template #default> 商品分类 </template>
    </Navbar>

    3.flex:1是什么意思在弹性布局当中

    • flex属性是以下三个属性的简写(用于弹性元素-也就是父元素被设置了display:flex,然后子元素就可以设置flex属性)

      • flex-grow :设置增长系数(也就是如何分配剩余的空闲空间) 如果元素不是弹性盒对象的元素,则 flex-grow 属性不起作用。
      • flex-shrink :设置收缩系数
      • flex-basis:设置基准值
    • 当我们设置为flex:1的时候,相当于设置了 flex:1 1 0 也就是设置了flex-grow,其他都设置默认值

    • 经常用来设置元素不管其内容如何,使所有弹性项目的长度均相同

    • 具体可以看看MdnWeb @flex属性

    4.vue3使用编程式导航

    • vue2的时候,编程式导航都是通过this.$router.push等方法来进行的,但是到了vue3,是没有this了~

    vue3使用编程式导航和获取当前路由对象

    • 首先导入

      1
      2
      3
      4
      5
      //为了获取路由总管 --- 注意,这里有字母r
      import {useRouter} from "vue-router";

      //为了获取当前自身路由信息 --- 注意,这里没有字母r
      import {useRoute} from "vue-router";
    • setup当中调用useRouter方法获取路由总管 和调用useRoute方法获取自身路由信息

      • const router = useRouter(); 这样子就相当于vue2时候的this.$router
      • const route = useRouter(); 这样子就相当于vue2时候的this.$route
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import { useRouter, useRoute } from "vue-router";
    export default {
    name: "HomeView",
    components: {},
    setup() {
    //路由总管
    const router = useRouter();
    //自身路由
    const route = useRoute();
    //输出查看
    console.log(route);
    //编程式导航
    function clickTo() {
    router.push({ path: "/about" });
    }
    return { clickTo };
    },
    };

    5.设置超出容器内容隐藏并添加省略号

    • 设置overflow:hidden white-space:nowrap text-overflow:ellipsis

    示例

    1
    2
    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
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>超出部分添加省略号</title>
    <style>
    *{
    margin:0;
    padding: 0;
    }
    #over{
    /* 设置下位置 */
    margin: 20px auto;
    width: 200px;
    height: 40px;
    border: 1px solid red;
    /* 查出隐藏 */
    overflow: hidden;
    text-overflow: ellipsis;/* 文本超出省略号 */
    white-space: nowrap;/* 连续的空白符会被合并。但文本内的换行无效 */
    }
    </style>
    </head>
    <body>
    <div id="over">
    <span>我认为, 文森特·皮尔说过一句富有哲理的话,改变你的想法,你就改变了自己的世界。这不禁令我深思。 的发生,到底需要如何做到,不的发生,又会如何产生。 的发生,到底需要如何做到,不的发生,又会如何产生。 这样看来, 佚名说过一句富有哲理的话,感激每一个新的挑战,因为它会锻造你的意志和品格。这似乎解答了我的疑惑。 要想清楚,,到底是一种怎么样的存在。 总结的来说, 培根曾经说过,合理安排时间,就等于节约时间。这不禁令我深思。</span>
    </div>

    </body>
    </html>

    6.vue3的ref来获取dom元素

    • 1.元素添加ref属性
      • 比如<div ref="content">我是内容</div>
    • 2.setup当中添加const 刚刚添加的ref属性名称 = ref(null)
      • const content = ref(null) 注意,变量名字必须要和ref属性名字一样!
    • 3.通过setup返回,然后就可以在其他当中调用了
    • 注意
      • 需要在return当中返回
      • 需要添加.value才可以正确获取到dom节点(因为返回的是引用示例,所以需要在访问一层才可以)

    示例代码

    • 通过ref来读取属性并输出里面的内容
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <template>
    <div style="border: 1px solid red" ref="content">我是动感超人</div>
    </template>

    <script>
    import { onMounted } from "vue";
    import { ref } from "vue";
    export default {
    name: "HomeView",
    components: {},
    setup() {
    const content = ref(null);
    onMounted(() => {
    //输出'我是动感超人'
    console.log(content.value.textContent);
    });
    return {
    content,
    };
    },
    };
    </script>

    7.getBoundingClientRect

    功能

    示例说明

    • 主要关注#box的样式
    1
    2
    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
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    </head>

    <body>
    <style>
    #box {
    width: 400px;
    height: 200px;
    padding: 20px;
    margin: 50px auto;
    background: purple;
    }
    </style>
    <div id="box"></div>
    <script>
    var object = document.querySelector("#box");
    var objectDomInfo = object.getBoundingClientRect();
    console.log(objectDomInfo);
    </script>

    </body>

    </html>
    • getBoundingClientRect返回对象的属性
    1
    2
    3
    4
    5
    6
    7
    8
    bottom: 290
    height: 240
    left: 539.6000366210938
    right: 979.6000366210938
    top: 50
    width: 440
    x: 539.6000366210938
    y: 50
    • getBoundingClientRect返回对象的属性说明

      • x: /left: 元素左上角顶点相当于当前浏览器视口的左边距离

      • y: /top:元素左上角顶点相对于当年浏览器视口的顶部距离


      • width:400+20+20 = 440 (width+padding-left+padding-right)

        • 当前元素的width+paddin-left+padding-right的值
      • height:200+20+20 = 240 (height+padding-top+padding-bottom)

        • 当前元素的height+padding-top+padding-bottom的值
      • 注意点:

        • width/height 是与元素的 width/height + padding 相等的。

      • bottom:元素右下角顶点相对于当年浏览器视口顶部的距离

      • right:元素右下角顶点相对于当年浏览器视口的左边的距离

    8.better-scroll

    • @中文文档

    • 安装

      1
      npm install better-scroll --save
    • 引入

      1
      import BetterScroll from "better-scroll";
    • 使用

    1
    2
    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
    35
    36
    import BetterScroll from "better-scroll";
    import throttle from "lodash/throttle";
    import debounce from "lodash/debounce";
    setup(){
    //一般在dom结构创建完成后调用
    onMounted(()=>{
    // 创建BetterScroll对象
    bs = new BetterScroll(".home-wrapper", {
    probeType: 3,
    click: true,
    pullUpLoad: true,
    });
    })
    //添加滚动事件,使用下节流阀
    bs.on(
    "scroll",
    throttle((position) => {
    //position.y可以获取滚动距离
    }, 100)
    );
    //当拉动到底部的时候
    bs.on(
    "pullingUp",
    throttle(() => {
    console.log("到底部了");
    //发送ajax请求获取新页

    //....

    //完成上划动作
    bs.finishPullUp();
    bs.refresh();
    }, 80)
    );

    }

    9.手机端常用的左边分类,上部标签页,右部分布局页的结构代码

    效果图

    代码

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    <template>
    <div>
    <Navbar>
    <template #default> 商品分类 </template>
    </Navbar>
    <div class="category-wrapper">
    <!-- 排序分布栏 -->
    <div class="navbar">
    1
    </div>
    <!-- 左侧分类列表 -->
    <div class="left-list">2</div>
    <!-- 书籍展示 -->
    <div class="show-list">3</div>
    </div>
    </div>
    </template>

    <script>
    import Navbar from "@/components/common/navbar/Navbar";
    export default {
    name: "Category",
    components: {
    Navbar,
    },
    };
    </script>

    <style lang="less" scoped>
    .category-wrapper {
    //隔开顶部nab
    margin-top: 45px;
    display: flex;
    // 为左侧分类列表的宽度
    @leftItemWidth: 130px;
    //nav的宽度
    @navBarHidth: 40px;
    .navbar {
    background-color: red;
    float: right;
    position: fixed;
    right: 0;
    height: @navBarHidth;
    left: @leftItemWidth;
    z-index: 10;
    }
    // 左侧分类列表
    .left-list {
    position: fixed;
    width: @leftItemWidth;
    left: 0;
    height: 100%;
    background-color: green;
    }
    // 书籍展示
    .show-list {
    background-color: blue;
    position: absolute;
    right: 0;
    // nav的宽度 + 顶部的商品分类宽度
    top: @navBarHidth+45;
    left:@leftItemWidth;
    height: 100%;
    }
    }
    </style>

    10.深度选择器vue3

    • 通过:deep(选择器) { .... } 来实现,具体看代码~

    • ::v-deep在vue3被废弃了,不推荐使用,推荐使用:deep()这种方式

    示例

    • less代码示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <style lang="less" scoped>
    #address {
    margin-top: 45px;
    .content {
    background: white;
    :deep(.van-radio__icon) {
    display: none;
    }
    }
    :deep(.van-address-list__bottom){
    bottom: 50px;
    background-color:unset;
    }
    }
    </style>

    11.vant组件库van-collapse 下拉折叠面板出现Collapse: “v-model” should not be Array in accordion mode

    • 原因
      • 默认的下拉折叠面板是可以展示多个数据的,所以v-model绑定的是一个数组,但是这里使用了accordion属性,就只记录一个的值,不需要使用到数组,所以就出现这个提示
    • 解决办法
      • v-model=”activeNames”当中的activeNames变量值改为一个基本数据类型即可
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    <!-- v-model="activeNames" -->
    由于这里使用了只能选中一个,并且这里为什么取名叫activeNames,是记录展开的列表项,但是
    但是这里使用了accordion属性,就只记录一个,就没有必要用数组了
    <van-collapse v-model="activeNames" accordion>
    <!-- title为一级列表的展示名 -->
    <van-collapse-item
    v-for="(firstLevel, index) in categoryList"
    :key="firstLevel.id"
    :title="firstLevel.name"
    :name="firstLevel.name"
    >
    <!-- 二级列表 -->
    <van-sidebar v-model="active">
    <van-sidebar-item
    v-for="(secondLevel,index) in firstLevel.children"
    :key="secondLevel.id"
    :title="secondLevel.name"
    />
    </van-sidebar>
    </van-collapse-item>
    </van-collapse>


    setup() {
    //左分类默认项
    const active = ref(0);
    但是这里使用了accordion属性,就只记录一个,就没有必要用数组了
    //const activeNames = ref([]);
    const activeNames = ref();//这样子就可以

    const categoryList = ref([]);
    onMounted(async () => {
    let result = await reqCategory();
    //保存存储数据
    categoryList.value = result.categories;
    });
    return {
    active,
    activeNames,
    categoryList,
    };
    },

    14.vant组件库sidebar(侧边导航) 和 Collapse(折叠面板)的结合使用

    效果:

    代码:

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    <!-- 左侧分类列表 -->
    <div class="left-list">
    <div class="left-item-content">

    <van-sidebar v-model="activeSecond">
    <!-- 当做一个整体 -->
    <van-collapse v-model="activeName" accordion>
    <!-- 循环一级菜单 -->
    <van-collapse-item
    v-for="item in categoryList"
    :key="item.id"
    :title="item.name"
    :name="item.name">

    <!-- 循环二级菜单 -->
    <van-sidebar-item
    v-for="sub in item.children"
    :title="sub.name"
    :key="sub.id"/>

    </van-collapse-item>
    </van-collapse>
    </van-sidebar>
    </div>
    </div>
    setup当中内容
    import { ref } from "vue";
    export default {
    name: "HomeView",
    components: {},
    setup() {
    //设置为99999,就不会出现初始化的时候单击任意一级菜单看到二级菜单选中的情况了
    const activeSecond = ref(99999); //二级分类列表目前活动项
    const categoryList = ref([]); //左侧分类列表
    const activeName = ref(); //一级分类列表的活动项记录
    return {
    activeSecond,
    categoryList,
    activeName
    };
    },
    };

    12.linear-gradient的知识点

    linear-gradient确认起点和终点

    • linear-gradient线性渐变是通过一个定义的,这个轴称为渐变轴/渐变线。如图,红色的为渐变轴/渐变线,正方形的为div容器示例

    • 轴通过顺时针旋转来进行设置起点和终点

      • 设置完成起点和终点后,将颜色进行分布(如果没有对颜色位置进行分布,则平均分配颜色位置)
    • linear-gradient默认方向为to bottom

    linear-gradient常见的绘制方向的坐标轴和效果示例

    • to top, to bottom, to leftto right 这些值会被转换成角度 0 度、180 度、270 度和 90 度。
    • 简单理解方向
      • to 指明终点渲染的方向

    1.background-image:linear-gradient(red, green);

    • 默认情况轴被旋转了180度
    • 等同于linear-gradient(to bottom,red,green)
    • 默认值to bottom转化为180度

    background-image:linear-gradient(red, green)

    2.background-image: linear-gradient(to top,red,green);

    • 轴没有被旋转的情况
    • 0度

    background-image: linear-gradient(to top,red,green)

    3.background-image: linear-gradient(to left,red,green);

    • 轴被旋转270度的情况
    • 270 度

    background-image: linear-gradient(to left,red,green)

    4.background-image: linear-gradient(to right, red,green);

    • 轴被旋转90度的情况
    • 90度

    background-image: linear-gradient(to right, red,green)

    5.background-image: linear-gradient(45deg, red,green);

    • 轴被旋转45度的情况
    • 45度

    6.示例代码

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    </head>
    <body>

    <div id="div1">
    div1
    </div>
    <div id="div2">
    div2
    </div>
    <div id="div3">
    div3
    </div>
    <div id="div4">
    div4
    </div>
    <div id="div5">
    div5
    </div>
    <style>
    #div1,#div2,#div3,#div4,#div5{
    float: left;
    width: 200px;
    height: 200px;
    margin: 10px 5px;
    }
    /* 指定渐变线,默认是to bottom */
    /* to top, to bottom, to left 和 to right
    这些值会被转换成角度 0 度、180 度、270 度和 90 度。
    其余值会被转换为一个以向顶部中央方向为起点顺时针旋转的角度。渐变线的结束点与其起点中心对称。 */
    #div1{
    /* 等同于linear-gradient(to bottom,red,green),轴旋转了180度 */
    background-image:linear-gradient(red, green);
    }
    #div2{
    /* 轴旋转了0度 */
    background-image: linear-gradient(to top,red,green);
    }
    #div3{
    /* 轴旋转了270度 */
    background-image: linear-gradient(to left,red,green);
    }
    #div4{
    /* 轴旋转了90度 */
    background-image: linear-gradient(to right, red,green);
    }
    #div5{
    /* 轴被旋转45度的情况 */
    background-image: linear-gradient(45deg, red,green);
    }
    </style>
    </body>
    </html>

    linear-gradient颜色绘制参数理解

    • 就是渐变轴上颜色分布情况,和ps有些类似

    示例

    • linear-gradient(0deg, blue, green 40%, red);
    • 从下到上,从蓝色开始渐变、到高度40%位置是绿色渐变开始、最后以红色结束
    • 演示结构图

    • 效果图

    13.vant表单验证实现邮箱为空提示信息1,格式不正确提示信息2

    • 看代码就可以,在配置rules的时候,传入多个配置对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <van-field
    autocomplete
    v-model="email"
    type="text"
    label="电子邮箱"
    name="pattern"
    placeholder="请输入邮件"
    :rules="[
    //为空提示这个
    { required: true, message: '请填写您的邮箱!' }, //正则出错提示这个
    { pattern: /^[a-z0-9]+([._\\-]*[a-z0-9])*@([a-z0-9]+[-a-z0-9]*[a-z0-9]+.){1,63}[a-z0-9]+$/, message: '邮箱格式错误!'}]" />

    14.vant组件库Toast轻提示全局注册没有用并且样式丢失了

    • 解决:原来的全局注册换成哪里用到就引入

      • import {Toast} from "vant";
    • 样式丢失错误

    • 解决错误

      • 引入样式,我们需要使用哪个组件,就引入哪个组件样式即可,比如我们只使用按钮组件,则只需要引入按钮样式,如下:
      • import 'vant/lib/toast/style'

    15.vue3.x组件当中和js当中使用vuex

    • 组件当中(.vue)

      • 引入import {useStore} from "Vuex";
      • 然后调用useStore返回的值相当于vue2.x时候的this.$store
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      import { useStore } from "vuex";
      export default {
      name: "HomeView",
      components: {},
      setup() {
      //使用vuex仓库,等同于vue时候的this.$store
      const store = useStore();
      //获取 Authorization 当中的值
      console.log(store.state.Authorization);
      return {

      };
      },
      };
    • js当中使用

      • 首先引入import store from "@store/index.js"
      • 然后这个store相当于vue2时候的的this.$store

    16.vant的Toast(轻提示)设置不自动关闭,后面手动关闭

    • vant的回答解决办法 @地址 也就下面这种方法
    • 通过设置配置对象当中的duration:0 即可
    1
    Toast.loading({message:"正在加载...",forbidClick:true,duration:0})
    • 然后后面可以调用Toast.clear()来关闭提示

    17.window.localStorage.getItem获取不到指定的key值,返回的是null而不是undefined

    18.vue3使用keep-alive路由缓存保活

    添加路由缓存/保活

    • 1.在需要缓存的路由组件添加元信息
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    const routes = [
    {
    path: '/',
    name: 'home',
    component: HomeView
    },
    {
    path: '/',
    name: "Home",
    component: Home,
    meta: {
    title: "梦洁小站-图书兄弟",
    keepAlive: true, // 组件需要缓存
    },
    },
    ]
    • 2.App.vue原来的<router-view></router-view>删除
    • 3.App.vue更换为如下代码的路由组件
    1
    2
    3
    4
    5
    6
    7
    8
    <router-view v-slot="{ Component }">
    <transition>
    <!-- includeList变量需要通过setup返回 -->
    <keep-alive :include="includeList" :key="Component">
    <component :is="Component" />
    </keep-alive>
    </transition>
    </router-view>
    • 4.App.vue组件当中添加如下内容
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    import { onMounted, toRefs, reactive, watch } from "vue";
    import {useRoute} from "vue-router";
    export default {
    name: "App",
    setup() {
    const route = useRoute();
    //设置需要缓存的路由组件
    const state = reactive({
    includeList: [],
    });
    onMounted(() => {
    //监视路由的改变,如果改变了,则添加进入路由组件
    watch(() => route,(newVal,oldVal)=>{
    //读取当前路由组件的元数据,看是否有KeepAlive并且为true
    //并且在缓存列表includeList当中不存在当前组件才添加进入缓存组件
    if(newVal.meta.keepAlive && state.includeList.indexOf(newVal.name) === -1){
    state.includeList.push(newVal.name);
    }
    },{deep:true}) // 开启深度监听,不然没有效果
    return {
    //提供给`router-view`组件使用变量名
    ...toRefs(state),
    };
    },
    };

    使用失效的问题

    • 我发现是vue-router当中index.js当中的name属性vue组件当中的name值不匹配导致的,改为相同就可以

    • 还有就是,name属性名称第一个字母大写…(为了迎合组件当中name属性的写法)之前我一直小写的

    • 其他失效可以看看这个博客 @vue 的 keep-alive include 属性不生效问题

    19.vue3项目本地热更新时报错TypeError: parentComponent.ctx.deactivate is not a function

    报错信息

    • 解决方法: 在keep-alive、component上设置key进行排序(即加个key)

    • 改之前的代码会报这个错

    1
    2
    3
    4
    5
    6
    7
    <router-view v-slot="{ Component }">
    <transition>
    <keep-alive :include="includeList" >
    <component :is="Component" />
    </keep-alive>
    </transition>
    </router-view>
    • 改之后,添加key
    1
    2
    3
    4
    5
    6
    7
    <router-view v-slot="{ Component }">
    <transition>
    <keep-alive :include="includeList" :key="Component">
    <component :is="Component" />
    </keep-alive>
    </transition>
    </router-view>
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/9ff8515e.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    avatar
    梦洁
    小小的字,有大大的梦想~分享我的前端学习过程,经历,错误,和一些其他折腾过程
    关注下我(* ̄▽ ̄*)
    公告
    不断更新中,有问题请留言回复(会通过邮箱提醒~)
    目录
    1. 1. 1.vue3中reactive定义的引用类型直接赋值导致数据失去响应式
      1. 1.1. 失去响应式示例
    2. 2. 2.vue使用插槽的简写和使用默认插槽
      1. 2.1. 使用插槽简写v-slot直接用字符’#’代替
    3. 3. 3.flex:1是什么意思在弹性布局当中
    4. 4. 4.vue3使用编程式导航
      1. 4.1. vue3使用编程式导航和获取当前路由对象
    5. 5. 5.设置超出容器内容隐藏并添加省略号
      1. 5.1. 示例
    6. 6. 6.vue3的ref来获取dom元素
      1. 6.1. 示例代码
    7. 7. 7.getBoundingClientRect
      1. 7.1. 功能
      2. 7.2. 示例说明
    8. 8. 8.better-scroll
    9. 9. 9.手机端常用的左边分类,上部标签页,右部分布局页的结构代码
      1. 9.1. 效果图
      2. 9.2. 代码
    10. 10. 10.深度选择器vue3
      1. 10.1. 示例
    11. 11. 11.vant组件库van-collapse 下拉折叠面板出现Collapse: “v-model” should not be Array in accordion mode
      1. 11.1. 14.vant组件库sidebar(侧边导航) 和 Collapse(折叠面板)的结合使用
      2. 11.2. 效果:
      3. 11.3. 代码:
    12. 12. 12.linear-gradient的知识点
      1. 12.1. linear-gradient确认起点和终点
      2. 12.2. linear-gradient常见的绘制方向的坐标轴和效果示例
        1. 12.2.1. 1.background-image:linear-gradient(red, green);
        2. 12.2.2. 2.background-image: linear-gradient(to top,red,green);
        3. 12.2.3. 3.background-image: linear-gradient(to left,red,green);
        4. 12.2.4. 4.background-image: linear-gradient(to right, red,green);
        5. 12.2.5. 5.background-image: linear-gradient(45deg, red,green);
        6. 12.2.6. 6.示例代码
      3. 12.3. linear-gradient颜色绘制参数理解
        1. 12.3.1. 示例
    13. 13. 13.vant表单验证实现邮箱为空提示信息1,格式不正确提示信息2
    14. 14. 14.vant组件库Toast轻提示全局注册没有用并且样式丢失了
    15. 15. 15.vue3.x组件当中和js当中使用vuex
    16. 16. 16.vant的Toast(轻提示)设置不自动关闭,后面手动关闭
    17. 17. 17.window.localStorage.getItem获取不到指定的key值,返回的是null而不是undefined
    18. 18. 18.vue3使用keep-alive路由缓存保活
      1. 18.1. 添加路由缓存/保活
      2. 18.2. 使用失效的问题
    19. 19. 19.vue3项目本地热更新时报错TypeError: parentComponent.ctx.deactivate is not a function
    最新文章
    \ No newline at end of file diff --git a/a00af0bf.html b/a00af0bf.html new file mode 100644 index 000000000..cbe3835a7 --- /dev/null +++ b/a00af0bf.html @@ -0,0 +1 @@ +迭代器自定义数据输出当中this指向 | 梦洁小站-属于你我的小天地

    迭代器自定义数据输出当中this指向

    前置知识

    1. 在箭头函数当中,this的指向为函数声明时所在作用域下this的值

    代码

    next为普通函数

    代码

    1
    2
    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
    35
    36
    37
    38
    39
    var resultGet = {
    "code": "1",
    "day": "02/ 27",
    "result": [{
    "date": "272年02月27日",
    "title": "罗马帝国皇帝君士坦丁大帝出生"
    },
    {
    "date": "684年02月27日",
    "title": "唐中宗李显被母亲武则天废为庐陵王,驱离京师"
    },
    ]
    };
    resultGet[Symbol.iterator] = function () {
    var i = 0;
    console.log('外面的', this);//普通函数--指向的是要处理的数据,在这里也就是resultGet
    return {
    //这里改为箭头函数,其他的不变
    'next': function() {
    if (i < 1) {
    i++;
    console.log('里面的', this);//普通--指向的指向的是刚刚return创建的指针对象
    return {
    'value': '测试',
    'done': false
    };
    } else {
    return {
    'value': '测试',
    'done': true
    };
    }
    }
    }
    }
    //调用迭代器,测试里面的console.log()
    for (let v of resultGet) {

    }

    运行结果

    next为普通函数运行结果截图

    next为箭头函数

    代码

    1
    2
    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
    35
    36
    37
    38
    var resultGet = {
    "code": "1",
    "day": "02/ 27",
    "result": [{
    "date": "272年02月27日",
    "title": "罗马帝国皇帝君士坦丁大帝出生"
    },
    {
    "date": "684年02月27日",
    "title": "唐中宗李显被母亲武则天废为庐陵王,驱离京师"
    },
    ]
    };
    resultGet[Symbol.iterator] = function () {
    var i = 0;
    console.log('外面的', this);//普通函数--指向的是要处理的数据,在这里也就是resultGet
    return {
    //这里改为箭头函数,其他的不变
    'next': ()=> {
    if (i < 1) {
    i++;
    console.log('里面的', this);//箭头函数--指向的依旧是要处理的数据,在这里也就是resultGet
    return {
    'value': '测试',
    'done': false
    };
    } else {
    return {
    'value': '测试',
    'done': true
    };
    }
    }
    }
    }
    //调用迭代器,测试里面的console.log()
    for (let v of resultGet) {

    运行结果

    next为箭头函数运行结果截图

    为什么?

    个人理解,可能有误

    证明个人理解

    实例1代码及运行结果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    let p = {
    a: function () {
    var obj = {
    i: 10,
    b: () => {
    console.log(this.i, this);
    },
    }
    obj.b();
    }
    }
    p.a(); //undefined {a: ƒ}

    实例2代码及运行结果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    var objSelf = {
    i: 10,

    b: () => {
    console.log(this.i, this)
    },

    c: function () {
    console.log(this.i, this)
    }

    }

    objSelf.b(); // undefined window{...}

    objSelf.c(); // 10 {i: 10, b: ƒ, c: ƒ}
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/a00af0bf.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/a23f7333.html b/a23f7333.html new file mode 100644 index 000000000..8068a177b --- /dev/null +++ b/a23f7333.html @@ -0,0 +1 @@ +mouseinc-smartUp Gestures被禁用后的替代品 | 梦洁小站-属于你我的小天地

    mouseinc-smartUp Gestures被禁用后的替代品

    前言

    设置

    名称动作备注
    后退[ [ “PostMessage”, “274”, “33000” ] ]
    前进[ [ “PostMessage”, “274”, “33001” ] ]
    打开关闭的标签[ [ “SendKeys”, “Ctrl+Shift+T” ] ]
    扩展程序[ [ “PostMessage”, “274”, “40022” ] ]
    刷新[ [ “PostMessage”, “274”, “33002” ] ]
    • 其实也就是这些常量的值
    1
    2
    3
    4
    5
    6
    7
    8
    #定义 IDC_BACK                         33000 # 后退
    #定义 IDC_FORWARD 33001 #前进
    #定义 IDC_RELOAD 33002 #刷新
    #定义 IDC_HOME 33003 #首页
    #定义 IDC_OPEN_CURRENT_URL 33004
    #定义 IDC_STOP 33006
    #定义 IDC_RELOAD_BYPASSING_CACHE 33007
    #定义 IDC_RELOAD_CLEARING_CACHE 33009

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/a23f7333.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/a50ac5da.html b/a50ac5da.html new file mode 100644 index 000000000..497492df2 --- /dev/null +++ b/a50ac5da.html @@ -0,0 +1 @@ +typescript完成的贪吃蛇前端小游戏 | 梦洁小站-属于你我的小天地

    typescript完成的贪吃蛇前端小游戏

    前言

    • 使用typescript的语法完成的
    • 使用了webpack打包工具,可以兼容IE10以上浏览器
    • 该有的碰撞到自己身体和碰到墙壁游戏结束和食物随机功能都有~,具体可以自己体验体验
    • 有源代码和编译好后的文件(dist目录)
    • @在线演示

    下载

    制作难点

    蛇掉头的功能

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    /* 设置坐标 */
    XYPositionSet(newValue: number, axle: string) {
    //新的坐标等于原来的坐标,不设置
    //@ts-ignore
    if (newValue === this[axle]) {
    return;
    }
    //判断是否超过范围
    if (this.isOutArea(newValue)) {
    throw new Error("碰到墙壁了");
    }

    //设置是哪一个坐标上的位置
    let setPosition = axle === "X" ? "left" : "top";

    /**
    * 1.判断掉头二种方法,一种是根据方向,如果当前为左边的方法,那么下一次方向为右边,那么就是掉头了,上下的判断也是
    * 2.第二种方法: 左右判断,如果此时蛇头的left等于第一个蛇身的left,那么就是掉头了
    * 上下判断,如果此时蛇头的top等于第一个蛇身的top,那么就是掉头了
    */
    let one = this.bodies[1] as HTMLElement;
    let checkPosition = axle === "X" ? "offsetLeft" : "offsetTop";
    //注意顺序!
    //如果此时蛇头的left/top等于新设置的蛇头的值,那么就是掉头了,并且此时已经在向新方向在移动了!!!!!!!!!!
    //@ts-ignore
    if (one && one[checkPosition] === newValue) {
    //说明掉头了
    // console.log("掉头了");
    //掉头后,方向都变化了,比如left => right , top => down
    // 蛇 向左走是left在减少 向右走是left在增加
    // 蛇 向上走是top在减少 向下走是top在增加
    // 并且,执行到这一段代码的时候,方向已经被改变了,并且已经确定了新位置

    //对于水平突然变化
    // 如果新值newValue大于旧值X,则说明蛇之前在向左走, 现在方向被修改为了向右,所以现在每次蛇都是向右在移动位置(每次都+10的left),要使蛇继续向左走就需要值每次减少而不是增加(设置每次都-10的left)
    // 如果新值newValue小于旧值X,则说明蛇之前在向右走, 现在方向被修改为了向左,所以现在每次蛇都是向左在移动位置(每次都-10的left),要使蛇继续向右走就需要值每次增加而不是减少(设置每次都+10的left)

    //对于垂直突然变化
    // 如果新值newValue大于旧值Y,则说明蛇之前向上走,现在方向被修改了向下,所以现在每次蛇都是向下在移动位置(每次都+10的top),要使蛇继续向下走就需要值每次减少而不是增加(设置每次都-10的top)
    // 如果新值newValue小于旧值Y,则说明蛇之前向下走,现在方向被修改了向上,所以现在每次蛇都是向上在移动位置(每次都-10的top),要使蛇继续向下走就需要值每次增加而不是减少(设置每次都+10的top)

    //@ts-ignore
    if (newValue > this[axle]) {
    //@ts-ignore
    newValue = this[axle] - 10;
    } else {
    //@ts-ignore
    newValue = this[axle] + 10;
    }
    }
    //设置body位置
    this.setBodyiesPosition();
    //设置头的位置
    //@ts-ignore
    this.head.style[setPosition] = newValue + "px";
    //判断是否碰撞到了自己
    this.checkBump();
    }

    效果截图

    • 图片

    • 动态图

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/a50ac5da.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/a816b0a1.html b/a816b0a1.html new file mode 100644 index 000000000..d587bf838 --- /dev/null +++ b/a816b0a1.html @@ -0,0 +1 @@ +深入自定义事件和原生DOM事件($attr等) | 梦洁小站-属于你我的小天地

    深入自定义事件和原生DOM事件($attr等)

    深入自定义事件和原生DOM事件

    自定义事件

    1. 在组件上标签上添加的事件就是自定义事件,不管系统是否带这些事件

    2. 比如添加<自定义组件 @自定义事件 = "回调函数"></自定义组件> 那么@自定义事件在自定义组件上就是自定义事件

    3. <自定义组件 @click = "回调函数"></自定义组件>,那么@click就是自定义事件

    4. 添加的事件如果没有传入参数,那么输出就是undefined

    • 如图,传入了参数,单击button,输出为10,如果没有传递参数10,则输出undefined

    1. 自定义组件上绑定原生DOM事件使用native和不使用的区别,如下图
    • 如图

    自定义事件示例

    App.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <template>
    <div id="app">
    <!-- 给自定义组件绑定自定义事件为click事件,并将事件的回调函数绑定在事件check上 -->
    <MyComponent @click="check"></MyComponent>
    </div>
    </template>

    <script>
    import MyComponent from '@/components/MyComponent'
    export default {
    name: 'App',
    components: {
    MyComponent,
    },
    methods:{
    //给自定义组件绑定的自定义事件
    check(event){
    console.log("event",event);
    }
    }
    }
    </script>

    自定义组件MyComponent

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    <template>
    <div class="my">
    这是我的组件
    <!-- 用于触发给其绑定的自定义事件 -->
    <button @click="chufa">触发自定义事件</button>
    </div>
    </template>

    <script>
    export default {
    name: 'MyComponent',
    methods:{
    //触发自定义事件click,并传入一个参数100
    chufa(){
    this.$emit("click",1000);
    }
    }
    }
    </script>

    <style scoped>
    .my{
    border: 1px solid red;
    }
    </style>

    效果

    原生DOM事件

    1. 在HTML标签上添加就是原生DOM事件,比如说@click @mousemove这些系统自带的原生事件

    2. 添加的事件如果没有传入参数,那么系统会默认传入event参数

      1
      2
      3
      <button @click="test1">我是按钮1</button>
      //等同于
      <button @click="test1($event)">我是按钮2</button>

    vue自定义的事件在html标签和组件标签上的区别

    1. 在html标签上添加自定义事件无意义,所以自定义事件是给组件标签添加的

    2. 事件名可以任意,也可以和原生DOM事件名相同,但是是自定义的

    3. 如果自定义事件上想绑定原生的事件,那么就需要在事件对象名称后面添加 .native 并且绑定的事件添加在添加到组件根元素上,通过委派的形式使得子元素可以被触发

    • 如图

    深入理解v-model

    1. 首先我们需要知道v-model在HTML标签上的原理

    2. v-model原来写法

      1
      2
      // 标准v-model写法 普通写法
      <input type="text" v-model="msg" />
    3. v-model拆解写法(等同于上方直接写v-model)

      • 先使用v-bind绑定一个值给输入框,再添加事件@input,当触发就将当前触发对象的值传递给v-bind绑定的那个值,就这样子完成了数据更新
      1
      2
      // v-model拆解写法
      <input type="text" :value="msg" @input="msg = $event.target.value"/>

    那么v-model当中在自定义组件要怎么实现呢?

    • 根据v-model在原生DOM上拆解的写法,我们应该这样子写( 以自定义组件CustomInput为例 )

    • 需要知道的是: 在自定义组件当中,$event就是$emit当中第二个参数传递过来的数据

    • 组件

      1
      2
      3
      4
      5
      6
      <!--父亲给CustomInput传递 "msg2" 数据  -->
      <!--儿子要使用props接收( props:["value"] ),并且传递了自定义事件input -->
      <CustomInput :value="msg" @input="msg = $event"></CustomInput>
      <!--上面这一行代码可以简写为下面这一行但是子组件不能简写-->
      <!--必须要写下面这些内容!!!!!!!!!!!!!! -->
      <CustomInput v-model="msg"></CustomInput>
    • 组件 CustomInput.vue

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      <template>
      <div>
      <input type="text"
      // 绑定从父亲传递过来的参数
      :value = "value"
      // 绑定原生DOM事件@input,输入框内容发生改变,就将改变的值作为$emit的第二个参数
      // 并通过$emit触发父亲给子组件的自定义事件input
      @input="$emit('input',$event.target.value)"/>
      </div>
      </template>
      <script>
      export default {
      name: "CustomInput",
      // 接收从父亲传递过来的:value="msg"的值
      // 如果是简写形式(<CustomInput v-model="msg"></CustomInput>)也是从value接收
      props:["value"]
      }
      </script>

    .sync修饰符实现父子数据同步和.sync和v-model的区别

    1
    2
    [需求]
    父亲向儿子传递一个值叫money),儿子每次都花100块钱,要求儿子花了多少钱,爸爸那边可以同步

    第一次(无效果) (直接传递数据给儿子, 儿子每次单击都花100块钱)

    • 父亲 (直接传递数据给儿子)

      1
      2
       <!-- 会弹出警告说数据不同步 --> 
      <Child :money="moneyFather"></Child>

      弹出警告

    • 儿子

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      <template>
      <span>小明每次花100元</span>

      <!-- 每次单击爸爸的钱就减少100 -->
      <button @click="money = money - 100">单击我花钱</button>

      <!-- 显示爸爸剩余多少钱 -->
      爸爸还剩 {{ money }} 元
      </template>

      <script>
      export default {
      name:"Child",
      //接收父亲传递过来的信息,告诉了我现在父亲有多少钱
      props:["money"]
      }

      </script>
    • 结果

      • 儿子花钱按钮被单击,儿子当中的父亲的钱数量被改变,但是父亲兜兜里面的钱没有变化

    第二次(有效果) 数据传递给儿子,但是儿子每次单击花钱的时候就告诉父亲我花钱了

    • 父亲 ($event在自定义组件当中是$emit传递的参数 父亲收到儿子传递过来的金钱数,就更新自己的金钱数)

      1
      2
      3
      4
      5
      <!-- $event在自定义组件当中是$emit传递的参数 -->
      <!-- @update:xxx为固定格式,不可以更改,xxx为绑定的数据属性也就是v-bind:xxx="值"(当中的xxx)
      对应简写属性 :xxx="值" 当中的xxx
      -->
      <Child :money="moneyFather" @update:money="moneyFather = $event"></Child>
    • 儿子每次单击爸爸的钱就减少100 并且告诉爸爸 (通过$emit),并且传递 金钱-100的 值 给父亲

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      <template>
      <div style="background: #ccc; height: 50px">
      <span>小明每次花100元</span>

      <!-- 每次单击爸爸的钱少100 并且告诉爸爸 $emit,并且传递 金钱-100的值 给父亲-->
      <!-- update:money 为自定义事件名称 -->
      <button @click="$emit('update:money', money - 100)">花钱</button>

      <!-- 显示爸爸剩余多少钱 -->
      爸爸还剩 {{ money }} 元
      </div>
      </template>

      <script type="text/ecmascript-6">
      export default {

      name: "Child",

      // 接收父亲传递过来的信息,告诉了我现在父亲有多少钱
      props: ["money"],
      };
      </script>

    第三次(有效果)等同于第二次简写

    • 父亲 (使用sync修饰符)

      1
      2
      3

      <Child :money.sync="moneyFather"></Child>

    • 儿子 (和第二次一样) 每次单击爸爸的钱就减少100 并且告诉爸爸 (通过$emit),并且传递 金钱-100的 值 给父亲

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      <template>
      <div style="background: #ccc; height: 50px">
      <span>小明每次花100元</span>

      <!-- 每次单击爸爸的钱少100 并且告诉爸爸 $emit,并且传递 金钱-100的值 给父亲-->
      <!-- update:money 为自定义事件名称 -->
      <button @click="$emit('update:money', money - 100)">花钱</button>

      <!-- 显示爸爸剩余多少钱 -->
      爸爸还剩 {{ money }} 元
      </div>
      </template>

      <script type="text/ecmascript-6">
      export default {

      name: "Child",

      // 接收父亲传递过来的信息,告诉了我现在父亲有多少钱
      props: ["money"],
      };
      </script>

    v-model的使用在数据同步的使用

    • 父亲依旧是通过:value向子传递 $event依旧是子通过$emit传递过来的数据

      • 自定义组件$event返回的都是$emit传递过来的数据)
      1
      <Child :value="moneyFather" @input="moneyFather = $event"></Child>
    • 儿子

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      <template>
      <div style="background: #ccc; height: 50px">
      <span>小明每次花100元</span>

      <!-- 每次单击爸爸的钱少100 并且告诉爸爸 $emit,并且传递 金钱-100的值 给父亲-->

      <button @click="$emit('input', value - 100)">花钱</button>

      <!-- 显示爸爸剩余多少钱 -->
      爸爸还剩 {{ value }} 元
      </div>
      </template>

      <script type="text/ecmascript-6">
      export default {

      name: "Child",

      // 接收父亲传递过来的信息,告诉了我现在父亲有多少钱
      props: ["value"],
      };
      </script>

    .sync和v-model在数据同步使用区别

    • v-model和.sync都可以实现父子组件数据同步,下面是约定成俗的规定
      • v-model 是当子组件当中有表单类元素的时候使用
      • .sync 是当子组件当中不是表单类元素的时候使用

    自定义带hover提示的el-button和$attrs和$listeners的使用

    • 前置知识
      • el-button
        • 如果想带图标,那么添加icon属性可以,注意: icon属性里面的值都是以el-icon-xxx形式出现的
        • element-ui的icon库
    • 原来的el-button组件并没有鼠标悬停上去就出现提示的功能,我们可以通过对el-button进行再次包装

    简易的包装(但是不能达到自己去传入配置设置的要求)

    • 如图 MyButton.vue对el-button进行包装

      1
      2
      3
      4
      5
      <template>
      <a title="这个是提示框">
      <el-button></el-button>
      </a>
      </template>
    • 包装后的使用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      <template>
      <div>
      <h2>自定义带Hover提示的按钮</h2>
      <!-- 使用二次封装后的 -->
      <MyButton></MyButton>
      </div>
      </template>
      <script>
      <!-- 引入二次封装后的el-button组件 -->
      import MyButton from "./myButton.vue"
      export default {
      name:"AttrsListenersTest",
      <!-- 注册使用自定义组件 -->
      componets:{
      MyButton
      }
      }
      </script>
    • 效果图

    使用$attrs和$listeners进行复杂包装(可自定义参数效果)

    • 前面要知道的

      • 同等效果
        • 传递数据给组件,可以使用 :key="value"或者 直接省略 : ,直接写 key= “value” 也是可以的,不过有冒号的 :key="value" value为js代码,没有冒号的 key=”value” value值为字符串!!
      1
      2
      3
      <MyButton aa="10"></MyButton>
      <!-- 上一行和下一行是同等效果 -->
      <MyButton aa="10"></MyButton>
      • 不同等效果

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        <!-- 传递字符串 "b" -->
        <MyButton aa="b"></MyButton>

        <!-- 传递变量b的值,即为10 -->
        <MyButton aa="b"></MyButton>

        <script>
        <!-- 引入二次封装后的el-button组件 -->
        import MyButton from "./myButton.vue"
        export default {
        name:"AttrsListenersTest",
        data(){
        return {
        b:10
        }
        },
        <!-- 注册使用自定义组件 -->
        componets:{
        MyButton
        }
        }
        </script>

    • $attrs 获取传递给组件的所有属性,它会排除 props已经声明接收的属性 以及class,style这二个样式

      • 如图

    • 排除props接收到了的

      • 如图

    • $listeners 获取父组件传递给子组件的所有自定义事件监听组成的对象

      • 如图

    • $attrs$listeners一键绑定在组件上

      • 可以通过v-bind一次性把父组件传递过来的属性添加给子组件(v-bind不可以简写为:)
      • 可以通过v-on 一次性把父组件传递过来的事件监听添加给子组件(v-on不可以简写@)
      • 如图

    $children和$parent和$refs的使用

    • this.$refs放在HTML和组件标签身上的区别
      • this.$refs.名称 放在html标签身上拿到的就是这个DOM元素
      • this.$refs放在组件标签身上拿到的就是组件对象本身

    this.$refs妙用

    • 可以直接通过this.$refs.名称来获取组件,并且在组件当中去操作这个获取到的组件里面的数据

      如图所示(父亲) 父亲直接通过this.$refs.son.money就操控了儿子和女儿的钱

    $children 和 $parent的妙用

    $parent

    • 前提:

      • 必须只有一个父亲才可以使用!如果这个组件有多个父亲,那么就不可以用!(为什么有多个父亲,因为存在组件复用的情况!)
    • 如图,儿子通过 this.$parent.money来获取父亲的钱并且修改

    $children

    • 获取当前组件的所有的子组件,返回子组件的数组,(无顺序,不能说[0]一定就是儿子,[1]就一定是女儿)

    • 如图

    本案例当中,均在配置对象当中书写了data数据

    mixin混入的基本使用

    • @官方API:混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。

    • 说通俗点就是混入就是将别人的东东变为自己的东东,这里的混入只说一些基本的使用

    • 先来看示例吧

    mixin/test.js文件的代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    const mixins = {
    data(){
    return {
    sex:"男",
    age:"18",
    }
    },
    methods:{
    sayHello(){
    console.log("你好,世界");
    }
    }
    };
    export default mixins;

    App.vue

    使用mixin/test.js的混入

    1
    2
    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
    <template>
    <div>
    <div>姓名:{{name}}</div>
    <div>年龄:{{sex}}</div>
    <div>性别:{{age}}</div>
    <button @click="sayHello">单击我 - 说hello</button>
    <button @click="sayThankyou">单击我 - 说谢谢</button>
    </div>
    </template>

    <script>
    // 引入混入
    import myMixin from "@/mixin/test";
    export default {
    name: "",
    //使用混入
    mixins:[myMixin],
    data() {
    return {
    name:"李白",
    }
    },
    methods:{
    sayThankyou(){
    console.log("谢谢你");
    }
    }

    };
    </script>

    功能测试是否正常,可以看到,可以正常显示和调用函数

    • 当然了,还有很多情况,比如说混入的时候,当mixin/test.js里面的数据或者方法和App.vue当中的数据或者方法冲突的时候要怎么解决之类的,具体看官网吧~ @官网

    之前做的一个混入的图

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/a816b0a1.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    avatar
    梦洁
    小小的字,有大大的梦想~分享我的前端学习过程,经历,错误,和一些其他折腾过程
    关注下我(* ̄▽ ̄*)
    公告
    不断更新中,有问题请留言回复(会通过邮箱提醒~)
    最新文章
    \ No newline at end of file diff --git a/about/index.html b/about/index.html new file mode 100644 index 000000000..98a9419ba --- /dev/null +++ b/about/index.html @@ -0,0 +1 @@ +关于 | 梦洁小站-属于你我的小天地

    为什么写?


    因为


    动感超人



    评论
    \ No newline at end of file diff --git a/af3652d4.html b/af3652d4.html new file mode 100644 index 000000000..a4e9c898a --- /dev/null +++ b/af3652d4.html @@ -0,0 +1 @@ +关于flex布局最后一个不对齐的解决方法和为什么这样子解决是讨论 | 梦洁小站-属于你我的小天地

    关于flex布局最后一个不对齐的解决方法和为什么这样子解决是讨论

    关于flex布局justify-content:space-around最后一个不对齐的解决方法和为什么这样子解决是讨论

    • 其实解决也很简单,在最外层添加一个伪类即可

      html结构

      1
      2
      3
      4
      5
      6
      7
      8
      9
        <!-- 外层 -->
      <div class="root">

      <!-- 遍历数据的 -->
      <div v-for="(shopItem, index) in shopList" :key="index">
      <img style="width: 200px;" :src="shopItem.url" alt="" />
      </div>

      </div>

      css解决办法,在root后面添加一个伪类即可解决

      1
      2
      3
      4
      5
      6
      .root::after{
      content: "";
      width: 200px;
      /* 不可以提供高度 */
      /* height: 200px; */
      }

    那么为什么设置::after伪类就可以解决呢justify-content所带来的布局问题呢?

    • 当那个循环列表是奇数的时候会出现那个填充框框对吧?

    奇数

    • 但是如果是偶数会不会出现这个伪类::after的填充框呢,我们再添加一个项目框框
      • 可以看到,并没有出现这个红色框框(不是我截图没有截到,是真的没有出现红色框框)

    偶数

    • 那么为什么呢?就好像知道这个浏览器知道我们需要这个填充内容出现一样
      • 在之前我猜测可能是盒子模型计算出来了伪类::after的宽度,但是我记得盒子模型计算的是针对于块级元素的
      • 后面查看资料说

    CSS伪元素::after用来创建一个伪元素,作为已选中元素的最后一个子元素。通常会配合content属性来为该元素添加装饰内容。这个虚拟元素默认是行内元素。

    • 那么肯定不是盒子模型计算的问题了,那么是为什么呢?(下面推断个人想法,可能有误)
      • 后面想了想出现这种情况是什么
        • 1.最后一行没有被填充,有空隙的时候,就会出现伪类的填充
        • 2.当容器最后没有留空的时候,就不会出现伪类的填充
    • 后面查看弹性盒属性,发现了flex-basis

    CSS 属性 flex-basis 指定了 flex 元素在主轴方向上的初始大小。如果不使用 box-sizing 改变盒模型的话,那么这个属性就决定了 flex 元素的内容盒(content-box)的尺寸。

    flex-basis 默认值为auto

    • 于是我设置伪类::afterflex-basis:0,看看会发生什么,
    1
    2
    3
    4
    5
    6
    7
    8
    .root::after{
    content: "";
    width: 200px;
    /* 不可以提供高度 */
    /* height: 200px; */
    background: red;
    flex-basis:0;
    }
    • 可以看到,伪类::after高度都没有,不像前面的一行没有满的情况有填充出现

    flex-basis:0为伪类::after设置

    • 我们在将他设置为flex-basis:auto看看会发生什么

    flex-basis:auto

    • 当然,由于我们没有设置基础的宽度,所以设置flex-basis:content也是一样的效果

    flex-basis:content

    • 甚至设置height:auto是也一样的效果

    flex-basis官方解释—由于最初规范中没有包括这个值,在一些早期的浏览器实现的 flex 布局中,content 值无效,可以利用设置 (widthheight) 为 auto 达到同样的效果。

    总结

    • 浏览器知道我们需要这个填充内容出现是因为flex-basis默认值为auto,由浏览器为元素计算并选择一个高度或者宽度,当然,假如事先设置了宽度和高度,那么flex-basis:auto是没有用的(这里是没有设置height,所以只讨论height的情况)
    • 所以要多了解了解flex布局呀~~~
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/af3652d4.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/archives/2022/02/index.html b/archives/2022/02/index.html new file mode 100644 index 000000000..242af99bf --- /dev/null +++ b/archives/2022/02/index.html @@ -0,0 +1 @@ +二月 2022 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2022/03/index.html b/archives/2022/03/index.html new file mode 100644 index 000000000..d0bc67960 --- /dev/null +++ b/archives/2022/03/index.html @@ -0,0 +1 @@ +三月 2022 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2022/04/index.html b/archives/2022/04/index.html new file mode 100644 index 000000000..143249525 --- /dev/null +++ b/archives/2022/04/index.html @@ -0,0 +1 @@ +四月 2022 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2022/04/page/2/index.html b/archives/2022/04/page/2/index.html new file mode 100644 index 000000000..3361f74cb --- /dev/null +++ b/archives/2022/04/page/2/index.html @@ -0,0 +1 @@ +四月 2022 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2022/05/index.html b/archives/2022/05/index.html new file mode 100644 index 000000000..1b58030cb --- /dev/null +++ b/archives/2022/05/index.html @@ -0,0 +1 @@ +五月 2022 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2022/05/page/2/index.html b/archives/2022/05/page/2/index.html new file mode 100644 index 000000000..ff7cc3256 --- /dev/null +++ b/archives/2022/05/page/2/index.html @@ -0,0 +1 @@ +五月 2022 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2022/05/page/3/index.html b/archives/2022/05/page/3/index.html new file mode 100644 index 000000000..46801fccc --- /dev/null +++ b/archives/2022/05/page/3/index.html @@ -0,0 +1 @@ +五月 2022 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2022/05/page/4/index.html b/archives/2022/05/page/4/index.html new file mode 100644 index 000000000..68996fddf --- /dev/null +++ b/archives/2022/05/page/4/index.html @@ -0,0 +1 @@ +五月 2022 | 梦洁小站-属于你我的小天地
    文章总览 - 32
    2022
    今日刷题-decodeURI
    今日刷题-decodeURI
    今日刷题-CMD和AMD的模块化
    今日刷题-CMD和AMD的模块化
    \ No newline at end of file diff --git a/archives/2022/06/index.html b/archives/2022/06/index.html new file mode 100644 index 000000000..ff316c64e --- /dev/null +++ b/archives/2022/06/index.html @@ -0,0 +1 @@ +六月 2022 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2022/07/index.html b/archives/2022/07/index.html new file mode 100644 index 000000000..a1e5204af --- /dev/null +++ b/archives/2022/07/index.html @@ -0,0 +1 @@ +七月 2022 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2022/07/page/2/index.html b/archives/2022/07/page/2/index.html new file mode 100644 index 000000000..da949c27b --- /dev/null +++ b/archives/2022/07/page/2/index.html @@ -0,0 +1 @@ +七月 2022 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2022/08/index.html b/archives/2022/08/index.html new file mode 100644 index 000000000..76efd9c63 --- /dev/null +++ b/archives/2022/08/index.html @@ -0,0 +1 @@ +八月 2022 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2022/09/index.html b/archives/2022/09/index.html new file mode 100644 index 000000000..dc0c791e6 --- /dev/null +++ b/archives/2022/09/index.html @@ -0,0 +1 @@ +九月 2022 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2022/10/index.html b/archives/2022/10/index.html new file mode 100644 index 000000000..74d98b38c --- /dev/null +++ b/archives/2022/10/index.html @@ -0,0 +1 @@ +十月 2022 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2022/11/index.html b/archives/2022/11/index.html new file mode 100644 index 000000000..02f873d37 --- /dev/null +++ b/archives/2022/11/index.html @@ -0,0 +1 @@ +十一月 2022 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2022/12/index.html b/archives/2022/12/index.html new file mode 100644 index 000000000..c252dbcea --- /dev/null +++ b/archives/2022/12/index.html @@ -0,0 +1 @@ +十二月 2022 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2022/index.html b/archives/2022/index.html new file mode 100644 index 000000000..e3e8a05d5 --- /dev/null +++ b/archives/2022/index.html @@ -0,0 +1 @@ +2022 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2022/page/10/index.html b/archives/2022/page/10/index.html new file mode 100644 index 000000000..2fdcc0ddc --- /dev/null +++ b/archives/2022/page/10/index.html @@ -0,0 +1 @@ +2022 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2022/page/11/index.html b/archives/2022/page/11/index.html new file mode 100644 index 000000000..948164f7a --- /dev/null +++ b/archives/2022/page/11/index.html @@ -0,0 +1 @@ +2022 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2022/page/12/index.html b/archives/2022/page/12/index.html new file mode 100644 index 000000000..783372b3b --- /dev/null +++ b/archives/2022/page/12/index.html @@ -0,0 +1 @@ +2022 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2022/page/2/index.html b/archives/2022/page/2/index.html new file mode 100644 index 000000000..f1cc32adb --- /dev/null +++ b/archives/2022/page/2/index.html @@ -0,0 +1 @@ +2022 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2022/page/3/index.html b/archives/2022/page/3/index.html new file mode 100644 index 000000000..74057f2a4 --- /dev/null +++ b/archives/2022/page/3/index.html @@ -0,0 +1 @@ +2022 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2022/page/4/index.html b/archives/2022/page/4/index.html new file mode 100644 index 000000000..613370a8f --- /dev/null +++ b/archives/2022/page/4/index.html @@ -0,0 +1 @@ +2022 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2022/page/5/index.html b/archives/2022/page/5/index.html new file mode 100644 index 000000000..5dc2f29b4 --- /dev/null +++ b/archives/2022/page/5/index.html @@ -0,0 +1 @@ +2022 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2022/page/6/index.html b/archives/2022/page/6/index.html new file mode 100644 index 000000000..2f06d8584 --- /dev/null +++ b/archives/2022/page/6/index.html @@ -0,0 +1 @@ +2022 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2022/page/7/index.html b/archives/2022/page/7/index.html new file mode 100644 index 000000000..a128b934f --- /dev/null +++ b/archives/2022/page/7/index.html @@ -0,0 +1 @@ +2022 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2022/page/8/index.html b/archives/2022/page/8/index.html new file mode 100644 index 000000000..c04ee31e1 --- /dev/null +++ b/archives/2022/page/8/index.html @@ -0,0 +1 @@ +2022 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2022/page/9/index.html b/archives/2022/page/9/index.html new file mode 100644 index 000000000..95499517d --- /dev/null +++ b/archives/2022/page/9/index.html @@ -0,0 +1 @@ +2022 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2023/02/index.html b/archives/2023/02/index.html new file mode 100644 index 000000000..5e3516e29 --- /dev/null +++ b/archives/2023/02/index.html @@ -0,0 +1 @@ +二月 2023 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2023/03/index.html b/archives/2023/03/index.html new file mode 100644 index 000000000..d2bfac1a0 --- /dev/null +++ b/archives/2023/03/index.html @@ -0,0 +1 @@ +三月 2023 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2023/04/index.html b/archives/2023/04/index.html new file mode 100644 index 000000000..a20bb64e9 --- /dev/null +++ b/archives/2023/04/index.html @@ -0,0 +1 @@ +四月 2023 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2023/05/index.html b/archives/2023/05/index.html new file mode 100644 index 000000000..f1b3ac26c --- /dev/null +++ b/archives/2023/05/index.html @@ -0,0 +1 @@ +五月 2023 | 梦洁小站-属于你我的小天地
    文章总览 - 1
    2023
    前端文字实现拼音标注
    前端文字实现拼音标注
    \ No newline at end of file diff --git a/archives/2023/06/index.html b/archives/2023/06/index.html new file mode 100644 index 000000000..24fd5f18d --- /dev/null +++ b/archives/2023/06/index.html @@ -0,0 +1 @@ +六月 2023 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2023/09/index.html b/archives/2023/09/index.html new file mode 100644 index 000000000..c220c7e6c --- /dev/null +++ b/archives/2023/09/index.html @@ -0,0 +1 @@ +九月 2023 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2023/10/index.html b/archives/2023/10/index.html new file mode 100644 index 000000000..ae24d5c8d --- /dev/null +++ b/archives/2023/10/index.html @@ -0,0 +1 @@ +十月 2023 | 梦洁小站-属于你我的小天地
    文章总览 - 1
    2023
    defineAsync-Suspense学习
    defineAsync-Suspense学习
    \ No newline at end of file diff --git a/archives/2023/11/index.html b/archives/2023/11/index.html new file mode 100644 index 000000000..82bac6ee2 --- /dev/null +++ b/archives/2023/11/index.html @@ -0,0 +1 @@ +十一月 2023 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2023/12/index.html b/archives/2023/12/index.html new file mode 100644 index 000000000..6c769ca3c --- /dev/null +++ b/archives/2023/12/index.html @@ -0,0 +1 @@ +十二月 2023 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2023/index.html b/archives/2023/index.html new file mode 100644 index 000000000..9964e81ef --- /dev/null +++ b/archives/2023/index.html @@ -0,0 +1 @@ +2023 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2023/page/2/index.html b/archives/2023/page/2/index.html new file mode 100644 index 000000000..7b23cdc71 --- /dev/null +++ b/archives/2023/page/2/index.html @@ -0,0 +1 @@ +2023 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2023/page/3/index.html b/archives/2023/page/3/index.html new file mode 100644 index 000000000..825f77294 --- /dev/null +++ b/archives/2023/page/3/index.html @@ -0,0 +1 @@ +2023 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2024/01/index.html b/archives/2024/01/index.html new file mode 100644 index 000000000..98e2ca0c8 --- /dev/null +++ b/archives/2024/01/index.html @@ -0,0 +1 @@ +一月 2024 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2024/02/index.html b/archives/2024/02/index.html new file mode 100644 index 000000000..d482cd6fc --- /dev/null +++ b/archives/2024/02/index.html @@ -0,0 +1 @@ +二月 2024 | 梦洁小站-属于你我的小天地
    文章总览 - 1
    2024
    Github action的学习
    Github action的学习
    \ No newline at end of file diff --git a/archives/2024/03/index.html b/archives/2024/03/index.html new file mode 100644 index 000000000..ad765a8c1 --- /dev/null +++ b/archives/2024/03/index.html @@ -0,0 +1 @@ +三月 2024 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2024/04/index.html b/archives/2024/04/index.html new file mode 100644 index 000000000..6fabfa86d --- /dev/null +++ b/archives/2024/04/index.html @@ -0,0 +1 @@ +四月 2024 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2024/05/index.html b/archives/2024/05/index.html new file mode 100644 index 000000000..39eefbdc0 --- /dev/null +++ b/archives/2024/05/index.html @@ -0,0 +1 @@ +五月 2024 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2024/06/index.html b/archives/2024/06/index.html new file mode 100644 index 000000000..0855fff9c --- /dev/null +++ b/archives/2024/06/index.html @@ -0,0 +1 @@ +六月 2024 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2024/07/index.html b/archives/2024/07/index.html new file mode 100644 index 000000000..63a173fda --- /dev/null +++ b/archives/2024/07/index.html @@ -0,0 +1 @@ +七月 2024 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2024/08/index.html b/archives/2024/08/index.html new file mode 100644 index 000000000..553f32716 --- /dev/null +++ b/archives/2024/08/index.html @@ -0,0 +1 @@ +八月 2024 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2024/10/index.html b/archives/2024/10/index.html new file mode 100644 index 000000000..46385cc7d --- /dev/null +++ b/archives/2024/10/index.html @@ -0,0 +1 @@ +十月 2024 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2024/index.html b/archives/2024/index.html new file mode 100644 index 000000000..bb399f9de --- /dev/null +++ b/archives/2024/index.html @@ -0,0 +1 @@ +2024 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2024/page/2/index.html b/archives/2024/page/2/index.html new file mode 100644 index 000000000..4fefd3f5d --- /dev/null +++ b/archives/2024/page/2/index.html @@ -0,0 +1 @@ +2024 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/2024/page/3/index.html b/archives/2024/page/3/index.html new file mode 100644 index 000000000..9413af91d --- /dev/null +++ b/archives/2024/page/3/index.html @@ -0,0 +1 @@ +2024 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/index.html b/archives/index.html new file mode 100644 index 000000000..93cf5b908 --- /dev/null +++ b/archives/index.html @@ -0,0 +1 @@ +归档 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/page/10/index.html b/archives/page/10/index.html new file mode 100644 index 000000000..7526c0e9f --- /dev/null +++ b/archives/page/10/index.html @@ -0,0 +1 @@ +归档 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/page/11/index.html b/archives/page/11/index.html new file mode 100644 index 000000000..d9315bc5f --- /dev/null +++ b/archives/page/11/index.html @@ -0,0 +1 @@ +归档 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/page/12/index.html b/archives/page/12/index.html new file mode 100644 index 000000000..bb8f12744 --- /dev/null +++ b/archives/page/12/index.html @@ -0,0 +1 @@ +归档 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/page/13/index.html b/archives/page/13/index.html new file mode 100644 index 000000000..e6192948b --- /dev/null +++ b/archives/page/13/index.html @@ -0,0 +1 @@ +归档 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/page/14/index.html b/archives/page/14/index.html new file mode 100644 index 000000000..d77a0cebd --- /dev/null +++ b/archives/page/14/index.html @@ -0,0 +1 @@ +归档 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/page/15/index.html b/archives/page/15/index.html new file mode 100644 index 000000000..6974f1547 --- /dev/null +++ b/archives/page/15/index.html @@ -0,0 +1 @@ +归档 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/page/16/index.html b/archives/page/16/index.html new file mode 100644 index 000000000..cd596915d --- /dev/null +++ b/archives/page/16/index.html @@ -0,0 +1 @@ +归档 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/page/17/index.html b/archives/page/17/index.html new file mode 100644 index 000000000..cf35523cc --- /dev/null +++ b/archives/page/17/index.html @@ -0,0 +1 @@ +归档 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/page/2/index.html b/archives/page/2/index.html new file mode 100644 index 000000000..96122b347 --- /dev/null +++ b/archives/page/2/index.html @@ -0,0 +1 @@ +归档 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/page/3/index.html b/archives/page/3/index.html new file mode 100644 index 000000000..a9261ef43 --- /dev/null +++ b/archives/page/3/index.html @@ -0,0 +1 @@ +归档 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/page/4/index.html b/archives/page/4/index.html new file mode 100644 index 000000000..de0026f93 --- /dev/null +++ b/archives/page/4/index.html @@ -0,0 +1 @@ +归档 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/page/5/index.html b/archives/page/5/index.html new file mode 100644 index 000000000..7981e9893 --- /dev/null +++ b/archives/page/5/index.html @@ -0,0 +1 @@ +归档 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/page/6/index.html b/archives/page/6/index.html new file mode 100644 index 000000000..0a79e0c07 --- /dev/null +++ b/archives/page/6/index.html @@ -0,0 +1 @@ +归档 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/page/7/index.html b/archives/page/7/index.html new file mode 100644 index 000000000..022434486 --- /dev/null +++ b/archives/page/7/index.html @@ -0,0 +1 @@ +归档 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/page/8/index.html b/archives/page/8/index.html new file mode 100644 index 000000000..aa5c7819a --- /dev/null +++ b/archives/page/8/index.html @@ -0,0 +1 @@ +归档 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/archives/page/9/index.html b/archives/page/9/index.html new file mode 100644 index 000000000..3087a2bc3 --- /dev/null +++ b/archives/page/9/index.html @@ -0,0 +1 @@ +归档 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/b691d3ea.html b/b691d3ea.html new file mode 100644 index 000000000..625f7fbd0 --- /dev/null +++ b/b691d3ea.html @@ -0,0 +1 @@ +我来图书馆小程序抓包抢位置 | 梦洁小站-属于你我的小天地

    我来图书馆小程序抓包抢位置

    window工具自动抢位置和签到

    提交预约流程

    注意

    1. 抓包分析好像还涉及到wxlib/wx/login 不过从后面提交数据来看好像用不到,可能我技术问题吧

    抓包分析

    有人可能最新微信PC抓不了小程序包

    解决办法
    1. 打开一个任意小程序,打开任务管理器,找到进程。右键打开文件位置。

    2. 退出电脑微信,右键结束小程序进程。

    3. 找到这个目录后删除这个目录

    4. 或者你有everything这个工具,直接搜索 WMPFRuntime 然后右键打开所在文件夹,把里面这个4376目录删除就可以

    不想分析了~,具体的可以自己抓包看看

    微信小程序反编译(可以看看源代码~)

    微信小程序反编译

    这次小程序反编译出来的文件

    userID可以去抓包获取,这个没办法模拟请求获取

    抢座nodejs代码

    注意安装axios依赖

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    var axios = require('axios');

    var configself = {
    method: 'get',
    url: 'https://wxcourse.jxufe.cn/wxlib/wx/appoint',
    params: {
    isPeriod: 1,
    //自己的userID有效期多久未测试
    userId: "",
    //座位id,可以通过网站传参抓包获取
    //519代表A208这个位置
    seatId: 519,
    //不知道干嘛用
    appointType: 0,
    //代表区域,A区域代码为23,F区域为115,G区域为113等等
    vdId: 23,
    //时间段,1代表8:00-12:00时间段
    //时间段,2代表12:00-17:00时间段
    //时间段2,代表17:30-23:00时间段
    timeSlot: 1,
    //大学名称
    officeCode: "jxcjdx",
    //大学id
    colleageId: 51,
    //预约日期
    day: "2022-04-07",
    //预约截止时间段:
    //比如8:00-12:00截止日期为12:00,
    //比如12:00-17:00截止日期为17:00
    //比如17:30-23:00截止日期为23:00
    appointTo: "12:00"
    },
    headers: {}
    };
    axios(configself)
    .then(function (response) {
    console.log(JSON.stringify(response.data));
    })
    .catch(function (error) {
    console.log(error);
    });

    运行

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/b691d3ea.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/b92efe42.html b/b92efe42.html new file mode 100644 index 000000000..627bb65b5 --- /dev/null +++ b/b92efe42.html @@ -0,0 +1 @@ +今日刷题-温故而知新 | 梦洁小站-属于你我的小天地

    今日刷题-温故而知新

    题目1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var foo=”Hello”;
    (function(){
    var bar=”World”;
    alert(foo+bar);
    })();
    alert(foo+bar);
    A: Hello World报错
    B: Hello World Hello World
    C: Hello World Hello
    D: Hello World Hello
    • 答案
      • A
    • 解析
      • var是函数作用域,也就是以函数和分割,函数内声明了var,在这个函数范围内都可以访问到.,而let是块级作用域,比如for循环当中的let变量,在for循环里面就可以访问到,在for循环外面就访问不到了!

    题目2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    以上 JavaScript 代码,在浏览器中运行的结果是
    var arrTemp = [1,2,3];
    arrTemp.shift();
    arrTemp.push(1);
    arrTemp.unshift(2);
    var arrNew = arrTemp.concat([1,2]);
    console.log(arrNew);
    A: [2,2,3,1,1,2]
    B: [2,1,2,1,1,2]
    C: [2,2,3,1,[1,2]]
    D: [2,1,2,1,[1,2]]
    • 答案

      • A
    • 解析

      • concat可以用于连接字符串和数组,均不会改变原来的变量,均返回一个连接后的变量

    题目3

    1
    2
    3
    4
    5
    以下对call() 和 apply() 说法哪些是正确的  () (多选)
    A: apply()函数有二个参数,第一个参数是上下文,第二个参数是参数组成的数组
    B: 非严格模式,如果第一个参数是null,则使用全局对象代替
    C: call和apply的意思一样,只不过是参数列表不一样
    D: 通过apply可以将数组转化为参数列表的集合
    • 答案
      • ABCD
    • 解析
      • call() 方法使用一个指定的this值和单独给出的一个或多个参数来调用一个函数。(该方法的语法和作用与 apply() 方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组。)
      • 第一个参数均为可选的
        • function 函数运行时使用的 this 值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 nullundefined 时会自动替换为指向全局对象,原始值会被包装。(也就是说如果在严格模式下,则不会被替换为全局对象window,而是会被指向null和undefined)
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/b92efe42.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/b9640c69.html b/b9640c69.html new file mode 100644 index 000000000..3bebfa70f --- /dev/null +++ b/b9640c69.html @@ -0,0 +1 @@ +ant-design-vue select动态下拉加载更多和加载状态的添加 | 梦洁小站-属于你我的小天地

    ant-design-vue select动态下拉加载更多和加载状态的添加

    需求

    • ant-design-vue select下拉列表当中动态加载列表,当滚动到最后的时候继续加载,没有数据了就不加载了
    • 这里的defHttp就是axios的二次封装,你们可以替换为axios就可以
    • 效果的话就不展示了,因为数据都是后台请求的~

    做法

    • 用到的ant design vue组件有
      • a-spin加载圈圈的
      • a-select下拉列表
      • a-empty空状态的(这里自定义了空状态,使其文字替换为加载圈圈)

    示例

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    //必须要写
    const VNodes = (_, { attrs }) => {
    return attrs.vnodes;
    }
    <a-select
    v-model:value="searchForm.userIds"
    allow-clear
    mode="multiple"
    @dropdownVisibleChange="handleDropDownVisibleChange"
    @popupScroll="handlePopupScroll"
    :max-tag-count="1"
    :max-tag-text-length="2"
    placeholder="请选择">
    <a-select-option v-for="item in dropDownData.options" :key="item.id" :value="item.id">
    {{item.name}}
    </a-select-option>

    <template #dropdownRender="{ menuNode: menu }">
    <a-empty v-if="dropDownData.isLoading">
    <template #description>
    <span><a-spin /></span>
    </template>
    </a-empty>
    <a-empty v-else-if="dropDownData.options.length === 0">
    <template #description>
    暂无数据
    </template>
    </a-empty>
    <!--显示结果-->
    <!--v-nodes必须要写在最后面,这个代表要渲染的下拉列表数据-->
    <template v-else>
    <v-nodes :vnodes="menu" />
    </template>
    </template>
    </a-select>


    //要发送给后台的数据
    const searchForm = ref<SearchFormTypes>({
    userName:'',
    userIds:[],//记录用户选取的下拉列表数据
    });

    /* 下拉列表请求-初次的时候 */
    const handleDropDownVisibleChange = async (open) => {
    if(open && dropDownData.value.options.length === 0 ){
    //初次加载
    const result = await defHttp.post(({url:'/xxxx',params:{userName:searchForm.value.userName,...dropDownData.value.pagination}}));
    dropDownData.value.pagination.total = result.total ?? 0;
    dropDownData.value.isLoading = false;
    //从结果中map获取列表数据
    dropDownData.value.options = result?.list?.map(item => ({
    id:item.id ?? 0,
    name:item.name ?? "",
    })) ?? [];
    }
    }

    /* 下拉滚动 */
    const handlePopupScroll = async (e) => {
    //已经有的下拉项目 大于等于后台返回的下拉项总长度,那么就返回不请求了
    if(dropDownData.value.options.length >= dropDownData.value.pagination.total) return;
    const { scrollHeight, scrollTop, clientHeight } = e.target;
    if (scrollHeight - scrollTop === clientHeight) {
    //到达了底部,请求数据
    dropDownData.value.pagination.page++;//分页器自增1
    const result = await defHttp.post(({url:'/xxxxx',params:{userName:searchForm.value.userName,...dropDownData.value.pagination}}));
    //从结果中map获取列表数据
    const temp = result?.list?.map(item => ({
    id:item.id ?? 0,
    name:item.name ?? "",
    })) ?? [];
    //注意顺序,这里是先结构之前的,在结构之后的
    dropDownData.value.options = [...dropDownData.value.options,...temp];
    }
    }

    // 下拉列表
    const dropDownData = ref<dropDownDataTypes>({
    options:[],//下拉项列表
    pagination:{
    page:1,
    pageSize:10,
    total:0,//用于记录数据总长度,后期判断是否还继续加载
    },//分页器下拉的
    isLoading:true, //是否正在加载数据
    })

    回调说明和其他的一些说明

    • @dropdownVisibleChange="handleDropDownVisibleChange"初次选择下拉列表的时候执行,可以用于初始化数据
    • @popupScroll="handlePopupScroll"下拉列表滚动的时候执行的回调,用于判断是否加载到了底部
    • <v-nodes :vnodes="menu" />这个代表的就是那个下拉列表项数据,没有这个就不渲染

    参考文章

    https://blog.csdn.net/qq_36437172/article/details/109515201

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/b9640c69.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/b9ff50aa.html b/b9ff50aa.html new file mode 100644 index 000000000..53237bd91 --- /dev/null +++ b/b9ff50aa.html @@ -0,0 +1 @@ +git merge origin master和git merge master的区别(个人理解) | 梦洁小站-属于你我的小天地

    git merge origin master和git merge master的区别(个人理解)

    先说结论

    git merge origin master

    • 意思是当前的分支,进行合并,合并二个分支分别是远程分支master在本地的副本和本地分支的master

    git merge master

    • 当前分支于本地所处的master分支进行合并

    • 还有就是
      • git merge origin master是把origin merge 到 master 上的说法是错误的!!!!!!!!!!!!!

    git merge实践出真理(放弃,待填坑)

    准备工作

    1. 建立二个分支

      • stream分支
      • stream-qiuye分支
    2. 三个分支分别在文件添加内容,并提交到远程(图片下面这行字忽略)

    1. 分支切换到main分支

    前置需要了解

    origin 并不是指得是远程的仓库,而是指得是远程仓库在本地的一个指针(这个指针有可能过时的)。当我们使用使用merge 的时候,我们进行合并的时候只是上一次fetch 从远程拿到的版本。不是远程仓库的最新版本

    • 在另外一个人向main分支推送了新内容,本地没有更新,并且本地所处main分支情况下进行下面二个操作(为保证效果,)

    • get merge origin

    • get merge origin/main

    尝试(放弃,待填坑)

    • 分支都切换在main分支进行操作
    • 主分支名称我改了默认的,改为了main

    git merge origin main操作合并(放弃,待填坑)

    • git merge origin main(另外一个人在main分支做了修改并提交到了远程服务器,并且本地还没有pull的情况)

    • 当然,你可以会出现这种情况

    1
    2
    3
    4
    merge: origin - not something we can merge

    合并命令更换为即可,效果是一样的
    git merge origin/main main

    git merge main操作合并(放弃,待填坑)

    • git merge main

    那么再看看看push吧

    提前总结

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    #如果想在当前分支想要更新现在所处的分支或者上传到远程同名的分支
    #直接下面一行代码就可以
    git push/pull origin 分支名即可
    #如果想要在其他分支更新或者推送其他分支,则需要完整的写法
    git push/pull <远程主机名> <本地分支名>:<远程分支名>


    #git pull举例子
    #比如我在stream分支,想要更新(pull)main分支,你可以像下面这样子做

    #方法1-先切换到main分支
    git checkout main
    git pull origin main #表示远程分支和当前所处分支合并

    #方法2-不变更当前分支进行更新main分支(我不知道该怎么做-有的话可以评论留言)
    #git pull origin main:main如果直接这样子,会导致当前分支于main合并

    说明

    • push写法你们可能像下面一样的写法
    1
    2
    3
    # 这句话代码意思就是
    # 将本地的推送feature/stream到远程仓库的feature/stream
    git push origin feature/stream
    • 不过其实这种写法不是完整的写法,而是一种简写,完整的写法应该是如下
    1
    2
    3
    # 这句话代码意思就是
    #将本地的推送feature/stream到远程仓库的feature/stream
    git push origin feature/stream:feature/stream
    • 完整的语法示范如下,所以后面的远程分支名不写,则表示远程分支是与当前分支合并
    1
    git push <远程主机名> <本地分支名>:<远程分支名>

    同时git pull也是和git push是差不多的,只不过调换了下顺序最后,因为pull是拉取分支,用户肯定想要是默认输入远程分支,而不是本地分支

    1
    git pull <远程主机名> <远程分支名>:<本地分支名>

    之前看过有人说git merge的时候没有origin就是本地和远程

    • 这老铁回答的没毛病,origin就代表的远程分支(可以这么认为),现在分支名称为feature-qiuye为例
    • 本地分支执行: git merge origin/feature
      • 就是代表本地分支和远程上的feature进行合并,也就是远程文件和本地文件合并,而非本地合并
    • 本地分支执行:git merge feature
      • 就是代表本地分支和本地上的feature进行合并,也就是本地文件进行合并操作

    搜索问题之git pull 更新非当前分支会连同当前分支一起合并

    1
    如果当前分支是 master,那么执行 git pull origin test:test 不仅会合并 test-->origin/test,还会合并当前分支 master-->origin/test
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    我觉得主要是命令的语法有问题。

    如果用推送代码,那么你用 git push origin bch1:bch2 命令的目的就很明确。指定远程仓库,指定本地分支bch1推送到远程分支bch2.

    但是如果是git pull,这是拉取远程历史并同步到本地分支的动作。而你指定的分支又不是当前工作分支。这就很容易有歧义了。这个问题可以通过改变命令的方式来解决。

    先 git fetch 远程仓库的历史。

    再切换到要同步的本地分支,然后再merge origin/test 好了。

    或者是切换到 本地 test分支,再直接git pull origin/test 好了。

    参考文章

    git文档参考1

    git文档参考2

    git的origin的含义

    Git 里面的 origin 到底代表啥意思?

    git merge origin master和git merge origin/master的区别

    git fetch,git pull

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/b9ff50aa.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/ba8189ed.html b/ba8189ed.html new file mode 100644 index 000000000..799c1efe8 --- /dev/null +++ b/ba8189ed.html @@ -0,0 +1 @@ +ant design vue treeDefaultExpandAll 没有展 | 梦洁小站-属于你我的小天地

    ant design vue treeDefaultExpandAll 没有展

    • ant design vue treeDefaultExpandAll 更换数据后没有自动展开,找了官网,看到也没什么解决办法在vue当中,所以只好这样子做
    • 原理很简单,就是销毁tree-select然后重新构建
    1
    2
    <a-input v-if="isLoadingTagSelectList" :disabled="isLoadingTagSelectList"/>
    <a-tree-select v-else" v-model:value="searchForm.thirdTagIds" show-search tree-default-expand-all tree-node-filter-prop="title" :tree-data="tagSelectListOptions"></a-tree-select>

    舔狗日记 2022年5月30日 雨天

    突然想找你聊天,打开窗口,发现上次的结尾是我,你没有回,又欲言又止了。每天都翻你的朋友圈,点开你的头像,偷偷看你的微博,寻找着有关你的任何消息。

    舔狗日记 2022年5月31日 晴天

    偶尔也会找别的男生玩,在每一个你不理我的时候。

    舔狗日记 2022年6月1日 晴天

    想着谁,就要告诉谁,不然白想了。

    舔狗日记 2022年6月4日 雨天

    就算我今天把话说得再绝,明天醒来我还是会喜欢你,就是这么没出息,这你知道…

    舔狗日记 2022年6月5日 雨天

    宝~今天我找你,你叫我哪凉快哪呆着去,你可真心疼我,知道天气这么热,这么关心我,我太爱你了我的宝贝~

    舔狗日记 2022年6月6日 晴天

    我暗恋的说眼睛疼了,所以我买了眼药水寄过去了,但他告诉我他有喜欢的人了,让我别再打扰,距离遥远,顺丰要三天才到,可他为什么一秒钟就把眼药水滴在我眼睛里。

    舔狗日记 2022年6月13日 晴天

    我吃完了一个十斤的西瓜,也没等到你回复我的消息。我想,这应该不是你讨厌我,而是这西瓜不够大。

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/ba8189ed.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/baidu.txt b/baidu.txt new file mode 100644 index 000000000..2093c2875 --- /dev/null +++ b/baidu.txt @@ -0,0 +1,10 @@ +https://www.dreamlove.top/bbed5cd2.html +https://www.dreamlove.top/91ff580f.html +https://www.dreamlove.top/50575b3e.html +https://www.dreamlove.top/6ace453b.html +https://www.dreamlove.top/f4cb1979.html +https://www.dreamlove.top/84b2339c.html +https://www.dreamlove.top/30ebed6d.html +https://www.dreamlove.top/a23f7333.html +https://www.dreamlove.top/7ec112ae.html +https://www.dreamlove.top/5419262f.html \ No newline at end of file diff --git a/bbed5cd2.html b/bbed5cd2.html new file mode 100644 index 000000000..98b3299ff --- /dev/null +++ b/bbed5cd2.html @@ -0,0 +1 @@ +大家好我是夯大力,这是我的影视,大力金刚等系列 | 梦洁小站-属于你我的小天地

    大家好我是夯大力,这是我的影视,大力金刚等系列

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/bbed5cd2.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/bbee8271.html b/bbee8271.html new file mode 100644 index 000000000..f917b4683 --- /dev/null +++ b/bbee8271.html @@ -0,0 +1 @@ +ant design vue 当中的表格自定义结构超出隐藏的自适应 动态显示省略号 | 梦洁小站-属于你我的小天地

    ant design vue 当中的表格自定义结构超出隐藏的自适应 动态显示省略号

    前言

    • 我们知道,在ant design vue当中的table组件里面的列表项目,我们一般会使用columns属性定义列表项目,比如下面
    1
    2
    3
    4
    5
    6
    7
    8
    9
    export const columns = [
    {
    title:'姓名',
    dataIndex:'name',
    slots:{customRender: 'name'},
    ellipsis: true,
    align: 'center',
    },
    ]
    • 其中有一个属性,叫ellipsis这个是设置超出部门是否是省略号的,这个属性很有用,但是如果使用了columns当中的插槽,就会失效,所以这个时候我们必须要手动设置下超出省略显示逗号,但是之前设置的查出省略总是出现这个问题,大概就是这样子,当缩小屏幕的时候,明明可以占据完全,但是依旧显示的是省略号

    • 原来的样式(less书写)和html结构代码,其实后面改进主要是改进css样式,html结构没有改动
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    .demo {
    //描述
    &_table{
    &_item-wrapper{
    display: flex;
    //头像
    &_avatar{
    flex-shrink: 0;
    background-color: red;
    vertical-align: middle;
    margin-right: 7px;
    }
    &_info{
    display: flex;
    flex-direction: column;
    text-align: left;
    &>div{
    max-width: 90px;
    text-overflow: ellipsis;
    white-space: nowrap;
    overflow: hidden;
    }
    }
    }
    &_detail{
    color:#FE9136;
    margin-left: 10px;
    cursor:pointer;
    }
    }
    }

    <div class="demo_table_item-wrapper">
    <a-avatar class="demo_table_item-wrapper_avatar" shape="square" size="large"></a-avatar>
    <!--信息-->
    <div class="demo_table_item-wrapper_info">
    <div class="demo_table_item-wrapper_info_name">
    <a-tooltip placement="top">
    <template #title>
    <span>超人</span>
    </template>
    <span>超人</span>
    </a-tooltip>
    </div>
    <div class="demo_table_item-wrapper_info_other">
    <a-tooltip placement="top">
    <template #title>
    <span>冬瓜超人冬瓜超人冬瓜超人</span>
    </template>
    <span>冬瓜超人冬瓜超人冬瓜超人</span>
    </a-tooltip>
    </div>
    </div>
    </div>

    改造和改造后的代码

    • 主要是设置了max-width
    • 可以看到,后期省略号会随着列表宽度而进行改变,而不是固定死了这个省略号

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
            
    .demo {
    //数据展示
    &_table{
    &_item-wrapper{
    display: flex;
    //关键部分
    width: 100%;
    //头像
    &_avatar{
    flex-shrink: 0;
    background-color: red;
    vertical-align: middle;
    margin-right: 7px;
    }
    &_info{
    display: flex;
    flex-flow: column nowrap;
    text-align: left;
    //关键部分
    width: calc(100% - 40px);//减去头像的宽度,有头像就减去,没有就设为100%
    &>div{
    &>span{
    display: inline-block;
    //关键部分,不可以设置width,要设置max-width
    max-width: 95%;
    text-overflow: ellipsis;
    white-space: nowrap;
    overflow: hidden;
    }
    }
    }
    }
    &_detail{
    color:#FE9136;
    margin-left: 10px;
    cursor:pointer;
    }
    }
    }

    <div class="demo_table_item-wrapper">
    <a-avatar class="demo_table_item-wrapper_avatar" shape="square" size="large" >{{record?.userName?.slice(0,1)}}</a-avatar>
    <!--信息-->
    <div class="demo_table_item-wrapper_info">
    <div class="demo_table_item-wrapper_info_name">
    <a-tooltip placement="top">
    <template #title>
    <span>{{ record?.userName }}</span>
    </template>
    <span>{{ record?.userName }}</span>
    </a-tooltip>
    </div>
    <div class="demo_table_item-wrapper_info_other">
    <a-tooltip placement="top">
    <template #title>
    <span>@{{ record?.corpName }}</span>
    </template>
    <span>@{{ record?.corpName }}</span>
    </a-tooltip>
    </div>
    </div>
    </div>
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/bbee8271.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/bd7bee46.html b/bd7bee46.html new file mode 100644 index 000000000..fe614cc8f --- /dev/null +++ b/bd7bee46.html @@ -0,0 +1,2 @@ +idea,webstorm等系列通用激活 | 梦洁小站-属于你我的小天地

    idea,webstorm等系列通用激活

    第一步

    • 下载

    • 替换内容

      • 打开ja-netfilter\config中的url.conf,内容配置如下
      1
      2
      [URL]
      PREFIX,https://account.jetbrains.com/lservice/rpc/validateKey.action

      • 打开ja-netfilter\config中的dns.conf,内容配置如下
      1
      2
      3
      [DNS]
      EQUAL,jetbrains.com;
      EQUAL,dbeaver.com

      • 如果激活的版本低于2022.2,这一步可以不做, 打开ja-netfilter\config中的power.conf,内容配置如下
      1
      2
      3
      4
      5
      [Args]

      [Result]
      EQUAL,120506319308405029943033101198259523557651500267734599270782782071425072541184605728867830395125412768750966448411447392137801711908001958831204692561738046570955709184538088569271703484602917023462976408329100293802371486063140115775311907530943821345005598057265747678100463689973450156515895355214983079672467769169324175533323801179755544364921063654340185317077965735659865485150734884110709760680757502730007505995422237875348017761382234951127263548660889969621730944377739766734765769747684457663965611896398862841334032542726392699785677440644859509166466497325071885386505404431787167239320957696896447925472784312642576835792921100239616617639216190447230487878404191838684279341834945197861631446454083984351911070798505031973496634229907567362853550735007045265430703581336189733180744888091740381912913980707537008943084904260746266383019688346709856215660232636334604552145129775009725685598798774376749830567219982166661918408832945395290223853748014160473876195098438959881711585152480525870219408398012002829112863175041709512032251930709608035158747101960447898838942705485214217426612863919268749874079707310181890737049603255938886865558759802593500502795018952114650332765839003032013708006750600413455628536259,65537,860106576952879101192782278876319243486072481962999610484027161162448933268423045647258145695082284265933019120714643752088997312766689988016808929265129401027490891810902278465065056686129972085119605237470899952751915070244375173428976413406363879128531449407795115913715863867259163957682164040613505040314747660800424242248055421184038777878268502955477482203711835548014501087778959157112423823275878824729132393281517778742463067583320091009916141454657614089600126948087954465055321987012989937065785013284988096504657892738536613208311013047138019418152103262155848541574327484510025594166239784429845180875774012229784878903603491426732347994359380330103328705981064044872334790365894924494923595382470094461546336020961505275530597716457288511366082299255537762891238136381924520749228412559219346777184174219999640906007205260040707839706131662149325151230558316068068139406816080119906833578907759960298749494098180107991752250725928647349597506532778539709852254478061194098069801549845163358315116260915270480057699929968468068015735162890213859113563672040630687357054902747438421559817252127187138838514773245413540030800888215961904267348727206110582505606182944023582459006406137831940959195566364811905585377246353->31872219281407242025505148642475109331663948030010491344733687844358944945421064967310388547820970408352359213697487269225694990179009814674781374751323403257628081559561462351695605167675284372388551941279783515209238245831229026662363729380633136520288327292047232179909791526492877475417113579821717193807584807644097527647305469671333646868883650312280989663788656507661713409911267085806708237966730821529702498972114194166091819277582149433578383639532136271637219758962252614390071122773223025154710411681628917523557526099053858210363406122853294409830276270946292893988830514538950951686480580886602618927728470029090747400687617046511462665469446846624685614084264191213318074804549715573780408305977947238915527798680393538207482620648181504876534152430149355791756374642327623133843473947861771150672096834149014464956451480803326284417202116346454345929350148770746553056995922154382822307758515805142704373984019252210715650875853634697920708113806880196144197384637328982263167395073688501517286678083973976140696077590122053014085412828620051470085033364773099146103525313018873319293728800442101520384088109603555959893639842091339193857485407672132882577840295039058621747654642202620767068924079813640067442975
      EQUAL,8028659553836119901593655311677865290672387540027895708985570867455842278776015838142490556122515317003830575671206217290165955723210315889275621408086645995280770696135307020454887097794294273869941097888549275028604248332746117479367032100139091095818169444690976206636597409322539276252570779516636180497560345090851316373570301807158645002654208816162902430571101092599540795501152368695431168224953320283502815852695423193526255836776240019085157444254721864134058745605280085897450952937893645487302683006269553010996013513395044612932182772364336368242146044741660443063207438830622376694839772096688572619877,65537,21052260334349247097390263197515551021430500095747078612475171670547647379514624742422155617118382403386162585789957995106937640909858927441120214136124618650916253946431099279059999234690271861285094667690686174087562943995337813383652323725628494261414287817117703355799303086256914782640807165021059760198249458510362432176960683009890989990086614909076853502936665842869163947730574085863127445475967466399017447434906719734480523659879746056728772390182338236187070557277461449143752467418310063647027554915213099799725713708651142505590086828211040619445941301844994775362846837122335522584661592447560060751169->986236757547332986472011617696226561292849812918563355472727826767720188564083584387121625107510786855734801053524719833194566624465665316622563244215340671405971599343902468620306327831715457360719532421388780770165778156818229863337344187575566725786793391480600129482653072861971002459947277805295727097226389568776499707662505334062639449916265137796823793276300221537201727072401742985542559596685092673521228140822200236743113743661549252453726123450722876929538747702356573783116197523966334991563351853851212597377279504828784687920949198341066450537230593608440475006386024448307924665012521692416658191

    第二步

    • C:\Users\admin\AppData\Roaming\JetBrains\产品名\产品名.exe.vmoptions(不同的电脑位置不一样)

    1
    2
    3
    4
    --add-opens=java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED
    --add-opens=java.base/jdk.internal.org.objectweb.asm.tree=ALL-UNNAMED
    //以上两行仅在Java17时需要添加,即JB产品的2022.2版本开始需要添加 同时本行注释需要删除
    -javaagent:C:/ja-netfilter/ja-netfilter.jar

    第三步

    使用在线激活

    • 激活网站
    1
    https://jetbra.in/
    • 选择许可证服务器后点击激活

    • 如果链接失败,记得开启vpn~

    在线找激活码(老是说非法)

    • 选择复制

    *

    • 
      +
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/bd7bee46.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/be489729.html b/be489729.html new file mode 100644 index 000000000..1930f8a09 --- /dev/null +++ b/be489729.html @@ -0,0 +1 @@ +navigator.clipboard.readtext开发的时候有用,编译后测试却不生效 | 梦洁小站-属于你我的小天地

    navigator.clipboard.readtext开发的时候有用,编译后测试却不生效

    使用navigator.clipboard.readtext开发的时候有用,编译后测试却不生效

    • 当然,navigator.clipboard.writeText复制也不会生效~

    原因

    • 原因就是在本地的时候都是安全域名,编辑后在服务器上测试的时候可能使用的就是不安全域名了(比如http
    • 安全域包括本地访问与开启TLS安全认证的地址,如 https 协议的地址127.0.0.1localhost

    解决

    1. 使用clipboard.js,官网https://clipboardjs.com/,具体怎么用看https://www.npmjs.com/package/clipboard

    2. 使用window.isSecureContext判断集合document.execCommand使用(不过document.execCommand废弃了,不推荐使用)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      function copyValue = (value) => {
      value = value + "";
      if(window.isSecureContext){
      navigator.clipboard.writeText(value).then(res=>{
      message.success('复制成功');
      })
      }
      //不安全域使用
      else{
      const textArea = document.createElement('textarea');
      document.body.appendChild(textArea);
      textArea.textContent = value;
      //选择
      textArea.select();
      //复制
      document.execCommand && document.execCommand('copy');
      message.success('复制成功');
      }
      }
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/be489729.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/be7ed336.html b/be7ed336.html new file mode 100644 index 000000000..e6ba1c52c --- /dev/null +++ b/be7ed336.html @@ -0,0 +1 @@ +vue后台的一个项目遇到的一些问题和解决办法的记录 | 梦洁小站-属于你我的小天地

    vue后台的一个项目遇到的一些问题和解决办法的记录

    vue使用echarts报错Error in mounted hook: “TypeError: this.dom.getContext is not a function”

    • 解决
      • 一开始是this.$refs.dom获取节点的,后面使用在<el-row>标签上就不可以,所以如果使用this.$refs进行echarts的初始化操作会报错,就使用原生dom获取节点后初始化即可

    elementUI 日期选择器在vue-admin中设置中文显示

    • 方法

      • main.js文件当中

        1
        2
        3
        // import locale from 'element-ui/lib/locale/lang/en' // lang i18n 注释掉
        import locale from 'element-ui/lib/locale/lang/zh-CN' //添加
        Vue.use(ElementUI, { locale });//添加
    • 设置前

    • 设置后

    moment日期插件输出格式错误

    • 之前输出console.log(moment().format("yyyy-MM-dd"));
    • 原来是字母问题,改为大写就可以了
    • 之后改为console.log(moment().format("YYYY-MM-DD"));

    moment获取本周-本月

    • 获取本周
      • moment().day(1)即可设置为星期一
      • moment().day(1).format('YYYY-MM-DD');//输出本周星期一的日期也就是2022/05/09
      • moment().day(7)即可设置为星期天
      • moment().day(7).format("YYYY-MM-DD");//输出本周星期一的日期也就是2022/05/15
    • 获取本月1日
      • moment().startOf('month')即可获取本月一日
      • moment().startOf('month').format("YYYY-MM-DD")//输出本月1日也就是 2022-05-01
    • 获取本月结尾
      • moment().endOf('month')即可获取本月最后一天的日期
      • moment().endOf('month').format("YYYY-MM-DD")//输出本月最后一天,也就是 2022-05-31
    • 获取本日
      • moment().startOf('day')

    明明组件是复用的,为什么echarts图表只显示一个?

    • 如图,只有左边有,为什么会这样子?

    • 解决

      • 原来初始化的时候获取dom是document.querySelector(xxxx)改为this.$refs.xxxx即可

      如图

      • 成功解决

        成功解决

    支付的轮询

    代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    //开始轮询
    if(!this.timer){
    this.timer = setInterval(async () => {
    let result = await this.$API.queryPayStatus(this.orderNo)
    if(result.code == 200){
    //说明支付成功了
    //清除定时器
    clearInterval(this.timer);
    //置空timer
    this.timer = null;
    //更改支付状态记录表
    this.payStatu = result.code;
    //关闭信息弹窗
    this.$msgbox.close();
    //跳转路由
    this.$router.push("/paysuccess");
    }
    }, 2000);
    }

    流程图

    轮询支付流程图

    数组去重

    • set构造函数去重
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <script>
    var tempArray = [1,2,3,4,5,5,6,7];
    //转化为set
    var tempSet = new Set(tempArray);
    //set转换回来数组 - 方法1
    var tempAfterArray1 = [...tempSet];
    ///set转换回来数组 - 方法2
    var tempAfterArray2 = Array.from(tempSet);
    //[1, 2, 3, 4, 5, 6, 7]
    console.log(tempAfterArray1);
    //[1, 2, 3, 4, 5, 6, 7]
    console.log(tempAfterArray2);
    </script>
    • 普通方法(这里就说一个~)

    filterindexOf结合,filter为真的时候才会返回,indexOf如果找到第一个会停止寻找

    1
    2
    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
    35
    36
    37
    38
    39
    40
    var tempArray = [1, 2, 5, 5, 6, 6, 7];

    //item为当前遍历的项
    //index为当前遍历项的索引
    var a = tempArray.filter((item, index) => {
    return tempArray.indexOf(item) == index;
    })
    //[1, 2, 5, 6, 7]
    console.log(a);

    //遍历过程
    item = 1,index=0
    tempArray.indexOf(item) 返回 0
    return 0 == 0 ;//为true,存储'1'

    item = 2,index=1
    tempArray.indexOf(item) 返回 1
    return 1 == 1 ;//为true,存储'2'


    item = 5,index=2
    tempArray.indexOf(item) 返回 2
    return 2 == 2 ;//为true,存储'5'

    item = 5,index=3
    tempArray.indexOf(item) 返回 2
    return 2 == 3 ;//为false,不存储


    item = 6,index=4
    tempArray.indexOf(item) 返回 4
    return 4 == 4 ;//为true,存储'6'

    item = 6,index=5
    tempArray.indexOf(item) 返回 4
    return 4 == 5 ;//为false,不存储

    item = 7,index=6
    tempArray.indexOf(item) 返回 6
    return 6 == 6 ;//为true,存储'7'

    element-ui当中<el-table></el-table>索引自定义

    关键在于为type='index'的绑定:index="自定义函数"

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    <template>
    <div>
    <el-table :data="objects" border>
    <el-table-column
    align="center"
    width="80"
    type="index"
    :index="indexMethod"
    label="索引">
    </el-table-column>
    <el-table-column prop="prop" label="工作地址"> </el-table-column>
    </el-table>
    </div>
    </template>

    <script>
    export default {
    name: "",
    data() {
    return {
    objects: [
    {
    ID: "1",
    JobTitle: "Front Desk Coordinator",
    EmailAddress: "Sofie_Jennson149@deons.tech",
    FirstNameLastName: "Sofie Jennson",
    },
    {
    ID: "2",
    JobTitle: "Global Logistics Supervisor",
    EmailAddress: "Wade_Gallacher1821@elnee.tech",
    FirstNameLastName: "Wade Gallacher",
    },
    ],
    };
    },
    methods: {
    //自定义索引,转化为0001,0002,0003的这种
    indexMethod(index) {
    //转字符串
    index = index.toString();
    while (index.length < 4) {
    index = "0" + index;
    }
    return index;
    },
    },
    };
    </script>

    <style lang="less" scoped>
    </style>

    效果

    element-ui当中<el-table></el-table>索引自定义

    el-table-column使用插槽并将数据绑定在v-model为什么可以实现双向绑定影响到原来数据

    当初学的时候很懵懵懂懂,觉得既然把数据传递给了组件去显示,那应该影响不到原来的数据呢,为什么还会影响到原来数据

    例子

    1
    2
    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
    35
    36
    37
    <template>
    <div>
    <el-table :data="attrForm" border>
    <el-table-column prop="EmailAddress" label="邮箱地址">
    <template slot-scope="{ row }">
    <!-- 为什么可以实现用户输入后data当中的数据也改变? -->
    <el-input v-model="row.EmailAddress"></el-input>
    </template>
    </el-table-column>
    </el-table>
    </div>
    </template>

    <script>
    export default {
    name: "",
    data() {
    return {
    attrForm: [
    {
    ID: "1",
    JobTitle: "Front Desk Coordinator",
    EmailAddress: "Sofie_Jennson149@deons.tech",
    FirstNameLastName: "Sofie Jennson",
    },
    {
    ID: "2",
    JobTitle: "Global Logistics Supervisor",
    EmailAddress: "Wade_Gallacher1821@elnee.tech",
    FirstNameLastName: "Wade Gallacher",
    },
    ],
    };
    },
    };
    </script>

    当初的疑问

    原因

    因为element-ui当中,是按照列来传递数据的,也就是当element-ui遍历attrForm的时候,会将当前遍历项目传递给每一个<el-table-column>,所以为什么输入框当中输入的数据会影响到data

    • 首先是v-model的原因
    • 其次就是传递的是引用数据类型使用指向同一个数据

    差不多这样子图过程吧

    数组哪些方法的使用不会影响数组的响应式?

    1
    2
    3
    4
    5
    6
    7
    push()
    pop()
    shift()
    unshift()
    splice()
    sort()
    reverse()

    再加上一个整体替换也不会

    比如data当中的a数组是响应式的,整体替换,this.a = b;(b也为一个数组),也不会影响数组的响应式

    获取输入框的焦点

    1
    this.$refs.xxx.focus();获取焦点

    el-dialog的显示隐藏的控制

    <el-dialog></el-dialog>是支持.sync的写法的,比如<el-dialog :visible.sync="xxxx"></el-dialog>
    由这个xxx来决定这个dialog是否是显示还是隐藏

    el-form当中的el-form-item占据一行问题

    el-form-item添加下属性label-width:"80px"或者80px自己改为其他的即可

    前后效果

    顺带一提

    <el-input>改为输入框设置type="textarea"再添加下row="4"即可多行输入

    type="textarea"

    可以使用混入mixin解决export default过长

    混入,说简单就是将一个东西和另外一个东西混合在一起,注意是混合,不是替换!,比如我在一个文件里面有方法A,我混入在另外一个文件夹里面,那么另外一个文件夹就可以使用A了

    使用:

    • 引入要混入的对象
    • 配置对象添加mixins:[],数组当中填写引入的混入对象的名称即可

    例子:

    com.js(可以看到,和组件传入的配置对象基本一样)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    export default {
    data() {
    return {
    address:"地球村"
    }
    },
    methods: {
    sayOther(){
    console.log("回收装备,没区别");
    }
    },
    }

    Home.vue(混入使用com.js)

    1
    2
    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
    <template>
    <div></div>
    </template>

    <script>
    import com from "@/other/com.js";
    export default {
    name: "",
    mixins:[com],
    data() {
    return {
    name: "李白",
    };
    },
    mounted(){
    //调用自己的方法
    this.show();
    //调用混入其中的方法
    this.sayOther();
    console.log(this.name);//李白
    console.log(this.address);//地球村
    },
    methods: {
    show() {
    console.log("大家好,我叫" + this.name);
    },
    },
    };
    </script>

    watch和$nextTick结合使用

    • watch只能监视数据的变化,而因为数据变化导致的dom更新是否已经完成watch并不知道(相当于你数据一发生变化,我就执行你设置的回调函数)
    • 而如果我们希望等待dom更新完成后在执行回调,我们就需要结合$nextTick使用
    • $nextTick意思是等待下一次DOM更新后在执行回调
    • 比如说轮播图,如果我们轮播图数据发生了变化,watch监视到了,如果我们立马执行操作使得轮播图重新绘制生成,那么肯定是不行的,因为dom都没有生成,轮播图怎么重新获取dom进行生成,所以我们就可以在里面添加$nextTick等下次DOM更新完成后执行即可
    • 顺带一提: watch支持异步请求,并且支持深度监视,computed不支持
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.14/vue.js"></script>
    <div id="app">
    <p ref="title">{{name}}</p>
    </div>
    <script>
    var vm = new Vue({
    data: {
    name: 'tom'
    }
    }).$mount('#app');
    vm.name = "汤姆";
    //设置新名字后立马输出里面的文本,发现输出的依旧是'tom',而不是'汤姆'
    //因为dom还没有更新完成
    console.log(vm.$refs.title.textContent); // tom

    //下一次dom更新完成后输出,发送输出的是'汤姆'了
    vm.$nextTick(()=>{
    console.log(vm.$refs.title.textContent); // tom
    })
    </script>

    解构赋值 { } 和 [ ]

    • { }不多说

    • [ ] 按顺序解构赋值

      1
      2
      let[,attr] = "v-on:text".split(":");
      console.log(attr);//输出text

    vue-router配置对象当中的scrollBehavior

    可以使得切换路由的时候,路由滚动条可以滚动到我们想滚动的位置

    1
    2
    3
    4
    5
    6
    7
    8
    const router = new VueRouter({
    mode: "history",
    routes,
    //每次路由切换的时候,就将滚动条滚动到最顶端
    scrollBehavior(to, from, savedPosition){
    return {x:0,y:0}
    }
    })

    getters当中要用一个||[] ||{} 的用处

    • 因为有些项目需要从后台发送请求来渲染页面,但是这些数据因为网络延迟的问题肯定不能及时到达,所以就需要在到达之前使用[]或者{}(依据返回数据是数组还是对象来选择),来进行填充,不然你一个空字符串去参与遍历(比如v-for)那肯定会报错的
    • 再者,有人会说getters的事情和我组件有什么关系,一个是仓库,一个是组件,还是有关系的,(因为组件调用了mapGetters来获取仓库的数据),当数据不存在的时候或者遍历一个不可以遍历的数据的时候,就会报错(虽然报错后数据显示依旧正常,是因为后期数据返回,重新渲染了~)(这叫假报错)
    • 所以有时候为了避免假报错,就需要使用||[] ||{}

    比如这个

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    const getters = {
    // 面包屑
    categoryView(state){
    return state.skuDetailInfo.categoryView||{};
    },
    // 商品详情
    skuInfo(state){
    return state.skuDetailInfo.skuInfo||{};
    },
    // 商品售卖属性
    spuSaleAttrList(state){
    return state.skuDetailInfo.spuSaleAttrList||[]
    }
    }
    • 还有就是有时候我们多层嵌套读取数据,比如a.b.c通过a读取b,又通过b读取c,假如读取到b的时候,b是undefined,那么在读取c就会报错,所以这个时候就可以考虑使用||[] 或者 ||{}

    localStorage.getItem();如果获取不到指定的key,返回的是null不是返回undefined

    axios的请求头(Content-Type)

    1
    2
    3
    4
    5
    6
    7
    8
    // 1 默认的格式请求体中的数据会以json字符串的形式发送到后端(默认)
    'Content-Type: application/json '

    // 2 请求体中的数据会以普通表单形式(键值对)发送到后端
    'Content-Type: application/x-www-form-urlencoded'

    // 3 它会将请求体的数据处理为一条消息,以标签为单元,用分隔符分开。既可以上传键值对,也可以上传文件
    'Content-Type: multipart/form-data'

    注意:

    jQuery当中的$.post默认请求头(Content-Type)为 application/x-www-form-urlencoded; charset=UTF-8

    当不使用vuex的时候,我们可以把接口请求函数全部封装在对象当中并挂载Vue原型上

    如:在main.js当中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // 也就是先导入封装所有ajax请求的api.js
    import * as API from "@/api.js"
    // 挂载到vue原型上,和全局事件总线挂载一样
    // 都是在vue生命周期的beforeCreate挂载
    new Vue({
    ...
    beforeCreate(){
    //全局事件总线
    //Vue.prototype.$bus = this;
    //ajax请求
    Vue.prototype.$API = API;
    },
    ...
    })

    Vue注册全局注册的二种方式

    • Vue.use()

    main.js文件(主入口文件)使用Vue.use方法全局注册

    其实element-ui官方也是使用Vue.use()来注册全局组件的~

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import Vue from 'vue'
    import App from './App.vue'
    //引入element-ui组件
    import ElementUI from "element-ui"
    import "element-ui/lib/theme-chalk/index.css"
    Vue.use(ElementUI);

    Vue.config.productionTip = false

    new Vue({
    render: h => h(App),
    }).$mount('#app')

    • Vue.component()

    一般我们用Vue.componet()比较多,因为使用Vue.use()注册全局组件使用起来麻烦点,element-ui看起来使用简单是因为内部封装好了

    • Vue.componet(参数1,参数2)
      • 参数1:注册的组件名
      • 参数2:注册的组件

    main.js文件(主入口文件)使用Vue.component方法全局注册

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    import Vue from 'vue'
    import App from './App.vue'
    //引入自定义组件
    import MyButton from '@/components/MyButton'

    //全局组成element-ui组件
    //参数1: 注册的组件名字为 'MyButton'
    //参数2: 注册的组件为 MyButton
    Vue.component('MyButton',MyButton);

    //或者如果组件配置了name属性,可以直接使用组件的name当中的值
    //Vue.component(MyButton.name,MyButton);

    new Vue({
    render: h => h(App),
    }).$mount('#app')

    Vue当中的watch

    直接就一个监视回调函数
    1
    2
    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
    <template>
    <div>
    <button @click="name = '我是渣渣辉'">这是按钮</button>
    <span>{{ name }}</span>
    </div>
    </template>

    <script>
    export default {
    name: "MyButton",
    data() {
    return {
    name: "李白",
    };
    },
    watch: {
    // 监视name值的变化
    name(newValue, oldValue) {
    console.log("值发生了变化");
    },
    //代码等同于
    // name: {
    // handler(newValue, oldValue) {
    // console.log("值发生了变化");
    // },
    // },
    },
    };
    </script>

    <style lang="less" scoped>
    </style>

    如果需要监视对象当中某一个值的变化的话,就需要用到这种形式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    data() {
    return {
    eat:{
    vegetable:"西红柿",
    meat:"牛肉"
    }
    };
    },
    watch: {
    // 监视eat对象当中meat值的变化
    'eat.meat'(newValue, oldValue) {
    console.log("值发生了变化");
    },
    },
    书写配置项(比如是否深度监视)

    如果我们想监视一个对象当中所有值的变化,包括内部对象的值的变化,我们不可以一个个去书写监听回调吧?我们可以使用配置项当中的deep

    1
    2
    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
    35
    36
    37
    38
    39
    40
    <template>
    <div>
    <button @click="eat.meat = '和牛'">改变肉类</button><br/>
    <button @click="eat.vegetable = '青菜'">改变蔬菜</button><br/>
    <button @click="eat.other.fruit = '苹果'">改变水果</button><br/>
    <span>{{ eat.meat }}</span> <br/>
    <span>{{ eat.vegetable }}</span><br/>
    <span>{{ eat.other.fruit }}</span><br/>
    </div>
    </template>

    <script>
    export default {
    name: "MyButton",
    data() {
    return {
    eat: {
    vegetable: "西红柿",
    meat: "牛肉",
    other: {
    fruit: "草莓",
    },
    },
    };
    },
    watch: {
    //深度监听eat当中值的变化,嵌套多少层都会监听到
    eat: {
    deep: true,
    handler() {
    console.log("值发生了变化");
    },
    },
    },
    };
    </script>

    <style lang="less" scoped>
    </style>

    npm run build:prod 或者 npm run build:stage

    • npm run build:prod: 构建生产环境
      • 打包的时候会读取.env.development文件的,所以不需要前缀可以编辑下这个文件
    • npm run build:stage: 构建测试环境
      • 打包的时候会读取.env.production文件的,所以不需要前缀可以编辑下这个文件

    npm run build:prod 或者 npm run build:stage

    解决:

    vue_project\src\router\index.js 路由主入口文件当中添加如下代码

    1
    2
    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

    const originalPush = VueRouter.prototype.push
    //解决重复提交相同链接报错
    VueRouter.prototype.push = function push(location, onResolve, onReject) {
    if (onResolve || onReject)
    return originalPush.call(this, location, onResolve, onReject)
    return originalPush.call(this, location).catch((err) => {
    if (VueRouter.isNavigationFailure(err)) {
    // resolve err
    return err
    }
    // rethrow error
    return Promise.reject(err)
    })
    }
    const originalReplace = VueRouter.prototype.replace
    VueRouter.prototype.replace = function replace(location, onResolve, onReject) {
    if (onResolve || onReject){
    //回调函数里面会用到this的指向,所以就要使用call
    return originalReplace.call(this, location, onResolve, onReject)
    }
    return originalReplace.call(this, location).catch((err) => {
    if (VueRouter.isNavigationFailure(err)) {
    //如果为相同链接引发的错误,返回错误原因,promise状态为resolve
    // resolve err
    return err
    }
    // rethrow error
    return Promise.reject(err)
    })
    }
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/be7ed336.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    avatar
    梦洁
    小小的字,有大大的梦想~分享我的前端学习过程,经历,错误,和一些其他折腾过程
    关注下我(* ̄▽ ̄*)
    公告
    不断更新中,有问题请留言回复(会通过邮箱提醒~)
    最新文章
    \ No newline at end of file diff --git a/bedba37b.html b/bedba37b.html new file mode 100644 index 000000000..09ed2d9e5 --- /dev/null +++ b/bedba37b.html @@ -0,0 +1 @@ +index.min.js1 Warning value should in shape of { value string number, label ReactNode } when you set labelInValue` to true` | 梦洁小站-属于你我的小天地

    index.min.js1 Warning value should in shape of { value string number, label ReactNode } when you set labelInValue` to true`

    index.min.js1 Warning value should in shape of { value string number, label ReactNode } when you set labelInValue to true

    因为开启了labelInValue属性,所以在设置默认值的时候,就应该传递对应数据,格式也就是要{ label:””,value:”” }

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/bedba37b.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/bfae07e0.html b/bfae07e0.html new file mode 100644 index 000000000..41861c021 --- /dev/null +++ b/bfae07e0.html @@ -0,0 +1 @@ +网页出现为了更好的体验,请将手机竖过来 | 梦洁小站-属于你我的小天地

    网页出现为了更好的体验,请将手机竖过来

    前言

    1
    2
    3
    @media (orientation: landscape) and (min-width: 718px){
    /** ... */
    }

    知识点

    媒体查询(Media Query)是CSS的一种功能,用于在不同设备和屏幕尺寸下应用不同的样式。通过媒体查询,可以根据设备的特性(如宽度、高度、分辨率等)来定制网页的布局和设计。

    orientation 是媒体查询中的一个属性,用来检测设备的方向。它有两个可能的值:

    1. portrait - 纵向模式,指设备的高度大于宽度(例如,手机竖屏)。
    2. landscape - 横向模式,指设备的宽度大于高度(例如,手机横屏或者大多数电脑显示器)。
    3. 在这个示例中,当设备处于横向模式时,页面背景颜色将变为浅蓝色;当设备处于纵向模式时,背景颜色将变为浅绿色。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    /* 适用于设备处于横向模式时的样式 */
    @media screen and (orientation: landscape) {
    body {
    background-color: lightblue;
    }
    }

    /* 适用于设备处于纵向模式时的样式 */
    @media screen and (orientation: portrait) {
    body {
    background-color: lightgreen;
    }
    }
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/bfae07e0.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/bing.json b/bing.json new file mode 100644 index 000000000..80abb96a6 --- /dev/null +++ b/bing.json @@ -0,0 +1 @@ +{"siteUrl":"https://www.dreamlove.top","urlList":["https://www.dreamlove.top/bbed5cd2.html","https://www.dreamlove.top/91ff580f.html","https://www.dreamlove.top/50575b3e.html","https://www.dreamlove.top/6ace453b.html","https://www.dreamlove.top/f4cb1979.html","https://www.dreamlove.top/84b2339c.html","https://www.dreamlove.top/30ebed6d.html","https://www.dreamlove.top/a23f7333.html","https://www.dreamlove.top/7ec112ae.html","https://www.dreamlove.top/5419262f.html"]} \ No newline at end of file diff --git a/c1036aa3.html b/c1036aa3.html new file mode 100644 index 000000000..36079cd5d --- /dev/null +++ b/c1036aa3.html @@ -0,0 +1 @@ +TS2322 Type Element is not assignable to type ReactNode | 梦洁小站-属于你我的小天地

    TS2322 Type Element is not assignable to type ReactNode

    出现TS2322 Type Element is not assignable to type ReactNode

    • 大部分都是ts的问题,没有识别到,目前在preact出现过这种

    解决

    • tsconfig.json添加下面内容
    1
    2
    3
    4
    "paths": {
    "react": ["./node_modules/preact/compat"],
    "react-dom": ["./node_modules/preact/compat"]
    }
    • tsconfig.json完整内容
    1
    2
    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
    {
    "compilerOptions": {
    "target": "ESNext",
    "useDefineForClassFields": true,
    "lib": ["DOM", "DOM.Iterable", "ESNext"],
    "allowJs": false,
    "skipLibCheck": true,
    "esModuleInterop": false,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "forceConsistentCasingInFileNames": true,
    "module": "ESNext",
    "moduleResolution": "Node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react-jsx",
    "jsxImportSource": "preact",
    "paths": {
    "react": ["./node_modules/preact/compat"],
    "react-dom": ["./node_modules/preact/compat"]
    }
    },
    "include": ["src"],
    "references": [
    {
    "path": "./tsconfig.node.json"
    }
    ]
    }
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/c1036aa3.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/c108c2d.html b/c108c2d.html new file mode 100644 index 000000000..66223d9f3 --- /dev/null +++ b/c108c2d.html @@ -0,0 +1 @@ +小红书2020校招前端笔试题卷一 | 梦洁小站-属于你我的小天地

    小红书2020校招前端笔试题卷一

    题目1-下列说法正确的是()多选

    1
    2
    3
    4
    5
    6
    7
    8
    A: requestAnimationFrame(foo) 确保使浏览器在下一次重绘之前调用 foo 方法

    B: 在 addEventListener 的处理方法中使用 e.preventDefault() 可以阻止事件冒泡

    C: 把 <script> 标签的引入放在文档末尾可以确保脚本下载和执行均在文档解析完成后发生

    D: 多个 <script> 标签使用 defer 属性引入脚本时,可以确保脚本的执行是按照其被引入的顺序的

    A选项:

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

    BCD选项:

    • B: 记住,阻止事件默认行为是preventDefault 停止事件冒泡是stopPropagation (记住关键字就好了,阻止和停止这二个关键字)

    • C: 由于js是单线程的,要么css在解析,要么js在解析,所以某一个时刻要么只有js,要么只有css在运行,所以把script放在最后可以等待css解析完成后执行js脚本

    • D: <script defer></script>

      • 具有defer特性的脚本不会阻塞页面,相当于遇到了就丢到后台去执行
      • 具有defer特征的脚本总是到等到DOM解析完毕,但在 DOMContentLoaded 事件之前执行。
      • 具有defer特征的脚本保持其引入顺序执行,(有多个defer,则执行顺序按照引入顺序来执行)

    假设,我们有两个具有 defer 特性的脚本:long.js 在前,small.js 在后。

    1
    2
    3
    <script defer src="https://javascript.info/article/script-async-defer/long.js"></script>
    <script defer src="https://javascript.info/article/script-async-defer/small.js"></script>

    浏览器扫描页面寻找脚本,然后并行下载它们,以提高性能。因此,在上面的示例中,两个脚本是并行下载的。small.js 可能会先下载完成。

    ……但是,defer 特性除了告诉浏览器“不要阻塞页面”之外,还可以确保脚本执行的相对顺序。因此,即使 small.js 先加载完成,它也需要等到 long.js 执行结束才会被执行。

    当我们需要先加载 JavaScript 库,然后再加载依赖于它的脚本时,这可能会很有用。

    总结:

    ​ 使用 defer 属性可以让脚本在文档完全呈现之后再执行,延迟脚本总是按照指定它们的顺序执行。
    ​ 使用 async 属性可以表示当前脚本不必等待其他脚本,也不必阻塞文档呈现。不能保证异步脚本按照它们在页面中出现的顺序执行。

    题目2-请根据下面的示例描述原型链与继承的关系并解释原因:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    class A {}
    class B extends A {}
    const a = new A()
    const b = new B()
    a.__proto__ ===
    b.__proto__ ===
    B.__proto ===
    B.prototype.__proto__ ===
    b.__proto__.__proto__ ===
    • 答案

      1
      2
      3
      4
      5
      a.__proto__ === A.prototype
      b.__proto__ === B.prototype
      B.__proto__ === A
      B.prototype.__proto__ === A.prototype
      b.__proto__.__proto__ === A.prorotype

    解析

    大概意思就是如图

    题目3-ajax 的 readyState 有哪几个状态,含义分别是什么?

    有5个状态,分别是

    0 : 代表open方法还没有被调用

    1: 代表send方法还没有被调用

    2: 还没有收到响应(刚刚发送)

    3: 收到一部分响应(数据流一样慢慢来)

    4: 收到全部数据(数据传输完成)

    补充:

    使用XMLHttpRequest发送ajax请求

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var xhr = new XMLHttpRequest();
    xhr.open("get","http://127.0.0.1:8000");
    xhr.send();
    xhr.onreadystatechange = function() {
    // xhr.readyState === 4 && xhr.status >=200 && xhr.stats <300也可以
    if(xhr.readyState === 4 && xhr.status === 200){
    console.log(xhr.responseText);
    }
    }
    xhr.onerror = function (error) {
    console.log(error)
    };

    使用javascript实现每隔三位使用逗号分隔一次

    图1

    图2

    代码:参考大佬的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    function paddingNum(num){
    //记录下是正数还是负数
    let flag = num > 0 ? true : false;
    //取绝对值并转化为字符串
    num = Math.abs(num)+"";
    //获取整数部分和小数部分
    let [numberLeft,numberRight] = num.split(".");
    //没有小数位就用空字符串填充下
    numberRight = numberRight ? "."+numberRight : "";
    //暂存数字
    let temp = "";
    while(numberLeft.length > 3){
    //每次截取整数部分后3个字符串
    temp = "," + numberLeft.slice(-3) + temp;
    //将整数部分除去最后三个字符串
    numberLeft = numberLeft.slice(0,numberLeft.length - 3);
    }
    //循环结束,最后长度小于3,就将剩余的连接上
    return flag ? numberLeft+temp+numberRight : "-"+numberLeft+temp+numberRight;
    }

    当然,你需要的话也可以用这种方法

    1
    2
    3
    4
    5
    6
    7
    const paddingNum = num => (num).toLocaleString('en-US')

    //写复杂点就是
    function paddingNum(num){
    let temp = num.toLocaleString('en-US');
    return temp;
    }

    题目3-给定长度为n的整数数组nums,其中n > 1,返回输出数组output ,其中output[i] 等于nums中除nums[i] 之外其余各元素的乘积

    题目3

    代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function productExceptSelf(numArray) {
    let tempArray = [];
    numArray.forEach(item1 => {
    let num = 1;
    numArray.forEach(item2 => {
    if (item1 != item2) {
    num = num * item2;
    }
    });
    tempArray.push(num);
    });
    return tempArray;
    }

    题目4-薯队长带着小红薯参加密室逃脱团建游戏,首先遇到了反转游戏,小红薯们根据游戏提示收集了多个单词线索,并将单词按要求加一个空格组 成了句子,最终要求把句子按单词反转解密。 说明:收集的时候单词前后可能会有多个空格,反转后单词不能有多个空格,具体见输入输出样例。

    输入描述:

    输入一个字符串。包含空格和可见字符。长度<=100000。

    输出描述:

    输出一个字符串,表示反转后结果。

    示例1

    1
    2
    3
    4
    5
    输入
    the sky is blue!

    输出
    blue! is sky the

    代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <script>

    console.log(reverse("the sky is blue!"));

    function reverse(str) {
    //匹配任何非空白字符。等价于[^ \f\n\r\t\v]。
    var a = str.match(/(\S+)/g);
    //调用数组的方法
    a = a.reverse();
    return a.join(" ");
    }
    </script>
    测试数据
    jifgykoserhurjkcnhskdncvgkiyhnsjdukzecrunyst 827136547tr8yh74f8o9l3w92qujwhe8iu7ryhf uincymh9oxw,z.ej/09l8kyut8w9o,x.9elo58km4yu ;78p0[2845){*(){P:T*#Q)I(OPUQjfpii[upOYHUIYG 9430q80965tpyw4[h 898888888888888888888888888888888888888888 iouy4ewrt8iu7hgfo2UIYTRFYJHMGDSAI f87e6rwtyugfhkjhki ZHONGGUOGONGCHANDANGWANSUI MAOZHUXIWANSUI
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/c108c2d.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    avatar
    梦洁
    小小的字,有大大的梦想~分享我的前端学习过程,经历,错误,和一些其他折腾过程
    关注下我(* ̄▽ ̄*)
    公告
    不断更新中,有问题请留言回复(会通过邮箱提醒~)
    \ No newline at end of file diff --git a/c2752c3f.html b/c2752c3f.html new file mode 100644 index 000000000..0bdd70d00 --- /dev/null +++ b/c2752c3f.html @@ -0,0 +1 @@ +今日刷题-Object.defineProperty和Object.getOwnPropertyDescriptor | 梦洁小站-属于你我的小天地

    今日刷题-Object.defineProperty和Object.getOwnPropertyDescriptor

    题目1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    下列window方法中,可以显示对话框的一项是()

    A: confirm()

    B: alert()

    C: prompt()

    D: open()

    • 答案
      • C
    • 解析
      • A: 弹出一个判断对话框(有确认,取消按钮和提示文本),返回值为用户所按下的,为返回true,为返回false
      • B: 弹出一个对话框
      • C: 弹出一个用户输入对话框,返回值为用户输入的值,没有输入则返回值为null
      • D: 打开一个指定的窗口并指定打开方式

    题目2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var s = Symbol('key'); ...①
    console.log(s + '123'); ...②
    var obj = {
    [s]:function(){console.log(1);} ...③
    }
    var b = Reflect.ownKeys(obj); ...④
    A: ①

    B: ②

    C: ③

    D: ④

    • 答案

      • B
    • 解析

      • Symbol是ES6新增的基本数据类型之一(全部基本数据类型为undefined string symbol number null boolean) ‘记忆:usnb,你如此牛逼’

      • Symbol不可以发生类型转换(发生就报错)

      • Symbol的值不能与其他类型进行运算

      • 在对象内部使用Symbol 值作为属性名的时候,必须要将值放在方括号中

      • Symbol 值如果想要作为属性名,那就不能再用点运算符,因为点运算符后面跟的总是字符串

        1
        2
        3
        4
        5
        6
        7
        8
         let id = Symbol("id");
        let obj = {
        [id]:'symbol'
        };
        let array = Object.getOwnPropertySymbols(obj);
        console.log(array); //[Symbol(id)]
        console.log(obj[array[0]]); //'symbol'

        image-20220511091545286

    题目3

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    var obj = {brand:'华为',price:1999};
    Object.defineProperty(obj,'id',{value:1})
    Object.defineProperty(obj,'price',{configurable:false})
    console.log(Object.keys(obj).length); ...①
    for (var k in obj){
    console.log(obj[k]); ...②
    }
    obj.price = 999;
    delete obj['price']
    console.log(obj); ...③
    A: ①式输出结果为3

    B: ②式输出结果为华为 1999 1

    C: ③式输出结果为{brand: '华为', price: 999, id: 1}

    D: ③式输出结果为{brand: '华为', id: 1}

    • 答案

      • C
    • 解析

      • Object.defineProperty注意点

        • 通过其添加的属性值,如果没有设置该属性的描述符(比如writeable,configurable,和enumberable)那么默认为false
        • 可以使用**Object.getOwnPropertyDescriptor(obj,key)**来查看
      • 查看

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        var obj = {
        brand: '华为',
        price: 1999
        };
        Object.defineProperty(obj, 'id', {
        value: 1
        })
        Object.defineProperty(obj, 'price', {
        configurable: false
        })

        // 下面一行代码输出结果
        // 新添加的属性,未设置其他属性,则默认为false
        // configurable: false
        // enumerable: false
        // value: 1
        // writable: false
        console.log(Object.getOwnPropertyDescriptor(obj,'id'));

        //下面一行代码输出结果
        // 本来就有的属性,上方只是对值做了修改,并不是新增加的
        // configurable: false
        // enumerable: true
        // value: 1999
        // writable: true
        console.log(Object.getOwnPropertyDescriptor(obj,'price'));
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/c2752c3f.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/c3c76e8e.html b/c3c76e8e.html new file mode 100644 index 000000000..c7f5ea40f --- /dev/null +++ b/c3c76e8e.html @@ -0,0 +1 @@ +穿越火线crossfire2.0 sql2014数据库文件 | 梦洁小站-属于你我的小天地

    穿越火线crossfire2.0 sql2014数据库文件

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/c3c76e8e.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/c6b5dfff.html b/c6b5dfff.html new file mode 100644 index 000000000..7f63830a3 --- /dev/null +++ b/c6b5dfff.html @@ -0,0 +1 @@ +js当中的图片懒加载,懒加载的一些概念和相关知识点 | 梦洁小站-属于你我的小天地

    js当中的图片懒加载,懒加载的一些概念和相关知识点

    知道什么是懒加载

    说通俗点就是轮到我我再出现,而不是一进入页面就出现

    为什么需要

    1. 节省资源
    2. 加快网页打开
    3. 提升用户体育
    4. 巴拉巴拉

    懒加载原理

    红色为我们实际看到的浏览器窗口,黑色的为实际网页的长度

    我们刚进入主页的时候

    用户拖动滚动条,看到绿色的框框,这些绿色的框框才开始加载出来

    必要知道的知识

    1. 获取元素距离顶部(body)的距离:元素.offsetTop

      1. 注意:如果元素的父元素(不管是祖父还是曾祖父还是亲生父亲),只要任意一个父亲开启了定位(不管是相对还是绝对),那么子元素的offsetTop属性值都是参考父元素的
      2. 如果父元素没有开启定位,那么就参考body
      3. 元素.offsetLeft同理
    2. 获取浏览器垂直滚动的距离:document.documentElement.onscrollTop

    3. offsetTop和offsetLeft 这两个属性,IE 、Opera和Firefox对它俩的解释存在差异:

      1. IE5.0+ 、Opera8.0+: offsetTop和offsetLeft 都是相对父级元素
      2. Firefox1.06: offsetTop和offsetLeft 都是相对于body元素
    4. 为了解决父元素如果开启定位的问题,我们可以使用offsetParent加循环获取距离body的距离

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      //获取元素距离body的top值和left值
      function getPoint(obj) {
      //记录left的值
      var left = 0;
      //记录top的值
      var top = 0;
      //如果需要减去边框的距离,可以解除下面这二行注释
      //var borderLeftWidth;
      //var borderTopWidth;

      //如果obj.offsetParent获取不到父级了,就会跳出循环
      while (obj) {
      left = left + obj.offsetLeft;
      top = top + obj.offsetTop;
      //如果需要减去边框的距离,可以解除下面这二行注释,并将前二行注释
      //left = left + obj.offsetLeft - borderLeftWidth;
      //top = top + obj.offsetTop - borderTopWidth;
      obj = obj.offsetParent;
      }
      //返回距离body的left值和top值
      return {
      left,
      top
      };
      }

    实现懒加载

    1. 一开始的思路(错误的):

      1. 先获取元素距离可视窗口顶部的距离,然后再和可视窗口的高度进行比较,如果小于可视窗口的高度,就加载出来(错误,因为二者距离都是死的)
    2. 后来的思路(正确的)

      1. 还是依旧获取元素距离可是窗口顶部的距离,然后再和(可视窗口的高度+滚动的距离)进行比较,如果某一个元素距离顶部距离小于这二者之和的距离,则元素里面的资源加载出来

      代码

      css代码
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      <style>
      * {
      margin: 0;
      padding: 0;
      }
      body,
      html {
      width: 100%;
      height: 100%;
      }
      img {
      vertical-align: bottom;
      display: inline;
      width: 590px;
      height: 500px;
      }
      .content {
      width: 1200px;
      margin: 0 auto;
      }
      </style>
      HTML代码
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      <div class="header">
      <h2>图片展示</h2>
      </div>
      <div class="content">
      <img data-src="./img/01.jpg" alt="">
      <img data-src="./img/02.jpg" alt="">
      <img data-src="./img/03.jpg" alt="">
      <img data-src="./img/04.jpg" alt="">
      <img data-src="./img/05.jpg" alt="">
      <img data-src="./img/06.jpg" alt="">
      <img data-src="./img/07.jpg" alt="">
      <img data-src="./img/08.jpg" alt="">
      <img data-src="./img/09.jpg" alt="">
      <img data-src="./img/10.jpg" alt="">
      <img data-src="./img/10.jpg" alt="">
      <img data-src="./img/11.jpg" alt="">
      <img data-src="./img/12.jpg" alt="">
      </div>
      JavaScript代码
      1
      2
      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
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      <script>
      //获取元素距离body的top值和left值
      function getPoint(obj) {
      var left = 0;
      var top = 0;
      while (obj) {
      left = left + obj.offsetLeft;
      top = top + obj.offsetTop;
      obj = obj.offsetParent;
      }
      return {
      left,
      top
      };
      }
      //防止图片没有加载出来获取不到图片的offsetTop值,就填加了一个window.onload事件
      //页面初始化(用户刚刚进入页面的时候)
      window.onload = function () {
      var domHeight = document.documentElement.clientHeight; //视口的高度
      var imgs = document.querySelectorAll("img");
      //然后循环
      //每一个元素判断距离body的值是否 小于 (可视窗口的高度+垂直滚动的距离)
      for (var v of imgs) {
      if (getPoint(v).top < (domHeight + document.documentElement.scrollTop)) {
      //显示图片
      // v.src=v.dataset.src;也可以获取自定义属性值
      v.src = v.getAttribute("data-src");
      }
      }
      }
      //滚动事件
      window.onscroll = function () {
      var domHeight = document.documentElement.clientHeight; //视口的高度
      var imgs = document.querySelectorAll("img");
      //然后循环
      for (var v of imgs) {
      //每一个元素判断距离body的值是否 小于 (可视窗口的高度+垂直滚动的距离)
      if (getPoint(v).top < (domHeight + document.documentElement.scrollTop)) {
      //显示图片
      // v.src=v.dataset.src;
      v.src = v.getAttribute("data-src");
      }
      }
      }
      </script>

      展示效果

      可以看到图片不是一次性加载出来,而是随着滚动条滚动才加载出来的

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/c6b5dfff.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/c7600fcc.html b/c7600fcc.html new file mode 100644 index 000000000..06633b67a --- /dev/null +++ b/c7600fcc.html @@ -0,0 +1 @@ +LeanCloud白嫖valine评论和避免休眠指南 | 梦洁小站-属于你我的小天地

    LeanCloud白嫖valine评论和避免休眠指南

    由于白嫖原因,LenCloud会自动休眠导致valine评论通知邮箱发送不了,所以这里记录了下我解决的过程

    另外一种直接在valine添加定时器的方式失败,所以就用另外一种

    绑定域名

    1. 添加云引擎、ClientEngine 域名

    假设你的域名为example.com,为Valine后端分配的二级域名是xxx.example.com,那么就在域名栏内填入xxx.example.com

    2.去域名管理添加CNAME解析

    3.等待一会,会自动部署

    4.部署完成,添加管理员账号密码

    • 添加你的账号密码
    • 添加一行后记得添加下你的email

    添加Email

    避免休眠(免费的原因~)

    注意: !!!一定要等绑定域名显示完成后才有效果

    注意: !!!一定要等绑定域名显示完成后才有效果

    注意: !!!一定要等绑定域名显示完成后才有效果

    一定要等绑定域名显示完成后才有效果

    • 因为是免费的,所以每天必须休眠 6 小时~~~

    • 并且单纯定时器的方式会提示因流控原因,通过定时任务唤醒体验版实例失败,建议升级至标准版云引擎实例避免休眠

    • 所以需要解决

    • 这里使用的是crontab命令代码

    • 其他避免休眠方法可以看这个博主的

    服务器命令提示行输入crontab -e(由于没有选择默认编辑器,所以我就会出现这个提示)

    添加下方代码
    1
    2
    */29 7-23 * * * curl https://your_site &> /dev/null && date "+\%D \%H:\%M:\%S" >> ~/wakeup.log
    https://your_site为后台评论的地址

    接下来只需保存退出,按Ctrl键和O键保存,然后回车确定。

    测试可用

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/c7600fcc.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/categories/Antd/React/index.html b/categories/Antd/React/index.html new file mode 100644 index 000000000..a767e5e15 --- /dev/null +++ b/categories/Antd/React/index.html @@ -0,0 +1 @@ +分类: React | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/Antd/index.html b/categories/Antd/index.html new file mode 100644 index 000000000..801b48e8b --- /dev/null +++ b/categories/Antd/index.html @@ -0,0 +1 @@ +分类: Antd | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/Css/index.html b/categories/Css/index.html new file mode 100644 index 000000000..cb888e1c5 --- /dev/null +++ b/categories/Css/index.html @@ -0,0 +1 @@ +分类: Css | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/HTML/css/index.html b/categories/HTML/css/index.html new file mode 100644 index 000000000..1d0a61aa4 --- /dev/null +++ b/categories/HTML/css/index.html @@ -0,0 +1 @@ +分类: css | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/HTML/echarts/index.html b/categories/HTML/echarts/index.html new file mode 100644 index 000000000..5099c2f66 --- /dev/null +++ b/categories/HTML/echarts/index.html @@ -0,0 +1 @@ +分类: echarts | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/HTML/index.html b/categories/HTML/index.html new file mode 100644 index 000000000..9630727bc --- /dev/null +++ b/categories/HTML/index.html @@ -0,0 +1 @@ +分类: HTML | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/HTML/javascript/css/index.html b/categories/HTML/javascript/css/index.html new file mode 100644 index 000000000..9a24ad030 --- /dev/null +++ b/categories/HTML/javascript/css/index.html @@ -0,0 +1 @@ +分类: css | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/HTML/javascript/index.html b/categories/HTML/javascript/index.html new file mode 100644 index 000000000..f27dc6b86 --- /dev/null +++ b/categories/HTML/javascript/index.html @@ -0,0 +1 @@ +分类: javascript | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/HTML/javascript/wechat/index.html b/categories/HTML/javascript/wechat/index.html new file mode 100644 index 000000000..8f2fe5f58 --- /dev/null +++ b/categories/HTML/javascript/wechat/index.html @@ -0,0 +1 @@ +分类: wechat | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/HTML/javscript/css/index.html b/categories/HTML/javscript/css/index.html new file mode 100644 index 000000000..10ecede0b --- /dev/null +++ b/categories/HTML/javscript/css/index.html @@ -0,0 +1 @@ +分类: css | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/HTML/javscript/echarts/index.html b/categories/HTML/javscript/echarts/index.html new file mode 100644 index 000000000..a7d15e47d --- /dev/null +++ b/categories/HTML/javscript/echarts/index.html @@ -0,0 +1 @@ +分类: echarts | 梦洁小站-属于你我的小天地
    分类 - echarts
    2022
    Echarts图表的基本使用
    Echarts图表的基本使用
    \ No newline at end of file diff --git a/categories/HTML/javscript/echarts/vue/index.html b/categories/HTML/javscript/echarts/vue/index.html new file mode 100644 index 000000000..9823a9312 --- /dev/null +++ b/categories/HTML/javscript/echarts/vue/index.html @@ -0,0 +1 @@ +分类: vue | 梦洁小站-属于你我的小天地
    分类 - vue
    2022
    Echarts图表的基本使用
    Echarts图表的基本使用
    \ No newline at end of file diff --git a/categories/HTML/javscript/index.html b/categories/HTML/javscript/index.html new file mode 100644 index 000000000..4b67c2d2e --- /dev/null +++ b/categories/HTML/javscript/index.html @@ -0,0 +1 @@ +分类: javscript | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/HTML/javscript/page/2/index.html b/categories/HTML/javscript/page/2/index.html new file mode 100644 index 000000000..3c1824fe6 --- /dev/null +++ b/categories/HTML/javscript/page/2/index.html @@ -0,0 +1 @@ +分类: javscript | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/HTML/javscript/page/3/index.html b/categories/HTML/javscript/page/3/index.html new file mode 100644 index 000000000..9ddf25e7b --- /dev/null +++ b/categories/HTML/javscript/page/3/index.html @@ -0,0 +1 @@ +分类: javscript | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/HTML/javscript/page/4/index.html b/categories/HTML/javscript/page/4/index.html new file mode 100644 index 000000000..f94621bdb --- /dev/null +++ b/categories/HTML/javscript/page/4/index.html @@ -0,0 +1 @@ +分类: javscript | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/HTML/javscript/page/5/index.html b/categories/HTML/javscript/page/5/index.html new file mode 100644 index 000000000..5c93e03d4 --- /dev/null +++ b/categories/HTML/javscript/page/5/index.html @@ -0,0 +1 @@ +分类: javscript | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/HTML/javscript/page/6/index.html b/categories/HTML/javscript/page/6/index.html new file mode 100644 index 000000000..e055b8e8b --- /dev/null +++ b/categories/HTML/javscript/page/6/index.html @@ -0,0 +1 @@ +分类: javscript | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/HTML/javscript/socket/express/index.html b/categories/HTML/javscript/socket/express/index.html new file mode 100644 index 000000000..b793d6ec6 --- /dev/null +++ b/categories/HTML/javscript/socket/express/index.html @@ -0,0 +1 @@ +分类: express | 梦洁小站-属于你我的小天地
    分类 - express
    2022
    express+socket简易聊天室
    express+socket简易聊天室
    \ No newline at end of file diff --git a/categories/HTML/javscript/socket/index.html b/categories/HTML/javscript/socket/index.html new file mode 100644 index 000000000..d1f76fb84 --- /dev/null +++ b/categories/HTML/javscript/socket/index.html @@ -0,0 +1 @@ +分类: socket | 梦洁小站-属于你我的小天地
    分类 - socket
    2022
    express+socket简易聊天室
    express+socket简易聊天室
    \ No newline at end of file diff --git a/categories/HTML/javscript/typescript/index.html b/categories/HTML/javscript/typescript/index.html new file mode 100644 index 000000000..8522f14a8 --- /dev/null +++ b/categories/HTML/javscript/typescript/index.html @@ -0,0 +1 @@ +分类: typescript | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/HTML/javscript/vite/index.html b/categories/HTML/javscript/vite/index.html new file mode 100644 index 000000000..7eab193db --- /dev/null +++ b/categories/HTML/javscript/vite/index.html @@ -0,0 +1 @@ +分类: vite | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/HTML/javscript/vue/index.html b/categories/HTML/javscript/vue/index.html new file mode 100644 index 000000000..fc2cf9e03 --- /dev/null +++ b/categories/HTML/javscript/vue/index.html @@ -0,0 +1 @@ +分类: vue | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/HTML/javscript/vue/page/2/index.html b/categories/HTML/javscript/vue/page/2/index.html new file mode 100644 index 000000000..42dc0b1de --- /dev/null +++ b/categories/HTML/javscript/vue/page/2/index.html @@ -0,0 +1 @@ +分类: vue | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/HTML/javscript/vue/page/3/index.html b/categories/HTML/javscript/vue/page/3/index.html new file mode 100644 index 000000000..4fee8d33a --- /dev/null +++ b/categories/HTML/javscript/vue/page/3/index.html @@ -0,0 +1 @@ +分类: vue | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/HTML/javscript/webpack/index.html b/categories/HTML/javscript/webpack/index.html new file mode 100644 index 000000000..d56015595 --- /dev/null +++ b/categories/HTML/javscript/webpack/index.html @@ -0,0 +1 @@ +分类: webpack | 梦洁小站-属于你我的小天地
    分类 - webpack
    2022
    webpack构建工具的学习
    webpack构建工具的学习
    \ No newline at end of file diff --git a/categories/HTML/javscript/wechat/index.html b/categories/HTML/javscript/wechat/index.html new file mode 100644 index 000000000..230aa4346 --- /dev/null +++ b/categories/HTML/javscript/wechat/index.html @@ -0,0 +1 @@ +分类: wechat | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/HTML/page/2/index.html b/categories/HTML/page/2/index.html new file mode 100644 index 000000000..348891502 --- /dev/null +++ b/categories/HTML/page/2/index.html @@ -0,0 +1 @@ +分类: HTML | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/HTML/page/3/index.html b/categories/HTML/page/3/index.html new file mode 100644 index 000000000..ed45b2076 --- /dev/null +++ b/categories/HTML/page/3/index.html @@ -0,0 +1 @@ +分类: HTML | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/HTML/page/4/index.html b/categories/HTML/page/4/index.html new file mode 100644 index 000000000..ba360f4c4 --- /dev/null +++ b/categories/HTML/page/4/index.html @@ -0,0 +1 @@ +分类: HTML | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/HTML/page/5/index.html b/categories/HTML/page/5/index.html new file mode 100644 index 000000000..1db4fbbe0 --- /dev/null +++ b/categories/HTML/page/5/index.html @@ -0,0 +1 @@ +分类: HTML | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/HTML/page/6/index.html b/categories/HTML/page/6/index.html new file mode 100644 index 000000000..d6d496ccb --- /dev/null +++ b/categories/HTML/page/6/index.html @@ -0,0 +1 @@ +分类: HTML | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/HTML/page/7/index.html b/categories/HTML/page/7/index.html new file mode 100644 index 000000000..3b06c7931 --- /dev/null +++ b/categories/HTML/page/7/index.html @@ -0,0 +1 @@ +分类: HTML | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/HTML/page/8/index.html b/categories/HTML/page/8/index.html new file mode 100644 index 000000000..3240ae0f5 --- /dev/null +++ b/categories/HTML/page/8/index.html @@ -0,0 +1 @@ +分类: HTML | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/JAVA/index.html b/categories/JAVA/index.html new file mode 100644 index 000000000..8009c45a2 --- /dev/null +++ b/categories/JAVA/index.html @@ -0,0 +1 @@ +分类: JAVA | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/MATLAB/index.html b/categories/MATLAB/index.html new file mode 100644 index 000000000..a4d76aba0 --- /dev/null +++ b/categories/MATLAB/index.html @@ -0,0 +1 @@ +分类: MATLAB | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/categories/QQ\345\206\234\345\234\272/index.html" "b/categories/QQ\345\206\234\345\234\272/index.html" new file mode 100644 index 000000000..c9396161d --- /dev/null +++ "b/categories/QQ\345\206\234\345\234\272/index.html" @@ -0,0 +1 @@ +分类: QQ农场 | 梦洁小站-属于你我的小天地
    分类 - QQ农场
    2024
    QQ农场明月鲨鱼学习记录
    QQ农场明月鲨鱼学习记录
    \ No newline at end of file diff --git a/categories/React/index.html b/categories/React/index.html new file mode 100644 index 000000000..0bee24dee --- /dev/null +++ b/categories/React/index.html @@ -0,0 +1 @@ +分类: React | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/antd/index.html b/categories/antd/index.html new file mode 100644 index 000000000..48158ad1d --- /dev/null +++ b/categories/antd/index.html @@ -0,0 +1 @@ +分类: antd | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/antd/react/dva/index.html b/categories/antd/react/dva/index.html new file mode 100644 index 000000000..6ec825ccc --- /dev/null +++ b/categories/antd/react/dva/index.html @@ -0,0 +1 @@ +分类: dva | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/antd/react/index.html b/categories/antd/react/index.html new file mode 100644 index 000000000..2808e219a --- /dev/null +++ b/categories/antd/react/index.html @@ -0,0 +1 @@ +分类: react | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/docker/index.html b/categories/docker/index.html new file mode 100644 index 000000000..e192c5733 --- /dev/null +++ b/categories/docker/index.html @@ -0,0 +1 @@ +分类: docker | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/docker/rustDesk/index.html b/categories/docker/rustDesk/index.html new file mode 100644 index 000000000..914a0c5b4 --- /dev/null +++ b/categories/docker/rustDesk/index.html @@ -0,0 +1 @@ +分类: rustDesk | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/electron/index.html b/categories/electron/index.html new file mode 100644 index 000000000..fee0f9be0 --- /dev/null +++ b/categories/electron/index.html @@ -0,0 +1 @@ +分类: electron | 梦洁小站-属于你我的小天地
    分类 - electron
    2024
    electron-键盘打字小猫吐花学习
    electron-键盘打字小猫吐花学习
    \ No newline at end of file diff --git a/categories/github/action/index.html b/categories/github/action/index.html new file mode 100644 index 000000000..12badb8d9 --- /dev/null +++ b/categories/github/action/index.html @@ -0,0 +1 @@ +分类: action | 梦洁小站-属于你我的小天地
    分类 - action
    2024
    Github action的学习
    Github action的学习
    \ No newline at end of file diff --git a/categories/github/index.html b/categories/github/index.html new file mode 100644 index 000000000..63421615d --- /dev/null +++ b/categories/github/index.html @@ -0,0 +1 @@ +分类: github | 梦洁小站-属于你我的小天地
    分类 - github
    2024
    Github action的学习
    Github action的学习
    \ No newline at end of file diff --git a/categories/hexo/index.html b/categories/hexo/index.html new file mode 100644 index 000000000..dba6aef65 --- /dev/null +++ b/categories/hexo/index.html @@ -0,0 +1 @@ +分类: hexo | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/categories/hexo/\347\263\273\347\273\237/index.html" "b/categories/hexo/\347\263\273\347\273\237/index.html" new file mode 100644 index 000000000..182dc4c0d --- /dev/null +++ "b/categories/hexo/\347\263\273\347\273\237/index.html" @@ -0,0 +1 @@ +分类: 系统 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/index.html b/categories/index.html new file mode 100644 index 000000000..c7fad4064 --- /dev/null +++ b/categories/index.html @@ -0,0 +1 @@ +分类 | 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/categories/javscript/html/index.html b/categories/javscript/html/index.html new file mode 100644 index 000000000..78b939371 --- /dev/null +++ b/categories/javscript/html/index.html @@ -0,0 +1 @@ +分类: html | 梦洁小站-属于你我的小天地
    分类 - html
    2022
    纯css+html实现的分页器功能
    纯css+html实现的分页器功能
    \ No newline at end of file diff --git a/categories/javscript/index.html b/categories/javscript/index.html new file mode 100644 index 000000000..5c6059aaa --- /dev/null +++ b/categories/javscript/index.html @@ -0,0 +1 @@ +分类: javscript | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/javscript/jQuery/index.html b/categories/javscript/jQuery/index.html new file mode 100644 index 000000000..492911701 --- /dev/null +++ b/categories/javscript/jQuery/index.html @@ -0,0 +1 @@ +分类: jQuery | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/python/index.html b/categories/python/index.html new file mode 100644 index 000000000..46480331a --- /dev/null +++ b/categories/python/index.html @@ -0,0 +1 @@ +分类: python | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/react/index.html b/categories/react/index.html new file mode 100644 index 000000000..83c54c682 --- /dev/null +++ b/categories/react/index.html @@ -0,0 +1 @@ +分类: react | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/ts/index.html b/categories/ts/index.html new file mode 100644 index 000000000..d256e60eb --- /dev/null +++ b/categories/ts/index.html @@ -0,0 +1 @@ +分类: ts | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/ts/typescript/HTML/index.html b/categories/ts/typescript/HTML/index.html new file mode 100644 index 000000000..86f1f120e --- /dev/null +++ b/categories/ts/typescript/HTML/index.html @@ -0,0 +1 @@ +分类: HTML | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/ts/typescript/HTML/javscript/index.html b/categories/ts/typescript/HTML/javscript/index.html new file mode 100644 index 000000000..06c5d47a6 --- /dev/null +++ b/categories/ts/typescript/HTML/javscript/index.html @@ -0,0 +1 @@ +分类: javscript | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/ts/typescript/index.html b/categories/ts/typescript/index.html new file mode 100644 index 000000000..2593dbf54 --- /dev/null +++ b/categories/ts/typescript/index.html @@ -0,0 +1 @@ +分类: typescript | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/typescript/index.html b/categories/typescript/index.html new file mode 100644 index 000000000..ce01fbb36 --- /dev/null +++ b/categories/typescript/index.html @@ -0,0 +1 @@ +分类: typescript | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/valine/LeanCloud/index.html b/categories/valine/LeanCloud/index.html new file mode 100644 index 000000000..c047fe0c0 --- /dev/null +++ b/categories/valine/LeanCloud/index.html @@ -0,0 +1 @@ +分类: LeanCloud | 梦洁小站-属于你我的小天地
    分类 - LeanCloud
    2022
    LeanCloud白嫖valine评论和避免休眠指南
    LeanCloud白嫖valine评论和避免休眠指南
    \ No newline at end of file diff --git "a/categories/valine/LeanCloud/\347\231\275\345\253\226\346\214\207\345\215\227/index.html" "b/categories/valine/LeanCloud/\347\231\275\345\253\226\346\214\207\345\215\227/index.html" new file mode 100644 index 000000000..400d8297a --- /dev/null +++ "b/categories/valine/LeanCloud/\347\231\275\345\253\226\346\214\207\345\215\227/index.html" @@ -0,0 +1 @@ +分类: 白嫖指南 | 梦洁小站-属于你我的小天地
    分类 - 白嫖指南
    2022
    LeanCloud白嫖valine评论和避免休眠指南
    LeanCloud白嫖valine评论和避免休眠指南
    \ No newline at end of file diff --git a/categories/valine/index.html b/categories/valine/index.html new file mode 100644 index 000000000..d4f5cdb61 --- /dev/null +++ b/categories/valine/index.html @@ -0,0 +1 @@ +分类: valine | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/vant/index.html b/categories/vant/index.html new file mode 100644 index 000000000..ebb219594 --- /dev/null +++ b/categories/vant/index.html @@ -0,0 +1 @@ +分类: vant | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/vant/vue/index.html b/categories/vant/vue/index.html new file mode 100644 index 000000000..5658ef63d --- /dev/null +++ b/categories/vant/vue/index.html @@ -0,0 +1 @@ +分类: vue | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/vue/index.html b/categories/vue/index.html new file mode 100644 index 000000000..6d0298c6e --- /dev/null +++ b/categories/vue/index.html @@ -0,0 +1 @@ +分类: vue | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/categories/wechat/index.html b/categories/wechat/index.html new file mode 100644 index 000000000..5274f063a --- /dev/null +++ b/categories/wechat/index.html @@ -0,0 +1 @@ +分类: wechat | 梦洁小站-属于你我的小天地
    分类 - wechat
    2023
    微信小程序渐进式骨架屏的写法
    微信小程序渐进式骨架屏的写法
    \ No newline at end of file diff --git a/categories/x64dbg/index.html b/categories/x64dbg/index.html new file mode 100644 index 000000000..9cd79ee59 --- /dev/null +++ b/categories/x64dbg/index.html @@ -0,0 +1 @@ +分类: x64dbg | 梦洁小站-属于你我的小天地
    分类 - x64dbg
    2024
    x64dbg反汇编技术入门学习笔记
    x64dbg反汇编技术入门学习笔记
    \ No newline at end of file diff --git "a/categories/\345\210\267\351\242\230/CSS/index.html" "b/categories/\345\210\267\351\242\230/CSS/index.html" new file mode 100644 index 000000000..0ef8b3611 --- /dev/null +++ "b/categories/\345\210\267\351\242\230/CSS/index.html" @@ -0,0 +1 @@ +分类: CSS | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/categories/\345\210\267\351\242\230/index.html" "b/categories/\345\210\267\351\242\230/index.html" new file mode 100644 index 000000000..723f8477d --- /dev/null +++ "b/categories/\345\210\267\351\242\230/index.html" @@ -0,0 +1 @@ +分类: 刷题 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/categories/\345\210\267\351\242\230/page/2/index.html" "b/categories/\345\210\267\351\242\230/page/2/index.html" new file mode 100644 index 000000000..00b8b6388 --- /dev/null +++ "b/categories/\345\210\267\351\242\230/page/2/index.html" @@ -0,0 +1 @@ +分类: 刷题 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/categories/\345\210\267\351\242\230/page/3/index.html" "b/categories/\345\210\267\351\242\230/page/3/index.html" new file mode 100644 index 000000000..65642d100 --- /dev/null +++ "b/categories/\345\210\267\351\242\230/page/3/index.html" @@ -0,0 +1 @@ +分类: 刷题 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/categories/\345\210\267\351\242\230/\347\254\224\350\257\225\350\257\225\345\215\267/index.html" "b/categories/\345\210\267\351\242\230/\347\254\224\350\257\225\350\257\225\345\215\267/index.html" new file mode 100644 index 000000000..992c62055 --- /dev/null +++ "b/categories/\345\210\267\351\242\230/\347\254\224\350\257\225\350\257\225\345\215\267/index.html" @@ -0,0 +1 @@ +分类: 笔试试卷 | 梦洁小站-属于你我的小天地
    分类 - 笔试试卷
    2022
    小红书2020校招前端笔试题卷一
    小红书2020校招前端笔试题卷一
    \ No newline at end of file diff --git "a/categories/\345\211\215\347\253\257/index.html" "b/categories/\345\211\215\347\253\257/index.html" new file mode 100644 index 000000000..95f063b1c --- /dev/null +++ "b/categories/\345\211\215\347\253\257/index.html" @@ -0,0 +1 @@ +分类: 前端 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/categories/\345\220\216\347\253\257/index.html" "b/categories/\345\220\216\347\253\257/index.html" new file mode 100644 index 000000000..f0d0595a1 --- /dev/null +++ "b/categories/\345\220\216\347\253\257/index.html" @@ -0,0 +1 @@ +分类: 后端 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/categories/\345\250\261\344\271\220/index.html" "b/categories/\345\250\261\344\271\220/index.html" new file mode 100644 index 000000000..fc454d653 --- /dev/null +++ "b/categories/\345\250\261\344\271\220/index.html" @@ -0,0 +1 @@ +分类: 娱乐 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/categories/\345\267\245\345\205\267/index.html" "b/categories/\345\267\245\345\205\267/index.html" new file mode 100644 index 000000000..296cce114 --- /dev/null +++ "b/categories/\345\267\245\345\205\267/index.html" @@ -0,0 +1 @@ +分类: 工具 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/categories/\345\267\245\345\205\267/page/2/index.html" "b/categories/\345\267\245\345\205\267/page/2/index.html" new file mode 100644 index 000000000..9b7e71b1b --- /dev/null +++ "b/categories/\345\267\245\345\205\267/page/2/index.html" @@ -0,0 +1 @@ +分类: 工具 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/categories/\345\267\245\345\205\267/\345\260\217\347\250\213\345\272\217/index.html" "b/categories/\345\267\245\345\205\267/\345\260\217\347\250\213\345\272\217/index.html" new file mode 100644 index 000000000..af0c00e2f --- /dev/null +++ "b/categories/\345\267\245\345\205\267/\345\260\217\347\250\213\345\272\217/index.html" @@ -0,0 +1 @@ +分类: 小程序 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/categories/\345\267\245\345\205\267/\346\277\200\346\264\273/index.html" "b/categories/\345\267\245\345\205\267/\346\277\200\346\264\273/index.html" new file mode 100644 index 000000000..a6cd58236 --- /dev/null +++ "b/categories/\345\267\245\345\205\267/\346\277\200\346\264\273/index.html" @@ -0,0 +1 @@ +分类: 激活 | 梦洁小站-属于你我的小天地
    分类 - 激活
    2024
    idea,webstorm等系列通用激活
    idea,webstorm等系列通用激活
    \ No newline at end of file diff --git "a/categories/\345\267\245\345\205\267\345\272\223/index.html" "b/categories/\345\267\245\345\205\267\345\272\223/index.html" new file mode 100644 index 000000000..6a3c0c3bc --- /dev/null +++ "b/categories/\345\267\245\345\205\267\345\272\223/index.html" @@ -0,0 +1 @@ +分类: 工具库 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/categories/\346\212\230\350\205\276/index.html" "b/categories/\346\212\230\350\205\276/index.html" new file mode 100644 index 000000000..e6f843e96 --- /dev/null +++ "b/categories/\346\212\230\350\205\276/index.html" @@ -0,0 +1 @@ +分类: 折腾 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/categories/\347\247\201\346\234\215/index.html" "b/categories/\347\247\201\346\234\215/index.html" new file mode 100644 index 000000000..6a7b4c8fa --- /dev/null +++ "b/categories/\347\247\201\346\234\215/index.html" @@ -0,0 +1 @@ +分类: 私服 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/categories/\347\263\273\347\273\237/index.html" "b/categories/\347\263\273\347\273\237/index.html" new file mode 100644 index 000000000..bbd8857a2 --- /dev/null +++ "b/categories/\347\263\273\347\273\237/index.html" @@ -0,0 +1 @@ +分类: 系统 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/categories/\351\232\217\350\256\260/index.html" "b/categories/\351\232\217\350\256\260/index.html" new file mode 100644 index 000000000..cb62af203 --- /dev/null +++ "b/categories/\351\232\217\350\256\260/index.html" @@ -0,0 +1 @@ +分类: 随记 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/categories/\351\235\242\350\257\225/HTML/index.html" "b/categories/\351\235\242\350\257\225/HTML/index.html" new file mode 100644 index 000000000..1322c07b8 --- /dev/null +++ "b/categories/\351\235\242\350\257\225/HTML/index.html" @@ -0,0 +1 @@ +分类: HTML | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/categories/\351\235\242\350\257\225/HTML/javscript/index.html" "b/categories/\351\235\242\350\257\225/HTML/javscript/index.html" new file mode 100644 index 000000000..58ead87f0 --- /dev/null +++ "b/categories/\351\235\242\350\257\225/HTML/javscript/index.html" @@ -0,0 +1 @@ +分类: javscript | 梦洁小站-属于你我的小天地
    分类 - javscript
    2022
    前端面试可能会问到的知识点记录
    前端面试可能会问到的知识点记录
    \ No newline at end of file diff --git "a/categories/\351\235\242\350\257\225/HTML/javscript/vue/index.html" "b/categories/\351\235\242\350\257\225/HTML/javscript/vue/index.html" new file mode 100644 index 000000000..540abf76e --- /dev/null +++ "b/categories/\351\235\242\350\257\225/HTML/javscript/vue/index.html" @@ -0,0 +1 @@ +分类: vue | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/categories/\351\235\242\350\257\225/index.html" "b/categories/\351\235\242\350\257\225/index.html" new file mode 100644 index 000000000..3ecca3505 --- /dev/null +++ "b/categories/\351\235\242\350\257\225/index.html" @@ -0,0 +1 @@ +分类: 面试 | 梦洁小站-属于你我的小天地
    分类 - 面试
    2022
    前端面试可能会问到的知识点记录
    前端面试可能会问到的知识点记录
    \ No newline at end of file diff --git a/cb27eea3.html b/cb27eea3.html new file mode 100644 index 000000000..37ee398d3 --- /dev/null +++ b/cb27eea3.html @@ -0,0 +1 @@ +移动端前端的适配和rem,vm其他的一些的复习 | 梦洁小站-属于你我的小天地

    移动端前端的适配和rem,vm其他的一些的复习

    前置总结

    • 1个css像素对应1个设备独立像素 对应n个物理像素
    • 设置一个div:height:100px;width:50px,那么height:100px(css像素) 对应 100个设备独立像素
      • 在dpr为2的屏幕下: height:100px(css像素) 对应 2 * 100 = 200个物理像素
      • drp为3的屏幕下: width:50px(css像素) 对应 50 * 3 = 150个物理像素

    物理像素及屏幕分辨率

    注意:

    屏幕分辨率是一个固定的值,屏幕生产出来就确定了,无法修改!

    我们在电脑所设置的,都是显示分辨率! 显示分辨率和屏幕分辨率是二个概念!

    重要前提: 物理像素不等于屏幕分辨率,物理像素和独立像素不可改变

    我们电脑屏幕,他叫物理像素(也有的叫设备像素),物理像素不可以改变,我们电脑改变的叫分辨率,而不是物理像素.

    屏幕密度

    每英寸里包含的物理像素点的个数,单位是PPI(pixels per inch),还有另外一个是dpi(dots per inch) ,dpi是衡量打印机的,ppi是衡量屏幕的

    计算公式:

    计算公式

    就是一个勾股定理

    像素相关

    • 物理像素

      • 又称:设备像素,物理像素是一个长度单位,单位是px,1个物理像素就是屏幕上的一个物理成像点,就是屏幕中一个微小的发光物理元器件(可简单理解为超级微小的灯泡),是屏幕能显示的最小粒度。它由屏幕制造商决定,屏幕生产后无法修改。例如 iPhone 6 的横向上拥有的物理像素为750、纵向上拥有的物理像素为1334 ,我们也可以用:750* 1334表示。
    • css像素

      • 又称: 逻辑像素,css像素是一个抽象的长度单位,单位也是px,它是为 Web 开发者创造的,用来精确的度量Web 页面上的内容大小。我们在编写css、js、less中所使用的都是css像素(可以理解为:“程序员像素”);
      • 思考:我代码中所写的1px(css像素),到了屏幕上到底对应几个物理像素呢?是1个css像素就对应1个物理像素(“发光的灯泡”)吗?要探讨这个对应关系,就要学习接下来的新概念:设备独立像素。
    • 设备独立像素

      • 程序员写了:width = 2px,height = 2px 的盒子,若1个css像素直接对应1个物理像素,由于iPhone3G/S 与iPhone4屏幕尺寸相同,但iPhone4的屏幕能容纳下更多的物理像素点,所以iPhone4的物理像素点比iPhone3G/S小很多,那么理论上这个盒子在iPhone4屏幕上也就会比iPhone3G/S屏幕上小很多,而事实是iPhone3G/S 和 iPhone4下这个盒子是一样大的!!!,只不过 iPhone4更加细腻、清晰。如何做到的呢?这就要靠设备独立像素。
      • 设备独立像素的出现,使得即使在【高清屏】下,(例如苹果的Retina屏),也可以让元素有正常的尺寸,让代码不受到设备的影响,它是设备厂商根据屏幕特性设置的,无法更改。

    设备独立像素与物理像素的关系

    • 普通屏幕下1个设备独立像素对应1个物理像素(1:1关系)
    • 高清屏幕下1个设备独立像素对应N个物理像素(1:n关系)

    设备独立像素与css像素关系

    • 在无缩放的情况下(标准情况): 1css像素 = 1设备独立像素

      • 在dpr为2的开启下,设置css像素为width:100px;height:100px.那么对应的设备独立像素就是width:100px;height;100px,对应的物理像素就是width:200px;height:200px
    • css像素和设备独立像素永远是1:1的关闭(而css像素和物理像素-也就是我们常说的分辨率,就是1:n的关系了,n为像素比dpr)

    • 可以简单理解为css像素不可以直接显示在屏幕下方,而是会通过dpr转换后进行显示,转换过程中的中间量就是设备独立像素

    像素比

    像素比(dpr):单一方向上设备的物理像素和设备独立像素的比例

    即dpr = 物理像素 / 设备独立像素

    1
    2
    js获取当前屏幕像素比
    window.devicePixelRadio

    几款手机的屏幕像素参数,点击这里查看更多

    型号分辨率(物理像素点总和)设备独立像素(dip或dp)像素比(dpr)
    IPhone 3GS320 * 480320 * 4801
    IPhone 4 / 4s640 * 960320 * 4802
    IPhone 5 / 5s640 * 1136320 * 5682
    IPhone 6 / 7 / 8750 * 1334375 * 6672
    IPhone 6p / 7p / 8p1242 x 2208414 * 7363
    IPhone X1125 * 2436375 * 8123
    HUAWEI P101080 x 1920360 x 6403

    图形的高清显示

    概念:

    由于css像素和像素比(dpr)的存在,可能会导致图片在不同(像素比)下显示模糊之类的

    • 比如说一个图片设置css像素为200px * 200px那么在dpr为1的情况下占据屏幕分辨率为200px * 200px(物理像素)
      • 刚刚好一个css像素对应一个屏幕的分辨率
    • 如果在dpr为2的情况下,占据的屏幕分辨率就是400px * 400px(物理像素)
      • 就会导致一个css像素占据了二个物理像素,所以屏幕要怎么处理?肯定是平均下这一个css像素的颜色,使其分散在2个格子上面,不然另外一个格子没有内容啊(没有填充内容肯定不行啊!所以肯定要平均下!)所以这就导致了图片的模糊
    • 解决办法很简单,就是将图片的像素提示,dpr为2的使用2x图,dpr为3的使用3x图等等

    结合-webkit-min-device-pixel-ratiomedia媒体查询来使用替换图片多用于品牌LOGO

    示例:

    1
    2
    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
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>Document</title>
    <style>
    #one {
    width: 200px;
    height: 200px;
    }

    /* 媒体查询 */
    @media screen and (-webkit-min-device-pixel-ratio:2) {

    /* dpr最小值为2,也就是>=2的时候,使用2x图 */
    #one {
    content: url("img/logo@2x.png")
    }
    }

    @media screen and (-webkit-min-device-pixel-ratio:3) {
    #one {
    content: url("img/logo@3x.png")
    }
    }
    </style>
    </head>

    <body>
    <img id="one" src="./img/logo.png" alt="">
    </body>

    </html>

    随着dpr的改变,图片也会被替换,可以明显感觉到图片越来越清晰

    视口相关

    pc端视口

    在pc端,视口的默认宽度和浏览器窗口的宽度一致。在 css 标准文档中,pc端视口也被称为:初始包含块,它是所有 css 百分比宽度推算的根源,在pc端可通过如下几种方式获取宽度:

    1
    2
    3
    4
    console.log('最干净的显示区域',document.documentElement.clientWidth);//常用
    console.log('最干净的显示区域+滚动条',window.innerWidth);
    console.log('最干净的显示区域+滚动条+浏览器边框',window.outerWidth);
    console.log('与浏览器无关,当前设备显示分辨率横向的值',screen.width);

    移动端视口

    ​ 在移动端,浏览器厂商面临着一个比较大的问题,他们如何将数以万计甚至可以说是数以亿计的pc端网页完整的呈现在移动端设备上,并且不会出现横向滚动条呢?那就要引出移动端的三个概念:1.布局视口、2.视觉视口、3. 理想视口

    1.布局视口

    ​ 早期的时候我们这样做:pc端网页宽度一般都为:960px ~ 1024px 这个范围,就算超出了该范围,960px ~ 1024px这个区域也依然是版心的位置,浏览器厂商针对移动端设备设计了一个容器,先用这个容器去承装pc端的网页,这容器的宽度一般是**980px,不同的设备可能有所差异,但相差并不大;随后将这个容器等比例压缩**到与手机等宽,这样就可以保证没有滚动条且能完整呈现页面,但是这样做依然有问题:网页内容被压缩的太小,严重影响用户体验。

    移动端获取布局视口方式:document.documentElement.clientWidth (一般布局视口为980px)

    • 说简单就是早期我们手机显示pc端的网页是将pc端的网页等比压缩成为手机端的980px,然后将这个980px渲染到手机端
      • 实际上是对网页进行了整体的缩小
      • 例如200宽的盒子占据了980份的200份200/980
      • 若缩小后文字的小于12px,线条小于1px,将被分别显示为12px和1px

    见下图,在物理分辨率是750px上面显示pc端的内容,会将内容等比压缩

    • 图片中98px* 98px的绿色方框实际显示为75px*75px
    • 13px和12px的文字显示为12px,16px文字进行了相应的缩小;1px的线条还是显示为1px

    图

    • 所以为什么我们在不设置meta的参数的情况下想要将容器占满手机端,我们就需要设置width为980px才可以占满

    所以为了让所有的网站显示正常,各移动浏览器厂商统一将设备默认布局视口设置为 980px。

    比如在宽 375px 的 iphone6 上显示一个宽为 980px 的页面,大多数浏览器为了让页面显示全而缩小页面。

    我们可以使用meta viewport让浏览器布局视区等于屏幕宽度也就是375px,这样显示出来就是理想效果。

    1
    <meta name="viewport" content="width=device-width"/>

    2.视觉视口

    视觉视口就是用户可见的区域,它的绝对宽度永远和设备屏幕一样宽,但是这个宽度里所包含的css像素值是变化的,例如:一般手机会将980个css像素放入视觉视口中,而ipad Pro会将1024个css像素放入视觉视口中。

    ​ 移动端获取视觉视口方式:window.innerWidth,不过在Android2、Opera mini 、UC8 中无法正确获取。(一般不通过代码看视觉视口)

    理想视口标准

    屏幕(设备独立像素)等宽的布局视口,称之为理想视口,所以也可以说理想视口是一种标准:让布局视口宽度 与 屏幕等宽(设备独立像素),靠meta标签实现。

    理想视口的特点:

    • 布局视口和屏幕等宽,以iPhone6为例,符合理想视口标准之后:设备独立像素:375px,布局视口宽度:375px。
    • 用户不需要缩放、滚动就能看到网站的全部内容。
    • 要为移动端设备单独设计一个移动端网站。

    设置理想视口的具体方法:

    1
    2
    3
    <meta name="viewport" content="width=device-width"/>
    也可以
    <meta name="viewport" content="width=device-width,initial-scale=1.0">

    总结

    不写meta标签(不符合理想视口标准):

    1. 描述屏幕:物理像素:750px 、设备独立像素:375px、css像素:980px。
    2. 优点:元素在不同设备上,呈现效果几乎一样,因都是通过布局容器等比缩放的,例如200宽的盒子:200/980(占据了980份的200份)
    3. 缺点:元素太小,页面文字不清楚,用户体验不好。

    meta标签(符合理想视口标准):

    1. 描述屏幕:物理像素:750px 、设备独立像素:375px、css像素:375px

    2. 优点:

      1. 页面清晰展现,内容不再小到难以观察,用户体验较好。

      2. 更清晰的像素关系:布局视口 = 视觉视口 = 设备独立像素 = 375px。

      3. 更清晰的dpr,即dpr = 物理像素/设备独立像素 = 物理像素/css像素。

        例如:dpr=2的设备,1 * 1 css像素 = 1 * 1 设备独立像素 = 2 * 2 物理像素

    3. 缺点:同一个元素,在不同屏幕(设备)上,呈现效果不一样,例如375宽的盒子: 375/375 和 375/414 (不是等比显示)

    4. 解决缺点:做适配。

    布局视口,视觉视口,理想视口总结和区别

    • 布局视口
      • 将一个网页按照比例压缩为980px,用户可以看到这个网页的全部
    • 视觉视口
      • 对网页不进行压缩,用户可以看到网页的部分内容,但是可以滚动条来滑动

    • 理想视口
      • 布局视口 = 视觉视口,当前用的最多的

    缩放和放大

    PC端的

    放大的时候

    • 视口变小(可以理解为在聚焦,所以视口变小了)
    • 元素的css像素值不变,但是一个css像素所占面积变大了

    缩小的时候

    • 视口变大
    • 元素的css像素值不变,但一个css像素值所占的面积变小了

    移动端

    放大的时候

    • 布局视口不变
    • 视觉视口变小(视觉视口就是用户可见的区域)

    缩小的时候

    • 布局视口不变
    • 视觉视口变大(视觉视口就是用户可见的区域)

    注意:移动端缩放不会影响页面布局,因为缩放的时候,布局视口视口没有变化,简记:移动端的缩放没有改变布局视口的任何东西!

    说简单点就是因为移动端并不改变布局视口,而pc端会改变布局视口

    1
    2
    3
    4
    5
    移动端或者布局视口和获取pc端视口
    document.documentElement.clientWidth

    移动端获取视觉视口
    window.innerWidth

    总结缩放放大

    pc端页面的缩放和放大视口的变化情况

    pc端页面的缩放和放大视口的变化情况

    手机端的不好录,具体你们可以自己体验下~

    viewport

    meta-viewport 标签是苹果公司在 2007 年引进的,用于移动端布局视口的控制。

    使用示例:

    1
    <meta name="viewport" content="width=device-width,initial-scale=1.0">

    viewport 相关选项

    1. width 布局视口的宽度
    2. initial-scale 【系统】初始缩放比例
    3. maximum-scale 允许【用户】缩放的最大比例
    4. minimum-scale 允许【用户】缩放的最小比例
    5. user-scalable 是否允许用户缩放
    6. viewport-fit 设置为cover值可以解决刘海屏的留白问题

    1.width

    width值可以是设备宽度标识 device-width,也可以是具体值,但有些安卓手机是不支持具体值,IOS全系列都支持。

    2.initial-scale

    1. initial-scale 为页面初始化时的显示比例。

    2. initial-scale = 屏幕宽度(设备独立像素) / 布局视口宽度。

      1. 但是设备独立像素是固定的,并且initial-scale也被修改为了一个固定值,所以可以变化的,就是布局视口宽度了~
    3. 只写initial-scale = 1.0 也可以实现完美视口,但为了良好的兼容性,width=device-width, initial-scale=1.0一般一起写。

    3.maximum-scale

    1. 设置允许用户最大缩放比例,苹果浏览器 safari 不认识该属性

    2. maximum-scale = 屏幕宽度(设备独立像素) / 视觉视口宽度值

    4.minimum-scale

    1. 设置允许用户最小缩放比例。

    2. minimum-scale = 屏幕宽度(设备独立像素) / 视觉视口宽度值

    5.user-scalable

    ​ 是否允许用户通过手指缩放页面。苹果浏览器 safari 不认识该属性

    6.viewport-fit

    设置为 cover 可以解决『刘海屏』的留白问题

    解决『刘海屏』的留白问题

    适配

    1.viewport适配

    • 方法:拿到设计稿之后,设置布局视口宽度为设计稿宽度,然后直接按照设计稿给宽高进行布局即可。
    • 优点:不用复杂的计算,直接使用图稿上标注的px值
    • 缺点:
      • 不能使用完整的meta标签,会导致在某些安卓手机上有兼容性问题。
      • 不希望适配的东西,例如边框,也强制参与了适配
      • 图片会失真

    相当于设置

    1
    2
    3
    4
    <meta name="viewport" content="width="设计稿要求的宽度" />
    //比如设计稿是在375px下设计的,那么就将width="375"
    <meta name="viewport" content="width="375" />

    2.rem适配

    em和rem

    em 和 rem 都是 css 中的长度单位。而且两个都是相对长度单位,不过两个有点区别

    • em 相对的是父级元素的字体大小
    • rem 相对的是根元素的字体大小
      • 比如html,body

    rem适配的原理:编写样式时统一使用rem为单位,在不同设备上动态调整根字体大小

    方案1-动态改变根字体大小

    淘宝、百度的移动端页面用的此方案

    1. 设置完美视口
    2. 通过js设置根字体大小 = **( 当前设备横向独立像素值 100) / 设计稿宽度*
    3. 编写样式时,直接以rem为单位,值为:设计值 / 100
    4. 增加 JS 代码进行实时适配

    优势:编写样式时直接挪动小数点即可。

    例子:

    比如有一个设计稿是,可以看到是在iponhe6(**设备独立像素宽度为375px)**的条件下设计的

    设计稿

    那么如果我们需要使得这张设计稿设计的345px * 150px的div容器在iPhone6-plus上(设备独立像素宽度为414px)显示正常要怎么做呢?(肯定是按照比例缩放对吧)

    经过人们发现,就会发现设备物理像素宽度大小和根字体的大小会成比例(当然你也可以参照别的,这里用根字体是因为有一个单位rem直接参照了根元素)

    发现的比例关系就是这样(当然,字体大小可以随意设置,都取100只是为了方便计算!)

    image-20220523171544365

    求出x(也就是在iPhone6-plus的根字体大小应该设计为110.4比例才正确)

    image-20220523171901209

    所以这里换算出来的关系就是这样子

    计算结果

    375px设备分辨率下的345px在其他设备的适配代码

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
    * {
    margin: 0;
    padding: 0;
    }

    html,
    body {
    font-size: 100px;
    }

    #one {
    background-color: hotpink;
    /* width: 345px; */
    /* height: 150px; */
    /* margin: 15px; */

    /* 345/100 = 3.45 */
    width: 3.45rem;
    /* 150/100 = 1.5 */
    height: 1.5rem;
    /* 15/100 = 0.15 */
    margin: 0.15rem;
    margin-bottom: 0;
    }
    </style>
    </head>

    <body>
    <div id="one"></div>
    <script>
    //初次调入
    changeFontSizeHTML();
    //窗口发生改变再次调用
    window.onresize = changeFontSizeHTML;
    function changeFontSizeHTML() {
    //获取设备独立像素(因为设置了width=device-width),所以布局视口会等于独立像素
    var buju = document.documentElement.clientWidth;
    //计算字体大小
    var fontSizeHTML = (buju * 100) / 375;
    //设置字体大小
    document.documentElement.style.fontSize = fontSizeHTML + "px";
    }
    </script>
    </body>

    </html>

    效果:

    这样子在不同设备下的根字体大小就不同,显示的效果也是一样的

    方案2

    搜狐、唯品会的移动端页面用的此方案

    1. 设置完美视口
    2. 通过js设置根字体大小 = 当前设备横向独立像素值 / 10
    3. 编写样式时,直接以rem为单位,值为:设计值 / (设计稿宽度 / 10)
    4. 增加 JS 代码进行实时适配
    • 搜狐,唯品会就是发现这样一个规律

    要求的根字体大小

    • 计算出来的根字体大小为 41.4,会发现根字体的大小恰巧是当前设备横向独立像素值 / 10

    计算出的根字体大小

    所以换算出来的关系如图

    方案2换算关系

    可以看到,我们算根字体的大小到底是多少十分简单,但是换算rem的时候就会十分麻烦

    示例代码

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
    * {
    margin: 0;
    padding: 0;
    }

    html,
    body {
    font-size: 100px;
    }

    #one {
    background-color: hotpink;
    /* width: 345px; */
    /* height: 150px; */
    /* margin: 15px; */
    /* 345 / 37.5 = 9.2 */
    width: 9.2rem;
    /* 150 / 37.5 = 4 */
    height: 4rem;
    /* 15 / 37.5 = 0.4 */
    margin: 0.4rem;
    margin-bottom: 0;
    }
    </style>
    </head>

    <body>
    <div id="one"></div>
    <script>
    //初次调入
    changeFontSizeHTML();
    //窗口发生改变再次调用
    window.onresize = changeFontSizeHTML;
    function changeFontSizeHTML() {
    //获取设备独立像素(因为设置了width=device-width),所以布局视口会等于独立像素
    var buju = document.documentElement.clientWidth;
    //计算字体大小
    var fontSizeHTML = buju / 10;
    //设置字体大小
    document.documentElement.style.fontSize = fontSizeHTML + "px";
    }
    </script>
    </body>

    </html>

    唯品会官网使用

    less写法

    html内容

    1
    2
    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
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- <link rel="stylesheet" href="./css/index.css"> -->
    <link rel="stylesheet" href="./css/index.css">
    </head>

    <body>
    <div id="one"></div>
    <script>
    //初次调入
    changeFontSizeHTML();
    //窗口发生改变再次调用
    window.onresize = changeFontSizeHTML;
    function changeFontSizeHTML() {
    //获取设备独立像素(因为设置了width=device-width),所以布局视口会等于独立像素
    var buju = document.documentElement.clientWidth;
    //计算字体大小
    var fontSizeHTML = buju / 10;
    //设置字体大小
    document.documentElement.style.fontSize = fontSizeHTML + "px";
    }
    </script>
    </body>

    </html>

    less内容(注意,注意加上括号!)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // 定义变量
    @fontSize:375/10rem;

    * {
    margin: 0;
    padding: 0;
    }

    #one {
    background-color: hotpink;
    width:(345/@fontSize);
    height: (150/@fontSize);
    margin: (15/@fontSize);
    margin-bottom: 0;
    }

    3.vw适配

    • 有人就会想,既然我是找比例关系,那么肯定会有这样子关系

    • 设计图上的屏幕宽度是375px,设计图上一个div的宽度为345px,那么345px占了375px的0.92(92%),那么我到别的屏幕宽度去,我就想办法让这个div也占当前屏幕宽度的92%就可以完成适配了对吧,

    占比关系

    所以vw ,vh就出来了

    • 1vw(视图宽度) = 等于布局视口宽度的1%
    • 1vh(视图高度) = 等于布局视口高度的1%

    不过vw和vh有一定的兼容性问题:详见:这里

    所以我们可以代码大概可以怎么写?

    • 按照当前设计图给出的屏幕宽度(布局视口宽度)算出每一个容器所占据的百分比后用vw来替换(注意,是vw!vw!vw!)
    • 为什么要强调是vw呢? 因为假如你使用vh,1vh是布局视口高度的1%,可是!不管是哪一个网页,屏幕的高度都不是固定的吧?只有宽度是固定的

    普通写法代码实例

    1
    2
    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
    35
    36
    37
    38
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
    * {
    margin: 0;
    padding: 0;
    }

    #one {
    background-color: hotpink;
    /* width: 345px;
    height: 150px;
    margin: 15px; */

    /* 345/375 *100 = 92 */
    width: 92vw;

    /* 150/375 *100 = 40 */
    height: 40vw;

    /* 15/375 *100 = 4 */
    margin: 4vw;
    margin-bottom: 0;
    }
    </style>
    </head>

    <body>
    <div id="one"></div>
    </body>

    </html>

    less的写法

    html内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <!DOCTYPE html>
    <html lang="en">

    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <link rel="stylesheet" href="./css/index.css">
    </head>

    <body>
    <div id="one"></div>
    </body>

    </html>

    less内容(依旧注意括号问题)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    @vwSize: 375/100vw;
    * {
    margin: 0;
    padding: 0;
    }

    #one {
    background-color: hotpink;
    /* width: 345px;
    height: 150px;
    margin: 15px; */
    width:(345/@vwSize);
    height: (150/@vwSize);
    margin: (15/@vwSize);
    margin-bottom: 0;
    }

    4.1物理像素边框

    在dpr为2或者3以上的情况下,1css像素对应2个或者3个物理像素(说通俗点就是高dpr下,1px的css像素所显示的效果并没是我们想象的1px效果)

    默认情况下的1pxcss像素的显示效果

    默认情况下的1px css像素的显示效果

    放大后的1px css像素的显示效果,可以明显看到边框肯定不是1px

    放大后的1px css像素的显示效果

    设置物理像素边框适配后的效果,可以看到,即使在搞dpr下,边框也依旧为1px

    设置物理像素边框适配后的效果

    代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //关键代码就是媒体查询
    //dpr为2的情况下,就将border1px设置为0.5px
    //这样子0.5px的css像素 只能点亮1px物理像素(0.5*2)
    @media screen and (-webkit-min-device-pixel-ratio:2) {
    #app{
    border: 0.5px solid black;
    }
    }

    //dpr为3的情况下,就将border1px设置为0.33px
    //这样子0.33px的css像素 只能点亮1px物理像素(0.33*3)
    @media screen and (-webkit-min-device-pixel-ratio:3) {
    #app{
    border: 0.33px solid black;
    }
    }

    移动端事件

    事件类型

    移动端事件列表

    • touchstart 元素上触摸开始时触发
    • touchmove 元素上触摸移动的时候触发
    • touchend 手指从触摸元素上离开时候触发
    • touchcancel 触摸被打断时触发
      • 比如说来了一个电话或者网页有一个弹窗
    • 每一个事件都会传入一个默认的参数,也就是事件回调对象

    这几个事件最早出现于IOS safari中,为了向开发人员转达一些特殊的信息。

    事件回调

    如图,输出事件回调对象

    事件回调对象

    我们使用的比较多的图片圈圈出来了

    1.touches(伪数组)

    屏幕上的触点数(屏幕上总共的手指头,当然,也不一定要手指头)

    2.targetToches(伪数组)

    被触摸元素的触点数(就是有多少个手指头在这个元素上,当然,也不一定要手指头)

    3.changedTouches(伪数组)

    屏幕上改变的触点数(就是手指头变化的数量),当然,也可以应用于屏幕上同时按下几个手指头的判断

    比如一封婚礼请帖,需要嘴唇亲吻才可以进入下一步领取红包,就可以使用changedTouches来判断

    应用场景

    • touchstart 事件可用于元素触摸的交互,比如页面跳转,标签页切换

    • touchmove 事件可用于页面的滑动特效,网页游戏,画板

    • touchend 事件主要跟 touchmove 事件结合使用

    • touchcancel 使用率不高

    注意:

    • touchmove 事件触发后,即使手指离开了元素,touchmove 事件也会持续触发

    如图,手指头移出了粉红区域后依旧触发了touchmove事件

    手指头移出了粉红区域后依旧触发了touchmove事件

    • 触发 touchmove 与 touchend 事件,一定要先触发 touchstart
    • 事件的作用在于实现移动端的界面交互

    点击穿透

            touch 事件结束后会默认触发元素的 click 事件,如没有设置完美视口,则事件触发的时间间隔为 300ms 左右,如设置完美视口则时间间隔为 30ms 左右(备注:具体的时间也看设备的特性)。

    • 点击穿透条件
      • 被touch的元素,触发了事件后将自己隐藏了(比如display:none)
      • 被touch的元素下面有click事件
    • 想更详细的可以看看这位博主写的

    ​ 也可以这样子说:如果 touch 事件隐藏了元素,则 click 动作将作用到新的元素上,触发新元素的 click 事件或页面跳转,此现象称为点击穿透

    触发蒙层下的touchstart方法,蒙层消失,触发该元素位置下的click事件。

    代码示例:(单击关闭后会穿透到click)

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>点击穿透</title>
    <style>
    *{
    margin: 0;
    padding: 0;
    }
    html,body{
    width: 100%;
    height: 100%;
    }
    #app{
    position: relative;
    width: 100%;
    height: 100%;
    background-color: skyblue;
    }
    .banner{
    display: block;
    background-color: orange;
    width: 100%;
    height: 200px;
    }
    .shade{
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.5);
    text-align: center;
    }
    h1{
    margin-top: 100px;
    color:white;
    }
    button{
    width: 100px;
    height: 50px;
    font-size: 20px;
    }
    </style>
    </head>
    <body>
    <div id="app">
    <a class="banner" href="https://www.baidu.com">点我去百度</a>
    <div class="shade">
    <h1>恭喜一等奖</h1>
    <button id="btn">关闭</button>
    </div>
    </div>
    <script type="text/javascript" >
    const shade = document.querySelector('.shade')
    const btn = document.getElementById('btn')
    btn.addEventListener('touchstart',()=>{
    shade.style.display = 'none'
    })
    </script>
    </body>
    </html>
    解决方法一

    阻止默认行为

    1
    2
    3
    4
    5
    //阻止默认行为
    node.addEventListener('touchstart', function(e){
    console.log('hello')
    e.preventDefault();
    })
    解决方法二

    使背后元素不具备click特性,用touchXxxx代替click

    1
    2
    3
    banner_img.addEventListener('touchstart',()=>{
    location.href = 'http://www.baidu.com'
    })
    解决方法三

    让背后的元素暂时失去click事件,300毫秒左右再复原

    1
    2
    3
    #anode{
    pointer-events: none;
    }
    1
    2
    3
    4
    5
    6
    btn.addEventListener('touchstart',(event)=>{
    shade.style.display = 'none'
    setTimeout(()=>{
    anode.style.pointerEvents = 'auto'
    },500)
    })
    解决方法四

    让隐藏的元素延迟300毫秒左右再隐藏

    1
    2
    3
    4
    5
    btn.addEventListener('touchstart',(event)=>{
    setTimeout(()=>{
    shade.style.display = 'none'
    },300)
    })
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/cb27eea3.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    avatar
    梦洁
    小小的字,有大大的梦想~分享我的前端学习过程,经历,错误,和一些其他折腾过程
    关注下我(* ̄▽ ̄*)
    公告
    不断更新中,有问题请留言回复(会通过邮箱提醒~)
    \ No newline at end of file diff --git a/cb63a8.html b/cb63a8.html new file mode 100644 index 000000000..49b04558b --- /dev/null +++ b/cb63a8.html @@ -0,0 +1,10 @@ +今日刷题-数组的splice和map方法 | 梦洁小站-属于你我的小天地

    今日刷题-数组的splice和map方法

    题目1

    1
    2
    3
    4
    5
    以下哪些Array对象的方法不会更改原有数组?(多选)
    A: concat()
    B: splice()
    C: map()
    D: sort()
    • 答案

      • A,C
    • 解析

      • A: 用于连接字符串的操作,返回一个连接后的字符串

      • B: splice用于对数组进行增,删,改,会对原数组进行修改,返回值为增加,删除,修改后的数组(可以理解为修剪后产生的屑)

      • C: map函数遍历数组每一项,并将其返回值作为新值传递给新数组

        • 比如 [1,2,3].map(item,index,array=>{return item * 2 }); //返回[2,4,6]
      • D: 对数组进行排序

        • array.sort( ( a , b ) => { return a - b } );// 升序
        • array.sort ( ( a , b ) ) => { return b - a } ;// 降序
      • 会改变数组的方法:                            不会改变数组的方法:
        +push()									   filter
        +pop()									   concat
        +shift()									   slice
        +unshift()								   map
        +splice()
        +sort()
        +reverse()
        +forEach()
        +
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/cb63a8.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/cc4cf5d9.html b/cc4cf5d9.html new file mode 100644 index 000000000..117e7a825 --- /dev/null +++ b/cc4cf5d9.html @@ -0,0 +1 @@ +右键新建txt,新建文本文件不见了,通过添加注册表就可以解决,找来找去办法解决不了的终极办法 | 梦洁小站-属于你我的小天地

    右键新建txt,新建文本文件不见了,通过添加注册表就可以解决,找来找去办法解决不了的终极办法

    有时候右键txt文本文件不见了

    • 如下图示例,你什么都没有看到

    解决办法

    1.下载文件或复制文件内容

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    Windows Registry Editor Version 5.00

    ;【新建文本文档】
    [HKEY_CLASSES_ROOT\.txt]
    @="txtfilelegacy"
    "Content Type"="text/plain"
    "PerceivedType"="text"
    [HKEY_CLASSES_ROOT\.txt\PersistentHandler]
    @="{5e941d80-bf96-11cd-b579-08002b30bfeb}"
    [HKEY_CLASSES_ROOT\.txt\ShellNew]
    "ItemName"=hex(2):40,00,25,00,53,00,79,00,73,00,74,00,65,00,6d,00,52,00,6f,00,\
    6f,00,74,00,25,00,5c,00,73,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,\
    00,6e,00,6f,00,74,00,65,00,70,00,61,00,64,00,2e,00,65,00,78,00,65,00,2c,00,\
    2d,00,34,00,37,00,30,00,00,00
    "NullFile"=""
    [HKEY_CLASSES_ROOT\txtfilelegacy]
    @="Text Document"
    "EditFlags"=dword:00210000
    "FriendlyTypeName"=hex(2):40,00,25,00,53,00,79,00,73,00,74,00,65,00,6d,00,52,\
    00,6f,00,6f,00,74,00,25,00,5c,00,73,00,79,00,73,00,74,00,65,00,6d,00,33,00,\
    32,00,5c,00,6e,00,6f,00,74,00,65,00,70,00,61,00,64,00,2e,00,65,00,78,00,65,\
    00,2c,00,2d,00,34,00,36,00,39,00,00,00
    [HKEY_CLASSES_ROOT\txtfilelegacy\DefaultIcon]
    @=hex(2):25,00,53,00,79,00,73,00,74,00,65,00,6d,00,52,00,6f,00,6f,00,74,00,25,\
    00,5c,00,73,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,69,00,6d,00,\
    61,00,67,00,65,00,72,00,65,00,73,00,2e,00,64,00,6c,00,6c,00,2c,00,2d,00,31,\
    00,30,00,32,00,00,00
    [HKEY_CLASSES_ROOT\txtfilelegacy\shell]
    [HKEY_CLASSES_ROOT\txtfilelegacy\shell\open]
    [HKEY_CLASSES_ROOT\txtfilelegacy\shell\open\command]
    @=hex(2):25,00,53,00,79,00,73,00,74,00,65,00,6d,00,52,00,6f,00,6f,00,74,00,25,\
    00,5c,00,73,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,4e,00,4f,00,\
    54,00,45,00,50,00,41,00,44,00,2e,00,45,00,58,00,45,00,20,00,25,00,31,00,00,\
    00
    [HKEY_CLASSES_ROOT\txtfilelegacy\shell\print]
    [HKEY_CLASSES_ROOT\txtfilelegacy\shell\print\command]
    @=hex(2):25,00,53,00,79,00,73,00,74,00,65,00,6d,00,52,00,6f,00,6f,00,74,00,25,\
    00,5c,00,73,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,4e,00,4f,00,\
    54,00,45,00,50,00,41,00,44,00,2e,00,45,00,58,00,45,00,20,00,2f,00,70,00,20,\
    00,25,00,31,00,00,00
    [HKEY_CLASSES_ROOT\txtfilelegacy\shell\printto]
    [HKEY_CLASSES_ROOT\txtfilelegacy\shell\printto\command]
    @=hex(2):25,00,53,00,79,00,73,00,74,00,65,00,6d,00,52,00,6f,00,6f,00,74,00,25,\
    00,5c,00,73,00,79,00,73,00,74,00,65,00,6d,00,33,00,32,00,5c,00,6e,00,6f,00,\
    74,00,65,00,70,00,61,00,64,00,2e,00,65,00,78,00,65,00,20,00,2f,00,70,00,74,\
    00,20,00,22,00,25,00,31,00,22,00,20,00,22,00,25,00,32,00,22,00,20,00,22,00,\
    25,00,33,00,22,00,20,00,22,00,25,00,34,00,22,00,00,00
    ;【用记事本打开】
    [HKEY_CLASSES_ROOT\*\shell\Notepad]
    @="用记事本打开"
    [HKEY_CLASSES_ROOT\*\shell\Notepad\Command]
    @="notepad %1"
    • 如果是新建的,记得保存文件

    2.双击解决右键新建txt消失问题注册表.reg或者tool.reg

    3.右键刷新下就可以看到了

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/cc4cf5d9.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/cd8d332b.html b/cd8d332b.html new file mode 100644 index 000000000..dd76754ab --- /dev/null +++ b/cd8d332b.html @@ -0,0 +1 @@ +vue一些比较重要知识点的复习 | 梦洁小站-属于你我的小天地

    vue一些比较重要知识点的复习

    Vue的MVVM模型

    知道什么是MVVM

    • M: (Model 模型) 即后端传递过来或者自己定义的数据(对应vue组件当中的data,props,computed等)
    • V: (View 视图) 即用户看到的界面UI (也就是我们组件当中的template部分)
    • VM: ViewModel,负责实现View和Model之间数据状态同步的中间对象

    MVVM的关系

    MVVM关系

    MVVM优点

    在MVVM架构下,View 和 Model 之间并没有直接的联系,而是通过 ViewModel 进行交互,
    Model 和 ViewModel 之间的交互是双向的,
    因此 View 数据的变化会同步到 Model 中,而 Model 数据的变化也会立即反应到View 上。
    ViewModel 通过双向数据绑定把 View 和 Model 连接了起来,而 View 和 Model 之间的同步工作完全是自动的,无需人为干涉,因此开发者只需关注业务逻辑,不需要手动操作DOM, 不需要关注数据状态的同步问题,复杂的数据状态维护完全由 MVVM 来统一管理。

    假如面试问到我说说看MVVM,我可能会这样子回答

    • 我理解的MVVM就是M是model,也就是我们通过ajax请求获取到的数据或者是自己定义的数据
    • V:也就是用户在浏览器所看到的视图
    • VM:vue的实例化对象,我们通过vm完成model和view的数据状态同步,从而实现数据变动,视图也变动

    Vue的绑定键盘鼠标操作和在组件使用上的要点

    Vue的事件操作(v-on:click=”xxx”或@click)

    • 基本使用:
      • <div @click="事件"></div>
      • 或者<div @click.xxxx="事件"></div>
      • 或者<div v-on.click="事件"></div>
      • 或者<div v-on.click.xxx="事件"></div>
    1
    2
    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
    //除了@click="鼠标单击事件外"
    //还有@click.xxx="经过修饰符指定的类型的事件"
    .stop //停止事件冒泡(等同于event.stopPropagination())
    .prevent //阻止默认行为(比如单击一个按钮莫名其妙提交了表单,等用于event.preventDefault())
    .capture
    .self
    .once //只执行一次
    .passive

    <!-- 阻止单击事件继续传播 -->
    <a v-on:click.stop="doThis"></a>

    <!-- 提交事件不再重载页面 -->
    <form v-on:submit.prevent="onSubmit"></form>

    <!-- 修饰符可以串联 -->
    <a v-on:click.stop.prevent="doThat"></a>

    <!-- 只有修饰符 -->
    <form v-on:submit.prevent></form>

    <!-- 添加事件监听器时使用事件捕获模式 -->
    <!-- 即内部元素触发的事件先在此处理,然后才交由内部元素进行处理 -->
    <div v-on:click.capture="doThis">...</div>

    <!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
    <!-- 即事件不是从内部元素触发的 -->
    <div v-on:click.self="doThat">...</div>

    Vue的按键操作的别名(这样子就不用通过判断键代码来获取用户是哪一个操作了)

    • 基本使用: <div @keyup.xxxx="事件"></div> 或者<div v-on.keyup.xxx="事件"></div>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    .enter
    .tab
    .delete (捕获“删除”和“退格”键)
    .esc
    .space
    .up
    .down
    .left
    .right

    组件使用操作按钮注意点

    • 不管是什么,在组件上传递的事件,都是自定义事件!在HTML标签上添加就是原生DOM事件,比如说@click,@mousemove这些系统自带的原生事件在组件上直接使用的话都是自定义事件,组件内部需要对其进行处理才可以使用! 具体可以看看这博客
    • 所以如果组件上想使用原生事件,需要添加.native
    1
    2
    3
    4
    5
    6
    7
    8
    9
    比如有一个自定义组件MyButton

    //这里的@click即为自定义事件(因为是绑定在组件上的!)
    //如果MyButton组件没有对这个自定义事件进行处理,那么这个事件是没有用的
    <MyButton @click="事件"></MyButton>

    //这里的@click即为原生事件(因为使用了.native)
    //即使MyButton组件没有对这个事件进行处理,这个事件依旧会触发(因为是原生的,vue帮我们处理好了)
    <MyButton @click.native="事件"></MyButton>

    Vue的数据绑定响应式

    vue当中,除了data当中的数据会被vue转化为响应式数据以外,其他地方添加的数据均不是响应式,如果需要,必须要使用$set或者set方法官方是这样子说的,但是好像computed其实也是响应式

    如何追踪变化

    简单一句就是setter和getter方式,这里就不多说了,感兴趣的可以看看官方,这里记录下官方的话语

    • 当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setterObject.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。
    • 这些 getter/setter 对用户来说是不可见的,但是在内部它们让 Vue 能够追踪依赖,在 property 被访问和修改时通知变更。这里需要注意的是不同浏览器在控制台打印数据对象时对 getter/setter 的格式化并不同,所以建议安装 vue-devtools 来获取对检查数据更加友好的用户界面。
    • 每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。

    图片

    特别注意

    对于对象
    • data当中初始化的数据为响应式(也就是数据变了,视图也会发生变化!)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var vm = new Vue({
    data:{
    a:1
    }
    })

    // `vm.a` 是响应式的

    vm.b = 2
    // `vm.b` 是非响应式的
    (数据发生变化,依据这个数据绑定的标签之类的不会发生变化)
    • 非响应式数据变成响应式数据$set或者set(使得数据发生变化,视图也会发生变化)
    1
    2
    3
    4
    5
    6
    7
    //以myObj当中新添加的属性 b ,并设置为响应式数据 为例子
    //myObj.b = 2;//直接添加-非响应式
    Vue.set(myObj,'b',2);//set添加-响应式

    //或者 this为Vue的实例化对象
    this.$set(myObj,'b',2);//$set添加-响应式

    • 多个属性设置为响应式数据Object.assign方法
      • 通过整体替换的形式
      • 在线演示 @地址
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    var vm = new Vue({
    data(){
    return {
    someObject:{
    name:"李白",
    sex:"男"
    }
    }
    }
    })

    this.someObject = Object.assign({}, this.someObject, {
    a: 1,
    b: 2
    });
    //这样子myObj就成为了一个响应式对象
    console.log(this.someObject);
    //{ name: '李白', sex: '男', a: 1, b: 2 }

    注意,不可以this.someObject = Object.assign(this.someObject, { a: 1,b: 2 });因为这依旧是对原来的数据进行添加删除,而不是进行了替换!

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    //证明
    //有效(数据变化,视图也变换!)
    this.someObject = Object.assign({}, this.someObject, { a: 1,b: 2 });

    //无效(数据变化了,视图没有变化!)
    this.someObject = Object.assign(this.someObject, { a: 1,b: 2 });

    <template>
    <div>
    <button @click="addProperty">单击我</button>
    <span>{{ someObject.a }}</span>
    </div>
    </template>

    <script>
    export default {
    name: "Home",
    data() {
    return {
    someObject: {
    name: "李白",
    sex: "男",
    },
    };
    },
    methods: {
    addProperty() {
    // 单击后视图没有出现 新添加的数字1,既然是响应式数据,数据变化了,视图也会发生变化,这里却没有
    // 所以代码1 的写法是错误的!
    // 代码1 : this.someObject = Object.assign(this.someObject, { a: 1,b: 2 });

    //单击后视图出现了 新添加的数字1
    //数据变化,视图变化,响应式数据
    this.someObject = Object.assign({}, this.someObject, {
    a: 1,
    b: 2,
    });//出现了1
    },
    },
    };
    </script>

    对于数组

    由于对象当中有setter和getters,数组没有,所以数组的所有操作并不像对象那样容易引起数据改变,视图改变的状况

    例子

    1
    2
    3
    4
    5
    6
    7
    var vm = new Vue({
    data: {
    items: ['a', 'b', 'c']
    }
    })
    vm.items[1] = 'x' // 不是响应性的
    vm.items.length = 2 // 不是响应性的

    解决

    1. 使用Vue.set或者$set
    1
    2
    3
    4
    5
    6
    // Vue.set的实例,$set和set是一样的使用
    // 参数1为要设置的数组items
    // 参数2为设置的索引
    // 参数3为新添加的值
    // 这样子新添加的newValue即为响应式数据了
    Vue.set(vm.items, indexOfItem, newValue)
    1. 使用Vue当中重写的几个数组的方法,这样子操作就不会影响数据响应式效果
    1
    2
    // Array.prototype.splice
    vm.items.splice(indexOfItem, 1, newValue)
    1. Vue 将被侦听的数组的变更方法进行了包裹,所以它们也将会触发视图更新。这些被包裹过的方法包括:**(大白话就是Vue重写了这些方法,通过这些方法修改的数组也会触发视图的更新)**
    1
    2
    3
    4
    5
    6
    7
    push()
    pop()
    shift()
    unshift()
    splice()
    sort()
    reverse()
    1. 也可以替换数组来达到修改后依旧是响应式效果
    1
    2
    3
    4
    5
    6
    7
    //example1.items 本身为一个响应式数组
    //这里进行了替换操作
    example1.items = example1.items.filter(function (item) {
    return item.message.match(/Foo/)
    })

    //filter()、concat() 和 slice()等返回新数组的操作都可以~

    官网对于替换数组的解释:你可能认为这将导致 Vue 丢弃现有 DOM 并重新渲染整个列表。幸运的是,事实并非如此。Vue 为了使得 DOM 元素得到最大范围的重用而实现了一些智能的启发式方法,所以用一个含有相同元素的数组去替换原来的数组是非常高效的操作。

    Vue组件通信的六种方式

    Props通信

    • 主要用于父子间的通信,不仅仅是普通的子,也可以是孙子之类的之间的通信),
    • 为了方便,这里就不区分什么孙子,曾孙子了,统一以父亲和儿子来说
    props通信主要有
    • 传递非函数数据——-适用于父亲向儿子传递数据
      • 如果是普通数据-则是父亲想给儿子东东
      • 如果是传递的是函数,则是儿子想传递某些东东给父亲

    例子1: 父亲向儿子传递普通数据

    父亲(向儿子传递’title’,告诉儿子显示这个’title’)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    //父亲
    <template>
    <div>
    <h1>props通信</h1>
    <!-- 传递title属性给儿子,告诉儿子title信息 -->
    <Son title="标题设置为动感超人"></Son>
    </div>
    </template>

    <script>
    import Son from "./son.vue"
    export default {
    name: 'Father',
    components:{
    Son
    }
    }
    </script>

    儿子(负责显示父亲传递过来的’title’)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    //儿子
    <template>
    <div>
    <h1>{{title}}</h1>
    </div>
    </template>

    <script>
    export default {
    name: 'Son',
    //儿子接收父亲传递过来的props
    // 父亲刚刚传递了一个 title,这里就添加一个title
    props:["title"]
    }
    </script>

    效果图

    props-子向父传递非函数数据

    例子2 : 传递函数数据——儿子想告诉父亲什么东西(也就是想传递数据给父亲)

    父亲(传递一个函数 ‘getInfo’ 给儿子)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    //父亲
    <template>
    <div>
    <h1>props通信</h1>
    <!-- 传递getInfo属性给儿子,告诉儿子老爸想要你的信息 -->
    <Son :getInfo="returnInfo"></Son>
    </div>
    </template>

    <script>
    import Son from "./son.vue";
    export default {
    name: "Father",
    components: {
    Son,
    },
    methods: {
    //获取儿子的信息
    returnInfo(info) {
    console.log("儿子传递给我的信息是", info);
    },
    },
    };
    </script>

    儿子(接收父亲传递过来的函数’getInfo’,并调用这个函数)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    //儿子
    <template>
    <div>
    <button @click="tellFather">单击这个按钮告诉爸爸信息</button>
    <!-- 或者简单点 -->
    <button @click="getInfo('儿子很好,爸爸放心1')">简单调用写法</button>
    </div>
    </template>

    <script>
    export default {
    name: 'Son',
    //儿子接收父亲传递过来的props
    // 父亲刚刚传递了一个 getInfo,这里就添加一个getInfo
    props:["getInfo"],
    methods:{
    //告诉爸爸
    tellFather(){
    this.getInfo("儿子很好,爸爸放心2");
    }
    }
    }
    </script>

    效果图(单击’’这个按钮告诉爸爸信息’’,儿子就会告诉父亲’儿子很好,爸爸放心’)

    props-父亲向儿子传递函数数据

    props的类型
    • 数组

      • props:[“title” , “name” , “say”];
      • 儿子接收父亲传递过来的数据,都是写在数组里面的,并且是以字符串形式组成的数组(如上面二个案例就是用数组接收传递的)
    • 对象(普通key,value)

      • 用于指明props的值类型

      • 例子如下

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      props: {
      //指明title只能是字符串类型
      title: String,
      //指明likes只能是数值类型
      likes: Number,
      //指明isPublished只能是布尔值类型
      isPublished: Boolean,
      //指明commentIds只能是数组类型
      commentIds: Array,
      //指明author只能是对象类型
      author: Object,
      //指明callback只能是函数类型
      callback: Function,
      //指明contactsPromise只能是Promise类型
      contactsPromise: Promise // or any other constructor
      }
    • 对象里面又有配置对象

      • 配置对象参数

        • type:(指明props的值的类型)

        • required:(布尔值,代表是否必填,和default冲突)

        • default:(默认值参数)

        • validator:(验证参数)

          验证不通过报错

    • 配置对象代码示例

    1
    2
    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
    Vue.component('my-component', {
    props: {
    // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)
    propA: Number,
    // 多个可能的类型
    propB: [String, Number],
    // 必填的字符串
    propC: {
    type: String,
    required: true
    },
    // 带有默认值的数字
    propD: {
    type: Number,
    default: 100
    },
    // 带有默认值的对象
    propE: {
    type: Object,
    // 对象或数组默认值必须从一个工厂函数获取
    default: function () {
    return { message: 'hello' }
    }
    },
    // 自定义验证函数
    propF: {
    validator: function (value) {
    // 这个值必须匹配下列字符串中的一个
    return ['success', 'warning', 'danger'].indexOf(value) !== -1
    }
    }
    }
    })
    props通信变量注意点
    • prop的大小写
      • 调用组件时Prop使用驼峰式,props选项声明中需要全部小写
      • props中命名采用驼峰式,在调用组件的标签中使用短横线分割命名方式命名属性
    • 官网说明@地址

    也就是说,会把传入的props转化为小写,比如如下例子-采用了驼峰命名方式

    • 这里使用了驼峰命名clickAmount传递了属性
    • 那么vue会自动将其全部转化为小写(其实不管你有没有驼峰命名,vue都会将其转化为小写~),然后接受的时候如果使用相同的名字去接收,会接收失败!!!!!!!!!!!!!!!
    • 所以不能驼峰,如果确实想,就需要使用kebab-case (短横线分隔命名) 命名去传递参数了,然后接收的时候就需要使用驼峰接收了
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8" />
    <title></title>
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.9/vue.js"></script>
    </head>
    <body>
    <div id="box">
    <my-component
    message='理想是人生的太阳'
    v-bind:msg="msg"
    myTitle="诗与远方"
    // 这里使用了驼峰命名
    clickAmount="1000"
    my-name="自定义属性">
    </my-component>
    </div>
    <script type="text/javascript">
    Vue.component('my-component',{
    //接收的时候就需要全部改为小写,因为
    props:['message','msg','mytitle','myName','clickamount'],
    template:`<div>
    <p>{{msg}}</p>
    <p>{{message}}</p>
    <p>{{mytitle}}</p>
    <p>{{myName}}</p>
    <p>{{clickamount}}</p>
    </div>
    `,
    })
    var vm = new Vue({
    el:'#box',
    data:{
    msg:'父组件数据'
    }
    });
    </script>
    </body>
    </html>

    • 使用kebab-case (短横线分隔命名) 命名传递,并使用驼峰命名接收
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8" />
    <title></title>
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.6.9/vue.js"></script>
    </head>
    <body>
    <div id="box">
    <my-component
    message='理想是人生的太阳'
    v-bind:msg="msg"
    myTitle="诗与远方"
    // kebab-case (短横线分隔命名) 命名传递
    click-amount="1000"
    my-name="自定义属性">
    </my-component>
    </div>
    <script type="text/javascript">
    Vue.component('my-component',{
    // 驼峰命名接收 clickAmount
    props:['message','msg','mytitle','myName','clickAmount'],
    template:`<div>
    <p>{{msg}}</p>
    <p>{{message}}</p>
    <p>{{mytitle}}</p>
    <p>{{myName}}</p>
    <p>{{clickAmount}}</p>
    </div>
    `,
    })
    var vm = new Vue({
    el:'#box',
    data:{
    msg:'父组件数据'
    }
    });
    </script>
    </body>
    </html>

    自定义事件通信

    知道自定义事件和系统事件(原生事件)的区别
    • 自定义事件(事件类型自己定义,回调函数自己定义)
    • 系统事件(原生事件) (事件类型系统定义,回调函数自己定义)
    绑定和触发自定义事件
    • 关键用法是$on$emit

      • $on(参数1,参数2)
        • 参数1为绑定的事件名称(用于后面$emit触发)
        • 参数2为绑定的回调函数
      • $on经常绑定在获取数据的一方(订阅者)

      • $emit(参数1,参数2)
        • 参数1为要触发的事件名称(和$on的第一个参数要对得上)
        • 参数2为给事件名称对应的回调函数传递的参数值
      • $emit经常绑定在发送数据的一方(发布者)
    • 注意,如果$emit需要传递多个参数,要使用对象,比如说$emit(xxxx,{参数1:value,参数2,value2})

    自定义事件复杂写法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    //父亲
    <Son ref="erzi"></Son>
    var vm = new Vue({
    mounted(){
    //注意,第一个complex为自己定义的,用于后面$emit触发
    //第二个complex为绑定的回调函数!
    this.$refs.erzi.$on("complex",complex);
    },
    methods:{
    complex(content){
    console.log(content)
    }
    }
    })

    //儿子
    <span>我是儿子</span>
    //单击按钮,调用$emit触发自定义事件,并传递数据'爸爸,我很好'给父亲
    <button @click="this.$emit('complex','爸爸,我很好')"></button>
    自定义事件的简单写法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    //父亲
    //第一个complex为自己定义的,用于后面$emit触发
    //第二个complex为绑定的回调函数!
    <Son @complex="complex"></Son>
    var vm = new Vue({
    methods:{
    complex(content){
    console.log(content)
    }
    }
    })

    //儿子
    <span>我是儿子</span>
    //单击按钮,调用$emit触发自定义事件,并传递数据'爸爸,我很好'给父亲
    <button @click="this.$emit('complex','爸爸,我很好')"></button>
    顺带一提
    • 组件配置中
      • data函数,methods的函数,watch的函数,computed中的函数,他们的this均是[VueComponent实例对象]
    • new Vue(options)配置中
      • data函数,methods函数,watch的函数,computed的函数,他们的this均是[Vue实例对象]

    全局事件总线通信

    选择一个对象作为全局事件总线通信任务的东东需要具备下面几个条件

    1. 要是一个对象
    2. 这个对象可以被所有组件所访问
    3. 这个对象可以调用$emit$on方法

    所以选取vm作为全局事件总线通信任务人~

    至于为什么是vm,可以看看Vue和VueComponent的关系

    Vue和VueComponent的关系

    Vue和VueComponent的关系

    全局事件总线使用
    • 全局事件总线选取规则
      • 1.所有的组件均可以访问到
      • 2.被选取当做总线的具有$on,$emit方法
      • 3.需要是一个对象
    • 所以选择vm做为全局事件总线
    1. 在主入口文件的main.js当中添加beforeCreate对象,并在里面添加代码(注意绑定位置!)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      //正确的示例
      new Vue({
      beforeCreate(){
      Vue.prototype.$bus = this;
      }
      })

      //错误的示例
      //注意
      //这个是不可以的!!!
      var vm = new Vue({
      ...
      })
      Vue.prototype.$bus = vm;
      因为new Vue的那一刻,其他组件也会创建!创建好后才会执行这一行代码
    2. 在A组件(负责接受B组件或其他组件的数据)配置对象mounted当中添加$on,回调函数留在A组件当中(这里以Index.vue组件为例)

      1
      2
      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
      //Index组件
      <template>
      <div>
      <One></One>
      <Two></Two>
      </div>
      </template>
      <script>
      import Two from "@/views/allBus/two.vue"
      import One from "@/views/allBus/one.vue"
      export default {
      name: "Index",
      components:{
      Two,
      One
      },
      mounted(){
      //绑定tellText事件,回调函数为tellText
      this.$bus.$on("tellText",this.tellText);
      },
      methods:{
      tellText(content){
      console.log('有人说:',content);
      }
      }
      };
      </script>
    3. 在B组件或其他组件(负责发送A组件数据)当中添加$emit(以One.vue 和 Two.vue组件为例)

      //One.vue组件

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      <template>
      <div style="border:1px solid red">
      <h1>我是One</h1>
      <button @click="saySome">我来说句话</button>
      </div>
      </template>

      <script>
      export default {
      name: 'One',
      methods:{
      saySome(){
      //触发全局事件总线上的tellText事件,并向回调函数传递'我是One,我说话完毕'
      this.$bus.$emit("tellText","我是One,我说话完毕")
      }
      }
      }
      </script>

      <style lang="less" scoped>

      </style>

      //Two.vue组件

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      <template>
      <div style="border:1px solid blue">
      <h1>我是Two</h1>
      <button @click="saySome">我来说句话</button>
      </div>
      </template>

      <script>
      export default {
      name: 'Two',
      methods:{
      saySome(){
      //触发全局事件总线上的tellText事件,并向回调函数传递'我是One,我说话完毕'
      this.$bus.$emit("tellText","我是Two,我说话完毕")
      }
      }
      }
      </script>

      <style lang="less" scoped>

      </style>

    Vue.prototype.$bus = vm;为什么不能用,为什么一定要用beforeCreate钩子实现?(可能有误~)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //正确的示例
    new Vue({
    beforeCreate(){
    Vue.prototype.$bus = this;
    }
    })

    //错误的示例
    //注意
    //这个是不可以的!!!
    var vm = new Vue({
    ...
    })
    Vue.prototype.$bus = vm;//下面就来解释为什么这样子不可以
    首先了解下Vue与VueComponent的关系
    • 这位博主更加详细的介绍了下Vue组件化之VueComponent介绍
    • 组件都是通过一个叫VueComponent的构造函数创建的,并且这个VueComponent不是我们写的,而是Vue.extend函数生成的,并且每次生成的都是不一样的VueComponent的构造函数。
    • 每当我们使用组件标签时(比如说有一个School的自定义组件)使用VueComponent构造函数创建一个VueComponent对象,帮我们执行 new VueComponent(options)
    • this的指向
      • 在组件配置中:data函数,methods中配置的函数,watch中配置的函数,computed中配置的函数的this指向的都是VueComponent组件对象。
      • 在vue实例配置中:data函数,methods中配置的函数,watch中配置的函数,computed中配置的函数的this指向的都是vue对象。
    原因解释
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //正确的示例
    new Vue({
    beforeCreate(){
    //在这里挂载到了Vue的原型上,后期通过Vue.extend函数生成的均可以获取到
    Vue.prototype.$bus = this;
    }
    })

    //错误的示例

    var vm = new Vue({
    ...
    })
    Vue.prototype.$bus = vm;//执行到这里组件已经创建完成,没有用了

    正确绑定总线流程图执行过程

    正确绑定总线流程图执行过程

    错误绑定总线流程图执行过程

    错误绑定总线流程图执行过程

    至于为什么控制台console.log(可以输出查看到)

    因为控制台展开会再次去读取最新的代码,所以你可以控制台看到~

    pubsub-js通信

    前置准备
    • 首先要安装

      1
      npm install pubsub-js --save
    • 然后用到的地方都导入

      1
      import PubSub from "pubsub-js"
    • 当然,你也可以全局使用捆绑在原型上

      • 比如import PubSub from 'pubsub-js'; Vue.prototype.PubSub = PubSub;
    • 一句话说清楚添加订阅和发布订阅

      • 需要数据的成为订阅者
      • 传送数据的成为发布者
    绑定事件的应用(可以立即为一颗定时炸弹)
    • 事件名
    • 事件的回调函数
    • 声明事件对象参数
    • 获取数据的一方
    • 对比其他
      • PubSub:订阅者
      • Vue: $on()
    触发事件(可以理解为炸弹引爆器-告诉炸弹是否引爆)
    • 事件名
    • 传入事件对象
    • 提供数据的一方
    • 对比其他
      • 1.PubSub:发布
      • 2.Vue: $emit
    添加订阅(类似于$on)
    • 语法格式

      1
      PubSub.subscribe("发布消息的名称",回调函数)
    • 简单理解为PubSub.subscribe("要订阅的公众号",回调函数)

    注意:

    • 回调函数会传入二个参数
      • 参数1为: 发布消息的名称 (不管用不用,都必须接收并占位!否者收不到第二个参数)
      • 参数2为: 即为发布订阅传递过来的消息
    • 注意:
      • 回调函数当中的第一个参数不管用不用,都需要进行占位
    1
    2
    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
    //Two.vue
    <template>
    <div style="border: 1px solid blue">
    <h1>Two-测试pubsub-js</h1>
    {{ content }}
    </div>
    </template>

    <script>
    import PubSub from "pubsub-js";
    export default {
    name: "",
    data() {
    return {
    content: "",
    };
    },
    mounted() {
    //添加订阅(类似于$on),消息名称为'tellMeSome',绑定的回调为'tell'
    PubSub.subscribe("tellMeSome", this.tell);
    },
    methods: {
    //回调函数,第一个参数必须要写,否者第二个传递过来的参数接收不到
    tell(msg, content) {
    console.log(msg); //输出tellMeSome
    console.log("有人告诉了我", content);
    this.content = content;
    },
    },
    };
    </script>


    发布订阅(类似于$emit)
    • 语法格式

      1
      PubSub.publish("发布消息的名称",传递的参数)
    • 简单理解为PubSub.subscribe("公众号名称","传输给用户的文章")

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    //One.vue
    <template>
    <div style="border:1px solid red">
    <h1>One-测试pubsub-js</h1>
    <button @click="itellyou">单击我告诉Two</button>
    </div>
    </template>

    <script>
    import PubSub from "pubsub-js";
    export default {
    name: "One",
    methods: {
    itellyou() {
    //发布订阅,并传入数据'Two你好,我是One,来测试pubsub的'
    PubSub.publish("tellMeSome", "Two你好,我是One,来测试pubsub的");
    },
    },
    };
    </script>

    <style lang="less" scoped>
    </style>

    效果

    单击后显示文字,传递信息成功

    单击后显示文字,传递信息成功

    插槽

    作用: 让父组件可以向子组件指定位置插入html结构,适用于父组件 到 子组件

    适合在多个组件当中,有一部分内容相同,部分内容不同的情况下使用,如下面这种情况就很适合,3个组件都有按钮,但是按钮下方的内容是不同的

    3个组件都有按钮,但是按钮下方的内容是不同的

    默认插槽

    关键: slot标签什么属性都没有的,就一个纯标签

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    父组件
    <Son>
    <!--使用默认插槽-->
    <template>
    我是html结构
    </template>
    </Son>
    或者省略默认template
    <Son>
    <!--使用默认插槽-->
    我是html结构
    </Son>


    子组件
    <template>
    <div>
    <!--定义插槽-->
    <slot>插槽默认内容(没有被使用的默认显示内容)</slot>
    </div>
    </template>
    具名插槽

    理解: slot是含有name属性的标签

    关键: 父组件使用具名插槽是关键是添加属性slot="插槽名称(对应插槽slot的name属性值)" 或者 v-slot:插槽名称

    • 不过vue3不推荐使用slot属性去使用具名插槽,而是使用v-slot属性去使用具名插槽
    • 跟 v-on 和 v-bind 一样,v-slot 也有缩写。
      • v-slot: 替换为字符#
      • 例如 v-slot:header 可以被简写为 #header

    父组件

    1
    2
    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
    <template>
    <div>
    <h3>我是父亲</h3>
    <Son>

    <!-- slot="xxx"使用具名插槽 -->
    <template slot="one">
    我会更换具名插槽的内容
    </template>

    或者 v-slot:one
    <template v-slot:one>
    我会更换具名插槽的内容
    </template>
    </Son>
    </div>
    </template>

    <script>
    import Son from "./Son.vue"
    export default {
    name: '',
    components:{
    Son
    }
    }
    </script>

    子组件

    1
    2
    3
    4
    5
    6
    7
    8
    <template>
    <div>
    <!-- 具名插槽 -->
    <slot name="one">
    我是具名插槽的默认内容
    </slot>
    </div>
    </template>
    作用域插槽

    理解:

    • slot是含有绑定数据的标签,并且父组件可以拿到子组件的数据(通过slot给父亲),传递数据的方式类似于props通信结构
    • 数据在组件的自身,但根据数据生成的结构需要组件的使用者来决定
      • 像element-ui当的<el-table-column></el-table-column>就需要我们使用作用域插槽来决定结构

    关键: 父组件使用具名插槽是关键是添加属性slot-scope="" 或者 slot=""

    父组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <template>
    <div>
    <h3>我是父亲</h3>
    <Son>
    <!-- 使用作用域插槽 -->
    <!-- 传递过来的完整数据其实是一个对象,这里使用了解构赋值 -->
    <!-- 接收从插槽当中传递过来的数据,并使用解构赋值接收 -->
    <template slot-scope="{games}">
    {{games}}
    </template>
    </Son>
    </div>
    </template>
    <script>
    import Son from "./Son.vue"
    export default {
    name: '',
    components:{
    Son
    }
    }
    </script>

    子组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <template>
    <div>
    <!-- 作用域插槽 -->
    <slot :games="games"> 我是作用域插槽默认内容 </slot>
    </div>
    </template>
    <script>
    export default {
    name: "",
    data() {
    return {
    games: ["穿越火线", "英雄联盟", "王者荣耀", "战地之王"],
    };
    },
    };
    </script>

    输出示例

    作用域插槽

    vuex(不使用模块化)

    安装
    • 对于vue2: npm install vuex@3 --save

    • 对于vue3: npm install vuex --save

    • 查看vue版本

      • 打开你的package.json文件夹就可以看到

        查看vue是2.x还是3.x

    • 查看npm当中vuex的所有版本 npm view vuex versions

      查看vuex所有版本

    使用
    1. 添加store文件夹并在里面建立index.js文件

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      import Vuex from "vuex"
      import Vue from "vue"
      Vue.use(Vuex);
      const state = { ... }
      const mutations = { ... }
      const actions = { ... }
      const getters = { ... }
      //别忘记new Vuex.Store(配置对象)了!
      export default new Vuex.Store({
      state,
      mutations,
      actions,
      getters
      })
    2. 主入口文件main.js引入store文件夹的index.js

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      import store from "@/store"
      或者
      import store from "@/store/index.js"

      添加到vue的配置对象当中
      new Vue({
      ...
      store,
      ...
      })
    3. 其他组件可以通过this.$store.dispatch(actions的方法名,传递的参数)来进行调用并存储数据(如果需要传递多个参数,请封装成为对象后传递)

    详细配置对象参数
    • new Vuex.Store(配置对象),配置对象当中的含义

      • state: 是一个包含多个属性(不包含方法)的对象,用来存储数据

      • mutations: 是一个包含多个方法的对象,用这些方法去操作state当中的对象(也就是操作数据)

        • 只接收普通的函数,不接受任何if ,for,异步函数
        • 里面的方法第一个参数state(名字可以随意),为当前配置对象当中state
        • 里面的方法第二个参数为通过commit方法传递过来的参数
      • actions:是一个包含多个方法的对象,这个对象里面的方法用于给其他组件去调用

        • 可以包含if ,for , 异步函数

        • 里面的方法传入的第一个为参数content(名字可以随意),即为当前的store对象

          输出查看第一个参数

        • 里面的方法传入的第二个参数为通过dispatch传递过来的参数

      • getters: (类似于computed),是一个包含多个方法的对象,通过计算返回数据

        • 里面的方法传入的第一个参数为state(参数名称随意),也就是当前state对象
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        var state = {...};
        var mutations = {...};
        var actions = {...};
        var getters = {
        sayHello(){

        },
        方法1:(state)=>{

        },
        方法2(state){

        },
        //方法2写法等同于
        //只不过看你是否用到了this
        //方法2:function(state){
        //
        //}
        }
    使用示例1(不使用模块化)

    单击按钮后vuex的name由”李白” 变成了”动感超人”

    普通组件One.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <template>
    <div>
    <div>我是One</div>
    <button @click="gaibian">单击我改变store当中名字</button>
    </div>
    </template>

    <script>
    export default {
    name: "One",
    methods:{
    gaibian(){
    //调用vuex当中的dispatch
    this.$store.dispatch("changName","动感超人")
    }
    }
    };
    </script>

    store文件夹当中的index.js

    1
    2
    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
    import Vuex from "vuex"
    import Vue from "vue"
    Vue.use(Vuex);

    var state = {
    name:"李白"
    }
    var mutations = {
    //用于改变state当中的name
    SET_NEW_NAME(state,newValue){
    state.name = newValue;
    }
    }
    //用户使用this.$store.dispatch()调用的正是actions当中的方法
    var actions = {
    //用于调用mutations当中的方法去改变name值
    //使用解构赋值解构出commit
    changName({commit},value){
    commit("SET_NEW_NAME",value)
    }
    }
    var getters = {

    }
    export default new Vuex.Store({
    state,
    mutations,
    actions,
    getters
    })

    vuex(使用模块化)

    • 其他什么都不需要改变,只需要改变下store文件夹下的index.js(主入口文件)

    • 文件目录结构如下

      • store
        • shop(文件夹)
          • address1.js
          • address2.js
        • food(文件夹)
          • vegetable.js
          • meat.js
        • user(文件夹)
          • vipuser.js
          • user.js
        • index.js(主入口文件)
    • 以后的主入口文件index.js只需要写成下面就可以

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      import Vuex from "vuex"
      import Vue from "vue"
      Vue.use(Vuex);
      // 模块用法

      //引入One.js模块
      import One from "./One.js";
      export default new Vuex.Store({
      //可以用自己的下面四大类,这里没有使用而已
      // state:{}
      // mutations:{},
      // actions:{},
      // getters:{},
      state:{
      name:"李白"
      },
      //使用模块化
      modules:{
      One
      }
      })
    • One.js模块内容

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      var state = {
      name:"李白"
      }
      var mutations = {
      //用于改变自己state当中的name
      SET_NEW_NAME(state,newValue){
      state.name = newValue;
      }
      }
      var actions = {
      //用于调用mutations当中的方法去改变name值
      changName({commit},value){
      commit("SET_NEW_NAME",value)
      }
      }
      var getters = {

      }
      //别忘记暴露出现
      export default {
      state,
      mutations,
      actions,
      getters
      }

    模块化后this.$store.state内容如图

    模块化后this.$store.state内容

    模块化之前输出this.$store
    • 可以看到,state当中没有嵌套什么

    模块化之前输出this.$store

    模块化之后输出this.$store
    • 可以看到,state当中嵌套了另外一个对象

    • 可以看到,One.js暴露的内容当中的state部分成为了this.$store.state里面的对象并且One为key值,value为One.js当中的state对象的值

    模块化之后输出this.$store

    模块化需要注意的点
    • 默认情况下,模块内部的actionmutation (官网是这样子说的,但是我测试后发现getters也是注册在全局下的) 是注册在全局命名空间的(也就是会放在this.$store对应的actions,mutations,getters对象上)(所以如果不使用命名空间的话,不管有没有模块化,想调用里面的方法只需要this.$store.dispatch(actions当中的名称,传递的参数)即可调用)

    • 如果没有使用命名空间,那么外界想调用模块当中的actions里面的方法,都是可以直接调用的,比如One.js,已经使用了模块化,但是外面想要调用actions当中的方法依旧只需要this.$store.dispatch(actions当中的名称,传递的参数)就可以调用!但是如果想要获取One.js当中state里面的数据,就需要多调用一层,即:this.$store.One.属性名才可以

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      One.js并且使用了模块化

      //想获取One.js里面的age属性,模块化后要怎么获取?
      //其他组件调用 this.$store.state.One.age即可
      var state = {
      name:"李白",
      age:2000
      }
      var mutations = {...}
      //想调用actions当中的'changeName'
      //在其他组件调用 this.$store.diaptch("changeName","李黑");
      //即可调用One.js当中actions里面的changName方法
      var actions = {
      changName(content,value){
      }
      }
      var getters = {...}
      export default {
      state,
      mutations,
      actions,
      getters
      }
    使用命名空间(针对模块化)
    • 影响到actionsgetters方法使用,其他state,mutation不受影响(因为如果不使用命名空间,后面组件调用dispatch方法,只要模块的actions里面有这个被调用的函数名称,就会被调用,不管你有几个重复的,都会被触发)

    • 模块配置对象当中添加namespaced:true即可使用命名空间

    • One.js当中的内容

      1
      2
      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
      var state = {
      name:"李白",
      age:"100"
      }
      var mutations = {
      //用于改变state当中的name
      SET_NEW_NAME(state,newValue){
      state.name = newValue;
      }
      }
      var actions = {
      //用于调用mutations当中的方法去改变name值
      changName({commit},value){
      commit("SET_NEW_NAME",value)
      }
      }
      var getters = {
      allInfo(state){
      return state.name+state.age;
      },
      getOther(){
      return "啊啊";
      }
      }
      export default {
      //使用命名空间
      namespaced:true,
      state,
      mutations,
      actions,
      getters
      }
    • 未使用命名空间如何调用actions里面方法和getters里面的方法

      1
      2
      3
      4
      5
      6
      7
      //调用actions当中的changName,如果有多个changName,也会被调用
      this.$store.dispatch("changName");

      //调用getters当中的allInfo来获取值
      this.$store.getters.allInfo
      //放置在标签上
      <span>{{$store.getters.allInfo}}</span>

      没有使用命名空间之后输出this.$store从而查看getters和actions等其他

    • 使用命名空间后如何调用actions里面方法和getters里面的方法

      1
      2
      3
      4
      5
      6
      7
      //只会调用One.js文件下的actions里面的changName方法
      this.$store.dispatch("One/changName");

      //调用getters当中的allInfo来获取值
      this.$store.getters['One/allInfo']
      //放置在标签上
      <span>{{this.$store.getters['One/allInfo']}}</span>

    使用命名空间之后输出this.$store从而查看getters和actions等其他

    前缀名字会和modules的key对应

    前缀名字会和modules的key对应

    如果key改为了OneE

    改为了OneE

    那么命名空间当中的名字也会改变

      前缀名字会和modules的key对应

    vuex使用mapState和mapGetters

    • mapState放在哪里?

      • 放在data肯定不行,因为data一般存放已经定义好的数据,props中,也不行,props一般接收通过标签属性来传递的,所以mapState放在computed当中
    • 为什么要mapState和其他的mapXXXXX(这里以不使用模块化为例)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      假设store文件夹下方的入口文件index.js含有下方内容

      import Vuex from "vuex"
      import Vue from "vue"
      Vue.use(Vuex);
      var state = {
      name:"李白",
      age:"100",
      address:"地球"
      }
      var mutations = {...}
      var actions = {...}
      var getters = {...}
      export default new Vuex.Store({
      state,
      mutations,
      actions,
      getters
      });
    • 那么我有一个组件叫One.vue,我想获取到vuex里面的数据放在我这里组件上使用,那么要怎么使用?

      • 那么平时,我们必须要写n多个 this.$store.state.xxxxx
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      One.vue
      那么我们就必须要写n多个 this.$store.state.xxxxx
      export default {
      name: "",
      mounted() {},
      components: {
      One,
      },
      computed: {
      name() {
      return this.$store.state.name;
      },
      age() {
      return this.$store.state.age;
      },
      address() {
      return this.$store.state.age;
      },
      },
      };
    使用mapState(不使用模块化下)
    • 先引入 import {mapState} from "vuex"; 别忘记了花括号!

    • 使用(对象的写法)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      import { mapGetters } from 'vuex'
      // 每次会传入一个参数,这个参数一般命名为state
      //state等同于this.$store.state的内容!!!
      var obb = mapState({

      自定义名称:function(state){
      console.log(state === this.$store.state);//输出为true
      return state.要获取的属性
      },
      //也可以简写
      自定义名称:state => state.要获取的属性
      })

      //返回值为一个对象

      比如下方代码
      var myObj = mapState({
      sex:function(state){
      return state.sex;
      }
      });
      //再简单点可以写为
      var myObj = mapState({
      sex: state => state.sex,
      });
      console.log(myObj);//{sex: ƒ}
    • 使用(数组的写法)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      import { mapState } from 'vuex'

      export default {
      // ...
      computed: {
      // 使用对象展开运算符将 getter 混入 computed 对象中
      ...mapState([
      'sex',
      'age',
      // ...
      ])
      }
      }
      //代码等同于
      computed: {
      ...mapState({
      sex:state=>state.sex,
      age:state=>state.age,
      })
      }
    • 有了上面的代码例子,这里做一个小结

      • mapState一般这样子使用,传入一个对象,里面对象的key为自定义名称,value值为一个函数(因为要通过函数当中的参数获取值),并且这个函数默认有一个参数(一般取名state),并且这个参数会等于this.$store.state
    结合对象展开符-不使用模块化下(常用)

    mapState 函数返回的是一个对象。我们如何将它与局部计算属性混合使用呢?通常,我们需要使用一个工具函数将多个对象合并为一个,以使我们可以将最终对象传给 computed 属性。但是自从有了对象展开运算符,我们可以极大地简化写法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    //简写
    computed: {
    ...
    // 使用对象展开运算符将此对象混入到外部对象中
    ...mapState({
    sex: state => state.sex,
    address: state => state.address
    });
    ...
    }

    //复杂写
    computed: {
    ...
    // 使用对象展开运算符将此对象混入到外部对象中
    ...mapState({
    sex: function(state){
    return state.sex;
    },
    address: function(state){
    return state.address;
    }
    });
    ...
    }
    使用mapState(使用模块化下)
    • 先引入 import {mapState} from "vuex"; 别忘记了花括号!

    • 和没有使用模块化相比,对象当中多了一层寻找,也就是对应的模块

      • 比如之前要寻找One.js当中的sex属性,我们只需要this.$store.sex

      • 而模块化后,我们要this.$store.One.sex

        • (注意,对应的模块的名称要和store文件夹下的index.js下书写的models对应)
        1
        2
        3
        4
        5
        6
        7
        8
        import One from "./One.js";
        export default new Vuex.Store({
        modules:{
        One
        }
        })
        //获取One.js当中的sex属性
        this.$store.One.sex
    • 使用

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      // 每次会传入一个参数,这个参数一般命名为state
      //state等同于this.$store.state的内容!!!!!!!
      var obb = mapState({
      自定义名称:function(state){
      console.log(state === this.$store.state);//输出为true
      return state.对应的模块.要获取的属性
      },
      //也可以简写
      自定义名称:state => state.对应的模块.要获取的属性
      })

      //返回值为一个对象

      比如下方代码
      var myObj = mapState({
      sex:function(state){
      //注意这里多了一层
      return state.One.sex;
      }
      });

      写简单点可以这样子写
      var myObj = mapState({
      sex:state => state.One.sex,
      });
      console.log(myObj);//{sex: ƒ}
    结合对象展开符-使用模块化下(常用)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    //简写
    computed: {
    ...
    // 使用对象展开运算符将此对象混入到外部对象中
    ...mapState({
    sex: state => state.One.sex,
    address: state => state.One.address
    });
    ...
    }

    //复杂写
    computed: {
    ...
    // 使用对象展开运算符将此对象混入到外部对象中
    ...mapState({
    sex:function(state){
    return state.One.sex;
    },
    address:function(state){
    return state.One.address;
    }
    });
    ...
    }
    使用mapGetters
    先来了解下getters

    getters和state也是一样,getters读取如果是模块化了就要多嵌套一层,其他的使用都和state一样,只不过state字换成了

    • 先来看看getters当中的使用(可以看到,getters当中的方法会默认传入参数state)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    var state = {
    name:"李白",
    age:"100"
    }
    var getters = {
    allInfo(state){
    //返回值会作为allInfo的值,类似于computed的用法
    return state.name+state.age;
    }
    }
    • 其他对象使用getters
    1
    2
    3
    4
    //未模块化
    this.$store.getters.allInfo
    //模块化后
    this.$store.getters.One.allInfo;//多了一层
    使用mapGetters(用不用模块都是这个)
    • 数组写法

      1
      2
      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
      import { mapGetters } from 'vuex'
      export default {
      // ...
      computed: {
      // 使用对象展开运算符将 getter 混入 computed 对象中
      ...mapGetters([
      // ...
      'allInfo',
      // ...
      ])
      }
      }

      !!!!!!!!!代码等同于(注意,只是理解上的效果,实际在这getters写是错误的是不行的!)!!!!!!!!!!!!!!
      // 注意,只是理解上的效果,实际在这getters写是错误的是不行的!
      //注意,只是理解上的效果,实际在这getters写是错误的是不行的!
      import { mapGetters } from 'vuex'
      export default {
      // ...
      computed: {
      // 使用对象展开运算符将 getter 混入 computed 对象中
      ...mapGetters({
      allInfo:function(getters){
      return getters.allInfo
      }
      })
      }
      }
    • 对象写法(如果你想将一个 getter 属性另取一个名字,使用对象形式)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      import { mapGetters } from 'vuex'
      export default {
      // ...
      computed: {
      // 使用对象展开运算符将 getter 混入 computed 对象中
      ...mapGetters({
      allName:"allInfo"
      })
      }
      }
      !!!!!!!!!代码等同于(注意,只是理解上的效果,实际在这getters写是错误的是不行的!)!!!!!!!!!!!!!!
      // 注意,只是理解上的效果,实际在这getters写是错误的是不行的!
      //注意,只是理解上的效果,实际在这getters写是错误的是不行的!
      import { mapGetters } from 'vuex'
      export default {
      // ...
      computed: {
      // 使用对象展开运算符将 getter 混入 computed 对象中
      ...mapGetters({
      allName:function(getters){
      return getters.allInfo
      }
      })
      }
      }
    使用mapGetters如果在命名空间下要怎么使用mapGetters
    • 只可以使用mapGetters对象的形式!
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    其他组件Index.vue内容
    //使用mapGetters调用命名空间下的getters
    import { mapGetters } from "vuex";
    export default {
    computed: {
    ...mapGetters({
    allInfo:"One/allInfo"
    })
    },
    };
    </script>

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    One.js内容
    var state = {...}
    var mutations = {...}
    var actions = {...}
    var getters = {
    allInfo(state){
    return state.name+state.age;
    },
    }
    export default {
    namespaced:true,
    state,
    mutations,
    actions,
    getters
    }
    vue-admin-template当中明明使用了mapGetters,为什么不是对象的写法,而依旧是数组的写法

    正常来说,你肯定会以为自vue-admin-template的getters是写在对应的模块上的,比如One.jsgetters就写在One.js里面,Two.jsgetters就写在Two.js里面,那你就错了~

    vue-admin-template模板作者是将**每一个模块的getters都写在了主入口文件src\index.js当中!**其他模板根本就没有getters这个配置对象!

    每一个模块的getters都写在了主入口文件src\index.js当中!

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //getters.js内容
    const getters = {
    sidebar: state => state.app.sidebar,
    device: state => state.app.device,
    token: state => state.user.token,
    avatar: state => state.user.avatar,
    name: state => state.user.name,
    //用户角色
    roles: state => state.user.roles,
    //最终用户可拥有的路由
    currentAsyncRoutes: state => state.user.currentAsyncRoutes,
    }
    export default getters

    所以为什么vue-admin-template依旧可以使用mapGetters的数组写法而使用对象写法,因为人家getters绑定在了主入口文件index.js里面,并且这个index.js是没有开启命名空间的~

    1
    2
    3
    4
    5
    6
    7
    vue-admin-template其他文件组件使用mapGetters的方法
    computed: {
    ...mapGetters([
    'sidebar',
    'currentAsyncRoutes'
    ])
    }

    vuex其他一些要点

    mutations不一定要大写,之前大写的因为这些变量是常量

    为什么要通过三步actions-mutations-state来修改数据?

    异步修改数据会导致数据不可控,不知道谁先完成,所以要同步

    • mutations是同步修改数据
    • actions是异步请求修改数据
    • 当是同步的时候,就可以直接修改通过mutations修改state当中的数据,就可以不通过actions来修改
    mapState,mapActions,mapMutations位置说明
    • mapActions是映射vuex当中函数的,所以写在methods当中(这是本质)

    • 并且mapActions是一个函数,不然怎么映射对吧?mapActions函数需要传入一个对象,不然它怎么知道要映射什么呢?

      • mapActions(["xxxx"])
    • mapState 外部来的数据不可能定义在data当中,data当中只可以放自己定义的,也不是通过标签属性传递过来的,所以也不会出现在props当中,所以只能出现在watch当中

    • 现在watch当中指明在返回哪一个数据,因为你想想看,computed在vue是怎么用的,也是需要返回一个数据

    路由组件和非路由组件

    基本概念

    • 非路由组件:定义在其他组件当中的,就叫非路由组件(不一定要注册在App.vue当中)
    • **路由组件:**定义,注册在路由当中的,就叫路由组件(路由组件的创建和其他非路由组件一样,只不过最后注册的时候不一样)
    • 一般情况下
      • 非路由组件放在components文件夹当中
      • 路由组件放在views或者pages文件夹当中
    • 路由组件在切换的时候会被销毁,显示的时候重新创建(所以路由组件的生命周期会被重新执行)

    使用

    • 安装
      • vue2: npm install vue-router@3 --save
      • vue3: npm install vue-router --save
    • src当中建立router文件夹,里面含有主入口文件index.js

    index.js内容(src/router/index.js)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    import Vue from "vue"
    import VueRouter from "vue-router"
    Vue.use(VueRouter);
    import Home from "@/src/views/Home"
    export default new VueRouter({
    routes:[
    ...
    {
    path:"/home",
    component:Home
    },
    ....
    //重定向
    //浏览器输入http://localhost/就会自动跳转到http://localhost/home
    //{
    // path:"/",
    // redirect:"/home"
    //}
    ]
    })
    • main.js当中配置对象添加index.js暴露出的内容

    main.js内容(项目主入口文件)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import Vue from 'vue'
    import App from './App.vue'

    import router from "@/router"

    new Vue({
    render: h => h(App),
    router,
    }).$mount('#app')

    • 别忘记了在App.vue添加<router-view></router-view>哦,不然你就算创建了路由没有设置这个也没有用~

    App.vue内容

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <template>
    <div>
    <router-view></router-view>
    </div>
    </template>

    <script>
    export default {
    name: 'App',
    }
    </script>

    Home.vue内容(src/views/Home.vue)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <template>
    <div>我是Home</div>
    </template>

    <script>
    export default {
    name: 'Home',
    }
    </script>

    地址栏输入xxxx/home就会显示

    二级路由,三级路由,四级路由,,,n级路由

    关键在于配置对象当中children:[],

    1
    2
    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
    routes:[
    //一级路由
    {
    path:"/home",
    component:Home,
    //二级路由
    children:[
    {
    // path:"/home/messsage"或者下面这一行
    path:"message",//注意: 不需要再写 '/'
    component:Message,
    //三级路由
    children:[
    {
    // path:"/home/messsage/info"或者下面这一行
    path:"info",
    component:Info
    }
    ]
    }
    ]
    },
    {
    path:"/about",
    component:About
    },

    //重定向,默认路由
    {
    path:"/",
    redirect:"/home"
    }
    ]

    路由链接和路由切换标签(就是单击后切换路由组件和显示路由组件)

    <router-link to="切换的路由路径">文本</router-link>用户点击的链接,经过vue编译后会生成一个超链接,会跳转到to内容

    例子: 单击标签就会跳转到/food路由

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    //路由表
    import Vue from "vue"
    import VueRouter from "vue-router"
    Vue.use(VueRouter);

    import Home from "@/views/Home";
    import Food from "@/views/Food"
    export default new VueRouter({
    routes:[
    {
    path:"/home",
    component:Home
    },
    {
    path:"/",
    redirect:"/home"
    },
    {
    path:"/food",
    // component:() => import("@/views/Food"),
    component:Food
    }
    ]
    })


    //Home.vue

    <template>
    <div>
    <h1>我是Home</h1>
    <router-link to="/food">用户点击的链接 </router-link>
    </div>

    </template>

    //Food.vue

    <template>
    <div>
    <h1>我是Food</h1>
    </div>

    </template>

    <script>
    export default {
    name: 'Food',
    }
    </script>

    声明式导航和编程式导航

    • 声明式导航: <router-link to="路径"></router-link>

    • 编程式导航: 运用this.$router.push 或者 this.$router.replace等实现跳转

    • 声明式导航占用资源多,编程式导航占用资源少,所以推荐使用编程式导航

    • this.$router代表路由器 - 我理解为统一管理路由的管理者

      • router是VueRouter的一个对象,通过Vue.use(VueRouter)和VueRouter构造函数得到一个router的实例对象,这个对象中是一个全局的对象,他(this.$router)包含了所有的路由和包含了许多关键的对象和属性

        输出this.$router

      • this.$router一些常见的方法

        1
        2
        3
        4
        5
        1. this.$router.push(path): 相当于点击路由链接(可以返回到当前路由界面)
        2. this.$router.replace(path): 用新路由替换当前路由(不可以返回到当前路由界面)
        3. this.$router.back(): 请求(返回)上一个记录路由
        4. this.$router.go(-1): 请求(返回)上一个记录路由
        5. this.$router.go(1): 请求下一个记录路由
    • this.$route代表当前路由对象

      • $route是当前激活的路由,包含了当前激活的路由状态信息,它包含了当前URL解析得到的信息.

      • http://localhost/home下输出this.$route

        输出this.$route

    使用编程式导航
    • 示例如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <div>
    <h1>我是Home</h1>
    <button @click="$router.push('/food')">跳转到food</button>
    </div>

    //等同于
    <div>
    <h1>我是Home</h1>
    <router-link to="/food">跳转到food</router-link>
    </div>

    路由传参props和query和params

    • 在不同路由当中,可以存储一定的参数信息,比如下面这张图,就可以看到/home这个路由对象里面有很多参数,我们这里关注queryparams

    控制台输出this.$route,如下图

    输出this.$route

    query方式传参
    1. 直接在路径后面接上去,就如同发送ajax请求的时候后面携带的?aa=bb&cc=dd一样

      1
      2
      3
      4
      5
      //比如声明式导航跳转到/home的时候带了参数
      <router-link :to="`/home/message/msgdetail?id=${ms.id}&msg=${ms.msg}`">{{ms.msg}}</router-link>
      <router-link to="/food?name=茄子&weight=1">跳转到food并且携带query参数</router-link>
      //编程式导航
      <button @click="$router.push('/food?name=茄子&weight=1')">跳转到food并且携带query参</button>
    2. 使用对象的形式传递query参数

      1
      2
      3
      4
      //声明式导航
      <router-link :to="{path:'/food',query:{name:'茄子',weight:1}}">跳转到food并且携带query参数</router-link>
      //编程式导航
      <button @click="$router.push({path:'/food',query:{name:'茄子',weight:1}})">跳转到food并且携带query参数</button>

    携带query参数后打印输出foodthis.$route

    携带query参数后打印输出`food`的`this.$route`

    params方式传参

    重要,重要!很重要!

    必须要在路由当中提前占位,否者就是跳转到别的路由了!!!!!!!!!!!!!!!!!!!!!!!!!!!

    特别注意:路由携带params参数时,若使用to的对象写法,则不能使用path配置项,必须使用name配置!

    path: “/food/:foodname/:foodweight”,如果这样子填写了,后面访问这个路由就必须要添加这二个参数,比如/food/vegetable/1

    不可以少写也不可以漏写,否者访问不了参数

    如果需要想写想不写,就可以在占位后面添加一个”?”,比如path: “/food/:foodname?/:foodweight?”就代表这二个参数可写可不写,输入/food/vegetable或者/food或者/food/vegetable/1都可以跳转到指定路由

    路由占位和name配置

    占位格式: :key值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import Home from "@/views/Home";
    import Food from "@/views/Food"
    export default new VueRouter({
    routes:[
    ...
    {
    //使用params参数必须要写name,不可以使用path了!!!!
    name:"food",
    //路由占位
    //对应路由组件的this.$route当中params对象的key值
    path:"/food/:foodname/:foodweight",
    component:Food,
    }
    ...
    ]
    })
    1. 格式就类似于/food/茄子/1这种

      1
      2
      3
      4
      5
      // 声明式导航
      <router-link to="/food/qiezi/1/">跳转到food并且携带params参数</router-link>

      // 编程式导航 注意这里是通过name跳转,使用了params传参,就必须要用name进行跳转
      <button @click="$router.push({name:'food',params:{foodname:'qiezi',foodweight:1}})">跳转到food并且携带params参数</button>

    携带params参数后打印输出`food`的`this.$route`

    路由当中的meta

    我们可以看到,输出this.$route的时候,除了可以看到queryparams这二个经常使用的对象,还可以看到meta这个对象

    • 作用如下

    给每个路由添加一个自定义的meta对象,在meta对象中可以设置一些状态,来进行一些操作。经常用它来做登录校验

    设置meta直接在注册路由组件的时候设置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import Vue from "vue"
    import VueRouter from "vue-router"
    Vue.use(VueRouter);

    import Home from "@/views/Home";
    import Food from "@/views/Food"
    export default new VueRouter({
    routes:[
    {
    name:"food",
    path:"/food/:foodname/:foodweight",
    component:Food,
    //使用meta
    meta:{
    isShow:true
    }
    }
    ]
    })

    后面想要读取也很简单,就在当前路由组件输出this.$route.meta即可获取

    路由props配置对象的配置

    使用路由props配置对象,可以使得组件可以更加方便的接收参数

    props配置对象传入一个对象(死数据)
    • 只能映射传递额外的静态的数据,一般不用

    src\index.js配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    import Vue from "vue"
    import VueRouter from "vue-router"
    Vue.use(VueRouter);
    import Food from "@/views/Food"
    const router = new VueRouter({
    routes: [
    {
    name: "food",
    path:"/food",
    component: Food,
    //props为一个对象
    props:{
    name:"李白",
    sex:"男",
    age:'100'
    }
    }
    ]
    });
    export default router;

    Food.vue接收

    对象当中有什么字段,Food.vue就使用props配置对象接收什么字段

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <template>
    <div>
    <h1>我是Food</h1>
    </div>
    </template>
    <script>
    export default {
    name: "Food",
    //接收路由src\index.js为food配置的props参数
    props:["name","sex","age"],
    };
    </script>
    props配置对象传入一个布尔值
    • 把路径params参数映射为要显示的组件内属性

    • 布尔值为true,则把路由收到的所有params参数通过props传给对应组件

    src\index.js配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    import Vue from "vue"
    import VueRouter from "vue-router"
    Vue.use(VueRouter);
    import Food from "@/views/Food"
    const router = new VueRouter({
    routes: [
    {
    name: "food",
    path: "/food/:foodname?/:foodweight?",
    component: Food,
    //props为一个布尔值,且为true
    props:true
    }
    ]
    });
    export default router;

    Food.vue接收

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <template>
    <div>
    <h1>我是Food</h1>
    </div>
    </template>
    <script>
    export default {
    name: "Food",
    //接收food当中的params参数
    //字段必须要为路由当中占位的字段一样!!!
    props:["foodname","foodweight"],
    };
    </script>
    props配置对象传入一个函数
    • 自己手动映射params参数和query参数
    • 该函数返回的对象中的每一组key-value都会通过props传给对应组件

    src\index.js配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    import Vue from "vue"
    import VueRouter from "vue-router"
    Vue.use(VueRouter);
    import Food from "@/views/Food"
    const router = new VueRouter({
    routes: [
    {
    name: "food",
    path: "/food/:foodname?/:foodweight?",
    component: Food,
    //props为一个布尔值,且为true
    //$route为默认传入的一个参数(名字可以随意,这里取名$route)
    //之所以取这个是因为这个参数代表着当前路由组件
    //this.$route === $route
    props:($route){
    return {
    '蔬菜名称':$route.params.foodname,
    '蔬菜重量':$route.params.foodweight,
    '谁吃':$route.query.user
    }
    }
    }
    ]
    });
    export default router;

    Food.vue接收

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <template>
    <div>
    <h1>我是Food</h1>
    </div>
    </template>
    <script>
    export default {
    name: "Food",
    //字段必须要为路由当中返回的字段名称一样!!!
    props:["蔬菜名称","蔬菜重量","谁吃"],
    };
    </script>

    浏览器访问http://localhost:8080/#/food/vegetable/1005?user=李白

    浏览器访问后

    其他

    其他

    导航守卫

    • 全局前置守卫beforeEach:

      • 为什么是单词是Each,那是因为全局前置守卫需要对来往的每一个路由进行过滤,所以就是单词Each
      • 进入任意一个路由前,必须要经过全局前置守卫(进入学校,需要经过保安审批~)
    • 全局解析守卫beforeResolve

    • 全局后置钩子afterEach

    • 路由独享守卫beforeEnter

    • 组件内的守卫(不经常使用)

      • beforeRouteEnter
      • beforeRouteUpdate
      • beforeRouteLeave
    全局前置守卫

    要使用全局前置守卫,我们就需要使用到由VueRouter通过new生成的实例化对象,所以需要改改store\index.js,其他不用动

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    store\index.js
    //本来是
    //export default new VueRouter({...});

    //需要改为
    const router = new VueRouter({...});
    //全局前置守卫
    router.beforeEach((to,from,next)=>{
    ...
    })
    export default router;

    beforeEach回调参数

    • to: 是要去的某一个路由对象信息(目的地)
    • **from:**是来自的哪里的信息(起始点)
    • next控制跳转

    beforEach例子

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    router.beforeEach((to, from, next) => {
    //从sessionStorage获取token
    let token = sessionStorage.getItem('token');
    //如果目的地址不为'/login'并且token不存在
    if (to.path != '/login' && !token) {
    //跳转到'/login'
    next({
    path: '/login'
    })
    }
    //目的地址为'/login'并且token存在
    else {
    //如果目的地址为'/login'并且token存在
    if (to.path == '/login' && token) {
    //跳转到'/dashboard'
    next('/dashboard')
    }
    //目的地址不为为'/login'并且token存在
    else {
    next()
    }
    }
    })
    路由独享守卫

    前面的是全局前置守卫(也就是大老板router),这个是路由独享守卫,也就是属性每一个路由组件的,在进入路由组件之前进行判断是否符合条件,符合条件才让你走

    注意:

    beforeEnter 守卫 只在进入路由时触发,不会在 params、query 或 hash 改变时触发。例如,从 /users/2 进入到 /users/3 或者从 /users/2#info 进入到 /users/2#projects是不会触发的,它们只有在 从一个不同的 路由导航时,才会被触发

    基本使用

    1
    2
    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
    const router = new VueRouter({
    routes: [
    {
    path: "/home/:id?",
    component: Home
    },
    {
    path: "/",
    redirect: "/home"
    },
    {
    name: "food",
    path: "/food/:foodname?/:foodweight?",
    component: Food,
    meta: {
    isShow: true
    },
    //从http://localhost:8080/#/home?q=libai到http://localhost:8080/#/food
    //会自动帮我们分离参数到对应的路由对象当中,比如这个携带了query,就帮我们把参数写到了query对象里面,并且to.path依旧为/home
    //从http://localhost:8080/#/home/100到http://localhost:8080/#/food
    //params依旧也会分离参数到params对象里面,不过to.path不会改变,依旧为/home/100
    beforeEnter:(to,from,next)=>{
    if(from.path === '/home'){
    next();//放行
    }
    else{
    next("/home");
    }
    }
    }
    ]
    });
    注意点

    query参数携带在路径后面不会影响到to.path或者from.path

    params参数携带在路径后面会影响到to.path或者from.path

    1
    2
    3
    4
    http://localhost:8080/#/home?q=libai到http://localhost:8080/#/food
    会自动帮我们分离参数到对应的路由对象当中,比如这个携带了query,就帮我们把参数写到了query对象里面,并且to.path依旧为/home
    http://localhost:8080/#/home/100到http://localhost:8080/#/food
    params依旧也会分离参数到params对象里面,不过to.path不会改变,依旧为/home/100

    其他

    路由组件和非路由组件的最大区别

    生命周期钩子

    • 生命周期,就是组件从创建到销毁期间会自动执行的函数

    • 钩子函数

      • 在生命周期的过程中我们又很多特殊的时间段,我们希望在这些特殊的时间段对vue做一些事情,所以出现了钩子函数
      • 钩子函数就是作者在设计vue的时候,在vue从初始化到销毁这段时间内的特定时间段给我们一些定义函数的权利
      • 如果我们定义了钩子函数,就会执行,不定义就不会执行
    • 这个大佬绘制的图

    大佬绘制的图

    大佬说的太好了,我自己总结下自己~

    • data,methods,计算属性,event/watch事件回调created的时候就已经完成了初始化,但是dom树没有完成

    • 然后到了beforeMount 这里的dom树并不是真实的dom,而是虚拟的(所以你获取节点返回的是undefined)

      • 比如this.$refs.a ;//返回undefined
    • 然后到了mounted这里的dom已经生成,可以获取到dom节点了,可以对dom进行操作了

      • mounted 不会保证所有的子组件也都一起被挂载。如果你希望等到整个视图都渲染完毕,可以在 mounted 内部使用 vm.$nextTick
      • 发送请求获取数据在mounted生命周期当中发送,具体可以看看这篇文章
      • 理解可能有误,删除-至于为什么需要在mounted当中发送,而不再created发送,这个博主总结的很好,created的时候,虽然数据和事件依旧完成了初始化操作,但是假如因为网络延迟发送的数据直到mounted生命周期完成后(也就是DOM生成后)才获取到,那么有什么意义呢(相当于我建房子的时候你没给我砖块,我建完了你才给我砖块去填充,没意义啊)
      • 到底在哪里发送请求获取数据看了很多,说应该放在created当中,因为此时dom还没有渲染完成,这个时候发送数据就可以获取到数据用于构建dom,而说应该放mounted的说放在created当中会造成数据没有放回造成白屏,然后created的人又反驳说请求是异步的,怎么会白屏…还有人说created会产生分支,而mounted不会

      分支

      1
      2
      3
      4
      5
      6
      7
      //放在created
      created => API请求 => 获取数据 => 组件重新渲染

      => mounted => 组件首次渲染
      //放在mounted
      created => mounted => 组件首次渲染 => API请求 => 获取到数据 => 组件重新渲染

    再次学习总结

    • 我们知道,vue2的生命周期全部如下,我们重点关注前四个
    1
    2
    3
    4
    5
    6
    7
    8
    9
    beforeCreate 创建前
    created 创建后
    beforeMounted 挂载前
    mounted 挂载后

    beforeUpdate 更新前
    updated 更新后
    beforeDestroy 销毁前
    destroy 销毁后
    beforeCreate 创建前
    • 刚执行new的操作,其他什么都没有做
    created 创建后
    • 此时属性方法都绑定在了实例身上,但是依旧获取不到DOM
    beforeMount 挂载前
    • 数据还没有进行替换操作
      • 也就是{{ title }} 没有被替换为data当中的数据
    • 虚拟DOM存在了(但是注意,这是虚拟的DOM,所以不可以对节点进行操作)
      • 所以你获取节点返回的是undefined
    mounted 挂载后
    • 数据已经完成了替换操作
      • 也就是{{title }} 被替换为data当中的数据了
    • 虚拟DOM被替换为了真实的DOM(可以对节点进行DOM操作了)
      • 所以你获取节点返回的是节点信息了

    vue自定义指令

    自定义全局指令

    • Vue.directive("指令名称",回调函数)
    • 指令名称只能小写
    • 回调函数包含二个参数
      • 参数1: 为绑定指令的节点
      • 参数2 :为绑定指令的节点的相关信息

    示例,实现小写转化为大小

    1
    2
    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
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>自定义指令</title>
    </head>
    <body>
    <div id="app">
    <p v-text="msg"></p>
    <p v-upper="msg"></p>
    </div>
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.7.6/vue.js"></script>
    <script>
    //自定义全局指令
    Vue.directive("upper", function (el, bindings) {
    el.textContent = bindings.value.toUpperCase();
    });
    new Vue({
    el: "#app",
    data: {
    msg: "i love you~ zhao li ying~",
    },
    });
    </script>
    </body>
    </html>

    如图,输出回调函数的二个参数

    1
    2
    3
    Vue.directive("upper",function(el,bindings){
    console.log(el,bindings);
    })

    自定义局部指令

    • 和全局指令不同的是在配置对象当中设置directives即可
    1
    2
    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
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>自定义指令</title>
    </head>
    <body>
    <div id="app">
    <p v-text="msg"></p>
    <p v-upper="msg"></p>
    </div>
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.7.6/vue.js"></script>
    <script>
    new Vue({
    el: "#app",
    data: {
    msg: "i love you~ zhao li ying~",
    },
    // 自定义局部指令
    directives:{
    upper(el,bindings){
    el.textContent = bindings.value.toUpperCase();
    }
    }
    });
    </script>
    </body>
    </html>

    vue自定义过滤器

    示例效果图 上面为过滤前,下面为过滤后

    自定义全局过滤器

    • 通过Vue.filter("自定义名称",回调函数)
    • 自定义名称可以驼峰也可以全小写
    • 回调函数传入一个参数为过滤器前的值,通过return返回一个新值完成过滤
    1
    2
    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
    35
    36
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    </head>
    <body>
    <div id="app">
    <!-- 原始格式 -->
    <p>{{timeNow}}</p>
    <!-- 经过过滤器 -->
    <p>{{timeNow | timeFormat}}</p>
    </div>
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.7.6/vue.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/moment.js/2.29.1/moment.js"></script>
    <script>

    //定义全局过滤器
    Vue.filter("timeFormat",function(value){
    //传入毫秒数对其格式化后再返回
    return moment(value).format("YYYY-MM-DD hh:mm:ss");
    });

    //vue当中的过滤器可以理解成是为了让数据进一步的计算,得到最终的结果
    new Vue({
    el:'#app',
    data:{
    timeNow:Date.now()//当前时间 1970年1月1日0时分0秒到现在的毫秒数
    },

    })

    </script>
    </body>
    </html>

    自定义局部过滤器

    • 注意别漏掉了s
    1
    2
    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
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    </head>
    <body>
    <div id="app">
    <!-- 原始格式 -->
    <p>{{timeNow}}</p>
    <!-- 经过过滤器 -->
    <p>{{timeNow | timeFormat}}</p>
    </div>
    <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.7.6/vue.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/moment.js/2.29.1/moment.js"></script>
    <script>
    //vue当中的过滤器可以理解成是为了让数据进一步的计算,得到最终的结果
    new Vue({
    el:'#app',
    data:{
    timeNow:Date.now()//当前时间 1970年1月1日0时分0秒到现在的毫秒数
    },
    filters:{
    timeFormat(value){
    return moment(value).format("YYYY-MM-DD hh:mm:ss");
    }
    }
    })

    </script>
    </body>
    </html>

    mixin混入的基本使用

    • @官方API:混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。

    • 说通俗点就是混入就是将别人的东东变为自己的东东,这里的混入只说一些基本的使用

    • 先来看示例吧

    mixin/test.js文件的代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    const mixins = {
    data(){
    return {
    sex:"男",
    age:"18",
    }
    },
    methods:{
    sayHello(){
    console.log("你好,世界");
    }
    }
    };
    export default mixins;

    App.vue

    使用mixin/test.js的混入

    1
    2
    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
    <template>
    <div>
    <div>姓名:{{name}}</div>
    <div>年龄:{{sex}}</div>
    <div>性别:{{age}}</div>
    <button @click="sayHello">单击我 - 说hello</button>
    <button @click="sayThankyou">单击我 - 说谢谢</button>
    </div>
    </template>

    <script>
    // 引入混入
    import myMixin from "@/mixin/test";
    export default {
    name: "",
    //使用混入
    mixins:[myMixin],
    data() {
    return {
    name:"李白",
    }
    },
    methods:{
    sayThankyou(){
    console.log("谢谢你");
    }
    }

    };
    </script>

    功能测试是否正常,可以看到,可以正常显示和调用函数

    • 当然了,还有很多情况,比如说混入的时候,当mixin/test.js里面的数据或者方法和App.vue当中的数据或者方法冲突的时候要怎么解决之类的,具体看官网吧~ @官网

    其他一些问题

    v.for为什么要用key

    简单点说就是可以在后期dom发生变化的时候准确识别元素并更新,具体可以看看这位博主写的

    Vue.component第一个参数命名规则

    • 说通俗点就是,使用Vue.componet有多个单词,注册和使用都使用短横线!!!

    • 组件命名方式:驼峰式,短横线

    • 注册组件使用驼峰式命名时,使用的时候必须要使用短横线对驼峰进行分割

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <div id="app">
    <shop-center></shop-center>
    <!-- <shopCenter></shopCenter> 错误的 -->
    <div>
    Vue.component("shopCenter",{
    template:`
    <div>
    <div>
    <slot>
    <img src="https://www.dreamlove.top/img/favicon.png"/>
    </slot>
    </div>
    <div>
    <slot name="detail">
    <h1>手机名称</h1>
    <h2 style="color:gray">手机价格</h2>
    </slot>
    </div>
    </div>
    `,

    });
    • 注册组件使用短横命名时,使用的时候必须要使用短横线
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <div id="app">
    <shop-center></shop-center>
    <!-- <shopCenter></shopCenter> 错误的 -->
    </div>
    Vue.component("shop-center",{
    template:`
    <div>
    <div>
    <slot>
    <img src="https://www.dreamlove.top/img/favicon.png"/>
    </slot>
    </div>
    <div>
    <slot name="detail">
    <h1>手机名称</h1>
    <h2 style="color:gray">手机价格</h2>
    </slot>
    </div>
    </div>
    `,

    });
    • 驼峰和短横
    1
    2
    3
    4
    5
    6
    7
    8
    //驼峰式
    Vue.component('myComponent',{
    template:'<h4>组件命名</h4>'
    });
    //短横线
    Vue.component('my-component1',{
    template:'<h4>组件命名</h4>'
    });

    方法,和computed,watch的区别

    • 方法
      • 页面每次重新渲染都会重新执行,性能消耗大,除非不希望有缓存的时候用
    • computed
      • 是计算属性,依赖其他属性来计算值,并且computed的值具有缓存,只有当依赖的其他属性发生变化的时候才会重新计算
    • watch
      • 监听到值的变化就会执行回调,在回调中可以进行一些逻辑操作
    • 总结
      • 除非不希望有数据缓存,否者都不会用方法
      • 一般来说需要依赖别的数据来动态获取值的时候可以使用computed
      • 对于监听来说,需要做异步操作或开销比较大的时候可以用watch
    • 为什么computed不能异步操作?而watch却可以?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    export default {
    computed:{
    totalMoney(){
    setTimeout(()=>{
    //这个return的是内部的,不是外部的,所以不能异步操作
    return 100;
    },1000)
    }
    }
    }

    watch却可以

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    export default {
    watch:{
    arr:{
    handle(){
    setTimeout(()=>{
    //更新数据
    this.totalMoney = 1000;
    },1000)
    },
    deel:true,
    immediate:true,
    }
    }
    }

    数据代理

    • 什么是数据代理
      • 就是通过vm(vue的实例化对象来直接操作data当中的数据),而不用通过this._data来修改数据
      • 比如data当中有一个name属性,那么如果没有数据代理的话,我们需要操作就需要通过this._data.name属性来获取和修改
      • 微信小程序就是没有实现数据代理,每次我们读取里面的data,都需要通过this.data来进行读取
      • 这样子就简化了我们的操作
    • 数据代理的原理是什么
      • 原理就是通过defineProperty来给vm(vue实例对象)身上添加data当中所有的属性
      • 并设置gettersetter
      • 当获取的时候就调用getter方法
      • 当修改的时候就调用setter方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    data() {
    return {
    name: "李白",
    age: "18",
    sex: "男",
    };
    },
    created() {
    console.log(this);
    },

    输出查看this,可以看到,this里面有name,age,sex这些属性

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/cd8d332b.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    avatar
    梦洁
    小小的字,有大大的梦想~分享我的前端学习过程,经历,错误,和一些其他折腾过程
    关注下我(* ̄▽ ̄*)
    公告
    不断更新中,有问题请留言回复(会通过邮箱提醒~)
    目录
    1. 1. Vue的MVVM模型
      1. 1.1. 知道什么是MVVM
      2. 1.2. MVVM的关系
      3. 1.3. MVVM优点
      4. 1.4. 假如面试问到我说说看MVVM,我可能会这样子回答
    2. 2. Vue的绑定键盘鼠标操作和在组件使用上的要点
      1. 2.1. Vue的事件操作(v-on:click=”xxx”或@click)
      2. 2.2. Vue的按键操作的别名(这样子就不用通过判断键代码来获取用户是哪一个操作了)
      3. 2.3. 组件使用操作按钮注意点
    3. 3. Vue的数据绑定响应式
      1. 3.1. 如何追踪变化
      2. 3.2. 特别注意
        1. 3.2.1. 对于对象
        2. 3.2.2. 对于数组
    4. 4. Vue组件通信的六种方式
      1. 4.1. Props通信
        1. 4.1.1. props通信主要有
        2. 4.1.2. props的类型
        3. 4.1.3. props通信变量注意点
      2. 4.2. 自定义事件通信
        1. 4.2.1. 知道自定义事件和系统事件(原生事件)的区别
        2. 4.2.2. 绑定和触发自定义事件
          1. 4.2.2.1. 自定义事件复杂写法
          2. 4.2.2.2. 自定义事件的简单写法
          3. 4.2.2.3. 顺带一提
      3. 4.3. 全局事件总线通信
        1. 4.3.1. 全局事件总线使用
        2. 4.3.2. Vue.prototype.$bus = vm;为什么不能用,为什么一定要用beforeCreate钩子实现?(可能有误~)
          1. 4.3.2.1. 首先了解下Vue与VueComponent的关系
          2. 4.3.2.2. 原因解释
          3. 4.3.2.3. 至于为什么控制台console.log(可以输出查看到)
      4. 4.4. pubsub-js通信
        1. 4.4.1. 前置准备
        2. 4.4.2. 绑定事件的应用(可以立即为一颗定时炸弹)
        3. 4.4.3. 触发事件(可以理解为炸弹引爆器-告诉炸弹是否引爆)
        4. 4.4.4. 添加订阅(类似于$on)
        5. 4.4.5. 发布订阅(类似于$emit)
        6. 4.4.6. 效果
      5. 4.5. 插槽
        1. 4.5.1. 默认插槽
        2. 4.5.2. 具名插槽
        3. 4.5.3. 作用域插槽
      6. 4.6. vuex(不使用模块化)
        1. 4.6.1. 安装
        2. 4.6.2. 使用
        3. 4.6.3. 详细配置对象参数
        4. 4.6.4. 使用示例1(不使用模块化)
      7. 4.7. vuex(使用模块化)
        1. 4.7.0.1. 模块化之前输出this.$store
        2. 4.7.0.2. 模块化之后输出this.$store
        3. 4.7.0.3. 模块化需要注意的点
        4. 4.7.0.4. 使用命名空间(针对模块化)
    5. 4.8. vuex使用mapState和mapGetters
      1. 4.8.1. 使用mapState(不使用模块化下)
        1. 4.8.1.1. 结合对象展开符-不使用模块化下(常用)
      2. 4.8.2. 使用mapState(使用模块化下)
        1. 4.8.2.1. 结合对象展开符-使用模块化下(常用)
      3. 4.8.3. 使用mapGetters
        1. 4.8.3.1. 先来了解下getters
        2. 4.8.3.2. 使用mapGetters(用不用模块都是这个)
        3. 4.8.3.3. 使用mapGetters如果在命名空间下要怎么使用mapGetters
        4. 4.8.3.4. vue-admin-template当中明明使用了mapGetters,为什么不是对象的写法,而依旧是数组的写法
    6. 4.9. vuex其他一些要点
      1. 4.9.1. mutations不一定要大写,之前大写的因为这些变量是常量
      2. 4.9.2. 为什么要通过三步actions-mutations-state来修改数据?
      3. 4.9.3. mapState,mapActions,mapMutations位置说明
  • 5. 路由组件和非路由组件
    1. 5.1. 基本概念
    2. 5.2. 使用
    3. 5.3. 二级路由,三级路由,四级路由,,,n级路由
    4. 5.4. 路由链接和路由切换标签(就是单击后切换路由组件和显示路由组件)
    5. 5.5. 声明式导航和编程式导航
      1. 5.5.1. 使用编程式导航
    6. 5.6. 路由传参props和query和params
      1. 5.6.1. query方式传参
      2. 5.6.2. params方式传参
      3. 5.6.3. 路由当中的meta
    7. 5.7. 路由props配置对象的配置
      1. 5.7.1. props配置对象传入一个对象(死数据)
      2. 5.7.2. props配置对象传入一个布尔值
      3. 5.7.3. props配置对象传入一个函数
    8. 5.8. 导航守卫
      1. 5.8.1. 全局前置守卫
      2. 5.8.2. 路由独享守卫
      3. 5.8.3. 注意点
  • 6. 生命周期钩子
    1. 6.1. 大佬说的太好了,我自己总结下自己~
    2. 6.2. 再次学习总结
      1. 6.2.1. beforeCreate 创建前
      2. 6.2.2. created 创建后
      3. 6.2.3. beforeMount 挂载前
      4. 6.2.4. mounted 挂载后
  • 7. vue自定义指令
    1. 7.1. 自定义全局指令
    2. 7.2. 自定义局部指令
  • 8. vue自定义过滤器
    1. 8.1. 自定义全局过滤器
    2. 8.2. 自定义局部过滤器
  • 9. mixin混入的基本使用
  • 10. 其他一些问题
    1. 10.1. v.for为什么要用key
    2. 10.2. Vue.component第一个参数命名规则
    3. 10.3. 方法,和computed,watch的区别
    4. 10.4. 数据代理
  • 最新文章
    \ No newline at end of file diff --git a/ce8945f.html b/ce8945f.html new file mode 100644 index 000000000..83e49d2ca --- /dev/null +++ b/ce8945f.html @@ -0,0 +1 @@ +electron-键盘打字小猫吐花学习 | 梦洁小站-属于你我的小天地

    electron-键盘打字小猫吐花学习

    前言

    用到的库

    uiohook-napi

    • 监听键盘,nodejs专用

    一些关键

    图片设置无法拖动

    • ondragstart="r eturn false;" draggable="false"

    设置可拖动元素

    • 默认情况下, 无边框窗口是不可拖拽的。 应用程序需要在 CSS 中指定 -webkit-app-region: drag 来告诉 Electron 哪些区域是可拖拽的(如操作系统的标准标题栏)在可拖拽区域内部使用 -webkit-app-region: no-drag 则可以将其中部分区域排除。

      要使整个窗口可拖拽, 您可以添加 -webkit-app-region: drag 作为 body,html 的样式:

    1
    2
    3
    body,html {
    -webkit-app-region: drag;
    }

    父通信子

    • 主进程需要去调用渲染进程的方法

      • main.js
    1
    win?.webContents.send('toFlower')
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import Mouse1 from "./assets/闭嘴.png";
    import "./App.css"
    function App() {
    window.ipcRenderer.on("toFlower",() => {
    alert("调用了我");
    })
    return (
    <img src={Mouse1} className={'miao'} draggable={"false"}/>
    )
    }

    export default App

    窗口穿透

    • 原窗口其实是这样的,如果设置透明而不做其他处理,就会导致被占据的白色这一块下面的元素无法被点击,这种情况就是鼠标穿透的问题

    • 解决代码
      • preload.js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    const electron = require('@electron/remote')
    const win = electron.getCurrentWindow();
    // 设置指定区域的鼠标点击不穿透
    window.addEventListener("mousemove", event => {
    const el = document.getElementById("imgAvatar");
    let flag = event.target === el;
    if (!flag) {
    //允许穿透
    win.setIgnoreMouseEvents(true, {forward: true});
    } else {
    //不允许穿透
    win.setIgnoreMouseEvents(false);
    }
    });

    requestAnimationFrame

    • requestAnimationFrame是一个浏览器提供的用于执行动画的JavaScript方法。它允许我们按照浏览器的刷新率来调度动画帧,从而实现更加流畅和高效的动画效果。

    • 大多数电脑显示器的刷新频率是 60Hz,大概相当于每秒钟重绘 60 次。大多数浏览器都会对重绘操作加以限制,不超过显示器的重绘频率,因为即使超过那个频率用户体验也不会有提升。因此最平滑动画的最佳循环间隔是1000ms/60,约等于 16.6ms。

      在之前,为了实现动画,常常使用setTimeout()或setInterval()方法来定时更新动画帧。然而,这种方法并不理想,因为它们受限于定时器的精度和浏览器在不同设备上的性能差异。它们的内在运行机制决定了时间间隔参数实际上只是指定了把动画代码添加到浏览器 UI 线程队列中以等待执行的时间。如果队列前面已经加入了其他任务,那动画代码就要等前面的任务完成后再执行

      requestAnimationFrame的出现解决了这个问题。它使用浏览器的刷新率作为参考,确保动画帧的更新在每一帧之间的间隔是最佳的,从而实现更加流畅和自然的动画效果。

    • 具体看这位博主的,写的很好

      • https://zhuanlan.zhihu.com/p/600296111
      • window.requestAnimationFrame(callbackFn)默认只会执行一次,就是说cancelAnimationFrame基本上用不到,代码中的继续执行是使用递归的形式,持续调用才会继续执行,当距离左侧超过720像素时,不去递归调用了,就停下来了。不像setInterval需要注意清除的时机

    问题

    开启-webkit-app-region后可以右键了

    • 小猫开启-webkit-app-region:drag后,会发现可以右键了

    main.js添加下面代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //关闭因为css: -webkit-app-region: drag;   引起的默认鼠标右键菜单
    //这里是关闭这个鼠标右键功能
    win.hookWindowMessage(278,function(_){
    win?.setEnabled(false);//窗口禁用
    setTimeout(()=>{
    win?.setEnabled(true);//窗口启用
    },100);
    return true;
    })

    巧妙的点

    • 在body,html上设置了justify-content:end,align-items:end,display:flex,这样就可以让小猫在右下角集合,同时设置花朵起始坐标的时候,设置为负数,这样就可以将原本很难定位的猫嘴位置进行确定了

      • 因为如果你设置了猫右下角,如果花朵需要出现在猫嘴,就需要结合窗口高度,宽度来决定translate的偏移量,通过这种方法,只需要根据猫的大小来确定了
    • 顺带一提,transform:translate是根据原有位置的坐标来决定起始位置的

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/ce8945f.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/css/index.css b/css/index.css new file mode 100644 index 000000000..2451c7ffa --- /dev/null +++ b/css/index.css @@ -0,0 +1 @@ +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-webkit-text-size-adjust:100%}body{margin:0}main{display:block}h1{font-size:2em;margin:.67em 0}hr{box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}img{border-style:none}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details{display:block}summary{display:list-item}template{display:none}[hidden]{display:none}#article-container .flink .flink-item-desc,#article-container .flink .flink-item-name,#aside-content .card-archives ul.card-archive-list>.card-archive-list-item a span,#aside-content .card-categories ul.card-category-list>.card-category-list-item a span,#nav #blog-info,#pagination .next_info,#pagination .prev_info,#sidebar #sidebar-menus .menus_items .site-page,.limit-one-line,.site-data>a .headline{overflow:hidden;-o-text-overflow:ellipsis;text-overflow:ellipsis;white-space:nowrap}#article-container figure.gallery-group .gallery-group-name,#article-container figure.gallery-group p,#aside-content .aside-list>.aside-list-item .content>.comment,#aside-content .aside-list>.aside-list-item .content>.name,#aside-content .aside-list>.aside-list-item .content>.title,#post-info .post-title,#recent-posts>.recent-post-item>.recent-post-info>.article-title,#recent-posts>.recent-post-item>.recent-post-info>.content,.article-sort-item-title,.limit-more-line,.relatedPosts>.relatedPosts-list .content .title{display:-webkit-box;overflow:hidden;-webkit-box-orient:vertical}#post .post-copyright:before,#post .post-outdate-notice:before,.custom-hr:before,.fontawesomeIcon,.note:not(.no-icon)::before,.search-dialog hr:before{display:inline-block;font-weight:600;font-family:'Font Awesome 6 Free';text-rendering:auto;-webkit-font-smoothing:antialiased}#aside-content .card-widget,#recent-posts>.recent-post-item,.cardHover,.layout>.recent-posts .pagination>:not(.space),.layout>div:first-child:not(.recent-posts){border-radius:8px;background:var(--card-bg);-webkit-box-shadow:var(--card-box-shadow);box-shadow:var(--card-box-shadow);-webkit-transition:all .3s;-moz-transition:all .3s;-o-transition:all .3s;-ms-transition:all .3s;transition:all .3s}#aside-content .card-widget:hover,#recent-posts>.recent-post-item:hover,.cardHover:hover,.layout>.recent-posts .pagination>:not(.space):hover,.layout>div:first-child:not(.recent-posts):hover{-webkit-box-shadow:var(--card-hover-box-shadow);box-shadow:var(--card-hover-box-shadow)}#aside-content .aside-list>.aside-list-item .thumbnail :first-child,#recent-posts>.recent-post-item .post_cover .post-bg,.article-sort-item-img :first-child,.imgHover{width:100%;height:100%;-webkit-transition:filter 375ms ease-in .2s,-webkit-transform .6s;-moz-transition:filter 375ms ease-in .2s,-moz-transform .6s;-o-transition:filter 375ms ease-in .2s,-o-transform .6s;-ms-transition:filter 375ms ease-in .2s,-ms-transform .6s;transition:filter 375ms ease-in .2s,transform .6s;object-fit:cover}#aside-content .aside-list>.aside-list-item .thumbnail :first-child:hover,#recent-posts>.recent-post-item .post_cover .post-bg:hover,.article-sort-item-img :first-child:hover,.imgHover:hover{-webkit-transform:scale(1.1);-moz-transform:scale(1.1);-o-transform:scale(1.1);-ms-transform:scale(1.1);transform:scale(1.1)}#pagination .next-post:hover .cover,#pagination .prev-post:hover .cover,.postImgHover:hover .cover,.relatedPosts>.relatedPosts-list>div:hover .cover{opacity:.8;-webkit-transform:scale(1.1);-moz-transform:scale(1.1);-o-transform:scale(1.1);-ms-transform:scale(1.1);transform:scale(1.1)}#pagination .next-post .cover,#pagination .prev-post .cover,.postImgHover .cover,.relatedPosts>.relatedPosts-list>div .cover{position:absolute;width:100%;height:100%;opacity:.4;-webkit-transition:all .6s,filter 375ms ease-in .2s;-moz-transition:all .6s,filter 375ms ease-in .2s;-o-transition:all .6s,filter 375ms ease-in .2s;-ms-transition:all .6s,filter 375ms ease-in .2s;transition:all .6s,filter 375ms ease-in .2s;object-fit:cover}#algolia-search .search-dialog .ais-Hits-list,.category-lists ul,.list-beauty{list-style:none}#algolia-search .search-dialog .ais-Hits-list li,.category-lists ul li,.list-beauty li{position:relative;padding:.12em .4em .12em 1.4em}#algolia-search .search-dialog .ais-Hits-list li:hover:before,.category-lists ul li:hover:before,.list-beauty li:hover:before{border-color:var(--pseudo-hover)}#algolia-search .search-dialog .ais-Hits-list li:before,.category-lists ul li:before,.list-beauty li:before{position:absolute;top:.67em;left:0;width:.43em;height:.43em;border:.215em solid #49b1f5;border-radius:.43em;background:0 0;content:'';cursor:pointer;-webkit-transition:all .3s ease-out;-moz-transition:all .3s ease-out;-o-transition:all .3s ease-out;-ms-transition:all .3s ease-out;transition:all .3s ease-out}.custom-hr,.search-dialog hr{position:relative;margin:40px auto;border:2px dashed var(--hr-border);width:calc(100% - 4px)}.custom-hr:hover:before,.search-dialog hr:hover:before{left:calc(95% - 20px)}.custom-hr:before,.search-dialog hr:before{position:absolute;top:-10px;left:5%;z-index:1;color:var(--hr-before-color);content:'\f0c4';font-size:20px;line-height:1;-webkit-transition:all 1s ease-in-out;-moz-transition:all 1s ease-in-out;-o-transition:all 1s ease-in-out;-ms-transition:all 1s ease-in-out;transition:all 1s ease-in-out}#content-inner,#footer{-webkit-animation:bottom-top 1s;-moz-animation:bottom-top 1s;-o-animation:bottom-top 1s;-ms-animation:bottom-top 1s;animation:bottom-top 1s}#page-header:not(.full_page){-webkit-animation:header-effect 1s;-moz-animation:header-effect 1s;-o-animation:header-effect 1s;-ms-animation:header-effect 1s;animation:header-effect 1s}#site-subtitle,#site-title{-webkit-animation:titleScale 1s;-moz-animation:titleScale 1s;-o-animation:titleScale 1s;-ms-animation:titleScale 1s;animation:titleScale 1s}#nav.show{-webkit-animation:headerNoOpacity 1s;-moz-animation:headerNoOpacity 1s;-o-animation:headerNoOpacity 1s;-ms-animation:headerNoOpacity 1s;animation:headerNoOpacity 1s}#web_bg,canvas:not(#ribbon-canvas){-webkit-animation:to_show 4s;-moz-animation:to_show 4s;-o-animation:to_show 4s;-ms-animation:to_show 4s;animation:to_show 4s}#ribbon-canvas{-webkit-animation:ribbon_to_show 4s;-moz-animation:ribbon_to_show 4s;-o-animation:ribbon_to_show 4s;-ms-animation:ribbon_to_show 4s;animation:ribbon_to_show 4s}#sidebar-menus.open>:first-child{-webkit-animation:sidebarItem .2s;-moz-animation:sidebarItem .2s;-o-animation:sidebarItem .2s;-ms-animation:sidebarItem 0.2s;animation:sidebarItem .2s}#sidebar-menus.open>:nth-child(2){-webkit-animation:sidebarItem .4s;-moz-animation:sidebarItem .4s;-o-animation:sidebarItem .4s;-ms-animation:sidebarItem 0.4s;animation:sidebarItem .4s}#sidebar-menus.open>:nth-child(3){-webkit-animation:sidebarItem .6s;-moz-animation:sidebarItem .6s;-o-animation:sidebarItem .6s;-ms-animation:sidebarItem 0.6s;animation:sidebarItem .6s}#sidebar-menus.open>:nth-child(4){-webkit-animation:sidebarItem .8s;-moz-animation:sidebarItem .8s;-o-animation:sidebarItem .8s;-ms-animation:sidebarItem 0.8s;animation:sidebarItem .8s}.scroll-down-effects{-webkit-animation:scroll-down-effect 1.5s infinite;-moz-animation:scroll-down-effect 1.5s infinite;-o-animation:scroll-down-effect 1.5s infinite;-ms-animation:scroll-down-effect 1.5s infinite;animation:scroll-down-effect 1.5s infinite}.reward-main{-webkit-animation:donate_effcet .3s .1s ease both;-moz-animation:donate_effcet .3s .1s ease both;-o-animation:donate_effcet .3s .1s ease both;-ms-animation:donate_effcet 0.3s 0.1s ease both;animation:donate_effcet .3s .1s ease both}@-moz-keyframes scroll-down-effect{0%{opacity:.4;-webkit-transform:translate(0,0);-moz-transform:translate(0,0);-o-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}50%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:translate(0,-16px);-moz-transform:translate(0,-16px);-o-transform:translate(0,-16px);-ms-transform:translate(0,-16px);transform:translate(0,-16px)}100%{opacity:.4;-webkit-transform:translate(0,0);-moz-transform:translate(0,0);-o-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}}@-webkit-keyframes scroll-down-effect{0%{opacity:.4;-webkit-transform:translate(0,0);-moz-transform:translate(0,0);-o-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}50%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:translate(0,-16px);-moz-transform:translate(0,-16px);-o-transform:translate(0,-16px);-ms-transform:translate(0,-16px);transform:translate(0,-16px)}100%{opacity:.4;-webkit-transform:translate(0,0);-moz-transform:translate(0,0);-o-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}}@-o-keyframes scroll-down-effect{0%{opacity:.4;-webkit-transform:translate(0,0);-moz-transform:translate(0,0);-o-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}50%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:translate(0,-16px);-moz-transform:translate(0,-16px);-o-transform:translate(0,-16px);-ms-transform:translate(0,-16px);transform:translate(0,-16px)}100%{opacity:.4;-webkit-transform:translate(0,0);-moz-transform:translate(0,0);-o-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}}@keyframes scroll-down-effect{0%{opacity:.4;-webkit-transform:translate(0,0);-moz-transform:translate(0,0);-o-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}50%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:translate(0,-16px);-moz-transform:translate(0,-16px);-o-transform:translate(0,-16px);-ms-transform:translate(0,-16px);transform:translate(0,-16px)}100%{opacity:.4;-webkit-transform:translate(0,0);-moz-transform:translate(0,0);-o-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}}@-moz-keyframes header-effect{0%{opacity:0;-webkit-transform:translateY(-50px);-moz-transform:translateY(-50px);-o-transform:translateY(-50px);-ms-transform:translateY(-50px);transform:translateY(-50px)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes header-effect{0%{opacity:0;-webkit-transform:translateY(-50px);-moz-transform:translateY(-50px);-o-transform:translateY(-50px);-ms-transform:translateY(-50px);transform:translateY(-50px)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@-o-keyframes header-effect{0%{opacity:0;-webkit-transform:translateY(-50px);-moz-transform:translateY(-50px);-o-transform:translateY(-50px);-ms-transform:translateY(-50px);transform:translateY(-50px)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@keyframes header-effect{0%{opacity:0;-webkit-transform:translateY(-50px);-moz-transform:translateY(-50px);-o-transform:translateY(-50px);-ms-transform:translateY(-50px);transform:translateY(-50px)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@-moz-keyframes headerNoOpacity{0%{-webkit-transform:translateY(-50px);-moz-transform:translateY(-50px);-o-transform:translateY(-50px);-ms-transform:translateY(-50px);transform:translateY(-50px)}100%{-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes headerNoOpacity{0%{-webkit-transform:translateY(-50px);-moz-transform:translateY(-50px);-o-transform:translateY(-50px);-ms-transform:translateY(-50px);transform:translateY(-50px)}100%{-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@-o-keyframes headerNoOpacity{0%{-webkit-transform:translateY(-50px);-moz-transform:translateY(-50px);-o-transform:translateY(-50px);-ms-transform:translateY(-50px);transform:translateY(-50px)}100%{-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@keyframes headerNoOpacity{0%{-webkit-transform:translateY(-50px);-moz-transform:translateY(-50px);-o-transform:translateY(-50px);-ms-transform:translateY(-50px);transform:translateY(-50px)}100%{-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@-moz-keyframes bottom-top{0%{opacity:0;-webkit-transform:translateY(50px);-moz-transform:translateY(50px);-o-transform:translateY(50px);-ms-transform:translateY(50px);transform:translateY(50px)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes bottom-top{0%{opacity:0;-webkit-transform:translateY(50px);-moz-transform:translateY(50px);-o-transform:translateY(50px);-ms-transform:translateY(50px);transform:translateY(50px)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@-o-keyframes bottom-top{0%{opacity:0;-webkit-transform:translateY(50px);-moz-transform:translateY(50px);-o-transform:translateY(50px);-ms-transform:translateY(50px);transform:translateY(50px)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@keyframes bottom-top{0%{opacity:0;-webkit-transform:translateY(50px);-moz-transform:translateY(50px);-o-transform:translateY(50px);-ms-transform:translateY(50px);transform:translateY(50px)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@-moz-keyframes titleScale{0%{opacity:0;-webkit-transform:scale(.7);-moz-transform:scale(.7);-o-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}@-webkit-keyframes titleScale{0%{opacity:0;-webkit-transform:scale(.7);-moz-transform:scale(.7);-o-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}@-o-keyframes titleScale{0%{opacity:0;-webkit-transform:scale(.7);-moz-transform:scale(.7);-o-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}@keyframes titleScale{0%{opacity:0;-webkit-transform:scale(.7);-moz-transform:scale(.7);-o-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}@-moz-keyframes search_close{0%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}100%{opacity:0;-webkit-transform:scale(.7);-moz-transform:scale(.7);-o-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}}@-webkit-keyframes search_close{0%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}100%{opacity:0;-webkit-transform:scale(.7);-moz-transform:scale(.7);-o-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}}@-o-keyframes search_close{0%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}100%{opacity:0;-webkit-transform:scale(.7);-moz-transform:scale(.7);-o-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}}@keyframes search_close{0%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}100%{opacity:0;-webkit-transform:scale(.7);-moz-transform:scale(.7);-o-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}}@-moz-keyframes to_show{0%{opacity:0}100%{opacity:1;-ms-filter:none;filter:none}}@-webkit-keyframes to_show{0%{opacity:0}100%{opacity:1;-ms-filter:none;filter:none}}@-o-keyframes to_show{0%{opacity:0}100%{opacity:1;-ms-filter:none;filter:none}}@keyframes to_show{0%{opacity:0}100%{opacity:1;-ms-filter:none;filter:none}}@-moz-keyframes to_hide{0%{opacity:1;-ms-filter:none;filter:none}100%{opacity:0}}@-webkit-keyframes to_hide{0%{opacity:1;-ms-filter:none;filter:none}100%{opacity:0}}@-o-keyframes to_hide{0%{opacity:1;-ms-filter:none;filter:none}100%{opacity:0}}@keyframes to_hide{0%{opacity:1;-ms-filter:none;filter:none}100%{opacity:0}}@-moz-keyframes ribbon_to_show{0%{opacity:0}100%{opacity:.6}}@-webkit-keyframes ribbon_to_show{0%{opacity:0}100%{opacity:.6}}@-o-keyframes ribbon_to_show{0%{opacity:0}100%{opacity:.6}}@keyframes ribbon_to_show{0%{opacity:0}100%{opacity:.6}}@-moz-keyframes avatar_turn_around{from{-webkit-transform:rotate(0);-moz-transform:rotate(0);-o-transform:rotate(0);-ms-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);-moz-transform:rotate(360deg);-o-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg)}}@-webkit-keyframes avatar_turn_around{from{-webkit-transform:rotate(0);-moz-transform:rotate(0);-o-transform:rotate(0);-ms-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);-moz-transform:rotate(360deg);-o-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg)}}@-o-keyframes avatar_turn_around{from{-webkit-transform:rotate(0);-moz-transform:rotate(0);-o-transform:rotate(0);-ms-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);-moz-transform:rotate(360deg);-o-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg)}}@keyframes avatar_turn_around{from{-webkit-transform:rotate(0);-moz-transform:rotate(0);-o-transform:rotate(0);-ms-transform:rotate(0);transform:rotate(0)}to{-webkit-transform:rotate(360deg);-moz-transform:rotate(360deg);-o-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg)}}@-moz-keyframes sub_menus{0%{opacity:0;-webkit-transform:translateY(10px);-moz-transform:translateY(10px);-o-transform:translateY(10px);-ms-transform:translateY(10px);transform:translateY(10px)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes sub_menus{0%{opacity:0;-webkit-transform:translateY(10px);-moz-transform:translateY(10px);-o-transform:translateY(10px);-ms-transform:translateY(10px);transform:translateY(10px)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@-o-keyframes sub_menus{0%{opacity:0;-webkit-transform:translateY(10px);-moz-transform:translateY(10px);-o-transform:translateY(10px);-ms-transform:translateY(10px);transform:translateY(10px)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@keyframes sub_menus{0%{opacity:0;-webkit-transform:translateY(10px);-moz-transform:translateY(10px);-o-transform:translateY(10px);-ms-transform:translateY(10px);transform:translateY(10px)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@-moz-keyframes donate_effcet{0%{opacity:0;-webkit-transform:translateY(-20px);-moz-transform:translateY(-20px);-o-transform:translateY(-20px);-ms-transform:translateY(-20px);transform:translateY(-20px)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes donate_effcet{0%{opacity:0;-webkit-transform:translateY(-20px);-moz-transform:translateY(-20px);-o-transform:translateY(-20px);-ms-transform:translateY(-20px);transform:translateY(-20px)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@-o-keyframes donate_effcet{0%{opacity:0;-webkit-transform:translateY(-20px);-moz-transform:translateY(-20px);-o-transform:translateY(-20px);-ms-transform:translateY(-20px);transform:translateY(-20px)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@keyframes donate_effcet{0%{opacity:0;-webkit-transform:translateY(-20px);-moz-transform:translateY(-20px);-o-transform:translateY(-20px);-ms-transform:translateY(-20px);transform:translateY(-20px)}100%{opacity:1;-ms-filter:none;filter:none;-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@-moz-keyframes sidebarItem{0%{-webkit-transform:translateX(200px);-moz-transform:translateX(200px);-o-transform:translateX(200px);-ms-transform:translateX(200px);transform:translateX(200px)}100%{-webkit-transform:translateX(0);-moz-transform:translateX(0);-o-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}}@-webkit-keyframes sidebarItem{0%{-webkit-transform:translateX(200px);-moz-transform:translateX(200px);-o-transform:translateX(200px);-ms-transform:translateX(200px);transform:translateX(200px)}100%{-webkit-transform:translateX(0);-moz-transform:translateX(0);-o-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}}@-o-keyframes sidebarItem{0%{-webkit-transform:translateX(200px);-moz-transform:translateX(200px);-o-transform:translateX(200px);-ms-transform:translateX(200px);transform:translateX(200px)}100%{-webkit-transform:translateX(0);-moz-transform:translateX(0);-o-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}}@keyframes sidebarItem{0%{-webkit-transform:translateX(200px);-moz-transform:translateX(200px);-o-transform:translateX(200px);-ms-transform:translateX(200px);transform:translateX(200px)}100%{-webkit-transform:translateX(0);-moz-transform:translateX(0);-o-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}}:root{--global-font-size:14px;--global-bg:#fff;--font-color:#4c4948;--hr-border:#a4d8fa;--hr-before-color:#80c8f8;--search-bg:#f6f8fa;--search-input-color:#4c4948;--search-a-color:#4c4948;--preloader-bg:#37474f;--preloader-color:#fff;--tab-border-color:#f0f0f0;--tab-botton-bg:#f0f0f0;--tab-botton-color:#1f2d3d;--tab-button-hover-bg:#dcdcdc;--tab-button-active-bg:#fff;--card-bg:#fff;--sidebar-bg:#f6f8fa;--btn-hover-color:#ff7242;--btn-color:#fff;--btn-bg:#49b1f5;--text-bg-hover:rgba(73,177,245,0.7);--light-grey:#eee;--dark-grey:#cacaca;--white:#fff;--text-highlight-color:#1f2d3d;--blockquote-color:#6a737d;--blockquote-bg:rgba(73,177,245,0.1);--reward-pop:#f5f5f5;--toc-link-color:#666261;--card-box-shadow:0 3px 8px 6px rgba(7,17,27,0.05);--card-hover-box-shadow:0 3px 8px 6px rgba(7,17,27,0.09);--pseudo-hover:#ff7242;--headline-presudo:#a0a0a0;--scrollbar-color:#49b1f5;--default-bg-color:#49b1f5;--zoom-bg:#fff;--mark-bg:rgba(0,0,0,0.3)}body{position:relative;min-height:100%;background:var(--global-bg);color:var(--font-color);font-size:var(--global-font-size);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI','Helvetica Neue',Lato,Roboto,'PingFang SC','Microsoft YaHei',sans-serif;line-height:2;-webkit-tap-highlight-color:transparent}::-webkit-scrollbar{width:5px;height:5px}::-webkit-scrollbar-thumb{background:var(--scrollbar-color)}::-webkit-scrollbar-track{background-color:transparent}*{scrollbar-width:thin;scrollbar-color:var(--scrollbar-color) transparent}input::placeholder{color:var(--font-color)}h1,h2,h3,h4,h5,h6{position:relative;margin:20px 0 14px;color:var(--text-highlight-color);font-weight:700}h1 code,h2 code,h3 code,h4 code,h5 code,h6 code{font-size:inherit!important}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.table-wrap{overflow-x:scroll;margin:0 0 20px}table{display:table;width:100%;border-spacing:0;border-collapse:collapse;empty-cells:show}table thead{background:rgba(153,169,191,.1)}table td,table th{padding:6px 12px;border:1px solid var(--light-grey);vertical-align:middle}::selection{background:#00c4b6;color:#f7f7f7}button{padding:0;outline:0;border:none;background:0 0;cursor:pointer;touch-action:manipulation}a{color:#99a9bf;text-decoration:none;word-wrap:break-word;-webkit-transition:all .2s;-moz-transition:all .2s;-o-transition:all .2s;-ms-transition:all .2s;transition:all .2s;overflow-wrap:break-word}a:hover{color:#49b1f5}.is-center{text-align:center}.pull-left{float:left}.pull-right{float:right}img:not([src]),img[src='']{opacity:0}.img-alt{margin:-10px 0 10px;color:#858585}.img-alt:hover{text-decoration:none!important}blockquote{margin:0 0 20px;padding:12px 15px;border-left:3px solid #49b1f5;background-color:var(--blockquote-bg);color:var(--blockquote-color)}blockquote footer cite:before{padding:0 5px;content:'—'}blockquote>:last-child{margin-bottom:0!important}:root{--hl-color:#a6accd;--hl-bg:#292d3e;--hltools-bg:#252938;--hltools-color:#a6accd;--hlnumber-bg:#292d3e;--hlnumber-color:rgba(166,172,205,0.5);--hlscrollbar-bg:#393f57;--hlexpand-bg:linear-gradient(180deg, rgba(41,45,62,0.6), rgba(41,45,62,0.9))}[data-theme=dark]{--hl-color:rgba(255,255,255,0.7);--hl-bg:#171717;--hltools-bg:#1a1a1a;--hltools-color:#90a4ae;--hlnumber-bg:#171717;--hlnumber-color:rgba(255,255,255,0.4);--hlscrollbar-bg:#1f1f1f;--hlexpand-bg:linear-gradient(180deg, rgba(23,23,23,0.6), rgba(23,23,23,0.9))}figure.highlight table{scrollbar-color:var(--hlscrollbar-bg) transparent}figure.highlight table::-webkit-scrollbar-thumb{background:var(--hlscrollbar-bg)}figure.highlight pre .deletion{color:#bf42bf}figure.highlight pre .addition{color:#105ede}figure.highlight pre .meta{color:#c792ea}figure.highlight pre .comment{color:#676e95}figure.highlight pre .attribute,figure.highlight pre .css .class,figure.highlight pre .css .id,figure.highlight pre .css .pseudo,figure.highlight pre .html .doctype,figure.highlight pre .regexp,figure.highlight pre .ruby .constant,figure.highlight pre .tag .name,figure.highlight pre .variable,figure.highlight pre .xml .doctype,figure.highlight pre .xml .pi,figure.highlight pre .xml .tag .title{color:#ff5370}figure.highlight pre .tag{color:#89ddff}figure.highlight pre .command,figure.highlight pre .constant,figure.highlight pre .literal,figure.highlight pre .number,figure.highlight pre .params,figure.highlight pre .preprocessor{color:#f78c6c}figure.highlight pre .built_in{color:#ffcb6b}figure.highlight pre .css .rules .attribute,figure.highlight pre .formula,figure.highlight pre .header,figure.highlight pre .inheritance,figure.highlight pre .number,figure.highlight pre .ruby .class .title,figure.highlight pre .ruby .symbol,figure.highlight pre .special,figure.highlight pre .string,figure.highlight pre .value,figure.highlight pre .xml .cdata{color:#c3e88d}figure.highlight pre .css .hexcolor,figure.highlight pre .keyword,figure.highlight pre .title{color:#89ddff}figure.highlight pre .coffeescript .title,figure.highlight pre .function,figure.highlight pre .javascript .title,figure.highlight pre .perl .sub,figure.highlight pre .python .decorator,figure.highlight pre .python .title,figure.highlight pre .ruby .function .title,figure.highlight pre .ruby .title .keyword{color:#82aaff}figure.highlight pre .javascript .function,figure.highlight pre .tag .attr{color:#c792ea}#article-container figure.highlight .line.marked{background-color:rgba(113,124,180,.314)}#article-container figure.highlight table{display:block;overflow:auto;border:none}#article-container figure.highlight table td{padding:0;border:none}#article-container figure.highlight .gutter pre{padding-right:10px;padding-left:10px;background-color:var(--hlnumber-bg);color:var(--hlnumber-color);text-align:right}#article-container figure.highlight .code pre{padding-right:10px;padding-left:10px;width:100%}#article-container figure.highlight,#article-container pre{overflow:auto;margin:0 0 20px;padding:0;background:var(--hl-bg);color:var(--hl-color);line-height:1.6}#article-container code,#article-container pre{font-size:var(--global-font-size);font-family:consolas,Menlo,'PingFang SC','Microsoft YaHei',sans-serif!important}#article-container code{padding:2px 4px;background:rgba(27,31,35,.05);color:#f47466}#article-container pre{padding:10px 20px}#article-container pre code{padding:0;background:0 0;color:var(--hl-color);text-shadow:none}#article-container figure.highlight{position:relative}#article-container figure.highlight pre{margin:0;padding:8px 0;border:none}#article-container figure.highlight .caption,#article-container figure.highlight figcaption{padding:6px 0 2px 14px;font-size:var(--global-font-size);line-height:1em}#article-container figure.highlight .caption a,#article-container figure.highlight figcaption a{float:right;padding-right:10px;color:var(--hl-color)}#article-container figure.highlight .caption a:hover,#article-container figure.highlight figcaption a:hover{border-bottom-color:var(--hl-color)}#article-container figure.highlight.copy-true{-webkit-user-select:all;-moz-user-select:all;-ms-user-select:all;user-select:all;-webkit-user-select:all}#article-container figure.highlight.copy-true>pre,#article-container figure.highlight.copy-true>table{display:block!important;opacity:0}#article-container .highlight-tools{position:relative;display:-webkit-box;display:-moz-box;display:-webkit-flex;display:-ms-flexbox;display:box;display:flex;-webkit-box-align:center;-moz-box-align:center;-o-box-align:center;-ms-flex-align:center;-webkit-align-items:center;align-items:center;overflow:hidden;min-height:24px;height:2.15em;background:var(--hltools-bg);color:var(--hltools-color);font-size:var(--global-font-size)}#article-container .highlight-tools.closed~*{display:none}#article-container .highlight-tools.closed .expand{-webkit-transition:all .3s;-moz-transition:all .3s;-o-transition:all .3s;-ms-transition:all .3s;transition:all .3s;-webkit-transform:rotate(-90deg)!important;-moz-transform:rotate(-90deg)!important;-o-transform:rotate(-90deg)!important;-ms-transform:rotate(-90deg)!important;transform:rotate(-90deg)!important}#article-container .highlight-tools .expand{position:absolute;padding:.57em .7em;cursor:pointer;-webkit-transition:-webkit-transform .3s;-moz-transition:-moz-transform .3s;-o-transition:-o-transform .3s;-ms-transition:-ms-transform .3s;transition:transform .3s}#article-container .highlight-tools .expand+.code-lang{left:1.7em}#article-container .highlight-tools .code-lang{position:absolute;left:14px;text-transform:uppercase;font-weight:700;font-size:1.15em;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-user-select:none}#article-container .highlight-tools .copy-notice{position:absolute;right:2.4em;opacity:0;-webkit-transition:opacity .4s;-moz-transition:opacity .4s;-o-transition:opacity .4s;-ms-transition:opacity .4s;transition:opacity .4s}#article-container .highlight-tools .copy-button{position:absolute;right:14px;cursor:pointer;-webkit-transition:color .2s;-moz-transition:color .2s;-o-transition:color .2s;-ms-transition:color .2s;transition:color .2s}#article-container .highlight-tools .copy-button:hover{color:#49b1f5}#article-container .gutter{-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-user-select:none}#article-container .gist table{width:auto}#article-container .gist table td{border:none}@-moz-keyframes code-expand-key{0%{opacity:.6}50%{opacity:.1}100%{opacity:.6}}@-webkit-keyframes code-expand-key{0%{opacity:.6}50%{opacity:.1}100%{opacity:.6}}@-o-keyframes code-expand-key{0%{opacity:.6}50%{opacity:.1}100%{opacity:.6}}@keyframes code-expand-key{0%{opacity:.6}50%{opacity:.1}100%{opacity:.6}}.article-sort{margin-left:10px;padding-left:20px;border-left:2px solid #aadafa}.article-sort-title{position:relative;margin-left:10px;padding-bottom:20px;padding-left:20px;font-size:1.72em}.article-sort-title:hover:before{border-color:var(--pseudo-hover)}.article-sort-title:before{position:absolute;top:calc(((100% - 36px)/ 2));left:-9px;z-index:1;width:10px;height:10px;border:5px solid #49b1f5;border-radius:10px;background:var(--card-bg);content:'';line-height:10px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;-ms-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.article-sort-title:after{position:absolute;bottom:0;left:0;z-index:0;width:2px;height:1.5em;background:#aadafa;content:''}.article-sort-item{position:relative;display:-webkit-box;display:-moz-box;display:-webkit-flex;display:-ms-flexbox;display:box;display:flex;-webkit-box-align:center;-moz-box-align:center;-o-box-align:center;-ms-flex-align:center;-webkit-align-items:center;align-items:center;margin:0 0 20px 10px;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;-ms-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.article-sort-item:hover:before{border-color:var(--pseudo-hover)}.article-sort-item:before{position:absolute;left:calc(-20px - 17px);width:6px;height:6px;border:3px solid #49b1f5;border-radius:6px;background:var(--card-bg);content:'';-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;-ms-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.article-sort-item.no-article-cover{height:80px}.article-sort-item.no-article-cover .article-sort-item-info{padding:0}.article-sort-item.year{font-size:1.43em}.article-sort-item.year:hover:before{border-color:#49b1f5}.article-sort-item.year:before{border-color:var(--pseudo-hover)}.article-sort-item-time{color:#858585;font-size:95%}.article-sort-item-time time{padding-left:6px;cursor:default}.article-sort-item-title{color:var(--font-color);font-size:1.1em;-webkit-transition:all .3s;-moz-transition:all .3s;-o-transition:all .3s;-ms-transition:all .3s;transition:all .3s;-webkit-line-clamp:2}.article-sort-item-title:hover{color:#49b1f5;-webkit-transform:translateX(10px);-moz-transform:translateX(10px);-o-transform:translateX(10px);-ms-transform:translateX(10px);transform:translateX(10px)}.article-sort-item-img{overflow:hidden;width:80px;height:80px}.article-sort-item-info{-webkit-box-flex:1;-moz-box-flex:1;-o-box-flex:1;box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;padding:0 16px}.category-lists .category-title{font-size:2.57em}@media screen and (max-width:768px){.category-lists .category-title{font-size:2em}}.category-lists .category-list{margin-bottom:0}.category-lists .category-list a{color:var(--font-color)}.category-lists .category-list a:hover{color:#49b1f5}.category-lists .category-list .category-list-count{margin-left:8px;color:#858585}.category-lists .category-list .category-list-count:before{content:'('}.category-lists .category-list .category-list-count:after{content:')'}.category-lists ul{padding:0 0 0 20px}.category-lists ul ul{padding-left:4px}.category-lists ul li{position:relative;margin:6px 0;padding:.12em .4em .12em 1.4em}#body-wrap{display:-webkit-box;display:-moz-box;display:-webkit-flex;display:-ms-flexbox;display:box;display:flex;-webkit-box-orient:vertical;-moz-box-orient:vertical;-o-box-orient:vertical;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;min-height:100vh}.layout{display:-webkit-box;display:-moz-box;display:-webkit-flex;display:-ms-flexbox;display:box;display:flex;-webkit-box-flex:1;-moz-box-flex:1;-o-box-flex:1;box-flex:1;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;margin:0 auto;padding:40px 15px;max-width:1200px;width:100%}@media screen and (max-width:900px){.layout{-webkit-box-orient:vertical;-moz-box-orient:vertical;-o-box-orient:vertical;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}}@media screen and (max-width:768px){.layout{padding:20px 5px}}@media screen and (min-width:2000px){.layout{max-width:70%}}.layout>div:first-child:not(.recent-posts){-webkit-align-self:flex-start;align-self:flex-start;-ms-flex-item-align:start;padding:50px 40px}@media screen and (max-width:768px){.layout>div:first-child:not(.recent-posts){padding:36px 14px}}.layout>div:first-child{width:74%;-webkit-transition:all .3s;-moz-transition:all .3s;-o-transition:all .3s;-ms-transition:all .3s;transition:all .3s}@media screen and (max-width:900px){.layout>div:first-child{width:100%!important}}.layout.hide-aside{max-width:1000px}@media screen and (min-width:2000px){.layout.hide-aside{max-width:1300px}}.layout.hide-aside>div{width:100%!important}.apple #page-header.full_page{background-attachment:scroll!important}.apple .avatar-img,.apple .flink-item-icon,.apple .recent-post-item{-webkit-transform:translateZ(0);-moz-transform:translateZ(0);-o-transform:translateZ(0);-ms-transform:translateZ(0);transform:translateZ(0)}#article-container .flink{margin-bottom:20px}#article-container .flink .flink-list{overflow:auto;padding:10px 10px 0;text-align:center}#article-container .flink .flink-list>.flink-list-item{position:relative;float:left;overflow:hidden;margin:15px 7px;width:calc(100% / 3 - 15px);height:90px;border-radius:8px;line-height:17px;-webkit-transform:translateZ(0)}@media screen and (max-width:1024px){#article-container .flink .flink-list>.flink-list-item{width:calc(50% - 15px)!important}}@media screen and (max-width:600px){#article-container .flink .flink-list>.flink-list-item{width:calc(100% - 15px)!important}}#article-container .flink .flink-list>.flink-list-item:hover .flink-item-icon{margin-left:-10px;width:0}#article-container .flink .flink-list>.flink-list-item:before{position:absolute;top:0;right:0;bottom:0;left:0;z-index:-1;background:var(--text-bg-hover);content:'';-webkit-transition:-webkit-transform .3s ease-out;-moz-transition:-moz-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;-ms-transition:-ms-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:scale(0);-moz-transform:scale(0);-o-transform:scale(0);-ms-transform:scale(0);transform:scale(0)}#article-container .flink .flink-list>.flink-list-item:active:before,#article-container .flink .flink-list>.flink-list-item:focus:before,#article-container .flink .flink-list>.flink-list-item:hover:before{-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}#article-container .flink .flink-list>.flink-list-item a{color:var(--font-color);text-decoration:none}#article-container .flink .flink-list>.flink-list-item a .flink-item-icon{float:left;overflow:hidden;margin:15px 10px;width:60px;height:60px;border-radius:35px;-webkit-transition:width .3s ease-out;-moz-transition:width .3s ease-out;-o-transition:width .3s ease-out;-ms-transition:width .3s ease-out;transition:width .3s ease-out}#article-container .flink .flink-list>.flink-list-item a .flink-item-icon img{width:100%;height:100%;-webkit-transition:filter 375ms ease-in .2s,-webkit-transform .3s;-moz-transition:filter 375ms ease-in .2s,-moz-transform .3s;-o-transition:filter 375ms ease-in .2s,-o-transform .3s;-ms-transition:filter 375ms ease-in .2s,-ms-transform .3s;transition:filter 375ms ease-in .2s,transform .3s;object-fit:cover}#article-container .flink .flink-list>.flink-list-item a .img-alt{display:none}#article-container .flink .flink-item-name{padding:16px 10px 0 0;height:40px;font-weight:700;font-size:1.43em}#article-container .flink .flink-item-desc{padding:16px 10px 16px 0;height:50px;font-size:.93em}#article-container .flink .flink-name{margin-bottom:5px;font-weight:700;font-size:1.5em}#recent-posts>.recent-post-item:not(:first-child){margin-top:20px}#recent-posts>.recent-post-item{display:-webkit-box;display:-moz-box;display:-webkit-flex;display:-ms-flexbox;display:box;display:flex;-webkit-box-orient:horizontal;-moz-box-orient:horizontal;-o-box-orient:horizontal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;-webkit-box-align:center;-moz-box-align:center;-o-box-align:center;-ms-flex-align:center;-webkit-align-items:center;align-items:center;overflow:hidden;height:16.8em}@media screen and (max-width:768px){#recent-posts>.recent-post-item{-webkit-box-orient:vertical;-moz-box-orient:vertical;-o-box-orient:vertical;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column;height:auto}}#recent-posts>.recent-post-item:hover img.post-bg{-webkit-transform:scale(1.1);-moz-transform:scale(1.1);-o-transform:scale(1.1);-ms-transform:scale(1.1);transform:scale(1.1)}#recent-posts>.recent-post-item.ads-wrap{display:block!important;height:auto!important}#recent-posts>.recent-post-item .post_cover{overflow:hidden;width:42%;height:100%}@media screen and (max-width:768px){#recent-posts>.recent-post-item .post_cover{width:100%;height:230px}}#recent-posts>.recent-post-item .post_cover.right{-webkit-box-ordinal-group:1;-moz-box-ordinal-group:1;-o-box-ordinal-group:1;-ms-flex-order:1;-webkit-order:1;order:1}@media screen and (max-width:768px){#recent-posts>.recent-post-item .post_cover.right{-webkit-box-ordinal-group:0;-moz-box-ordinal-group:0;-o-box-ordinal-group:0;-ms-flex-order:0;-webkit-order:0;order:0}}#recent-posts>.recent-post-item>.recent-post-info{padding:0 40px;width:58%}@media screen and (max-width:768px){#recent-posts>.recent-post-item>.recent-post-info{padding:20px 20px 30px;width:100%}}#recent-posts>.recent-post-item>.recent-post-info.no-cover{width:100%}@media screen and (max-width:768px){#recent-posts>.recent-post-item>.recent-post-info.no-cover{padding:30px 20px}}#recent-posts>.recent-post-item>.recent-post-info>.article-title{color:var(--text-highlight-color);font-size:1.55em;line-height:1.4;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;-ms-transition:all .2s ease-in-out;transition:all .2s ease-in-out;-webkit-line-clamp:2}#recent-posts>.recent-post-item>.recent-post-info>.article-title .sticky{margin-right:10px;color:#ff7242;-webkit-transform:rotate(45deg);-moz-transform:rotate(45deg);-o-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg)}@media screen and (max-width:768px){#recent-posts>.recent-post-item>.recent-post-info>.article-title{font-size:1.43em}}#recent-posts>.recent-post-item>.recent-post-info>.article-title:hover{color:#49b1f5}#recent-posts>.recent-post-item>.recent-post-info>.article-meta-wrap{margin:6px 0;color:#858585;font-size:.9em}#recent-posts>.recent-post-item>.recent-post-info>.article-meta-wrap>.post-meta-date{cursor:default}#recent-posts>.recent-post-item>.recent-post-info>.article-meta-wrap i{margin:0 4px 0 0}#recent-posts>.recent-post-item>.recent-post-info>.article-meta-wrap .fa-spinner{margin:0}#recent-posts>.recent-post-item>.recent-post-info>.article-meta-wrap .article-meta-label{padding-right:4px}#recent-posts>.recent-post-item>.recent-post-info>.article-meta-wrap .article-meta-separator{margin:0 6px}#recent-posts>.recent-post-item>.recent-post-info>.article-meta-wrap .article-meta-link{margin:0 4px}#recent-posts>.recent-post-item>.recent-post-info>.article-meta-wrap a{color:#858585}#recent-posts>.recent-post-item>.recent-post-info>.article-meta-wrap a:hover{color:#49b1f5;text-decoration:underline}#recent-posts>.recent-post-item>.recent-post-info>.content{-webkit-line-clamp:2}.tag-cloud-list a{display:inline-block;padding:0 8px;-webkit-transition:all .3s;-moz-transition:all .3s;-o-transition:all .3s;-ms-transition:all .3s;transition:all .3s}.tag-cloud-list a:hover{color:#49b1f5!important;-webkit-transform:scale(1.1);-moz-transform:scale(1.1);-o-transform:scale(1.1);-ms-transform:scale(1.1);transform:scale(1.1)}@media screen and (max-width:768px){.tag-cloud-list a{zoom:0.85}}.tag-cloud-title{font-size:2.57em}@media screen and (max-width:768px){.tag-cloud-title{font-size:2em}}h1.page-title+.tag-cloud-list{text-align:left}#aside-content{width:26%}@media screen and (min-width:900px){#aside-content{padding-left:15px}}@media screen and (max-width:900px){#aside-content{width:100%}}#aside-content>.card-widget:first-child{margin-top:0}@media screen and (max-width:900px){#aside-content>.card-widget:first-child{margin-top:20px}}#aside-content .card-widget{position:relative;overflow:hidden;margin-top:20px;padding:20px 24px}#aside-content .card-info .author-info__name{font-weight:500;font-size:1.57em}#aside-content .card-info .author-info__description{margin-top:-.42em}#aside-content .card-info .card-info-data{margin:14px 0 4px}#aside-content .card-info .card-info-social-icons{margin:6px 0 -6px}#aside-content .card-info .card-info-social-icons .social-icon{margin:0 10px;color:var(--font-color);font-size:1.4em}#aside-content .card-info .card-info-social-icons i{-webkit-transition:all .3s;-moz-transition:all .3s;-o-transition:all .3s;-ms-transition:all .3s;transition:all .3s}#aside-content .card-info .card-info-social-icons i:hover{-webkit-transform:rotate(360deg);-moz-transform:rotate(360deg);-o-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg)}#aside-content .card-info #card-info-btn{display:block;margin-top:14px;background-color:var(--btn-bg);color:var(--btn-color);text-align:center;line-height:2.4}#aside-content .card-info #card-info-btn:hover{background-color:var(--btn-hover-color)}#aside-content .card-info #card-info-btn span{padding-left:10px}#aside-content .item-headline{padding-bottom:6px;font-size:1.2em}#aside-content .item-headline span{margin-left:6px}@media screen and (min-width:900px){#aside-content .sticky_layout{position:sticky;position:-webkit-sticky;top:20px;-webkit-transition:top .3s;-moz-transition:top .3s;-o-transition:top .3s;-ms-transition:top .3s;transition:top .3s}}#aside-content .card-tag-cloud a{display:inline-block;padding:0 4px}#aside-content .card-tag-cloud a:hover{color:#49b1f5!important}#aside-content .aside-list>span{display:block;margin-bottom:10px;text-align:center}#aside-content .aside-list>.aside-list-item{display:-webkit-box;display:-moz-box;display:-webkit-flex;display:-ms-flexbox;display:box;display:flex;-webkit-box-align:center;-moz-box-align:center;-o-box-align:center;-ms-flex-align:center;-webkit-align-items:center;align-items:center;padding:6px 0}#aside-content .aside-list>.aside-list-item:first-child{padding-top:0}#aside-content .aside-list>.aside-list-item:not(:last-child){border-bottom:1px dashed #f5f5f5}#aside-content .aside-list>.aside-list-item:last-child{padding-bottom:0}#aside-content .aside-list>.aside-list-item .thumbnail{overflow:hidden;width:4.2em;height:4.2em}#aside-content .aside-list>.aside-list-item .content{-webkit-box-flex:1;-moz-box-flex:1;-o-box-flex:1;box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;padding-left:10px;word-break:break-all}#aside-content .aside-list>.aside-list-item .content>.name{-webkit-line-clamp:1}#aside-content .aside-list>.aside-list-item .content>.name,#aside-content .aside-list>.aside-list-item .content>time{display:block;color:#858585;font-size:85%}#aside-content .aside-list>.aside-list-item .content>.comment,#aside-content .aside-list>.aside-list-item .content>.title{color:var(--font-color);font-size:95%;line-height:1.5;-webkit-line-clamp:2}#aside-content .aside-list>.aside-list-item .content>.comment:hover,#aside-content .aside-list>.aside-list-item .content>.title:hover{color:#49b1f5}#aside-content .aside-list>.aside-list-item.no-cover{min-height:4.4em}#aside-content .card-archives ul.card-archive-list,#aside-content .card-categories ul.card-category-list{margin:0;padding:0;list-style:none}#aside-content .card-archives ul.card-archive-list>.card-archive-list-item a,#aside-content .card-categories ul.card-category-list>.card-category-list-item a{display:-webkit-box;display:-moz-box;display:-webkit-flex;display:-ms-flexbox;display:box;display:flex;-webkit-box-orient:horizontal;-moz-box-orient:horizontal;-o-box-orient:horizontal;-webkit-flex-direction:row;-ms-flex-direction:row;flex-direction:row;padding:3px 10px;color:var(--font-color);-webkit-transition:all .4s;-moz-transition:all .4s;-o-transition:all .4s;-ms-transition:all .4s;transition:all .4s}#aside-content .card-archives ul.card-archive-list>.card-archive-list-item a:hover,#aside-content .card-categories ul.card-category-list>.card-category-list-item a:hover{padding:3px 17px;background-color:var(--text-bg-hover)}#aside-content .card-archives ul.card-archive-list>.card-archive-list-item a span:first-child,#aside-content .card-categories ul.card-category-list>.card-category-list-item a span:first-child{-webkit-box-flex:1;-moz-box-flex:1;-o-box-flex:1;box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1}#aside-content .card-categories .card-category-list.child{padding:0 0 0 16px}#aside-content .card-categories .card-category-list>.parent>a.expand i{-webkit-transform:rotate(-90deg);-moz-transform:rotate(-90deg);-o-transform:rotate(-90deg);-ms-transform:rotate(-90deg);transform:rotate(-90deg)}#aside-content .card-categories .card-category-list>.parent>a.expand+.child{display:block}#aside-content .card-categories .card-category-list>.parent>a .card-category-list-name{width:70%!important}#aside-content .card-categories .card-category-list>.parent>a .card-category-list-count{width:calc(100% - 70% - 20px);text-align:right}#aside-content .card-categories .card-category-list>.parent>a i{float:right;margin-right:-.5em;padding:.5em;-webkit-transition:-webkit-transform .3s;-moz-transition:-moz-transform .3s;-o-transition:-o-transform .3s;-ms-transition:-ms-transform .3s;transition:transform .3s;-webkit-transform:rotate(0);-moz-transform:rotate(0);-o-transform:rotate(0);-ms-transform:rotate(0);transform:rotate(0)}#aside-content .card-webinfo .webinfo .webinfo-item{display:-webkit-box;display:-moz-box;display:-webkit-flex;display:-ms-flexbox;display:box;display:flex;-webkit-box-align:center;-moz-box-align:center;-o-box-align:center;-ms-flex-align:center;-webkit-align-items:center;align-items:center;padding:2px 10px 0}#aside-content .card-webinfo .webinfo .webinfo-item div:first-child{-webkit-box-flex:1;-moz-box-flex:1;-o-box-flex:1;box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;padding-right:20px}@media screen and (min-width:901px){#aside-content #card-toc{right:0!important}}@media screen and (max-width:900px){#aside-content #card-toc{position:fixed;right:55px;bottom:30px;z-index:100;max-width:380px;max-height:calc(100% - 60px);width:calc(100% - 80px);-webkit-transition:none;-moz-transition:none;-o-transition:none;-ms-transition:none;transition:none;-webkit-transform:scale(0);-moz-transform:scale(0);-o-transform:scale(0);-ms-transform:scale(0);transform:scale(0);-webkit-transform-origin:right bottom;-moz-transform-origin:right bottom;-o-transform-origin:right bottom;-ms-transform-origin:right bottom;transform-origin:right bottom}#aside-content #card-toc.open{-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}#aside-content #card-toc .toc-percentage{float:right;margin-top:-9px;color:#a9a9a9;font-style:italic;font-size:140%}#aside-content #card-toc .toc-content{overflow-y:scroll;overflow-y:overlay;margin:0 -24px;max-height:calc(100vh - 120px);width:calc(100% + 48px)}@media screen and (max-width:900px){#aside-content #card-toc .toc-content{max-height:calc(100vh - 140px)}}#aside-content #card-toc .toc-content>*{margin:0 20px!important}#aside-content #card-toc .toc-content>*>.toc-item>.toc-child{margin-left:10px;padding-left:10px;border-left:1px solid var(--dark-grey)}#aside-content #card-toc .toc-content:not(.is-expand) .toc-child{display:none}@media screen and (max-width:900px){#aside-content #card-toc .toc-content:not(.is-expand) .toc-child{display:block!important}}#aside-content #card-toc .toc-content:not(.is-expand) .toc-item.active .toc-child{display:block}#aside-content #card-toc .toc-content li,#aside-content #card-toc .toc-content ol{list-style:none}#aside-content #card-toc .toc-content>ol{padding:0!important}#aside-content #card-toc .toc-content ol{margin:0;padding-left:18px}#aside-content #card-toc .toc-content .toc-link{display:block;margin:4px 0;padding:1px 6px;color:var(--toc-link-color);-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;-ms-transition:all .2s ease-in-out;transition:all .2s ease-in-out}#aside-content #card-toc .toc-content .toc-link:hover{color:#49b1f5}#aside-content #card-toc .toc-content .toc-link.active{background:#00c4b6;color:#fff}#aside-content .sticky_layout:only-child>:first-child{margin-top:0}#aside-content .card-more-btn{float:right;color:inherit}#aside-content .card-more-btn:hover{-webkit-animation:more-btn-move 1s infinite;-moz-animation:more-btn-move 1s infinite;-o-animation:more-btn-move 1s infinite;-ms-animation:more-btn-move 1s infinite;animation:more-btn-move 1s infinite}#aside-content .card-announcement .item-headline i{color:red}.avatar-img{overflow:hidden;margin:0 auto;width:110px;height:110px;border-radius:70px}.avatar-img img{width:100%;height:100%;-webkit-transition:filter 375ms ease-in .2s,-webkit-transform .3s;-moz-transition:filter 375ms ease-in .2s,-moz-transform .3s;-o-transition:filter 375ms ease-in .2s,-o-transform .3s;-ms-transition:filter 375ms ease-in .2s,-ms-transform .3s;transition:filter 375ms ease-in .2s,transform .3s;object-fit:cover}.avatar-img img:hover{-webkit-transform:rotate(360deg);-moz-transform:rotate(360deg);-o-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg)}.site-data{display:table;width:100%;table-layout:fixed}.site-data>a{display:table-cell}.site-data>a div{-webkit-transition:all .3s;-moz-transition:all .3s;-o-transition:all .3s;-ms-transition:all .3s;transition:all .3s}.site-data>a:hover div{color:#49b1f5!important}.site-data>a .headline{color:var(--font-color)}.site-data>a .length-num{margin-top:-.32em;color:var(--text-highlight-color);font-size:1.4em}@media screen and (min-width:900px){html.hide-aside .layout{-webkit-box-pack:center;-moz-box-pack:center;-o-box-pack:center;-ms-flex-pack:center;-webkit-justify-content:center;justify-content:center}html.hide-aside .layout>.aside-content{display:none}html.hide-aside .layout>div:first-child{width:80%}}.page .sticky_layout{display:-webkit-box;display:-moz-box;display:-webkit-flex;display:-ms-flexbox;display:box;display:flex;-webkit-box-orient:vertical;-moz-box-orient:vertical;-o-box-orient:vertical;-webkit-flex-direction:column;-ms-flex-direction:column;flex-direction:column}@-moz-keyframes more-btn-move{0%,100%{-webkit-transform:translateX(0);-moz-transform:translateX(0);-o-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}50%{-webkit-transform:translateX(3px);-moz-transform:translateX(3px);-o-transform:translateX(3px);-ms-transform:translateX(3px);transform:translateX(3px)}}@-webkit-keyframes more-btn-move{0%,100%{-webkit-transform:translateX(0);-moz-transform:translateX(0);-o-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}50%{-webkit-transform:translateX(3px);-moz-transform:translateX(3px);-o-transform:translateX(3px);-ms-transform:translateX(3px);transform:translateX(3px)}}@-o-keyframes more-btn-move{0%,100%{-webkit-transform:translateX(0);-moz-transform:translateX(0);-o-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}50%{-webkit-transform:translateX(3px);-moz-transform:translateX(3px);-o-transform:translateX(3px);-ms-transform:translateX(3px);transform:translateX(3px)}}@keyframes more-btn-move{0%,100%{-webkit-transform:translateX(0);-moz-transform:translateX(0);-o-transform:translateX(0);-ms-transform:translateX(0);transform:translateX(0)}50%{-webkit-transform:translateX(3px);-moz-transform:translateX(3px);-o-transform:translateX(3px);-ms-transform:translateX(3px);transform:translateX(3px)}}@-moz-keyframes toc-open{0%{-webkit-transform:scale(.7);-moz-transform:scale(.7);-o-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}100%{-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}@-webkit-keyframes toc-open{0%{-webkit-transform:scale(.7);-moz-transform:scale(.7);-o-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}100%{-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}@-o-keyframes toc-open{0%{-webkit-transform:scale(.7);-moz-transform:scale(.7);-o-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}100%{-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}@keyframes toc-open{0%{-webkit-transform:scale(.7);-moz-transform:scale(.7);-o-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}100%{-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}}@-moz-keyframes toc-close{0%{-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}100%{-webkit-transform:scale(.7);-moz-transform:scale(.7);-o-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}}@-webkit-keyframes toc-close{0%{-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}100%{-webkit-transform:scale(.7);-moz-transform:scale(.7);-o-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}}@-o-keyframes toc-close{0%{-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}100%{-webkit-transform:scale(.7);-moz-transform:scale(.7);-o-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}}@keyframes toc-close{0%{-webkit-transform:scale(1);-moz-transform:scale(1);-o-transform:scale(1);-ms-transform:scale(1);transform:scale(1)}100%{-webkit-transform:scale(.7);-moz-transform:scale(.7);-o-transform:scale(.7);-ms-transform:scale(.7);transform:scale(.7)}}#post-comment .comment-head{margin-bottom:20px}#post-comment .comment-head:after{display:block;clear:both;content:''}#post-comment .comment-head .comment-headline{display:inline-block;vertical-align:middle;font-weight:700;font-size:1.43em}#post-comment .comment-head .comment-switch{display:inline-block;float:right;margin:2px auto 0;padding:4px 16px;width:max-content;border-radius:8px;background:#f6f8fa}#post-comment .comment-head .comment-switch .first-comment{color:#49b1f5}#post-comment .comment-head .comment-switch .second-comment{color:#ff7242}#post-comment .comment-head .comment-switch #switch-btn{position:relative;display:inline-block;margin:-4px 8px 0;width:42px;height:22px;border-radius:34px;background-color:#49b1f5;vertical-align:middle;cursor:pointer;-webkit-transition:.4s;-moz-transition:.4s;-o-transition:.4s;-ms-transition:.4s;transition:.4s}#post-comment .comment-head .comment-switch #switch-btn:before{position:absolute;bottom:4px;left:4px;width:14px;height:14px;border-radius:50%;background-color:#fff;content:'';-webkit-transition:.4s;-moz-transition:.4s;-o-transition:.4s;-ms-transition:.4s;transition:.4s}#post-comment .comment-wrap>div{-webkit-animation:tabshow .5s;-moz-animation:tabshow .5s;-o-animation:tabshow .5s;-ms-animation:tabshow 0.5s;animation:tabshow .5s}#post-comment .comment-wrap>div:nth-child(2){display:none}#post-comment.move #switch-btn{background-color:#ff7242}#post-comment.move #switch-btn:before{-webkit-transform:translateX(20px);-moz-transform:translateX(20px);-o-transform:translateX(20px);-ms-transform:translateX(20px);transform:translateX(20px)}#post-comment.move .comment-wrap>div:first-child{display:none}#post-comment.move .comment-wrap>div:last-child{display:block}#footer{position:relative;background-color:#49b1f5;background-attachment:scroll;background-position:bottom;background-size:cover}#footer:before{position:absolute;width:100%;height:100%;background-color:var(--mark-bg);content:''}#footer-wrap{position:relative;padding:40px 20px;color:var(--light-grey);text-align:center}#footer-wrap a{color:var(--light-grey)}#footer-wrap a:hover{text-decoration:underline}#footer-wrap .footer-separator{margin:0 4px}#footer-wrap .icp-icon{padding:0 4px;max-height:1.4em;width:auto;vertical-align:text-bottom}#page-header{position:relative;width:100%;background-color:#49b1f5;background-position:center center;background-size:cover;background-repeat:no-repeat;-webkit-transition:all .5s;-moz-transition:all .5s;-o-transition:all .5s;-ms-transition:all .5s;transition:all .5s}#page-header:not(.not-top-img):before{position:absolute;width:100%;height:100%;background-color:var(--mark-bg);content:''}#page-header.full_page{height:100vh;background-attachment:fixed}#page-header.full_page #site-info{position:absolute;top:43%;padding:0 10px;width:100%}#page-header #scroll-down .scroll-down-effects,#page-header #site-subtitle,#page-header #site-title{text-align:center;text-shadow:2px 2px 4px rgba(0,0,0,.15);line-height:1.5}#page-header #site-title{margin:0;color:var(--white);font-size:1.85em}@media screen and (min-width:768px){#page-header #site-title{font-size:2.85em}}#page-header #site-subtitle{color:var(--light-grey);font-size:1.15em}@media screen and (min-width:768px){#page-header #site-subtitle{font-size:1.72em}}#page-header #site_social_icons{display:none;margin:0 auto;text-align:center}@media screen and (max-width:768px){#page-header #site_social_icons{display:block}}#page-header #site_social_icons .social-icon{margin:0 10px;color:var(--light-grey);text-shadow:2px 2px 4px rgba(0,0,0,.15);font-size:1.43em}#page-header #scroll-down{position:absolute;bottom:10px;width:100%;cursor:pointer}#page-header #scroll-down .scroll-down-effects{position:relative;width:100%;color:var(--light-grey);font-size:20px}#page-header.not-home-page{height:400px}@media screen and (max-width:768px){#page-header.not-home-page{height:280px}}#page-header #page-site-info{position:absolute;top:200px;padding:0 10px;width:100%}@media screen and (max-width:768px){#page-header #page-site-info{top:140px}}#page-header.post-bg{height:400px}@media screen and (max-width:768px){#page-header.post-bg{height:360px}}#page-header #post-info{position:absolute;bottom:30px;padding:0 8%;width:100%}@media screen and (max-width:768px){#page-header #post-info{bottom:22px;padding:0 22px}}#page-header.not-top-img{margin-bottom:10px;height:60px;background:0}#page-header.not-top-img #nav{background:rgba(255,255,255,.8);-webkit-box-shadow:0 5px 6px -5px rgba(133,133,133,.6);box-shadow:0 5px 6px -5px rgba(133,133,133,.6)}#page-header.not-top-img #nav .site-name,#page-header.not-top-img #nav a{color:var(--font-color);text-shadow:none}#page-header.nav-fixed #nav{position:fixed;top:-60px;z-index:91;background:rgba(255,255,255,.8);-webkit-box-shadow:0 5px 6px -5px rgba(133,133,133,.6);box-shadow:0 5px 6px -5px rgba(133,133,133,.6);-webkit-transition:-webkit-transform .2s ease-in-out,opacity .2s ease-in-out;-moz-transition:-moz-transform .2s ease-in-out,opacity .2s ease-in-out;-o-transition:-o-transform .2s ease-in-out,opacity .2s ease-in-out;-ms-transition:-ms-transform .2s ease-in-out,opacity .2s ease-in-out;transition:transform .2s ease-in-out,opacity .2s ease-in-out}#page-header.nav-fixed #nav #blog-info{color:var(--font-color)}#page-header.nav-fixed #nav #blog-info:hover{color:#49b1f5}#page-header.nav-fixed #nav #blog-info .site-name{text-shadow:none}#page-header.nav-fixed #nav #toggle-menu,#page-header.nav-fixed #nav a{color:var(--font-color);text-shadow:none}#page-header.nav-fixed #nav #toggle-menu:hover,#page-header.nav-fixed #nav a:hover{color:#49b1f5}#page-header.nav-fixed.fixed #nav{top:0;-webkit-transition:all .5s;-moz-transition:all .5s;-o-transition:all .5s;-ms-transition:all .5s;transition:all .5s}#page-header.nav-visible:not(.fixed) #nav{-webkit-transition:all .5s;-moz-transition:all .5s;-o-transition:all .5s;-ms-transition:all .5s;transition:all .5s;-webkit-transform:translate3d(0,100%,0);-moz-transform:translate3d(0,100%,0);-o-transform:translate3d(0,100%,0);-ms-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}#page-header.nav-visible:not(.fixed)+.layout>.aside-content>.sticky_layout{top:70px;-webkit-transition:top .5s;-moz-transition:top .5s;-o-transition:top .5s;-ms-transition:top .5s;transition:top .5s}#page-header.fixed #nav{position:fixed}#page-header.fixed+.layout>.aside-content>.sticky_layout{top:70px;-webkit-transition:top .5s;-moz-transition:top .5s;-o-transition:top .5s;-ms-transition:top .5s;transition:top .5s}#page-header.fixed+.layout #card-toc .toc-content{max-height:calc(100vh - 170px)}#page h1.page-title{margin:8px 0 20px}#post>#post-info{margin-bottom:30px}#post>#post-info .post-title{padding-bottom:4px;border-bottom:1px solid var(--light-grey);color:var(--text-highlight-color)}#post>#post-info .post-title .post-edit-link{float:right}#post>#post-info #post-meta,#post>#post-info #post-meta a{color:#78818a}#post-info .post-title{margin-bottom:8px;color:var(--white);font-weight:400;font-size:2.5em;line-height:1.5;-webkit-line-clamp:3}@media screen and (max-width:768px){#post-info .post-title{font-size:2.1em}}#post-info .post-title .post-edit-link{padding-left:10px}#post-info #post-meta{color:var(--light-grey);font-size:95%}@media screen and (min-width:768px){#post-info #post-meta>.meta-secondline>span:first-child{display:none}}@media screen and (max-width:768px){#post-info #post-meta{font-size:90%}#post-info #post-meta>.meta-firstline,#post-info #post-meta>.meta-secondline{display:inline}}#post-info #post-meta .post-meta-separator{margin:0 5px}#post-info #post-meta .post-meta-icon{margin-right:4px}#post-info #post-meta .post-meta-label{margin-right:4px}#post-info #post-meta a{color:var(--light-grey);-webkit-transition:all .3s ease-out;-moz-transition:all .3s ease-out;-o-transition:all .3s ease-out;-ms-transition:all .3s ease-out;transition:all .3s ease-out}#post-info #post-meta a:hover{color:#49b1f5;text-decoration:underline}#nav{position:absolute;top:0;z-index:90;display:-webkit-box;display:-moz-box;display:-webkit-flex;display:-ms-flexbox;display:box;display:flex;-webkit-box-align:center;-moz-box-align:center;-o-box-align:center;-ms-flex-align:center;-webkit-align-items:center;align-items:center;padding:0 36px;width:100%;height:60px;font-size:1.3em;opacity:0;-webkit-transition:all .5s;-moz-transition:all .5s;-o-transition:all .5s;-ms-transition:all .5s;transition:all .5s}@media screen and (max-width:768px){#nav{padding:0 16px}}#nav.show{opacity:1;-ms-filter:none;filter:none}#nav #blog-info{-webkit-box-flex:1;-moz-box-flex:1;-o-box-flex:1;box-flex:1;-webkit-flex:1;-ms-flex:1;flex:1;color:var(--light-grey)}#nav #blog-info .site-icon{margin-right:6px;height:36px;vertical-align:middle}#nav #toggle-menu{display:none;padding:2px 0 0 6px;vertical-align:top}#nav #toggle-menu:hover{color:var(--white)}#nav a{color:var(--light-grey)}#nav a:hover{color:var(--white)}#nav .site-name{text-shadow:2px 2px 4px rgba(0,0,0,.15);font-weight:700}#nav .menus_items{display:inline}#nav .menus_items .menus_item{position:relative;display:inline-block;padding:0 0 0 14px}#nav .menus_items .menus_item:hover .menus_item_child{display:block}#nav .menus_items .menus_item:hover>a>i:last-child{-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-o-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}#nav .menus_items .menus_item>a>i:last-child{padding:4px;-webkit-transition:-webkit-transform .3s;-moz-transition:-moz-transform .3s;-o-transition:-o-transform .3s;-ms-transition:-ms-transform .3s;transition:transform .3s}#nav .menus_items .menus_item .menus_item_child{position:absolute;right:0;display:none;margin-top:8px;padding:0;width:max-content;border-radius:5px;background-color:var(--sidebar-bg);-webkit-box-shadow:0 5px 20px -4px rgba(0,0,0,.5);box-shadow:0 5px 20px -4px rgba(0,0,0,.5);-webkit-animation:sub_menus .3s .1s ease both;-moz-animation:sub_menus .3s .1s ease both;-o-animation:sub_menus .3s .1s ease both;-ms-animation:sub_menus 0.3s 0.1s ease both;animation:sub_menus .3s .1s ease both}#nav .menus_items .menus_item .menus_item_child:before{position:absolute;top:-8px;left:0;width:100%;height:20px;content:''}#nav .menus_items .menus_item .menus_item_child li{list-style:none}#nav .menus_items .menus_item .menus_item_child li:hover{background:var(--text-bg-hover)}#nav .menus_items .menus_item .menus_item_child li:first-child{border-top-left-radius:5px;border-top-right-radius:5px}#nav .menus_items .menus_item .menus_item_child li:last-child{border-bottom-right-radius:5px;border-bottom-left-radius:5px}#nav .menus_items .menus_item .menus_item_child li a{display:inline-block;padding:8px 16px;width:100%;color:var(--font-color)!important;text-shadow:none!important}#nav.hide-menu #toggle-menu{display:inline-block!important}#nav.hide-menu #toggle-menu .site-page{font-size:inherit}#nav.hide-menu .menus_items{display:none}#nav.hide-menu #search-button span{display:none}#nav #search-button{display:inline;padding:0 0 0 14px}#nav .site-page{position:relative;padding-bottom:6px;text-shadow:1px 1px 2px rgba(0,0,0,.3);font-size:.78em;cursor:pointer}#nav .site-page:not(.child):after{position:absolute;bottom:0;left:0;z-index:-1;width:0;height:3px;background-color:#80c8f8;content:'';-webkit-transition:all .3s ease-in-out;-moz-transition:all .3s ease-in-out;-o-transition:all .3s ease-in-out;-ms-transition:all .3s ease-in-out;transition:all .3s ease-in-out}#nav .site-page:not(.child):hover:after{width:100%}#pagination .pagination{margin-top:20px;text-align:center}#pagination .page-number.current{background:#00c4b6;color:var(--white)}#pagination .pagination-info{position:absolute;top:50%;padding:20px 40px;width:100%;-webkit-transform:translate(0,-50%);-moz-transform:translate(0,-50%);-o-transform:translate(0,-50%);-ms-transform:translate(0,-50%);transform:translate(0,-50%)}#pagination .next_info,#pagination .prev_info{color:var(--white);font-weight:500}#pagination .next-post .pagination-info{text-align:right}#pagination .pull-full{width:100%!important}#pagination .next-post .label,#pagination .prev-post .label{color:var(--light-grey);text-transform:uppercase;font-size:90%}#pagination .next-post,#pagination .prev-post{width:50%}@media screen and (max-width:768px){#pagination .next-post,#pagination .prev-post{width:100%}}#pagination .next-post a,#pagination .prev-post a{position:relative;display:block;overflow:hidden;height:150px}#pagination.pagination-post{overflow:hidden;margin-top:40px;width:100%;background:#000}.layout>.recent-posts .pagination>*{display:inline-block;margin:0 6px;width:2.5em;height:2.5em;line-height:2.5em}.layout>.recent-posts .pagination>:not(.space):hover{background:var(--btn-hover-color);color:var(--btn-color)}.layout>div:not(.recent-posts) .pagination .page-number{display:inline-block;margin:0 4px;min-width:24px;height:24px;text-align:center;line-height:24px;cursor:pointer}#article-container{word-wrap:break-word;overflow-wrap:break-word}#article-container a{color:#49b1f5}#article-container a:hover{text-decoration:underline}#article-container img{display:block;margin:0 auto 20px;max-width:100%;-webkit-transition:filter 375ms ease-in .2s;-moz-transition:filter 375ms ease-in .2s;-o-transition:filter 375ms ease-in .2s;-ms-transition:filter 375ms ease-in .2s;transition:filter 375ms ease-in .2s}#article-container p{margin:0 0 16px}#article-container iframe{margin:0 0 20px}#article-container kbd{margin:0 3px;padding:3px 5px;border:1px solid #b4b4b4;border-radius:3px;background-color:#f8f8f8;-webkit-box-shadow:0 1px 3px rgba(0,0,0,.25),0 2px 1px 0 rgba(255,255,255,.6) inset;box-shadow:0 1px 3px rgba(0,0,0,.25),0 2px 1px 0 rgba(255,255,255,.6) inset;color:#34495e;white-space:nowrap;font-weight:600;font-size:.9em;font-family:Monaco,'Ubuntu Mono',monospace;line-height:1em}#article-container ol ol,#article-container ol ul,#article-container ul ol,#article-container ul ul{padding-left:20px}#article-container ol li,#article-container ul li{margin:4px 0}#article-container ol p,#article-container ul p{margin:0 0 8px}#article-container>:last-child{margin-bottom:0!important}#article-container hr{margin:20px 0}#post .tag_share:after{display:block;clear:both;content:''}#post .tag_share .post-meta__tag-list{display:inline-block}#post .tag_share .post-meta__tags{display:inline-block;margin:8px 8px 8px 0;padding:0 12px;width:fit-content;border:1px solid #49b1f5;border-radius:12px;color:#49b1f5;font-size:.85em;-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;-ms-transition:all .2s ease-in-out;transition:all .2s ease-in-out}#post .tag_share .post-meta__tags:hover{background:#49b1f5;color:var(--white)}#post .tag_share .post_share{display:inline-block;float:right;margin:8px 0 0;width:fit-content}#post .tag_share .post_share .social-share{font-size:.85em}#post .tag_share .post_share .social-share .social-share-icon{margin:0 4px;width:1.85em;height:1.85em;font-size:1.2em;line-height:1.85em}#post .post-copyright{position:relative;margin:40px 0 10px;padding:10px 16px;border:1px solid var(--light-grey);-webkit-transition:box-shadow .3s ease-in-out;-moz-transition:box-shadow .3s ease-in-out;-o-transition:box-shadow .3s ease-in-out;-ms-transition:box-shadow .3s ease-in-out;transition:box-shadow .3s ease-in-out}#post .post-copyright:before{position:absolute;top:2px;right:12px;color:#49b1f5;content:'\f1f9';font-size:1.3em}#post .post-copyright:hover{-webkit-box-shadow:0 0 8px 0 rgba(232,237,250,.6),0 2px 4px 0 rgba(232,237,250,.5);box-shadow:0 0 8px 0 rgba(232,237,250,.6),0 2px 4px 0 rgba(232,237,250,.5)}#post .post-copyright .post-copyright-meta{color:#49b1f5;font-weight:700}#post .post-copyright .post-copyright-meta i{margin-right:3px}#post .post-copyright .post-copyright-info{padding-left:6px}#post .post-copyright .post-copyright-info a{text-decoration:underline;word-break:break-word}#post .post-copyright .post-copyright-info a:hover{text-decoration:none}#post .post-outdate-notice{position:relative;margin:0 0 20px;padding:.5em 1.2em;border-radius:3px;background-color:#ffe6e6;color:#f66;padding:.5em 1em .5em 2.6em;border-left:5px solid #ff8080}#post .post-outdate-notice:before{position:absolute;top:50%;left:.9em;color:#ff8080;content:'\f071';-webkit-transform:translateY(-50%);-moz-transform:translateY(-50%);-o-transform:translateY(-50%);-ms-transform:translateY(-50%);transform:translateY(-50%)}#post .ads-wrap{margin:40px 0}.relatedPosts{margin-top:40px}.relatedPosts>.headline{margin-bottom:5px;font-weight:700;font-size:1.43em}.relatedPosts>.relatedPosts-list>div{position:relative;display:inline-block;overflow:hidden;margin:3px;width:calc(33.333% - 6px);height:200px;background:#000;vertical-align:bottom}@media screen and (max-width:768px){.relatedPosts>.relatedPosts-list>div{margin:2px;width:calc(50% - 4px);height:150px}}@media screen and (max-width:600px){.relatedPosts>.relatedPosts-list>div{width:calc(100% - 4px)}}.relatedPosts>.relatedPosts-list .content{position:absolute;top:50%;padding:0 20px;width:100%;-webkit-transform:translate(0,-50%);-moz-transform:translate(0,-50%);-o-transform:translate(0,-50%);-ms-transform:translate(0,-50%);transform:translate(0,-50%)}.relatedPosts>.relatedPosts-list .content .date{color:var(--light-grey);font-size:90%}.relatedPosts>.relatedPosts-list .content .title{color:var(--white);-webkit-line-clamp:2}.post-reward{position:relative;margin-top:80px;width:100%;text-align:center;pointer-events:none}.post-reward>*{pointer-events:auto}.post-reward .reward-button{display:inline-block;padding:4px 24px;background:var(--btn-bg);color:var(--btn-color);cursor:pointer}.post-reward .reward-button i{margin-right:5px}.post-reward:hover .reward-button{background:var(--btn-hover-color)}.post-reward:hover>.reward-main{display:block}.post-reward .reward-main{position:absolute;bottom:40px;left:0;z-index:100;display:none;padding:0 0 15px;width:100%}.post-reward .reward-main .reward-all{display:inline-block;margin:0;padding:20px 10px;border-radius:4px;background:var(--reward-pop)}.post-reward .reward-main .reward-all:before{position:absolute;bottom:-10px;left:0;width:100%;height:20px;content:''}.post-reward .reward-main .reward-all:after{position:absolute;right:0;bottom:2px;left:0;margin:0 auto;width:0;height:0;border-top:13px solid var(--reward-pop);border-right:13px solid transparent;border-left:13px solid transparent;content:''}.post-reward .reward-main .reward-all .reward-item{display:inline-block;padding:0 8px;list-style-type:none;vertical-align:top}.post-reward .reward-main .reward-all .reward-item img{width:130px;height:130px}.post-reward .reward-main .reward-all .reward-item .post-qr-code-desc{width:130px;color:#858585}#rightside{position:fixed;right:-48px;bottom:40px;z-index:100;opacity:0;-webkit-transition:all .5s;-moz-transition:all .5s;-o-transition:all .5s;-ms-transition:all .5s;transition:all .5s}#rightside.rightside-show{opacity:.8;-webkit-transform:translate(-58px,0);-moz-transform:translate(-58px,0);-o-transform:translate(-58px,0);-ms-transform:translate(-58px,0);transform:translate(-58px,0)}#rightside #rightside-config-hide{height:0;opacity:0;-webkit-transition:-webkit-transform .4s;-moz-transition:-moz-transform .4s;-o-transition:-o-transform .4s;-ms-transition:-ms-transform .4s;transition:transform .4s;-webkit-transform:translate(45px,0);-moz-transform:translate(45px,0);-o-transform:translate(45px,0);-ms-transform:translate(45px,0);transform:translate(45px,0)}#rightside #rightside-config-hide.show{height:auto;opacity:1;-ms-filter:none;filter:none;-webkit-transform:translate(0,0);-moz-transform:translate(0,0);-o-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}#rightside #rightside-config-hide.status{height:auto;opacity:1;-ms-filter:none;filter:none}#rightside>div>a,#rightside>div>button{display:block;margin-bottom:5px;width:35px;height:35px;border-radius:5px;background-color:var(--btn-bg);color:var(--btn-color);text-align:center;font-size:16px;line-height:35px}#rightside>div>a:hover,#rightside>div>button:hover{background-color:var(--btn-hover-color)}#rightside #mobile-toc-button{display:none}@media screen and (max-width:900px){#rightside #mobile-toc-button{display:block}}@media screen and (max-width:900px){#rightside #hide-aside-btn{display:none}}#sidebar #menu-mask{position:fixed;z-index:102;display:none;width:100%;height:100%;background:rgba(0,0,0,.8)}#sidebar #sidebar-menus{position:fixed;top:0;right:-300px;z-index:103;overflow-x:hidden;overflow-y:scroll;padding-left:5px;width:300px;height:100%;background:var(--sidebar-bg);-webkit-transition:all .5s;-moz-transition:all .5s;-o-transition:all .5s;-ms-transition:all .5s;transition:all .5s}#sidebar #sidebar-menus.open{-webkit-transform:translate3d(-100%,0,0);-moz-transform:translate3d(-100%,0,0);-o-transform:translate3d(-100%,0,0);-ms-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}#sidebar #sidebar-menus>.avatar-img{margin:20px auto}#sidebar #sidebar-menus .sidebar-site-data{padding:0 10px}#sidebar #sidebar-menus hr{margin:20px auto}#sidebar #sidebar-menus .menus_items{padding:0 10px}#sidebar #sidebar-menus .menus_items .site-page{position:relative;display:block;padding:3px 28px 3px 20px;color:var(--font-color);font-size:1.15em;border-radius:6px}#sidebar #sidebar-menus .menus_items .site-page:hover{background:var(--text-bg-hover)}#sidebar #sidebar-menus .menus_items .site-page i:first-child{width:15%;text-align:left}#sidebar #sidebar-menus .menus_items .site-page.group>i:last-child{position:absolute;top:.78em;right:13px;-webkit-transition:-webkit-transform .3s;-moz-transition:-moz-transform .3s;-o-transition:-o-transform .3s;-ms-transition:-ms-transform .3s;transition:transform .3s}#sidebar #sidebar-menus .menus_items .site-page.group.hide>i:last-child{-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-o-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}#sidebar #sidebar-menus .menus_items .site-page.group.hide+.menus_item_child{display:none}#sidebar #sidebar-menus .menus_items .menus_item_child{margin:0;padding-left:25px;list-style:none}#vcomment{font-size:1.1em}#vcomment .vbtn{border:none;background:var(--btn-bg);color:var(--btn-color)}#vcomment .vbtn:hover{background:var(--btn-hover-color)}#vcomment .vimg{-webkit-transition:all .3s;-moz-transition:all .3s;-o-transition:all .3s;-ms-transition:all .3s;transition:all .3s}#vcomment .vimg:hover{-webkit-transform:rotate(360deg);-moz-transform:rotate(360deg);-o-transform:rotate(360deg);-ms-transform:rotate(360deg);transform:rotate(360deg)}#vcomment .vcards .vcard .vcontent.expand:after,#vcomment .vcards .vcard .vcontent.expand:before{z-index:22}#waline-wrap{--waline-font-size:1.1em;--waline-theme-color:#49b1f5;--waline-active-color:#ff7242}#waline-wrap .wl-comment-actions>button:not(last-child){padding-right:4px}.fireworks{position:fixed;top:0;left:0;z-index:9999;pointer-events:none}.medium-zoom-image--opened{z-index:99999!important;margin:0!important}.medium-zoom-overlay{z-index:99999!important}.mermaid-wrap{margin:0 0 20px;text-align:center}.mermaid-wrap>svg{height:100%}.fb-comments iframe,.utterances{width:100%!important}#gitalk-container .gt-meta{margin:0 0 .8em;padding:6px 0 16px}.katex-wrap{overflow:auto}.katex-wrap::-webkit-scrollbar{display:none}mjx-container{overflow-x:auto;overflow-y:hidden;padding-bottom:4px;max-width:100%}mjx-container[display]{display:block!important;min-width:auto!important}mjx-container:not([display]){display:inline-grid!important}mjx-assistive-mml{right:0;bottom:0}.aplayer{color:#4c4948}#article-container .aplayer{margin:0 0 20px}.snackbar-css{border-radius:5px!important}.abc-music-sheet{margin:0 0 20px;opacity:0;-webkit-transition:opacity .3s;-moz-transition:opacity .3s;-o-transition:opacity .3s;-ms-transition:opacity .3s;transition:opacity .3s}.abc-music-sheet.abcjs-container{opacity:1;-ms-filter:none;filter:none}@media screen and (max-width:768px){.fancybox__toolbar__column.is-middle{display:none}}#article-container .btn-center{margin:0 0 20px;text-align:center}#article-container .btn-beautify{display:inline-block;margin:0 4px 6px;padding:0 15px;background-color:var(--btn-beautify-color,#777);color:#fff;line-height:2}#article-container .btn-beautify.blue{--btn-beautify-color:#428bca}#article-container .btn-beautify.pink{--btn-beautify-color:#ff69b4}#article-container .btn-beautify.red{--btn-beautify-color:#f00}#article-container .btn-beautify.purple{--btn-beautify-color:#6f42c1}#article-container .btn-beautify.orange{--btn-beautify-color:#ff8c00}#article-container .btn-beautify.green{--btn-beautify-color:#5cb85c}#article-container .btn-beautify:hover{background-color:var(--btn-hover-color)}#article-container .btn-beautify i+span{margin-left:6px}#article-container .btn-beautify:not(.block)+.btn-beautify:not(.block){margin:0 4px 20px}#article-container .btn-beautify.block{display:block;margin:0 0 20px;width:fit-content;width:-moz-fit-content}#article-container .btn-beautify.block.center{margin:0 auto 20px}#article-container .btn-beautify.block.right{margin:0 0 20px auto}#article-container .btn-beautify.larger{padding:6px 15px}#article-container .btn-beautify:hover{text-decoration:none}#article-container .btn-beautify.outline{border:1px solid transparent;border-color:var(--btn-beautify-color,#777);background-color:transparent;color:var(--btn-beautify-color,#777)}#article-container .btn-beautify.outline:hover{background-color:var(--btn-beautify-color,#777)}#article-container .btn-beautify.outline:hover{color:#fff!important}#article-container figure.gallery-group{position:relative;float:left;overflow:hidden;margin:6px 4px;width:calc(50% - 8px);height:250px;border-radius:8px;background:#000;-webkit-transform:translate3d(0,0,0)}@media screen and (max-width:600px){#article-container figure.gallery-group{width:calc(100% - 8px)}}#article-container figure.gallery-group:hover img{opacity:.4;-webkit-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}#article-container figure.gallery-group:hover .gallery-group-name::after{-webkit-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}#article-container figure.gallery-group:hover p{opacity:1;-ms-filter:none;filter:none;-webkit-transform:translate3d(0,0,0);-moz-transform:translate3d(0,0,0);-o-transform:translate3d(0,0,0);-ms-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}#article-container figure.gallery-group img{position:relative;margin:0;max-width:none;width:calc(100% + 20px);height:250px;-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;-ms-backface-visibility:hidden;backface-visibility:hidden;opacity:.8;-webkit-transition:all .3s,filter 375ms ease-in .2s;-moz-transition:all .3s,filter 375ms ease-in .2s;-o-transition:all .3s,filter 375ms ease-in .2s;-ms-transition:all .3s,filter 375ms ease-in .2s;transition:all .3s,filter 375ms ease-in .2s;-webkit-transform:translate3d(-10px,0,0);-moz-transform:translate3d(-10px,0,0);-o-transform:translate3d(-10px,0,0);-ms-transform:translate3d(-10px,0,0);transform:translate3d(-10px,0,0);object-fit:cover}#article-container figure.gallery-group figcaption{position:absolute;top:0;left:0;padding:30px;width:100%;height:100%;color:#fff;text-transform:uppercase;-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;-ms-backface-visibility:hidden;backface-visibility:hidden}#article-container figure.gallery-group figcaption>a{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1000;opacity:0}#article-container figure.gallery-group p{margin:0;padding:8px 0 0;letter-spacing:1px;font-size:1.1em;line-height:1.5;opacity:0;-webkit-transition:opacity .35s,-webkit-transform .35s;-moz-transition:opacity .35s,-moz-transform .35s;-o-transition:opacity .35s,-o-transform .35s;-ms-transition:opacity .35s,-ms-transform .35s;transition:opacity .35s,transform .35s;-webkit-transform:translate3d(100%,0,0);-moz-transform:translate3d(100%,0,0);-o-transform:translate3d(100%,0,0);-ms-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0);-webkit-line-clamp:4}#article-container figure.gallery-group .gallery-group-name{position:relative;margin:0;padding:8px 0;font-weight:700;font-size:1.65em;line-height:1.5;-webkit-line-clamp:2}#article-container figure.gallery-group .gallery-group-name:after{position:absolute;bottom:0;left:0;width:100%;height:2px;background:#fff;content:'';-webkit-transition:-webkit-transform .35s;-moz-transition:-moz-transform .35s;-o-transition:-o-transform .35s;-ms-transition:-ms-transform .35s;transition:transform .35s;-webkit-transform:translate3d(-100%,0,0);-moz-transform:translate3d(-100%,0,0);-o-transform:translate3d(-100%,0,0);-ms-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}#article-container .gallery-group-main{overflow:auto;padding:0 0 16px}#article-container .gallery-container{margin:0 0 16px;text-align:center}#article-container .gallery-container img{display:initial;margin:0;width:100%;height:100%}#article-container .gallery-container .gallery-data{display:none}#article-container .gallery-container button{margin-top:25px;padding:10px;width:9em;border-radius:5px;background:var(--btn-bg);color:var(--btn-color);font-weight:700;font-size:1.1em;-webkit-transition:all .3s;-moz-transition:all .3s;-o-transition:all .3s;-ms-transition:all .3s;transition:all .3s}#article-container .gallery-container button:hover{background:var(--btn-hover-color)}#article-container .loading-container{display:inline-block;overflow:hidden;width:154px;height:154px}#article-container .loading-container .loading-item{position:relative;width:100%;height:100%;-webkit-backface-visibility:hidden;-moz-backface-visibility:hidden;-ms-backface-visibility:hidden;backface-visibility:hidden;-webkit-transform:translateZ(0) scale(1);-moz-transform:translateZ(0) scale(1);-o-transform:translateZ(0) scale(1);-ms-transform:translateZ(0) scale(1);transform:translateZ(0) scale(1);-webkit-transform-origin:0 0;-moz-transform-origin:0 0;-o-transform-origin:0 0;-ms-transform-origin:0 0;transform-origin:0 0}#article-container .loading-container .loading-item div{position:absolute;width:30.8px;height:30.8px;border-radius:50%;background:#e15b64;-webkit-transform:translate(61.6px,61.6px) scale(1);-moz-transform:translate(61.6px,61.6px) scale(1);-o-transform:translate(61.6px,61.6px) scale(1);-ms-transform:translate(61.6px,61.6px) scale(1);transform:translate(61.6px,61.6px) scale(1);-webkit-animation:loading-ball 1.92s infinite cubic-bezier(0,.5,.5,1);-moz-animation:loading-ball 1.92s infinite cubic-bezier(0,.5,.5,1);-o-animation:loading-ball 1.92s infinite cubic-bezier(0,.5,.5,1);-ms-animation:loading-ball 1.92s infinite cubic-bezier(0,0.5,0.5,1);animation:loading-ball 1.92s infinite cubic-bezier(0,.5,.5,1)}#article-container .loading-container .loading-item div:first-child{background:#f47e60;-webkit-transform:translate(113.96px,61.6px) scale(1);-moz-transform:translate(113.96px,61.6px) scale(1);-o-transform:translate(113.96px,61.6px) scale(1);-ms-transform:translate(113.96px,61.6px) scale(1);transform:translate(113.96px,61.6px) scale(1);-webkit-animation:loading-ball-r .48s infinite cubic-bezier(0,.5,.5,1),loading-ball-c 1.92s infinite step-start;-moz-animation:loading-ball-r .48s infinite cubic-bezier(0,.5,.5,1),loading-ball-c 1.92s infinite step-start;-o-animation:loading-ball-r .48s infinite cubic-bezier(0,.5,.5,1),loading-ball-c 1.92s infinite step-start;-ms-animation:loading-ball-r 0.48s infinite cubic-bezier(0,0.5,0.5,1),loading-ball-c 1.92s infinite step-start;animation:loading-ball-r .48s infinite cubic-bezier(0,.5,.5,1),loading-ball-c 1.92s infinite step-start}#article-container .loading-container .loading-item div:nth-child(2){background:#e15b64;-webkit-animation-delay:-.48s;-moz-animation-delay:-.48s;-o-animation-delay:-.48s;-ms-animation-delay:-0.48s;animation-delay:-.48s}#article-container .loading-container .loading-item div:nth-child(3){background:#f47e60;-webkit-animation-delay:-.96s;-moz-animation-delay:-.96s;-o-animation-delay:-.96s;-ms-animation-delay:-0.96s;animation-delay:-.96s}#article-container .loading-container .loading-item div:nth-child(4){background:#f8b26a;-webkit-animation-delay:-1.44s;-moz-animation-delay:-1.44s;-o-animation-delay:-1.44s;-ms-animation-delay:-1.44s;animation-delay:-1.44s}#article-container .loading-container .loading-item div:nth-child(5){background:#abbd81;-webkit-animation-delay:-1.92s;-moz-animation-delay:-1.92s;-o-animation-delay:-1.92s;-ms-animation-delay:-1.92s;animation-delay:-1.92s}@-moz-keyframes loading-ball{0%{-webkit-transform:translate(9.24px,61.6px) scale(0);-moz-transform:translate(9.24px,61.6px) scale(0);-o-transform:translate(9.24px,61.6px) scale(0);-ms-transform:translate(9.24px,61.6px) scale(0);transform:translate(9.24px,61.6px) scale(0)}25%{-webkit-transform:translate(9.24px,61.6px) scale(0);-moz-transform:translate(9.24px,61.6px) scale(0);-o-transform:translate(9.24px,61.6px) scale(0);-ms-transform:translate(9.24px,61.6px) scale(0);transform:translate(9.24px,61.6px) scale(0)}50%{-webkit-transform:translate(9.24px,61.6px) scale(1);-moz-transform:translate(9.24px,61.6px) scale(1);-o-transform:translate(9.24px,61.6px) scale(1);-ms-transform:translate(9.24px,61.6px) scale(1);transform:translate(9.24px,61.6px) scale(1)}75%{-webkit-transform:translate(61.6px,61.6px) scale(1);-moz-transform:translate(61.6px,61.6px) scale(1);-o-transform:translate(61.6px,61.6px) scale(1);-ms-transform:translate(61.6px,61.6px) scale(1);transform:translate(61.6px,61.6px) scale(1)}100%{-webkit-transform:translate(113.96px,61.6px) scale(1);-moz-transform:translate(113.96px,61.6px) scale(1);-o-transform:translate(113.96px,61.6px) scale(1);-ms-transform:translate(113.96px,61.6px) scale(1);transform:translate(113.96px,61.6px) scale(1)}}@-webkit-keyframes loading-ball{0%{-webkit-transform:translate(9.24px,61.6px) scale(0);-moz-transform:translate(9.24px,61.6px) scale(0);-o-transform:translate(9.24px,61.6px) scale(0);-ms-transform:translate(9.24px,61.6px) scale(0);transform:translate(9.24px,61.6px) scale(0)}25%{-webkit-transform:translate(9.24px,61.6px) scale(0);-moz-transform:translate(9.24px,61.6px) scale(0);-o-transform:translate(9.24px,61.6px) scale(0);-ms-transform:translate(9.24px,61.6px) scale(0);transform:translate(9.24px,61.6px) scale(0)}50%{-webkit-transform:translate(9.24px,61.6px) scale(1);-moz-transform:translate(9.24px,61.6px) scale(1);-o-transform:translate(9.24px,61.6px) scale(1);-ms-transform:translate(9.24px,61.6px) scale(1);transform:translate(9.24px,61.6px) scale(1)}75%{-webkit-transform:translate(61.6px,61.6px) scale(1);-moz-transform:translate(61.6px,61.6px) scale(1);-o-transform:translate(61.6px,61.6px) scale(1);-ms-transform:translate(61.6px,61.6px) scale(1);transform:translate(61.6px,61.6px) scale(1)}100%{-webkit-transform:translate(113.96px,61.6px) scale(1);-moz-transform:translate(113.96px,61.6px) scale(1);-o-transform:translate(113.96px,61.6px) scale(1);-ms-transform:translate(113.96px,61.6px) scale(1);transform:translate(113.96px,61.6px) scale(1)}}@-o-keyframes loading-ball{0%{-webkit-transform:translate(9.24px,61.6px) scale(0);-moz-transform:translate(9.24px,61.6px) scale(0);-o-transform:translate(9.24px,61.6px) scale(0);-ms-transform:translate(9.24px,61.6px) scale(0);transform:translate(9.24px,61.6px) scale(0)}25%{-webkit-transform:translate(9.24px,61.6px) scale(0);-moz-transform:translate(9.24px,61.6px) scale(0);-o-transform:translate(9.24px,61.6px) scale(0);-ms-transform:translate(9.24px,61.6px) scale(0);transform:translate(9.24px,61.6px) scale(0)}50%{-webkit-transform:translate(9.24px,61.6px) scale(1);-moz-transform:translate(9.24px,61.6px) scale(1);-o-transform:translate(9.24px,61.6px) scale(1);-ms-transform:translate(9.24px,61.6px) scale(1);transform:translate(9.24px,61.6px) scale(1)}75%{-webkit-transform:translate(61.6px,61.6px) scale(1);-moz-transform:translate(61.6px,61.6px) scale(1);-o-transform:translate(61.6px,61.6px) scale(1);-ms-transform:translate(61.6px,61.6px) scale(1);transform:translate(61.6px,61.6px) scale(1)}100%{-webkit-transform:translate(113.96px,61.6px) scale(1);-moz-transform:translate(113.96px,61.6px) scale(1);-o-transform:translate(113.96px,61.6px) scale(1);-ms-transform:translate(113.96px,61.6px) scale(1);transform:translate(113.96px,61.6px) scale(1)}}@keyframes loading-ball{0%{-webkit-transform:translate(9.24px,61.6px) scale(0);-moz-transform:translate(9.24px,61.6px) scale(0);-o-transform:translate(9.24px,61.6px) scale(0);-ms-transform:translate(9.24px,61.6px) scale(0);transform:translate(9.24px,61.6px) scale(0)}25%{-webkit-transform:translate(9.24px,61.6px) scale(0);-moz-transform:translate(9.24px,61.6px) scale(0);-o-transform:translate(9.24px,61.6px) scale(0);-ms-transform:translate(9.24px,61.6px) scale(0);transform:translate(9.24px,61.6px) scale(0)}50%{-webkit-transform:translate(9.24px,61.6px) scale(1);-moz-transform:translate(9.24px,61.6px) scale(1);-o-transform:translate(9.24px,61.6px) scale(1);-ms-transform:translate(9.24px,61.6px) scale(1);transform:translate(9.24px,61.6px) scale(1)}75%{-webkit-transform:translate(61.6px,61.6px) scale(1);-moz-transform:translate(61.6px,61.6px) scale(1);-o-transform:translate(61.6px,61.6px) scale(1);-ms-transform:translate(61.6px,61.6px) scale(1);transform:translate(61.6px,61.6px) scale(1)}100%{-webkit-transform:translate(113.96px,61.6px) scale(1);-moz-transform:translate(113.96px,61.6px) scale(1);-o-transform:translate(113.96px,61.6px) scale(1);-ms-transform:translate(113.96px,61.6px) scale(1);transform:translate(113.96px,61.6px) scale(1)}}@-moz-keyframes loading-ball-r{0%{-webkit-transform:translate(113.96px,61.6px) scale(1);-moz-transform:translate(113.96px,61.6px) scale(1);-o-transform:translate(113.96px,61.6px) scale(1);-ms-transform:translate(113.96px,61.6px) scale(1);transform:translate(113.96px,61.6px) scale(1)}100%{-webkit-transform:translate(113.96px,61.6px) scale(0);-moz-transform:translate(113.96px,61.6px) scale(0);-o-transform:translate(113.96px,61.6px) scale(0);-ms-transform:translate(113.96px,61.6px) scale(0);transform:translate(113.96px,61.6px) scale(0)}}@-webkit-keyframes loading-ball-r{0%{-webkit-transform:translate(113.96px,61.6px) scale(1);-moz-transform:translate(113.96px,61.6px) scale(1);-o-transform:translate(113.96px,61.6px) scale(1);-ms-transform:translate(113.96px,61.6px) scale(1);transform:translate(113.96px,61.6px) scale(1)}100%{-webkit-transform:translate(113.96px,61.6px) scale(0);-moz-transform:translate(113.96px,61.6px) scale(0);-o-transform:translate(113.96px,61.6px) scale(0);-ms-transform:translate(113.96px,61.6px) scale(0);transform:translate(113.96px,61.6px) scale(0)}}@-o-keyframes loading-ball-r{0%{-webkit-transform:translate(113.96px,61.6px) scale(1);-moz-transform:translate(113.96px,61.6px) scale(1);-o-transform:translate(113.96px,61.6px) scale(1);-ms-transform:translate(113.96px,61.6px) scale(1);transform:translate(113.96px,61.6px) scale(1)}100%{-webkit-transform:translate(113.96px,61.6px) scale(0);-moz-transform:translate(113.96px,61.6px) scale(0);-o-transform:translate(113.96px,61.6px) scale(0);-ms-transform:translate(113.96px,61.6px) scale(0);transform:translate(113.96px,61.6px) scale(0)}}@keyframes loading-ball-r{0%{-webkit-transform:translate(113.96px,61.6px) scale(1);-moz-transform:translate(113.96px,61.6px) scale(1);-o-transform:translate(113.96px,61.6px) scale(1);-ms-transform:translate(113.96px,61.6px) scale(1);transform:translate(113.96px,61.6px) scale(1)}100%{-webkit-transform:translate(113.96px,61.6px) scale(0);-moz-transform:translate(113.96px,61.6px) scale(0);-o-transform:translate(113.96px,61.6px) scale(0);-ms-transform:translate(113.96px,61.6px) scale(0);transform:translate(113.96px,61.6px) scale(0)}}@-moz-keyframes loading-ball-c{0%{background:#e15b64}25%{background:#abbd81}50%{background:#f8b26a}75%{background:#f47e60}100%{background:#e15b64}}@-webkit-keyframes loading-ball-c{0%{background:#e15b64}25%{background:#abbd81}50%{background:#f8b26a}75%{background:#f47e60}100%{background:#e15b64}}@-o-keyframes loading-ball-c{0%{background:#e15b64}25%{background:#abbd81}50%{background:#f8b26a}75%{background:#f47e60}100%{background:#e15b64}}@keyframes loading-ball-c{0%{background:#e15b64}25%{background:#abbd81}50%{background:#f8b26a}75%{background:#f47e60}100%{background:#e15b64}}blockquote.pullquote{position:relative;max-width:45%;font-size:110%}blockquote.pullquote.left{float:left;margin:1em .5em 0 0}blockquote.pullquote.right{float:right;margin:1em 0 0 .5em}.video-container{position:relative;overflow:hidden;margin-bottom:16px;padding-top:56.25%;height:0}.video-container iframe{position:absolute;top:0;left:0;margin-top:0;width:100%;height:100%}.hide-block>.hide-button,.hide-inline>.hide-button{display:inline-block;padding:5px 18px;background:#49b1f5;color:var(--white)}.hide-block>.hide-button:hover,.hide-inline>.hide-button:hover{background-color:var(--btn-hover-color)}.hide-block>.hide-button.open,.hide-inline>.hide-button.open{display:none}.hide-block>.hide-button.open+div,.hide-inline>.hide-button.open+div{display:block}.hide-block>.hide-button.open+span,.hide-inline>.hide-button.open+span{display:inline}.hide-block>.hide-content,.hide-inline>.hide-content{display:none}.hide-inline>.hide-button{margin:0 6px}.hide-inline>.hide-content{margin:0 6px}.hide-block{margin:0 0 16px}.toggle{margin-bottom:20px;border:1px solid #f0f0f0}.toggle>.toggle-button{padding:6px 15px;background:#f0f0f0;color:#1f2d3d;cursor:pointer}.toggle>.toggle-content{margin:30px 24px}#article-container .inline-img{display:inline;margin:0 3px;height:1.1em;vertical-align:text-bottom}.hl-label{padding:2px 4px;border-radius:3px;color:#fff}.hl-label.default{background-color:#777}.hl-label.blue{background-color:#428bca}.hl-label.pink{background-color:#ff69b4}.hl-label.red{background-color:red}.hl-label.purple{background-color:#6f42c1}.hl-label.orange{background-color:#ff8c00}.hl-label.green{background-color:#5cb85c}.note{position:relative;margin:0 0 20px;padding:15px;border-radius:3px}.note.icon-padding{padding-left:3em}.note>.note-icon{position:absolute;top:calc(50% - .5em);left:.8em;font-size:larger}.note.blue:not(.disabled){border-left-color:#428bca!important}.note.blue:not(.disabled).modern{border-left-color:transparent!important;color:#428bca}.note.blue:not(.disabled):not(.simple){background:#e3eef7!important}.note.blue>.note-icon{color:#428bca}.note.pink:not(.disabled){border-left-color:#ff69b4!important}.note.pink:not(.disabled).modern{border-left-color:transparent!important;color:#ff69b4}.note.pink:not(.disabled):not(.simple){background:#ffe9f4!important}.note.pink>.note-icon{color:#ff69b4}.note.red:not(.disabled){border-left-color:red!important}.note.red:not(.disabled).modern{border-left-color:transparent!important;color:red}.note.red:not(.disabled):not(.simple){background:#ffd9d9!important}.note.red>.note-icon{color:red}.note.purple:not(.disabled){border-left-color:#6f42c1!important}.note.purple:not(.disabled).modern{border-left-color:transparent!important;color:#6f42c1}.note.purple:not(.disabled):not(.simple){background:#e9e3f6!important}.note.purple>.note-icon{color:#6f42c1}.note.orange:not(.disabled){border-left-color:#ff8c00!important}.note.orange:not(.disabled).modern{border-left-color:transparent!important;color:#ff8c00}.note.orange:not(.disabled):not(.simple){background:#ffeed9!important}.note.orange>.note-icon{color:#ff8c00}.note.green:not(.disabled){border-left-color:#5cb85c!important}.note.green:not(.disabled).modern{border-left-color:transparent!important;color:#5cb85c}.note.green:not(.disabled):not(.simple){background:#e7f4e7!important}.note.green>.note-icon{color:#5cb85c}.note.simple{border:1px solid #eee;border-left-width:5px}.note.modern{border:1px solid transparent!important;background-color:#f5f5f5;color:#4c4948}.note.flat{border:initial;border-left:5px solid #eee;background-color:#f9f9f9;color:#4c4948}.note h2,.note h3,.note h4,.note h5,.note h6{margin-top:3px;margin-bottom:0;padding-top:0!important;border-bottom:initial}.note blockquote:first-child,.note img:first-child,.note ol:first-child,.note p:first-child,.note pre:first-child,.note table:first-child,.note ul:first-child{margin-top:0!important}.note blockquote:last-child,.note img:last-child,.note ol:last-child,.note p:last-child,.note pre:last-child,.note table:last-child,.note ul:last-child{margin-bottom:0!important}.note .img-alt{margin:5px 0 10px}.note:not(.no-icon){padding-left:3em}.note:not(.no-icon)::before{position:absolute;top:calc(50% - .95em);left:.8em;font-size:larger}.note.default.flat{background:#f7f7f7}.note.default.modern{border-color:#e1e1e1;background:#f3f3f3;color:#666}.note.default.modern a:not(.btn){color:#666}.note.default.modern a:not(.btn):hover{color:#454545}.note.default:not(.modern){border-left-color:#777}.note.default:not(.modern) h2,.note.default:not(.modern) h3,.note.default:not(.modern) h4,.note.default:not(.modern) h5,.note.default:not(.modern) h6{color:#777}.note.default:not(.no-icon)::before{content:'\f0a9'}.note.default:not(.no-icon):not(.modern)::before{color:#777}.note.primary.flat{background:#f5f0fa}.note.primary.modern{border-color:#e1c2ff;background:#f3daff;color:#6f42c1}.note.primary.modern a:not(.btn){color:#6f42c1}.note.primary.modern a:not(.btn):hover{color:#453298}.note.primary:not(.modern){border-left-color:#6f42c1}.note.primary:not(.modern) h2,.note.primary:not(.modern) h3,.note.primary:not(.modern) h4,.note.primary:not(.modern) h5,.note.primary:not(.modern) h6{color:#6f42c1}.note.primary:not(.no-icon)::before{content:'\f055'}.note.primary:not(.no-icon):not(.modern)::before{color:#6f42c1}.note.info.flat{background:#eef7fa}.note.info.modern{border-color:#b3e5ef;background:#d9edf7;color:#31708f}.note.info.modern a:not(.btn){color:#31708f}.note.info.modern a:not(.btn):hover{color:#215761}.note.info:not(.modern){border-left-color:#428bca}.note.info:not(.modern) h2,.note.info:not(.modern) h3,.note.info:not(.modern) h4,.note.info:not(.modern) h5,.note.info:not(.modern) h6{color:#428bca}.note.info:not(.no-icon)::before{content:'\f05a'}.note.info:not(.no-icon):not(.modern)::before{color:#428bca}.note.success.flat{background:#eff8f0}.note.success.modern{border-color:#d0e6be;background:#dff0d8;color:#3c763d}.note.success.modern a:not(.btn){color:#3c763d}.note.success.modern a:not(.btn):hover{color:#32562c}.note.success:not(.modern){border-left-color:#5cb85c}.note.success:not(.modern) h2,.note.success:not(.modern) h3,.note.success:not(.modern) h4,.note.success:not(.modern) h5,.note.success:not(.modern) h6{color:#5cb85c}.note.success:not(.no-icon)::before{content:'\f058'}.note.success:not(.no-icon):not(.modern)::before{color:#5cb85c}.note.warning.flat{background:#fdf8ea}.note.warning.modern{border-color:#fae4cd;background:#fcf4e3;color:#8a6d3b}.note.warning.modern a:not(.btn){color:#8a6d3b}.note.warning.modern a:not(.btn):hover{color:#714f30}.note.warning:not(.modern){border-left-color:#f0ad4e}.note.warning:not(.modern) h2,.note.warning:not(.modern) h3,.note.warning:not(.modern) h4,.note.warning:not(.modern) h5,.note.warning:not(.modern) h6{color:#f0ad4e}.note.warning:not(.no-icon)::before{content:'\f06a'}.note.warning:not(.no-icon):not(.modern)::before{color:#f0ad4e}.note.danger.flat{background:#fcf1f2}.note.danger.modern{border-color:#ebcdd2;background:#f2dfdf;color:#a94442}.note.danger.modern a:not(.btn){color:#a94442}.note.danger.modern a:not(.btn):hover{color:#84333f}.note.danger:not(.modern){border-left-color:#d9534f}.note.danger:not(.modern) h2,.note.danger:not(.modern) h3,.note.danger:not(.modern) h4,.note.danger:not(.modern) h5,.note.danger:not(.modern) h6{color:#d9534f}.note.danger:not(.no-icon)::before{content:'\f056'}.note.danger:not(.no-icon):not(.modern)::before{color:#d9534f}#article-container .tabs{position:relative;margin:0 0 20px;border-right:1px solid var(--tab-border-color);border-bottom:1px solid var(--tab-border-color);border-left:1px solid var(--tab-border-color)}#article-container .tabs>.nav-tabs{display:-webkit-box;display:-moz-box;display:-webkit-flex;display:-ms-flexbox;display:box;display:flex;-webkit-box-lines:multiple;-moz-box-lines:multiple;-o-box-lines:multiple;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;margin:0;padding:0;background:var(--tab-botton-bg)}#article-container .tabs>.nav-tabs>.tab{-webkit-box-flex:1;-moz-box-flex:1;-o-box-flex:1;-ms-box-flex:1;box-flex:1;-webkit-flex-grow:1;flex-grow:1;padding:8px 18px;border-top:2px solid var(--tab-border-color);background:var(--tab-botton-bg);color:var(--tab-botton-color);line-height:2;-webkit-transition:all .4s;-moz-transition:all .4s;-o-transition:all .4s;-ms-transition:all .4s;transition:all .4s}#article-container .tabs>.nav-tabs>.tab i{width:1.5em}#article-container .tabs>.nav-tabs>.tab.active{border-top:2px solid #49b1f5;background:var(--tab-button-active-bg);cursor:default}#article-container .tabs>.nav-tabs>.tab:not(.active):hover{border-top:2px solid var(--tab-button-hover-bg);background:var(--tab-button-hover-bg)}#article-container .tabs>.nav-tabs.no-default~.tab-to-top{display:none}#article-container .tabs>.tab-contents .tab-item-content{position:relative;display:none;padding:36px 24px 10px}@media screen and (max-width:768px){#article-container .tabs>.tab-contents .tab-item-content{padding:24px 14px}}#article-container .tabs>.tab-contents .tab-item-content.active{display:block;-webkit-animation:tabshow .5s;-moz-animation:tabshow .5s;-o-animation:tabshow .5s;-ms-animation:tabshow 0.5s;animation:tabshow .5s}#article-container .tabs>.tab-contents .tab-item-content>:last-child{margin-bottom:0}#article-container .tabs>.tab-to-top{padding:0 16px 10px 0;width:100%;text-align:right}#article-container .tabs>.tab-to-top button{color:#99a9bf}#article-container .tabs>.tab-to-top button:hover{color:#49b1f5}@-moz-keyframes tabshow{0%{-webkit-transform:translateY(15px);-moz-transform:translateY(15px);-o-transform:translateY(15px);-ms-transform:translateY(15px);transform:translateY(15px)}100%{-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@-webkit-keyframes tabshow{0%{-webkit-transform:translateY(15px);-moz-transform:translateY(15px);-o-transform:translateY(15px);-ms-transform:translateY(15px);transform:translateY(15px)}100%{-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@-o-keyframes tabshow{0%{-webkit-transform:translateY(15px);-moz-transform:translateY(15px);-o-transform:translateY(15px);-ms-transform:translateY(15px);transform:translateY(15px)}100%{-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}@keyframes tabshow{0%{-webkit-transform:translateY(15px);-moz-transform:translateY(15px);-o-transform:translateY(15px);-ms-transform:translateY(15px);transform:translateY(15px)}100%{-webkit-transform:translateY(0);-moz-transform:translateY(0);-o-transform:translateY(0);-ms-transform:translateY(0);transform:translateY(0)}}#article-container .timeline{margin:0 0 20px 10px;padding:14px 20px 5px;border-left:2px solid var(--timeline-color,#49b1f5)}#article-container .timeline.blue{--timeline-color:#428bca;--timeline-bg:rgba(66,139,202, 0.2)}#article-container .timeline.pink{--timeline-color:#ff69b4;--timeline-bg:rgba(255,105,180, 0.2)}#article-container .timeline.red{--timeline-color:#f00;--timeline-bg:rgba(255,0,0, 0.2)}#article-container .timeline.purple{--timeline-color:#6f42c1;--timeline-bg:rgba(111,66,193, 0.2)}#article-container .timeline.orange{--timeline-color:#ff8c00;--timeline-bg:rgba(255,140,0, 0.2)}#article-container .timeline.green{--timeline-color:#5cb85c;--timeline-bg:rgba(92,184,92, 0.2)}#article-container .timeline .timeline-item{margin:0 0 15px}#article-container .timeline .timeline-item:hover .item-circle:before{border-color:var(--timeline-color,#49b1f5)}#article-container .timeline .timeline-item.headline .timeline-item-title .item-circle>p{font-weight:600;font-size:1.2em}#article-container .timeline .timeline-item.headline .timeline-item-title .item-circle:before{left:-28px;border:4px solid var(--timeline-color,#49b1f5)}#article-container .timeline .timeline-item.headline:hover .item-circle:before{border-color:var(--pseudo-hover)}#article-container .timeline .timeline-item .timeline-item-title{position:relative}#article-container .timeline .timeline-item .item-circle:before{position:absolute;top:50%;left:-27px;width:6px;height:6px;border:3px solid var(--pseudo-hover);border-radius:50%;background:var(--card-bg);content:'';-webkit-transition:all .3s;-moz-transition:all .3s;-o-transition:all .3s;-ms-transition:all .3s;transition:all .3s;-webkit-transform:translate(0,-50%);-moz-transform:translate(0,-50%);-o-transform:translate(0,-50%);-ms-transform:translate(0,-50%);transform:translate(0,-50%)}#article-container .timeline .timeline-item .item-circle>p{margin:0 0 8px;font-weight:500}#article-container .timeline .timeline-item .timeline-item-content{position:relative;padding:12px 15px;border-radius:8px;background:var(--timeline-bg,#e4f3fd);font-size:.93em}#article-container .timeline .timeline-item .timeline-item-content>:last-child{margin-bottom:0}#article-container .timeline+.timeline{margin-top:-20px}[data-theme=dark]{--global-bg:#0d0d0d;--font-color:rgba(255,255,255,0.7);--hr-border:rgba(255,255,255,0.4);--hr-before-color:rgba(255,255,255,0.7);--search-bg:#121212;--search-input-color:rgba(255,255,255,0.7);--search-a-color:rgba(255,255,255,0.7);--preloader-bg:#0d0d0d;--preloader-color:rgba(255,255,255,0.7);--tab-border-color:#2c2c2c;--tab-botton-bg:#2c2c2c;--tab-botton-color:rgba(255,255,255,0.7);--tab-button-hover-bg:#383838;--tab-button-active-bg:#121212;--card-bg:#121212;--sidebar-bg:#121212;--btn-hover-color:#787878;--btn-color:rgba(255,255,255,0.7);--btn-bg:#1f1f1f;--text-bg-hover:#383838;--light-grey:rgba(255,255,255,0.7);--dark-grey:rgba(255,255,255,0.2);--white:rgba(255,255,255,0.9);--text-highlight-color:rgba(255,255,255,0.9);--blockquote-color:rgba(255,255,255,0.7);--blockquote-bg:#2c2c2c;--reward-pop:#2c2c2c;--toc-link-color:rgba(255,255,255,0.6);--scrollbar-color:#525252;--timeline-bg:#1f1f1f;--zoom-bg:#121212;--mark-bg:rgba(0,0,0,0.6)}[data-theme=dark] #web_bg:before{position:absolute;width:100%;height:100%;background-color:rgba(0,0,0,.7);content:''}[data-theme=dark] #article-container code{background:#2c2c2c}[data-theme=dark] #article-container pre>code{background:#171717}[data-theme=dark] #article-container figure.highlight{-webkit-box-shadow:none;box-shadow:none}[data-theme=dark] #article-container .note code{background:rgba(27,31,35,.05)}[data-theme=dark] #article-container .aplayer{filter:brightness(.8)}[data-theme=dark] #article-container kbd{border-color:#696969;background-color:#525252;color:#e2f1ff}[data-theme=dark] #page-header.nav-fixed>#nav,[data-theme=dark] #page-header.not-top-img>#nav{background:rgba(18,18,18,.8);-webkit-box-shadow:0 5px 6px -5px rgba(133,133,133,0);box-shadow:0 5px 6px -5px rgba(133,133,133,0)}[data-theme=dark] #post-comment .comment-switch{background:#2c2c2c!important}[data-theme=dark] #post-comment .comment-switch #switch-btn{filter:brightness(.8)}[data-theme=dark] .note{filter:brightness(.8)}[data-theme=dark] #article-container iframe,[data-theme=dark] .ads-wrap,[data-theme=dark] .btn-beautify,[data-theme=dark] .error-img,[data-theme=dark] .gist,[data-theme=dark] .hide-button,[data-theme=dark] .hl-label,[data-theme=dark] .post-outdate-notice{filter:brightness(.8)}[data-theme=dark] img{filter:brightness(.8)}[data-theme=dark] #aside-content .aside-list>.aside-list-item:not(:last-child){border-bottom:1px dashed rgba(255,255,255,.1)}[data-theme=dark] #gitalk-container{filter:brightness(.8)}[data-theme=dark] #gitalk-container svg{fill:rgba(255,255,255,0.9)!important}[data-theme=dark] #disqusjs #dsqjs .dsqjs-no-comment,[data-theme=dark] #disqusjs #dsqjs .dsqjs-tab-active,[data-theme=dark] #disqusjs #dsqjs:focus,[data-theme=dark] #disqusjs #dsqjs:hover{color:rgba(255,255,255,.7)}[data-theme=dark] #disqusjs #dsqjs .dsqjs-order-label{background-color:#1f1f1f}[data-theme=dark] #disqusjs #dsqjs .dsqjs-post-body{color:rgba(255,255,255,.7)}[data-theme=dark] #disqusjs #dsqjs .dsqjs-post-body code,[data-theme=dark] #disqusjs #dsqjs .dsqjs-post-body pre{background:#2c2c2c}[data-theme=dark] #disqusjs #dsqjs .dsqjs-post-body blockquote{color:rgba(255,255,255,.7)}[data-theme=dark] #artitalk_main #lazy{background:#121212}[data-theme=dark] #operare_artitalk .c2{background:#121212}@media screen and (max-width:900px){[data-theme=dark] #card-toc{background:#1f1f1f}}.read-mode{--font-color:#4c4948;--readmode-light-color:#fff;--white:#4c4948;--light-grey:#4c4948;--gray:#d6dbdf;--hr-border:#d6dbdf;--hr-before-color:#b9c2c9;--highlight-bg:#f7f7f7;--exit-btn-bg:#c0c0c0;--exit-btn-color:#fff;--exit-btn-hover:#8d8d8d;--pseudo-hover:none}[data-theme=dark] .read-mode{--font-color:rgba(255,255,255,0.7);--readmode-light-color:#0d0d0d;--white:rgba(255,255,255,0.9);--light-grey:rgba(255,255,255,0.7);--gray:rgba(255,255,255,0.7);--hr-border:rgba(255,255,255,0.5);--hr-before-color:rgba(255,255,255,0.7);--highlight-bg:#171717;--exit-btn-bg:#1f1f1f;--exit-btn-color:rgba(255,255,255,0.9);--exit-btn-hover:#525252}.read-mode{background:var(--readmode-light-color)}.read-mode .exit-readmode{position:fixed;top:30px;right:30px;z-index:100;width:40px;height:40px;border-radius:8px;background:var(--exit-btn-bg);color:var(--exit-btn-color);font-size:16px;-webkit-transition:background .3s;-moz-transition:background .3s;-o-transition:background .3s;-ms-transition:background .3s;transition:background .3s}@media screen and (max-width:768px){.read-mode .exit-readmode{top:initial;bottom:30px}}.read-mode .exit-readmode:hover{background:var(--exit-btn-hover)}.read-mode #aside-content{display:none}.read-mode #page-header.post-bg{background:0 0!important}.read-mode #page-header.post-bg:before{opacity:0}.read-mode #page-header.post-bg>#post-info{text-align:center}.read-mode #post{margin:0 auto;background:0 0;-webkit-box-shadow:none;box-shadow:none}.read-mode #post:hover{-webkit-box-shadow:none;box-shadow:none}.read-mode>canvas{display:none!important}.read-mode #footer,.read-mode #nav,.read-mode #post>:not(#post-info):not(.post-content),.read-mode #rightside,.read-mode #web_bg,.read-mode .highlight-tools,.read-mode .not-top-img,.read-mode .post-outdate-notice{display:none!important}.read-mode #article-container a{color:#99a9bf}.read-mode #article-container .highlight:not(.js-file-line-container),.read-mode #article-container pre{background:var(--highlight-bg)!important}.read-mode #article-container .highlight:not(.js-file-line-container) *,.read-mode #article-container pre *{color:var(--font-color)!important}.read-mode #article-container figure.highlight{border-radius:0!important;-webkit-box-shadow:none!important;box-shadow:none!important}.read-mode #article-container figure.highlight>:not(.highlight-tools){display:block!important}.read-mode #article-container figure.highlight .line:before{color:var(--font-color)!important}.read-mode #article-container figure.highlight .hljs{background:var(--highlight-bg)!important}.read-mode #article-container h1,.read-mode #article-container h2,.read-mode #article-container h3,.read-mode #article-container h4,.read-mode #article-container h5,.read-mode #article-container h6{padding:0}.read-mode #article-container h1:before,.read-mode #article-container h2:before,.read-mode #article-container h3:before,.read-mode #article-container h4:before,.read-mode #article-container h5:before,.read-mode #article-container h6:before{content:''}.read-mode #article-container h1:hover,.read-mode #article-container h2:hover,.read-mode #article-container h3:hover,.read-mode #article-container h4:hover,.read-mode #article-container h5:hover,.read-mode #article-container h6:hover{padding:0}.read-mode #article-container li:hover:before,.read-mode #article-container ol:hover:before,.read-mode #article-container ul:hover:before{-webkit-transform:none!important;-moz-transform:none!important;-o-transform:none!important;-ms-transform:none!important;transform:none!important}.read-mode #article-container li:before,.read-mode #article-container ol:before{background:0 0!important;color:var(--font-color)!important}.read-mode #article-container ul>li:before{border-color:var(--gray)!important}.read-mode #article-container .tabs{border:2px solid var(--tab-border-color)}.read-mode #article-container .tabs>.nav-tabs{background:0 0}.read-mode #article-container .tabs>.nav-tabs>.tab{border-top:none!important}.read-mode #article-container .tabs>.tab-contents .tab-item-content.active{-webkit-animation:none;-moz-animation:none;-o-animation:none;-ms-animation:none;animation:none}.read-mode #article-container code{color:var(--font-color)}.read-mode #article-container blockquote{border-color:var(--gray);background-color:var(--readmode-light-color)}.read-mode #article-container kbd{border:1px solid var(--gray);background-color:transparent;-webkit-box-shadow:none;box-shadow:none;color:var(--font-color)}.read-mode #article-container .hide-toggle{border:1px solid var(--gray)!important}.read-mode #article-container .btn-beautify,.read-mode #article-container .hide-button,.read-mode #article-container .hl-label{border:1px solid var(--gray)!important;background:var(--readmode-light-color)!important;color:var(--font-color)!important}.read-mode #article-container .note{border:2px solid var(--gray);border-left-color:var(--gray)!important;filter:none;background-color:var(--readmode-light-color)!important;color:var(--font-color)}.read-mode #article-container .note .note-icon,.read-mode #article-container .note:before{color:var(--font-color)}.search-dialog{position:fixed;top:10%;left:50%;z-index:1001;display:none;margin-left:-300px;padding:20px;width:600px;border-radius:8px;background:var(--search-bg);--search-height:100vh}@media screen and (max-width:768px){.search-dialog{top:0;left:0;margin:0;width:100%;height:100%;border-radius:0}}.search-dialog hr{margin:20px auto}.search-dialog .search-nav{margin:0 0 14px;color:#49b1f5;font-size:1.4em;line-height:1}.search-dialog .search-nav .search-dialog-title{margin-right:10px}.search-dialog .search-nav .search-close-button{float:right;color:#858585;-webkit-transition:color .2s ease-in-out;-moz-transition:color .2s ease-in-out;-o-transition:color .2s ease-in-out;-ms-transition:color .2s ease-in-out;transition:color .2s ease-in-out}.search-dialog .search-nav .search-close-button:hover{color:#49b1f5}.search-dialog hr{margin:20px auto}#search-mask{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1000;display:none;background:rgba(0,0,0,.6)}#algolia-search .search-dialog .ais-SearchBox input{padding:5px 14px;width:100%;outline:0;border:2px solid #49b1f5;border-radius:40px;background:var(--search-bg);color:var(--search-input-color)}#algolia-search .search-dialog .ais-Hits-list{margin:0;padding:0}#algolia-search .search-dialog .ais-Hits-list a{color:var(--search-a-color)}#algolia-search .search-dialog .ais-Hits-list a:hover{color:#49b1f5}#algolia-search .search-dialog .ais-Hits-list mark{background:0 0;color:#f47466;font-weight:700}#algolia-search .search-dialog .algolia-hits-item-title{font-weight:600}#algolia-search .search-dialog .algolia-hit-item-content{margin:0 0 8px;word-break:break-word}#algolia-search .search-dialog .ais-Pagination{margin:15px 0 0;padding:0;text-align:center}#algolia-search .search-dialog .ais-Pagination .ais-Pagination-list{margin:0;padding:0;list-style:none}#algolia-search .search-dialog .ais-Pagination .ais-Pagination-item{display:inline;margin:0 4px;padding:0}#algolia-search .search-dialog .ais-Pagination .ais-Pagination-item .ais-Pagination-link{display:inline-block;min-width:24px;height:24px;text-align:center;line-height:24px}#algolia-search .search-dialog .ais-Pagination .ais-Pagination-item--selected a{background:#00c4b6;color:#eee;cursor:default}#algolia-search .search-dialog .ais-Pagination .ais-Pagination-item--disabled{visibility:hidden}#algolia-search .search-dialog #algolia-hits>div{overflow-y:scroll;margin:0 -20px;padding:0 22px;max-height:calc(80vh - 240px)}@media screen and (max-width:768px){#algolia-search .search-dialog #algolia-hits>div{max-height:none;height:calc(var(--search-height) - 265px)}}#algolia-search .search-dialog #algolia-info div{display:inline}#algolia-search .search-dialog #algolia-info .algolia-poweredBy{float:right;vertical-align:text-top}#algolia-search .search-dialog #algolia-info .algolia-poweredBy svg{height:1.1em} \ No newline at end of file diff --git a/placeholder b/css/var.css similarity index 100% rename from placeholder rename to css/var.css diff --git a/d054e8a4.html b/d054e8a4.html new file mode 100644 index 000000000..2e1bd92e3 --- /dev/null +++ b/d054e8a4.html @@ -0,0 +1 @@ +影视节前端网站,,以前东拼西凑的,前端课程设计应该可以吧 | 梦洁小站-属于你我的小天地

    影视节前端网站,,以前东拼西凑的,前端课程设计应该可以吧

    介绍

    图片展示

    主页

    影片展示

    优秀演员

    获奖名单

    奖项介绍

    舔狗日志(填充字段)

    舔狗日记7月15日 晴

    今天我还是照常给你发消息,汇报日常工作,你终于回了我四个字:“嗯嗯,好的”你开始愿意敷衍我了,我太感动了受宠若惊。我愿意天天给你发消息。就算你天天骂我,我也不觉得烦。

    舔狗日记 7月14日 阴

    你昨天晚上又没会我的消息,在我孜孜不倦的骚扰下,你终于舍得回我了,你说“滚”,这其中一定有什么含义,我想了很久,滚是三点水,这代表你对我的思念也如滚滚流水一样汹涌,我感动哭了,不知道你现在在干嘛,我很想你。

    舔狗日记 7月13日 雨

    你说你想买口红,今天我去了叔叔的口罩厂做了一天的打包。拿到了两百块钱,加上我这几天省下的钱刚好能给你买一根小金条。即没有给我自己剩下一分钱,但你不用担心,因为厂里包吃包住。对了打包的时候,满脑子都是你,想着你哪天突然就接受我的橄榄枝了呢。而且今天我很棒呢,主管表扬我很能干,其实也有你的功劳啦,是你给了我无穷的力量。今天我比昨天多想你一点,比明天少想你一点。

    舔狗日记 7月13日 晴

    你说你想买AJ,今天我去了叔叔的口罩厂做了一天的打包。拿到了两百块钱,加上我这几天省下的钱刚好能给你买一个鞋盒。即没有给我自己剩下一分钱,但你不用担心,因为厂里包吃包住。对了打包的时候,满脑子都是你,想着你哪天突然就接受我的橄榄枝了呢。而且今天我很棒呢,主管表扬我很能干,其实也有你的功劳啦,是你给了我无穷的力量。今天我比昨天多想你一点,比明天少想你一点。

    舔狗日记 7月12日 阴

    听说你想要一套化妆品,我算了算,明天我去公司里面扫一天厕所,就可以拿到200块钱,再加上我上个月攒下来的零花钱,刚好给你买一套迪奥。

    保安日志 7月11日 晴

    在我入职保安的那天,队长问我:你知道你要保护谁嘛?

    我嘴上说的是业主,心里却是你。over

    舔狗反击日记 7月10日 晴

    今天我对她说:我想问一下,爱奇艺会员,你有吗?

    她没发现是“我爱你”的藏头诗,还骂我穷鬼,让我滚。滚就滚,我看不了青春有你,我的青春也没有你。

    舔狗日记 7月9日

    昨晚你和朋友大佬一晚上游戏,你破天荒的给我看了你的战绩,虽然我看不懂但我相信你一定是最厉害的,最棒的!我给你发了好多消息夸你,告诉你我多崇拜你,你回了我一句:啥b

    我翻来覆去思考这是什么意思?sh-a傻,噢你的意思是说我傻,那b就是baby的意思了吧,原来你是在叫我傻宝,这么宠溺的语气,我竟一时不相信,其实你也是喜欢我的对吧

    女王日记 7月8日

    昨晚本王和朋友打了一晚上游戏,我破天荒的的给17号舔狗看了战绩,虽然他看不懂但是他相信本王一定是最厉害的,最棒的!他给本王发了好多消息,告诉我有多崇拜我,我回了他一句“啥b”。并不是因为什么,只是刚刚男神给我打电话,叫我去宾馆。哎一晚上整的我死去活来,第二天就给他打电话叫他来接我,才发现他竟然改了ID,“我是你的啥b啊”

    舔狗日记 7月7日 天气晴

    今天表白被拒绝了,她对我说能不能脱下裤子撒泼尿照照自己,当我脱下裤子,她咽了口水,说我们可以试一下。

    声明:摘录于网络,如有不当之处,侵删

    舔狗日记 7月6日 暴雨

    刚从派出所出来,原因前几天14号情人节,我想送你礼物,我去偷东西的时候被抓了,我本来想反抗,警察说了一句老实点别动,我立刻就放弃了反抗,因为我记得你说过,你喜欢老实人。

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/d054e8a4.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/d1a0c593.html b/d1a0c593.html new file mode 100644 index 000000000..3205cc325 --- /dev/null +++ b/d1a0c593.html @@ -0,0 +1 @@ +前端面试之手写call,apply,bind | 梦洁小站-属于你我的小天地

    前端面试之手写call,apply,bind

    了解下this的指向

    • 普通函数当中的this指向window
    • 对象身上的方法指向这个对象
    • 箭头函数的this执行上层作用域下的this

    手写call

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22

    Function.prototype.myCall = function(context,...arg){
    context = context!== undefined && context !== null ? Object(context) : window;
    let callFn = this;//获取call
    let symbolTemp = Symbol('临时');//临时存储
    context[symbolTemp] = callFn;
    let result = context[symbolTemp](...arg);
    delete context[symbolTemp];//用后删除
    console.log(context[symbolTemp]);//undefine
    return result;
    }
    const testExample = {
    x: 42,
    getX: function (a,b,c) {
    console.log('传入的参数',a,b,c);
    return this.x;
    },
    };
    const test = testExample.getX;
    // console.log( test.call(testExample)); 相当于变为了testExample.test();
    console.log(test.myCall(testExample,10,20,30));

    手写apply

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
     Function.prototype.myApply = function(context,argArray){
    context = context !== undefined && context !== null ? Object(context) : window;
    argArray = argArray?.length ? argArray : [];
    let callFn = this;
    let symbolTemp = Symbol('临时存储');
    context[symbolTemp] = callFn;
    return context[symbolTemp](...argArray)
    }
    const testExample = {
    x: 42,
    getX: function (a,b,c,d) {
    console.log('查看参数',a,b,c,d);
    return this.x;
    },
    };
    const test = testExample.getX;
    // console.log(test.apply(testExample,[10,20,30,40])); //官方用法 相当变为了testExample.test(10,20,30,40);
    console.log(test.myApply(testExample,[10,20,30,40]));

    手写bind

    • 最简单版本
      • 利用了对象身上的方法被执行的时候指向的是这个对象的原理
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    Function.prototype.myBind = function(context,...args){
    context = context !== undefined && context !== null ? Object(context) : window;
    const callFn = this;
    const symbolTemp = Symbol('临时对象')
    context[symbolTemp] = callFn;
    return function(...argArray){
    let result = context[symbolTemp](...argArray,...args);
    delete context[symbolTemp];//用后删除
    console.log(context[symbolTemp]);//undefine
    return result
    }
    }
    const testExample = {
    x: 42,
    getX: function () {
    return this.x;
    },
    };
    const test = testExample.getX;
    // console.log(test.bind(testExample)()); //官方用法
    console.log( test.myBind(testExample) ());

    chat-gpt修改意见-以apply为例

    • 首先,对于传入的 context 参数,您已经考虑了一些边界情况,例如 undefinednull,并使用 Object(context) 将其转换为对象。但是,您的实现并没有考虑传递非对象的原始值(例如字符串或数字),在这种情况下,您的代码将抛出 TypeError。您可以通过使用 Object(context) 来强制将原始值转换为其包装对象,以避免这种情况。

    • 其次,对于传入的 argArray 参数,您使用了可选链式调用 ?. 来检查它是否为 null 或 undefined,并且使用 length 属性来检查其是否为空数组。这是一个很好的实践,但是如果您的代码要在较早的 JavaScript 版本中运行,则可能会出现问题。在这种情况下,您可以使用更传统的 if 语句来进行检查,例如 if (!argArray || argArray.length === 0) { argArray = []; }

    • 最后,您的实现使用了一个唯一的 Symbol 来存储函数,并将其存储在上下文对象中,然后使用调用运算符 () 来调用该函数并传递参数数组。这是一种非常聪明和安全的方法来确保不会意外覆盖上下文对象中的现有属性。但是,如果您的代码需要在旧版本的 JavaScript 中运行,则可能需要使用字符串键来存储函数,例如 context['__myApply_temp_fn'] = callFn;

    • 下面是经过改进的代码:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      Function.prototype.myApply = function(context, argArray) {
      context = Object(context);
      argArray = (argArray && argArray.length) ? argArray : [];
      let callFn = this;
      let tempKey = '__myApply_temp_fn';
      context[tempKey] = callFn;
      let result = context[tempKey](...argArray);
      delete context[tempKey];
      return result;
      };

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/d1a0c593.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/d1af866b.html b/d1af866b.html new file mode 100644 index 000000000..a68eaf3ab --- /dev/null +++ b/d1af866b.html @@ -0,0 +1 @@ +今日刷题-四舍五入等于... | 梦洁小站-属于你我的小天地

    今日刷题-四舍五入等于...

    题目1

    1
    2
    3
    4
    5
    Math.round(-2019.5)的结果是
    A: 2019
    B: -2019
    C: 2020
    D: -2020
    • 答案

      • B
    • 解析

      • 不多说,了解到的一个技巧,Math.round() 原来的数字加上0.5 然后向下取整就是最终值

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        // 例子
        //2019.49 + 0.5 = 2019.99 向下取整 2019
        x=Math.round(2019.49);

        //2019.5 + 0.5 = 2020.0 向下取整 2020
        x=Math.round(2019.5);

        //-2019.79 + 0.5 = -2019.39 向下取整 2020
        x=Math.round(-2019.79);

        //-2019.51 + 0.5 = -2019.01 向下取整 -2020
        x=Math.round(-2019.51);

    题目2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    执行以下代码,输出的a值为()
    if(! "a" in window){
    var a = 1;
    }
    alert(a);
    A: null
    B: 1
    C: undefined
    D: 其他选项均不正确
    • 答案

      • C
    • 解析

      • A in B 用于判断A是否在B的属性上或者是原型链上(官方解释为如果指定的属性在指定的对象或其原型链中,则**in 运算符**返回true。)

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        //   "a" 为字符串,会存在变量提升(不可能存在没有类型的一个变量!)

        //判断全局对象window中是否有变量a,如果没有变量a,就进入判断将a赋值为1
        if(! "a" in window){
        // 判断 "a" 是否是在window的属性或者原型链上
        // 如果为true ,那么取反为 false,则不执行if语句
        // 如果为false , 那么取反为true,则执行if语句
        // 由于变量的提前声明特性,在执行这段代码之后,全局对象window中就已经存在a这个变量了,所以不进入if
        var a = 1;
        }
        alert(a);//输出undefined
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/d1af866b.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/d236288b.html b/d236288b.html new file mode 100644 index 000000000..425d230cb --- /dev/null +++ b/d236288b.html @@ -0,0 +1 @@ +使用优启通(EasyU)重装系统教程(详细) | 梦洁小站-属于你我的小天地

    使用优启通(EasyU)重装系统教程(详细)

    前言

    • 为什么要制作一个启动盘才能重装系统?

    通俗点来说就是你自己生病了,自己一般不能自己帮自己看病,一般都是别人看病。电脑也是如此,需要一个第三方来“治疗”。

    • 为什么使用EasyU?而不使用老毛_,巴拉巴拉。。。。。

    因为其他的很多启动盘制作工具有广告,会在系统安装的时候安装第三方软件。

    资源下载

    • 启动盘制作工具(任选一个下载地址):

    下载地址1:123盘(不限速)

    下载地址2:百度网盘

    • 系统镜像:

    http://y-os.net/(第三方修改镜像)

    ITELLYOU.CN.(官方原版镜像)

    https://www.winos.me/(第三方修改镜像)

    • 激活工具:

    EasyActivate

    第一步:制作启动盘

    1. 下载后解压文件夹,选中这个EasyU

    1. 选中自己的u盘,然后点击全新制作

      U盘会格式化!!!!,在制作启动盘之前先备份U盘文件!!!

    1. 单击确定

    注意看是否是自己的u盘,比如说金士顿的,一般里面会有kingston,闪迪的SanDisk等等,注意看自己有没有选错了U盘

    1. 等待制作完成

    (这个是EasyU Vip版本的,功能更多点,使用制作起来会比较慢)

    等啊等

    1. 制作启动盘完成

    1. 单击退出

    7.制作好后的启动盘在磁盘当中是这样子的

    第二步:下载镜像并存入u盘

    这里随便找了一个别人封装好的系统镜像

    好了,放进去了

    第三步:正式开始重装

    1. 进入启动盘,很多笔记本是电脑开机后,一直按ESC,等到出现,就像这样子(各个品牌不同,有的F2 F12,具体可以百度搜索品牌+进入启动项快捷键)

    1. 我们选择UEFIXXXXX开头的,如果 UEFI:Generic STORAGE DEVICE 1404,Partition 1启动失败,那么我们选择 UEFI:Generic STORAGE DEVICE 1404,Partition 2
    2. 选择一个进入mini系统~,我选择的是[3] UEFI WINDOWS 10 PE X64

    1. 进入mini系统后,我们选择红色圈圈(EIX系统安装)

    1. 我们点击这方框这三个(按照1,2,3顺序点击)

    最后才点击一键恢复!!!

    1. 关闭 恢复完成后自动运行万能驱动 选择, 之后再次点击确定

    1. 等待安装完成

    2. 安装完后过段时间会自动启动系统,等待系统安装完成

    第四步:激活系统

    完成系统激活

    注意事项

    1. 系统安装完成后系统会自动联网安装驱动,比如显卡驱动
    2. 以防万一,最好自己用软件装下驱动,比如360驱动大师,驱动总裁
    3. 下面二个必装运行库(不装后面很多软件会报错导致打不开)

    微软常用运行库合集提取码1fa4 解压密码yrxitong.com

    DirectX.Repair

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/d236288b.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/d2befc3c.html b/d2befc3c.html new file mode 100644 index 000000000..ee404a96b --- /dev/null +++ b/d2befc3c.html @@ -0,0 +1 @@ +知乎日报项目学习笔记 | 梦洁小站-属于你我的小天地

    知乎日报项目学习笔记

    项目完工

    初始化项目

    ts方式(此项目以ts运行)

    1
    create-react-app zhihu-daily --template typescript
    • 没有安装create-react-app的同学,请使用npx命令
    1
    npx create-react-app zhihu-daily --template typescript

    js方式

    • 删除后面的typescript即可

    Rem响应式处理

    手动处理

    • 我们制作移动端网页的时候,需要考虑兼容性,比如我们UI给出的原型图是以iPhone5/6或者其他手机尺寸为参考的,这里就设置设计稿的宽度为375px,同时为了方便计算,我们设置1rem = 100px

    • 然后我们测量UI图的尺寸的时候,就**默认除以100,**这样子就得到了rem单位

    • 但是呢,每一人的手机不一定是375px宽度,我们在375宽度下设置了1rem = 100px,其他手机宽度的转换公式就如下

    • 最后就可以得到在不同手机上1rem应该等于多少px计算公式( 设备宽度 x 100 / 375 = ?px )

    • 知道了原理,书写下代码
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
    *{
    margin: 0;
    padding: 0;
    }
    html{
    /* 默认设置为100px */
    font-size: 100px;
    }
    #abc{
    width: 2rem;
    height: 1rem;
    font-size:0.18rem ;
    background-color: rebeccapurple;
    }
    </style>
    </head>
    <body>
    <div id="abc">
    你好
    </div>
    <script>
    (() => {
    const computed = () => {
    const html = document.documentElement;//获取html元素
    const deviceWidth = html.clientWidth;//获取设备宽度
    const designWidth = 375;//设计图的宽度
    const ratio = deviceWidth * 100 / designWidth;

    /* 这里你可以默认缩放,也可以设置超出设计图不进行扩展 */
    // if(deviceWidth > designWidth) {
    // html.style.fontSize = '100px';
    // return;
    // }
    html.style.fontSize = ratio + 'px';
    }
    computed();
    window.addEventListener('resize',computed);
    })();
    </script>

    </body>
    </html>

    自动处理

    • postcss-pxtorem:将px转换为px
    • amfe-flexible:为html、body添加font-size,窗口调整时候重新设置font-size
    • 安装
    1
    2
    npm install amfe-flexible -S
    npm install postcss-pxtorem -D
    • 在主入口文件引入amfe-flexible
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import React from 'react';
    import ReactDOM from 'react-dom/client';
    import App from './App';
    import "amfe-flexible"
    const root = ReactDOM.createRoot(document.getElementById('root'));
    root.render(
    <React.StrictMode>
    <App />
    </React.StrictMode>
    );
    • 配置postcss-pxtorem,可vue.config.js.postcssrc.jspostcss.config.js其中之一配置,权重从左到右降低,没有则新建文件,只需要设置其中一个即可:

    • 如果是react项目一开始没有eject,就需要安装下CRACO,这里就以这个为例子(好像还有react-app-rewired)

    1
    npm install  @craco/craco --save
    • 在项目根目录下创建配置文件craco.config.js,并根据实际情况完善配置
    1
    2
    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
    module.exports = {
    style: {
    postcss: {
    mode: 'extends',
    loaderOptions: {
    postcssOptions: {
    ident: 'postcss',
    plugins: [
    [
    'postcss-pxtorem',
    {
    rootValue: 750/10, // (Number | Function) 表示根元素字体大小或根据input参数返回根元素字体大小
    //unitPrecision: 5, // (数字)允许 REM 单位增长到的十进制数字
    propList: ['*'], // 可以从 px 更改为 rem 的属性 使用通配符*启用所有属性
    //selectorBlackList: [],// (数组)要忽略并保留为 px 的选择器。
    //replace: true, // 替换包含 rems 的规则,而不是添加回退。
    //mediaQuery: false, // 允许在媒体查询中转换 px
    //minPixelValue: 0, // 最小的转化单位
    exclude: /node_modules/i // 要忽略并保留为 px 的文件路径
    }
    ]
    ],
    },
    },
    },
    },
    };

    • 修改 package.json 中的 scripts
    1
    2
    3
    4
    5
    "scripts": {
    "start": "craco start",
    "build": "craco build",
    "test": "craco test",
    },
    • 最终可以看到进行了更改
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    .App {
    width: 250px;
    height: 100px;
    background-color: red;
    }
    //自动转化为了
    .App {
    width: 3.33333rem;
    height: 1.33333rem;
    background-color: red
    }

    package.json列表

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    {
    "name": "zhihu-daily",
    "version": "0.1.0",
    "private": true,
    "dependencies": {
    "@craco/craco": "^7.1.0",
    "@testing-library/jest-dom": "^5.16.5",
    "@testing-library/react": "^13.4.0",
    "@testing-library/user-event": "^13.5.0",
    "amfe-flexible": "^2.2.1",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "react-scripts": "5.0.1",
    "web-vitals": "^2.1.4"
    },
    "scripts": {
    "start": "craco start",
    "build": "craco build",
    "test": "craco test",
    "eject": "react-scripts eject"
    },
    "eslintConfig": {
    "extends": [
    "react-app",
    "react-app/jest"
    ]
    },
    "browserslist": {
    "production": [
    ">0.2%",
    "not dead",
    "not op_mini all"
    ],
    "development": [
    "last 1 chrome version",
    "last 1 firefox version",
    "last 1 safari version"
    ]
    },
    "devDependencies": {
    "postcss-pxtorem": "^6.0.0"
    }
    }

    参考

    使用reduxjs/toolkit

    • 安装
    1
    yarn add reduxjs/toolkit react-redux
    • 使用起来也很方便,先抛弃一切redux的,这里只有切片,我们除了创建切片和一个主入口文件,其他什么都没有了

    • 创建切片

      • src\store/slice/base/index.ts
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    import { createSlice } from "@reduxjs/toolkit";

    export const Info = {

    }

    export const Base = createSlice({
    name:'base',
    initialState:() => {
    return Info;
    },
    reducers:{

    }
    })

    export const BaseSliceAction = Base.actions;
    export const BaseSliceReducer = Base.reducer;

    • 主入口文件
      • src\store/index.ts
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import { configureStore } from "@reduxjs/toolkit";
    import { BaseSliceReducer } from "@/store/slice/base";

    const Store = configureStore({
    reducer:{
    base:BaseSliceReducer,
    }
    })

    export default Store;
    • 传递各个组件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import { Provider } from "react-redux";
    import store from "@/store";

    const root = ReactDOM.createRoot(
    document.getElementById('root') as HTMLElement
    );
    root.render(
    <ConfigProvider locale={zhCN}>
    <Provider store={store}>
    <App />
    </Provider>
    </ConfigProvider>
    );

    • 组件使用

      • 获取设置的state参数const { useSelector } from "react-redux"
      1
      2
      3
      4
      5
      6
      import {useSelector} from "react-redux"
      const selectProjectModalOpen = state => state.projectList.projectModalOpen;
      const showModal = useSelector(selectProjectModalOpen);


      //等同于 const showModal = useSelector((state) => state.projectList.projectModalOpen)
      • 调用设置的方法const { useDispatch } from "react-redux"
      1
      2
      3
      4
      const { useDispatch } from "react-redux";
      import {projectListSliceActions} from "../projectList/projectList.slice";
      const dispatch = useDispatch();//不需要传入任何参数,react-redux会自动去处理store
      <button onClick={() => dispatch(projectListSliceActions.closeProjectModal())}>点击我关闭</button>

    元素隐藏/显示

    详情页

    可以利用useEffect来实现并发操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    useEffect(() => {
    (async () => {
    //获取详情图
    const result = await api.queryNewsInfo(id ?? '');
    console.log(result)
    })()
    },[])
    useEffect(() => {
    (async () => {
    //获取点赞信息
    const result = await api.queryStoryExtra(id ?? '');
    })()
    })

    React渲染html字符串

    1
    2
    3
    4
    5
    6
    7
    function createMarkup() {
    return {__html: 'First &middot; Second'};
    }

    function MyComponent() {
    return <div dangerouslySetInnerHTML={createMarkup()} />;
    }

    创建的css样式放置到document.head当中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const handleStyle = () => {
    const { css } = info;
    if(!Array.isArray(css)) return;
    const cssLink = css[0];//获取css链接
    console.log(cssLink)
    const linkDOM = document.createElement('link');
    linkDOM.rel = 'stylesheet';
    linkDOM.href = cssLink;
    document.head.append(linkDOM);
    }

    使用flushSync

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    import React, { useState } from 'react';
    import { flushSync } from 'react-dom';

    const App: React.FC = () => {
    const [count1, setCount1] = useState(0);
    const [count2, setCount2] = useState(0);
    return (
    <div
    onClick={() => {
    flushSync(() => {
    setCount1(count => count + 1);
    });
    // 第一次更新
    flushSync(() => {
    setCount2(count => count + 1);
    });
    // 第二次更新
    }}
    >
    <div>count1: {count1}</div>
    <div>count2: {count2}</div>
    </div>
    );
    };

    export default App;
    • 需要注意的是,如果通过useEffect来,且依赖收集为一个空数组,那么就需要注意函数调用的state值的问题了

      • 初次渲染的时候,函数指向的是初始化时候的值,当有数据重新渲染的时候,如果不进行依赖收集去更新useEffect当中函数的指向,那么就会导致useEffect指向的永远是初始化时候的函数,从而导致函数内部的state值永远为初始化时候的值
      • 所以老师的解决办法如下
        • 也就是传入参数的方式
      1
      2
      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
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      //老师解决办法
      useEffect(() => {
      (async () => {
      //获取详情图
      const result = await api.queryNewsInfo(id ?? '');
      flushSync(() => {
      setInfo(result)
      handleStyle(result);
      })

      handleImage(result);
      })()
      },[])

      //下面这种是错误的,handleStyle和handleImage无法获取到最新的state值
      useEffect(() => {
      (async () => {
      //获取详情图
      const result = await api.queryNewsInfo(id ?? '');
      flushSync(() => {
      setInfo(result)
      handleStyle();
      })
      //保证DOM可以获取到
      handleImage();
      })()
      },[])


      //顺带一提,输出结果为 1,2,3,4
      useEffect(() => {
      (async () => {
      //获取详情图
      const result = await api.queryNewsInfo(id ?? '');
      console.log(1)
      flushSync(() => {
      console.log(2)
      setInfo(result)
      console.log(3)
      })
      console.log(4,info)
      handleStyle(result);
      //保证DOM可以获取到
      handleImage(result);
      })()
      },[])

    设置图片

    • 图片设置
      • 为了更加好的体验,加入了onloadonerror事件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    /* 处理大图 */
    const handleImage = (info:any) => {
    const { image } = info;
    if(!image) return;
    const picDOM = document.querySelector<HTMLElement>('.img-place-holder');
    const imgDOM = document.createElement('img');
    imgDOM.src = image;
    imgDOM.onload = () => {
    //完成加载
    imgDOM.style.cssText = 'width:100%'
    //@ts-ignore;
    picDOM.style.cssText = 'overflow:hidden';
    picDOM?.appendChild(imgDOM);
    }
    imgDOM.onerror = () => {
    //移除外层容器
    const parent = picDOM?.parentElement;
    parent?.removeChild(picDOM as any);
    }
    }

    登录页面

    • reduxjs/toolkit

    reduxjs/toolkit使用异步-方法1

    • 缺点是需要使用@ts-ignore,否者会报A computed property name must be of type 'string', 'number', 'symbol', or 'any'.警告

    • 异步函数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import {createAsyncThunk} from "@reduxjs/toolkit";

    export const fetchUserDataAction = createAsyncThunk('fetch/fetchUserDataAction',() => {
    console.log('执行了我')
    //将作为payload值
    return {
    age:'18888'
    }
    })

    • store基本步骤
    1
    2
    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
    35
    36
    37
    38
    39
    import {fetchUserDataAction} from "./actions";
    import { createSlice} from "@reduxjs/toolkit";
    //创建
    export const Base = createSlice({
    name:'base',
    initialState:() => {
    return Info;
    },
    reducers:{
    userInfo: (state, action) => {
    console.log(state,action)
    state.other = {
    name:'我是新名称'
    }
    }
    },

    extraReducers:{
    //@ts-ignore
    [fetchUserData.fulfilled](state,{payload}){
    console.log('请看下图',action)
    }
    },
    })



    //主入口
    import { configureStore } from "@reduxjs/toolkit";
    import { BaseSliceReducer } from "@/store/slice/base";

    const Store = configureStore({
    reducer:{
    base:BaseSliceReducer,
    }
    })
    export default Store;


    • 使用
    1
    2
    3
    4
    5
    import {fetchUserDataAction} from "@/store/slice/base/actions";
    import {useDispatch} from "react-redux";
    const dispatch = useDispatch()
    //调用异步
    dispatch(fetchUserDataAction() as any);

    执行输出

    reduxjs/toolkit使用异步-方法2

    • 异步函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    import {createAsyncThunk} from "@reduxjs/toolkit";

    export const fetchUserDataAction = createAsyncThunk('fetch/fetchUserDataAction',() => {
    console.log('执行了我')
    //将作为payload值
    return {
    age:'18888'
    }
    })
    • store基本步骤
    1
    2
    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
    import {fetchUserDataAction} from "./actions";
    import { createSlice} from "@reduxjs/toolkit";
    export const Base = createSlice({
    name:'base',
    initialState:() => {
    return Info;
    },
    reducers:{
    userInfo: (state, action) => {
    console.log(state,action)
    state.other = {
    name:'我是新名称'
    }
    }
    },
    extraReducers(builder){
    builder
    .addCase(fetchUserDataAction.fulfilled,(state, action) => {
    console.log('执行了我',action)
    })
    }
    })


    //主入口
    import { configureStore } from "@reduxjs/toolkit";
    import { BaseSliceReducer } from "@/store/slice/base";

    const Store = configureStore({
    reducer:{
    base:BaseSliceReducer,
    }
    })
    export default Store;
    • 使用
    1
    2
    3
    4
    5
    import {fetchUserDataAction} from "@/store/slice/base/actions";
    import {useDispatch} from "react-redux";
    const dispatch = useDispatch()
    //调用异步
    dispatch(fetchUserDataAction() as any);

    执行输出

    需要做的跳转处理

    路由设置

    • 类似于Vued的路由前置守卫
    • 做法
      • 看下图

    自己绘制说明

    老师说明

    • 代码这里借助一个hooks来书写
    1
    2
    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
    35
    * 路由校验-判断是否需要登录 */
    export const useCheckNeedAuth = (path:string) => {
    const [,setRandomValue] = useState<string>('');
    const { info }: any = useSelector<any>((state) => state.base);
    const needAuth = !info && needAuthPath.includes(path)//登录信息不存在,并且访问的路径在需要认证的路由路径就需要认证;
    const dispatch = useDispatch();
    const navigate = useNavigate();
    const location = useLocation();
    useEffect(() => {
    (async () => {
    //不需要校验,直接返回
    if(!needAuth) return;
    //需要校验,请求获取用户信息(因为redux刷新后状态就没有了,需要重新请求)
    const {payload:info} = await dispatch(fetchUserDataAction() as any).catch(() => {})
    if(!info){
    Toast.show({
    icon:'fail',
    content:'请先登录'
    })
    //console.log('执行跳转');
    navigate({
    pathname:'/login',
    search:`redirect=${location.pathname}`,
    })
    return;
    }
    //console.log('获取到了信息')
    //这里采用的方法就是更新一个值,从而去触发重新渲染,老师用的是时间戳,我这里就用uuid
    setRandomValue(uuidv4());
    })();
    })
    return [
    needAuth,
    ]
    }
    • 顺带一提,直接在useEffect当中使用async也是不被允许的
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // 错误写法
    // useEffect(async () => {
    // const result = await axios(
    // 'https://hn.algolia.com/api/v1/search?query=redux',
    // );

    // setData(result.data);
    // });

    // 正确写法
    useEffect(() => {
    const fetchData = async () => {
    const result = await axios(
    'https://hn.algolia.com/api/v1/search?query=redux',
    );

    setData(result.data);
    };

    fetchData();
    }, []);

    收藏/取消收藏

    • 需要注意的是,如果我们想携带params参数和search参数,就需要自己组合了
      • 当然,你也可以使用window.location.href获取完整的路径信息,不过需要自己对http://localhost做处理

    • 所以我解决办法就是,使用组合
    1
    2
    3
    4
    props.navigate({
    pathname:'/login',
    search:props.location.pathname + props.location.search,
    })

    收藏夹

    • 就添加了确认弹窗操作

    实现组件的缓存

    • 缓存的方式
    1
    2
    3
    4
    5
    6
    7
    主流思想上:
    1.不是标准的组件缓存,只是数据缓存A->B在A组件路由跳转的时候,把A组件中需要的数据「或者A组件的全部虚拟DOM」存储到redux中! !A组件释放,B组件加载! !当从B回到A的时候,A开始加载首先判断redux中是否存储了数据(或者虚拟DOM),如果没存储,就是第一次加载逻辑的处理; 如果存储了,则把存储的数据拿来渲染! !

    2.修改路由的跳转机制,在路由跳转的时候,把指定的组件不销毁,只是控制display:none隐藏;后期从B回到A的时候,直接让A组件display:block! !

    3.把A组件的真实DOM等信息,直接缓存起来;从B跳转回A的时候,直接把A之前缓存的信息拿出来用! !

    • 老师用的是一个老师的组件,这里就使用cjy0208大佬的react-activation(毕竟下载人数多嘛)
      • 好吧,此组件react18当中bug太多了………………并且需要使用老的ReactDOM.render写法,就不使用了
    1
    2
    3
    yarn add react-activation
    # 或者
    npm install react-activation
    • 跳过……………..

    修改个人信息-文件上传

    • 一开始纠结文件上传失败后还显示图片,后面解决办法很简单
    1
    2
    3
    4
    5
    在uplod中上传失败之后抛出异常

    throw new Error('上传失败,请重新上传')

    然后在ImageUpload中加入属性 showFailed: false

    遇到的问题

    无法解析scss/sass提示create-react-app Cannot find module ‘sass’

    • 使用 create-react-app 的创建的项目,其默认的 webpack.config.js (这个文件默认隐藏,要查看需要运行 npm run eject,运行这个命令前需要本地 commit 代码)的文件中,可以看到是默认配置了 sass-loader 的选项的,所以在 react 项目中使用 sass 还是比较方便的。虽然默认配置了 sass-loader,但要使用 sass 还是需要先安装一下的,不然就会像我一样出现create-react-app Cannot find module 'sass'
    1
    2
    3
    npm install sass -D 
    or
    yarn add sass -D

    无法解析less或提示Cannot find module ‘./index.module.less’ or its corresponding type declarations

    • 安装
    1
    2
    3
    npm install craco-less -D
    or
    yarn add craco-less -D
    • 编辑craco.config.js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    const CracoLessPlugin = require('craco-less');

    module.exports = {

    plugins: [
    {
    plugin: CracoLessPlugin,
    options: {
    // 此处根据 less-loader 版本的不同会有不同的配置,详见 less-loader 官方文档
    lessLoaderOptions: {
    lessOptions: {
    modifyVars: {},
    javascriptEnabled: true
    }
    }
    }
    }
    ]
    };

    • 这样我们就可以使用下面命令来使用less了
    1
    import "./index.less"
    • 可能会出现下面问题Cannot find module './index.module.less' or its corresponding type declarations.

    • 找到src\react-app-env.d.ts,添加如下内容
    1
    2
    3
    4
    declare module "*.less" {
    const content: { [className: string]: string };
    export default content;
    }

    配置别名

    • 更改craco.config.js
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const path = require('path');
    const resolve = dir => path.resolve(__dirname,dir);/* 计算路径 */
    module.exports = {

    webpack:{
    alias:{
    "@":resolve('src'),
    }
    }
    };

    • 然后就可以使用了
    1
    2
    //component:lazy(() => import("../views/Login")),
    component:lazy(() => import("@/views/Login")),
    • 不过可能会出现Cannot find module '@/views/Login' or its corresponding type declarations.
      • 可以在 项目的根目录创建一个jsconfig.json或者tsconfig.json,添加如下内容即可
    1
    2
    3
    4
    5
    6
    7
    8
    {
    "compilerOptions": {
    "baseUrl": "./",
    "paths": {
    "@/*": ["src/*"]
    },
    }
    }
    • 更改完成记得重启服务,如果上述服务都没有用,可以试试看craco-alias(不过这个库已经被废弃了)

    依赖收集导致无法获取到最新state值

    • 在做下拉加载组件的时候,下面的代码有问题
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    const { onBottom,options,style } = props;
    const loadRef = useRef<any>();

    useEffect(() => {
    const loadRefCurrent = loadRef.current;
    const observer = new IntersectionObserver((change) => {
    const {isIntersecting} = change[0];
    if(isIntersecting){
    onBottom && onBottom();
    }
    },options ?? {});
    observer.observe(loadRef.current);
    return () => {
    //移除监听
    observer.unobserve(loadRefCurrent);
    }
    //这里有问题,依赖未写入
    },[])
    • 导致父组件当中
    1
    2
    3
    4
    5
    /* 执行到底部的回调 */
    const handleOnBottom = () => {
    //子组件未添加依赖收集,导致newList永远是初始阶段的值
    console.log(newList)
    }
    • 所以需要添加依赖
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    const { onBottom,options,style } = props;
    const loadRef = useRef<any>();

    useEffect(() => {
    const loadRefCurrent = loadRef.current;
    const observer = new IntersectionObserver((change) => {
    const {isIntersecting} = change[0];
    if(isIntersecting){
    onBottom && onBottom();
    }
    },options ?? {});
    observer.observe(loadRef.current);
    return () => {
    //移除监听
    observer.unobserve(loadRefCurrent);
    }
    //注意添加依赖
    },[onBottom,options,style])

    dispatch出现Argument of type ‘AsyncThunkAction{ age: string; }, void, AsyncThunkConfig>’ is not assignable to parameter of type ‘AnyAction’

    • 方法1:设置为any
    1
    2
    3
    4
    5
    6
    import {fetchUserDataAction} from "@/store/slice/base/actions";
    import {useDispatch} from "react-redux";

    const dispatch = useDispatch()
    dispatch(fetchUserDataAction() as any)

    • 方法2:暴露Store当中的dispatch类型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    import { configureStore } from "@reduxjs/toolkit";


    const Store = configureStore({
    reducer:{

    }
    })
    export type AppDispatch = typeof Store.dispatch
    export default Store;


    //使用
    import {AppDispatch} from "@/store";
    import {useDispatch} from "react-redux";
    import {fetchUserDataAction} from "@/store/slice/base/actions";
    const dispatch = useDispatch<AppDispatch>()
    dispatch(fetchUserDataAction())

    小知识点

    stylesheet引入html文档的外部样式表

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    rel="styleSheet" 
    打个比喻:就好比你带了个妞去一个party,虽然你知道这个妞是谁,但是你没给别人介绍啊,谁知道这个妞是干嘛的。
    于是你加上rel="stylesheet",然后人们就知道了,哦......原来这个妞是你带来蹭饭的!

    那么type="text/css" 也是一个道理,都是用来告诉浏览器的,我这个是一个css的文本,你要是不认识就别乱搞。

    对于一些特殊浏览器 不能识别css的,会将代码认为text,从而不显示也不报错。

    不过根据官方建议 ,一般还是加上比较好。

    因为这个表示的是浏览器的解释方式,如果不定义的话,有些CSS效果浏览器解释得不一样。

    React默认Event类型可以使用React.MouseEvent来指明

    解构赋值省略掉部分参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 解构赋值省略掉部分参数
    let obj = {
    name:'法外狂徒',
    age:18,
    sex:'男',
    qq:'15946875963',
    phone:'15689784598'
    }
    // 利用解构得rest语法收集那些尚未被解构模式拾取的剩余可枚举属性键
    /*
    rest语法:剩余参数语法
    官网:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Functions/Rest_parameters
    如果函数(对象)的最后一个命名参数以...为前缀,则它将成为一个由剩余参数组成的真数组(对象)
    */
    //remaining参数可以随意命名
    let {name,...remaining} = obj;
    console.log(remaining) //{ age: 18, sex: '男', qq: '15946875963', phone: '15689784598' }

    才发现注释可以标明类型

    • 在非ts的情况下

    标准React组件的类型

    • 可以使用React.ReactNode

    老师正则的意思

    • 老师写了一个正则let reg = /\/api(\/[^/?#]+)/
    • 如果不考虑转义的问题和首尾固定的/ /这二个符号,这个正则就可以简写为下面这种
    1
    /api(/[^/?#])
    • 如果不考虑分组捕获,可以再简化
      • 含义: 匹配字符串当中具有/api内容的,并且获取后面内容不是?或者是#或者是/的字符内容
    1
    /api/[^/?#]
    • 图示

    • 分组捕获添加上去后老师的演示代码

    less文件引入图片

    • 前提有别名~才可以这样子,否者要一层一层找下去
    1
    background-image: url("~@/assets/images/personBg.png");

    自定义虚线

    • 示例1
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    //自定义虚线
    &_dashed{
    position: absolute;
    bottom: 0;
    left: 0;
    width: 100%;
    height: 1px;
    background-image: linear-gradient(
    to right,
    #ccc 0%,
    #ccc 50%,
    transparent 50%
    );
    //可以设置此值大小来改变间距
    background-size: 14px 1Px;
    background-repeat: repeat-x;
    }
    • 示例2
    1
    2
    3
    4
    5
    6
    7
    8
    9
    background: linear-gradient(
    to right,
    transparent 0%,
    transparent 50%,
    #ccc 50%,
    #ccc 100%
    );
    background-size: 50px 1px;
    background-repeat: repeat-x;

    指明传入props当中的回调类型提示: Type ‘Function’ is not assignable to type ‘MouseEventHandler<HTMLDivElement>‘.

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/d2befc3c.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    avatar
    梦洁
    小小的字,有大大的梦想~分享我的前端学习过程,经历,错误,和一些其他折腾过程
    关注下我(* ̄▽ ̄*)
    公告
    不断更新中,有问题请留言回复(会通过邮箱提醒~)
    目录
    1. 1. 项目完工
    2. 2. 初始化项目
      1. 2.1. ts方式(此项目以ts运行)
      2. 2.2. js方式
    3. 3. Rem响应式处理
      1. 3.1. 手动处理
      2. 3.2. 自动处理
      3. 3.3. package.json列表
      4. 3.4. 参考
    4. 4. 使用reduxjs/toolkit
    5. 5. 元素隐藏/显示
    6. 6. 详情页
      1. 6.1. 可以利用useEffect来实现并发操作
      2. 6.2. React渲染html字符串
      3. 6.3. 创建的css样式放置到document.head当中
      4. 6.4. 使用flushSync
      5. 6.5. 设置图片
    7. 7. 登录页面
      1. 7.1. reduxjs/toolkit使用异步-方法1
      2. 7.2. reduxjs/toolkit使用异步-方法2
      3. 7.3. 需要做的跳转处理
    8. 8. 路由设置
    9. 9. 收藏/取消收藏
    10. 10. 收藏夹
    11. 11. 实现组件的缓存
    12. 12. 修改个人信息-文件上传
    13. 13. 遇到的问题
      1. 13.1. 无法解析scss/sass提示create-react-app Cannot find module ‘sass’
      2. 13.2. 无法解析less或提示Cannot find module ‘./index.module.less’ or its corresponding type declarations
      3. 13.3. 配置别名
      4. 13.4. 依赖收集导致无法获取到最新state值
      5. 13.5. dispatch出现Argument of type ‘AsyncThunkAction{ age: string; }, void, AsyncThunkConfig>’ is not assignable to parameter of type ‘AnyAction’
    14. 14. 小知识点
      1. 14.0.1. stylesheet引入html文档的外部样式表
      2. 14.0.2. React默认Event类型可以使用React.MouseEvent来指明
      3. 14.0.3. 解构赋值省略掉部分参数
      4. 14.0.4. 才发现注释可以标明类型
      5. 14.0.5. 标准React组件的类型
      6. 14.0.6. 老师正则的意思
      7. 14.0.7. less文件引入图片
      8. 14.0.8. 自定义虚线
      9. 14.0.9. 指明传入props当中的回调类型提示: Type ‘Function’ is not assignable to type ‘MouseEventHandler<HTMLDivElement>‘.
    最新文章
    \ No newline at end of file diff --git a/d2cac383.html b/d2cac383.html new file mode 100644 index 000000000..ba85d5fa8 --- /dev/null +++ b/d2cac383.html @@ -0,0 +1 @@ +知乎日报项目前端+后端-React18 + React-Router6 + React-redux + reduxtoolkit | 梦洁小站-属于你我的小天地

    知乎日报项目前端+后端-React18 + React-Router6 + React-redux + reduxtoolkit

    地址

    介绍

    • 技术栈使用React18 + React-Router6 + React-redux + redux/toolkit + craco(重写配置) + ( amfe-flexible + postcss-pxtorem 达到适配 ) + less

    • keep-alive效果没实现,找了几个库效果都不理想,就没去弄了

    运行

    前端运行

    • 1.安装依赖(node什么的自行安装)
    1
    yarn install
    • 2.运行
    1
    npm run start

    后端运行

    • 1.安装依赖(node什么的自行安装)
    1
    2
    3
    yarn install
    或者
    npm install
    • 2.运行
    1
    node server.js

    图片展示

    首页

    详情页面

    个人中心页

    修改个人信息页

    收藏页

    加载骨架屏

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/d2cac383.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/d3048434.html b/d3048434.html new file mode 100644 index 000000000..9542da48b --- /dev/null +++ b/d3048434.html @@ -0,0 +1 @@ +ts泛型,映射,条件类型和类型提取infer和一些常用工具库的说明 | 梦洁小站-属于你我的小天地

    ts泛型,映射,条件类型和类型提取infer和一些常用工具库的说明

    Typescript当中的T,K,V到底是个啥

    • 有时候,我们看到下面的代码,当然,这里是简单例子来说
    1
    2
    3
    function identity <T> (value:T) : T {
    return value;
    }
    • 其实泛型就是使用字母来代替将要接收的类型,这里的”T”是代表类型的缩写,表示对将要接收类型的一个占位符,占位符可以是任意字母,下面是一些常用的占位符

      • T(Type) 表示类型
      • K(Key) 表示对象中键的类型
      • V(value) 表示对象中值的类型
      • E(Element) 表示元素类型
    • 如果在函数中使用了泛型,那么我们可以在使用的时候指明类型,也可以不显式指明类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function identity <T , U>(value: T ,message: U) : T {
    console.log(message)
    return value;
    }
    //不指定泛型变量的实际类型
    console.log(identity(20,'动感超人'));

    //手动指定泛型变量的实际类型
    console.log(identity<number,string>(20,'动感超人'))

    declare

    • 假如我们在html当中引入了jquery插件,那么就会在全局当中增加一个关键字$,此时如果我们在ts文件当中的书写关键字$

    • 就会发现ts会提示找不到$,ts2304,也就是说ts不认识这个全局变量$

    • 所以我们可以使用declare来定义这个全局变量declare const $ = xxxx,这样子ts就认识这个全局变量$了

    • 注意点

      • declare声明不包含具体的实现,也就是说我们只是声明,不做具体处理
      • declare可以定义全局变量,全局函数,全局枚举,全局类等
      • 你看到的xxx.d.ts就是用于放置声明文件的
    • declare和一些声明文件查询:@地址

    疑问

    • 为什么我们可以在ts中直接写Math,JSON,Object这些全局对象呢?那是英文typescript已经在文件当中声明了
    • 在vie当中,我们可以在node_modules/vite/client.d.ts当中就可以看到declare的声明,部分代码如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    declare module '*.module.css' {
    const classes: CSSModuleClasses
    export default classes
    }
    // CSS
    declare module '*.css' {
    const css: string
    export default css
    }
    // images
    declare module '*.jpg' {
    const src: string
    export default src
    }
    • vite为什么要这样子做呢,因为如果它不这样子做,那么我们在使用下面代码就会报错
    1
    2
    3
    4
    5
    //如果vite不declare,那么就会提示找不到模块"./file.css"/或其相应的类型声明
    import css from "./file.css";

    //如果vite不declare,那么就会提示找不到模块"./abao.jpg"或其相应的类型声明
    import logo from "./abao.jpg";
    • 从ts2.0开始,declare支持通配符了,就如上声明一样使用了通配符

    any类型和unknown类型

    • any:我不在乎它的类型

    • unknown:我不知道它的类型(可以理解为类型安全的any),使用了unknown,必须要自己进行类型检测后才可以对变量进行操作,否则会报警告或错误

    • 对于下列函数,如果是使用any类型,是不会有任何报错提示的

    1
    2
    3
    4
    5
    6
    7
    8
    function invokeCallBack(callback:any){
    try {
    callback();
    }catch (e){
    console.log(e)
    }
    }
    invokeCallBack(1)
    • 而对于我们使用了unknown,则会提示TS2571: Object is of type 'unknown'.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    function invokeCallBack(callback:unknown){
    try {
    //TS2571: Object is of type 'unknown'.
    callback();
    }catch (e){
    console.log(e)
    }
    }
    invokeCallBack(1)
    • 所以对于unknown来说,我们在使用前就必须要判断是否可以执行后才可以没有警告
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function invokeCallBack(callback:unknown){
    try {
    if(typeof callback === 'function'){
    callback();
    }
    }catch (e){
    console.log(e)
    }
    }
    invokeCallBack(1)
    • 需要注意的是,unknown类型的变量只可以赋值给unknown类型或者是any类型

    typescript当中的类型

    • never是空集,所以never类型无法被其他类型所赋值
    1
    2
    3
    4
    5
    //Type 'string' is not assignable to type 'never'
    let num: never = 123;

    //Type 'string' is not assignable to type 'never'
    let name: never = '超人';
    • interface接口定义对象类型,可以使用extends进行扩展
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    interface Vector1D {x:number}

    //等同于
    interface Vector3D {
    x:number,
    y:number,
    }
    interface Vector2D extends Vector1D {y:number}

    //等同于
    interface Vector3D {
    x:number,
    y:number,
    z:number
    }
    interface Vector3D extends Vector2d {z:number};

    type和interface的异同

    前置知识

    type 类型别名

    • 针对基本类型和非对象类型的情况非常有用,支持泛型

    interface 接口

    • interface只能定义对象类型
    • 定义接口类型时,可以同时声明对象身上的属性和方法

    相同点

    1. 类型别名和接口都可以用来描述对象或函数
    1
    2
    3
    4
    5
    type Point  = {
    x: number
    y:number
    }
    type SetPoint = (x:number,y:number) => void;
    1
    2
    3
    4
    5
    6
    7
    8
    interface Point {
    x: number;
    y: number; // ";" 或则 "," 或者不写都可以
    }
    interface SetPoint {
    (x: number, y: number): void;
    }

    1. 类型别名和接口都支持扩展
      • 类型别名扩展使用&交叉运算符进行合并运算,注意,交叉类型中的交叉,并不是指两个类型的交集,而是并集
      • 接口扩展使用关键字extends
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    //类型别名通过交叉运算符来扩展
    type Animal = {
    name: string;
    };
    type Bear = Animal & {
    honey: boolean;
    };
    const bear: Bear = {
    name: "熊大",
    honey: false,
    };
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    // 接口通过extends来扩展
    interface Animal {
    name: string;
    }
    interface Bear extends Animal {
    honey: boolean;
    }
    const bear: Bear = {
    name: "熊大",
    honey: false,
    };
    1. 类型别名和接口都支持相互扩展,但需要注意的是接口只支持使用extends关键字来继承,类型也只支持使用&来完成扩展
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    type Animal = {
    name: string;
    };
    interface Bear extends Animal {
    honey: boolean;
    }
    const bear: Bear = {
    name: "熊大",
    honey: false,
    };
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    interface Animal {
    name: string;
    }
    type Bear = Animal & {
    honey: boolean;
    };
    const bear: Bear = {
    name: "熊大",
    honey: false,
    };

    不同点

    1. 类型别名可以为基本类型,联合类型或元组类型定义别名,接口不行
    1
    2
    3
    type MyNumber = number; //基本类型定义别名
    type StringOrNumber = string | number; //联合类型定义别名
    type Point = [number, number]; //元组类型定义别名
    1. 同名接口会自动合并,而类型别名不会
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    interface User {
    name: string;
    }
    interface User {
    age: number;
    }
    let user: User = {
    name: "李白",
    age: 1000,
    };
    user.name; //李白
    user.age; //1000
    1
    2
    3
    4
    5
    6
    7
    type User = {
    name: string;
    };
    //标识符“User”重复。ts(2300)
    type User = {
    age: number;
    };

    类型别名和接口的一些使用场景

    • 使用类型别名的场景

      • 定义基本类型的别名时,使用type
      • 定义元组类型时,使用type
      • 定义函数类型时,使用type
      • 定义联合类型时,使用type
      • 定义映射类型时,使用type
    • 使用接口的场景

      • 需要利用接口自动合并特征的时候,使用interface
      • 定义对象类型且无需使用type的时候,调用interface

    索引签名和Record内置工具类型

    • 有时候我们可能会想这样子,我想规定一个对象类型的key只能为字符串,值是任意的,那么要怎么做呢?可以使用索引签名

    • 格式语法如下

      • {[key:KeyType] : ValueType}
      • key: 固定的写法
      • **keyType: ** key的类型,只支持string,number,symbol,不能为字面量类型或者是泛型类型,如需要使用字面量或泛型,则需要使用Record内置工具类型
      • valueType: value的类型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    //比如下面
    interface selfName1 {
    [key:string] : string
    }
    const test1:selfName1 = {
    name:'李白',
    hobby:'吃饭',
    }

    const selfName2:{[key:string] : string} = {
    name:'李白',
    hobby:'吃饭',
    age:1000,//报错,警告
    }
    • 需要注意的是KeyType只能为string 或者 number或者symbol不能为其他的值
    1
    2
    3
    4
    5
    //错误的keyType
    interface selfName3 {
    //keytype只能为string,number,symbol
    [key:boolean] : string
    }
    • 使用索引签名也可以和别的已知的key,value使用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    interface Options {
    [key:string]:string | number | boolean,
    timeout:number,//已知的键
    }
    const option:Options = {
    timeout:1000,
    errorMessae:'The request timed out!',
    isSuccess:false,
    }
    • 此外,索引签名也可以和模板字符串使用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    interface PropChangeHandler {
    [key:`${string}Changed`]: () => void;
    }

    let handlers:PropChangeHandler = {
    idChanged: () => {},
    nameChanged: () => {},

    //报错,因为后面少了一个字符"d",和规定的type不相同
    ageChange: () => {},
    }

    Record内置工具类型和索引签名

    • 索引签名类型参数不能为字面量类型或者是泛型类型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    type User1 = {
    //报错 索引签名类型参数不能为字面量类型或者是泛型类型
    [key:"id"]:string,
    }

    type User2 = {
    //报错 索引签名类型参数不能为字面量类型或者是泛型类型
    [key:"id" | "name"] :string
    }

    //有人可能会有疑问,说,哎呀,这个为什么可以
    interface PropChangeHandler {
    //这个没问题,因为这个不是字面量啊
    [key:`${string}Changed`]: () => void;
    }
    • 而Record却可以包括字面量和泛型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    type User3 = Record<"id", string>
    const a:User3 = {
    id:'2tjawjtiaowt',
    }

    type User4 = Record<'id' | 'name', string>;
    const b:User4 = {
    id:'2tjawjtiaowt',
    name:'动感超人'
    }

    原来映射类型是这样子工作的

    • ts的一些工具类型,比如说Pick,就是从某一个类型当中挑选一部分
    • ts中的工具类型操作的是类型,而js当中工具类型操作的是值,
    • 下面用ts的pick用js来做下解释,调用ts工具类型(类似函数)使用的是尖括号,js函数则是小括号(一句话,ts用尖括号,js用小括号)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //K extends T 泛型约束的语法,用于约束泛型K类型
    type Pick <T,K extends keyof T> = {
    [P in K]:T[P]
    }
    //用js来解释就是
    function Pick (obj,keys) {
    const result = {};
    for(const key of keys){
    result[key] = obj[key]''
    }
    return result;
    }

    动图

    ts内置工具类型中的keyof操作符有啥用

    • 首先来说下js当中的Object.keys函数作用,Object.keys会返回对象身上所有可枚举key组成的数组,数组中属性名的排列顺序和正常循环遍历该对象时返回的顺序一致。
    1
    2
    3
    4
    5
    6
    7
    8
    const object1 = {
    a: 'somestring',
    b: 42,
    c: false
    };

    console.log(Object.keys(object1));
    // expected output: Array ["a", "b", "c"]
    • 那么ts当中的keyof也是,返回对象身上key值组成的联合类型
    1
    2
    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
    class Person {
    year:number = 2022;
    hobby:string = '吃饭';
    }
    //等同于 type P0Types = 'year' | 'hobby'
    type P0Types = keyof Person;
    const P01:P0Types = 'year';
    const P02:P0Types = 'hobby';


    interface Person1Inter {
    id:number,
    name:string,
    }
    //等同于 type P1Types = 'id' | 'name'
    type P1Types = keyof Person1Inter;
    const P11:P1Types = 'id';
    const P12:P1Types = 'name';


    //针对枚举
    enum HttpMethods {
    Get,
    Post,
    }
    type Methods = keyof typeof HttpMethods;
    const P21:Methods = 'Get';
    const P22:Methods = 'Post';
    • 对原始的数据类型也会获取到对应的联合类型
    1
    2
    3
    4
    5
    6
    7
    type K1 = keyof boolean ;// ValueOf

    // "toString" | "toFixed" | "toExponential"
    // | "toPrecision" | "valueOf" | "toLocaleString"
    type K2 = keyof number;

    type K3 = keyof any; string | number | symbol

    ts为什么keyof typeof可以拿到枚举的联合类型 ?

    1
    2
    3
    4
    5
    6
    7
    8
    export enum ab {
    'a',
    'b'
    }

    //uni等同于 'a' | 'b'
    type uni = keyof typeof ab

    TypeScript 中分“类型”和“值”,类型是 TypeScript 认的,一般编译后会消失(不存在于 JS 中)。枚举是比较特殊的定义,虽然定义成类型,但实际是值,它在编译成 JS 之后是一个对象。

    TypeScript 中的枚举还分情况,有数值型枚举,也有字符串型枚举,还有混合型的……不讨论复杂了,这里就说数值型的。

    1
    2
    3
    4
    5
    6
    enum Hello {
    A,
    B
    }

    type X = keyof Hello;

    你猜 X 是什么呢?你会发现它包含 toFixedtoPrecision 等,是不是感觉像是个 Number 类型的 Key 呢?

    再来看看 Number 类型的 …… 果然一样

    如果不加 Exclude 运算,会看到 keyof Number 看不到键列表

    想想,实际上也是,如果这样使用

    1
    const a: Hello = Hello.A;

    a 的值实际上是一个 Number(仅数值型枚举的情况)

    所以 TypeScript 中需要使用 typeof Hello 来取实际的枚举类型(不然就是 Number 的子类型),实际上它是一个接口。

    这个类型取出来之后,枚举值名称是被当作类型的 Key 的,所以可以用 keyof 把键值取出来。

    ts的映射和泛型

    • ts的映射个人觉得有点像是js当中的map吧,操作都是传入a,经过处理后返回b

    • 语法:

      • { [P in K]?:T }
    • 一些工具库,比如说Partial,Required,Pick就是通过映射来实现的

    • 比如现在有一个需求,需要把这个类型全部改为可选的,代码如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    type User = {
    name: string,
    password: string,
    address: string,
    phone: string,
    }

    //里面的属性需要全部改为可选的
    type User1 = {
    name?:string,
    password?:string,
    address?:string,
    phone?:string,
    }
    //你可以使用工具库当中的Partial,也可以自己写一个~
    type selfPartial<T> = {
    [K in keyof T]?:T[k]
    }
    • 在映射的过程当中,可以通过添加+,-来添加.移除修饰符(+(加号为默认))
      • 没有添加前缀,默认使用加号
    • 映射类型示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    type Item =
    { a: string;
    b: number;
    c: boolean
    };


    // { x: number,y: number }
    type T1 = { [P in 'x' | 'y']: number };


    //{ x: 'x',y: 'y }
    type T2 = { [P in 'x' | 'y']: P };

    //{a: string,b: number}
    type T3 = { [P in 'a' | 'b']: Item[P] }

    //{a: string,b: number,c: boolean}
    type T4 = { [P in keyof Item]: Item[P] }
    • 映射演示

    映射演示

    ts条件类型Conditional Types

    • ts的条件类型和js当中的三元运算符差不多

    • 还是一句话,ts操作的是类型,js操作的是值

    • 语法

      • T extends U ? X : Y
      • T,U,X,Y都是类型占位符
      • 解释:当类型T可以赋值给类型U的时候,就返回X,否则就返回Y
    • 先来看一个简单的例子

      • type I2 = IsString<any>://输出类型为boolean,是因为any这二个值都可以满足,所以就为boolean
    1
    2
    3
    4
    5
    6
    7
    type IsString<T> = T extends string ? true :false;

    type I0 = IsString<number>;// false

    type I1 = IsString<'abc'>;// true
    type I2 = IsString<any>;// boolean
    type I3 = IsString<never>;// never

    除了判断单一类型之外,利用条件类型和条件链,我们还可以同时判断多种类型

    • 当传入any的时候,会返回联合类型,因为都符合,所以在ts的三元是一直运算下去的
    1
    2
    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
    type TypeName<T> = 
    T extends string ? string:
    T extends number ? number:
    T extends boolean ? boolean:
    T extends undefined ? undefined:
    T extends Function ? Function:
    Object;
    type T0 = TypeName<string> //string
    type T1 = TypeName<'a'> //string
    type T2 = TypeName<true> //boolean
    type T3 = TypeName<() => void> //Function
    type T4 = TypeName<string[]> //Object
    type T5 = TypeName<any>; //string | number | boolean | Function | Object

    //用js来书写上面的如下
    function example() {
    return condition1 ? value1
    : condition2 ? value2
    : condition3 ? value3
    : value4;
    }
    // 等价于
    function example() {
    if (condition1) { return value1; }
    else if (condition2) { return value2; }
    else if (condition3) { return value3; }
    else { return value4; }
    }

    如果传入的是联合类型会发生什么结果

    1
    2
    3
    4
    5
    6
    7
    8
    9
    type TypeName<T> = 
    T extends string ? string:
    T extends number ? number:
    T extends boolean ? boolean:
    T extends undefined ? undefined:
    T extends Function ? Function:
    Object;
    type T10 = TypeName<string | (() => void)>;//string | Function
    type T11 = TypeName<string | string[] | undefined>//string | object | undefined
    • 为什么T10 和 T11类型返回的是联合类型呢,因为TypeName属于分布式条件类型,在条件类型中,如果被检查的是一个”裸”类型参数,就是没有被数组,元组,或者Promise等包装过,则该条件类型被称为分布式条件类型,对于分布式条件类型来说,当传入的被检查类型是联合类型的时候,在运算过程中就会被依次运算

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    // 裸类型
    type Naked<T> = T extends boolean ? "Y" : "N";
    //分布式条件类型,判断每一个值是否都符合boolean
    //是就为每一个值返回对应的结果
    type T0 = Naked<number | boolean>;// "Y" | "N"

    //非裸类型
    //判断传入的T当中的每一个值是否都符合boolean,是就只返回一个"Y",否则只返回一个"N"
    type WrappedTuple<T> = [T] extends [boolean] ? "Y" : "N";

    //判断传入的T当中的每一个值为boolean类型的数组
    type WrappedArray<T> = T[] extends boolean[] ? "Y" : "N";
    type WrappedPromise<T> = Promise<T> extends Promise<boolean> ? "Y" : "N";

    type T1 = WrappedTuple<number | boolean>;// "N"
    type T2 = WrappedArray<number | boolean>;// "N"
    type T3 = WrappedPromise<number |boolean>;// "N;

    type T4 = WrappedTuple<true | false>;// "Y";
    type T5 = WrappedArray<true | false>; //"Y"

    • 由以上结果可知,如果条件类型中的类型参数 T 被包装过该条件类型就不属于分布式条件类型所以在运算过程中就不会被分解成多个分支
      • 说通俗点就是分布式条件运算返回值为联合类型,会对每一个值进行判断
      • 非分布式条件运算就是对整体每一个值进行判断,返回值为普通的类型

    Exclude内置工具类型执行流程

    • ts内置工具类型Exclude作用是传入T,U,将这两个相同的值消除,不同的值提取(利用了条件类型)
      • 返回值为never代表抛弃
    1
    2
    3
    4
    5
    6
    7
    type Exclude<T,U> = T extends U ? never : T;

    //返回:c
    type T4 = Exclude<'a' | 'b' | 'c','a' | 'b'>;

    //返回:never
    type T5 = Exclude<'a' | 'b' ,'a' | 'b'>;

    Exclude演示

    ts中的infer

    • 现在有一个需求,获取数组当中的类型,和函数返回的类型
    1
    2
    3
    4
    5
    6
    7
    type T0 = string[];
    type T1 = () => string;

    //需求,获取数组的类型和返回值的类型要怎么做?
    //做法如下,
    type UnpackedArray<T> = T extends (infer U)[] ? U : T;
    type T0Result = UnpackedArray<T0>; //string
    • infer是什么呢?

      • T extends (infer U)[] ? U : T:是条件类型的语法,而extends字句中的infer U 引入了一个新的类型变量U,用于存储被推断的类型,可以理解为后面这个U将用于存储类型
    • infer注意的点?

      • infer只能在条件类型extends字句中使用,同时infer声明的类型变量只能在条件类型的true分支中可用
      1
      2
      3
      type Wrong1<T extends (infer U)[]> = T[0] // Error
      type Wrong2<T> = (infer U)[] extends T ? U : T // Error
      type Wrong3<T> = T extends (infer U)[] ? T : U // Error
    • 那么infer到底要怎么用呢?

      • 一句话:要判断的是什么样子,infer就长什么样子
    • 我们再来看看怎么判断函数的返回类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //我们接着来下面这个的返回值类型
    type T1 = () => string;
    type T0 = string[];
    type UnpackedFunction<T> = T extends (...args:any[]) => (infer U) ? U : T;
    //返回 string
    type T1Types = UnpackedFunction<T1>

    //返回 string[]
    type Test = UnpackedFunction<T0>

    当遇到函数重载的场景,TypeScript 将使用最后一个调用签名进行类型推断

    1
    2
    3
    4
    5
    6
    7
    8
    9
    declare function foo(x:string):number;
    declare function foo(x:number):string;
    declare function foo(x:string | number):string | number;

    type UnpackedFn<T> = T extends (...args:any[]) => (infer U) ? U : T ;

    //返回:string | number;
    //代表返回类型
    type U2 = UnpackedFn<typeof foo>;

    利用条件链我们可以实现功能更加强大的 Unpacked 工具类型。

    • 还是那句话,使用infer,要判断的长什么样子,infer就长什么样子(括号包起来)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    type Unpacked<T> = 
    T extends (...args:any[]) => (infer U) ? U :
    T extends (infer U)[] ? U :
    T extends Promise<(infer U)> ? U : T;
    type T0 = Unpacked<string>; // string
    type T1 = Unpacked<string[]>; // string
    type T2 = Unpacked<() => string>; // string
    type T3 = Unpacked<Promise<string>>; // string
    type T4 = Unpacked<Promise<string>[]>; // Promise<string>
    type T5 = Unpacked<Unpacked<Promise<string>[]>>; // string

    利用条件类型和 infer,我们还可以推断出对象类型中键的类型

    1
    2
    3
    4
    5
    6
    7
    type User = {
    id: number;
    name: string;
    }

    type PropertyType<T> = T extends {id: (infer U),name: (infer R) } ? [U,R] : T;
    type U3 = PropertyType<User> // [number, string]
    推断类型-二个类型
    • 在 PropertyType 工具类型中,我们通过 infer 声明了两个类型变量 U 和 R,分别表示对象类型中 id 和 name 属性的类型。若类型配,我们就会以元组的形式返回 id 和 name 属性的类型。那么现在问题来了,在 PropertyType 工具类型中,如果只声明一个类型变量 U,那结果又会是怎样呢?下面我们来验证一下:
    1
    2
    3
    type PropertyType<T> =  T extends { id: infer U, name: infer U } ? U : T

    type U4 = PropertyType<User> // string | number
    • 由以上代码可知,U4 类型返回的是 string 和 number 类型组合成的联合类型。为什么会返回这样的结果呢?这是因为在协变位置上,若同一个类型变量存在多个候选者,则最终的类型将被推断为联合类型
    推断类型-只有一个变量
    • 然而,在逆变位置上,若同一个类型变量存在多个候选者,则最终的类型将被推断为交叉类型。同样,我们来实际验证一下:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    export interface tempType {
    a:(x:string) => void,
    b:(x:number) => void,
    }
    type Bar<T> = T extends
    {
    a:(x:infer U) => void,
    b:(x:infer U) => void
    }
    ? U : never;
    // string 和 number 类型组合成的交叉类型
    // 即最终的类型是 never 类型
    type U5 = Bar<tempType>; //string & number 为never

    ts的常用工具库

    @部分转载CSDN织_网

    Partial

    • 源码
    1
    2
    3
    type Partial<T> = {
    [P in keyof T]?: T[P]
    }

    作用: Partial;把T当中的所有属性都变为可选

    详细:生成一个新类型,该类型与 T 拥有相同的属性,但是所有属性皆为可选项

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    interface Foo {
    name: string
    age: number
    }
    type Bar = Partial<Foo>
    // 相当于
    type Bar = {
    name?: string
    age?: number
    }

    Required

    • 源码
    1
    2
    3
    type Required<T> = {
    [K in keyof T]-?:T[K]
    }

    作用:Required:将T所有属性变为必填的

    详细:生成一个新类型,该类型与 T 拥有相同的属性,但是所有属性皆为必选项

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    interface Foo {
    name: string
    age?: number
    }
    type Bar = Required<Foo>
    // 相当于
    type Bar = {
    name: string
    age: number
    }

    Pick

    • 源码
    1
    2
    3
    type Pick<T,U extends keyof T> = {
    [K in U]:T[K]
    }

    作用:Pick(A,B);从A当中挑选B并返回

    详细:生成一个新类型,该类型拥有 T 中的 K 属性集 ; 新类型 相当于 T 与 K 的交集

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    interface Foo {
    name: string;
    age?: number;
    gender: string;
    }
    type Bar = Pick<Foo, 'age' | 'gender'>
    // 相当于
    type Bar = {
    age?: number
    gender: string
    }

    const todo: Bar= {
    age?: 3,
    gender: 男
    };

    Exclude

    • 源码
      • 分布式条件类型来说,当传入的被检查类型是联合类型的时候,在运算过程中就会被依次运算
    1
    type Exclude<T,U> = T extends U ? never : T;

    作用:Exclude<A,B>; 排除A当中的B

    详细: 如果 T 是 U 的子类型则返回 never 不是则返回 T(never可以理解为丢弃值不会返回)

    1
    2
    3
    4
    5
    6
    type A = number | string | boolean
    type B = number | boolean

    type Foo = Exclude<A, B>
    // 相当于
    type Foo = string

    Extract

    • 源码
    1
    type Extract<T,U> = T extends U ? T : never;

    作用:Extract<A,B> 从A中提取B

    详细: 如果 T 是 U 的子类型则返回 T 不是则返回 never (never可以理解为丢弃值不会返回)

    1
    2
    3
    4
    5
    6
    type A = number | string | boolean
    type B = number | boolean

    type Foo = Extract<A, B>
    // 相当于
    type Foo = number | boolean

    Omit

    • 源码
    1
    type Omit<T,K extends keyof any> = Pick<T,Exclude<T,K>>

    作用:Exclude<A,B>; 排除A当中的B

    详细: 如果 T 是 U 的子类型则返回 never 不是则返回 T(never可以理解为丢弃值不会返回)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    type Foo = {
    name: string
    age: number
    }

    type Bar = Omit<Foo, 'age'>
    // 相当于
    type Bar = {
    name: string
    }

    NonNullable

    • 源码
    1
    type NonNullable<T> = T extends null | undefined ? never : T;

    作用:NonNullable;从T中排除null 和 undefined

    详细: 从泛型 T 中排除掉 null 和 undefined

    1
    2
    3
    type t = NonNullable<'name' | undefined | null>;
    //相当于
    // type t = "name"

    Parameters

    • 源码
    1
    2
    type Parameters<T extends (...args:any) => any> 
    = T extends (...args:infer P) => any ? P: never;

    作用:Parameters< (形参) => 返回值 > 以元组的形式返回形参

    详细: 以元组的方式获得函数的入参类型

    1
    2
    3
    type t = Parameters<(name: string) => any>; // type t = [string]

    type t2 = Parameters<((name: string) => any) | ((age: number) => any)>; // type t2 = [string] | [number]

    ReturnType

    • 源码
    1
    2
    3
    export type ReturnType<T extends (...args:any) => any> 
    =
    T extends (...arg:any) => infer R ? R : any;

    作用:ReturnType< (形参) => 返回值 >

    详细: 获得函数返回值的类型

    1
    2
    type t = ReturnType<(name: string) => string | number>
    // type t = string | number

    Uppercase

    • 源码
    1
    type Uppercase<S extends string> = intrinsic

    Uppercase将StringType转为大写,TS以内置关键字intrinsic来通过编译期来实现。

    1
    2
    3
    type a = Uppercase<'abcDEF'>
    //等同于
    type a = 'ABCDEF';

    Lowercase

    • 源码
    1
    type Lowercase<S extends string> = intrinsic;

    Lowercase将StringType转为小写,TS以内置关键字intrinsic来通过编译期来实现。

    1
    2
    3
    type a = Lowercase<'abcDEF'>
    //等同于
    type a = 'abcdef';

    Capitalize

    • 源码
    1
    type Capitalize<S extends string> = intrinsic;

    Capitalize将StringType首字母转为大写。

    1
    2
    3
    type CapitalizeExample = Capitalize<"abc">;
    //等同于
    type CapitalizeExample = "Abc"

    Uncapitalize

    • 源码
    1
    type Uncapitalize<S extends string> = intrinsic;

    Uncapitalize将StringType首字母转为小写

    1
    2
    3
    type a = Uncapitalize<'AbcDEF'>
    //等同于
    type a = 'abcDEF';
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/d3048434.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    avatar
    梦洁
    小小的字,有大大的梦想~分享我的前端学习过程,经历,错误,和一些其他折腾过程
    关注下我(* ̄▽ ̄*)
    公告
    不断更新中,有问题请留言回复(会通过邮箱提醒~)
    最新文章
    \ No newline at end of file diff --git a/d47a757b.html b/d47a757b.html new file mode 100644 index 000000000..191b0d824 --- /dev/null +++ b/d47a757b.html @@ -0,0 +1 @@ +图书兄弟移动端vue3.x项目(含在线演示) | 梦洁小站-属于你我的小天地

    图书兄弟移动端vue3.x项目(含在线演示)

    EWSHOP图书兄弟介绍

    截图演示

    主页部分

    • /

    分类

    • /category

    购物车

    • /shopcart

    • 购物车为空

    个人中心

    • /user

    订单管理

    订单详情

    订单支付

    • 由于接口原因,只有支付宝,还有就是,如果想弄那个跳转的,弄下也简单

    地址管理

    新增地址

    编辑地址

    关于我们

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/d47a757b.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/d9f65d5.html b/d9f65d5.html new file mode 100644 index 000000000..9efb0b16b --- /dev/null +++ b/d9f65d5.html @@ -0,0 +1 @@ +使用nodejs导出md/Markdown文档当中的图片到本地并替换原始图片链接为本地图片链接 | 梦洁小站-属于你我的小天地

    使用nodejs导出md/Markdown文档当中的图片到本地并替换原始图片链接为本地图片链接

    有需求才有动力~

    有时候经常需要将md文件当中的图片离线保存下来,指不定那一天图床挂了,图片找不到了!但是typora没有一键保存到本地图片功能!,必须要一个个右键保存才可以!!!!太坑爹!!!

    下载

    原理

    1
    2
    3
    4
    5
    6
    7
    读取指定目录下的所有文件 => 获取md文件并将路径保存到数组A => 遍历每一个md文件,使用正则判断是否有图片链接

    => 当前md的文件下所有图片链接保存到数组B当中的某一项当中(使用push),重复此操作

    => 弹出一个数组A的值(也就是md文件的路径) => 弹出一个数组B(也就是当前md所对应的图片链接)

    => 对数组B进行遍历访问并保存图片到本地 => 替换内容 => 遍历完成后写入新内容

    具体看源代码吧,我把注释写的挺详细的

    使用

    • nodejs环境
      • 直接下载源码,记得先安装下依赖!然后 node ./index.js输入安装提示输入文件地址即可(注意要是文件夹!!!)

    使用

    • window环境 尝试了下打包,感觉打包的文件好大,打包过程

    使用也很简单,双击运行输入文件夹路径即可,文件夹路径,文件夹路径!

    exe运行

    我打包nodejs的一些配置

    • 配置了下文件(具体的可以看官网,我这里直接copy了别人的)

    1
    2
    3
    4
    5
    6
    7
    8
    "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
    },
    "pkg": {
    "scripts": "build/**/*.js",
    "assets": "views/**/*",
    "targets": "node16"
    }
    • 然后运行npm pkg ,然后会下载三个包
      • index-linux index-macos index-win
    • 然后生成三个平台的运行文件(可以有配置生成哪一个平台文件,具体看官网就可以~我这里就不研究了)

    npm pck生成的文件

    打包时候安装的文件

    注意下

    • 由于担心访问过快导致被屏蔽,这里设置了300毫秒的等待,每访问一次图片网站就等待300毫秒,当然,你可以自己更改下~

    • 修改 utils\index.js

    修改延迟

    使用效果

    • 使用后md当中的图片链接就被替换了
    • 测试了下 有链接的,有img标签的移动端md
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //普通的
    ![](https://s1.ax1x.com/2020/06/27/NyZQbQ.png)
    =>替换为了
    ![](移动端.assets/NyZQbQ.png)

    <img src="https://s1.ax1x.com/2020/06/27/NyZ1Ej.png" style="zoom: 25%;" />
    =>替换为了
    <img src="移动端.assets/NyZ1Ej.png" style="zoom: 25%;" />

    都替换正常和显示正常

    如图

    生成了对应存储图片的文件夹

    使用截图

    使用

    故意失败测试~防止失败就中断

    使用

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/d9f65d5.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/dac4a301.html b/dac4a301.html new file mode 100644 index 000000000..a83e18227 --- /dev/null +++ b/dac4a301.html @@ -0,0 +1 @@ +ant design vue自定义表单验证不生效和自定义校验内容有值后自动清除警告的方法 | 梦洁小站-属于你我的小天地

    ant design vue自定义表单验证不生效和自定义校验内容有值后自动清除警告的方法

    前置-先来看看下面预期效果是什么

    • 当我们点击提交表单的时候,下面的输出结果是什么?红色*代表必填字段

    • 很明显你可能会角色在**测试这一行当中会提示信息,**但是却不会

    问题原因及解决

    • 在使用ant - design vue的表单验证的时候,如果不填写name属性,那么最后校验的时候是不会进行校验的
    1
    2
    3
    4
    5
    6
    <!--不进行校验,缺少name-->
    <a-form-item label="测试" :rules="[{ required: true, message: '测试不能为空', whitespace: true },]">
    <div style="position: relative;display: inline-block">
    <a-input v-model:value="dataForm.name" :maxlength="20" style="width: 500px;" placeholder="请输入" />
    </div>
    </a-form-item>
    • 所以老老实实加上name属性吧
    1
    2
    3
    4
    5
    6
    <!--加上name就会校验了-->
    <a-form-item label="测试" name="name" :rules="[{ required: true, message: '测试不能为空', whitespace: true },]">
    <div style="position: relative;display: inline-block">
    <a-input v-model:value="dataForm.name" :maxlength="20" style="width: 500px;" placeholder="请输入" />
    </div>
    </a-form-item>

    值得一提

    • 有时候我们使用了自定义规则校验,无论是trigger:change还是trigger:blur,但数据更新后,都无法将警告提示进行自动更改

    • 从而如果有警告提示信息,尽管用户在之后输入了新值,也依旧会有报错,那么要怎么解决呢?

      • 很简单<a-form-item></a-form-item>添加ref,调用ref当中的clearValidate即可
    • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
      <a-form-item ref="groupChat" label="选择群聊" name="chatId" :rules="[{required:true,trigger:'change',validator:validatorChatGroup}]">
    <SelectChatGroup :corpId="$route.query?.corpId ?? null" v-model="dataForm.chatId.chatIds" />
    <SelectedChatGroupList v-if=" dataForm.chatId.chatIds.length" v-model="dataForm.chatId.chatIds"/>
    </a-form-item>

    //对自定义属性进行监视,当有值的时候就调用里面的方法进行清除提示
    watch:{
    'dataForm.chatId.chatIds':{
    handler(){
    this.$refs.groupChat.clearValidate();
    },
    }
    },

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/dac4a301.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/e031a88b.html b/e031a88b.html new file mode 100644 index 000000000..bfdcc8476 --- /dev/null +++ b/e031a88b.html @@ -0,0 +1 @@ +前端面试可能会问到的知识点记录 | 梦洁小站-属于你我的小天地

    前端面试可能会问到的知识点记录

    vue数据响应式原理

    • 在vue初始化的时候,会对data当中的每一个属性进行遍历,创建对应的setter和getters,并创建与这个key对应的dep,这个dep是一个数组,然后当对模板进行编译的时候,发现有使用到这个data当中的某一个属性的时候,就会创建一个watcher并添加到与data当中属性对应的dep当中

    • 当数据发生变化的时候,observe监视到了,就会通知dep当的所有watcher,然后watcher触发更新

    内联样式和其他优先级计算

    样式类别

    • 行内样式(内联样式): 直接写在标签上的属性

      • <标签名 style="属性1:属性值1;属性2:属性值2;">内容</标签名>
    • **内部样式:**将css样式写在style当中,比如下面代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <head>
    <style type="text/css">
    div{
    color: red;
    font-size: 12px;
    }
    </style>
    </head>

    • 外部样式: 通过link标签引入的
      • 比如<link rel="stylesheet" type="text/css" href="css路径"/>

    优先级

    根据权重来计算出来的

    • 没有行内样式的情况下是权重来计算,比如通配符*的权重为1 , 标签选择器的权重为10,类选择器的权重为100,id选择器的权重为1000来进行计算的(权重值可能不一样,但是是这样子来算的),内联样式最高
    • !import会将至最高级!

    数组的遍历

    具体看mdnweb吧~这里只举例子

    forEach

    for循环

    map

    filter

    for…of

    for…in

    vue的生命周期

    • VUE的生命周期钩子函数:就是指在一个组件从创建到销毁的过程自动执行的函数,在组件的整个生命周期内,钩子函数都是可被自动调用的,且生命周期函数的执行顺序与书写的顺序无关
    • vue2的生命周期
    1
    2
    3
    4
    5
    6
    7
    8
    9
    beforeCreate 创建前
    created 创建后
    beforeMount 挂载前
    mounted 挂载后

    beforeUpdate 更新前
    updated 更新后
    beforeDestroy 销毁前
    destroyed 销毁后
    • 需要重点关注的4个生命周期

    • beforeCreate 创建前

      • vue实例刚刚被创建(刚执行new的操作),其他什么都没有做
    • created 创建后

      • 此时data,methods被绑定到了实例身上,但是依旧获取不到DOM
    • beforeMount

      • 此时DOM为虚拟的DOM,无法操作虚拟的DOM
    • mounted

      • 此时DOM已经生成并且被替换为了具体的数据,可以操作DOM了

    v-mode的实现原理和在自定义组件中怎么实现

    v-model原来写法

    1
    <input type="text" v-model="msg"/>

    v-model拆解写法

    1
    <input type="text" :value="msg" @input="msg = $event.target.value"/>

    v-model当中在自定义组件要怎么实现呢?

    父组件当中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <template>
    <div>
    <One v-model="money"></One>
    <div>父容器的money{{ money }}</div>
    </div>
    </template>

    <script>
    export default {
    name: "",
    data() {
    return {
    money: 1000,
    };
    },
    };
    </script>

    子组件当中:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <template>
    <div>
    <input :value="value" @input="$emit('input',$event.target.value)" />
    <div>儿子当中的值{{ value }}</div>
    </div>
    </template>

    <script>
    export default {
    name: "One",
    props:["value"]
    };
    </script>

    效果

    路由导航守卫

    • beforeEach: 全局前置守卫(在所有的路由到来之前都需要经过,所以是Each这个单词)
    • **beforeEnter:**路由独享守卫
    • 还有
      • **beforeResolve:**全局解析守卫
      • afterEach:全局后置钩子
    • 还有组件内的守卫
      • beforeRouteEnter:
      • beforeRouteUpdate:
      • beforeRouteLeave:
    1
    2
    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
    35
    //官方版
    const UserDetails = {
    template: `...`,
    beforeRouteEnter(to, from) {
    // 在渲染该组件的对应路由被验证前调用
    // 不能获取组件实例 `this` !
    // 因为当守卫执行时,组件实例还没被创建!
    },
    beforeRouteUpdate(to, from) {
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 `/users/:id`,在 `/users/1` 和 `/users/2` 之间跳转的时候,
    // 由于会渲染同样的 `UserDetails` 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 因为在这种情况发生的时候,组件已经挂载好了,导航守卫可以访问组件实例 `this`
    },
    beforeRouteLeave(to, from) {
    // 在导航离开渲染该组件的对应路由时调用
    // 与 `beforeRouteUpdate` 一样,它可以访问组件实例 `this`
    },
    }

    //注释版
    beforeRouteEnter(to,from,next){
    console.log("路由进入之前",to,from);
    //手动调用next()方法,确保路由跳转继续执行
    next();
    }
    beforeRouteUpdate(to,from,next){
    console.log("路由更新之前",to,from);
    next();
    }
    beforeRouteLevae(to,from,next){
    console.log("路由离开之前",to,from);
    next();
    }

    伪类和伪元素

    • 伪类作用对象是整个元素,比如整个链接,整个段落,容器div等
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    a:link {
    color: #111
    }

    a:hover {
    color: #222
    }

    div:first-child {
    color: #333
    }

    div:nth-child(3) {
    color: #444
    }
    等等
    • 而伪元素作用于元素的一部分,一个段落的第一行或者第一个字母
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    p::first-line {
    color: #555
    }

    p::first-letter {
    color: #666
    }
    a::after {
    content: "helloWorld"
    }

    a::before {
    content: "helloWorld"
    }

    用promise写个ajax(仿照axios)

    • 首先了解下xhr发送时候的状态码
      • 0 代表还没有调用open
      • 1 代表还没有调用send方法
      • 2 代表还没有收到响应(刚刚发送)
      • 3 代表收到了部分数据
      • 4 代表收到了完整的数据
    • xhr的onreadystatechange是异步的~(因为有回调函数)

    代码:

    1
    2
    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
    35
    <script>
    //类似于axios({...})
    const axios = (options) => {
    //获取method 和 url
    let {
    method,
    url
    } = options;
    if (method && url) {
    var returnPromise = new Promise((resolve, reject) => {
    let xhr = new XMLHttpRequest();
    xhr.open(method, url);
    xhr.send();
    xhr.onreadystatechange = function () {
    if (xhr.readyState == 4 && xhr.status >= 200 & xhr.status < 300) {
    resolve(xhr.response);
    }
    };
    //监听错误的
    xhr.onerror = function () {
    reject(new Error(xhr.statusText))
    }
    });
    return returnPromise;
    } else {
    return Promise.reject("请输入完整的参数");
    }
    }
    axios({
    method: "get",
    url: "https://api.oick.cn/dog/ap1i.php"
    }).then(data => {
    console.log(data);
    });
    </script>

    css画一个梯形和三角形

    基本原理都是利用边框

    三角形:

    @在线演示

    关键是设置width为0

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <!DOCTYPE html>
    <head>

    <style>
    *{
    margin: 0;
    padding: 0;
    }
    #app{
    width: 0;
    border-top: 100px solid transparent;
    border-bottom: 100px solid green;
    border-left: 100px solid transparent;
    border-right: 100px solid transparent;
    }
    </style>
    </head>
    <body>
    <div id="app"></div>
    </body>
    </html>

    梯形:

    @在线演示

    关键是设置width和height的值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <!DOCTYPE html>
    <head>
    <style>
    *{
    margin: 0;
    padding: 0;
    }
    #app{
    width: 50px;
    height: 50px;
    border-top: 100px solid transparent;
    border-bottom: 100px solid green;
    border-left: 100px solid transparent;
    border-right: 100px solid transparent;
    }
    </style>
    </head>
    <body>
    <div id="app"></div>
    </body>
    </html>

    http和https响应的状态码有哪些

    • 200~300一般是请求成功的
      • 204代表服务器处理了请求,但没有返回任何数据(也就是没有内容)
    • 404 (not found) 找不到页面
    • 403 服务器拒绝了请求
    • 503 服务不可用
    • cookie: 存储的数据比较小,4kb左右,在有效期之前一直存在,一般用于验证用户,比如说token,并且每次都会携带在HTTP请求头中
    • localStorage: 可以长期存储在用户电脑并且所有的网页都可以访问,存储大小大概5m
    • sessionStorage: 有效期存储在当前页面,页面关闭后sessionStorage就失效(不违法同源策略的情况下),存储大小大概5m

    TCP三次握手,四次挥手

    TCP三次握手

    • 目的就是为了确认双方的接收能力和发送能力是否正常

    • 客户端向服务端发送一个SYN(同步序列),等待服务器确认

    • 服务端收到客户端的SYN后进行确认客户端的SYN包,然后也发送一个自己的SYN包,发送(SYN+ACK)包给客户端

    • 客户端收到SYN+ACK包后,向服务端发送确认包ACK,发送完成则建立连接,开始传输数据

    四次挥手

    • 客户端发出连接释放的报文,并 进入终止等待1 状态
    • 服务器收到连接释放报文,发出确认报文,并且服务器进入关闭等待状态
    • 客户端收到服务器确认请求后,进入 终止等待2 状态,(管子里面还有数据,要流完!)
    • 服务器将最后的数据传输完成的时候,就告诉客户端,向客户端发送连接释放报文,并且服务器进入最后确认状态
    • 客户端收到服务器的连接释放报文后,必须发出确认,此时客户端进入时间等待状态,注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态
    • 服务器收到客户端发送的确认,立即进入关闭状态

    大概过程

    for … in 和for … of

    简单来说一句话的博主

    for…in是es5的用于遍历key

    for…of是es6的用于遍历value

    • 先有in,再由of
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <script>
    var arr = ["李白", "诗人", "陋室铭"];
    for (var key in arr) {
    //0 1 2
    console.log(key);
    }

    for (var value of arr) {
    //李白 诗人 陋室铭
    console.log(value);
    }
    </script>

    一个比较神奇的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    Object.prototype.objCustom = function () {}; 
    Array.prototype.arrCustom = function () {};

    let iterable = [3, 5, 7];
    iterable.foo = "hello";

    for (let i in iterable) {
    console.log(i); // 0, 1, 2, "foo", "arrCustom", "objCustom"
    }
    //arrCustom是继承自Array的属性,objCustom是继承自Object的属性。

    for (let i of iterable) {
    console.log(i); // 3, 5, 7
    }

    nginx正向代理,反向代理

    正向代理:

    ​ 面向客户,帮助客户解决问题,比如说我要访问YouTube,配置nginx后就可以访问了,这就是正向代理

    反向代理:

    ​ 面向服务器,帮助服务器解决问题,比如配置代理转发请求,原来是本地的请求转发到远程

    css九宫格

    弹性盒布局笔记

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
    *{
    margin: 0;
    padding: 0;
    }
    #wrap{
    width: 306px;
    height: 304px;
    display: flex;
    border: 1px solid red;
    /* 关键是设置弹性盒是否换行,不然的话就会压缩 */
    flex-wrap: wrap;
    }
    #wrap div{
    width: 100px;
    height: 100px;
    border: 1px solid blue;
    }
    </style>
    </head>
    <body>
    <div id="wrap">
    <div id="one"></div>
    <div id="two"></div>
    <div id="three"></div>
    <div id="four"></div>
    <div id="five"></div>
    <div id="six"></div>
    <div id="seven"></div>
    <div id="eight"></div>
    <div id="nine"></div>
    </div>
    </body>
    </html>

    效果图

    微任务和宏任务

    宏任务:定时器

    微任务:promise

    微任务的优先级大于宏任务

    computed和methods区别

    • computed是带缓存的,只有当引用的数据发生变化的时候才会重新计算,而methods是每次调用都会重新执行

    • computed是响应式的,methods不是响应式的

      • 当computed当中靠依赖的值计算出来的结果发生改变的时候,会引发computed重新计算,而methods不会
    • computed可以具有getter和setter方法,因此可以赋值,而methods是不行的。

      • 比如computed
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    data(){
    return {
    name:"李",
    lastName:"白"
    }
    },
    computed:{
    fullName(){
    return this.name+this.lastName;
    },
    //等同于
    fullName:{
    get(){
    return this.name+this.lastName
    }
    },
    //写全点这样子写
    fullName:{
    get(){
    return this.name+this.lastName;
    },
    set(newValue){
    //处理newValue
    }
    }
    }

    闭包和原型和原型链

    闭包

    • 内部函数有对上层作用域的引用
    • 内部函数在所在定义域外保持引用并进行访问
    • 这位博主写的很详细@地址
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <script>
    function father(){
    var a = 100;
    var b = function(){
    //内部函数有对上层作用域的引用
    console.log(a);
    a++;
    }
    return b;
    }
    // 内部函数在所在定义域外保持引用并进行访问
    var temp = father();
    temp();
    temp();
    temp();
    </script>

    原型和原型链

    原型

    • 每一个函数(比如说构造函数),都具有一个属性叫prototype,这个属性指向的是一个对象,我们叫这个对象叫原型对象
    • 每一个实例化对象,都具有一个属性叫__proto__,这个属性指向其构造函数的prototype属性,并且有如下关系(实例化对象.__proto__ === 构造函数.prototype)

    原型链

    • 当对象在自身找不到被调用的属性或者方法的时候,就会去原型链身上寻找
    • 以下代码寻找属性sex过程
      • 1.在自身上寻找sex属性,如果有,就返回,没有就接着下一步寻找
      • 2.在其构造函数身上寻找是否有sex属性(通过实例化对象.__proto__来访问),有就返回,没有就下一步寻找
      • 3.在其构造函数身上的上一层再次寻找(因为原型对象也是一个对象,是一个实例对象),通过实例化对象.__proto__.__proto__来进行访问并寻找是否有sex属性
      • 发现没有,就返回undefined,
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <script>
    function Dog(name, color) {
    this.name = name;
    this.color = color;
    }
    //为原型链上添加一个属性'sex';
    Dog.prototype.sex = "未知";
    //建立一个实例化对象
    var xiaobai = new Dog("小白", "白色");
    //输出结果为 '未知'
    console.log(xiaobai.age);
    </script>
    证明在原型链上寻找
    • hasOwnProperty查找某一个对象自身是否有某一个属性,不会去查找他的原型链
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    <script>
    function Dog(name, color) {
    this.name = name;
    this.color = color;
    }
    //为原型链上添加一个属性'sex';
    Dog.prototype.sex = "未知";
    //建立一个实例化对象
    var xiaobai = new Dog("小白", "白色");
    //搜索实例化对象身上是否有属性'sex'
    var result = xiaobai.hasOwnProperty("sex");
    //输出结果为false
    console.log(result);
    </script>

    说一说new会发生什么?

    1. 创建一个新的实例化对象

    2. 该构造函数的this指向新的实例化对象

    3. 执行该构造函数体

    4. 返回这个this(如果没有返回值的情况下)

    5. 创建一个新对象,并在内存当中开辟一个新对象

    6. 将新对象的__proto__(隐式原型链)指向构造函数的prototype(显示原型链)

    7. 将构造函数的this指向新的实例化对象

    8. 返回这个新对象

    项目中的难点

    1. 编程式路由往同一地址跳转时会报错的情况,很莫名其妙,虽然不影响,但是控制台总是会报错,找github找百度才解决

    router配置文件中添加如下代码

    1
    2
    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
    const originalPush = VueRouter.prototype.push;
    //解决重复提交相同链接报错
    VueRouter.prototype.push = function push(location, onResolve, onReject) {
    if (onResolve || onReject)
    return originalPush.call(this, location, onResolve, onReject)
    return originalPush.call(this, location).catch((err) => {
    if (VueRouter.isNavigationFailure(err)) {
    // resolve err
    return err
    }
    // rethrow error
    return Promise.reject(err)
    })
    }
    const originalReplace = VueRouter.prototype.replace;
    VueRouter.prototype.replace = function replace(location, onResolve, onReject) {
    if (onResolve || onReject){
    //回调函数里面会用到this的指向,所以就要使用call
    return originalReplace.call(this, location, onResolve, onReject)
    }
    return originalReplace.call(this, location).catch((err) => {
    if (VueRouter.isNavigationFailure(err)) {
    //如果为相同链接引发的错误,返回错误原因,promise状态为resolve
    // resolve err
    return err
    }
    // rethrow error
    return Promise.reject(err)
    })
    }
    1. 轮播图问题,数据显示正常,但是轮播图好像有问题,后面发现是数据在到达之前就设置swiper轮播图,所以添加$nextTick即可,保证数据有了后,dom被渲染了后在执行swiper初始化操作
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    watch:{
    /* 监视bannerList数据更新 */
    bannerList(){
    /* 等待页面更新完成后执行回调 */
    this.$nextTick(()=>{
    //不应该使用类选择器的,这样子后期生成会选择所有相同的类!!
    var mySwiper = new Swiper(this.$refs.mySwiper, {
    // direction: 'vertical', // 垂直切换选项
    loop: true, // 循环模式选项
    // 如果需要分页器
    pagination: {
    el: ".swiper-pagination",
    },
    autoplay: {
    //触碰后不会停止自动切换
    disableOnInteraction: false,
    },
    // 如果需要前进后退按钮
    navigation: {
    nextEl: ".swiper-button-next",
    prevEl: ".swiper-button-prev",
    },
    });
    });
    }
    },

    数组的去重

    • set构造函数去重
      • 调用set构造函数转换为一个集合,在进行转换回去数组(扩展运算符…或者Array.from)就实现了去重
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <script>
    var tempArray = [1,2,3,4,5,5,6,7];
    //转化为set
    var tempSet = new Set(tempArray);
    //set转换回来数组 - 方法1
    var tempAfterArray1 = [...tempSet];
    ///set转换回来数组 - 方法2
    var tempAfterArray2 = Array.from(tempSet);
    //[1, 2, 3, 4, 5, 6, 7]
    console.log(tempAfterArray1);
    //[1, 2, 3, 4, 5, 6, 7]
    console.log(tempAfterArray2);
    </script>
    • 普通方法去重1-双重for循环
      • 通过双重for循环,第一层循环为i的时候,第二层循环就从i+1位置开始遍历,如果发现相同的,则通过splice来删除,(splice会改变原数组)
    1
    2
    3
    4
    5
    6
    7
    8
    for(var i = 0;i<tempArray.length;i++){
    for(var j = i+1;j<tempArray.length;j++){
    if(tempArray[i] == tempArray[j]){
    tempArray.splice(j,1);
    j--;
    }
    }
    }
    • 普通方法去重-filter和indexOf结合
      • indexOf在数组中,返回该数组中第一个找到的索引位置,若未找到,则返回-1
    1
    2
    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
    35
    36
    37
    38
    39
    40
    var tempArray = [1, 2, 5, 5, 6, 6, 7];

    //item为当前遍历的项
    //index为当前遍历项的索引
    var a = tempArray.filter((item, index) => {
    return tempArray.indexOf(item) == index;
    })
    //[1, 2, 5, 6, 7]
    console.log(a);

    //遍历过程
    item = 1,index=0
    tempArray.indexOf(item) 返回 0
    return 0 == 0 ;//为true,存储'1'

    item = 2,index=1
    tempArray.indexOf(item) 返回 1
    return 1 == 1 ;//为true,存储'2'


    item = 5,index=2
    tempArray.indexOf(item) 返回 2
    return 2 == 2 ;//为true,存储'5'

    item = 5,index=3
    tempArray.indexOf(item) 返回 2
    return 2 == 3 ;//为false,不存储


    item = 6,index=4
    tempArray.indexOf(item) 返回 4
    return 4 == 4 ;//为true,存储'6'

    item = 6,index=5
    tempArray.indexOf(item) 返回 4
    return 4 == 5 ;//为false,不存储

    item = 7,index=6
    tempArray.indexOf(item) 返回 6
    return 6 == 6 ;//为true,存储'7'
    • 使用indexOf
    1
    2
    3
    4
    5
    6
    let temp = [];
    tempArray.forEach(item=>{
    if(temp.indexOf(item)===-1){
    temp.push(item);
    }
    })

    ES6+的常见语法

    let

    特点:

    • 没有变量提升的功能
    • 块级作用域
    • 不可以重复声明
      • 不可以出现let a = 100;然后又出现let a = 90;但是var变量可以
    • 具有暂时性死锁

    解构赋值

    数组的解构赋值

    1
    2
    3
    4
    5
    6
    7
    8
    var str = "动感超人&18";
    let[name,age] = str.split("&");
    console.log(name,age);//动感超人 18

    也可以跳过接收
    var str = "动感超人&18";
    let[,age] = str.split("&");
    console.log(age);// 18

    对象的解构赋值

    1
    2
    3
    4
    5
    6
    var objName = {
    name:"李白",
    age:2000,
    }
    let {name,age}=objName;
    console.log(name,age);//李白 2000

    箭头函数

    • this始终指向函数声明时所在作用域下的this的值(在什么环境下,什么this执行,不会受其他改变)

    Promise

    promise常用的方法

    • Promise.reject(reason)方法返回一个带有拒绝原因的Promise对象。
    • Promise.resolve(value)方法返回一个以给定值解析后的Promise 对象
    • Promise.all(iterable)方法获取这个可迭代对象的promise的结果,
      • 当iterable有一个是reject,那么就会触发catch,返回的值就是这个reject传递过来的值
      • 当iterable全部为resolve,那么就会触发then,返回的值就是由resolve传递过来的值组成的数组
      • 一句话,有错误立即输出错误的reject,没有错误就输出resolve返回的值
    1
    2
    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
    //iterable中有一个是reject
    <script>
    var p1 = Promise.resolve("promise1");
    var p2 = Promise.reject("promise2");
    var p3 = Promise.resolve("promise3");
    Promise.all([p1, p2, p3]).then((data) => {
    //不会被执行
    console.log(data);
    }).catch(function (error) {
    //catch方法将会被执行,输出结果为:"promise2"
    console.log(error);
    });
    </script>

    //iterable均为resolve
    <script>
    var p1 = Promise.resolve("promise1");
    var p2 = Promise.resolve("promise2");
    var p3 = Promise.resolve("promise3");
    Promise.all([p1, p2, p3]).then((data) => {
    //输出['promise1', 'promise2', 'promise3']
    console.log(data);
    }).catch(function (error) {
    //不会被执行
    console.log(error);
    });
    </script>

    • Promise.race(iterable)
      • 一句话概括:这个iterable竞争,只要有一个完成了,那么这个Promise.race的返回值就是这个第一个完成的值,不管结果本身是成功状态还是失败状态。

    class类

    可选链操作符

    • ?. 为可选链操作符
    • 有一段数据,我们是通过服务器来请求的,但是可以这段数据会嵌套很多层,有时候为了不报错,我们不得不去判断是否有数据才会去接着寻找下一层
    • 比如下面这层嵌套
    1
    2
    3
    4
    5
    6
    7
    8
    9
    var a = {
    b:{
    c:{
    d:{
    name:"李白的师傅"
    }
    }
    }
    }
    • 在这之前,我们需要找寻字段d的数据,就要这样子,才不会发生报错
    1
    var dataD = a && a.b && a.b.c && a.b.c.d && a.b.c.d.name
    • 有了可选链操作符,就不用这么麻烦了,当某一段/某一层数据不存在的时候,会返回undefined
    1
    var dataD = a?.b?.c?.d?.name

    ES6模块化

    async await

    • 一句话概括作用:使得异步的代码像同步一样实现

    async的返回值

    • async函数的返回值都是promise
    • 如果返回一个Promise类型的对象,由这个返回的Promise对象决定是resolve或者reject
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <script>
    async function getResult(){
    return Promise.resolve("解决了");
    }
    console.log(getResult());
    </script>

    <script>
    async function getResult(){
    return Promise.reject("失败了");
    }
    console.log(getResult());
    </script>

    resolve-输出一个状态为pending的promise

    resolve-输出一个状态为pending的promise

    reject-输出一个状态为pending的promise 并引发报错提示

    reject-输出一个状态为pending的promise

    • 如果返回一个非Promise类型,返回的结果js会自动帮助封装成为一个promise对象
    1
    2
    3
    4
    5
    6
    <script>
    async function getResult(){
    return "看看我是什么"
    }
    console.log(getResult());
    </script>

    返回一个状态为fulfiled的promise

    返回一个状态为fulfiled的promise

    • 抛出错误 ,返回的结果是一个失败的Promise对象
    1
    2
    3
    4
    5
    6
    <script>
    async function getResult(){
    return new Error('出错啦')
    }
    console.log(getResult());
    </script>

    抛出错误的async返回值

    await

    有时候在项目当中,经常使用await来发送ajax请求获取数据后的操作,因为await可以阻塞进程,等待这个promise有结果后才开始后面的代码!!!

    await遇上成功的promise(也就是resolve了)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <script>
    function testAwait() {
    return new Promise((resolve, reject) => {
    resolve("成功解决问题")
    })
    }

    async function getResult() {
    var result;
    try {
    result = await testAwait();
    console.log("成功了,结果为-", result);
    } catch (error) {
    console.log("捕捉到失败,原因为-", error);
    }
    console.log("我可不可以执行到这里")
    }
    console.log(getResult());
    </script>

    执行结果

    await遇上失败的promise(也就是reject了)

    例子1: 遇上失败的promise并且使用try...catch捕捉

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <script>
    function testAwait() {
    return new Promise((resolve, reject) => {
    reject("失败了")
    })
    }

    async function getResult() {
    var result;
    try {
    result = await testAwait();
    console.log("成功了,结果为",result);
    } catch (error) {
    console.log("捕捉到失败,原因为",error);
    }
    //输出结果证明可以
    console.log("我可不可以执行到这里")
    }
    console.log(getResult());
    </script>

    例子1输出结果

    遇上失败的promise并且使用`try...catch`捕捉

    例子2: 遇上失败的promise没有使用,没有使用try..catch捕捉

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <script>
    function testAwait() {
    return new Promise((resolve, reject) => {
    reject("失败了")
    })
    }

    async function getResult() {
    var result = await testAwait();;
    console.log("获取到的结果为",result);
    console.log("我可不可以执行到这里");//输出结果证明不会执行到这里
    // try {
    // result = await testAwait();
    // console.log("成功了,结果为",result);
    // } catch (error) {
    // console.log("捕捉到失败,原因为",error);
    // }
    }
    console.log(getResult());
    </script>

    例子2输出结果

    会发现报错了,并且console.log("我可不可以执行到这里") 没有执行,因为promise失败导致程序中断!

    遇上失败的promise没有使用,没有使用`try..catch捕捉`

    总结: await遇上失败的promise(也就是reject了)
    • await一旦遇到reject,后面代码就不会被执行了
    • 可以使用try...catch来解决,并且catch捕捉到的原因为reject传递的参数值

    模板字符串

    tab上面按键的符号 里面可以用${变量名}来使用变量

    1
    `你的名字为${name},年龄为${age}`

    什么是执行上下文

    ​ 执行上下文是指函数调用时在执行栈中产生的当前函数的执行环境,该环境如隔绝外部世界的容器边界,保管可访问的变量、this对象等。(说通俗点就是函数执行时候的一个环境)

    执行上下文

    分类

    • 分为全局执行上下文(我理解为刚执行js脚本时候的一些初始化操作,比如我们没有写window对象我们却可以使用window对象,并且却可以直接输出this)
    • 函数执行上下文

    注意

    不管是全局执行上下文,还是函数执行上下文,上下文的创建过程都是如下

    • 第一步:创建函数上下文,压入栈
      1. 收集变量,形成变量对象(里面也包含了this这个变量)
      2. 确定this的指向(全局执行上下文的this指向的是window)
      3. 确定作用域链(作用域链,查找变量的过程,如果在作用域链上找不到就报错),作用域链是数组的形式
    • 第二步: 执行上下文(也就是执行里面的代码)

    使用这一段代码来作为全局执行上下文和函数执行上下文的例子

    1
    2
    3
    4
    5
    6
    7
    <script>
    function Dog(name,age){
    this.name = name;
    this.age = age;
    }
    var xiaobai = new Dog("小白",10);
    </script>

    1.先全局执行上下文

    第一步:创建全局执行上下文,压入栈

    1.收集变量,形成变量对象

    收集变量,形成变量对象

    2.确定this的指向

    2确定this的指向

    3.确定作用域链

    如图

    第二步:执行全局上下文 比如说赋值

    要执行的代码如下,注意注释

    • 执行可以执行的代码,比如说函数的调用,变量的赋值之类的
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <script>
    //函数定义,不执行
    function Dog(name,age){
    this.name = name;
    this.age = age;
    }
    //执行
    var xiaobai = new Dog("小白",10);
    </script>

    执行var xiaobai = new Dog("小白",10);代码的过程 这里是在调用函数的过程,所以可以理解为在调用函数执行上下文,所以我们跳转到函数执行上下文的过程

    2.函数执行上下文

    第一步:创建函数执行上下文

    1.收集变量,形成变量对象,2.确定this的指向 3.确定作用域链

    如图

    第二步:执行函数上下文

    1
    2
    3
    4
    5
    function Dog(name,age){
    //执行里面的代码
    this.name = name;
    this.age = age;
    }

    如图

    注意: 执行函数上下文完成,所属的栈会被丢弃!

    但是,丢弃之前返回了this指向,并存储在了变量xiaobai当中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function Dog(name,age){
    //执行里面的代码,执行完成后销毁
    this.name = name;
    this.age = age;
    //你不添加系统也会自动加一个
    return this;
    }

    var xiaobai = new Dog("小白",10);

    如图,销毁前返回了this并保存在了变量xiaobai当中

    如图

    数据绑定原理

    需要解决的2个问题

    • 如何知道data的属性发生了变化的 (重要)
      • 也就通过observer为data当中的每一个属性通过数据劫持添加gettersetter
      • 原来是没有gettersetter的,通过数据劫持去添加了set,get
    • 那么要如何知道当前这个数据变化要更新哪些节点呢?
      • 订阅者/发布者模式

    img

    1.发布者observer

    • 给data中的所有层次的属性都添加settergetter(也就是数据劫持)
    • 同时为data当中的每一个属性创建一个对应的dep对象
    • dep对象data当中的每一个属性为一一对应的对象

    1.5中间还需要一个dep(订阅器),去通知订阅者

    • dep和watcher的关系是初始化的时候就建立起来的

    2.订阅者(watcher)

    • 解析每一个模板语法都创建一个watcher(比如模板当中有一个标签使用了插值语法,那就会创建一个watcher,v-bind也会触发创建watcher)
    • 并且创建watcher的时候最后一个参数为用于更新节点的回调函数
    • 订阅者需要知道数据变了,从而去更新界面

    webpack

    说说看什么是webpack

    • webpack就是一个模块化打包工具,它将所有的文件看成是模块,它会分析项目目录下的模块(比如说less,sass等),并将其转换和打包为合适的格式供浏览器使用

    什么是loader,什么又是plugin

    • loader: 是文件加载器,可以加载资源文件,并对这些文件进行一些处理,比如:编译,压缩等,
    • plugin:webpack在运行的生命周期会广播出许多事件,plugin可以监听这些事件,在合适的时机中通过webpack提供的api改变输出结果

    区别:

    • loader是将A文件进行编译形成B文件,这里操作的是文件,A.less => A.css
    • plugin是用于在webpack打包编译过程里,在对应的事件节点里执行自定义操作,比如资源管理、bundle文件优化等操作

    有哪些常见的Loader,他们都是解决什么问题的

    • 原本的webpack只能处理js/json的模块,有了loader或者plugin后,就可以处理更多的模块了

    常见的模块

    1. css-loader: 加载.css文件(也就是可以使用css模块了)
    2. style-loader:使用<style>将css-loader内部样式注入到我们的HTML页面
      1. 也就是将css-loader内部样式通过js在页面head部分创建style标签并将样式放入style标签内
    3. bable-loader:es6转化为es6(js语法转换)
    4. mini-css-extract-plugin: 提取css为单独的文件
    5. postcss(完整的应该为postcss postcss-loader postcss-preset-env) :处理css兼容性问题

    解释下引用数据类型的赋值和基本数据类型的赋值

    • 二种赋值都是将变量保存的内容赋值给另外一个变量
    • 只不过基本数据类型的变量保存的是值,所以基本数据类型赋值给别人的是值
    • 而引用数据类型变量保存的是地址,所以赋值的是一个地址给别的变量

    nodejs的引入/暴露和es6的引入/暴露-简记

    • 这里只记录了下简单的,新遇到的问题,具体想看es6的暴露和引入的具体的,可以看我之前写的文章,@地址

    es6

    引入

    • es6的引入需要在模块的最前面,不可以出现如下代码(使用静态的import会出现的问题)
    1
    2
    3
    4
    5
    import a from "./a.js"

    function getNumber(){
    import b from "./b.js";
    }

    否者出现就会报错,提示导入声明只能在模块的顶层使用

    • 那是因为你使用了静态的import,标准用法的 import 导入的模块是静态的,会使所有被导入的模块,在加载时就被编译(无法做到按需编译,降低首页加载速度)。有些场景中,你可能希望根据条件导入模块或者按需导入模块,这时你可以使用动态导入代替静态导入
      • 在vue路由的时候,经常使用这种动态的导入方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import('/modules/my-module.js')
    .then((module) => {
    // Do something with the module.
    });
    //这种使用方式也支持 await 关键字。
    async function getNumber(){
    const b = await import("./b.js");
    console.log(b);
    }
    getNumber();

    暴露

    • es6的暴露出去的都是对象
      • 在引入的时候除了默认暴露,其他的基本上都需要解构赋值才可以

    nodejs

    引入

    • nodejs是commojs的规范,他可以在使用到的时候再引入
    • 比如下面有一个需求,就是是当用户请求某一个路由的时候,我才去读取数据并返回给用户,这个时候就可以体现出nodejs引入的好处了,是代码执行到这一段的时候才去引入
    1
    2
    3
    4
    5
    6
    // 分类页
    router.get("/getCategoryData",(ctx)=>{
    //读取分类页数据,代码执行到这一行才开始引入
    const categoryData = require("../datas/categoryDatas.json");
    ctx.body = categoryData
    })

    暴露

    • node提供了二种暴露方式

      • module.exports
      • exports
    • 不想看下面的可以记住这句话就可以,不要使用exports去暴露,使用module.exports去暴露

    • exportsmodule.exports是一个引用

      • 当通过exports去改变内存块里内容时,module.exports的值也会改变
      • 当通过module.exports去改变内存块里内容时,exports的值也会改变
      • 当module.exports所指向的地址被改变的时候,exports不会被改变
      • 当exports所指向的地址被改变的时候,module.exports不会被改变
    • node的暴露可以简单理解为暴露什么,引入的就是什么

    暴露引入如下-暴露什么,引入的就是什么

    说说三级联动

    • 最典型的三级联动比如说地址选择的时候,叫我们依次选择省-市-区
    • 省-市-区在数据设置的时候就是这样子设置去实现三级联动的
      • 除了省外,每一级的数据都包括上一级的id信息
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    {
    "province": [
    {
    "name": "江西省",
    "id": 1
    }
    ],
    "city": [
    {
    "name": "南昌市",
    "id": 10,
    "parentId": 1
    }
    ],
    "area": [
    {
    "name": "高新区",
    "id": 100,
    "parentId": 1
    }
    ]
    }

    网页的seo优化

    • meta标签,比如添加keywords属性和description属性

    • 比如我的@网站就添加下面二个meta优化seo收录

      1
      2
      <meta name="keywords" content="前端,JavaScript,nodejs,es5,es6,vue">
      <meta name="description" content="小小的字,有大大的梦想~分享我的前端学习过程,经历,错误,和一些其他折腾过程">
    • 也可以通过一些语义化标签比如

      • header
      • footer
      • title
      • img中的alt属性
    • 更多关于SEO可以看这个wiki网站

    图片的alt和title

    • alt
      • 网络错误、内容被屏蔽或链接过期时,会显示alt 属性中的文本
    • title
      • 鼠标悬停时候的提示信息
      • title 属性不是 alt 属性可接受的替代品。并且,避免将 alt 属性的值直接复制到同一幅图片的title 属性上。这样可能会让一些屏幕阅读器把同一段描述读两遍,造成一定程度上的困扰。

    更改this的执行的方法

    假设有一个函数fn需要改变this执行,要怎么做

    • call,apply,bind方法作用
      • 让任意的函数,成为对象的方法,从而改变this的指向

    call方法

    • @mdnwebDoc - call

    • fn.call(this的指向,参数1,参数2,参数3,….)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      function Product(name, price) {
      this.name = name;
      this.price = price;
      }

      function Food(name, price) {
      Product.call(this, name, price);
      this.category = 'food';
      }

      //等同于,至于为什么等于
      //这就要涉及到创建一个构造函数的时候究竟做了什么
      //创建构造函数其实就是在this(新实例化对象上)上添加赋值并返回这个this
      //所以你看构造函数都没有返回值,那是因为系统自动添加了返回值
      //自动添加了return this
      function Food(name, price) {
      this.name = name;
      this.price = price;
      this.category = 'food';
      }
      console.log(new Food('cheese', 5).name);
      // expected output: "cheese"

    • 备注:该方法的语法和作用与 apply() 方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组

    语法

    1
    function.call(thisArg, arg1, arg2, ...)

    参数

    thisArg

    • 可选的。在 function 函数运行时使用的 this 值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 nullundefined 时会自动替换为指向全局对象,原始值会被包装。

    arg1, arg2, ...

    • 指定的参数列表

    apply方法

    语法

    1
    2
    apply(thisArg)
    apply(thisArg, argsArray)

    参数说明

    thisArg

    • func 函数运行时使用的 this 值。请注意,this 可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 nullundefined 时会自动替换为指向全局对象,原始值会被包装。

    argsArray

    • 一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。如果该参数的值为 nullundefined,则表示不需要传入任何参数。从 ECMAScript 5 开始可以使用类数组对象。浏览器兼容性请参阅本文底部内容。

    bind方法

    • fn.bind(this的指向,[参数1,参数2,…])

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      const module = {
      x: 42,
      getX: function() {
      return this.x;
      }
      };

      const unboundGetX = module.getX;
      console.log(unboundGetX()); //里面的this指向window
      //window里面没有x变量,所以返回undefined

      //更改this的执行,返回一个函数,并赋值给boundGetX
      const boundGetX = unboundGetX.bind(module);
      //执行更改this后的函数
      console.log(boundGetX());//输出42
    • bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

    语法

    1
    function.bind(thisArg[, arg1[, arg2[, ...]]])

    参数

    thisArg

    • 调用绑定函数时作为 this 参数传递给目标函数的值。 如果使用new运算符构造绑定函数,则忽略该值。当使用 bindsetTimeout 中创建一个函数(作为回调提供)时,作为 thisArg 传递的任何原始值都将转换为 object。如果 bind 函数的参数列表为空,或者thisArgnullundefined,执行作用域的 this 将被视为新函数的 thisArg

    arg1, arg2, ...

    • 当目标函数被调用时,被预置入绑定函数的参数列表中的参数。

    布局 - 圣杯布局

    • 圣杯布局-左右两边宽度固定,中间自适应

    示例1 - float布局

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
    *{
    margin: 0;
    padding: 0;
    }
    .left{
    width: 100px;
    height: 100%;
    background-color: red;
    float: left;
    }
    .content{
    width: calc(100% - 200px);
    height: 100%;
    background-color: green;
    float: left;
    }
    .right{
    width: 100px;
    height: 100%;
    background-color: blue;
    float: right;
    }
    </style>
    </head>
    <body>
    <!-- 圣杯布局 左右两边宽度固定,中间自适应-->
    <div class="left">

    </div>
    <div class="content">

    </div>
    <div class="right">

    </div>
    </body>
    </html>
    • 或者上面一个在套一层外壳
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
    *{
    margin: 0;
    padding: 0;
    }
    .container{
    /* 解决高度塌陷 */
    overflow: hidden;
    }
    .left{
    width: 100px;
    height: 100%;
    background-color: red;
    float: left;
    }
    .content{
    width:calc(100% - 200px);
    height: 100%;
    background-color: green;
    float: left;
    }
    .right{
    width: 100px;
    height: 100%;
    background-color: blue;
    float: right;
    }
    </style>
    </head>
    <body>
    <!-- 圣杯布局 左右两边宽度固定,中间自适应-->
    <div class="container">


    <div class="left">

    </div>
    <div class="content">

    </div>
    <div class="right">

    </div>
    </div>
    </body>
    </html>

    示例2-display布局

    • 设置外层父元素display:flex,中间设置增长系数为flex-grow:1即可,左右二边的宽度依旧是固定
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
    *{
    margin: 0;
    padding: 0;
    }
    .container{
    width: 100%;
    height: 100%;
    display: flex;
    }
    .left{
    width: 100px;
    height: 100%;
    background-color: red;
    }
    .content{
    background-color: green;
    flex-grow: 1;
    }
    .right{
    width: 100px;
    height: 100%;
    background-color: blue;
    }
    </style>
    </head>
    <body>
    <!-- 圣杯布局 左右两边宽度固定,中间自适应-->
    <div class="container">

    <div class="left">

    </div>
    <div class="content">
    display:flex;
    flex-grow:1;
    </div>
    <div class="right">

    </div>
    </div>
    </body>
    </html>

    效果图

    每一个js都需要导入Vue,并且多次去使用Vue.use(),会造成重复吗?

    • 不会,因为webpack打包的时候会有缓存
    • webpack的机制是允许多次引用,只会进行一次打包的。
    • @思否回答

    Vue.use都做了那些事情

    • Vue.use我们可以传入一个对象和函数
    • 如果是一个对象,那么会去寻找对象当中的install方法,并会默认传入Vue参数给install方法

    test.js文件

    1
    2
    3
    4
    5
    6
    const reg = {
    install(Vue){
    console.log("传入的参数",Vue);
    }
    }
    export default reg;

    输出结果

    main.js文件

    1
    2
    3
    4
    import Vue from 'vue';

    import Reg from "@/components/test.js";
    Vue.use(Reg,"参数1");
    • 如果是一个函数,那么就会被视为install方法去执行这个函数
    • 那么Elementui是怎么做到的只需要引入并注册就可以全局使用自定义组件的呢?通过install方法
      • 如下示例,通过这样子,就可以全局使用了

    test.js文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    import MyButton from "./MyButton.vue";//引入自定义组件
    const components = [
    MyButton,
    ]
    const reg = {
    install(Vue){
    //循环遍历注册组件
    components.forEach(component=>{
    //Vue.component("要注册的注册名称",注册的组件);
    Vue.component(component.name,component);
    })
    }
    }
    export default reg;

    main.js文件

    1
    2
    3
    4
    import Vue from 'vue';

    import Reg from "@/components/test.js";
    Vue.use(Reg,"参数1");

    说说vue3和vue2区别

    • vue3: 速度更快,体积减少,更易维护,更接近原生,更易使用,
    • vue3:组合式api
    • 巴拉巴拉巴拉,太复杂了这部分,具体百度

    下面代码输出怎么输出2,怎么输出1

    1
    2
    3
    4
    5
    6
    7
    var a = 1;
    var obj = {
    a:2,
    fn(){
    console.log(this.a)
    }
    }
    • 输出2的情况
      • 当执行obj.fn的时候,this指向obj对象,所以输出obj对象当中的2
    1
    2
    3
    4
    5
    6
    7
    8
    var a = 1;
    var obj = {
    a: 2,
    fn() {
    console.log(this.a);
    },
    };
    obj.fn();//输出2
    • 输出1的情况
      • 需要改下,把函数改为箭头函数,因为箭头函数是没有this的所以这个时候的this执行上层作用域的this,也就是window,所以输出全局变量当中的1
        • 箭头函数中的this引用的是最近作用域中的this,即向外层作用域中逐级查找this,直到有this的定义。
    1
    2
    3
    4
    5
    6
    7
    8
    var a = 1;
    var obj = {
    a: 2,
    fn:()=>{
    console.log(this.a);
    },
    };
    obj.fn(); //输出1

    写出如下代码要求

    • 要求书写一个函数如下
    1
    2
    3
    4
    5
    6
    a=fn()
    console.log(a()) //1
    console.log(a()) //2

    a(2) //2
    a(3) //5
    • 写法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    <script>
    function fn() {
    let a = 0;
    return function (number = 1) {
    return (a += number);
    };
    }
    let a = fn();
    console.log(a(2));
    console.log(a(3));
    </script>

    要如何实现如下图布局

    代码

    1
    2
    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
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
    *{
    margin: 0;
    padding: 0;
    }
    .container{
    display: flex;
    justify-content: space-between;
    }
    .container>div{
    width: 100px;
    height: 100px;
    border: 3px solid red;
    }
    </style>
    </head>
    <body>
    <div class="container">
    <div></div>
    <div></div>
    <div></div>
    </div>
    </body>
    </html>

    效果

    说说自定义组件怎么实现v-model(双向绑定)

    • 大概过程就是父组件给子组件添加一个属性v-model="要绑定的值"
      • 这其实是简写,全称为<自定义组件 :value="要绑定的值" @input="value = $event"></自定义组件>
      • 在自定义组件当中,$event就是$emit当中第二个参数传递过来的数据
    • 子组件接收到后使用props去接收这个绑定的值,并且是以属性名为value的情况下接收(也就是props:['value'])
    • 子组件在需要修改的地方去触发自定义事件input,并传递更新后的值

    父组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    <template>
    <div>
    我是父组件:<br/>
    {{account}}
    <Self v-model="account"></Self>
    </div>
    </template>

    <script>
    import Self from "./self.vue"
    export default {
    name: '',
    data(){
    return {
    account:100
    }
    },
    components:{
    Self,
    }
    }
    </script>

    子组件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <template>
    <div>
    <div>我是自定义组件</div>
    <div>
    {{value}}
    <!-- 修改值 -->
    <input type="text" :value="value" @input="$emit('input',$event.target.value)"/>
    </div>
    </div>
    </template>

    <script>
    export default {
    name: '',
    props:["value"]
    }
    </script>

    Promise.all当失败的时候会返回失败的结果,有没有办法都返回成功的,不会失败

    情况说明

    • 下面的代码只会输出执行结果-失败,因为Promise.all是当里面有一个是失败的时候,会只会返回那个失败的结果
    • 那么下面我就来说说看怎么解决这种情况,让我们可以获取不管失败还是成功的返回结果
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    var a = new Promise((resolve,reject)=>{
    // 模拟ajax请求
    setTimeout(()=>{
    resolve("执行结果-成功");
    },1000)
    });
    var b = new Promise((resolve,reject)=>{
    // 模拟ajax请求
    setTimeout(()=>{
    reject("执行结果-失败");
    },1000)
    });
    Promise.all([a,b])
    .then(response=>{
    console.log(response);
    document.write(response);
    })
    .catch(reason=>{
    console.log(reason);
    document.write(reason);
    })

    方法1: reject改为resolve

    • reject改为resolve,这样子不管成功还是失败都会返回结果了
    • @在线演示地址
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    var a = new Promise((resolve,reject)=>{
    // 模拟ajax请求
    setTimeout(()=>{
    resolve("执行结果-成功");
    },1000)
    });
    var b = new Promise((resolve,reject)=>{
    // 模拟ajax请求
    setTimeout(()=>{

    resolve("执行结果-失败");
    },1000)
    });
    Promise.all([a,b])
    .then(response=>{
    console.log(response);
    document.write(response);
    })
    .catch(reason=>{
    console.log(reason);
    document.write(reason);
    })

    方法2——-别人的

    • 首先我们需要知道Promise的状态具有可传递性,其次catch方法在执行后也会返回一个状态为resolved的新Promise实例,所以我们只要将可能reject的Promise实例先catch一遍就可以了,就像做一次状态预加工:
    • @原文链接
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    var p1 = new Promise((resolve, reject) => {
    setTimeout(() => {
    resolve(1);
    }, 1000);
    });
    var p2 = new Promise((resolve, reject) => {
    setTimeout(() => {
    reject(2);
    }, 1000);
    });

    var promiseArr = [p1, p2];
    var promiseArr_ = promiseArr.map(function (promiseItem) {
    return promiseItem.catch(function (err) {
    return err;
    })
    });

    Promise.all(promiseArr_)
    .then((resp) => {
    console.log(resp);//[1,2]
    }).catch((err) => {
    console.log(err);
    });

    Promise.then

    • Promise.then(成功回调函数,失败回调函数)(用的比较多的可能是只填写一个成功回调函数,然后通过catch来执行失败时候的回调的)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var p1 = new Promise((resolve, reject) => {
    resolve('成功!');
    // or
    // reject(new Error("出错了!"));
    });

    p1.then(value => {
    console.log(value); // 输出 成功!
    }, reason => {
    //执行reject,就会跳转到这里
    console.error(reason);
    });

    输出结果如图

    • then在前面,并且then里面具有失败回调函数,那么执行then里面的不执行catch里面的失败回调函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var p1 = new Promise((resolve, reject) => {
    reject(new Error("出错了!"));
    });
    p1.then(value => {
    console.log(value);
    }, reason => {
    //执行reject,就会跳转到这里
    console.error(reason); // 出错了!
    }).catch(res=>{
    //不会执行这里的代码
    console.log(res);
    });

    输出结果如图

    • 并且,Promise.then()Promise.catch()都会返回一个Promise,因为是方法嘛,会有返回值很正常

      • 我们知道,在没有书写返回值的情况下,不在特殊情况下(特殊情况下比如构造函数,就是返回this),会返回undefined,那么我们可以看看说这句话说的对不对

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        var p1 = new Promise((resolve, reject) => {
        reject(new Error("出错了!"));
        });
        p1
        .catch(res=>{
        //执行reject,就会跳转到这里
        console.log(res);//输出 出错了!
        return "123";
        })
        .then(
        // 成功回调
        (value) => {
        console.log(value);
        },
        // 失败回调
        (reason) => {
        //不会
        console.error(reason);
        }
        );
      • 执行上方代码,会输出出错了123

      • 那么我们改下那个return "123 ,把它改为return "456"

      二次return

    • 那么为什么上面代码只会执行成功回调呢?原因如下

      • return非promise,则PromiseState永远为resolve,且值为对应的回调函数(第一个或者第二个)的返回值
      • return 为Promise,则PromiseState为return当中的Promise执行结果,PromiseValue为return当中的Promise当中的resolve还是reject当中参数的值
      • 说简单点就是返回的不是Promise,那么就是resolve,会去执行resolve的回调,如果返回的是一个Promise,那么就是根据这个Promise成功或者失败的结果去执行对应的回调

    Promise的链式调用

    • 看上方的Promise.then,原理就是Promise.thenPromise.catch会返回一个Promise

    Promise.then有没有办法返回传递给下一个值到下面?

    • 有,自己返回一个Promise来代替默认的返回就可以

    • 示例1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    var a = new Promise((resolve, reject) => {
    setTimeout(() => {
    resolve("我是数据");
    }, 1000);
    });
    a.then((res) => {
    console.log("第一个then拿到的-" + res); //输出 第一个then拿到的-我是数据
    //传递给下方
    res += "-第一个then已读";
    return Promise.resolve(res);
    })
    .then((res) => {
    console.log("第二个then拿到的-" + res); //输出 第二个then拿到的-我是数据-第一个then已读
    })
    .catch((reason) => {
    console.log("捕捉到错误", reason);
    });

    示例1-输出结果

    • 示例2
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var a = new Promise((resolve, reject) => {
    setTimeout(() => {
    resolve("我是数据")
    }, 1000);
    });
    a
    .then(res=>{
    console.log("拿到的数据结果-"+res);//输出 拿到的数据结果-我是数据
    return Promise.reject("出错啦")
    })
    .catch(reason=>{
    console.log("捕捉到错误",reason);// 输出 捕捉到错误 出错啦
    })

    示例2-输出结果

    var和let的区别

    • var是函数作用域,let是块级作用域(也就是一个个花括号)

    • 在函数中声明了var,整个函数内都是有效的,比如说在for循环内定义的一个var变量,实际上其在for循环以外也是可以访问的。

    • 而let由于是块作用域,所以如果在块作用域内定义的变量,比如说在for循环内,在其外面是不可被访问的,所以for循环推荐用let。

    • let不能在定义之前访问该变量,但是var可以(这个是let的暂时性死锁)

    • let不能被重新定义,但是var是可以的

      1
      2
      3
      4
      5
      let a = 10;
      let a = 100;//报错

      var a = 10;
      var a = 1000;//不报错,正确

    说说css动画

    • animation@keyframs的结合使用
    • 过渡效果transition
    • 转变,转换效果transform,比如旋转(rotate),平移(translate)

    说说computed和watch

    computed

    • computed为一个计算属性,是通过data当中的属性值计算出来的一个新值,并且只有在依赖的属性值发生变化的时候才会重新计算并且默认第一次加载的时候就会调用一次computed
    • computed是同步的,不可以异步
    • 支持缓存,相依赖的数据发生改变才会重新计算

    watch

    • watch是监听一个值的变化,然后执行相应的回调函数,并且默认第一次加载是不做监听的,如果需要第一次加载做监听,就需要设置immediate属性为true
    • watch可以是异步的,也可以是同步的,
    • watch不支持缓存

    BFC

    BFC是什么

    • BFC(Block Formatting Context)是块级格式上下文的缩写
      • BFC是指浏览器中创建了一个独立的渲染区域,并且拥有一套渲染规则,他决定了其子元素如何定位,以及与其他元素的相互关系和作用
      • 具有 BFC 特性的元素可以看作是隔离了的独立容器,容器里面的元素不会在布局上影响到外面的元素,并且 BFC 具有普通容器所没有的一些特性。通俗一点来讲,可以把 BFC 理解为一个封闭的大箱子,箱子内部的元素无论如何翻江倒海,都不会影响到外部
      • BFC,就是一个与世隔绝的独立区域,不会互相影响
    • BFC用处/应用场景
      • 清除子元素浮动带来的高度塌陷
      • 清除盒子垂直方外边距合并的问题( 外边距的塌陷问题 ( 垂直塌陷 ) )
        • 盒子垂直方向的距离由margin决定。属于同一个BFC的两个相邻盒子垂直方向的margin会发生重叠。
        • 或者是设置内部盒子的margin影响到了外部盒子

    如何开启BFC

    • 根元素(自动开启)
    • 设置元素float不为none(比如设置float:left; float:right等) @webDoc-float
    • 设置元素overflow属性不为(visible)(比如设置overflow:hidden)
    • 设置元素displayflex,inline-block,table-cell,table-caption
    • 其他开启方法@webDoc-BFC

    BFC用处/应用举例

    • BFC特征
      • BFC是页面上的一个独立容器,容器里面的子元素不会影响外面的元素
      • BFC内部的块级盒会在垂直方向上一个接一个排列(这个我不懂,后面用到再说~)
      • 同一BFC下的相邻块级元素可能发生外边距折叠,创建新的BFC可以避免外边距折叠
      • 每个元素的外边距的左边与包含块边框盒的左边相接触(从右向左的格式的话,则相反),即使存在浮动
      • 浮动盒的区域不会和BFC重叠(也就是BFC之间不会重叠)
      • 计算BFC的高度时,浮动元素也会参与计算

    计算BFC的高度时,浮动元素也会参与计算

    • 一句话,高度塌陷造成的父元素高度问题
    1
    2
    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
    35
    36
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8" />
    <style type="text/css">
    .parent {
    width: 300px;
    border: 1px solid black;
    overflow: hidden;
    }
    .child {
    width: 100px;
    height: 100px;
    border: 1px solid red;
    float: left;
    }
    </style>
    </head>
    <body>
    <div class="parent">
    parent
    <div class="child">child</div>
    <div class="child">child</div>
    </div>
    <script>
    const parentHeight = document.querySelector('.parent').clientHeight;
    //父元素没有开启包含块的情况下输出21px;(可能不同浏览器有误差)
    //因为内部元素浮动了造成父元素高度塌陷了
    console.log(parentHeight);

    //父元素开启包含块的情况下输出102px(可能不同浏览器有误差)
    console.log(parentHeight);
    </script>
    </body>
    </html>

    浮动盒的区域不会和BFC重叠(也就是BFC之间不会重叠)

    • 如图,左边div浮动,右边没有,可以看到,右边div和左边div产生了重叠,我们可能不想要这种效果,只需要设置右边开启BFC即可(左边浮动已经开启了BFC);

    • 代码(解决重叠问题的代码)
    1
    2
    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
    <head>
    <style>
    * {
    margin: 0;
    padding: 0;
    }
    .left {
    float: left;
    height: 200px;
    margin-right: 10px;
    background-color: red;
    }
    .right {
    /* 解决重叠关键代码,开启BFC */
    overflow: hidden;
    height: 300px;
    background-color: green;
    }
    </style>
    </head>
    <body>
    <div>
    <div class="left">浮动元素,无固定宽度\</div>
    <div class="right">自适应</div>
    </div>
    </body>
    </html>

    清除子元素浮动带来的高度塌陷

    • 例子略,这个应该都知道,就不举例子了

    内部盒子margin影响到了外部盒子

    • 只需要设置外层父亲(红色区域)的开启BFC即可
      • 因为父元素开启BFC,那么不管内部元素怎么设置,都影响不到外部元素

    清除盒子垂直方向外边距合并

    • 问题的本质就是兄弟元素之间的相邻外边距会去最大值而不是取和

      • 也就是说只需要通过某元素,某文字,某边框进行隔开也可以处理这种情况
      • 更多解决方法请看这篇博客@地址
    • 最典型的就是二个div,一个设置margin-bottom:100px,一个设置margin-top:100px,可是发现两者之间的垂直距离只有100px,而不是100+100 = 200px

    • @在线演示-BFC-清除盒子垂直方向上外边距合并

    • 如下图所示

    示例代码

    1
    2
    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
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
    *{
    margin: 0;
    padding: 0;
    }
    .brother1{
    width: 200px;
    height: 200px;
    background-color: red;
    margin-bottom: 100px;
    }
    .brother2{
    width: 200px;
    height: 200px;
    background-color: green;
    margin-top: 100px;
    }
    </style>
    </head>
    <body>
    <div class="brother1"></div>
    <div class="brother2"></div>
    </body>
    </html>
    • 解决办法二个元素都设置为一个内部的元素,并将外部包裹的着开启BFC

    示例代码

    1
    2
    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
    35
    36
    37
    38
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
    *{
    margin: 0;
    padding: 0;
    }
    .brother1{
    width: 200px;
    height: 200px;
    background-color: red;
    margin-bottom: 100px;
    }
    .brother2{
    width: 200px;
    height: 200px;
    background-color: green;
    margin-top: 100px;
    }
    .bfc{
    overflow: hidden;
    }
    </style>
    </head>
    <body>
    <div class="bfc">
    <div class="brother1"></div>
    </div>
    <div class="bfc">
    <div class="brother2"></div>
    </div>
    </body>
    </html>

    参考文章

    https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flow_Layout/Intro_to_formatting_contexts

    https://www.itcast.cn/news/20201016/16152387135.shtml

    https://blog.csdn.net/qq_43205326/article/details/109789742

    为什么要对axios进行二次封装?

    • 可以在ajax请求发送前和数据到达的时候做一些处理,比如发送前可以使用nprogress进行一个假加载进度条
    • 我们可以使用axios里面的请求拦截器对发送前的请求做处理,也有响应拦截器对传递回来的数据做处理
    • 也可以通过二次封装来设置默认发送的前缀,这样子就不用每一次发送ajax都添加域名了,也方便后期修改

    koa模块的使用

    • Koa相比于express模块,koa更加方便和轻便的,有点像Python和c++他们之间的区别吧~

    koa的一个完整示例

    router/index.js文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    const Router = require("koa-router");
    const bodyParser = require('koa-bodyparser');

    //1.生成路由实例
    const router = new Router();
    router.use(bodyParser()); //使用中间件(这样子就可以解析post参数)

    router.get('/package/:aid/:cid',(ctx)=>{
    //获取动态路由的传值
    console.log(ctx.request.body);
    ctx.body="随便返回东东";
    })

    module.exports = router;

    server.js文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    const Koa = require("koa");


    //1.生成应用实例
    const app = new Koa();
    const router = require("./router/index.js");


    //2.使用路由中间件
    //为什么还要通过routes方法呢?
    //我猜是因为上一步的router.get 或者router.post
    //都是将这些放在了一个数组当中,然后我们暴露的只是暴露一个对象
    //要取出这些全部的路由信息,就需要通过router.routes()方法
    app.use(router.routes());


    //最后监听端口
    app.listen("3001",(error)=>{
    if(error){
    console.log(error);
    return;
    }
    console.log("服务器启动成功");
    console.log("服务器地址:xxxxxx:3001");
    });

    get的参数获取

    koa获取query参数

    • 通过ctx.query,会返回一个对象,包含着query参数,没有query参数的时候就会返回一个空对象
    • 如下图,访问http://localhost:3001/test?name=李白后输出ctx.query
    1
    2
    3
    4
    5
    6
    7
    8
    9
    const Router = require("koa-router");
    const router = new Router();

    router.get("/test",(ctx)=>{
    console.log(ctx.query); //输出{name:'李白'}
    ctx.body = "随便返回东东";
    })

    module.exports = router;

    koa取params参数

    • 获取前和vue一样,需要在匹配路径的时候去占位!
    • 如下代码,浏览器输入网址http://localhost:3001/package/123/456后输出下面,即可获取params参数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    const Router = require("koa-router");
    const router = new Router();

    router.get("/package/:aid/:cid",(ctx)=>{
    console.log(ctx.params); //{ aid: '123', cid: '456' }
    ctx.body = "随便返回东东";
    })

    module.exports = router;

    post参数的获取

    • 和express,也需要一个中间件,koa获取post参数中间件为koa-bodyparser
    • 以下示例均访问网址http://localhost:3001/package/123/456并携带一个post参数

    访问网址参数

    • 没有安装中间件
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    const Router = require("koa-router");
    const router = new Router();

    router.get('/package/:aid/:cid',(ctx)=>{
    //获取post参数
    console.log(ctx.request.body);//没有安装中间件,输出undefined
    ctx.body="随便返回东东";
    })

    module.exports = router;
    • 安装中间件

      1
      npm install koa-bodyparser --save
    • 示例:安装中间件后

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const Router = require("koa-router");
    const router = new Router();
    const bodyParser = require('koa-bodyparser');//引入
    router.use(bodyParser());//使用中间件
    router.get('/package/:aid/:cid',(ctx)=>{
    //获取post参数
    console.log(ctx.request.body);//安装中间件,输出{ age: '1000' }
    ctx.body="随便返回东东";
    })

    module.exports = router;
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/e031a88b.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    avatar
    梦洁
    小小的字,有大大的梦想~分享我的前端学习过程,经历,错误,和一些其他折腾过程
    关注下我(* ̄▽ ̄*)
    公告
    不断更新中,有问题请留言回复(会通过邮箱提醒~)
    目录
    1. 1. vue数据响应式原理
    2. 2. 内联样式和其他优先级计算
      1. 2.1. 样式类别
      2. 2.2. 优先级
    3. 3. 数组的遍历
      1. 3.1. forEach
      2. 3.2. for循环
      3. 3.3. map
      4. 3.4. filter
      5. 3.5. for…of
      6. 3.6. for…in
    4. 4. vue的生命周期
    5. 5. v-mode的实现原理和在自定义组件中怎么实现
      1. 5.1. v-model当中在自定义组件要怎么实现呢?
    6. 6. 路由导航守卫
    7. 7. 伪类和伪元素
    8. 8. 用promise写个ajax(仿照axios)
    9. 9. css画一个梯形和三角形
    10. 10. http和https响应的状态码有哪些
    11. 11. cookie localStorage sessionStorage区别
    12. 12. TCP三次握手,四次挥手
      1. 12.1. TCP三次握手
      2. 12.2. 四次挥手
    13. 13. for … in 和for … of
    14. 14. nginx正向代理,反向代理
    15. 15. css九宫格
    16. 16. 微任务和宏任务
    17. 17. computed和methods区别
    18. 18. 闭包和原型和原型链
      1. 18.1. 闭包
      2. 18.2. 原型和原型链
        1. 18.2.1. 原型
        2. 18.2.2. 原型链
          1. 18.2.2.1. 证明在原型链上寻找
    19. 19. 说一说new会发生什么?
    20. 20. 项目中的难点
    21. 21. 数组的去重
    22. 22. ES6+的常见语法
      1. 22.1. let
      2. 22.2. 解构赋值
      3. 22.3. 箭头函数
      4. 22.4. Promise
        1. 22.4.1. promise常用的方法
      5. 22.5. class类
      6. 22.6. 可选链操作符
      7. 22.7. ES6模块化
      8. 22.8. async await
        1. 22.8.1. async的返回值
        2. 22.8.2. await
          1. 22.8.2.1. await遇上成功的promise(也就是resolve了)
          2. 22.8.2.2. await遇上失败的promise(也就是reject了)
            1. 22.8.2.2.1. 总结: await遇上失败的promise(也就是reject了)
      9. 22.9. 模板字符串
    23. 23. 什么是执行上下文
      1. 23.1. 执行上下文
        1. 23.1.1. 分类
        2. 23.1.2. 注意
      2. 23.2. 1.先全局执行上下文
        1. 23.2.1. 第一步:创建全局执行上下文,压入栈
          1. 23.2.1.1. 1.收集变量,形成变量对象
          2. 23.2.1.2. 2.确定this的指向
          3. 23.2.1.3. 3.确定作用域链
        2. 23.2.2. 第二步:执行全局上下文 比如说赋值
      3. 23.3. 2.函数执行上下文
        1. 23.3.1. 第一步:创建函数执行上下文
          1. 23.3.1.1. 1.收集变量,形成变量对象,2.确定this的指向 3.确定作用域链
        2. 23.3.2. 第二步:执行函数上下文
    24. 24. 数据绑定原理
      1. 24.1. 需要解决的2个问题
      2. 24.2. 1.发布者observer
      3. 24.3. 1.5中间还需要一个dep(订阅器),去通知订阅者
      4. 24.4. 2.订阅者(watcher)
    25. 25. webpack
      1. 25.1. 说说看什么是webpack
      2. 25.2. 什么是loader,什么又是plugin
      3. 25.3. 有哪些常见的Loader,他们都是解决什么问题的
    26. 26. 解释下引用数据类型的赋值和基本数据类型的赋值
    27. 27. nodejs的引入/暴露和es6的引入/暴露-简记
      1. 27.1. es6
        1. 27.1.1. 引入
        2. 27.1.2. 暴露
      2. 27.2. nodejs
        1. 27.2.1. 引入
        2. 27.2.2. 暴露
    28. 28. 说说三级联动
    29. 29. 网页的seo优化
    30. 30. 图片的alt和title
    31. 31. 更改this的执行的方法
      1. 31.1. call方法
        1. 31.1.1. 语法
        2. 31.1.2. 参数
      2. 31.2. apply方法
        1. 31.2.1. 语法
        2. 31.2.2. 参数说明
      3. 31.3. bind方法
        1. 31.3.1. 语法
        2. 31.3.2. 参数
    32. 32. 布局 - 圣杯布局
      1. 32.0.1. 示例1 - float布局
      2. 32.0.2. 示例2-display布局
  • 33. 每一个js都需要导入Vue,并且多次去使用Vue.use(),会造成重复吗?
  • 34. Vue.use都做了那些事情
  • 35. 说说vue3和vue2区别
  • 36. 下面代码输出怎么输出2,怎么输出1
  • 37. 写出如下代码要求
  • 38. 要如何实现如下图布局
  • 39. 说说自定义组件怎么实现v-model(双向绑定)
  • 40. Promise.all当失败的时候会返回失败的结果,有没有办法都返回成功的,不会失败
    1. 40.1. 情况说明
    2. 40.2. 方法1: reject改为resolve
    3. 40.3. 方法2——-别人的
  • 41. Promise.then
  • 42. Promise的链式调用
  • 43. Promise.then有没有办法返回传递给下一个值到下面?
  • 44. var和let的区别
  • 45. 说说css动画
  • 46. 说说computed和watch
    1. 46.1. computed
    2. 46.2. watch
  • 47. BFC
    1. 47.1. BFC是什么
    2. 47.2. 如何开启BFC
    3. 47.3. BFC用处/应用举例
      1. 47.3.1. 计算BFC的高度时,浮动元素也会参与计算
      2. 47.3.2. 浮动盒的区域不会和BFC重叠(也就是BFC之间不会重叠)
      3. 47.3.3. 清除子元素浮动带来的高度塌陷
      4. 47.3.4. 内部盒子margin影响到了外部盒子
      5. 47.3.5. 清除盒子垂直方向外边距合并
  • 48. 为什么要对axios进行二次封装?
  • 49. koa模块的使用
    1. 49.1. koa的一个完整示例
    2. 49.2. get的参数获取
      1. 49.2.1. koa获取query参数
      2. 49.2.2. koa取params参数
    3. 49.3. post参数的获取
  • 最新文章
    \ No newline at end of file diff --git a/e03e7bbd.html b/e03e7bbd.html new file mode 100644 index 000000000..beb9b7d80 --- /dev/null +++ b/e03e7bbd.html @@ -0,0 +1 @@ +vue简单源码手写,实现基本的模板解析,v-text,v-html,v-onclick,@click基本语法指令 | 梦洁小站-属于你我的小天地

    vue简单源码手写,实现基本的模板解析,v-text,v-html,v-onclick,@click基本语法指令

    前言

    具体可以看bilibili的视频https://www.bilibili.com/video/BV15D4y1o73Z

    源码可以自己下载,和视频基本一样

    下载地址:https://github.com/superBiuBiuMan/vue-write-self

    笔记

    第五集的时候为什么设置Dep.target = this后又置为空

    1
    2
    3
    4
    5
    6
    7
    获取旧值表达式为以下
    getOldValue(){
    Dep.target = this;
    const oldvalue = compileUtil.getValue(this.expr,this.vm.$data);
    Dep.target = null;
    return oldValue;
    }
    • 一开始我看到这个,很奇怪,觉得这样子会可以让Observer成功添加到watcher吗?
      • 是的,的确可以,在Observer当中,我们通过defineProperty为data当中的每一个属性添加了set和get方法,也就是说
        我们每次读取值也会触发set,设置值则会触发get,所以当我们初始化页面编译模板的时候,就会触发set,从而向对应的dep添加对应的watcher
    • 所以这就解释了调用compileUtil.getValue()之后就会添加watcher的问题
    • 那么为什么需要设置Dep.target = null取消呢?
      • 在我们触发更新的时候,通过set设置了新值,后面肯定需要显示在页面,所以我们肯定需要调用get方法,此时如果我们不设置Dep.target = null,那么就会导致对应的watcher被重复添加了

    setValue为对象的时候会报错的解决

    • 因为如果是person.name的时候,我们必须要返回上一次获取的值作为vm.$data,才可以循环获取到最终的name值,老师写的vm.$data不会返回上一次获取到的值,所以就会导致报错
    • 说通俗点就是老师写的那个方法只适合一层对象,不适合多层对象,想要适合多层对象,就需要循环获取值
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    /*设置表达式的值*/
    setVal(expr,vm,inputValue){
    //当设置的值为person.fav这种多层对象的时候,会报错
    //expr.split(".").reduce((preData,currentValue)=>{
    // console.log(preData)
    // preData[currentValue] = inputValue;
    //},vm.$data)
    //所以下面是修复
    expr.split(".").reduce((preData,currentValueKey)=>{
    if(Object.prototype.toString.call(preData[currentValueKey]) === '[object Object]'){
    return preData[currentValueKey];
    }else{
    preData[currentValueKey] = inputValue;
    }
    },vm.$data)
    },

    总结

    • vue通过数据劫持和发布者订阅者模式,通过Object.defineProperty()当中的setter和getter来劫持数据变动,从而达到在数据变动的时候通知相应的dep去执行对应的watcher

    • 我们在创建vue则会执行模板的解析,也就是compile,在解析编译时候,为会为每一个使用到的字段创建相应的watcher,并添加到对应的Dep当中,在我们通过Observer劫持监听所有属性的时候,是通过Object.defineProperty的setter和getter来实现的,当我们更新数据的时候,会通知dep去执行里面的watcher

    数据绑定原理图-老师

    数据绑定原理图-自己

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/e03e7bbd.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/e08c9b47.html b/e08c9b47.html new file mode 100644 index 000000000..33ac08372 --- /dev/null +++ b/e08c9b47.html @@ -0,0 +1 @@ +vue3全局事件总线-mitt的使用(和vue2的全局总线不同) | 梦洁小站-属于你我的小天地

    vue3全局事件总线-mitt的使用(和vue2的全局总线不同)

    前置

    • Vue3移除了$on $off等自带的自定义事件相关方法,因此在vue3中他推荐我们下载mitt库来使用事件总线传递数据

    安装

    1
    npm install mitt

    使用mitt充当总线

    • 需要注意的是,每调用一个mitt(),都是一个总线,所以这里为什么要封装为一个工具库,就是只使用一个总线
    • 下列代码的效果为单击一个组件,传递给另外一个组件值
    • 当然,这里和vue2之前也是一样的,也是只能传递一个参数,需要传递多个参数的时候封装为一个对象,然后接受的时候结构就可以~

    1.定义一个工具库为bus.js

    1
    2
    3
    4
    5
    6
    7
    8
    9
    import mitt from "mitt";

    // export default mitt();

    //也可以写完整嗲

    const emitter = mitt();//每调用一个mitt,都是一个总线,所以这里为什么要封装为一个工具库,就是只使用一个总线

    export default emitter;

    2.组件中使用

    one.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <template>
    <p>从Two接收到的数据[{{receiveData}}]</p>
    </template>

    <script>
    import { ref } from 'vue';
    // 引入总线
    import emitter from "@/utils/bus";
    export default {
    name: 'One',
    setup(){
    const receiveData = ref([]);
    // 绑定总线(添加订阅)
    emitter.on('giveOne',(value)=>{
    // 回调函数
    receiveData.value = value;
    })
    return {
    receiveData,
    }
    }
    }
    </script>

    two.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <template>
    <button @click="giveValue">传递给One值</button>
    </template>

    <script>
    import emitter from '@/utils/bus';
    export default {
    name: 'Two',
    setup(){
    function giveValue(){
    // 调用总线,发布订阅
    emitter.emit('giveOne',"我是从Two传递过来的值");
    };
    return {
    giveValue,
    }
    }
    }
    </script>

    app.vue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <template>
    <One></One>
    <Two></Two>
    </template>

    <script>
    import One from "@/components/One"
    import Two from "@/components/Two"
    export default {
    name: "App",
    components: {
    One,
    Two,
    },
    };
    </script>

    <style>

    演示效果

    • 代码的效果为单击一个组件,传递给另外一个组件值

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/e08c9b47.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/e0e17efd.html b/e0e17efd.html new file mode 100644 index 000000000..7971e56dd --- /dev/null +++ b/e0e17efd.html @@ -0,0 +1 @@ +vue-admin管理模板npm安装依赖后npm run de提示依赖core-js,@babel等报错的解决办法 | 梦洁小站-属于你我的小天地

    vue-admin管理模板npm安装依赖后npm run de提示依赖core-js,@babel等报错的解决办法

    安装别人做好的后台管理项目,npm run dev后报错

    • 都是这种core-js/modules/es.array.concat.js什么的错误

    报错项

    解决

    1. 找到项目下的babel.config.js 原来的presets改为下面

      1
      presets: [ [ "@vue/app", { useBuiltIns: "entry" } ] ],
    2. 改好之后

    原来的presets改为下面

    1. 重新运行npm run dev 成功!

      成功

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/e0e17efd.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/e39b897f.html b/e39b897f.html new file mode 100644 index 000000000..491f1895e --- /dev/null +++ b/e39b897f.html @@ -0,0 +1 @@ +uni-app知识点和项目上遇到的问题和解决办法的记录 | 梦洁小站-属于你我的小天地

    uni-app知识点和项目上遇到的问题和解决办法的记录

    uni-app既支持vue的生命周期,也支持微信小程序的生命周期

    • 也就是我们可以这样子写
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <script>
    export default {
    onLoad:function(){
    //...
    },
    onShow: function() {
    //...
    },
    onHide: function() {
    //...
    },
    mounted(){
    //...
    },
    }
    </script>
    • 所以我们在uni-app路由跳转的时候,除了使用vue-router去跳转和接收参数,我们还可以使用小程序自带的路由跳转和接收参数,比如说wx.navigateTo去进行跳转并在onLoad去接收传递过来的参数(比如query参数)
    • 比如说单击商品图片跳转到商品详情,就可以这样子做,示例如下
    1
    2
    3
    4
    5
    6
    7
    8
    methods:{
    //跳转到商品详情页
    checkDetail(shopItem){
    wx.navigateTo({
    url:"/pages/detail/detail?shopitem="+JSON.stringify(shopItem),
    })
    },
    },

    那么在商品详情组件就可以这样子接收,通过生命周期onLoad

    1
    2
    3
    4
    5
    6
    7
    8
    onLoad(options){
    //接收传递过来的参数
    let shopItem = options.shopitem;
    //参数存在才解析
    if(shopItem){
    this.shopDetail = JSON.parse(shopItem);
    }
    },

    uni-app全局对象既可以是wx也可以是uni,并且rpx和upx是一样的效果

    • 看个人习惯吧,其实个人从心理上认为全局对象uniwx兼容性会更好一点在编译的时候

    小程序是可以设置placeholder的样式的,但是h5却不可以

    • @官方API-input

    • 主要是通过以下二个属性设置的

      • placeholder-class:指定 placeholder 的样式类,注意页面或组件的style中写了scoped时,需要在类名前写/deep/
        • 字节跳动小程序、飞书小程序、快手小程序不支持
      • placeholder-style:指定 placeholder 的样式
    • 示例

    1
    <input class="search-input" placeholder="搜索内容在这里" placeholder-class="search-placeholder">
    • 设置的样式
      • 注意层级关系
    1
    2
    3
    4
    5
    6
    7
    8
    9
    /* 多级别下与input是同级的 */
    .search-input .search-placeholder{
    background-color: red;
    }
    /* 或者直接不通过层级 */
    .search-placeholder{
    font-size: 25rpx;
    color: red;
    }

    uni-app是有默认样式的,这需要特别注意

    • 比如我们写了一个标签<button></button>
    • 我们打开小程序调试查看,可以看到设置了这么多默认样式,所以我们在设置自己的样式的时候,需要注意默认样式对我们的影响
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    button {
    -webkit-tap-highlight-color: transparent;
    background-color: #f8f8f8;
    border-radius: 5px;
    box-sizing: border-box;
    color: #000;
    cursor: pointer;
    display: block;
    font-size: 18px;
    line-height: 2.55555556;
    margin-left: auto;
    margin-right: auto;
    overflow: hidden;
    padding-left: 14px;
    padding-right: 14px;
    position: relative;
    text-align: center;
    text-decoration: none;
    }

    uni-app引入公共样式(css)和小程序引入公共样式(css)

    • uni-app是在App.vue当中去引入
      • 注意,这里需要引入的格式是
      • @import url("地址")
    • 示例代码如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    //App.vue
    <script>
    export default {
    onLaunch: function() {
    console.log('App Launch')
    },
    onShow: function() {
    console.log('App Show')
    },
    onHide: function() {
    console.log('App Hide')
    }
    }
    </script>

    <style lang="stylus">
    /*每个页面公共css */
    /* 引入iconfont字体图标*/
    @import url("static/iconfont/iconfont.styl");
    page
    width 100%
    height 100%
    </style>

    • 微信小程序引入公共样式
      • 注意,这里引入是
      • @import "地址"
    • 示例代码如下引入公共样式在微信小程序当中
    1
    @import './common/main.wxss';

    假如在uni-app开启display后元素没有在一行排列

    • 可以试试看设置下面这二个样式
    • white-space:nowrap:设置元素不换行
    • display:inline-block:设置元素和行内块元素(目的是让元素不单独占据一行)

    nodejs模块的引入是怎么引入的,和es6又有什么区别

    • 这里只记录了下简单的,新遇到的问题,具体想看es6的暴露和引入的具体的,可以看我之前写的文章,@地址

    es6

    引入
    • es6的引入需要在模块的最前面,不可以出现如下代码(使用静态的import会出现的问题)
    1
    2
    3
    4
    5
    import a from "./a.js"

    function getNumber(){
    import b from "./b.js";
    }

    否者出现就会报错,提示导入声明只能在模块的顶层使用

    • 那是因为你使用了静态的import,标准用法的 import 导入的模块是静态的,会使所有被导入的模块,在加载时就被编译(无法做到按需编译,降低首页加载速度)。有些场景中,你可能希望根据条件导入模块或者按需导入模块,这时你可以使用动态导入代替静态导入
      • 在vue路由的时候,经常使用这种动态的导入方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    import('/modules/my-module.js')
    .then((module) => {
    // Do something with the module.
    });
    //这种使用方式也支持 await 关键字。
    async function getNumber(){
    const b = await import("./b.js");
    console.log(b);
    }
    getNumber();
    暴露
    • es6的暴露出去的都是对象
      • 在引入的时候除了默认暴露,其他的基本上都需要解构赋值才可以

    nodejs

    引入
    • nodejs是commojs的规范,他可以在使用到的时候再引入
    • 比如下面有一个需求,就是是当用户请求某一个路由的时候,我才去读取数据并返回给用户,这个时候就可以体现出nodejs引入的好处了,是代码执行到这一段的时候才去引入
    1
    2
    3
    4
    5
    6
    // 分类页
    router.get("/getCategoryData",(ctx)=>{
    //读取分类页数据,代码执行到这一行才开始引入
    const categoryData = require("../datas/categoryDatas.json");
    ctx.body = categoryData
    })
    暴露
    • node提供了二种暴露方式

      • module.exports
      • exports
    • 不想看下面的可以记住这句话就可以,不要使用exports去暴露,使用module.exports去暴露

    • exportsmodule.exports是一个引用

      • 当通过exports去改变内存块里内容时,module.exports的值也会改变
      • 当通过module.exports去改变内存块里内容时,exports的值也会改变
      • 当module.exports所指向的地址被改变的时候,exports不会被改变
      • 当exports所指向的地址被改变的时候,module.exports不会被改变
    • node的暴露可以简单理解为暴露什么,引入的就是什么

    暴露引入如下-暴露什么,引入的就是什么

    nodejs的fs.readFileSync

    • 在没有指明编码方式的情况下,默认是Buffer

    • 官方解释

    1
    If the encoding option is specified then this function returns a string. Otherwise it returns a buffer.
    • 示例代码,未指明的时候为buffer流,指明了则为字符串
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    const fs = require("fs");
    const path = require("path");
    // 指明数据类型
    var result = fs.readFileSync(path.resolve(__dirname,"data/test.json"),"utf-8");
    console.log(result);
    console.log(typeof result); //类型为字符串

    //未指明数据类型
    var result = fs.readFileSync(path.resolve(__dirname,"data/test.json"));
    console.log(result);
    console.log(typeof result);

    设置文本超出二行隐藏

    1
    2
    3
    4
    5
    6
    7
    8
    9
    .show {
    width: 200px;
    text-overflow: ellipsis;
    overflow: hidden;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    /* 设置超出几行省略 */
    -webkit-line-clamp: 2;
    }

    关于flex布局justify-content:space-around最后一个不对齐的解决方法和为什么这样子解决是讨论

    • 其实解决也很简单,在最外层添加一个伪类即可

      html结构

      1
      2
      3
      4
      5
      6
      7
      8
      9
        <!-- 外层 -->
      <div class="root">

      <!-- 遍历数据的 -->
      <div v-for="(shopItem, index) in shopList" :key="index">
      <img style="width: 200px;" :src="shopItem.url" alt="" />
      </div>

      </div>

      css解决办法,在root后面添加一个伪类即可解决

      1
      2
      3
      4
      5
      6
      .root::after{
      content: "";
      width: 200px;
      /* 不可以提供高度 */
      /* height: 200px; */
      }

    那么为什么设置::after伪类就可以解决呢justify-content所带来的布局问题呢?

    • 当那个循环列表是奇数的时候会出现那个填充框框对吧?

    奇数

    • 但是如果是偶数会不会出现这个伪类::after的填充框呢,我们再添加一个项目框框
      • 可以看到,并没有出现这个红色框框(不是我截图没有截到,是真的没有出现红色框框)

    偶数

    • 那么为什么呢?就好像知道这个浏览器知道我们需要这个填充内容出现一样
      • 在之前我猜测可能是盒子模型计算出来了伪类::after的宽度,但是我记得盒子模型计算的是针对于块级元素的
      • 后面查看资料说

    CSS伪元素::after用来创建一个伪元素,作为已选中元素的最后一个子元素。通常会配合content属性来为该元素添加装饰内容。这个虚拟元素默认是行内元素。

    • 那么肯定不是盒子模型计算的问题了,那么是为什么呢?(下面推断个人想法,可能有误)
      • 后面想了想出现这种情况是什么
        • 1.最后一行没有被填充,有空隙的时候,就会出现伪类的填充
        • 2.当容器最后没有留空的时候,就不会出现伪类的填充
    • 后面查看弹性盒属性,发现了flex-basis

    CSS 属性 flex-basis 指定了 flex 元素在主轴方向上的初始大小。如果不使用 box-sizing 改变盒模型的话,那么这个属性就决定了 flex 元素的内容盒(content-box)的尺寸。

    flex-basis 默认值为auto

    • 于是我设置伪类::afterflex-basis:0,看看会发生什么,
    1
    2
    3
    4
    5
    6
    7
    8
    .root::after{
    content: "";
    width: 200px;
    /* 不可以提供高度 */
    /* height: 200px; */
    background: red;
    flex-basis:0;
    }
    • 可以看到,伪类::after高度都没有,不像前面的一行没有满的情况有填充出现

    flex-basis:0为伪类::after设置

    • 我们在将他设置为flex-basis:auto看看会发生什么

    flex-basis:auto

    • 当然,由于我们没有设置基础的宽度,所以设置flex-basis:content也是一样的效果

    flex-basis:content

    • 甚至设置height:auto是也一样的效果

    flex-basis官方解释—由于最初规范中没有包括这个值,在一些早期的浏览器实现的 flex 布局中,content 值无效,可以利用设置 (widthheight) 为 auto 达到同样的效果。

    总结

    • 浏览器知道我们需要这个填充内容出现是因为flex-basis默认值为auto,由浏览器为元素计算并选择一个高度或者宽度,当然,假如事先设置了宽度和高度,那么flex-basis:auto是没有用的(这里是没有设置height,所以只讨论height的情况)
    • 所以要多了解了解flex布局呀~~~

    axios在某些小程序上用不了,可以用flyio模块

    为什么使用jwt加密还可以解密出来

    • 还可以使用https://jwt.io/解密出来
    • 因为jwt不在于加密数据,而在于数据认证
    • jsonwebtoken⽬的不在加密保护数据,⽽是为了认证来源,认证来源,认证来源。JWT不保证数据不泄
      露,因为JWT的设计⽬的就不是数据加密和保护。

    v-if可以使用template进行包裹,外面套一层,这样子渲染就不会多出一层div了

    图片的alt和title

    • alt
      • 网络错误、内容被屏蔽或链接过期时,会显示alt 属性中的文本
    • title
      • 鼠标悬停时候的提示信息
      • title 属性不是 alt 属性可接受的替代品。并且,避免将 alt 属性的值直接复制到同一幅图片的title 属性上。这样可能会让一些屏幕阅读器把同一段描述读两遍,造成一定程度上的困扰。

    更改this的执行的方法

    假设有一个函数fn需要改变this执行,要怎么做

    • call,apply,bind方法作用
      • 让任意的函数,成为对象的方法,从而改变this的指向

    call方法

    • @mdnwebDoc - call

    • fn.call(this的指向,参数1,参数2,参数3,….)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      function Product(name, price) {
      this.name = name;
      this.price = price;
      }

      function Food(name, price) {
      Product.call(this, name, price);
      this.category = 'food';
      }

      //等同于,至于为什么等于
      //这就要涉及到创建一个构造函数的时候究竟做了什么
      //创建构造函数其实就是在this(新实例化对象上)上添加赋值并返回这个this
      //所以你看构造函数都没有返回值,那是因为系统自动添加了返回值
      //自动添加了return this
      function Food(name, price) {
      this.name = name;
      this.price = price;
      this.category = 'food';
      }
      console.log(new Food('cheese', 5).name);
      // expected output: "cheese"

    • 备注:该方法的语法和作用与 apply() 方法类似,只有一个区别,就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组

    语法
    1
    function.call(thisArg, arg1, arg2, ...)

    参数

    thisArg

    • 可选的。在 function 函数运行时使用的 this 值。请注意,this可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 nullundefined 时会自动替换为指向全局对象,原始值会被包装。

    arg1, arg2, ...

    • 指定的参数列表

    apply方法

    语法
    1
    2
    apply(thisArg)
    apply(thisArg, argsArray)
    参数说明

    thisArg

    • func 函数运行时使用的 this 值。请注意,this 可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 nullundefined 时会自动替换为指向全局对象,原始值会被包装。

    argsArray

    • 一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 func 函数。如果该参数的值为 nullundefined,则表示不需要传入任何参数。从 ECMAScript 5 开始可以使用类数组对象。浏览器兼容性请参阅本文底部内容。

    bind方法

    • fn.bind(this的指向,[参数1,参数2,…])

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      const module = {
      x: 42,
      getX: function() {
      return this.x;
      }
      };

      const unboundGetX = module.getX;
      console.log(unboundGetX()); //里面的this指向window
      //window里面没有x变量,所以返回undefined

      //更改this的执行,返回一个函数,并赋值给boundGetX
      const boundGetX = unboundGetX.bind(module);
      //执行更改this后的函数
      console.log(boundGetX());//输出42
    • bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

    语法
    参数

    thisArg

    • 调用绑定函数时作为 this 参数传递给目标函数的值。 如果使用new运算符构造绑定函数,则忽略该值。当使用 bindsetTimeout 中创建一个函数(作为回调提供)时,作为 thisArg 传递的任何原始值都将转换为 object。如果 bind 函数的参数列表为空,或者thisArgnullundefined,执行作用域的 this 将被视为新函数的 thisArg

    arg1, arg2, ...

    • 当目标函数被调用时,被预置入绑定函数的参数列表中的参数。

    使用reduce却莫名其妙返回一个undefined或者NaN

    • @webdoc-reduce
    • 需要特别注意
      • 1.当没有传入初始值的时候,就以第一个item为初始值,也就是索引为0的item项为初始值
      • 2.每一次循环遍历必须要返回上一次的计算结果(也就是必须要return一个值,不return就会返回undefined从而出现NaN)并将这个返回结果作为下一次的初始化值

    代码示例-没有做到每次都返回值

    • 本来是想计算大于8的值的所有数的合,但是却返回了NaN
    1
    2
    3
    4
    5
    6
    7
    8
    9
    const array1 = [1, 10, 2, 3, 4, 5, 8, 10];
    const initialValue = 0;
    let result = array1.reduce((prev, item) => {
    if (item >= 8) {
    return (prev += item);
    }
    }, 0);
    //返回NaN
    console.log("相加结果", result);

    正确代码如下 - 做到了每一次都有返回值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    const array1 = [1, 10, 2, 3, 4, 5, 8, 10];
    const initialValue = 0;
    let result = array1.reduce((prev, item) => {
    if (item >= 8) {
    return (prev += item);
    } else {
    return prev;
    }
    }, 0);
    //输出28
    console.log("相加结果", result);

    //或者你可以写的更简单一点
    const array1 = [1, 10, 2, 3, 4, 5, 8, 10];
    const initialValue = 0;
    let result = array1.reduce((prev, item) => {
    return (prev += item >= 8 ? item : 0);
    }, 0);
    //输出28
    console.log("相加结果", result);

    除了mapState需要手动指明返回什么,其他的mapGetters,mapActions,mapMutations都可以直接写数组(但是开启命名空间后情况是否是这样子不知道)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    computed:{
    //映射vuex当中数据 - 开启了模块化
    ...mapState({
    indexData: state => state.home.indexData,
    }),
    //映射vuex当中数据 - 没有开启模块化
    ...mapState({
    indexData: state => state.indexData,
    })
    //也可以这样子 - 没有开启模块化
    //当映射的计算属性的名称与 state 的子节点名称相同时,我们也可以给 mapState 传一个字符串数组。
    //映射 this.count 为 store.state.count
    ...mapState(["count"])s
    },

    fly请求微信的code2Session对返回的结果取data项返回的是字符串,而不是对象

    • fly请求微信的code2Session对返回的结果取data项返回的是字符串,而不是对象,我们必须手动去转换下才可以转化为JSON对象,而axios的则不用
      1
      2
      3
      4
      5
      6
      7
      8
      let result = await fly.get("https://api.weixin.qq.com/sns/jscode2session", {
      appid,
      secret,
      js_code,
      grant_type: "authorization_code",
      });
      console.log(result.data);
      console.log("fly时候返回的结果",typeof result.data);

    • 注意的是,axios模块在小程序是用不了的,因为因为axios是基于window身上的XMLHttpRequest的,使用如果想在小程序中使用,可以试试看fly库

    当我们修改state当中新添加字段数据的时候,注意修改为响应式数据

    • 比如通过this.$set或者Vue.set

    es6简写需要注意

    • es6对象可以简写是key值和变量是一样的名字才可以简写
    1
    2
    3
    4
    5
    6
    7
    <script>
    let name = "李白"
    let obj = {
    name,
    }
    console.log(obj);//{name: "李白"}
    </script>
    • 这种情况就不能省略,因为后面是字符串

    node引入json,自动转换为原生的对象了

    • 如果是在CommonJS模块中加载json文件,只需通过require()函数直接加载即可,即能得到json对象

    其他一些小知识点

    • text-align:可以设置图片也可以设置文本,也就是设置内联样式的

    • 图片默认基线对其的,

    • 除了mapState需要手动指明返回什么,其他的mapGetters,mapActions,mapMutations都可以直接写数组(但是开启命名空间后情况是否是这样子不知道)

    • 伪元素是针对元素的一部分内容来设置的,比如说::after

    • 而伪类是针对整个元素来设置的,比如说:hover

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/e39b897f.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    avatar
    梦洁
    小小的字,有大大的梦想~分享我的前端学习过程,经历,错误,和一些其他折腾过程
    关注下我(* ̄▽ ̄*)
    公告
    不断更新中,有问题请留言回复(会通过邮箱提醒~)
    目录
    1. 1. uni-app既支持vue的生命周期,也支持微信小程序的生命周期
    2. 2. uni-app全局对象既可以是wx也可以是uni,并且rpx和upx是一样的效果
    3. 3. 小程序是可以设置placeholder的样式的,但是h5却不可以
    4. 4. uni-app是有默认样式的,这需要特别注意
    5. 5. uni-app引入公共样式(css)和小程序引入公共样式(css)
    6. 6. 假如在uni-app开启display后元素没有在一行排列
    7. 7. nodejs模块的引入是怎么引入的,和es6又有什么区别
      1. 7.1. es6
        1. 7.1.1. 引入
        2. 7.1.2. 暴露
      2. 7.2. nodejs
        1. 7.2.1. 引入
        2. 7.2.2. 暴露
    8. 8. nodejs的fs.readFileSync
    9. 9. 设置文本超出二行隐藏
    10. 10. 关于flex布局justify-content:space-around最后一个不对齐的解决方法和为什么这样子解决是讨论
      1. 10.1. 那么为什么设置::after伪类就可以解决呢justify-content所带来的布局问题呢?
      2. 10.2. 总结
    11. 11. axios在某些小程序上用不了,可以用flyio模块
    12. 12. 为什么使用jwt加密还可以解密出来
    13. 13. v-if可以使用template进行包裹,外面套一层,这样子渲染就不会多出一层div了
    14. 14. 图片的alt和title
    15. 15. 更改this的执行的方法
      1. 15.1. call方法
        1. 15.1.1. 语法
      2. 15.2. 参数
      3. 15.3. apply方法
        1. 15.3.1. 语法
        2. 15.3.2. 参数说明
      4. 15.4. bind方法
        1. 15.4.1. 语法
        2. 15.4.2. 参数
    16. 16. 使用reduce却莫名其妙返回一个undefined或者NaN
    17. 17. 除了mapState需要手动指明返回什么,其他的mapGetters,mapActions,mapMutations都可以直接写数组(但是开启命名空间后情况是否是这样子不知道)
    18. 18. fly请求微信的code2Session对返回的结果取data项返回的是字符串,而不是对象
    19. 19. 当我们修改state当中新添加字段数据的时候,注意修改为响应式数据
    20. 20. es6简写需要注意
    21. 21. node引入json,自动转换为原生的对象了
    22. 22. 其他一些小知识点
    最新文章
    \ No newline at end of file diff --git a/e40fe344.html b/e40fe344.html new file mode 100644 index 000000000..62aa099e7 --- /dev/null +++ b/e40fe344.html @@ -0,0 +1 @@ +前端文字实现拼音标注 | 梦洁小站-属于你我的小天地

    前端文字实现拼音标注

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/e40fe344.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/e4e505ca.html b/e4e505ca.html new file mode 100644 index 000000000..8fb31d64d --- /dev/null +++ b/e4e505ca.html @@ -0,0 +1 @@ +使用vite的社区模板来创建对应的项目(比如React17,vue+electron) | 梦洁小站-属于你我的小天地

    使用vite的社区模板来创建对应的项目(比如React17,vue+electron)

    安装基础

    1
    2
    yarn add npx -g
    yarn add degit -g

    找到对应的模板进行下载

    • 复制此模板所在的github作者名称和项目地址

    • 命令行输入
    1
    npx degit 作者名/项目地址

    • 完成
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/e4e505ca.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/eb7b141c.html b/eb7b141c.html new file mode 100644 index 000000000..b96aedd05 --- /dev/null +++ b/eb7b141c.html @@ -0,0 +1 @@ +vite.config.js如何使用env的环境变量 | 梦洁小站-属于你我的小天地

    vite.config.js如何使用env的环境变量

    了解下环境变量在vite中

    • 官方文档说到.env.[mode] # 只在指定模式下加载,比如.env.development只在开发环境加载

    • 至于为什么是development,而不是其他的,因为默认就是developmentproduction来区分开发和生产

      • 你也可以自定义,只需要在启动的时候添加--mode xxxx就可以,比如下面的

      • 下图为输出查看import.meta.env,就会发现mode变为了abdfed

    “import.meta” is not available with the “cjs” output format and will be empty [empty-import-meta]

    • 如果你在vite.config.js中直接使用import.meta.env,就会发现出现这个错误了
    1
    2
    3
    4
    5
    6
    7
    正在编译中...
    ▲ [WARNING] "import.meta" is not available with the "cjs" output format and will be empty [empty-import-meta]

    vite.config.js:15:28:
    15target: import.meta.env.VITE_APP_BASE_API,
    ╵ ~~~~~~~~~~~

    1
    2
    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
    import { defineConfig,loadEnv } from 'vite'
    import uni from '@dcloudio/vite-plugin-uni'

    export default defineConfig(({ mode }) => {
    const root = process.cwd();
    const viteEnv = loadEnv(mode, root);
    console.log(viteEnv.VITE_API_ADDRESS);
    return {
    base: './',
    build: {
    minify: true,
    outDir: 'dist',
    },
    server: {
    port: '8067',
    proxy: {
    "^/sys": {
    target: "https://abc.com",
    changeOrigin: true,
    },
    },
    },
    plugins: [
    uni()
    ],
    exclude:[
    /\/README\.md$/,
    ]
    }
    })
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/eb7b141c.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/ebe3cda6.html b/ebe3cda6.html new file mode 100644 index 000000000..8897e7e9d --- /dev/null +++ b/ebe3cda6.html @@ -0,0 +1 @@ +订阅地址-发薪日-白嫖订阅 | 梦洁小站-属于你我的小天地

    订阅地址-发薪日-白嫖订阅

    白嫖机场汇总

    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    #白嫖机场汇总。 持续更新
    #排名不分先后!!!
    #bulink 老牌白嫖
    https://bulink.xyz/
    #几🐔 也是老牌
    https://a.luxury/signin
    #Openit 免费订阅
    https://openit.ml/
    #ikuuu 比较好,不用签到
    https://ikuuu.live/auth/login
    #FIY 节点少…
    https://fly.nullmouse.xyz/user
    #咪蒙
    https://love.mimon.cc/#/dashboard
    #SSRRUB 不验证邮箱随便注册
    https://sub.ssrsub.com/#/dashboard
    #狗子快跑 (不验证邮箱)
    https://www.freedog.cyou/user
    #Galaxy
    https://www.galaxy-cloud.com/#/dashboard
    #Welink 也是老牌
    https://t.me/welink345bot?start=sZJhe7zV
    #lilicloud (不验证邮箱)
    http://www.992266.xyz/#/dashboard
    #PDGC
    https://pdjc.cc/index.php#/dashboard
    #光速云公益站
    http://1q9719r673.qicp.vip/#/dashboard
    #2046 很好的白嫖机场,就是注册麻烦
    https://www.2046.gq/#/dashboard
    #v2云 也很不错,7天不签到销号
    https://cwv587.com/auth/login
    #emo tg群发优惠码获取白嫖码
    https://yyds.emovpn.top/#/login
    #emo的电报群 https://t.me/emovpn
    # 柚子 很不错的机场可以通过优惠码白嫖优惠码可能在tg群更新
    # 柚子tg群: https://t.me/youzi_pro
    https://www.youzi.pro/#/dashboard
    #泡泡狗 速度不错注册白嫖半年
    https://www.paopao.dog/#/dashboard
    #Magic School 白嫖时间长,看限速说的很厉害但是没感觉
    https://2220.it/portal/clientarea
    注:基本都不带aff,如要aff可以私聊群主 @gulaiguq

    ------------------分割线---------------------

    #如果感觉白嫖机场好用的话,帮忙点一下下面的aff谢谢
    #小奇点
    https://t.me/qdyun_bot?start=FJDx7RJK
    #天机
    https://t.me/tjvpn_bot?start=6aXmWbIO
    #神器
    https://t.me/shenseven_bot?start=2SXyiYQb
    这些也算白嫖机场,不过要拉人头…所以点一下吧…谢谢谢
    更新于2022-6-24 14:23

    TG群

    自己用的

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/ebe3cda6.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/ec12f96a.html b/ec12f96a.html new file mode 100644 index 000000000..a214c1038 --- /dev/null +++ b/ec12f96a.html @@ -0,0 +1 @@ +vue控制台警告Runtime directive used on component with non-element root node. | 梦洁小站-属于你我的小天地

    vue控制台警告Runtime directive used on component with non-element root node.

    控制台警告提示信息

    • 控制台警告Runtime directive used on component with non-element root node. The directives will not function as intended.

    原因和解决

    原因

    • 意思是自定义指令不能放到组件上,而是要放到自有的元素上,也就是这里用到的v-show不能放在自定义组件上,而是放在原来就有的标签上,所以这里套了一层div
    • 比如之前的是这样子,v-show指令用在了自定义组件ImageAddR身上,就警告了
    1
    <ImageAddR ref="ImageAddR" v-show="materialType === 'image'"></ImageAddR>

    解决

    • 外面套一层不是自定义组件的东东就可以,我这里套了一层div,你也可以嵌套一层template
    1
    2
    3
    4
    5
    6
    7
    8
    9
    <div v-show="materialType === 'image'">
    <ImageAddR ref="ImageAddR" ></ImageAddR>
    </div>

    或者(新版本好像会报警告'v-show' directives cannot be put on <template> tags)

    <template v-show="materialType === 'image'">
    <ImageAddR ref="ImageAddR" ></ImageAddR>
    </template>
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/ec12f96a.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/ef46a99b.html b/ef46a99b.html new file mode 100644 index 000000000..4378639d7 --- /dev/null +++ b/ef46a99b.html @@ -0,0 +1 @@ +mockjs生成假数据的基本使用 | 梦洁小站-属于你我的小天地

    mockjs生成假数据的基本使用

    mockjs生成假数据的基本使用

    1.安装

    1
    npm install mockjs --save 	
    • 截止写这篇文章的时候,mockjs安装的版本为"mockjs": "^1.1.0"

    2.建立一个文件夹(mock)和文件(mock.js)

    • 注意: mock文件夹和components组件或者是store是同级的(不是说一定,而是一般这样子做~)
    • 建立的文件如下

    3.编辑mock.js文件夹

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    //引入mockjs库
    import mockjs from "mockjs";
    //引入数据
    // import bannerData from "./bannerData.json";

    //这里为了演示,就不单独建立一个bannerData的文件了
    const bannerData = [
    {
    "id|1-3": "001",
    "name|1": ["家用电器1", "家用电器2", "家用电器3"],
    "keywords": ["节能补贴", "4K电视", "空气净化器", "IH电饭煲", "滚筒洗衣机"],
    }
    ]

    // 记录数据模板。当拦截到匹配 url 的 Ajax 请求时,将根据数据模板 template 生成模拟数据,并作为响应数据返回。
    Mock.mock("/mock/bannerData",{
    code:200,
    data:bannerData
    });

    4.在main.js中引入

    1
    2
    3
    4
    5
    6
    7
    import Vue from "vue"
    import App from "@/App"
    import "@/mock/mock";
    new Vue({
    el:"#app",
    render:h=>h(App)
    })

    5.测试

    • 为了测试mock,这里安装了下axios

    App.vue测试mock

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <template>
    <divdiv>
    </template>
    <script>
    import axios from "axios";
    export default {
    name: "",
    data() {
    return {}
    },
    mounted(){
    axios.get("/mock/bannerData").then(res=>{
    console.log("假数据结果",res.data);
    }).catch(error=>{
    console.log(error);
    })
    }
    };
    </script>

    输出结果

    mockjs的数据生成规范

    疑问

    • 我们刚刚编写了下面这段代码,去生成假数据,那么下面这段代码是什么意思呢?
    1
    2
    3
    4
    5
    6
    7
    const bannerData = [
    {
    "id|1-3": "001",
    "name|1": ["家用电器1", "家用电器2", "家用电器3"],
    "keywords": ["节能补贴", "4K电视", "空气净化器", "IH电饭煲", "滚筒洗衣机"],
    }
    ]
    • 根据规范的说明,大概如下

    数据模板中的每个属性由 3 部分构成:属性名、生成规则、属性值

    1
    2
    3
    4
    // 属性名   name
    // 生成规则 rule
    // 属性值 value
    'name|rule': value

    并且根据官方提供的生成规则和示例:

    1.属性值是字符串 String

    1. 'name|min-max': string

      通过重复 string 生成一个字符串,重复次数大于等于 min,小于等于 max

    2. 'name|count': string

      通过重复 string 生成一个字符串,重复次数等于 count

    2. 属性值是数字 Number

    1. 'name|+1': number

      属性值自动加 1,初始值为 number

    2. 'name|min-max': number

      生成一个大于等于 min、小于等于 max 的整数,属性值 number 只是用来确定类型。

    3. 'name|min-max.dmin-dmax': number

      生成一个浮点数,整数部分大于等于 min、小于等于 max,小数部分保留 dmindmax 位。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Mock.mock({
    'number1|1-100.1-10': 1,
    'number2|123.1-10': 1,
    'number3|123.3': 1,
    'number4|123.10': 1.123
    })
    // =>
    {
    "number1": 12.92,
    "number2": 123.51,
    "number3": 123.777,
    "number4": 123.1231091814
    }

    3. 属性值是布尔型 Boolean

    1. 'name|1': boolean

      随机生成一个布尔值,值为 true 的概率是 1/2,值为 false 的概率同样是 1/2。

    2. 'name|min-max': value

      随机生成一个布尔值,值为 value 的概率是 min / (min + max),值为 !value 的概率是 max / (min + max)

    4. 属性值是对象 Object

    1. 'name|count': object

      从属性值 object 中随机选取 count 个属性。

    2. 'name|min-max': object

      从属性值 object 中随机选取 minmax 个属性。

    5. 属性值是数组 Array

    1. 'name|1': array

      从属性值 array 中随机选取 1 个元素,作为最终值。

    2. 'name|+1': array

      从属性值 array 中顺序选取 1 个元素,作为最终值。

    3. 'name|min-max': array

      通过重复属性值 array 生成一个新数组,重复次数大于等于 min,小于等于 max

    4. 'name|count': array

      通过重复属性值 array 生成一个新数组,重复次数为 count

    6. 属性值是函数 Function

    1. 'name': function

      执行函数 function,取其返回值作为最终的属性值,函数的上下文为属性 'name' 所在的对象。

    7. 属性值是正则表达式 RegExp

    1. 'name': regexp

      根据正则表达式 regexp 反向生成可以匹配它的字符串。用于生成自定义格式的字符串。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      Mock.mock({
      'regexp1': /[a-z][A-Z][0-9]/,
      'regexp2': /\w\W\s\S\d\D/,
      'regexp3': /\d{5,10}/
      })
      // =>
      {
      "regexp1": "pJ7",
      "regexp2": "F)\fp1G",
      "regexp3": "561659409"
      }

    那么我们接着下面这段代码的含义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    const bannerData = [
    {
    //属性名为id,生成规则为1-3 属性值为 字符串'001'
    //所以根据规则,是重复字符串'001',重复次数为1-3次
    "id|1-3": "001",

    //属性名为name,生成规则为1 属性值为 数组
    //根据规则,从数组当中随机选取一个并返回
    "name|1": ["家用电器1", "家用电器2", "家用电器3"],

    //属性名为name,生成规则为空 属性值为 数组
    //根据规则,原封不动返回,因为生成规则为空
    "keywords": ["节能补贴", "4K电视", "空气净化器", "IH电饭煲", "滚筒洗衣机"],
    }
    ]

    mock从数组对象当中随机选取指定数目的值返回

    • 如下数据,我想从bannerData当中的shopList随机选取2项返回,那么要怎么做呢?
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    const bannerData = {
    shopList:[
    {
    id:"1",
    name:"李白",
    age:"18",
    },
    {
    id:"2",
    name:"李黑",
    age:"25",
    },
    {
    id:"3",
    name:"李明",
    age:"28",
    },
    {
    id:"4",
    name:"李暗",
    age:"32",
    },
    ]
    }

    误区

    • 一开始我想这样子,将生成规则设置为2,因为上面的数据生成规范说'name|1': array意思是从属性值 array 中随机选取 1 个元素,作为最终值。那么我讲生成规则设置为2,岂不是随机选取2个
      • 于是我设置"shopList|2",但是事与原委,结果是重复shopList这个数组重复了2次
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    const bannerData = {
    "shopList|2":[
    {
    id:"1",
    name:"李白",
    age:"18",
    },
    {
    id:"2",
    name:"李黑",
    age:"25",
    },
    {
    id:"3",
    name:"李明",
    age:"28",
    },
    {
    id:"4",
    name:"李暗",
    age:"32",
    },
    ]
    }

    shopList|2

    解法

    • 后面细看才知道,
      • 'name|1': array 从属性值 array 中随机选取 1 个元素,作为最终值。
      • 'name|count': array 通过重复属性值 array 生成一个新数组,重复次数为 count
    • 也就是说我们设置"shopList|2":array,因为我们设置的生成规则是2,2大于了1,所以是采取重复次数
    • 真正做法就是使用Mock当中的pick方法
      • 可以用过Mock.Random引入Random库
      • 也可以直接通过占位符来使用@官方api
    • Random.pick()在官方是传入一个参数,但是后面查看别人写法,也可以传入参数2,参数3,语法规范如下
      • Random.pick(要取的数据,取的最小数据个数(定位min),取的最大数据个数(定位max)),从要取的数据当中随机取min-max的数据个数
    1
    2
    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
    //引入mockjs库
    import Mock from "mockjs";
    var Random = Mock.Random;//引入Random库
    const bannerData = {
    "shopList":[
    {
    id:"1",
    name:"李白",
    age:"18",
    },
    {
    id:"2",
    name:"李黑",
    age:"25",
    },
    {
    id:"3",
    name:"李明",
    age:"28",
    },
    {
    id:"4",
    name:"李暗",
    age:"32",
    },
    ]
    }

    var a = Random.pick(bannerData.shopList,2,4)
    console.log(a);

    可以看到,每一次循环都是不一样的数据返回

    参考文章

    Mock.Random:https://github.com/nuysoft/Mock/wiki/Mock.Random

    Mock.js之数据占位符定义:https://www.jianshu.com/p/c12072f231d4

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/ef46a99b.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/efae7552.html b/efae7552.html new file mode 100644 index 000000000..f2b475462 --- /dev/null +++ b/efae7552.html @@ -0,0 +1 @@ +我来图书馆小程序加密后抓包分析反编译抢位置 | 梦洁小站-属于你我的小天地

    我来图书馆小程序加密后抓包分析反编译抢位置

    提交预约流程

    注意

    1. 抓包分析好像还涉及到wxlib/wx/login 不过从后面提交数据来看好像用不到,可能我技术问题吧

    抓包分析

    有人可能最新微信PC抓不了小程序包

    解决办法
    1. 打开一个任意小程序,打开任务管理器,找到进程。右键打开文件位置。

    2. 退出电脑微信,右键结束小程序进程。

    3. 找到这个目录后删除这个目录

    4. 或者你有everything这个工具,直接搜索 WMPFRuntime 然后右键打开所在文件夹,把里面这个4376目录删除就可以

    2.0新变化

    • 位置预约的一些信息加密了

    预约信息加密解决

    反编译

    微信小程序反编译

    这次小程序反编译出来的文件

    反编译后查看源代码

    • 预约位置关键代码

    1
    RSA加密的,跟着他代码来就可以
    • 找啊找,发现找到了,在app.js当中是rsa.modules和exponent加密代码

    • 找啊找,找到了小程序图书馆调用的函数工具库

    • 接下来就简答了,模拟导入就可以了

    计算生成预约js(nodejs下运行)代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    const ttt = require("./security.js");
    var modulus = "";//拆包一下就知道
    var exponent = "010001";
    var iii = ttt.getKeyPair(exponent, "", modulus);
    // time_end时间截止
    //时间段,1代表8:00-12:00时间段
    //时间段,2代表12:00-17:00时间段
    //时间段,3代表17:30-23:00时间段
    //day_time日期时间 2022-04-10
    //num 为时间段
    var id = 11;
    var vd_id = 8235; //区域代码
    var num = 1; //是预约时间段1 2 3
    var day_time = "2022-04-23"; //预约日期
    var time_end = "23:00"; //截止时间段
    // var r = a.data.selectSeat.id + "," + a.data.selectSeat.vd_id + "," + a.data.urlOptions.num + "," + a.data.urlOptions.day_time + ", " + a.data.urlOptions.time_end
    var rsa_data = id + "," + vd_id + "," + num + "," + day_time + ", " + time_end;

    //预约位置的时候的加密算法
    var rsa_result = ttt.encryptedString(iii, rsa_data);
    console.log("预约加密代码",rsa_result);//也就是获取info部分
    1
    接下来就简答了如果有了这个预约加密代码

    提交预约代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var userSeatInfoOption={
    method:"get",
    url:"https://wxcourse.jxufe.cn/wxlib/wx/appoint",
    params:{
    'isPeriod': 1,
    'userId': "",//依旧是抓包获取
    'appointType': 0,
    'officeCode': "jxcjdx",
    'colleageId': 51,
    //添加加密后的字段
    'info':""
    }
    };

    运行

    • 预约加密代码

    • 预约成功

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/efae7552.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/f28d574.html b/f28d574.html new file mode 100644 index 000000000..8ab72506a --- /dev/null +++ b/f28d574.html @@ -0,0 +1 @@ +nth-of-type选择器使用的一个坑 | 梦洁小站-属于你我的小天地

    nth-of-type选择器使用的一个坑

    前景

    一段HTML代码,我需要让第一个类名为content的div背景设置为红色的

    1. js代码
    1
    2
    3
    4
    <div class="header">1</div>
    <div class="content">2</div>
    <div class="header">3</div>
    <div class="content">4</div>
    1. style样式
    1
    2
    3
    4
    5
    6
    7
    <style>
    div{
    width: 100px;
    height: 100px;
    border: 1px solid red;
    }
    </style>

    一开始想到的

    1. 要选第一个类名为content,当然是nth-of-type了,因为我只想要让拥有content类的div容器被选择
    1
    2
    3
    4
    5
    6
    7
    //错误的选择器代码
    <style>
    .content:nth-of-type(1){
    background-color: red;
    }
    </style>

    1. 结果如下

    为什么没有用?

    1. :nth-of-type当中没有指定标签,那么就会查找第一个匹配的,同级别下所对应的所有元素标签(后面有具体解释和示例)
    2. :nth-of-type针对的是标签选择器,也就说你用类和nth-of-type结合使用是不恰当的,如果要使用,记得先用类名筛选,再用标签使用nth-of-type选择器
    1
    2
    3
    4
    //也就是这种,先用类名筛选,再用标签使用nth-of-type选择器
    .one p:nth-of-type(2){
    xxxxxx
    }

    分析设置代码的,浏览器做了什么

    1
    2
    3
    4
    5
    6
       //也就是这段js代码
    <style>
    .content:nth-of-type(1){
    background-color: red;
    }
    </style>
    1
    2
    3
    4
    5
    <!--js代码对应的html代码-->
    <div class="header">1</div>
    <div class="content">2</div>
    <div class="header">3</div>
    <div class="content">4</div>

    浏览器做了什么示意图

    再来举个例子

    1. 首先,由于我们没有书写具体要在哪一个标签,那么浏览器就会全局查找拥有这个类的标签,并且把这个类下同级别的所有标签加入到待查列表

    2. .content:nth-of-type(2) 例子

    3. .content:nth-of-type(1)例子

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/f28d574.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/f4cb1979.html b/f4cb1979.html new file mode 100644 index 000000000..24ab109ec --- /dev/null +++ b/f4cb1979.html @@ -0,0 +1 @@ +基于vue-pdf-embed的二开PDF预览的通用组件 | 梦洁小站-属于你我的小天地

    基于vue-pdf-embed的二开PDF预览的通用组件

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/f4cb1979.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/f54fdcb0.html b/f54fdcb0.html new file mode 100644 index 000000000..4c5ff8603 --- /dev/null +++ b/f54fdcb0.html @@ -0,0 +1 @@ +今日刷题-注意let | 梦洁小站-属于你我的小天地

    今日刷题-注意let

    题目1

    1
    2
    3
    4
    5
    下列不属于document对象方法的是?(多选)
    A: onload
    B: querySelectorAll
    C: children
    D: ajax
    • 答案

      • A,C,D
    • 解析

      • A: onload 属性是一个事件处理程序用于处理Window, XMLHttpRequest, <img> 等元素的加载事件,当资源已加载时被触发。

        比如window.onload = function ( ) { }

        可以参考

      • B: document.querySelectorAll(“选择器”)

      • C: parentNode.children 是属性,用于DOM节点查询子元素

        • childNodes 属性返回所有的节点,包括文本节点、注释节点
        • children 属性只返回元素节点
      • D: AJAX不是JavaScript的规范,它只是一个缩写:Asynchronous JavaScript and XML,意思就是用JavaScript执行异步网络请求。在现代浏览器中主要依靠 XmlHttpRequest 对象

    题目2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    //请问以下JS代码的输出是什么?
    let A = function() {}
    A.prototype.a = 1;
    let B = new A();
    A.prototype = {
    b: 2,
    c: 3
    }
    let C = new A();
    A.prototype.d = 4;
    console.log(B.a);
    console.log(B.b);
    console.log(C.c);
    console.log(C.d);
    • 答案

      • 1 undefined 3 4
    • 解析

      1
      2
      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
      let A = function() {}
      A.prototype.a = 1;
      //此时 A.prototype = { a: 1 }


      let B = new A(); //此时 B = {}
      //在创建B时,已将B._proto_ = A.prototype = { a: 1 }
      //即使后面A.prototype重新赋值,将A.prototype开辟了新的空间指向别的对象
      //B._proto_并没有改,还是指向{a:1}这个对象


      A.prototype = { //此时 A.prototype = { b: 2, c: 3 }
      b: 2,
      c: 3
      }
      let C = new A(); //C = {}
      A.prototype.d = 4;//此时A.prototype = { b: 2, c: 3, d: 4 }

      console.log(B.a); //1
      console.log(B.b); //undefined
      console.log(C.c); //3
      console.log(C.d); //4

      //C.d
      //着重区分: A.prototype.d = 4 和 A.prototype 重新赋值 不是一个概念
      //A.prototype重新赋值时,A.prototype已经指向另一个对象了
      //A.prototype.d = 4时,访问的还是同一个A.prototype 对象

    题目3

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //执行以下代码,下列选项中,说法正确的是()
    function * gen() {
    yield 1;
    yield 2;
    yield 3;
    }
    A: gen()执行后返回2
    B: gen()执行后返回undefined
    C: gen()执行后返回一个Generator对象
    D: gen()执行后返回1
    • 答案
      • C
    • 解析
      • 在函数声明时,由于带有星号,所以gen函数是一个生成器函数,调用生成器函数会返回生成器(Generator)对象,C选项正确。

    题目4

    1
    2
    3
    4
    5
    6
    7
    8
    9
    在 es6 中,下面程序运行结果输出,选项结果正确的是
    for(let i=0;i<12;i++){} console.log(i);
    const a = 12;a=13;
    console.log(a);
    const g = {b:3};
    console.log(g.b);
    g.b=12;console.log(g.b);
    let [head,...tail] = [1,2,3,4];
    conole.log(tail);
    • 答案

      • i not defined,TypeError,3,12,[2,3,4]
    • 解析

      • let 与var不同,存在块级作用域,在for循环中声明,循环之外销毁 所以 i not defined (for当中也是一个块级作用域,没想到)
      • const 声明一个常量无法更改,所以TypeError(这里是分号,不是逗号!!),但是const声明一个对象是可以改对象里面的属性的
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/f54fdcb0.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/f8f8513a.html b/f8f8513a.html new file mode 100644 index 000000000..78e0801f3 --- /dev/null +++ b/f8f8513a.html @@ -0,0 +1 @@ +今日刷题-decodeURI | 梦洁小站-属于你我的小天地

    今日刷题-decodeURI

    题目1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    下列正确定义函数的是
    A: function foo() {}

    B: Function foo() {}

    C: var foo = new Function() {}

    D: var foo = new function() {}

    • 答案
      • A
    • 解析
      • B当中的 ‘Function’ 写错了,应该是 ‘function’
      • C当中应该是 var foo = new Function (参数1,参数2,……)
      • D同C一样,都是弄错了

    题目2

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    以下代码执行时不会在控制台输出错误信息的是:
    A: let a = decodeURIComponent('%');
    B:
    var a;
    a();
    function a() {
    console.log(a);
    }
    C:
    Promise.reject(123).finally(a => {
    console.log(a);
    });
    D:
    var a = 1;
    let a = 2;
    console.log(a);

    • 答案

      • B
    • 解析

      • A: decodeURIComponent(字符串),对字符串进行解码,此时这里传入的字符串应该是使用encodeURI或者是encodeURIComponent编码后的字符串进行解码,直接传入%是一个没有编码的字符串( ‘%’ 编码后为 ‘ %25’ )

      • encodeURIComponentencodeURI不同的是

        • encodeURIComponent会对一些特殊字符进行编码

        • encodeURI不会对特殊字符进行编码

      • B: 函数提升和变量提升同时存在,函数提升优先级 高于 变量提升,所以变量a被函数所覆盖

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        console.log(foo); 
        var foo = 1 //变量提升
        console.log(foo)
        foo()
        function foo(){ //函数提升
        console.log('函数')
        }
        =========等价于=============>
        function foo(){ //提到顶端
        console.log('函数')
        }
        var foo
        console.log(foo) //输出foo这个函数,因为上面foo没有被赋值,foo还是原来的值
        foo = 1; //赋值不会提升,赋值后 foo就不再是函数类型了,而是number类型
        console.log(foo) //输出1
        foo() //这里会报错,因为foo不是函数了
      • C: 选项需要 .catch去补货 被 reject 的 Promise,才能不抛出错误

      • D: 选项因为 let 的 TDZ(“暂时性死区”),在 let 的作用域中无法重复声明,也无法在声明语句之前使用(没有变量提升)。简而言之,在 let 语句出现之前,都是无法使用该变量的(说通俗点就是——只要let所在定义域下(也就是在函数当中的 { } 当中),在let代码没有执行之前,如果这个时候有一段代码对let声明的变量名称进行了访问并且代码执行了( 比如console.log() ) ,那么就会发生报错!)

        1
        2
        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
        补充一点。这种情况下是可以正常运行的:
        var a = 1;
        {
        //块级作用域
        let a = 2;
        }
        console.log(a);//输出1


        也看看下面这种情况
        let foo = () => {
        console.log(x);//输出x=10;
        x++;
        }
        let x = 10;
        foo();
        //结合上面说所的,分析下过程
        //就是只要let所在定义域下(也就是在函数当中的 `{ }` 当中)
        //在let代码没有执行之前
        //如果这个时候有一段代码对let声明的变量名称进行了访问
        //( 比如console.log() ) ,那么就会发生报错!

        //拆解 执行过程
        //第一步
        let foo = {...};//此时函数并没有执行,所以不存在对let变量进行访问并执行,所以不报错
        //第二步
        let x = 10;
        //第三步
        foo();

    题目3

    1
    2
    3
    4
    5
    6
    7
    8
    9
    下列代码中,页面打开后能够弹出1的是?(多选)
    A: <iframe src=”javascript: alert(1)”></iframe>

    B: <img src=”” onerror=”alert(1)”/>

    C: IE下<s style=”top:expression(alert(1))”></s>

    D: <div onclick=”alert(1)”></div>

    • 答案
      • A,B,C
    • 解析
      • A:加载的时候会触发alert(1)代码
      • B:img当中的onerror,当图片不存在加载失败的时候会触发,此时这里src=“”,导致加载失败,所以触发alert(1)事件
      • C:在ie 7下会连续弹出, IE5及其以后版本支持在CSS中使用expression,用来把CSS属性和Javascript表达式关联起来,这里的CSS属性可以是元素固有的属性,也可以是自定义属性。就是说CSS属性后面可以是一段Javascript表达式,CSS属性的值等于Javascript表达式计算的结果。在表达式中可以直接引用元素自身的属性和方法,也可以使用其他浏览器对象。这个表达式就好像是在这个元素的一个成员函数中一样。
      • D:onclick,单击div标签才会触发
    1
    2
    3
    4
    5
    6
    7
    8
    当用户打开一个网页时,想一直停留在当前打开的页面,禁止页面前进和后退,以下正确的是(      )(多选)
    A: window.history.forward(1);

    B: window.history.back(1);

    C: window.history.go(-1);

    D: window.history.forward(-1);
    • 答案

      • A,D
    • 解析

      • 题目意思

        • 相当于URI当中输入了网站,比如说baidu.com,然后会发现只有后退没有前进按钮的操作,这种情况下从ABCD选

      • A:window.history.forward( 1 ) (forward不用传递参数,传入也会无效)前进一个页面,这里没办法前进,所以可以选

      • B:window.history.back( )(back不用传递参数,传入也会无效)后退一个页面,这里可以后退,但是题目要求禁止,所以不选

      • C:window.history.go(-1) 后退一个页面,这里可以后退,但是题目要求禁止,所以不选

        • window.history.go(delta);—-相对于当前页面你要去往历史页面的位置。负值表示向后移动,正值表示向前移动。因此,例如:history.go(2)向前移动两页,history.go(-2)则向后移动两页。如果未向该函数传参或delta相等于0,则该函数与调用location.reload()具有相同的效果。
      • D:window.history.forward( -1 ) 前进一个页面,这里没办法前进,所以可以选

    题目4

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    如何遍历下面的 my_data 对象?
    var my_data={a:'Ape', b:'Banana', c:'Citronella'};

    A: for(var key in my_data) {}

    B: foreach(my_data as key=>value) {}

    C: for(var i=0;i<my_data.length;i++) {}

    D: 全不选
    • 答案
      • A
    • 解析
      • A: 没有错,for...in语句以任意顺序迭代一个对象的除Symbol以外的可枚举属性,包括继承的可枚举属性。
        • for...in不应该用于迭代一个关注索引顺序的 Array。(Array用forEach或者for…of代替)
      • B: 大佬说是php的,php我不太懂
      • C: 对象没有.length属性,输出也是undefined(伪数组除外)
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/f8f8513a.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/fa4383.html b/fa4383.html new file mode 100644 index 000000000..abcf58303 --- /dev/null +++ b/fa4383.html @@ -0,0 +1 @@ +微信小程序模块化、组件传值、添加data,menthods类型等-持续更新 | 梦洁小站-属于你我的小天地

    微信小程序模块化、组件传值、添加data,menthods类型等-持续更新

    组件模块化

    基本概括

    • 模块化一般分为二种,页面和模块。页面由模块构成。
    • 我们拆分模块化可能是这样子的
      • modules(页面模块)
        • pageA
          • A1模块
          • A2模块
        • pageB
          • B1模块
          • B2模块
      • pages(页面)
        • pageA
        • pageB
    • 在小程序当中
      • 我们使用Page注册小程序中的一个页面@官方文档-page说明
      • 使用Component创建自定义组件@官方文档-Component说明
        • 注意下,通过Component创建的组件里面的样式,只会应用于当前组件,比如组件A内部设置了类A,那么在外部即使设置了同样的类名,也不会被应用相同的类名
      • 所以我们创建页面使用Page,创建模块使用Component
    • 模块化后需要注意的点(官方API拷贝过来的)
      • 因为 WXML 节点标签名只能是小写字母、中划线和下划线的组合,所以自定义组件的标签名也只能包含这些字符。
      • 自定义组件也是可以引用自定义组件的,引用方法类似于页面引用自定义组件的方式(使用 usingComponents 字段)。
      • 自定义组件和页面所在项目根目录名不能以“wx-”为前缀,否则会报错。
      • 出于性能考虑,使用 usingComponents 时, setData 内容不会被直接深复制,即 this.setData({ field: obj })this.data.field === obj 。(深复制会在这个值被组件间传递时发生。)

    页面使用模块化组件

    组件父子传值

    父亲给儿子值

    1
    2
    3
    4
    5
    vue时候
    <Son :name="name" :age="age"/>

    微信小程序
    <Son name="{{ name }}" age="{{ age }}"/>
    • 儿子需要做的
      • 就是需要添加properties属性,并未其添加对应的属性说明即可
    1
    2
    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
    35
    //小程序

    // Son.js
    Component({
    //...

    //书写将要接收的值
    properties:{
    name:{
    // 类型(必填),目前接受的类型包括:String, Number, Boolean, Object, Array, null(表示任意类型)
    type:String,
    // 属性初始值(可选),如果未指定则会根据类型选择一个
    value: '',
    // 属性被改变时执行的函数(可选)
    // 其实也可以不在这里写,在observers对象(监听data和properties变化)当中书写也可以
    observer: function (newVal, oldVal) { }
    },
    age:{
    type:number,
    value:0
    }
    }

    //...
    })

    //然后就可以正常使用了

    //Son.wxml

    <view>

    <text>名称是:{{ name }}</text>
    <text>年龄是: {{ age }}</text>
    </view>

    儿子给父亲值

    • 方式1: triggerEvent
    • 方式2: 双向绑定

    方式1:triggerEvent

    • 资料

    • 父亲需要做的

      • 通过bind:自定义事件名=回调函数传递给儿子
      • 注意,父亲通过triggerEvent接收的参数不单单是子组件传递过来的数据,还有很多其他的数据,子组件传递过来的数据仅仅是在其中的detail字段里面
      • 输出查看儿子传递过来的数据,可以看到,儿子传递过来的name数据被微信小程序放在了detail对象当中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // Father.wxml
    <Son bind:changething="getChangeParams"/>

    // Father.js
    Page({
    //...

    methods:{

    changething(e){
    console.log(e);
    //注意查看上面输出截图
    }
    },

    //...
    })
    • 儿子需要做的
      • 通过this.triggerEvent调用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // Son.wxml
    <view>
    <text bind:tap="handleChange">点击我触发事件</text>
    </view

    // Son.js

    Component({
    //...

    methods:{
    handleChange(){
    // 参数1为触发的事件名
    // 参数2为detail对象(也就是我们需要传递给父组件的值)
    // 参数3为触发发事件的选项(可选)
    const data = {name:'我是子组件传递的数据'}
    this.triggerEvent('changething',data)
    }
    },

    //...
    })

    方式2:双向绑定

    todo todo todo todo todo todo todo todo todo todo

    获取组件实例对象和组件自定义返回结果

    • 学习学习~

    获取组件示例对象同时调用组件实例

    组件自定义返回结果

    为Component/Page指明Data类型,Method类型(ts)

    • 感觉小程序ts挺乱的吧….话不多说

    Page页面指明

    • types.ts(类型文件)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    //定义data当中应该有的数据
    export interface Data{
    name:string,
    age:string,
    }

    //定义应该有的方法
    //export type SayHello = () => void;
    export interface Methods {
    sayHello:() => void;
    }
    • index.ts(每一个page都有的入口文件)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    import {Data,Methods} from "./types";

    Page<Data,Methods>({
    data:{
    name:'梦洁'
    age:18,
    sex:'知',//多了,ts检测报错
    },
    sayHello(){

    }
    })

    Component组件指明

    • Page有一点不一样,因为Component多了一个properties不过这个properties可以要,也可以不要

    • types.ts(类型文件)

    1
    2
    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
    import MethodOption = WechatMiniprogram.Component.MethodOption;
    import PropertyOption = WechatMiniprogram.Component.PropertyOption;
    //定义data当中应该有的数据
    export interface Data{
    name:string,
    age:string,
    }

    //定义Properties属性
    export interface ListParams {
    page:number
    pageSize:number
    }

    export interface Props extends PropertyOption{
    params: {
    type: ObjectConstructor,
    value: ListParams | any
    }
    }

    //定义methods有的方法
    //export type SayHello = () => void;
    export interface Methods extends MethodOption {
    sayHello:() => void;
    }

    • index.ts(每一个Component都有的入口文件)
    1
    2
    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
    import {Data,Props,Methods} from "./types";

    //如果不需要Properties说明,则第二个参数需要为 ‘{}’
    Component<Data,Props,Methods>{
    data:{
    name:'梦洁'
    age:18,
    sex:'知',//多了,ts检测报错
    },

    properties:{
    params:{
    //目前接受的类型包括:String, Number, Boolean, Object, Array, null(表示任意类型)
    type: Object,
    value: (() => {
    page:1,
    pageSize:10
    }),
    }
    },

    methods:{
    sayHello(){

    }
    }
    }


    外部样式类

    • @官方文档-外部样式类

    • 启用来源于一个t-design吧,要设置search的背景颜色,发现没有props,但是有external-classes,所以就来学习学习

    • 有时,组件希望接受外部传入的样式类。此时可以在 Component 中用 externalClasses 定义段定义若干个外部样式类。这个特性可以用于实现类似于 view 组件的 hover-class 属性:页面可以提供一个样式类,赋予 viewhover-class ,这个样式类本身写在页面中而非 view 组件的实现中。

    注意:在同一个节点上使用普通样式类和外部样式类时,两个类的优先级是未定义的,因此最好避免这种情况。(意思就是你在组件内部通过这个类名设置了样式,又在外部通过暴露的类名设置了样式,当样式发生重叠的时候,优先使用谁的不确定,但是可以使用important来强制)

    代码示例:

    • 组件
    1
    2
    3
    4
    /* 组件 custom-component.js */
    Component({
    externalClasses: ['my-class']
    })
    1
    2
    <!-- 组件 custom-component.wxml -->
    <custom-component class="my-class">这段文本的颜色由组件外的 class 决定</custom-component>
    • 页面
    1
    2
    <!-- 页面的 WXML -->
    <custom-component my-class="red-text" />
    1
    2
    3
    4
    /* 页面的 */
    .red-text {
    color: red;
    }

    组件的插槽

    • 如果是默认插槽,直接使用就可以,不需要其他做法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <!-- 组件模板 -->
    <view class="wrapper">
    <view>这里是组件的内部节点</view>
    <slot></slot>
    </view>

    <!-- 引用组件的页面模板 -->
    <view>
    <component-tag-name>
    <!-- 这部分内容将被放置在组件 <slot> 的位置上 -->
    <view>这里是插入到组件 slot 中的内容</view>
    </component-tag-name>
    </view>
    • 默认情况下,一个组件的 wxml 中只能有一个 slot 。需要使用多 slot 时,可以在组件 js 中声明启用
    1
    2
    3
    4
    5
    6
    7
    Component({
    options: {
    multipleSlots: true // 在组件定义时的选项中启用多 slot 支持
    },
    properties: { /* ... */ },
    methods: { /* ... */ }
    })
    • 然后在组件当中就可以以不同的name来区分插槽,并且父组件也可以使用了
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <!-- 组件模板 -->
    <view class="wrapper">
    <slot name="before"></slot>
    <view>这里是组件的内部细节</view>
    <slot name="after"></slot>
    </view>

    <!-- 引用组件的页面模板 -->
    <view>
    <component-tag-name>
    <!-- 这部分内容将被放置在组件 <slot name="before"> 的位置上 -->
    <view slot="before">这里是插入到组件slot name="before"中的内容</view>
    <!-- 这部分内容将被放置在组件 <slot name="after"> 的位置上 -->
    <view slot="after">这里是插入到组件slot name="after"中的内容</view>
    </component-tag-name>
    </view>

    路由传参 todo

    小程序登录 todo

    • 登录流程如图所示

    • 巴拉巴拉,就不赘叙了,前端一般都是调用wx.login然后向自己的后端提供的接口发送请求就完成,然后存储token

    小程序获取用户信息(正确应该叫头像昵称填写能力)

    小程序获取手机号

    1
    <button open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber">获取手机号</button>

    小程序骨架屏

    参考文章

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/fa4383.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/fcdd2823.html b/fcdd2823.html new file mode 100644 index 000000000..22b636c5d --- /dev/null +++ b/fcdd2823.html @@ -0,0 +1 @@ +今日刷题-let的暂时性死锁 | 梦洁小站-属于你我的小天地

    今日刷题-let的暂时性死锁

    题目1

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    以下代码执行后,输出结果为() 
    let x = 10;
    let foo = () => {
    console.log(x);
    let x = 20;
    x++;
    }
    foo();
    A: 抛出ReferenceError
    B: 10
    C: 20
    D: 21
    • 答案

      • A
    • 解析

      • 需要知道的知识点

        • let变量的没有变量提升的
        • 只要块级作用域内存在let命令,它所声明的变量就“绑定”(binding)这个区域,不再受外部的影响。**(暂时性死区)**
        • 上面这句话说通俗点: 就是只要let所在定义域下(也就是在函数当中的 { } 当中),在let代码没有执行之前,如果这个时候有一段代码对let声明的变量名称进行了访问并且代码执行了( 比如console.log() ) ,那么就会发生报错!
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        //注意下面这种情况
        let foo = () => {
        console.log(x);//输出x=10;
        x++;
        }
        let x = 10;
        foo();
        //结合上面说所的,分析下过程
        //就是只要let所在定义域下(也就是在函数当中的 `{ }` 当中)
        //在let代码没有执行之前
        //如果这个时候有一段代码对let声明的变量名称进行了访问
        //( 比如console.log() ) ,那么就会发生报错!

        //拆解 执行过程
        //第一步
        let foo = {...};//此时函数并没有执行,所以不存在对let变量进行访问并执行,所以不报错
        //第二步
        let x = 10;
        //第三步
        foo();

      • 具体let知识(这里解释的更完整)

    扩展

    es6中的class和let const一样都不存在提升

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // 在位置A打印变量a与在位置B打印变量a各会有怎样的输出?
    原来的代码
    var a = 1;
    function test(){
    //console.log(a) 位置A
    class a {}
    // console.log(a) 位置B
    }
    test();

    实际上提升后的
    var a = 1;
    function test(){
    console.log(a) 位置A //在test()作用域内找得到a
    //是一个class但是存在TDZ暂时性死区,访问报错
    class a {}
    console.log(a) 位置B //a已经声明创建出来了
    }
    test()

    题目2

    1
    2
    3
    4
    5
    6
    7
    8
    下列表达式中,返回值为true的是()多选
    ①Object.is(NaN,NaN)

    ②Object.is(+0,-0)

    ③NaN === NaN

    ④+0 === -0
    • 答案
      • ①④
    • 解析
      • NaN和谁比较运算(+ - * / == ,====)都是false
      • indexOf无法识别数组的NaN成员也就是[NaN].indexOf(NaN)输出结果为-1(代表未找到),所以想要找到数组当中NaN就可以使用includes,[1,2,NaN].includes(NaN)返回为true
      • Object.is()方法认为NaN等于NaN (没得说,记!)
        • Object.is(NaN, NaN) // true
        • Object.is(+0, -0) // false
        • +0===-0的返回结果为true
    1
    2
    3
    4
    5
    以下哪些事件支持冒泡?(多选)
    A: mouseenter
    B: scroll
    C: focus
    D: keypress
    • 答案

      • B D
    • 解析

      • 看图就好~

    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/fcdd2823.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/fce322fe.html b/fce322fe.html new file mode 100644 index 000000000..708c28ef7 --- /dev/null +++ b/fce322fe.html @@ -0,0 +1 @@ +vue当中addRoutes动态添加路由白屏解决和next(),next("/")的一些区别 | 梦洁小站-属于你我的小天地

    vue当中addRoutes动态添加路由白屏解决和next(),next("/")的一些区别

    问题产生前言

    • 使用动态添加路由router.addRoutes()后进入一个页面,对着这一个页面刷新一下,然后页面就白屏了并且不管刷新多少次都没有用,依旧是白屏,只有重新进入页面才有效果

      • 比如对于网站http://localhost:9528/#/product/attr/list,现在显示是正常的,对着这一个页面刷新一下,页面就白屏了,刷新多少次都没有用,必须要重新访问一次路由才可以必须要重新访问一次网站才可以(只要不再次刷新就可以)
    • 本文参考学习了该博主的文章

    问题分析

    • 动态添加路由无非就是几个过程

      1. router.addRoutes();
      2. 页面访问动态生成的路由
    • 步骤1没有问题,问题就出现在页面访问动态生成的路由上面

    我们再来分析下过程

    1. 页面被刷新,路由信息被重新计算生成并通过addRoutes方法动态添加到了router当中
    2. addRoutes方法还没有完成,用户就已经在访问界面了(可以理解为addRoutes和访问路由同时进行)
    3. 用户一边访问界面,后面一边动态添加路由,addRoutes相当于还没有完成就被访问了路由(可以理解访问了一个此刻不存在的路由导致的白屏)
    4. 所以必须要必须要重新访问一次路由才可以解决白屏问题

    要怎么解决这个问题?

    解决办法
    • 不应该使用next()

    • 全局前置守卫使用next({ ...to, replace: true })

      • next({ ...to}); 也是可以的

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        next({ ...to, replace: true })中的replace: true

        只是一个设置信息,告诉VUE本次操作后,不能通过浏览器后退按钮,返回前一个路由
        //换句话说,你使用了replace:true后重新访问了网站
        //就不可以通过浏览器来返回页面之前和之后的网站了

        举例子:

        //比如我刷新之前依次!依次!依次!访问了下面二个网站
        网站1: http://localhost:9528/#/product/attr/list
        网站2: http://localhost:9528/#/product/spu/list
        那么按照平时的来说,我刷新页面依旧可以使用浏览器的前进后退按钮进行跳转了
        后退按下,跳转到了网站1,然后此时前进按下,跳转到网站2

        //但是如果使用了replace:true
        那么刷新网页后就不可以通过前进后退按钮来后退了,之前记录都无效了

    动态添加路由,全局前置守卫应该修改成为如下代码(只是示例参考)
    • 下面代码是来自vue-element-admin模板当中src\permission.js文件夹的~这里进行了修改举例
    1
    2
    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
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    //如果token存在
    if (hasToken) {
    //token存在了还访问登录界面,就跳转到首页去
    if (to.path === '/login') {
    next({path: '/'});
    NProgress.done();
    }
    //token存在并且访问的不是登录地址
    else {
    //获取用户名
    const hasGetUserInfo = store.getters.name;
    //用户名存在
    if (hasGetUserInfo) {
    //放行
    next()
    }
    //用户名不存在,说明token过期了或者被删除了
    else {
    try {
    //发送请求获取并存储用户信息
    await store.dispatch('user/getInfo')
    //用于是动态添加的路由,所以这里应该修改
    // next();
    //改为这个
    next({...to});
    //或者
    // next({...to,replace:true});
    }
    //发生错误
    catch (error) {
    //移除token信息(不移除这个全局前置守卫就是死循环!)
    await store.dispatch('user/resetToken')
    //弹出信息框
    Message.error(error || 'Has Error')
    //跳转到登录页面
    next(`/login?redirect=${to.path}`)
    NProgress.done()
    }
    }
    }
    }
    //token不存在
    else {
    //如果用户访问的是登录界面,放行
    if ('/login' == to.path) {
    next()
    }
    //用户访问的不是登录界面,跳转到登录界面并携带跳转之前的网址
    //这样子当用户登录成功后就可以跳转到用户之前想去的网址
    else {
    next(`/login?redirect=${to.path}`)
    NProgress.done()
    }
    }

    为什么next()换为next({…to})(或者next({…to,replace:true}))就可以了,next和这二个区别在哪里

    首先我们需要知道路由守卫(全局前置守卫为例子)

    • 先上代码

      1
      2
      3
      4
      5
      beforeEach((to, from, next) => {
      to // 要去的路由
      from // 当前路由
      next() // 放行的意思
      }
    • 代码很简单,但是除了next() 我们应该还见过next("/") next("/login") next({...to}) next({...to,replace:true})

    • 在路由守卫当中,只有next()是放行(放你通过,不会在审核),而next("/") next("/login") next({...to}) next({...to,replace:true} 等,都是中断当中的全局前置守卫,执行新的全局前置守卫

      • 中断当中的全局前置守卫,执行新的全局前置守卫意思就是会再次调用beforeEach

      • 如下面代码例子

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        //比如这个,你一定以为是跳转到"/login"就完事了
        beforeEach((to, from, next) => {
        next('/login')
        }
        //实际上执行的过程代码
        beforeEach((to, from, next) => {
        beforeEach(('/logon', from, next) => {
        beforeEach(('/logon', from, next) => {
        beforeEach(('/logon', from, next) => {
        beforeEach... // 一直循环下去...... , 因为我们没有使用 next() 放行
        }
        }
        }
        }
      • 一直循环下去导致溢出

        Maximum call stack size exceeded

      • 再来看看这里例子 地址栏输入/home(从哪里来的不重要,我们只需要关注到哪里去)

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        beforeEach((to, from, next) => {
        //如果目的地址等于 '/home'
        //就跳转到 登录地址 '/login'
        if(to.path === '/home') {
        next('/login')
        }
        // 如果要去的地方不是 /home ,就放行
        else {
        next();//放行
        }
        }
        //访问过程如下代码
        beforeEach((to, from, next) => {
        //进行了中断跳转,会再次调用beforeEach去判断,此时的目的地址是'login'了
        beforeEach(('/login', from, next) => {
        // 现在要去的地方不是 /home , 因此放行
        next();
        }
        }

        看看这代码执行的流程图

        执行的流程图

    总结

    • next()是放行,不会引发beforeEach再次调用
    • next("/") next("/login") next({...to}) next({...to,replace:true})这些是中断(也就是会再次调用beforeEach),直到执行到了next()才会停止中断

    大家可以看看这些全局前置守卫死循环的例子

    这些都是死循环,使用就出现Maximum call stack size exceeded

    死循环1
    1
    2
    3
    4
    5
    6
    7
    8
    router.beforeEach((to, from, next) => {
    console.log('beforeEach');
    if (true) {
    next('/');
    } else {
    next();
    }
    });
    死循环2
    1
    2
    3
    4
    5
    6
    7
    8
    router.beforeEach((to, from, next) => {
    var user = JSON.parse(sessionStorage.getItem('user'));
    if(user == null){
    next({ path: '/login' }); // 没有用户,就跳去登录
    } else {
    next();
    }
    });
    死循环3
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    router.beforeEach((to,from,next) =>{
    if (sessionStorage.getItem("token")) {
    if(to.path === "/login"){
    next({path:"/dashboard"})
    }
    else{
    alert("1")
    next()
    }
    }else{
    next({path: "/login"}) // 会再次执行前置导航守卫,由于路径变化
    }
    })
    文章作者: 梦洁
    文章链接: https://www.dreamlove.top/fce322fe.html
    版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/google.txt b/google.txt new file mode 100644 index 000000000..2093c2875 --- /dev/null +++ b/google.txt @@ -0,0 +1,10 @@ +https://www.dreamlove.top/bbed5cd2.html +https://www.dreamlove.top/91ff580f.html +https://www.dreamlove.top/50575b3e.html +https://www.dreamlove.top/6ace453b.html +https://www.dreamlove.top/f4cb1979.html +https://www.dreamlove.top/84b2339c.html +https://www.dreamlove.top/30ebed6d.html +https://www.dreamlove.top/a23f7333.html +https://www.dreamlove.top/7ec112ae.html +https://www.dreamlove.top/5419262f.html \ No newline at end of file diff --git a/hidden/index.html b/hidden/index.html new file mode 100644 index 000000000..79ef8fa51 --- /dev/null +++ b/hidden/index.html @@ -0,0 +1 @@ +隐藏的小地方 | 梦洁小站-属于你我的小天地

    咦,你怎么发现我的~


    评论
    \ No newline at end of file diff --git a/img/404.jpg b/img/404.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f1cf9c7c7ec4b5c0be7e49fea02f119f55211282 GIT binary patch literal 12420 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 literal 0 HcmV?d00001 diff --git a/img/favicon.png b/img/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..862ebe85838eb999f7c19e80c498ebf286110f7e GIT binary patch literal 323 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 literal 0 HcmV?d00001 diff --git a/index.html b/index.html new file mode 100644 index 000000000..34a0dd75e --- /dev/null +++ b/index.html @@ -0,0 +1 @@ +梦洁小站-属于你我的小天地 - 前端,JavaScript,nodejs,es5,es6,vue
    大家好我是夯大力,这是我的影视,大力金刚等系列
    独角数卡,打开商品列表出现Undefined variable form的解决办法
    前端常用npm库大全-vue,react,通用(持续更新)
    基于vue-pdf-embed的二开PDF预览的通用组件
    x64dbg反汇编技术入门学习笔记
    linux普通服务器和NAT服务器下载transmission制作种子
    mouseinc-smartUp Gestures被禁用后的替代品
    docker环境下的verdaccio设置权限并配置域名
    NAT服务器搭建rustdesk服务-docker方式
    \ No newline at end of file diff --git a/js/main.js b/js/main.js new file mode 100644 index 000000000..5969a19a7 --- /dev/null +++ b/js/main.js @@ -0,0 +1,3 @@ +document.addEventListener("DOMContentLoaded",function(){let c,i;let e=false;const t=t=>{const e=t=>{return Array.from(t).reduce((t,e)=>t+e.offsetWidth,0)};if(t){const o=e(document.querySelector("#blog-info > a").children);const s=e(document.getElementById("menus").children);c=o+s;i=document.getElementById("nav")}const n=window.innerWidth<=768||c>i.offsetWidth-120;i.classList.toggle("hide-menu",n)};const n=()=>{t(true);i.classList.add("show")};const o={open:()=>{btf.sidebarPaddingR();document.body.style.overflow="hidden";btf.animateIn(document.getElementById("menu-mask"),"to_show 0.5s");document.getElementById("sidebar-menus").classList.add("open");e=true},close:()=>{const t=document.body;t.style.overflow="";t.style.paddingRight="";btf.animateOut(document.getElementById("menu-mask"),"to_hide 0.5s");document.getElementById("sidebar-menus").classList.remove("open");e=false}};const s=()=>{const t=()=>{btf.scrollToDest(document.getElementById("content-inner").offsetTop,300)};const e=document.getElementById("scroll-down");e&&btf.addEventListenerPjax(e,"click",t)};const a=()=>{const t=GLOBAL_CONFIG.highlight;if(!t)return;const{highlightCopy:e,highlightLang:o,highlightHeightLimit:i,plugin:n}=t;const s=GLOBAL_CONFIG_SITE.isHighlightShrink;const a=e||o||s!==undefined;const c=n==="highlight.js"?document.querySelectorAll("figure.highlight"):document.querySelectorAll('pre[class*="language-"]');if(!((a||i)&&c.length))return;const l=n==="prismjs";const r=s===true?"closed":"";const d=s!==undefined?'':"";const u=e?'
    ':"";const f=(t,e)=>{if(GLOBAL_CONFIG.Snackbar!==undefined){btf.snackbarShow(e)}else{const n=t.previousElementSibling;n.textContent=e;n.style.opacity=1;setTimeout(()=>{n.style.opacity=0},800)}};const m=t=>{if(document.queryCommandSupported&&document.queryCommandSupported("copy")){document.execCommand("copy");f(t,GLOBAL_CONFIG.copy.success)}else{f(t,GLOBAL_CONFIG.copy.noSupport)}};const g=t=>{const e=t.parentNode;e.classList.add("copy-true");const n=window.getSelection();const o=document.createRange();const s=l?"pre code":"table .code pre";o.selectNodeContents(e.querySelectorAll(`${s}`)[0]);n.removeAllRanges();n.addRange(o);m(t.lastChild);n.removeAllRanges();e.classList.remove("copy-true")};const h=t=>{t.classList.toggle("closed")};const L=function(t){const e=t.target.classList;if(e.contains("expand"))h(this);else if(e.contains("copy-button"))g(this)};const p=function(){this.classList.toggle("expand-done")};const b=(t,e,n)=>{const o=document.createDocumentFragment();if(a){const s=document.createElement("div");s.className=`highlight-tools ${r}`;s.innerHTML=d+t+u;btf.addEventListenerPjax(s,"click",L);o.appendChild(s)}if(i&&e.offsetHeight>i+30){const c=document.createElement("div");c.className="code-expand-btn";c.innerHTML='';btf.addEventListenerPjax(c,"click",p);o.appendChild(c)}if(n==="hl"){e.insertBefore(o,e.firstChild)}else{e.parentNode.insertBefore(o,e)}};if(l){c.forEach(t=>{if(o){const e=t.getAttribute("data-language")||"Code";const n=`
    ${e}
    `;btf.wrap(t,"figure",{class:"highlight"});b(n,t)}else{btf.wrap(t,"figure",{class:"highlight"});b("",t)}})}else{c.forEach(e=>{if(o){let t=e.getAttribute("class").split(" ")[1];if(t==="plain"||t===undefined)t="Code";const n=`
    ${t}
    `;b(n,e,"hl")}else{b("",e,"hl")}})}};const l=()=>{document.querySelectorAll("#article-container img").forEach(t=>{const e=t.title||t.alt;if(!e)return;const n=document.createElement("div");n.className="img-alt is-center";n.textContent=e;t.insertAdjacentElement("afterend",n)})};const r=()=>{btf.loadLightbox(document.querySelectorAll("#article-container img:not(.no-lightbox)"))};const d=async t=>{const e=await fetch(t);return await e.json()};const u=(s,l,c=false,t)=>{const r=l.length;const i=new InfiniteGrid.JustifiedInfiniteGrid(s,{gap:5,isConstantSize:true,sizeRange:[150,600],useResizeObserver:true,observeChildren:true,useTransform:true});if(t){btf.addGlobalFn("igOfTabs",()=>{i.destroy()},false,t)}const d=t=>t.replace(/"/g,""");const n=(t,n)=>{const o=[];const s=(t-1)*n;for(let e=0;e=r){break}const t=l[c];const i=t.alt?`alt="${d(t.alt)}"`:"";const a=t.title?`title="${d(t.title)}"`:"";o.push(`
    + +
    `)}return o};const o=GLOBAL_CONFIG.infinitegrid.buttonText;const a=e=>{const t=document.createElement("button");t.textContent=o;const n=t=>{t.target.removeEventListener("click",n);t.target.remove();btf.setLoading.add(e);u(i.getGroups().length+1,10)};t.addEventListener("click",n);e.insertAdjacentElement("afterend",t)};const u=(t,e)=>{i.append(n(t,e),t)};const f=Math.ceil(r/10);const m=t=>{const{updated:e,isResize:n,mounted:o}=t;if(!e.length||!o.length||n){return}btf.loadLightbox(s.querySelectorAll("img:not(.medium-zoom-image)"));if(i.getGroups().length===f){btf.setLoading.remove(s);i.off("renderComplete",m);return}if(c){btf.setLoading.remove(s);a(s)}};const g=btf.debounce(t=>{const e=(+t.groupKey||0)+1;u(e,10);if(e===f){i.off("requestAppend",g)}},300);btf.setLoading.add(s);i.on("renderComplete",m);if(c){u(1,10)}else{i.on("requestAppend",g);i.renderItems()}btf.addGlobalFn("justifiedGallery",()=>{i.destroy()})};const f=async(i,a=false)=>{const t=async()=>{for(const t of i){if(btf.isHidden(t))continue;if(a&&t.classList.contains("loaded")){t.querySelector(".gallery-items").innerHTML="";const s=t.querySelector(":scope > button");const c=t.querySelector(":scope > .loading-container");s&&s.remove();c&&c.remove()}const e=t.getAttribute("data-button")==="true";const n=t.firstElementChild.textContent;t.classList.add("loaded");const o=t.getAttribute("data-type")==="url"?await d(n):JSON.parse(n);u(t.lastElementChild,o,e,a)}};if(typeof InfiniteGrid==="function"){t()}else{await getScript(`${GLOBAL_CONFIG.infinitegrid.js}`);t()}};const m=t=>{const e=btf.getScrollPercent(t,document.body);const n=document.getElementById("go-up");if(e<95){n.classList.add("show-percent");n.querySelector(".scroll-percent").textContent=e}else{n.classList.remove("show-percent")}};const g=()=>{const n=document.getElementById("rightside");const o=window.innerHeight+56;let s=0;const c=document.getElementById("page-header");const i=typeof chatBtn!=="undefined";const a=GLOBAL_CONFIG.percent.rightside;if(document.body.scrollHeight<=o){n.classList.add("rightside-show");return}const l=t=>{const e=t>s;s=t;return e};let r="";const t=btf.throttle(()=>{const t=window.scrollY||document.documentElement.scrollTop;const e=l(t);if(t>56){if(r===""){c.classList.add("nav-fixed");n.classList.add("rightside-show")}if(e){if(r!=="down"){c.classList.remove("nav-visible");i&&window.chatBtn.hide();r="down"}}else{if(r!=="up"){c.classList.add("nav-visible");i&&window.chatBtn.show();r="up"}}}else{r="";if(t===0){c.classList.remove("nav-fixed","nav-visible")}n.classList.remove("rightside-show")}a&&m(t);if(document.body.scrollHeight<=o){n.classList.add("rightside-show")}},300);btf.addEventListenerPjax(window,"scroll",t,{passive:true})};const h=()=>{const n=GLOBAL_CONFIG_SITE.isToc;const t=GLOBAL_CONFIG.isAnchor;const e=document.getElementById("article-container");if(!(e&&(n||t)))return;let i,a,l,o,r;if(n){const f=document.getElementById("card-toc");a=f.querySelector(".toc-content");i=a.querySelectorAll(".toc-link");o=f.querySelector(".toc-percentage");r=a.classList.contains("is-expand");const m=t=>{const e=t.target.closest(".toc-link");if(!e)return;t.preventDefault();btf.scrollToDest(btf.getEleTop(document.getElementById(decodeURI(e.getAttribute("href")).replace("#",""))),300);if(window.innerWidth<900){f.classList.remove("open")}};btf.addEventListenerPjax(a,"click",m);l=t=>{const e=t.getBoundingClientRect().top;const n=a.scrollTop;if(e>document.documentElement.clientHeight-100){a.scrollTop=n+150}if(e<100){a.scrollTop=n-150}}}const d=e.querySelectorAll("h1,h2,h3,h4,h5,h6");let u="";const s=o=>{if(o===0){return false}let s="";let c="";d.forEach((t,e)=>{if(o>btf.getEleTop(t)-80){const n=t.id;s=n?"#"+encodeURI(n):"";c=e}});if(u===c)return;if(t)btf.updateAnchor(s);u=c;if(n){a.querySelectorAll(".active").forEach(t=>{t.classList.remove("active")});if(s===""){return}const e=i[c];e.classList.add("active");setTimeout(()=>{l(e)},0);if(r)return;let t=e.parentNode;for(;!t.matches(".toc");t=t.parentNode){if(t.matches("li"))t.classList.add("active")}}};const c=btf.throttle(()=>{const t=window.scrollY||document.documentElement.scrollTop;if(n&&GLOBAL_CONFIG.percent.toc){o.textContent=btf.getScrollPercent(t,e)}s(t)},100);btf.addEventListenerPjax(window,"scroll",c,{passive:true})};const L=n=>{const t=window.globalFn||{};const o=t.themeChange||{};if(!o){return}Object.keys(o).forEach(t=>{const e=o[t];if(["disqus","disqusjs"].includes(t)){setTimeout(()=>e(n),300)}else{e(n)}})};const p={readmode:()=>{const t=document.body;t.classList.add("read-mode");const e=document.createElement("button");e.type="button";e.className="fas fa-sign-out-alt exit-readmode";t.appendChild(e);const n=()=>{t.classList.remove("read-mode");e.remove();e.removeEventListener("click",n)};e.addEventListener("click",n)},darkmode:()=>{const t=document.documentElement.getAttribute("data-theme")==="dark"?"light":"dark";if(t==="dark"){activateDarkMode();GLOBAL_CONFIG.Snackbar!==undefined&&btf.snackbarShow(GLOBAL_CONFIG.Snackbar.day_to_night)}else{activateLightMode();GLOBAL_CONFIG.Snackbar!==undefined&&btf.snackbarShow(GLOBAL_CONFIG.Snackbar.night_to_day)}saveToLocal.set("theme",t,2);L(t)},"rightside-config":t=>{const e=t.firstElementChild;if(e.classList.contains("show")){e.classList.add("status");setTimeout(()=>{e.classList.remove("status")},300)}e.classList.toggle("show")},"go-up":()=>{btf.scrollToDest(0,500)},"hide-aside-btn":()=>{const t=document.documentElement.classList;const e=t.contains("hide-aside")?"show":"hide";saveToLocal.set("aside-status",e,2);t.toggle("hide-aside")},"mobile-toc-button":t=>{const e=document.getElementById("card-toc");e.style.transition="transform 0.3s ease-in-out";e.classList.toggle("open");e.addEventListener("transitionend",()=>{e.style.transition=""},{once:true})},"chat-btn":()=>{window.chatBtnFn()},translateLink:()=>{window.translateFn.translatePage()}};document.getElementById("rightside").addEventListener("click",function(t){const e=t.target.closest("[id]");if(e&&p[e.id]){p[e.id](this)}});const b=()=>{const t=t=>{const e=t.target.closest(".site-page.group");if(!e)return;e.classList.toggle("hide")};document.querySelector("#sidebar-menus .menus_items").addEventListener("click",t)};const y=()=>{const t=()=>{o.open()};btf.addEventListenerPjax(document.getElementById("toggle-menu"),"click",t)};const v=()=>{const{limitCount:o,languages:s}=GLOBAL_CONFIG.copyright;const t=t=>{t.preventDefault();const e=window.getSelection(0).toString();let n=e;if(e.length>o){n=`${e}\n\n\n${s.author}\n${s.link}${window.location.href}\n${s.source}\n${s.info}`}if(t.clipboardData){return t.clipboardData.setData("text",n)}else{return window.clipboardData.setData("text",n)}};document.body.addEventListener("copy",t)};const E=()=>{const t=document.getElementById("runtimeshow");if(t){const e=t.getAttribute("data-publishDate");t.textContent=`${btf.diffDate(e)} ${GLOBAL_CONFIG.runtime}`}};const w=()=>{const t=document.getElementById("last-push-date");if(t){const e=t.getAttribute("data-lastPushDate");t.textContent=btf.diffDate(e,true)}};const G=()=>{const t=document.querySelectorAll("#article-container table");if(!t.length)return;t.forEach(t=>{if(!t.closest(".highlight")){btf.wrap(t,"div",{class:"table-wrap"})}})};const O=()=>{const t=document.querySelectorAll("#article-container .hide-button");if(!t.length)return;const e=function(t){const e=this;e.classList.add("open");const n=e.nextElementSibling.querySelectorAll(".gallery-container");n.length&&f(n)};t.forEach(t=>{t.addEventListener("click",e,{once:true})})};const C=()=>{const t=document.querySelectorAll("#article-container .tabs");if(!t.length)return;const i=(t,e)=>{Array.from(t).forEach(t=>{t.classList.remove("active");if(t===e||t.id===e){t.classList.add("active")}})};const n=(t,c)=>{const e=function(t){const e=t.target.closest("button");if(e.classList.contains("active"))return;i(this.children,e);this.classList.remove("no-default");const n=e.getAttribute("data-href");const o=this.nextElementSibling;i(o.children,n);if(c){btf.removeGlobalFnEvent("igOfTabs",this);const s=o.querySelectorAll(`:scope > #${n} .gallery-container`);s.length&&f(s,this)}};btf.addEventListenerPjax(t.firstElementChild,"click",e)};const o=n=>{const t=t=>{const e=t.target.closest("button");if(!e)return;btf.scrollToDest(btf.getEleTop(n),300)};btf.addEventListenerPjax(n.lastElementChild,"click",t)};t.forEach(t=>{const e=!!t.querySelectorAll(".gallery-container");n(t,e);o(t)})};const A=()=>{const t=document.querySelector("#aside-cat-list.expandBtn");if(!t)return;const e=t=>{const e=t.target;if(e.nodeName==="I"){t.preventDefault();e.parentNode.classList.toggle("expand")}};btf.addEventListenerPjax(t,"click",e,true)};const I=()=>{const t=document.getElementById("switch-btn");if(!t)return;let e=false;const n=document.getElementById("post-comment");const o=()=>{n.classList.toggle("move");if(!e&&typeof loadOtherComment==="function"){e=true;loadOtherComment()}};btf.addEventListenerPjax(t,"click",o)};const B=()=>{const{limitDay:t,messagePrev:e,messageNext:n,position:o}=GLOBAL_CONFIG.noticeOutdate;const s=btf.diffDate(GLOBAL_CONFIG_SITE.postUpdate);if(s>=t){const c=document.createElement("div");c.className="post-outdate-notice";c.textContent=`${e} ${s} ${n}`;const i=document.getElementById("article-container");if(o==="top"){i.insertBefore(c,i.firstChild)}else{i.appendChild(c)}}};const S=()=>{window.lazyLoadInstance=new LazyLoad({elements_selector:"img",threshold:0,data_src:"lazy-src"})};const k=function(t){t.forEach(t=>{const e=t.getAttribute("datetime");t.textContent=btf.diffDate(e,true);t.style.display="inline"})};const N=function(){window.addEventListener("resize",()=>{t(false);e&&btf.isHidden(document.getElementById("toggle-menu"))&&o.close()});document.getElementById("menu-mask").addEventListener("click",t=>{o.close()});b();GLOBAL_CONFIG.islazyload&&S();GLOBAL_CONFIG.copyright!==undefined&&v();if(GLOBAL_CONFIG.autoDarkmode){window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",t=>{if(saveToLocal.get("theme")!==undefined)return;t.matches?L("dark"):L("light")})}};window.refreshFn=function(){n();if(GLOBAL_CONFIG_SITE.isPost){GLOBAL_CONFIG.noticeOutdate!==undefined&&B();GLOBAL_CONFIG.relativeDate.post&&k(document.querySelectorAll("#post-meta time"))}else{GLOBAL_CONFIG.relativeDate.homepage&&k(document.querySelectorAll("#recent-posts time"));GLOBAL_CONFIG.runtime&&E();w();A()}h();GLOBAL_CONFIG_SITE.isHome&&s();a();GLOBAL_CONFIG.isPhotoFigcaption&&l();g();btf.removeGlobalFnEvent("justifiedGallery");const t=document.querySelectorAll("#article-container .gallery-container");t.length&&f(t);r();G();O();C();I();y()};refreshFn();N()}); \ No newline at end of file diff --git a/js/search/algolia.js b/js/search/algolia.js new file mode 100644 index 000000000..644dd1400 --- /dev/null +++ b/js/search/algolia.js @@ -0,0 +1,5 @@ +window.addEventListener("load",()=>{const t=document.getElementById("search-mask");const a=document.querySelector("#algolia-search .search-dialog");const e=()=>{const e=document.body.style;e.width="100%";e.overflow="hidden";btf.animateIn(t,"to_show 0.5s");btf.animateIn(a,"titleScale 0.5s");setTimeout(()=>{document.querySelector("#algolia-search .ais-SearchBox-input").focus()},100);document.addEventListener("keydown",function e(t){if(t.code==="Escape"){n();document.removeEventListener("keydown",e)}});i();window.addEventListener("resize",i)};const n=()=>{const e=document.body.style;e.width="";e.overflow="";btf.animateOut(a,"search_close .5s");btf.animateOut(t,"to_hide 0.5s");window.removeEventListener("resize",i)};const i=()=>{if(window.innerWidth<768){a.style.setProperty("--search-height",window.innerHeight+"px")}};const s=()=>{btf.addEventListenerPjax(document.querySelector("#search-button > .search"),"click",e)};const o=()=>{t.addEventListener("click",n);document.querySelector("#algolia-search .search-close-button").addEventListener("click",n)};const l=e=>{if(e==="")return"";const t=e.indexOf("");let a=t-30;let n=t+120;let i="";let s="";if(a<=0){a=0;n=140}else{i="..."}if(n>e.length){n=e.length}else{s="..."}const o=i+e.substring(a,n)+s;return o};const c=GLOBAL_CONFIG.algolia;const r=c.appId&&c.apiKey&&c.indexName;if(!r){return console.error("Algolia setting is invalid!")}const d=instantsearch({indexName:c.indexName,searchClient:algoliasearch(c.appId,c.apiKey),searchFunction(e){e.state.query&&e.search()}});const h=instantsearch.widgets.configure({hitsPerPage:5});const g=instantsearch.widgets.searchBox({container:"#algolia-search-input",showReset:false,showSubmit:false,placeholder:GLOBAL_CONFIG.algolia.languages.input_placeholder,showLoadingIndicator:true});const u=instantsearch.widgets.hits({container:"#algolia-hits",templates:{item(e){const t=e.permalink?e.permalink:GLOBAL_CONFIG.root+e.path;const a=e._highlightResult;const n=a.contentStripTruncate?l(a.contentStripTruncate.value):a.contentStrip?l(a.contentStrip.value):a.content?l(a.content.value):"";return` + + ${a.title.value||"no-title"} +

    ${n}

    +
    `},empty:function(e){return'
    '+GLOBAL_CONFIG.algolia.languages.hits_empty.replace(/\$\{query}/,e.query)+"
    "}}});const p=instantsearch.widgets.stats({container:"#algolia-info > .algolia-stats",templates:{text:function(e){const t=GLOBAL_CONFIG.algolia.languages.hits_stats.replace(/\$\{hits}/,e.nbHits).replace(/\$\{time}/,e.processingTimeMS);return`
    ${t}`}}});const m=instantsearch.widgets.poweredBy({container:"#algolia-info > .algolia-poweredBy"});const f=instantsearch.widgets.pagination({container:"#algolia-pagination",totalPages:5,templates:{first:'',last:'',previous:'',next:''}});d.addWidgets([h,g,u,p,m,f]);d.start();s();o();window.addEventListener("pjax:complete",()=>{!btf.isHidden(t)&&n();s()});window.pjax&&d.on("render",()=>{window.pjax.refresh(document.getElementById("algolia-hits"))})}); \ No newline at end of file diff --git a/js/search/local-search.js b/js/search/local-search.js new file mode 100644 index 000000000..d503a2eee --- /dev/null +++ b/js/search/local-search.js @@ -0,0 +1 @@ +class LocalSearch{constructor({path:t="",unescape:e=false,top_n_per_article:n=1}){this.path=t;this.unescape=e;this.top_n_per_article=n;this.isfetched=false;this.datas=null}getIndexByWord(t,i,r=false){const c=[];const a=new Set;if(!r){i=i.toLowerCase()}t.forEach(t=>{if(this.unescape){const o=document.createElement("div");o.innerText=t;t=o.innerHTML}const e=t.length;if(e===0)return;let n=0;let s=-1;if(!r){t=t.toLowerCase()}while((s=i.indexOf(t,n))>-1){c.push({position:s,word:t});a.add(t);n=s+e}});c.sort((t,e)=>{if(t.position!==e.position){return t.position-e.position}return e.word.length-t.word.length});return[c,a]}mergeIntoSlice(t,e,n){let s=n[0];let{position:o,word:i}=s;const r=[];const c=new Set;while(o+i.length<=e&&n.length!==0){c.add(i);r.push({position:o,length:i.length});const a=o+i.length;n.shift();while(n.length!==0){s=n[0];o=s.position;i=s.word;if(a>o){n.shift()}else{break}}}return{hits:r,start:t,end:e,count:c.size}}highlightKeyword(t,e){let n="";let s=e.start;for(const{position:o,length:i}of e.hits){n+=t.substring(s,o);s=o+i;n+=`${t.substr(o,i)}`}n+=t.substring(s,e.end);return n}getResultItems(w){const y=[];this.datas.forEach(({title:t,content:e,url:n})=>{const[s,o]=this.getIndexByWord(w,t);const[i,r]=this.getIndexByWord(w,e);const c=new Set([...o,...r]).size;const a=s.length+i.length;if(a===0)return;const l=[];if(s.length!==0){l.push(this.mergeIntoSlice(0,t.length,s))}let h=[];while(i.length!==0){const g=i[0];const{position:p}=g;const m=Math.max(0,p-20);const f=Math.min(e.length,p+100);h.push(this.mergeIntoSlice(m,f,i))}h.sort((t,e)=>{if(t.count!==e.count){return e.count-t.count}else if(t.hits.length!==e.hits.length){return e.hits.length-t.hits.length}return t.start-e.start});const d=parseInt(this.top_n_per_article,10);if(d>=0){h=h.slice(0,d)}let u="";n=new URL(n,location.origin);n.searchParams.append("highlight",w.join(" "));if(l.length!==0){u+=`
    ${this.highlightKeyword(t,l[0])}`}else{u+=`";y.push({item:u,id:y.length,hitCount:a,includedCount:c})});return y}fetchData(){const e=!this.path.endsWith("json");fetch(this.path).then(t=>t.text()).then(t=>{this.isfetched=true;this.datas=e?[...(new DOMParser).parseFromString(t,"text/xml").querySelectorAll("entry")].map(t=>({title:t.querySelector("title").textContent,content:t.querySelector("content").textContent,url:t.querySelector("url").textContent})):JSON.parse(t);this.datas=this.datas.filter(t=>t.title).map(t=>{t.title=t.title.trim();t.content=t.content?t.content.trim().replace(/<[^>]+>/g,""):"";t.url=decodeURIComponent(t.url).replace(/\/{2,}/g,"/");return t});window.dispatchEvent(new Event("search:loaded"))})}highlightText(e,t,n){const s=e.nodeValue;let o=t.start;const i=[];for(const{position:r,length:c}of t.hits){const a=document.createTextNode(s.substring(o,r));o=r+c;const l=document.createElement("mark");l.className=n;l.appendChild(document.createTextNode(s.substr(r,c)));i.push(a,l)}e.nodeValue=s.substring(o,t.end);i.forEach(t=>{e.parentNode.insertBefore(t,e)})}highlightSearchWords(t){const e=new URL(location.href).searchParams.get("highlight");const s=e?e.split(" "):[];if(!s.length||!t)return;const n=document.createTreeWalker(t,NodeFilter.SHOW_TEXT,null);const o=[];while(n.nextNode()){if(!n.currentNode.parentNode.matches("button, select, textarea, .mermaid"))o.push(n.currentNode)}o.forEach(t=>{const[e]=this.getIndexByWord(s,t.nodeValue);if(!e.length)return;const n=this.mergeIntoSlice(0,t.nodeValue.length,e);this.highlightText(t,n,"search-keyword")})}}window.addEventListener("load",()=>{const{path:t,top_n_per_article:e,unescape:n,languages:r}=GLOBAL_CONFIG.localSearch;const c=new LocalSearch({path:t,top_n_per_article:e,unescape:n});const a=document.querySelector("#local-search-input input");const l=document.getElementById("local-search-stats-wrap");const h=document.getElementById("loading-status");const d=!t.endsWith("json");const s=()=>{if(!c.isfetched)return;let t=a.value.trim().toLowerCase();d&&(t=t.replace(//g,">"));if(t!=="")h.innerHTML='';const e=t.split(/[-\s]+/);const n=document.getElementById("local-search-results");let s=[];if(t.length>0){s=c.getResultItems(e)}if(e.length===1&&e[0]===""){n.textContent="";l.textContent=""}else if(s.length===0){n.textContent="";const o=document.createElement("div");o.className="search-result-stats";o.textContent=r.hits_empty.replace(/\$\{query}/,t);l.innerHTML=o.outerHTML}else{s.sort((t,e)=>{if(t.includedCount!==e.includedCount){return e.includedCount-t.includedCount}else if(t.hitCount!==e.hitCount){return e.hitCount-t.hitCount}return e.id-t.id});const i=r.hits_stats.replace(/\$\{hits}/,s.length);n.innerHTML=`
    ${s.map(t=>t.item).join("")}
    `;l.innerHTML=`
    ${i}
    `;window.pjax&&window.pjax.refresh(n)}h.textContent=""};let o=false;const i=document.getElementById("search-mask");const u=document.querySelector("#local-search .search-dialog");const g=()=>{if(window.innerWidth<768){u.style.setProperty("--search-height",window.innerHeight+"px")}};const p=()=>{const t=document.body.style;t.width="100%";t.overflow="hidden";btf.animateIn(i,"to_show 0.5s");btf.animateIn(u,"titleScale 0.5s");setTimeout(()=>{a.focus()},300);if(!o){!c.isfetched&&c.fetchData();a.addEventListener("input",s);o=true}document.addEventListener("keydown",function t(e){if(e.code==="Escape"){m();document.removeEventListener("keydown",t)}});g();window.addEventListener("resize",g)};const m=()=>{const t=document.body.style;t.width="";t.overflow="";btf.animateOut(u,"search_close .5s");btf.animateOut(i,"to_hide 0.5s");window.removeEventListener("resize",g)};const f=()=>{btf.addEventListenerPjax(document.querySelector("#search-button > .search"),"click",p)};const w=()=>{document.querySelector("#local-search .search-close-button").addEventListener("click",m);i.addEventListener("click",m);if(GLOBAL_CONFIG.localSearch.preload){c.fetchData()}c.highlightSearchWords(document.getElementById("article-container"))};window.addEventListener("search:loaded",()=>{const t=document.getElementById("loading-database");t.nextElementSibling.style.display="block";t.remove()});f();w();window.addEventListener("pjax:complete",()=>{!btf.isHidden(i)&&m();c.highlightSearchWords(document.getElementById("article-container"));f()})}); \ No newline at end of file diff --git a/js/tw_cn.js b/js/tw_cn.js new file mode 100644 index 000000000..de9745ce5 --- /dev/null +++ b/js/tw_cn.js @@ -0,0 +1 @@ +document.addEventListener("DOMContentLoaded",function(){const{defaultEncoding:t,translateDelay:e,msgToTraditionalChinese:n,msgToSimplifiedChinese:l}=GLOBAL_CONFIG.translate;const a=GLOBAL_CONFIG.Snackbar;let o=t;const i="translate-chn-cht";let c=saveToLocal.get(i)===undefined?t:Number(saveToLocal.get("translate-chn-cht"));let r;const d=a!==undefined;function f(){document.documentElement.lang=c===1?"zh-TW":"zh-CN"}function s(t){if(t===""||t==null)return"";if(o===1&&c===2)return A(t);else if(o===2&&c===1){return p(t)}else return t}function u(t){let e;if(typeof t==="object")e=t.childNodes;else e=document.body.childNodes;for(let t=0;t0||n===r){continue}if(n.title!==""&&n.title!=null){n.title=s(n.title)}if(n.alt!==""&&n.alt!=null)n.alt=s(n.alt);if(n.placeholder!==""&&n.placeholder!=null){n.placeholder=s(n.placeholder)}if(n.tagName==="INPUT"&&n.value!==""&&n.type!=="text"&&n.type!=="hidden"){n.value=s(n.value)}if(n.nodeType===3)n.data=s(n.data);else u(n)}}function h(){if(c===1){o=1;c=2;r.textContent=n;d&&btf.snackbarShow(a.cht_to_chs)}else if(c===2){o=2;c=1;r.textContent=l;d&&btf.snackbarShow(a.chs_to_cht)}saveToLocal.set(i,c,2);f();u()}function m(){return"万与丑专业丛东丝丢两严丧个丬丰临为丽举么义乌乐乔习乡书买乱争于亏云亘亚产亩亲亵亸亿仅从仑仓仪们价众优伙会伛伞伟传伤伥伦伧伪伫体余佣佥侠侣侥侦侧侨侩侪侬俣俦俨俩俪俭债倾偬偻偾偿傥傧储傩儿兑兖党兰关兴兹养兽冁内冈册写军农冢冯冲决况冻净凄凉凌减凑凛几凤凫凭凯击凼凿刍划刘则刚创删别刬刭刽刿剀剂剐剑剥剧劝办务劢动励劲劳势勋勐勚匀匦匮区医华协单卖卢卤卧卫却卺厂厅历厉压厌厍厕厢厣厦厨厩厮县参叆叇双发变叙叠叶号叹叽吁后吓吕吗吣吨听启吴呒呓呕呖呗员呙呛呜咏咔咙咛咝咤咴咸哌响哑哒哓哔哕哗哙哜哝哟唛唝唠唡唢唣唤唿啧啬啭啮啰啴啸喷喽喾嗫呵嗳嘘嘤嘱噜噼嚣嚯团园囱围囵国图圆圣圹场坂坏块坚坛坜坝坞坟坠垄垅垆垒垦垧垩垫垭垯垱垲垴埘埙埚埝埯堑堕塆墙壮声壳壶壸处备复够头夸夹夺奁奂奋奖奥妆妇妈妩妪妫姗姜娄娅娆娇娈娱娲娴婳婴婵婶媪嫒嫔嫱嬷孙学孪宁宝实宠审宪宫宽宾寝对寻导寿将尔尘尧尴尸尽层屃屉届属屡屦屿岁岂岖岗岘岙岚岛岭岳岽岿峃峄峡峣峤峥峦崂崃崄崭嵘嵚嵛嵝嵴巅巩巯币帅师帏帐帘帜带帧帮帱帻帼幂幞干并广庄庆庐庑库应庙庞废庼廪开异弃张弥弪弯弹强归当录彟彦彻径徕御忆忏忧忾怀态怂怃怄怅怆怜总怼怿恋恳恶恸恹恺恻恼恽悦悫悬悭悯惊惧惨惩惫惬惭惮惯愍愠愤愦愿慑慭憷懑懒懔戆戋戏戗战戬户扎扑扦执扩扪扫扬扰抚抛抟抠抡抢护报担拟拢拣拥拦拧拨择挂挚挛挜挝挞挟挠挡挢挣挤挥挦捞损捡换捣据捻掳掴掷掸掺掼揸揽揿搀搁搂搅携摄摅摆摇摈摊撄撑撵撷撸撺擞攒敌敛数斋斓斗斩断无旧时旷旸昙昼昽显晋晒晓晔晕晖暂暧札术朴机杀杂权条来杨杩杰极构枞枢枣枥枧枨枪枫枭柜柠柽栀栅标栈栉栊栋栌栎栏树栖样栾桊桠桡桢档桤桥桦桧桨桩梦梼梾检棂椁椟椠椤椭楼榄榇榈榉槚槛槟槠横樯樱橥橱橹橼檐檩欢欤欧歼殁殇残殒殓殚殡殴毁毂毕毙毡毵氇气氢氩氲汇汉污汤汹沓沟没沣沤沥沦沧沨沩沪沵泞泪泶泷泸泺泻泼泽泾洁洒洼浃浅浆浇浈浉浊测浍济浏浐浑浒浓浔浕涂涌涛涝涞涟涠涡涢涣涤润涧涨涩淀渊渌渍渎渐渑渔渖渗温游湾湿溃溅溆溇滗滚滞滟滠满滢滤滥滦滨滩滪漤潆潇潋潍潜潴澜濑濒灏灭灯灵灾灿炀炉炖炜炝点炼炽烁烂烃烛烟烦烧烨烩烫烬热焕焖焘煅煳熘爱爷牍牦牵牺犊犟状犷犸犹狈狍狝狞独狭狮狯狰狱狲猃猎猕猡猪猫猬献獭玑玙玚玛玮环现玱玺珉珏珐珑珰珲琎琏琐琼瑶瑷璇璎瓒瓮瓯电画畅畲畴疖疗疟疠疡疬疮疯疱疴痈痉痒痖痨痪痫痴瘅瘆瘗瘘瘪瘫瘾瘿癞癣癫癯皑皱皲盏盐监盖盗盘眍眦眬着睁睐睑瞒瞩矫矶矾矿砀码砖砗砚砜砺砻砾础硁硅硕硖硗硙硚确硷碍碛碜碱碹磙礼祎祢祯祷祸禀禄禅离秃秆种积称秽秾稆税稣稳穑穷窃窍窑窜窝窥窦窭竖竞笃笋笔笕笺笼笾筑筚筛筜筝筹签简箓箦箧箨箩箪箫篑篓篮篱簖籁籴类籼粜粝粤粪粮糁糇紧絷纟纠纡红纣纤纥约级纨纩纪纫纬纭纮纯纰纱纲纳纴纵纶纷纸纹纺纻纼纽纾线绀绁绂练组绅细织终绉绊绋绌绍绎经绐绑绒结绔绕绖绗绘给绚绛络绝绞统绠绡绢绣绤绥绦继绨绩绪绫绬续绮绯绰绱绲绳维绵绶绷绸绹绺绻综绽绾绿缀缁缂缃缄缅缆缇缈缉缊缋缌缍缎缏缐缑缒缓缔缕编缗缘缙缚缛缜缝缞缟缠缡缢缣缤缥缦缧缨缩缪缫缬缭缮缯缰缱缲缳缴缵罂网罗罚罢罴羁羟羡翘翙翚耢耧耸耻聂聋职聍联聩聪肃肠肤肷肾肿胀胁胆胜胧胨胪胫胶脉脍脏脐脑脓脔脚脱脶脸腊腌腘腭腻腼腽腾膑臜舆舣舰舱舻艰艳艹艺节芈芗芜芦苁苇苈苋苌苍苎苏苘苹茎茏茑茔茕茧荆荐荙荚荛荜荞荟荠荡荣荤荥荦荧荨荩荪荫荬荭荮药莅莜莱莲莳莴莶获莸莹莺莼萚萝萤营萦萧萨葱蒇蒉蒋蒌蓝蓟蓠蓣蓥蓦蔷蔹蔺蔼蕲蕴薮藁藓虏虑虚虫虬虮虽虾虿蚀蚁蚂蚕蚝蚬蛊蛎蛏蛮蛰蛱蛲蛳蛴蜕蜗蜡蝇蝈蝉蝎蝼蝾螀螨蟏衅衔补衬衮袄袅袆袜袭袯装裆裈裢裣裤裥褛褴襁襕见观觃规觅视觇览觉觊觋觌觍觎觏觐觑觞触觯詟誉誊讠计订讣认讥讦讧讨让讪讫训议讯记讱讲讳讴讵讶讷许讹论讻讼讽设访诀证诂诃评诅识诇诈诉诊诋诌词诎诏诐译诒诓诔试诖诗诘诙诚诛诜话诞诟诠诡询诣诤该详诧诨诩诪诫诬语诮误诰诱诲诳说诵诶请诸诹诺读诼诽课诿谀谁谂调谄谅谆谇谈谊谋谌谍谎谏谐谑谒谓谔谕谖谗谘谙谚谛谜谝谞谟谠谡谢谣谤谥谦谧谨谩谪谫谬谭谮谯谰谱谲谳谴谵谶谷豮贝贞负贠贡财责贤败账货质贩贪贫贬购贮贯贰贱贲贳贴贵贶贷贸费贺贻贼贽贾贿赀赁赂赃资赅赆赇赈赉赊赋赌赍赎赏赐赑赒赓赔赕赖赗赘赙赚赛赜赝赞赟赠赡赢赣赪赵赶趋趱趸跃跄跖跞践跶跷跸跹跻踊踌踪踬踯蹑蹒蹰蹿躏躜躯车轧轨轩轪轫转轭轮软轰轱轲轳轴轵轶轷轸轹轺轻轼载轾轿辀辁辂较辄辅辆辇辈辉辊辋辌辍辎辏辐辑辒输辔辕辖辗辘辙辚辞辩辫边辽达迁过迈运还这进远违连迟迩迳迹适选逊递逦逻遗遥邓邝邬邮邹邺邻郁郄郏郐郑郓郦郧郸酝酦酱酽酾酿释里鉅鉴銮錾钆钇针钉钊钋钌钍钎钏钐钑钒钓钔钕钖钗钘钙钚钛钝钞钟钠钡钢钣钤钥钦钧钨钩钪钫钬钭钮钯钰钱钲钳钴钵钶钷钸钹钺钻钼钽钾钿铀铁铂铃铄铅铆铈铉铊铋铍铎铏铐铑铒铕铗铘铙铚铛铜铝铞铟铠铡铢铣铤铥铦铧铨铪铫铬铭铮铯铰铱铲铳铴铵银铷铸铹铺铻铼铽链铿销锁锂锃锄锅锆锇锈锉锊锋锌锍锎锏锐锑锒锓锔锕锖锗错锚锜锞锟锠锡锢锣锤锥锦锨锩锫锬锭键锯锰锱锲锳锴锵锶锷锸锹锺锻锼锽锾锿镀镁镂镃镆镇镈镉镊镌镍镎镏镐镑镒镕镖镗镙镚镛镜镝镞镟镠镡镢镣镤镥镦镧镨镩镪镫镬镭镮镯镰镱镲镳镴镶长门闩闪闫闬闭问闯闰闱闲闳间闵闶闷闸闹闺闻闼闽闾闿阀阁阂阃阄阅阆阇阈阉阊阋阌阍阎阏阐阑阒阓阔阕阖阗阘阙阚阛队阳阴阵阶际陆陇陈陉陕陧陨险随隐隶隽难雏雠雳雾霁霉霭靓静靥鞑鞒鞯鞴韦韧韨韩韪韫韬韵页顶顷顸项顺须顼顽顾顿颀颁颂颃预颅领颇颈颉颊颋颌颍颎颏颐频颒颓颔颕颖颗题颙颚颛颜额颞颟颠颡颢颣颤颥颦颧风飏飐飑飒飓飔飕飖飗飘飙飚飞飨餍饤饥饦饧饨饩饪饫饬饭饮饯饰饱饲饳饴饵饶饷饸饹饺饻饼饽饾饿馀馁馂馃馄馅馆馇馈馉馊馋馌馍馎馏馐馑馒馓馔馕马驭驮驯驰驱驲驳驴驵驶驷驸驹驺驻驼驽驾驿骀骁骂骃骄骅骆骇骈骉骊骋验骍骎骏骐骑骒骓骔骕骖骗骘骙骚骛骜骝骞骟骠骡骢骣骤骥骦骧髅髋髌鬓魇魉鱼鱽鱾鱿鲀鲁鲂鲄鲅鲆鲇鲈鲉鲊鲋鲌鲍鲎鲏鲐鲑鲒鲓鲔鲕鲖鲗鲘鲙鲚鲛鲜鲝鲞鲟鲠鲡鲢鲣鲤鲥鲦鲧鲨鲩鲪鲫鲬鲭鲮鲯鲰鲱鲲鲳鲴鲵鲶鲷鲸鲹鲺鲻鲼鲽鲾鲿鳀鳁鳂鳃鳄鳅鳆鳇鳈鳉鳊鳋鳌鳍鳎鳏鳐鳑鳒鳓鳔鳕鳖鳗鳘鳙鳛鳜鳝鳞鳟鳠鳡鳢鳣鸟鸠鸡鸢鸣鸤鸥鸦鸧鸨鸩鸪鸫鸬鸭鸮鸯鸰鸱鸲鸳鸴鸵鸶鸷鸸鸹鸺鸻鸼鸽鸾鸿鹀鹁鹂鹃鹄鹅鹆鹇鹈鹉鹊鹋鹌鹍鹎鹏鹐鹑鹒鹓鹔鹕鹖鹗鹘鹚鹛鹜鹝鹞鹟鹠鹡鹢鹣鹤鹥鹦鹧鹨鹩鹪鹫鹬鹭鹯鹰鹱鹲鹳鹴鹾麦麸黄黉黡黩黪黾龙历志制一台皋准复猛钟注范签"}function g(){return"萬與醜專業叢東絲丟兩嚴喪個爿豐臨為麗舉麼義烏樂喬習鄉書買亂爭於虧雲亙亞產畝親褻嚲億僅從侖倉儀們價眾優夥會傴傘偉傳傷倀倫傖偽佇體餘傭僉俠侶僥偵側僑儈儕儂俁儔儼倆儷儉債傾傯僂僨償儻儐儲儺兒兌兗黨蘭關興茲養獸囅內岡冊寫軍農塚馮衝決況凍淨淒涼淩減湊凜幾鳳鳧憑凱擊氹鑿芻劃劉則剛創刪別剗剄劊劌剴劑剮劍剝劇勸辦務勱動勵勁勞勢勳猛勩勻匭匱區醫華協單賣盧鹵臥衛卻巹廠廳曆厲壓厭厙廁廂厴廈廚廄廝縣參靉靆雙發變敘疊葉號歎嘰籲後嚇呂嗎唚噸聽啟吳嘸囈嘔嚦唄員咼嗆嗚詠哢嚨嚀噝吒噅鹹呱響啞噠嘵嗶噦嘩噲嚌噥喲嘜嗊嘮啢嗩唕喚呼嘖嗇囀齧囉嘽嘯噴嘍嚳囁嗬噯噓嚶囑嚕劈囂謔團園囪圍圇國圖圓聖壙場阪壞塊堅壇壢壩塢墳墜壟壟壚壘墾坰堊墊埡墶壋塏堖塒塤堝墊垵塹墮壪牆壯聲殼壺壼處備複夠頭誇夾奪奩奐奮獎奧妝婦媽嫵嫗媯姍薑婁婭嬈嬌孌娛媧嫻嫿嬰嬋嬸媼嬡嬪嬙嬤孫學孿寧寶實寵審憲宮寬賓寢對尋導壽將爾塵堯尷屍盡層屭屜屆屬屢屨嶼歲豈嶇崗峴嶴嵐島嶺嶽崠巋嶨嶧峽嶢嶠崢巒嶗崍嶮嶄嶸嶔崳嶁脊巔鞏巰幣帥師幃帳簾幟帶幀幫幬幘幗冪襆幹並廣莊慶廬廡庫應廟龐廢廎廩開異棄張彌弳彎彈強歸當錄彠彥徹徑徠禦憶懺憂愾懷態慫憮慪悵愴憐總懟懌戀懇惡慟懨愷惻惱惲悅愨懸慳憫驚懼慘懲憊愜慚憚慣湣慍憤憒願懾憖怵懣懶懍戇戔戲戧戰戩戶紮撲扡執擴捫掃揚擾撫拋摶摳掄搶護報擔擬攏揀擁攔擰撥擇掛摯攣掗撾撻挾撓擋撟掙擠揮撏撈損撿換搗據撚擄摑擲撣摻摜摣攬撳攙擱摟攪攜攝攄擺搖擯攤攖撐攆擷擼攛擻攢敵斂數齋斕鬥斬斷無舊時曠暘曇晝曨顯晉曬曉曄暈暉暫曖劄術樸機殺雜權條來楊榪傑極構樅樞棗櫪梘棖槍楓梟櫃檸檉梔柵標棧櫛櫳棟櫨櫟欄樹棲樣欒棬椏橈楨檔榿橋樺檜槳樁夢檮棶檢欞槨櫝槧欏橢樓欖櫬櫚櫸檟檻檳櫧橫檣櫻櫫櫥櫓櫞簷檁歡歟歐殲歿殤殘殞殮殫殯毆毀轂畢斃氈毿氌氣氫氬氳彙漢汙湯洶遝溝沒灃漚瀝淪滄渢溈滬濔濘淚澩瀧瀘濼瀉潑澤涇潔灑窪浹淺漿澆湞溮濁測澮濟瀏滻渾滸濃潯濜塗湧濤澇淶漣潿渦溳渙滌潤澗漲澀澱淵淥漬瀆漸澠漁瀋滲溫遊灣濕潰濺漵漊潷滾滯灩灄滿瀅濾濫灤濱灘澦濫瀠瀟瀲濰潛瀦瀾瀨瀕灝滅燈靈災燦煬爐燉煒熗點煉熾爍爛烴燭煙煩燒燁燴燙燼熱煥燜燾煆糊溜愛爺牘犛牽犧犢強狀獷獁猶狽麅獮獰獨狹獅獪猙獄猻獫獵獼玀豬貓蝟獻獺璣璵瑒瑪瑋環現瑲璽瑉玨琺瓏璫琿璡璉瑣瓊瑤璦璿瓔瓚甕甌電畫暢佘疇癤療瘧癘瘍鬁瘡瘋皰屙癰痙癢瘂癆瘓癇癡癉瘮瘞瘺癟癱癮癭癩癬癲臒皚皺皸盞鹽監蓋盜盤瞘眥矓著睜睞瞼瞞矚矯磯礬礦碭碼磚硨硯碸礪礱礫礎硜矽碩硤磽磑礄確鹼礙磧磣堿镟滾禮禕禰禎禱禍稟祿禪離禿稈種積稱穢穠穭稅穌穩穡窮竊竅窯竄窩窺竇窶豎競篤筍筆筧箋籠籩築篳篩簹箏籌簽簡籙簀篋籜籮簞簫簣簍籃籬籪籟糴類秈糶糲粵糞糧糝餱緊縶糸糾紆紅紂纖紇約級紈纊紀紉緯紜紘純紕紗綱納紝縱綸紛紙紋紡紵紖紐紓線紺絏紱練組紳細織終縐絆紼絀紹繹經紿綁絨結絝繞絰絎繪給絢絳絡絕絞統綆綃絹繡綌綏絛繼綈績緒綾緓續綺緋綽緔緄繩維綿綬繃綢綯綹綣綜綻綰綠綴緇緙緗緘緬纜緹緲緝縕繢緦綞緞緶線緱縋緩締縷編緡緣縉縛縟縝縫縗縞纏縭縊縑繽縹縵縲纓縮繆繅纈繚繕繒韁繾繰繯繳纘罌網羅罰罷羆羈羥羨翹翽翬耮耬聳恥聶聾職聹聯聵聰肅腸膚膁腎腫脹脅膽勝朧腖臚脛膠脈膾髒臍腦膿臠腳脫腡臉臘醃膕齶膩靦膃騰臏臢輿艤艦艙艫艱豔艸藝節羋薌蕪蘆蓯葦藶莧萇蒼苧蘇檾蘋莖蘢蔦塋煢繭荊薦薘莢蕘蓽蕎薈薺蕩榮葷滎犖熒蕁藎蓀蔭蕒葒葤藥蒞蓧萊蓮蒔萵薟獲蕕瑩鶯蓴蘀蘿螢營縈蕭薩蔥蕆蕢蔣蔞藍薊蘺蕷鎣驀薔蘞藺藹蘄蘊藪槁蘚虜慮虛蟲虯蟣雖蝦蠆蝕蟻螞蠶蠔蜆蠱蠣蟶蠻蟄蛺蟯螄蠐蛻蝸蠟蠅蟈蟬蠍螻蠑螿蟎蠨釁銜補襯袞襖嫋褘襪襲襏裝襠褌褳襝褲襇褸襤繈襴見觀覎規覓視覘覽覺覬覡覿覥覦覯覲覷觴觸觶讋譽謄訁計訂訃認譏訐訌討讓訕訖訓議訊記訒講諱謳詎訝訥許訛論訩訟諷設訪訣證詁訶評詛識詗詐訴診詆謅詞詘詔詖譯詒誆誄試詿詩詰詼誠誅詵話誕詬詮詭詢詣諍該詳詫諢詡譸誡誣語誚誤誥誘誨誑說誦誒請諸諏諾讀諑誹課諉諛誰諗調諂諒諄誶談誼謀諶諜謊諫諧謔謁謂諤諭諼讒諮諳諺諦謎諞諝謨讜謖謝謠謗諡謙謐謹謾謫譾謬譚譖譙讕譜譎讞譴譫讖穀豶貝貞負貟貢財責賢敗賬貨質販貪貧貶購貯貫貳賤賁貰貼貴貺貸貿費賀貽賊贄賈賄貲賃賂贓資賅贐賕賑賚賒賦賭齎贖賞賜贔賙賡賠賧賴賵贅賻賺賽賾贗讚贇贈贍贏贛赬趙趕趨趲躉躍蹌蹠躒踐躂蹺蹕躚躋踴躊蹤躓躑躡蹣躕躥躪躦軀車軋軌軒軑軔轉軛輪軟轟軲軻轤軸軹軼軤軫轢軺輕軾載輊轎輈輇輅較輒輔輛輦輩輝輥輞輬輟輜輳輻輯轀輸轡轅轄輾轆轍轔辭辯辮邊遼達遷過邁運還這進遠違連遲邇逕跡適選遜遞邐邏遺遙鄧鄺鄔郵鄒鄴鄰鬱郤郟鄶鄭鄆酈鄖鄲醞醱醬釅釃釀釋裏钜鑒鑾鏨釓釔針釘釗釙釕釷釺釧釤鈒釩釣鍆釹鍚釵鈃鈣鈈鈦鈍鈔鍾鈉鋇鋼鈑鈐鑰欽鈞鎢鉤鈧鈁鈥鈄鈕鈀鈺錢鉦鉗鈷缽鈳鉕鈽鈸鉞鑽鉬鉭鉀鈿鈾鐵鉑鈴鑠鉛鉚鈰鉉鉈鉍鈹鐸鉶銬銠鉺銪鋏鋣鐃銍鐺銅鋁銱銦鎧鍘銖銑鋌銩銛鏵銓鉿銚鉻銘錚銫鉸銥鏟銃鐋銨銀銣鑄鐒鋪鋙錸鋱鏈鏗銷鎖鋰鋥鋤鍋鋯鋨鏽銼鋝鋒鋅鋶鐦鐧銳銻鋃鋟鋦錒錆鍺錯錨錡錁錕錩錫錮鑼錘錐錦鍁錈錇錟錠鍵鋸錳錙鍥鍈鍇鏘鍶鍔鍤鍬鍾鍛鎪鍠鍰鎄鍍鎂鏤鎡鏌鎮鎛鎘鑷鐫鎳鎿鎦鎬鎊鎰鎔鏢鏜鏍鏰鏞鏡鏑鏃鏇鏐鐔钁鐐鏷鑥鐓鑭鐠鑹鏹鐙鑊鐳鐶鐲鐮鐿鑔鑣鑞鑲長門閂閃閆閈閉問闖閏闈閑閎間閔閌悶閘鬧閨聞闥閩閭闓閥閣閡閫鬮閱閬闍閾閹閶鬩閿閽閻閼闡闌闃闠闊闋闔闐闒闕闞闤隊陽陰陣階際陸隴陳陘陝隉隕險隨隱隸雋難雛讎靂霧霽黴靄靚靜靨韃鞽韉韝韋韌韍韓韙韞韜韻頁頂頃頇項順須頊頑顧頓頎頒頌頏預顱領頗頸頡頰頲頜潁熲頦頤頻頮頹頷頴穎顆題顒顎顓顏額顳顢顛顙顥纇顫顬顰顴風颺颭颮颯颶颸颼颻飀飄飆飆飛饗饜飣饑飥餳飩餼飪飫飭飯飲餞飾飽飼飿飴餌饒餉餄餎餃餏餅餑餖餓餘餒餕餜餛餡館餷饋餶餿饞饁饃餺餾饈饉饅饊饌饢馬馭馱馴馳驅馹駁驢駔駛駟駙駒騶駐駝駑駕驛駘驍罵駰驕驊駱駭駢驫驪騁驗騂駸駿騏騎騍騅騌驌驂騙騭騤騷騖驁騮騫騸驃騾驄驏驟驥驦驤髏髖髕鬢魘魎魚魛魢魷魨魯魴魺鮁鮃鯰鱸鮋鮓鮒鮊鮑鱟鮍鮐鮭鮚鮳鮪鮞鮦鰂鮜鱠鱭鮫鮮鮺鯗鱘鯁鱺鰱鰹鯉鰣鰷鯀鯊鯇鮶鯽鯒鯖鯪鯕鯫鯡鯤鯧鯝鯢鯰鯛鯨鯵鯴鯔鱝鰈鰏鱨鯷鰮鰃鰓鱷鰍鰒鰉鰁鱂鯿鰠鼇鰭鰨鰥鰩鰟鰜鰳鰾鱈鱉鰻鰵鱅鰼鱖鱔鱗鱒鱯鱤鱧鱣鳥鳩雞鳶鳴鳲鷗鴉鶬鴇鴆鴣鶇鸕鴨鴞鴦鴒鴟鴝鴛鴬鴕鷥鷙鴯鴰鵂鴴鵃鴿鸞鴻鵐鵓鸝鵑鵠鵝鵒鷳鵜鵡鵲鶓鵪鶤鵯鵬鵮鶉鶊鵷鷫鶘鶡鶚鶻鶿鶥鶩鷊鷂鶲鶹鶺鷁鶼鶴鷖鸚鷓鷚鷯鷦鷲鷸鷺鸇鷹鸌鸏鸛鸘鹺麥麩黃黌黶黷黲黽龍歷誌製壹臺臯準復勐鐘註範籤"}function p(e){let n="";const l=m();const a=g();for(let t=0;t1e4&&l.indexOf(e.charAt(t))!==-1){n+=a.charAt(l.indexOf(e.charAt(t)))}else n+=e.charAt(t)}return n}function A(e){let n="";const l=m();const a=g();for(let t=0;t1e4&&a.indexOf(e.charAt(t))!==-1){n+=l.charAt(a.indexOf(e.charAt(t)))}else n+=e.charAt(t)}return n}function C(){r=document.getElementById("translateLink");if(r){if(o!==c){r.textContent=c===1?l:n;f();setTimeout(u,e)}}}window.translateFn={translatePage:h,Traditionalized:p,Simplized:A};C();document.addEventListener("pjax:complete",C)}); \ No newline at end of file diff --git a/js/utils.js b/js/utils.js new file mode 100644 index 000000000..6a8864123 --- /dev/null +++ b/js/utils.js @@ -0,0 +1,7 @@ +const btf={debounce:(o,i=0,a=false)=>{let s;return(...t)=>{const e=()=>{s=null;if(!a)o(...t)};const n=a&&!s;clearTimeout(s);s=setTimeout(e,i);if(n)o(...t)}},throttle:function(o,i,a={}){let s,l,r;let c=0;const d=()=>{c=a.leading===false?0:(new Date).getTime();s=null;o.apply(l,r);if(!s)l=r=null};const t=(...t)=>{const e=(new Date).getTime();if(!c&&a.leading===false)c=e;const n=i-(e-c);l=this;r=t;if(n<=0||n>i){if(s){clearTimeout(s);s=null}c=e;o.apply(l,r);if(!s)l=r=null}else if(!s&&a.trailing!==false){s=setTimeout(d,n)}};return t},sidebarPaddingR:()=>{const t=window.innerWidth;const e=document.body.clientWidth;const n=t-e;if(t!==e){document.body.style.paddingRight=n+"px"}},snackbarShow:(t,e=false,n=2e3)=>{const{position:o,bgLight:i,bgDark:a}=GLOBAL_CONFIG.Snackbar;const s=document.documentElement.getAttribute("data-theme")==="light"?i:a;Snackbar.show({text:t,backgroundColor:s,showAction:e,duration:n,pos:o,customClass:"snackbar-css"})},diffDate:(t,e=false)=>{const n=new Date;const o=new Date(t);const i=n.getTime()-o.getTime();const a=1e3*60;const s=a*60;const l=s*24;const r=l*30;const{dateSuffix:c}=GLOBAL_CONFIG;if(!e)return parseInt(i/l);const d=i/r;const f=i/l;const m=i/s;const u=i/a;if(d>12)return o.toISOString().slice(0,10);if(d>=1)return`${parseInt(d)} ${c.month}`;if(f>=1)return`${parseInt(f)} ${c.day}`;if(m>=1)return`${parseInt(m)} ${c.hour}`;if(u>=1)return`${parseInt(u)} ${c.min}`;return c.just},loadComment:(t,e)=>{if("IntersectionObserver"in window){const n=new IntersectionObserver(t=>{if(t[0].isIntersecting){e();n.disconnect()}},{threshold:[0]});n.observe(t)}else{e()}},scrollToDest:(o,i=500)=>{const a=window.pageYOffset;const t=document.getElementById("page-header").classList.contains("fixed");if(a>o||t)o=o-70;if("scrollBehavior"in document.documentElement.style){window.scrollTo({top:o,behavior:"smooth"});return}let s=null;o=+o;window.requestAnimationFrame(function t(e){s=!s?e:s;const n=e-s;if(a{t.style.display="block";t.style.animation=e},animateOut:(e,t)=>{e.addEventListener("animationend",function t(){e.style.display="";e.style.animation="";e.removeEventListener("animationend",t)});e.style.animation=t},wrap:(t,e,n)=>{const o=document.createElement(e);for(const[i,a]of Object.entries(n)){o.setAttribute(i,a)}t.parentNode.insertBefore(o,t);o.appendChild(t)},isHidden:t=>t.offsetHeight===0&&t.offsetWidth===0,getEleTop:t=>{let e=t.offsetTop;let n=t.offsetParent;while(n!==null){e+=n.offsetTop;n=n.offsetParent}return e},loadLightbox:t=>{const e=GLOBAL_CONFIG.lightbox;if(e==="mediumZoom"){mediumZoom(t,{background:"var(--zoom-bg)"})}if(e==="fancybox"){Array.from(t).forEach(t=>{if(t.parentNode.tagName!=="A"){const e=t.dataset.lazySrc||t.src;const n=t.title||t.alt||"";btf.wrap(t,"a",{href:e,"data-fancybox":"gallery","data-caption":n,"data-thumb":e})}});if(!window.fancyboxRun){Fancybox.bind("[data-fancybox]",{Hash:false,Thumbs:{showOnStart:false},Images:{Panzoom:{maxScale:4}},Carousel:{transition:"slide"},Toolbar:{display:{left:["infobar"],middle:["zoomIn","zoomOut","toggle1to1","rotateCCW","rotateCW","flipX","flipY"],right:["slideshow","thumbs","close"]}}});window.fancyboxRun=true}}},setLoading:{add:t=>{const e=` +
    +
    +
    +
    +
    + `;t.insertAdjacentHTML("afterend",e)},remove:t=>{t.nextElementSibling.remove()}},updateAnchor:t=>{if(t!==window.location.hash){if(!t)t=location.pathname;const e=GLOBAL_CONFIG_SITE.title;window.history.replaceState({url:location.href,title:e},e,t)}},getScrollPercent:(t,e)=>{const n=e.clientHeight;const o=document.documentElement.clientHeight;const i=e.offsetTop;const a=n>o?n-o:document.documentElement.scrollHeight-o;const s=(t-i)/a;const l=Math.round(s*100);const r=l>100?100:l<=0?0:l;return r},addGlobalFn:(t,e,n=false,o=window)=>{const i=o.globalFn||{};const a=i[t]||{};if(n&&a[n])return;n=n||Object.keys(a).length;a[n]=e;i[t]=a;o.globalFn=i},addEventListenerPjax:(t,e,n,o=false)=>{t.addEventListener(e,n,o);btf.addGlobalFn("pjax",()=>{t.removeEventListener(e,n,o)})},removeGlobalFnEvent:(t,e=window)=>{const{globalFn:n={}}=e;const o=n[t]||{};const i=Object.keys(o);if(!i.length)return;i.forEach(t=>{o[t]()});delete e.globalFn[t]}}; \ No newline at end of file diff --git a/link/index.html b/link/index.html new file mode 100644 index 000000000..31d64810e --- /dev/null +++ b/link/index.html @@ -0,0 +1 @@ +有用的链接在这里~ | 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/navigation/index.html b/navigation/index.html new file mode 100644 index 000000000..0ed4f7871 --- /dev/null +++ b/navigation/index.html @@ -0,0 +1 @@ +杂七杂八的导航~ | 梦洁小站-属于你我的小天地

    评论
    \ No newline at end of file diff --git a/page/10/index.html b/page/10/index.html new file mode 100644 index 000000000..c8ba5179c --- /dev/null +++ b/page/10/index.html @@ -0,0 +1 @@ +梦洁小站-属于你我的小天地 - 前端,JavaScript,nodejs,es5,es6,vue
    关于flex布局最后一个不对齐的解决方法和为什么这样子解决是讨论
    uni-app知识点和项目上遇到的问题和解决办法的记录
    使用输入法自定义短语一步创建Hexo front-matter格式
    右键新建txt,新建文本文件不见了,通过添加注册表就可以解决,找来找去办法解决不了的终极办法
    win11安装系统提示virtualBox不兼容需要卸载virtual的解决办法,但是卸载列表找不到virtual的解决办法
    前端面试可能会问到的知识点记录
    网易严选,使用uni-app实现,包含后台数据文件
    微信小程序相关知识点和云音乐项目制作遇到的问题及解决
    微信小程序项目之网易云音乐,云音乐
    影视节前端网站,,以前东拼西凑的,前端课程设计应该可以吧
    \ No newline at end of file diff --git a/page/11/index.html b/page/11/index.html new file mode 100644 index 000000000..481e15715 --- /dev/null +++ b/page/11/index.html @@ -0,0 +1 @@ +梦洁小站-属于你我的小天地 - 前端,JavaScript,nodejs,es5,es6,vue
    vue3.x项目图书兄弟项目上遇到的问题及解决办法的记录
    图书兄弟移动端vue3.x项目(含在线演示)
    图标库网站-汇集优秀的图标,插画等
    江西财经大学智慧江财登录分析
    问卷星问卷抓包分析
    订阅地址-发薪日-白嫖订阅
    vue3.0的学习
    记录下bilibili(b站)小火箭页面上划动画效果的实现
    Typescript的学习笔记
    typescript完成的贪吃蛇前端小游戏
    \ No newline at end of file diff --git a/page/12/index.html b/page/12/index.html new file mode 100644 index 000000000..ed02ff125 --- /dev/null +++ b/page/12/index.html @@ -0,0 +1 @@ +梦洁小站-属于你我的小天地 - 前端,JavaScript,nodejs,es5,es6,vue
    webpack构建工具的学习
    使用nodejs导出md/Markdown文档当中的图片到本地并替换原始图片链接为本地图片链接
    小红书2020校招前端笔试题卷一
    移动端适配vue小练习
    移动端前端的适配和rem,vm其他的一些的复习
    vue一些比较重要知识点的复习
    vue后台的一个项目遇到的一些问题和解决办法的记录
    盒子模型及块元素水平垂直定位和绝对元素的定位布局和弹性盒
    我来图书馆实现用云函数cfc进行自动化抢位置
    vue源码分析-快速版(DMQ的MVVM为例)
    \ No newline at end of file diff --git a/page/13/index.html b/page/13/index.html new file mode 100644 index 000000000..bd6c6bead --- /dev/null +++ b/page/13/index.html @@ -0,0 +1 @@ +梦洁小站-属于你我的小天地 - 前端,JavaScript,nodejs,es5,es6,vue
    git添加代理,让github克隆的速度增加变快
    vue当中addRoutes动态添加路由白屏解决和next(),next("/")的一些区别
    尚品汇Vue项目 前台+后台完成品源码(含在线演示)
    Echarts图表的基本使用
    vue-admin-template里面的异步路由,常量路由,任意路由的添加,记录笔记
    vue-admin管理模板npm安装依赖后npm run de提示依赖core-js,@babel等报错的解决办法
    LeanCloud白嫖valine评论和避免休眠指南
    canvas基本使用
    今日刷题-Object.defineProperty和Object.getOwnPropertyDescriptor
    前端真题刷题-注意基础知识不要忘了基本原理
    \ No newline at end of file diff --git a/page/14/index.html b/page/14/index.html new file mode 100644 index 000000000..011dfc5a8 --- /dev/null +++ b/page/14/index.html @@ -0,0 +1 @@ +梦洁小站-属于你我的小天地 - 前端,JavaScript,nodejs,es5,es6,vue
    今日刷题-微任务和宏任务
    今日刷题-类型转换必须解决
    今日刷题-原型链
    深入自定义事件和原生DOM事件($attr等)
    浅拷贝深拷贝和个人一些新理解(非普遍的理解)
    今日刷题-重写的ValueOf
    今日刷题-变量的回收和reduce的使用
    默认暴露,分别暴露,整体暴露的再次学习及常用知识
    今日刷题-四舍五入等于...
    今日刷题-类型转换
    \ No newline at end of file diff --git a/page/15/index.html b/page/15/index.html new file mode 100644 index 000000000..af18aae22 --- /dev/null +++ b/page/15/index.html @@ -0,0 +1 @@ +梦洁小站-属于你我的小天地 - 前端,JavaScript,nodejs,es5,es6,vue
    今日刷题-decodeURI
    今日刷题-CMD和AMD的模块化
    element-ui分页器设置每一页显示数量(page-size)后页码没有发生变化原因与解决
    今日刷题-注意优先级
    今日刷题-温故而知新
    MATLAB课程设计(非库函数实现高斯模糊,边缘检测,傅里叶等操作-基础)
    今日刷题-请求头和响应头有必要了解
    今日刷题-数组的splice和map方法
    今日刷题-注意数组的一些方法
    今日刷题-let的暂时性死锁
    \ No newline at end of file diff --git a/page/16/index.html b/page/16/index.html new file mode 100644 index 000000000..5989141f6 --- /dev/null +++ b/page/16/index.html @@ -0,0 +1 @@ +梦洁小站-属于你我的小天地 - 前端,JavaScript,nodejs,es5,es6,vue
    今日刷题-注意let
    我来图书馆小程序加密后抓包分析反编译抢位置
    ProcessOn的官网(less+jquery实现三端)
    今日刷题-JavaScript的身份证正则和内置可迭代对象
    今日刷题-js的call,apply为null,undefined的情况和日期的注意点
    今日刷题-try...catch...finally
    今日刷题-隐式转换和String和new String
    我来图书馆小程序抓包抢位置
    纯css+html实现的分页器功能
    express+socket简易聊天室
    \ No newline at end of file diff --git a/page/17/index.html b/page/17/index.html new file mode 100644 index 000000000..239f3a7c6 --- /dev/null +++ b/page/17/index.html @@ -0,0 +1 @@ +梦洁小站-属于你我的小天地 - 前端,JavaScript,nodejs,es5,es6,vue
    防抖节流的使用和封装成函数
    express,multer,jQuery前端后端上传单个文件
    js当中的图片懒加载,懒加载的一些概念和相关知识点
    使用优启通(EasyU)重装系统教程(详细)
    ES5当中var只有全局作用域和函数作用域,注意变量提升在var中的坑
    nth-of-type选择器使用的一个坑
    2020年用JAVA制作的一个小项目图标快捷启动管理的
    迭代器自定义数据输出当中this指向
    jQuery中API,post上传文件到阿里云OSS记录
    记一次配置picgo错误的记录和解决办法
    \ No newline at end of file diff --git a/page/2/index.html b/page/2/index.html new file mode 100644 index 000000000..534e266d9 --- /dev/null +++ b/page/2/index.html @@ -0,0 +1 @@ +梦洁小站-属于你我的小天地 - 前端,JavaScript,nodejs,es5,es6,vue
    vite.config.js如何使用env的环境变量
    文心一言开通后吐槽下
    artalk设置进程守护和普通方式部署下创建密码
    网页出现为了更好的体验,请将手机竖过来
    electron-键盘打字小猫吐花学习
    穿越火线crossfire2.0 sql2014数据库文件
    QQ农场-phpYeFarm添加数据教程
    idea,webstorm等系列通用激活
    TS2322 Type Element is not assignable to type ReactNode
    JAVA学习笔记
    \ No newline at end of file diff --git a/page/3/index.html b/page/3/index.html new file mode 100644 index 000000000..ccb66f5b8 --- /dev/null +++ b/page/3/index.html @@ -0,0 +1 @@ +梦洁小站-属于你我的小天地 - 前端,JavaScript,nodejs,es5,es6,vue
    java练习-简易博客的搭建
    java-lambda和练习之多线程下载工具
    前端SVG的学习
    前端canvas的学习和将网页生成canvas图片
    React多个echarts图表在一个页面的使用
    前端取图片相同颜色作为遮罩或者背景
    Github action的学习
    QQ农场明月鲨鱼学习记录
    nvm安装版本后设置默认镜像地址和nvm list available出现空白解决办法
    \ No newline at end of file diff --git a/page/4/index.html b/page/4/index.html new file mode 100644 index 000000000..b15bbdd54 --- /dev/null +++ b/page/4/index.html @@ -0,0 +1 @@ +梦洁小站-属于你我的小天地 - 前端,JavaScript,nodejs,es5,es6,vue
    前端map标签(创建热点区域或是点击图片指定区域跳转对应链接))
    前端进度条和进度条流光效果
    Antd React Form.Item内部是自定义组件怎么自定义返回值
    defineAsync-Suspense学习
    QQ农场怀旧版搭建(附带搭建完成示例)
    网盘文件批量分享,目前支持百度网盘,天翼网盘,115网盘,123盘,夸克网盘,蓝奏云
    油猴(篡改猴)学习记录
    antd3和dva-自定义组件初始化值的操作演示和自定义组件校验
    tdesign的白天黑夜模式实现原理
    前端div水平居中的几种实现方式
    \ No newline at end of file diff --git a/page/5/index.html b/page/5/index.html new file mode 100644 index 000000000..9cbc4ac5d --- /dev/null +++ b/page/5/index.html @@ -0,0 +1 @@ +梦洁小站-属于你我的小天地 - 前端,JavaScript,nodejs,es5,es6,vue
    前端面试之手写call,apply,bind
    前端文字实现拼音标注
    知乎日报项目前端+后端-React18 + React-Router6 + React-redux + reduxtoolkit
    知乎日报项目学习笔记
    微信小程序渐进式骨架屏的写法
    Ubuntu系统安装基本Nginx和docker和一些其他的软件的基本操作
    React17+React Hook+TS4 最佳实践仿 Jira 企业级项目笔记
    vercel和netlify部署代码并解决接口代理转发的问题(和Nginx功能一样)
    Vant组件当中van-list的使用在自定义列表当中
    pinia的基本创建和统一创建和解构失去响应式解决办法等知识点
    \ No newline at end of file diff --git a/page/6/index.html b/page/6/index.html new file mode 100644 index 000000000..aaac03ce5 --- /dev/null +++ b/page/6/index.html @@ -0,0 +1 @@ +梦洁小站-属于你我的小天地 - 前端,JavaScript,nodejs,es5,es6,vue
    微信小程序模块化、组件传值、添加data,menthods类型等-持续更新
    无js实现拖拽边框改变div大小的笔记
    vue3获取ref实例结合ts的InstanceType
    React的学习笔记-(Bilibili李立超)
    grid布局的学习
    flex1和auto区别-好记性不如烂笔头
    React的学习笔记-(Bilibili天禹老师)
    git merge origin master和git merge master的区别(个人理解)
    使用vite的社区模板来创建对应的项目(比如React17,vue+electron)
    使用backdrop-filter实现elementui官网的模糊滤镜效果的和毛玻璃效果
    \ No newline at end of file diff --git a/page/7/index.html b/page/7/index.html new file mode 100644 index 000000000..0573c7d64 --- /dev/null +++ b/page/7/index.html @@ -0,0 +1 @@ +梦洁小站-属于你我的小天地 - 前端,JavaScript,nodejs,es5,es6,vue
    ts泛型,映射,条件类型和类型提取infer和一些常用工具库的说明
    js的promise的究竟是同步还是异步的问题和promise.all可以同时请求多个接口是错误的回答的原因
    flex设置为1后为什么要设置width为0,和布局超出省略号为什么会超出容器,为什么会没有用和在苹果环境下获取屏幕高度需要注意的点
    ant design vue treeDefaultExpandAll 没有展
    vue简单源码手写,实现基本的模板解析,v-text,v-html,v-onclick,@click基本语法指令
    js正则匹配获取分组和正向反向的区别
    2022年11月07日刷题-CSS 如何使用服务端的字体
    vue的h渲染函数和customRender在ant design vue的table组件的使用
    vue-router当中内置component标签(组件)的使用
    ant-design-vue select动态下拉加载更多和加载状态的添加
    \ No newline at end of file diff --git a/page/8/index.html b/page/8/index.html new file mode 100644 index 000000000..cb83b2b0a --- /dev/null +++ b/page/8/index.html @@ -0,0 +1 @@ +梦洁小站-属于你我的小天地 - 前端,JavaScript,nodejs,es5,es6,vue
    ant design vue时间范围(range-picker)自定义时间段范围
    antd vue 多选框的全选功能实现和选中时候传入值的改变
    ant design vue 当中的表格自定义结构超出隐藏的自适应 动态显示省略号
    ant design vue自定义表单验证不生效和自定义校验内容有值后自动清除警告的方法
    navigator.clipboard.readtext开发的时候有用,编译后测试却不生效
    记录下ant design vue tab和pagination(分页器)使用导致分页器total和其他不正常的问题
    vue当中script setup语法糖
    微信小程序抓包-夜神模拟器结合BurpSuite抓包(可用于现在最新版本微信)
    vue当中绑定回调函数的时候添加括号和不添加括号的区别
    vue控制台警告Runtime directive used on component with non-element root node.
    \ No newline at end of file diff --git a/page/9/index.html b/page/9/index.html new file mode 100644 index 000000000..019570759 --- /dev/null +++ b/page/9/index.html @@ -0,0 +1 @@ +梦洁小站-属于你我的小天地 - 前端,JavaScript,nodejs,es5,es6,vue
    vue3全局事件总线-mitt的使用(和vue2的全局总线不同)
    vue3中使用混入mixins在setup当中
    Fiddler和Proxifier的配合使用抓取window应用的所有包
    vue静态资源的引用(相对路径,绝对路径,@,~的一些笔记,以图片引入为例,含在线演示)
    七牛云本想白嫖对象存储和cdn还是要一分钱
    mockjs生成假数据的基本使用
    vue2项目之明日科技51购物商店官网-本地项目版本
    我来图书馆小程序一键签到和一键抢位置
    微信小程序解密并拆包获取源码教程
    我来图书馆小程序签到流程分析
    \ No newline at end of file diff --git "a/tags/51\345\225\206\345\234\272/index.html" "b/tags/51\345\225\206\345\234\272/index.html" new file mode 100644 index 000000000..74f0af430 --- /dev/null +++ "b/tags/51\345\225\206\345\234\272/index.html" @@ -0,0 +1 @@ +标签: 51商场 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/API/index.html b/tags/API/index.html new file mode 100644 index 000000000..3dfa77a81 --- /dev/null +++ b/tags/API/index.html @@ -0,0 +1 @@ +标签: API | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/Antd/index.html b/tags/Antd/index.html new file mode 100644 index 000000000..8c48512cb --- /dev/null +++ b/tags/Antd/index.html @@ -0,0 +1 @@ +标签: Antd | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/ES5/index.html b/tags/ES5/index.html new file mode 100644 index 000000000..e2c61f966 --- /dev/null +++ b/tags/ES5/index.html @@ -0,0 +1 @@ +标签: ES5 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/ES6/index.html b/tags/ES6/index.html new file mode 100644 index 000000000..6564162cf --- /dev/null +++ b/tags/ES6/index.html @@ -0,0 +1 @@ +标签: ES6 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/HTML/index.html b/tags/HTML/index.html new file mode 100644 index 000000000..421f982c0 --- /dev/null +++ b/tags/HTML/index.html @@ -0,0 +1 @@ +标签: HTML | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/HTML/page/2/index.html b/tags/HTML/page/2/index.html new file mode 100644 index 000000000..eb5a2cbc7 --- /dev/null +++ b/tags/HTML/page/2/index.html @@ -0,0 +1 @@ +标签: HTML | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/HTML/page/3/index.html b/tags/HTML/page/3/index.html new file mode 100644 index 000000000..2d5741c0a --- /dev/null +++ b/tags/HTML/page/3/index.html @@ -0,0 +1 @@ +标签: HTML | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/HTML/page/4/index.html b/tags/HTML/page/4/index.html new file mode 100644 index 000000000..4b72a0ff3 --- /dev/null +++ b/tags/HTML/page/4/index.html @@ -0,0 +1 @@ +标签: HTML | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/HTML/page/5/index.html b/tags/HTML/page/5/index.html new file mode 100644 index 000000000..bbabe1230 --- /dev/null +++ b/tags/HTML/page/5/index.html @@ -0,0 +1 @@ +标签: HTML | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/JAVA/index.html b/tags/JAVA/index.html new file mode 100644 index 000000000..ac3cab010 --- /dev/null +++ b/tags/JAVA/index.html @@ -0,0 +1 @@ +标签: JAVA | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/JavaScript/index.html b/tags/JavaScript/index.html new file mode 100644 index 000000000..8cc939c7e --- /dev/null +++ b/tags/JavaScript/index.html @@ -0,0 +1 @@ +标签: JavaScript | 梦洁小站-属于你我的小天地
    标签 - JavaScript
    2022
    纯css+html实现的分页器功能
    纯css+html实现的分页器功能
    \ No newline at end of file diff --git a/tags/LeanCloud/index.html b/tags/LeanCloud/index.html new file mode 100644 index 000000000..245ee4766 --- /dev/null +++ b/tags/LeanCloud/index.html @@ -0,0 +1 @@ +标签: LeanCloud | 梦洁小站-属于你我的小天地
    标签 - LeanCloud
    2022
    LeanCloud白嫖valine评论和避免休眠指南
    LeanCloud白嫖valine评论和避免休眠指南
    \ No newline at end of file diff --git a/tags/MATLAB/index.html b/tags/MATLAB/index.html new file mode 100644 index 000000000..96b3e7d1a --- /dev/null +++ b/tags/MATLAB/index.html @@ -0,0 +1 @@ +标签: MATLAB | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/NAT/index.html b/tags/NAT/index.html new file mode 100644 index 000000000..78927d589 --- /dev/null +++ b/tags/NAT/index.html @@ -0,0 +1 @@ +标签: NAT | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/NodeJs/index.html b/tags/NodeJs/index.html new file mode 100644 index 000000000..7fac0cd39 --- /dev/null +++ b/tags/NodeJs/index.html @@ -0,0 +1 @@ +标签: NodeJs | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/tags/OSS\345\255\230\345\202\250/index.html" "b/tags/OSS\345\255\230\345\202\250/index.html" new file mode 100644 index 000000000..b61df9fea --- /dev/null +++ "b/tags/OSS\345\255\230\345\202\250/index.html" @@ -0,0 +1 @@ +标签: OSS存储 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/Pycharm/index.html b/tags/Pycharm/index.html new file mode 100644 index 000000000..01152a914 --- /dev/null +++ b/tags/Pycharm/index.html @@ -0,0 +1 @@ +标签: Pycharm | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/QQ/index.html b/tags/QQ/index.html new file mode 100644 index 000000000..fcbc46e1a --- /dev/null +++ b/tags/QQ/index.html @@ -0,0 +1 @@ +标签: QQ | 梦洁小站-属于你我的小天地
    标签 - QQ
    2024
    QQ农场-phpYeFarm添加数据教程
    QQ农场-phpYeFarm添加数据教程
    \ No newline at end of file diff --git "a/tags/QQ\345\206\234\345\234\272/index.html" "b/tags/QQ\345\206\234\345\234\272/index.html" new file mode 100644 index 000000000..0834ef997 --- /dev/null +++ "b/tags/QQ\345\206\234\345\234\272/index.html" @@ -0,0 +1 @@ +标签: QQ农场 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/React/index.html b/tags/React/index.html new file mode 100644 index 000000000..2c12fc44a --- /dev/null +++ b/tags/React/index.html @@ -0,0 +1 @@ +标签: React | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/action/index.html b/tags/action/index.html new file mode 100644 index 000000000..9ccab1a3b --- /dev/null +++ b/tags/action/index.html @@ -0,0 +1 @@ +标签: action | 梦洁小站-属于你我的小天地
    标签 - action
    2024
    Github action的学习
    Github action的学习
    \ No newline at end of file diff --git a/tags/antd/index.html b/tags/antd/index.html new file mode 100644 index 000000000..f1fba9319 --- /dev/null +++ b/tags/antd/index.html @@ -0,0 +1 @@ +标签: antd | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/artalk/index.html b/tags/artalk/index.html new file mode 100644 index 000000000..108990ee6 --- /dev/null +++ b/tags/artalk/index.html @@ -0,0 +1 @@ +标签: artalk | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/canvas/index.html b/tags/canvas/index.html new file mode 100644 index 000000000..dc063e504 --- /dev/null +++ b/tags/canvas/index.html @@ -0,0 +1 @@ +标签: canvas | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/css/index.html b/tags/css/index.html new file mode 100644 index 000000000..865a9a1fe --- /dev/null +++ b/tags/css/index.html @@ -0,0 +1 @@ +标签: css | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/tags/css\345\270\203\345\261\200/index.html" "b/tags/css\345\270\203\345\261\200/index.html" new file mode 100644 index 000000000..f154bd9e0 --- /dev/null +++ "b/tags/css\345\270\203\345\261\200/index.html" @@ -0,0 +1 @@ +标签: css布局 | 梦洁小站-属于你我的小天地
    标签 - css布局
    2023
    grid布局的学习
    grid布局的学习
    \ No newline at end of file diff --git a/tags/docker/index.html b/tags/docker/index.html new file mode 100644 index 000000000..7ff47bdcb --- /dev/null +++ b/tags/docker/index.html @@ -0,0 +1 @@ +标签: docker | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/dva/index.html b/tags/dva/index.html new file mode 100644 index 000000000..862bcbbcd --- /dev/null +++ b/tags/dva/index.html @@ -0,0 +1 @@ +标签: dva | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/echarts/index.html b/tags/echarts/index.html new file mode 100644 index 000000000..6b0716609 --- /dev/null +++ b/tags/echarts/index.html @@ -0,0 +1 @@ +标签: echarts | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/express/index.html b/tags/express/index.html new file mode 100644 index 000000000..4e117c8a6 --- /dev/null +++ b/tags/express/index.html @@ -0,0 +1 @@ +标签: express | 梦洁小站-属于你我的小天地
    标签 - express
    2022
    express+socket简易聊天室
    express+socket简易聊天室
    \ No newline at end of file diff --git a/tags/flash/index.html b/tags/flash/index.html new file mode 100644 index 000000000..84170c58c --- /dev/null +++ b/tags/flash/index.html @@ -0,0 +1 @@ +标签: flash | 梦洁小站-属于你我的小天地
    标签 - flash
    2024
    QQ农场-phpYeFarm添加数据教程
    QQ农场-phpYeFarm添加数据教程
    \ No newline at end of file diff --git a/tags/flex/index.html b/tags/flex/index.html new file mode 100644 index 000000000..3e162796a --- /dev/null +++ b/tags/flex/index.html @@ -0,0 +1 @@ +标签: flex | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/tags/flex\345\270\203\345\261\200/index.html" "b/tags/flex\345\270\203\345\261\200/index.html" new file mode 100644 index 000000000..e0d45b7e6 --- /dev/null +++ "b/tags/flex\345\270\203\345\261\200/index.html" @@ -0,0 +1 @@ +标签: flex布局 | 梦洁小站-属于你我的小天地
    标签 - flex布局
    2023
    flex1和auto区别-好记性不如烂笔头
    flex1和auto区别-好记性不如烂笔头
    \ No newline at end of file diff --git a/tags/git/index.html b/tags/git/index.html new file mode 100644 index 000000000..9181067aa --- /dev/null +++ b/tags/git/index.html @@ -0,0 +1 @@ +标签: git | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/github/index.html b/tags/github/index.html new file mode 100644 index 000000000..319c996e4 --- /dev/null +++ b/tags/github/index.html @@ -0,0 +1 @@ +标签: github | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/grid/index.html b/tags/grid/index.html new file mode 100644 index 000000000..d54ce8eae --- /dev/null +++ b/tags/grid/index.html @@ -0,0 +1 @@ +标签: grid | 梦洁小站-属于你我的小天地
    标签 - grid
    2023
    grid布局的学习
    grid布局的学习
    \ No newline at end of file diff --git a/tags/hexo/index.html b/tags/hexo/index.html new file mode 100644 index 000000000..8f951ff8e --- /dev/null +++ b/tags/hexo/index.html @@ -0,0 +1 @@ +标签: hexo | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/html/index.html b/tags/html/index.html new file mode 100644 index 000000000..12ab9cf78 --- /dev/null +++ b/tags/html/index.html @@ -0,0 +1 @@ +标签: html | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/idea/index.html b/tags/idea/index.html new file mode 100644 index 000000000..4b1d18304 --- /dev/null +++ b/tags/idea/index.html @@ -0,0 +1 @@ +标签: idea | 梦洁小站-属于你我的小天地
    标签 - idea
    2024
    idea,webstorm等系列通用激活
    idea,webstorm等系列通用激活
    \ No newline at end of file diff --git a/tags/jQuery/index.html b/tags/jQuery/index.html new file mode 100644 index 000000000..0c4e46000 --- /dev/null +++ b/tags/jQuery/index.html @@ -0,0 +1 @@ +标签: jQuery | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/java/index.html b/tags/java/index.html new file mode 100644 index 000000000..8b994dd7c --- /dev/null +++ b/tags/java/index.html @@ -0,0 +1 @@ +标签: java | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/javascript/index.html b/tags/javascript/index.html new file mode 100644 index 000000000..1359a6596 --- /dev/null +++ b/tags/javascript/index.html @@ -0,0 +1 @@ +标签: javascript | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/javascript/page/2/index.html b/tags/javascript/page/2/index.html new file mode 100644 index 000000000..e155196b8 --- /dev/null +++ b/tags/javascript/page/2/index.html @@ -0,0 +1 @@ +标签: javascript | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/javscript/index.html b/tags/javscript/index.html new file mode 100644 index 000000000..591f9d556 --- /dev/null +++ b/tags/javscript/index.html @@ -0,0 +1 @@ +标签: javscript | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/javscript/page/2/index.html b/tags/javscript/page/2/index.html new file mode 100644 index 000000000..6ddb5ddc2 --- /dev/null +++ b/tags/javscript/page/2/index.html @@ -0,0 +1 @@ +标签: javscript | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/javscript/page/3/index.html b/tags/javscript/page/3/index.html new file mode 100644 index 000000000..3306cd10f --- /dev/null +++ b/tags/javscript/page/3/index.html @@ -0,0 +1 @@ +标签: javscript | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/javscript/page/4/index.html b/tags/javscript/page/4/index.html new file mode 100644 index 000000000..530189eb6 --- /dev/null +++ b/tags/javscript/page/4/index.html @@ -0,0 +1 @@ +标签: javscript | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/javscript/page/5/index.html b/tags/javscript/page/5/index.html new file mode 100644 index 000000000..dffad7678 --- /dev/null +++ b/tags/javscript/page/5/index.html @@ -0,0 +1 @@ +标签: javscript | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/js/index.html b/tags/js/index.html new file mode 100644 index 000000000..bd7328cb1 --- /dev/null +++ b/tags/js/index.html @@ -0,0 +1 @@ +标签: js | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/miniConda/index.html b/tags/miniConda/index.html new file mode 100644 index 000000000..7565e9c5a --- /dev/null +++ b/tags/miniConda/index.html @@ -0,0 +1 @@ +标签: miniConda | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/mock/index.html b/tags/mock/index.html new file mode 100644 index 000000000..b20b4a1b9 --- /dev/null +++ b/tags/mock/index.html @@ -0,0 +1 @@ +标签: mock | 梦洁小站-属于你我的小天地
    标签 - mock
    2022
    mockjs生成假数据的基本使用
    mockjs生成假数据的基本使用
    \ No newline at end of file diff --git a/tags/mockjs/index.html b/tags/mockjs/index.html new file mode 100644 index 000000000..95156943b --- /dev/null +++ b/tags/mockjs/index.html @@ -0,0 +1 @@ +标签: mockjs | 梦洁小站-属于你我的小天地
    标签 - mockjs
    2022
    mockjs生成假数据的基本使用
    mockjs生成假数据的基本使用
    \ No newline at end of file diff --git a/tags/mouseInc/index.html b/tags/mouseInc/index.html new file mode 100644 index 000000000..14c183600 --- /dev/null +++ b/tags/mouseInc/index.html @@ -0,0 +1 @@ +标签: mouseInc | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/picgo/index.html b/tags/picgo/index.html new file mode 100644 index 000000000..bb164a36f --- /dev/null +++ b/tags/picgo/index.html @@ -0,0 +1 @@ +标签: picgo | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/pinia/index.html b/tags/pinia/index.html new file mode 100644 index 000000000..ceddfaaef --- /dev/null +++ b/tags/pinia/index.html @@ -0,0 +1 @@ +标签: pinia | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/react/index.html b/tags/react/index.html new file mode 100644 index 000000000..78e27b034 --- /dev/null +++ b/tags/react/index.html @@ -0,0 +1 @@ +标签: react | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/rustDesk/index.html b/tags/rustDesk/index.html new file mode 100644 index 000000000..60423b50c --- /dev/null +++ b/tags/rustDesk/index.html @@ -0,0 +1 @@ +标签: rustDesk | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/socket/index.html b/tags/socket/index.html new file mode 100644 index 000000000..438bfe142 --- /dev/null +++ b/tags/socket/index.html @@ -0,0 +1 @@ +标签: socket | 梦洁小站-属于你我的小天地
    标签 - socket
    2022
    express+socket简易聊天室
    express+socket简易聊天室
    \ No newline at end of file diff --git a/tags/ts/index.html b/tags/ts/index.html new file mode 100644 index 000000000..858e98fca --- /dev/null +++ b/tags/ts/index.html @@ -0,0 +1 @@ +标签: ts | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/typescript/index.html b/tags/typescript/index.html new file mode 100644 index 000000000..0e56261bd --- /dev/null +++ b/tags/typescript/index.html @@ -0,0 +1 @@ +标签: typescript | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/typescript/page/2/index.html b/tags/typescript/page/2/index.html new file mode 100644 index 000000000..38e30ea74 --- /dev/null +++ b/tags/typescript/page/2/index.html @@ -0,0 +1 @@ +标签: typescript | 梦洁小站-属于你我的小天地
    标签 - typescript
    2022
    Typescript的学习笔记
    Typescript的学习笔记
    \ No newline at end of file diff --git a/tags/uni-app/index.html b/tags/uni-app/index.html new file mode 100644 index 000000000..f8784bf9a --- /dev/null +++ b/tags/uni-app/index.html @@ -0,0 +1 @@ +标签: uni-app | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/valine/index.html b/tags/valine/index.html new file mode 100644 index 000000000..0b1013157 --- /dev/null +++ b/tags/valine/index.html @@ -0,0 +1 @@ +标签: valine | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/vant/index.html b/tags/vant/index.html new file mode 100644 index 000000000..99c7fd3e5 --- /dev/null +++ b/tags/vant/index.html @@ -0,0 +1 @@ +标签: vant | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/vite/index.html b/tags/vite/index.html new file mode 100644 index 000000000..6455d0c8c --- /dev/null +++ b/tags/vite/index.html @@ -0,0 +1 @@ +标签: vite | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/vue-admin/index.html b/tags/vue-admin/index.html new file mode 100644 index 000000000..168996e40 --- /dev/null +++ b/tags/vue-admin/index.html @@ -0,0 +1 @@ +标签: vue-admin | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/vue/index.html b/tags/vue/index.html new file mode 100644 index 000000000..915f4c7da --- /dev/null +++ b/tags/vue/index.html @@ -0,0 +1 @@ +标签: vue | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/vue/page/2/index.html b/tags/vue/page/2/index.html new file mode 100644 index 000000000..17e2dd3f2 --- /dev/null +++ b/tags/vue/page/2/index.html @@ -0,0 +1 @@ +标签: vue | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/vue/page/3/index.html b/tags/vue/page/3/index.html new file mode 100644 index 000000000..a12be5e8b --- /dev/null +++ b/tags/vue/page/3/index.html @@ -0,0 +1 @@ +标签: vue | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/vue/page/4/index.html b/tags/vue/page/4/index.html new file mode 100644 index 000000000..87f32e41b --- /dev/null +++ b/tags/vue/page/4/index.html @@ -0,0 +1 @@ +标签: vue | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git a/tags/x64dbg/index.html b/tags/x64dbg/index.html new file mode 100644 index 000000000..cc6caf7a5 --- /dev/null +++ b/tags/x64dbg/index.html @@ -0,0 +1 @@ +标签: x64dbg | 梦洁小站-属于你我的小天地
    标签 - x64dbg
    2024
    x64dbg反汇编技术入门学习笔记
    x64dbg反汇编技术入门学习笔记
    \ No newline at end of file diff --git "a/tags/\344\270\203\347\211\233\344\272\221/index.html" "b/tags/\344\270\203\347\211\233\344\272\221/index.html" new file mode 100644 index 000000000..576feeda7 --- /dev/null +++ "b/tags/\344\270\203\347\211\233\344\272\221/index.html" @@ -0,0 +1 @@ +标签: 七牛云 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/tags/\345\206\234\345\234\272/index.html" "b/tags/\345\206\234\345\234\272/index.html" new file mode 100644 index 000000000..7a5e69712 --- /dev/null +++ "b/tags/\345\206\234\345\234\272/index.html" @@ -0,0 +1 @@ +标签: 农场 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/tags/\345\210\267\345\210\267\345\210\267/index.html" "b/tags/\345\210\267\345\210\267\345\210\267/index.html" new file mode 100644 index 000000000..1f71acd58 --- /dev/null +++ "b/tags/\345\210\267\345\210\267\345\210\267/index.html" @@ -0,0 +1 @@ +标签: 刷刷刷 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/tags/\345\210\267\345\210\267\345\210\267/page/2/index.html" "b/tags/\345\210\267\345\210\267\345\210\267/page/2/index.html" new file mode 100644 index 000000000..3798cd4bc --- /dev/null +++ "b/tags/\345\210\267\345\210\267\345\210\267/page/2/index.html" @@ -0,0 +1 @@ +标签: 刷刷刷 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/tags/\345\210\267\345\210\267\345\210\267/page/3/index.html" "b/tags/\345\210\267\345\210\267\345\210\267/page/3/index.html" new file mode 100644 index 000000000..bfbf37774 --- /dev/null +++ "b/tags/\345\210\267\345\210\267\345\210\267/page/3/index.html" @@ -0,0 +1 @@ +标签: 刷刷刷 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/tags/\345\210\267\351\242\230/index.html" "b/tags/\345\210\267\351\242\230/index.html" new file mode 100644 index 000000000..ed780c911 --- /dev/null +++ "b/tags/\345\210\267\351\242\230/index.html" @@ -0,0 +1 @@ +标签: 刷题 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/tags/\345\210\267\351\242\230/page/2/index.html" "b/tags/\345\210\267\351\242\230/page/2/index.html" new file mode 100644 index 000000000..58a7629f5 --- /dev/null +++ "b/tags/\345\210\267\351\242\230/page/2/index.html" @@ -0,0 +1 @@ +标签: 刷题 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/tags/\345\210\267\351\242\230/page/3/index.html" "b/tags/\345\210\267\351\242\230/page/3/index.html" new file mode 100644 index 000000000..1904c24bb --- /dev/null +++ "b/tags/\345\210\267\351\242\230/page/3/index.html" @@ -0,0 +1 @@ +标签: 刷题 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/tags/\345\211\215\347\253\257/index.html" "b/tags/\345\211\215\347\253\257/index.html" new file mode 100644 index 000000000..5c6aab900 --- /dev/null +++ "b/tags/\345\211\215\347\253\257/index.html" @@ -0,0 +1 @@ +标签: 前端 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/tags/\345\220\216\347\253\257/index.html" "b/tags/\345\220\216\347\253\257/index.html" new file mode 100644 index 000000000..0d969853c --- /dev/null +++ "b/tags/\345\220\216\347\253\257/index.html" @@ -0,0 +1 @@ +标签: 后端 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/tags/\345\233\276\344\271\246\345\205\204\345\274\237/index.html" "b/tags/\345\233\276\344\271\246\345\205\204\345\274\237/index.html" new file mode 100644 index 000000000..59eb2dad9 --- /dev/null +++ "b/tags/\345\233\276\344\271\246\345\205\204\345\274\237/index.html" @@ -0,0 +1 @@ +标签: 图书兄弟 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/tags/\345\233\276\346\240\207\345\272\223/index.html" "b/tags/\345\233\276\346\240\207\345\272\223/index.html" new file mode 100644 index 000000000..e43aa7324 --- /dev/null +++ "b/tags/\345\233\276\346\240\207\345\272\223/index.html" @@ -0,0 +1 @@ +标签: 图标库 | 梦洁小站-属于你我的小天地
    标签 - 图标库
    2022
    图标库网站-汇集优秀的图标,插画等
    图标库网站-汇集优秀的图标,插画等
    \ No newline at end of file diff --git "a/tags/\345\244\247\345\212\233\351\207\221\345\210\232/index.html" "b/tags/\345\244\247\345\212\233\351\207\221\345\210\232/index.html" new file mode 100644 index 000000000..315c5d45d --- /dev/null +++ "b/tags/\345\244\247\345\212\233\351\207\221\345\210\232/index.html" @@ -0,0 +1 @@ +标签: 大力金刚 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/tags/\345\244\257\345\244\247\345\212\233/index.html" "b/tags/\345\244\257\345\244\247\345\212\233/index.html" new file mode 100644 index 000000000..d5ad05d2f --- /dev/null +++ "b/tags/\345\244\257\345\244\247\345\212\233/index.html" @@ -0,0 +1 @@ +标签: 夯大力 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/tags/\345\260\217\347\250\213\345\272\217/index.html" "b/tags/\345\260\217\347\250\213\345\272\217/index.html" new file mode 100644 index 000000000..0fbd54435 --- /dev/null +++ "b/tags/\345\260\217\347\250\213\345\272\217/index.html" @@ -0,0 +1 @@ +标签: 小程序 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/tags/\345\267\245\345\205\267/index.html" "b/tags/\345\267\245\345\205\267/index.html" new file mode 100644 index 000000000..94a005882 --- /dev/null +++ "b/tags/\345\267\245\345\205\267/index.html" @@ -0,0 +1 @@ +标签: 工具 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/tags/\345\267\245\345\205\267/page/2/index.html" "b/tags/\345\267\245\345\205\267/page/2/index.html" new file mode 100644 index 000000000..4d437f9b1 --- /dev/null +++ "b/tags/\345\267\245\345\205\267/page/2/index.html" @@ -0,0 +1 @@ +标签: 工具 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/tags/\345\267\245\345\205\267\345\272\223/index.html" "b/tags/\345\267\245\345\205\267\345\272\223/index.html" new file mode 100644 index 000000000..356040c3d --- /dev/null +++ "b/tags/\345\267\245\345\205\267\345\272\223/index.html" @@ -0,0 +1 @@ +标签: 工具库 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/tags/\345\276\256\344\277\241\345\260\217\347\250\213\345\272\217/index.html" "b/tags/\345\276\256\344\277\241\345\260\217\347\250\213\345\272\217/index.html" new file mode 100644 index 000000000..9aab242f2 --- /dev/null +++ "b/tags/\345\276\256\344\277\241\345\260\217\347\250\213\345\272\217/index.html" @@ -0,0 +1 @@ +标签: 微信小程序 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/tags/\346\212\223\345\214\205/index.html" "b/tags/\346\212\223\345\214\205/index.html" new file mode 100644 index 000000000..860358203 --- /dev/null +++ "b/tags/\346\212\223\345\214\205/index.html" @@ -0,0 +1 @@ +标签: 抓包 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/tags/\346\212\230\350\205\276/index.html" "b/tags/\346\212\230\350\205\276/index.html" new file mode 100644 index 000000000..5142059ad --- /dev/null +++ "b/tags/\346\212\230\350\205\276/index.html" @@ -0,0 +1 @@ +标签: 折腾 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/tags/\346\213\226\346\213\275/index.html" "b/tags/\346\213\226\346\213\275/index.html" new file mode 100644 index 000000000..bcfb30a67 --- /dev/null +++ "b/tags/\346\213\226\346\213\275/index.html" @@ -0,0 +1 @@ +标签: 拖拽 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/tags/\346\217\222\347\224\273\345\272\223/index.html" "b/tags/\346\217\222\347\224\273\345\272\223/index.html" new file mode 100644 index 000000000..25eb47c58 --- /dev/null +++ "b/tags/\346\217\222\347\224\273\345\272\223/index.html" @@ -0,0 +1 @@ +标签: 插画库 | 梦洁小站-属于你我的小天地
    标签 - 插画库
    2022
    图标库网站-汇集优秀的图标,插画等
    图标库网站-汇集优秀的图标,插画等
    \ No newline at end of file diff --git "a/tags/\346\220\234\347\213\227\350\276\223\345\205\245\346\263\225/index.html" "b/tags/\346\220\234\347\213\227\350\276\223\345\205\245\346\263\225/index.html" new file mode 100644 index 000000000..f95e7359e --- /dev/null +++ "b/tags/\346\220\234\347\213\227\350\276\223\345\205\245\346\263\225/index.html" @@ -0,0 +1 @@ +标签: 搜狗输入法 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/tags/\346\226\207\345\277\203\344\270\200\350\250\200/index.html" "b/tags/\346\226\207\345\277\203\344\270\200\350\250\200/index.html" new file mode 100644 index 000000000..268902ea3 --- /dev/null +++ "b/tags/\346\226\207\345\277\203\344\270\200\350\250\200/index.html" @@ -0,0 +1 @@ +标签: 文心一言 | 梦洁小站-属于你我的小天地
    标签 - 文心一言
    2024
    文心一言开通后吐槽下
    文心一言开通后吐槽下
    \ No newline at end of file diff --git "a/tags/\346\230\216\346\234\210\346\227\240\345\277\203\350\276\205\345\212\251/index.html" "b/tags/\346\230\216\346\234\210\346\227\240\345\277\203\350\276\205\345\212\251/index.html" new file mode 100644 index 000000000..470085dee --- /dev/null +++ "b/tags/\346\230\216\346\234\210\346\227\240\345\277\203\350\276\205\345\212\251/index.html" @@ -0,0 +1 @@ +标签: 明月无心辅助 | 梦洁小站-属于你我的小天地
    标签 - 明月无心辅助
    2024
    QQ农场明月鲨鱼学习记录
    QQ农场明月鲨鱼学习记录
    \ No newline at end of file diff --git "a/tags/\346\234\272\345\234\272/index.html" "b/tags/\346\234\272\345\234\272/index.html" new file mode 100644 index 000000000..18ce7c391 --- /dev/null +++ "b/tags/\346\234\272\345\234\272/index.html" @@ -0,0 +1 @@ +标签: 机场 | 梦洁小站-属于你我的小天地
    标签 - 机场
    2022
    订阅地址-发薪日-白嫖订阅
    订阅地址-发薪日-白嫖订阅
    \ No newline at end of file diff --git "a/tags/\346\277\200\346\264\273/index.html" "b/tags/\346\277\200\346\264\273/index.html" new file mode 100644 index 000000000..d855f207f --- /dev/null +++ "b/tags/\346\277\200\346\264\273/index.html" @@ -0,0 +1 @@ +标签: 激活 | 梦洁小站-属于你我的小天地
    标签 - 激活
    2024
    idea,webstorm等系列通用激活
    idea,webstorm等系列通用激活
    \ No newline at end of file diff --git "a/tags/\347\231\275\345\253\226/index.html" "b/tags/\347\231\275\345\253\226/index.html" new file mode 100644 index 000000000..0e34ed1b0 --- /dev/null +++ "b/tags/\347\231\275\345\253\226/index.html" @@ -0,0 +1 @@ +标签: 白嫖 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/tags/\347\231\275\345\253\226\346\214\207\345\215\227/index.html" "b/tags/\347\231\275\345\253\226\346\214\207\345\215\227/index.html" new file mode 100644 index 000000000..84334b7cd --- /dev/null +++ "b/tags/\347\231\275\345\253\226\346\214\207\345\215\227/index.html" @@ -0,0 +1 @@ +标签: 白嫖指南 | 梦洁小站-属于你我的小天地
    标签 - 白嫖指南
    2022
    LeanCloud白嫖valine评论和避免休眠指南
    LeanCloud白嫖valine评论和避免休眠指南
    \ No newline at end of file diff --git "a/tags/\347\247\273\345\212\250\347\253\257/index.html" "b/tags/\347\247\273\345\212\250\347\253\257/index.html" new file mode 100644 index 000000000..822b99e0b --- /dev/null +++ "b/tags/\347\247\273\345\212\250\347\253\257/index.html" @@ -0,0 +1 @@ +标签: 移动端 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/tags/\347\251\277\350\266\212\347\201\253\347\272\277/index.html" "b/tags/\347\251\277\350\266\212\347\201\253\347\272\277/index.html" new file mode 100644 index 000000000..de5bc8126 --- /dev/null +++ "b/tags/\347\251\277\350\266\212\347\201\253\347\272\277/index.html" @@ -0,0 +1 @@ +标签: 穿越火线 | 梦洁小站-属于你我的小天地
    标签 - 穿越火线
    2024
    穿越火线crossfire2.0 sql2014数据库文件
    穿越火线crossfire2.0 sql2014数据库文件
    \ No newline at end of file diff --git "a/tags/\347\263\273\347\273\237/index.html" "b/tags/\347\263\273\347\273\237/index.html" new file mode 100644 index 000000000..bfb110a3f --- /dev/null +++ "b/tags/\347\263\273\347\273\237/index.html" @@ -0,0 +1 @@ +标签: 系统 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/tags/\350\207\252\345\256\232\344\271\211\347\237\255\350\257\255/index.html" "b/tags/\350\207\252\345\256\232\344\271\211\347\237\255\350\257\255/index.html" new file mode 100644 index 000000000..d235383d0 --- /dev/null +++ "b/tags/\350\207\252\345\256\232\344\271\211\347\237\255\350\257\255/index.html" @@ -0,0 +1 @@ +标签: 自定义短语 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/tags/\350\277\255\344\273\243\345\231\250/index.html" "b/tags/\350\277\255\344\273\243\345\231\250/index.html" new file mode 100644 index 000000000..ba8063235 --- /dev/null +++ "b/tags/\350\277\255\344\273\243\345\231\250/index.html" @@ -0,0 +1 @@ +标签: 迭代器 | 梦洁小站-属于你我的小天地
    标签 - 迭代器
    2022
    迭代器自定义数据输出当中this指向
    迭代器自定义数据输出当中this指向
    \ No newline at end of file diff --git "a/tags/\351\207\215\350\243\205\347\263\273\347\273\237/index.html" "b/tags/\351\207\215\350\243\205\347\263\273\347\273\237/index.html" new file mode 100644 index 000000000..7cc24b2e3 --- /dev/null +++ "b/tags/\351\207\215\350\243\205\347\263\273\347\273\237/index.html" @@ -0,0 +1 @@ +标签: 重装系统 | 梦洁小站-属于你我的小天地
    标签 - 重装系统
    2022
    使用优启通(EasyU)重装系统教程(详细)
    使用优启通(EasyU)重装系统教程(详细)
    \ No newline at end of file diff --git "a/tags/\351\230\277\351\207\214\344\272\221/index.html" "b/tags/\351\230\277\351\207\214\344\272\221/index.html" new file mode 100644 index 000000000..a76931270 --- /dev/null +++ "b/tags/\351\230\277\351\207\214\344\272\221/index.html" @@ -0,0 +1 @@ +标签: 阿里云 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/tags/\351\232\217\350\256\260/index.html" "b/tags/\351\232\217\350\256\260/index.html" new file mode 100644 index 000000000..6795f74be --- /dev/null +++ "b/tags/\351\232\217\350\256\260/index.html" @@ -0,0 +1 @@ +标签: 随记 | 梦洁小站-属于你我的小天地
    \ No newline at end of file diff --git "a/tags/\351\235\242\350\257\225/index.html" "b/tags/\351\235\242\350\257\225/index.html" new file mode 100644 index 000000000..b116aa55e --- /dev/null +++ "b/tags/\351\235\242\350\257\225/index.html" @@ -0,0 +1 @@ +标签: 面试 | 梦洁小站-属于你我的小天地
    标签 - 面试
    2022
    前端面试可能会问到的知识点记录
    前端面试可能会问到的知识点记录
    \ No newline at end of file diff --git "a/tags/\351\262\250\351\261\274\350\276\205\345\212\251/index.html" "b/tags/\351\262\250\351\261\274\350\276\205\345\212\251/index.html" new file mode 100644 index 000000000..595a5df3f --- /dev/null +++ "b/tags/\351\262\250\351\261\274\350\276\205\345\212\251/index.html" @@ -0,0 +1 @@ +标签: 鲨鱼辅助 | 梦洁小站-属于你我的小天地
    标签 - 鲨鱼辅助
    2024
    QQ农场明月鲨鱼学习记录
    QQ农场明月鲨鱼学习记录
    \ No newline at end of file diff --git a/test/404.html b/test/404.html new file mode 100644 index 000000000..8a837b5a8 --- /dev/null +++ b/test/404.html @@ -0,0 +1 @@ +梦洁小站-属于你我的小天地 | 梦洁小站-属于你我的小天地
    404: This page could not be found.Create Next App

    404

    This page could not be found.


    评论
    \ No newline at end of file diff --git a/test/favicon.ico b/test/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..718d6fea4835ec2d246af9800eddb7ffb276240c GIT binary patch literal 25931 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 LrJugUA?W`A8`#=m literal 0 HcmV?d00001 diff --git a/test/index.html b/test/index.html new file mode 100644 index 000000000..dcbda0907 --- /dev/null +++ b/test/index.html @@ -0,0 +1 @@ +梦洁小站-属于你我的小天地 | 梦洁小站-属于你我的小天地
    Create Next App

    Get started by editing src/app/page.js

    Next.js Logo

    评论
    \ No newline at end of file diff --git a/test/index.txt b/test/index.txt new file mode 100644 index 000000000..3f7d64a26 --- /dev/null +++ b/test/index.txt @@ -0,0 +1,6 @@ +2:I[8173,["173","static/chunks/173-32f9ff9bdfb525b3.js","931","static/chunks/app/page-154f1f1ed3504cdc.js"],"Image"] +3:I[9275,[],""] +4:I[1343,[],""] +0:["FylDV66hHSFtUMeM45Mxh",[[["",{"children":["__PAGE__",{}]},"$undefined","$undefined",true],["",{"children":["__PAGE__",{},[["$L1",["$","main",null,{"className":"flex min-h-screen flex-col items-center justify-between p-24","children":[["$","div",null,{"className":"z-10 max-w-5xl w-full items-center justify-between font-mono text-sm lg:flex","children":[["$","p",null,{"className":"fixed left-0 top-0 flex w-full justify-center border-b border-gray-300 bg-gradient-to-b from-zinc-200 pb-6 pt-8 backdrop-blur-2xl dark:border-neutral-800 dark:bg-zinc-800/30 dark:from-inherit lg:static lg:w-auto lg:rounded-xl lg:border lg:bg-gray-200 lg:p-4 lg:dark:bg-zinc-800/30","children":["Get started by editing ",["$","code",null,{"className":"font-mono font-bold","children":"src/app/page.js"}]]}],["$","div",null,{"className":"fixed bottom-0 left-0 flex h-48 w-full items-end justify-center bg-gradient-to-t from-white via-white dark:from-black dark:via-black lg:static lg:h-auto lg:w-auto lg:bg-none","children":["$","a",null,{"className":"pointer-events-none flex place-items-center gap-2 p-8 lg:pointer-events-auto lg:p-0","href":"https://vercel.com?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app","target":"_blank","rel":"noopener noreferrer","children":["By"," ",["$","$L2",null,{"src":"/vercel.svg","alt":"Vercel Logo","className":"dark:invert","width":100,"height":24,"priority":true}]]}]}]]}],["$","div",null,{"className":"relative flex place-items-center before:absolute before:h-[300px] before:w-full sm:before:w-[480px] before:-translate-x-1/2 before:rounded-full before:bg-gradient-radial before:from-white before:to-transparent before:blur-2xl before:content-[''] after:absolute after:-z-20 after:h-[180px] after:w-full sm:after:w-[240px] after:translate-x-1/3 after:bg-gradient-conic after:from-sky-200 after:via-blue-200 after:blur-2xl after:content-[''] before:dark:bg-gradient-to-br before:dark:from-transparent before:dark:to-blue-700 before:dark:opacity-10 after:dark:from-sky-900 after:dark:via-[#0141ff] after:dark:opacity-40 before:lg:h-[360px] z-[-1]","children":["$","$L2",null,{"className":"relative dark:drop-shadow-[0_0_0.3rem_#ffffff70] dark:invert","src":"/next.svg","alt":"Next.js Logo","width":180,"height":37,"priority":true}]}],["$","div",null,{"className":"mb-32 grid text-center lg:max-w-5xl lg:w-full lg:mb-0 lg:grid-cols-4 lg:text-left","children":[["$","a",null,{"href":"https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app","className":"group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30","target":"_blank","rel":"noopener noreferrer","children":[["$","h2",null,{"className":"mb-3 text-2xl font-semibold","children":["Docs"," ",["$","span",null,{"className":"inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none","children":"->"}]]}],["$","p",null,{"className":"m-0 max-w-[30ch] text-sm opacity-50","children":"Find in-depth information about Next.js features and API."}]]}],["$","a",null,{"href":"https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app","className":"group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800 hover:dark:bg-opacity-30","target":"_blank","rel":"noopener noreferrer","children":[["$","h2",null,{"className":"mb-3 text-2xl font-semibold","children":["Learn"," ",["$","span",null,{"className":"inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none","children":"->"}]]}],["$","p",null,{"className":"m-0 max-w-[30ch] text-sm opacity-50","children":"Learn about Next.js in an interactive course with quizzes!"}]]}],["$","a",null,{"href":"https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app","className":"group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30","target":"_blank","rel":"noopener noreferrer","children":[["$","h2",null,{"className":"mb-3 text-2xl font-semibold","children":["Templates"," ",["$","span",null,{"className":"inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none","children":"->"}]]}],["$","p",null,{"className":"m-0 max-w-[30ch] text-sm opacity-50","children":"Explore starter templates for Next.js."}]]}],["$","a",null,{"href":"https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template&utm_campaign=create-next-app","className":"group rounded-lg border border-transparent px-5 py-4 transition-colors hover:border-gray-300 hover:bg-gray-100 hover:dark:border-neutral-700 hover:dark:bg-neutral-800/30","target":"_blank","rel":"noopener noreferrer","children":[["$","h2",null,{"className":"mb-3 text-2xl font-semibold","children":["Deploy"," ",["$","span",null,{"className":"inline-block transition-transform group-hover:translate-x-1 motion-reduce:transform-none","children":"->"}]]}],["$","p",null,{"className":"m-0 max-w-[30ch] text-sm opacity-50 text-balance","children":"Instantly deploy your Next.js site to a shareable URL with Vercel."}]]}]]}]]}]],null],null]},[["$","html",null,{"lang":"en","children":["$","body",null,{"className":"__className_aaf875","children":["$","$L3",null,{"parallelRouterKey":"children","segmentPath":["children"],"error":"$undefined","errorStyles":"$undefined","errorScripts":"$undefined","template":["$","$L4",null,{}],"templateStyles":"$undefined","templateScripts":"$undefined","notFound":[["$","title",null,{"children":"404: This page could not be found."}],["$","div",null,{"style":{"fontFamily":"system-ui,\"Segoe UI\",Roboto,Helvetica,Arial,sans-serif,\"Apple Color Emoji\",\"Segoe UI Emoji\"","height":"100vh","textAlign":"center","display":"flex","flexDirection":"column","alignItems":"center","justifyContent":"center"},"children":["$","div",null,{"children":[["$","style",null,{"dangerouslySetInnerHTML":{"__html":"body{color:#000;background:#fff;margin:0}.next-error-h1{border-right:1px solid rgba(0,0,0,.3)}@media (prefers-color-scheme:dark){body{color:#fff;background:#000}.next-error-h1{border-right:1px solid rgba(255,255,255,.3)}}"}}],["$","h1",null,{"className":"next-error-h1","style":{"display":"inline-block","margin":"0 20px 0 0","padding":"0 23px 0 0","fontSize":24,"fontWeight":500,"verticalAlign":"top","lineHeight":"49px"},"children":"404"}],["$","div",null,{"style":{"display":"inline-block"},"children":["$","h2",null,{"style":{"fontSize":14,"fontWeight":400,"lineHeight":"49px","margin":0},"children":"This page could not be found."}]}]]}]}]],"notFoundStyles":[],"styles":null}]}]}],null],null],[[["$","link","0",{"rel":"stylesheet","href":"/_next/static/css/fea71b32cf6af709.css","precedence":"next","crossOrigin":"$undefined"}]],"$L5"]]]] +5:[["$","meta","0",{"name":"viewport","content":"width=device-width, initial-scale=1"}],["$","meta","1",{"charSet":"utf-8"}],["$","title","2",{"children":"Create Next App"}],["$","meta","3",{"name":"description","content":"Generated by create next app"}],["$","link","4",{"rel":"icon","href":"/favicon.ico","type":"image/x-icon","sizes":"16x16"}]] +1:null diff --git a/test/next.svg b/test/next.svg new file mode 100644 index 000000000..5174b28c5 --- /dev/null +++ b/test/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/test/test.zip b/test/test.zip new file mode 100644 index 0000000000000000000000000000000000000000..74af53dbd2ae4736a4009a17806a969f1e198f5a GIT binary patch literal 448721 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 ITP&N3 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%0AL6=T-nv;y2O0wCOr{!C#g0 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
    j7N)Y72`2<)s<7D21#i_#dcVM3&1jvd_UnXR-K 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�Kj_|com15tI8O=?<)Lo3muu8fAUn8|1jL># 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
    vUCjSJy^O=R`(W0-+<` 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 literal 0 HcmV?d00001 diff --git a/test/vercel.svg b/test/vercel.svg new file mode 100644 index 000000000..a7a91feef --- /dev/null +++ b/test/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file