{"meta":{"version":1,"warehouse":"4.0.2"},"models":{"Asset":[{"_id":"themes/hexo-theme-cosy/source/css/0c63d269.css","path":"css/0c63d269.css","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/css/3a4a90d1.css","path":"css/3a4a90d1.css","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/css/3efc6cb5.css","path":"css/3efc6cb5.css","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/css/5c728363.css","path":"css/5c728363.css","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/css/5bfc518f.css","path":"css/5bfc518f.css","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/css/68d4249e.css","path":"css/68d4249e.css","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/css/69c863a0.css","path":"css/69c863a0.css","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/css/82dd7e5a.css","path":"css/82dd7e5a.css","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/css/80d65618.css","path":"css/80d65618.css","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/css/c080db9c.css","path":"css/c080db9c.css","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/css/dba23209.css","path":"css/dba23209.css","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/css/de5de8fb.css","path":"css/de5de8fb.css","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/css/e7914205.css","path":"css/e7914205.css","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/css/efca006a.css","path":"css/efca006a.css","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/css/f3729dde.css","path":"css/f3729dde.css","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/font/linear.woff2","path":"font/linear.woff2","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/font/motto.woff","path":"font/motto.woff","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/favicon.svg","path":"img/favicon.svg","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/icon-click.svg","path":"img/icon-click.svg","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/icon-arrow-left.svg","path":"img/icon-arrow-left.svg","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/icon-book.svg","path":"img/icon-book.svg","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/icon-date.svg","path":"img/icon-date.svg","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/icon-link.svg","path":"img/icon-link.svg","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/icon-layout.svg","path":"img/icon-layout.svg","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/icon-moon.svg","path":"img/icon-moon.svg","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/icon1.svg","path":"img/icon1.svg","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/icon2.svg","path":"img/icon2.svg","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/icon3.svg","path":"img/icon3.svg","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/logo.png","path":"img/logo.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/js/31d6cfe0.js","path":"js/31d6cfe0.js","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/js/3cf4fd98.js","path":"js/3cf4fd98.js","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/js/9f1cd854.js","path":"js/9f1cd854.js","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/icon-sun.svg","path":"img/icon-sun.svg","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/js/58c91c4e.js","path":"js/58c91c4e.js","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/js/ae2a0e7b.js","path":"js/ae2a0e7b.js","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/js/b9c2be9c.js","path":"js/b9c2be9c.js","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/js/b9c2be9c.js.LICENSE.txt","path":"js/b9c2be9c.js.LICENSE.txt","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/js/c413ebaa.js","path":"js/c413ebaa.js","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/js/ca6b30b5.js","path":"js/ca6b30b5.js","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/js/cdca7001.js","path":"js/cdca7001.js","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/js/d3872ea1.js","path":"js/d3872ea1.js","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/js/f8b20eb9.js","path":"js/f8b20eb9.js","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/100.png","path":"img/qweather-color-icon/100.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/101.png","path":"img/qweather-color-icon/101.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/102.png","path":"img/qweather-color-icon/102.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/103.png","path":"img/qweather-color-icon/103.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/104.png","path":"img/qweather-color-icon/104.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/150.png","path":"img/qweather-color-icon/150.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/151.png","path":"img/qweather-color-icon/151.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/153.png","path":"img/qweather-color-icon/153.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/152.png","path":"img/qweather-color-icon/152.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/302.png","path":"img/qweather-color-icon/302.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/301.png","path":"img/qweather-color-icon/301.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/300.png","path":"img/qweather-color-icon/300.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/304.png","path":"img/qweather-color-icon/304.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/303.png","path":"img/qweather-color-icon/303.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/305.png","path":"img/qweather-color-icon/305.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/306.png","path":"img/qweather-color-icon/306.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/307.png","path":"img/qweather-color-icon/307.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/308.png","path":"img/qweather-color-icon/308.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/309.png","path":"img/qweather-color-icon/309.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/310.png","path":"img/qweather-color-icon/310.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/311.png","path":"img/qweather-color-icon/311.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/312.png","path":"img/qweather-color-icon/312.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/313.png","path":"img/qweather-color-icon/313.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/314.png","path":"img/qweather-color-icon/314.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/315.png","path":"img/qweather-color-icon/315.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/316.png","path":"img/qweather-color-icon/316.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/317.png","path":"img/qweather-color-icon/317.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/318.png","path":"img/qweather-color-icon/318.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/350.png","path":"img/qweather-color-icon/350.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/351.png","path":"img/qweather-color-icon/351.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/399.png","path":"img/qweather-color-icon/399.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/400.png","path":"img/qweather-color-icon/400.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/401.png","path":"img/qweather-color-icon/401.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/402.png","path":"img/qweather-color-icon/402.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/403.png","path":"img/qweather-color-icon/403.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/404.png","path":"img/qweather-color-icon/404.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/406.png","path":"img/qweather-color-icon/406.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/407.png","path":"img/qweather-color-icon/407.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/408.png","path":"img/qweather-color-icon/408.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/410.png","path":"img/qweather-color-icon/410.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/409.png","path":"img/qweather-color-icon/409.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/456.png","path":"img/qweather-color-icon/456.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/457.png","path":"img/qweather-color-icon/457.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/499.png","path":"img/qweather-color-icon/499.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/501.png","path":"img/qweather-color-icon/501.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/500.png","path":"img/qweather-color-icon/500.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/503.png","path":"img/qweather-color-icon/503.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/502.png","path":"img/qweather-color-icon/502.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/504.png","path":"img/qweather-color-icon/504.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/507.png","path":"img/qweather-color-icon/507.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/508.png","path":"img/qweather-color-icon/508.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/509.png","path":"img/qweather-color-icon/509.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/510.png","path":"img/qweather-color-icon/510.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/511.png","path":"img/qweather-color-icon/511.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/512.png","path":"img/qweather-color-icon/512.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/513.png","path":"img/qweather-color-icon/513.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/514.png","path":"img/qweather-color-icon/514.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/900.png","path":"img/qweather-color-icon/900.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/515.png","path":"img/qweather-color-icon/515.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/901.png","path":"img/qweather-color-icon/901.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/999.png","path":"img/qweather-color-icon/999.png","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/lib/prism/one-dark.css","path":"lib/prism/one-dark.css","modified":1,"renderable":1},{"_id":"themes/hexo-theme-cosy/source/lib/prism/one-light.css","path":"lib/prism/one-light.css","modified":1,"renderable":1},{"_id":"source/img/algolia-api-keys.png","path":"img/algolia-api-keys.png","modified":1,"renderable":0},{"_id":"source/img/avatar.png","path":"img/avatar.png","modified":1,"renderable":0},{"_id":"source/img/bolt.png","path":"img/bolt.png","modified":1,"renderable":0},{"_id":"source/img/grid_1.png","path":"img/grid_1.png","modified":1,"renderable":0},{"_id":"source/img/cvpilot.png","path":"img/cvpilot.png","modified":1,"renderable":0},{"_id":"source/img/hexo-theme-linear-dark.png","path":"img/hexo-theme-linear-dark.png","modified":1,"renderable":0},{"_id":"source/img/mz10nq.png","path":"img/mz10nq.png","modified":1,"renderable":0},{"_id":"source/img/shader_10.png","path":"img/shader_10.png","modified":1,"renderable":0},{"_id":"source/img/shader_1.png","path":"img/shader_1.png","modified":1,"renderable":0},{"_id":"source/img/shader_12.png","path":"img/shader_12.png","modified":1,"renderable":0},{"_id":"source/img/shader_11.png","path":"img/shader_11.png","modified":1,"renderable":0},{"_id":"source/img/shader_14.png","path":"img/shader_14.png","modified":1,"renderable":0},{"_id":"source/img/shader_13.png","path":"img/shader_13.png","modified":1,"renderable":0},{"_id":"source/img/shader_15.png","path":"img/shader_15.png","modified":1,"renderable":0},{"_id":"source/img/shader_16.png","path":"img/shader_16.png","modified":1,"renderable":0},{"_id":"source/img/shader_18.png","path":"img/shader_18.png","modified":1,"renderable":0},{"_id":"source/img/shader_17.png","path":"img/shader_17.png","modified":1,"renderable":0},{"_id":"source/img/shader_19.png","path":"img/shader_19.png","modified":1,"renderable":0},{"_id":"source/img/shader_2.png","path":"img/shader_2.png","modified":1,"renderable":0},{"_id":"source/img/shader_21.png","path":"img/shader_21.png","modified":1,"renderable":0},{"_id":"source/img/shader_20.png","path":"img/shader_20.png","modified":1,"renderable":0},{"_id":"source/img/shader_22.png","path":"img/shader_22.png","modified":1,"renderable":0},{"_id":"source/img/shader_23.png","path":"img/shader_23.png","modified":1,"renderable":0},{"_id":"source/img/shader_24.png","path":"img/shader_24.png","modified":1,"renderable":0},{"_id":"source/img/shader_25.png","path":"img/shader_25.png","modified":1,"renderable":0},{"_id":"source/img/shader_26.png","path":"img/shader_26.png","modified":1,"renderable":0},{"_id":"source/img/shader_27.png","path":"img/shader_27.png","modified":1,"renderable":0},{"_id":"source/img/shader_28.png","path":"img/shader_28.png","modified":1,"renderable":0},{"_id":"source/img/shader_29.png","path":"img/shader_29.png","modified":1,"renderable":0},{"_id":"source/img/shader_3.png","path":"img/shader_3.png","modified":1,"renderable":0},{"_id":"source/img/shader_30.png","path":"img/shader_30.png","modified":1,"renderable":0},{"_id":"source/img/shader_4.png","path":"img/shader_4.png","modified":1,"renderable":0},{"_id":"source/img/shader_5.png","path":"img/shader_5.png","modified":1,"renderable":0},{"_id":"source/img/shader_6.png","path":"img/shader_6.png","modified":1,"renderable":0},{"_id":"source/img/shader_7.png","path":"img/shader_7.png","modified":1,"renderable":0},{"_id":"source/img/shader_8.png","path":"img/shader_8.png","modified":1,"renderable":0},{"_id":"source/img/shader_9.png","path":"img/shader_9.png","modified":1,"renderable":0}],"Cache":[{"_id":"source/_posts/Markdown Sample.md","hash":"474ccea7a7fc40113e81c6ac17d332299f9a61ab","modified":1699409281242},{"_id":"source/_posts/Cosy-Starter-Guide.md","hash":"2e850ac0f61d4e22ab165280ee4f6489f993ce7c","modified":1700189823295},{"_id":"source/.DS_Store","hash":"7f1dadbc3bded867e8dcd5a0b7bf8cf97fea026c","modified":1699344729323},{"_id":"source/roadmap/index.md","hash":"fd571d7ff1b43a7905438f4a36710bc153fda7c6","modified":1700196759950},{"_id":"source/img/.DS_Store","hash":"df2fbeb1400acda0909a32c1cf6bf492f1121e07","modified":1698730015110},{"_id":"source/_posts/A Guide to Effective Dialogue.md","hash":"6b921148f345d387426eda1d8f8b2329d9b78194","modified":1699248300499},{"_id":"source/resume/index.md","hash":"b585667730d961c1fca9317763f3220a9df32371","modified":1698981341632},{"_id":"source/img/avatar.png","hash":"7c2da2939b1a36315c45489fbc9930bac73f0880","modified":1698729532245},{"_id":"source/img/grid_1.png","hash":"c464cd495e7b9fca7ea2eca75e8f3ba0376301ec","modified":1699405771999},{"_id":"source/img/shader_10.png","hash":"b680ce28ce113c09dc89aee5cf7b82efbb3d0feb","modified":1699247573640},{"_id":"source/img/shader_18.png","hash":"fd93d1fb5b4a7f1c3884bb8592bc4ecbeee75d2f","modified":1699247573643},{"_id":"source/img/shader_19.png","hash":"d5d252b8f676d9f6a81f9fdb1dbf869827294060","modified":1699247573643},{"_id":"source/img/shader_20.png","hash":"b5900021cd88644a8902d24aa04b2e940f766547","modified":1699247573645},{"_id":"source/img/shader_23.png","hash":"53456d8475ed2d70180de00e1869534369bf8ddb","modified":1699247573646},{"_id":"source/img/shader_4.png","hash":"5a6b452396cc87aadc4a03b1c44482a1d4ee2f0f","modified":1699247573656},{"_id":"source/_posts/finance/付鹏:展望2023年下半年全球经济格局,异常的利差意味着什么.md","hash":"4976b314cd7932e8e1333688491e506442900a37","modified":1699248127164},{"_id":"source/_posts/finance/付鹏:逆全球化下的全球资产配置.md","hash":"9d2db9097826fd7549ebb1831488ee927782c1df","modified":1699247867196},{"_id":"source/_posts/front-end/Unity Shader入门精要.md","hash":"2533731c1e134f7d97160ccc531f458c3de5c9c6","modified":1699258090905},{"_id":"source/_posts/front-end/css奇技淫巧.md","hash":"305722194038130992e33ecacbc4d6434dbfea65","modified":1699407210288},{"_id":"source/_posts/front-end/jquery.md","hash":"c9a96157a671867e444d806d0dbbcaf85d1dd926","modified":1699257755529},{"_id":"source/_posts/front-end/ddd.md","hash":"2983b3e1398ca9d0e41939cf31be4f130a562eaa","modified":1699257759167},{"_id":"source/_posts/front-end/git.md","hash":"eae431c353fdeeb919b12005db09dcf7453c86ac","modified":1699257754511},{"_id":"source/_posts/front-end/verdaccio.md","hash":"50453d3e60c7e63e31bc9f3efcebc241d317fae0","modified":1699257759971},{"_id":"source/_posts/front-end/wsl2.md","hash":"628674ee566a260abf5e6090435feb822a43be96","modified":1699257758239},{"_id":"source/_posts/ky/数据结构.md","hash":"af18d6defbd5a34358ad77d9526452b26f822cb0","modified":1700544295895},{"_id":"source/_posts/social/内部循环与产出意识.md","hash":"88fdb3f9b6e5d4b432a7011ee8bba3f6b41e6f83","modified":1699247971986},{"_id":"source/_posts/front-end/码场悟道.md","hash":"aba1107b1b90f852294f895e7f9bf20f410404e5","modified":1699339688822},{"_id":"source/_posts/social/改掉选择困难症.md","hash":"98dfb13d9c9771ecdb1648dae778d83bfc1dc869","modified":1699248014555},{"_id":"themes/hexo-theme-cosy/source/js/31d6cfe0.js","hash":"da39a3ee5e6b4b0d3255bfef95601890afd80709","modified":1700187362835},{"_id":"source/_posts/ky/eng/vocabulary.md","hash":"a1c1f8699c02bc0bf169a96a5ba79ef0fb05d652","modified":1700545747159},{"_id":"source/_posts/social/烦恼的原因.md","hash":"477a662420ffb2e51136ac59712cb5375eef20ea","modified":1699247993454},{"_id":"source/_posts/we-media/douyin.md","hash":"3bf6d9d1d658d534a40f51cddad5a7ca39b6154a","modified":1699947197858},{"_id":"source/_posts/social/非暴力沟通.md","hash":"629439b58652484c61fbda33103ae89437929fb7","modified":1699247939336},{"_id":"source/_posts/social/走神.md","hash":"86869ea38150a552128649f3aa5fd7a0b6a9c90e","modified":1699248013780},{"_id":"source/img/bolt.png","hash":"9f178950034e1ad5c5248b9caaca36e2f784225d","modified":1698730006343},{"_id":"source/_posts/social/有效对话指南.md","hash":"d20ec18cf5f8929d6af14b12a1c296bef007bc2b","modified":1699247891496},{"_id":"source/img/cvpilot.png","hash":"e10abaca3fd20488b0889a4da84d65312e5ac3ad","modified":1698730476343},{"_id":"source/img/shader_12.png","hash":"9d303ac8621c91088f97834733aa7903c70eaa43","modified":1699247573640},{"_id":"source/img/shader_11.png","hash":"954022f8fb580fce66b491db72df770591746362","modified":1699247573640},{"_id":"source/img/shader_14.png","hash":"bde142cb701d1f17cc8733b66cdc8166b8334f32","modified":1699247573641},{"_id":"source/img/shader_17.png","hash":"6274e99ab2e3d2053bdf2c0b9e3cc9b2b786f086","modified":1699247573643},{"_id":"source/img/shader_15.png","hash":"c3feca0caadb737da89ad5a5a509057d379952c7","modified":1699247573642},{"_id":"source/img/shader_16.png","hash":"9bec75831c21d063f23502612f106bfa86a0b100","modified":1699247573642},{"_id":"source/img/shader_2.png","hash":"767fe07c2ff75bdb58e1bd5487f373ea7b33a163","modified":1699247573644},{"_id":"source/img/shader_22.png","hash":"610a67538d93eb8c81da6c99b90fe751657f9266","modified":1699247573646},{"_id":"source/img/shader_28.png","hash":"079730db179a7b1f9539b929c33fc2be2b4a5bb5","modified":1699247573653},{"_id":"source/img/shader_3.png","hash":"1260a52b8fc6351c62c3bf9f1e2907f495dd2f4e","modified":1699247573655},{"_id":"source/img/shader_8.png","hash":"ebe473df6f8f19aec27a1e97301c533d82653096","modified":1699247573659},{"_id":"themes/hexo-theme-cosy/languages/ar.yml","hash":"c5bf5f32d55971bc27ceec59357bd0ba9c06dd3a","modified":1700187362872},{"_id":"themes/hexo-theme-cosy/languages/de.yml","hash":"b93ab51f36fa62193caf7101c4f4ba3b81e10da7","modified":1700187362872},{"_id":"themes/hexo-theme-cosy/_config.yml","hash":"cd8d1a25d42db7368d475e8ddb4f2941e189f214","modified":1700544255011},{"_id":"themes/hexo-theme-cosy/.DS_Store","hash":"6971f3f5a01e146585c1e8391a78a45b241e724f","modified":1700190143861},{"_id":"themes/hexo-theme-cosy/languages/en.yml","hash":"ef659dc0692fc269cabc224dcfc4c72aac3e3861","modified":1700187362871},{"_id":"themes/hexo-theme-cosy/languages/es.yml","hash":"21f4d88855f6afd785268da57e5762bddf9667a9","modified":1700187362871},{"_id":"themes/hexo-theme-cosy/languages/ko.yml","hash":"e38a9692cc247ea21f06e383b47cf85f0d287b50","modified":1700187362871},{"_id":"themes/hexo-theme-cosy/languages/fr.yml","hash":"9ecddd8cc6755a8e07e75066b7a4001963250106","modified":1700187362871},{"_id":"themes/hexo-theme-cosy/languages/ja.yml","hash":"dff9e0bddce6d4846829b3995115e829e4faa2c2","modified":1700187362871},{"_id":"themes/hexo-theme-cosy/languages/ru.yml","hash":"7571248b920136f7c58e3da045650857eb3cff14","modified":1700187362870},{"_id":"themes/hexo-theme-cosy/languages/zh-CN.yml","hash":"3bbda115a0a4113fa06906c3f888585ad5b9be39","modified":1700187362870},{"_id":"themes/hexo-theme-cosy/languages/zh-TW.yml","hash":"cb42a35cc73a8233b40a0afb612ec15fb714edea","modified":1700187362870},{"_id":"themes/hexo-theme-cosy/layout/airplane.ejs","hash":"28521fce0f5dbd1fd62c612ae68b78ba254faef3","modified":1700187362870},{"_id":"themes/hexo-theme-cosy/layout/category.ejs","hash":"c2653f82add02705f350edd193715b4872a6411c","modified":1700187362869},{"_id":"themes/hexo-theme-cosy/layout/breadcrumb.ejs","hash":"81c91ad7b618a760b901741ff0d87333acddf60c","modified":1700187362869},{"_id":"themes/hexo-theme-cosy/layout/archive.ejs","hash":"c6e3c47c195dc1b7551e25d6c6ccf22489a5abec","modified":1700187362869},{"_id":"themes/hexo-theme-cosy/layout/homebar.ejs","hash":"603903d1ac644e1a4500599d51aa7404dc64b4ab","modified":1700187362869},{"_id":"themes/hexo-theme-cosy/layout/navigation.ejs","hash":"d5bee071deb56ad28dcc4523f425705ce1967510","modified":1700187362868},{"_id":"themes/hexo-theme-cosy/layout/layout.ejs","hash":"5dd45c1b25e2a658c5901d9ec33f7abfcadcbb0e","modified":1700187362868},{"_id":"themes/hexo-theme-cosy/layout/index.ejs","hash":"42af2e637dc5e2148199ef4f13d58932d690ccd5","modified":1700187362869},{"_id":"themes/hexo-theme-cosy/layout/roadmap.ejs","hash":"e16ef0b34e2f285c3cfd7733e61aae75894a8394","modified":1700187362867},{"_id":"themes/hexo-theme-cosy/layout/postCopyright.ejs","hash":"499a9f2fae167592caf9f03f7988bbbb61481acb","modified":1700187362868},{"_id":"themes/hexo-theme-cosy/layout/weather.ejs","hash":"04052e95f80ac11765b90019b149d7fb1bb68bab","modified":1700187362867},{"_id":"themes/hexo-theme-cosy/layout/post.ejs","hash":"1d755a0a1eefb6a59c6a53115f6f729167611732","modified":1700187362868},{"_id":"themes/hexo-theme-cosy/scripts/generate_category.js","hash":"03af36ba93e3ee17572e3f826430989119b1772a","modified":1700187362866},{"_id":"themes/hexo-theme-cosy/scripts/generate_excerpt.js","hash":"a7cfe46e7e389b1654c81f3e9a8a934dd80d735a","modified":1700187362866},{"_id":"themes/hexo-theme-cosy/scripts/mermaid_tag.js","hash":"bc74c79cf41709da1993597acaebae96d6b3761d","modified":1700187362865},{"_id":"themes/hexo-theme-cosy/layout/resume.ejs","hash":"4ecb48cb1e9bd5c2134b8e4e82ed0ef9df2d4b67","modified":1700187362868},{"_id":"themes/hexo-theme-cosy/layout/search.ejs","hash":"f3faa8ba2d4e12e615e9554079c8465b68f3b495","modified":1700187362867},{"_id":"themes/hexo-theme-cosy/scripts/postIcon.js","hash":"73362a6570b00613681425b59a30903be671cd91","modified":1700187362865},{"_id":"themes/hexo-theme-cosy/scripts/generate_nav.js","hash":"3d58530838daf9e6225b4474b9ac230a55f1e0d4","modified":1700187362866},{"_id":"themes/hexo-theme-cosy/scripts/post_counter.js","hash":"62aaa594dafcffe5aa457628eaf183075cabe159","modified":1700187362865},{"_id":"themes/hexo-theme-cosy/scripts/road-to-json.js","hash":"7fddd6117979d983e2172a04dd8764459a9c626b","modified":1700187362865},{"_id":"themes/hexo-theme-cosy/scripts/sort_posts.js","hash":"16818ebbeb612f34d07cf9e285d42645eac31805","modified":1700187362865},{"_id":"themes/hexo-theme-cosy/layout/welcome.ejs","hash":"ce7419f61089411de4cea72165a4ce89c4d55fbd","modified":1700187362867},{"_id":"themes/hexo-theme-cosy/source/css/3efc6cb5.css","hash":"1f1958bfd921ac3eea83a1e14ed97e8e83b31fb6","modified":1700187362863},{"_id":"themes/hexo-theme-cosy/source/css/0c63d269.css","hash":"6b9b9796cf8ff9349b724f4b789f1347eaa0d476","modified":1700187362864},{"_id":"themes/hexo-theme-cosy/source/css/5c728363.css","hash":"715635cbd3ce46fd0b82117bc757fab6d2277070","modified":1700187362863},{"_id":"themes/hexo-theme-cosy/source/css/5bfc518f.css","hash":"4b7c03f245b3d60c366321e6cad11ceee7c0a131","modified":1700187362863},{"_id":"themes/hexo-theme-cosy/source/css/68d4249e.css","hash":"1e3c7c24bbac6eb2b9377e318e6ffb7bc4b90ee1","modified":1700187362863},{"_id":"themes/hexo-theme-cosy/source/css/69c863a0.css","hash":"9047a6d26295eb0245c3e1dd9db3de600e0e4bda","modified":1700187362862},{"_id":"themes/hexo-theme-cosy/source/css/80d65618.css","hash":"3928a4a222e7353ed86a540aac9c9b6448ea9ff5","modified":1700187362862},{"_id":"themes/hexo-theme-cosy/source/css/3a4a90d1.css","hash":"4ffc2c5e9f1afd4fa74d7d9b785e0697cd7fb770","modified":1700187362864},{"_id":"themes/hexo-theme-cosy/source/css/82dd7e5a.css","hash":"c9561c3c190498369201f1ed27b6c17d255e0314","modified":1700187362862},{"_id":"themes/hexo-theme-cosy/source/css/c080db9c.css","hash":"7caf9f961e4c0a26c53e1e37ce3d88d93d0be5ff","modified":1700187362862},{"_id":"themes/hexo-theme-cosy/source/css/de5de8fb.css","hash":"a05682caa4e0b8a106578468f795dad472ee75da","modified":1700187362861},{"_id":"themes/hexo-theme-cosy/source/css/dba23209.css","hash":"c03ff22c4a332f16b5f4ce22e3394ee05010027a","modified":1700187362861},{"_id":"themes/hexo-theme-cosy/source/css/efca006a.css","hash":"97972d1b7ca177983b39a67e772736f661637468","modified":1700187362861},{"_id":"themes/hexo-theme-cosy/source/css/e7914205.css","hash":"121b14a58b519c3fc538173189372800dea0d3de","modified":1700187362861},{"_id":"themes/hexo-theme-cosy/source/css/f3729dde.css","hash":"2a7faa0c3816e3b653fb4540fa63504a26b2051f","modified":1700187362861},{"_id":"themes/hexo-theme-cosy/source/font/motto.woff","hash":"755620f3bad3ebdf683c074043104d4c11f8d23e","modified":1700187362860},{"_id":"themes/hexo-theme-cosy/source/font/linear.woff2","hash":"57cac19ad34a50d5a4da5e471e08174c950ce5fb","modified":1700187362860},{"_id":"themes/hexo-theme-cosy/source/img/icon-click.svg","hash":"b10df8b886a8b2d44293b26f42b440c9aedb66a7","modified":1700187362859},{"_id":"themes/hexo-theme-cosy/source/img/favicon.svg","hash":"6e88f8a231bb0a7ae4cc4598f85b6d346a286095","modified":1700187362859},{"_id":"themes/hexo-theme-cosy/source/img/icon-arrow-left.svg","hash":"a36362d2555e8b836fcec1f7eeeae4588a871bdd","modified":1700187362859},{"_id":"themes/hexo-theme-cosy/source/img/icon-book.svg","hash":"d5e4064468dde477bf9a630c3fababec41316708","modified":1700187362859},{"_id":"themes/hexo-theme-cosy/source/img/icon-date.svg","hash":"b391e34adafed83ef52d836fd6f81618494c4c6b","modified":1700187362858},{"_id":"themes/hexo-theme-cosy/source/img/icon-link.svg","hash":"436b5ee7ef2d28766e86ac1e65a567d1e786c1b3","modified":1700187362858},{"_id":"themes/hexo-theme-cosy/source/img/icon-moon.svg","hash":"032be7ed3d2320f22069e2efb2fb7f60592d6212","modified":1700187362858},{"_id":"themes/hexo-theme-cosy/source/img/icon-layout.svg","hash":"108ef43073a5b92552dc00744a9f11db8e5ac0a2","modified":1700187362858},{"_id":"themes/hexo-theme-cosy/source/img/icon2.svg","hash":"cc598540651110d977afd26dc0a1f01bbc95bf21","modified":1700187362857},{"_id":"themes/hexo-theme-cosy/source/img/icon1.svg","hash":"f15fbcecbaa00db99aeaca9807922514f6452d02","modified":1700187362857},{"_id":"themes/hexo-theme-cosy/source/img/icon3.svg","hash":"a1a9dcee7703ec48f2d92b0d533b62fef16097fc","modified":1700187362857},{"_id":"themes/hexo-theme-cosy/source/img/icon-sun.svg","hash":"57a0ce52ecce7188eaac5e06eab54609a8e572c8","modified":1700187362857},{"_id":"themes/hexo-theme-cosy/source/js/9f1cd854.js","hash":"070029c5073bc3e67d98f1ac377d12f585bb4a2f","modified":1700187362834},{"_id":"themes/hexo-theme-cosy/source/js/3cf4fd98.js","hash":"0d51155f6dc08d1d8c693d210c1407b2e26b5685","modified":1700187362835},{"_id":"themes/hexo-theme-cosy/source/js/58c91c4e.js","hash":"1935dee981143040a708a1144fd2a57894ce3137","modified":1700187362835},{"_id":"themes/hexo-theme-cosy/source/js/ae2a0e7b.js","hash":"d71a6f691ba45862066ad15bb7f8b3bd2ad9f5f5","modified":1700187362834},{"_id":"themes/hexo-theme-cosy/source/js/b9c2be9c.js.LICENSE.txt","hash":"a8820a0de0a074d43cb1f7db8eee78c8dbfd4d87","modified":1700187362834},{"_id":"themes/hexo-theme-cosy/source/js/b9c2be9c.js","hash":"c03c1885896b368caf85bf273a8148428e649f59","modified":1700187362834},{"_id":"themes/hexo-theme-cosy/source/js/ca6b30b5.js","hash":"481a8f20c3d4e565c1ea5dafe4218597d051a9fc","modified":1700187362833},{"_id":"themes/hexo-theme-cosy/source/js/c413ebaa.js","hash":"dd282af849c44345ad3e577efc8fa291468de653","modified":1700187362833},{"_id":"themes/hexo-theme-cosy/source/js/cdca7001.js","hash":"cd27e408febd15a99bafdf9da8a834bb1b6902c3","modified":1700187362833},{"_id":"themes/hexo-theme-cosy/source/js/d3872ea1.js","hash":"2c81c164e89dddf76fcee864da25ecf8e074248e","modified":1700187362832},{"_id":"themes/hexo-theme-cosy/source/js/f8b20eb9.js","hash":"86cff2cb169c614dedd464b373d94322328cb185","modified":1700187362832},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/100.png","hash":"ad42001979a0dbb8807c128b871dc28161b8c191","modified":1700187362856},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/101.png","hash":"890888efd8db7a3f29427e476ce3433f4a564321","modified":1700187362855},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/150.png","hash":"572b4a6e56271b89208daa538f02416d50249347","modified":1700187362854},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/103.png","hash":"656d81bd50728c7ba62572e838db7c355d522e51","modified":1700187362855},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/151.png","hash":"8041e428fc369be40f9e993f4f718ae99c7662fa","modified":1700187362854},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/102.png","hash":"74d7cfa4f23850a456c16d0e0956264e920f7a85","modified":1700187362855},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/153.png","hash":"3eb50d325ec84e1248585d347471b1093a83909c","modified":1700187362854},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/104.png","hash":"36add97ed90d57e691e6e15d3456898a42dccb16","modified":1700187362855},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/152.png","hash":"3ec0a1ac063ae068cc9d728eef88a9a6d7859153","modified":1700187362854},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/302.png","hash":"371fa4d79b6a93573fde068f179829ed97c8f4f8","modified":1700187362852},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/303.png","hash":"453442f93e62ac98884aae173530f26272d97ad6","modified":1700187362852},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/301.png","hash":"cc616d4c7ae6abbe0338f418fe35acce93c46164","modified":1700187362853},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/300.png","hash":"bd67445ff764f3f1d4eeb81625372585bb537b07","modified":1700187362853},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/304.png","hash":"3669eb04ee87dca604f70f7404c0ecb303d17af4","modified":1700187362852},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/305.png","hash":"618e18bf06b5d12d64269b30d6f044fc356d24b0","modified":1700187362851},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/308.png","hash":"ace460a40174aa90dde6c2ef71663f30cec24193","modified":1700187362850},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/306.png","hash":"69b55e71e8da4e0fe223ff76b9a68f98e4134b3d","modified":1700187362851},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/307.png","hash":"02a08e002d21a10e0854b1036556fb81b0308be3","modified":1700187362850},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/309.png","hash":"c5140be49b8936e5d30380f14f7dce49a7be7cb5","modified":1700187362849},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/310.png","hash":"9315203066fa97a7d10f2a6fb0fd3ce5b7805126","modified":1700187362849},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/313.png","hash":"afa70386816623e847c09f51032212d2520d1740","modified":1700187362848},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/311.png","hash":"ab5c5059f354da2f9525b30ee81312fbfa4749bf","modified":1700187362848},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/312.png","hash":"c672d7373fce89803b5e8d06420e158f11ea346f","modified":1700187362848},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/314.png","hash":"110f9fe81f80b8f372b7472fbcfd9c0071b33151","modified":1700187362847},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/316.png","hash":"4de1ac0254507c5509fc4d04e208fd81d2af7b34","modified":1700187362846},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/315.png","hash":"2100a23ee6d584227715164263c43875e8d8946c","modified":1700187362847},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/317.png","hash":"7e60725abd3b91b9ba388ad9b9f642690c39ff9e","modified":1700187362846},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/318.png","hash":"7a042920165671cdbfef6c58596a80f9a1f6f016","modified":1700187362845},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/400.png","hash":"79444a48a0e416e4cac1eb319496f495091f4c86","modified":1700187362844},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/350.png","hash":"f29c1aa55c4afd3df17aef7885a35b0bd177d771","modified":1700187362845},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/351.png","hash":"3ab30470a65acb8fe879aa7adc9064284abd8407","modified":1700187362845},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/399.png","hash":"993ddcd386480d211ec9b2e56656d659bb6c453b","modified":1700187362844},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/402.png","hash":"033fc236885a2bf7007d97eb054b0c5885035be5","modified":1700187362843},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/401.png","hash":"279c8bc00b4e57db9b1095df64044ec80b37edb0","modified":1700187362844},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/404.png","hash":"112e73e641d1a30712993b5dca681a5e033831ee","modified":1700187362843},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/403.png","hash":"dfad1a525c4467c3e95e281befdf813e145620df","modified":1700187362843},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/406.png","hash":"b9cd0421518e0a76041285e5bf0c1666a93428a5","modified":1700187362842},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/407.png","hash":"82223792a5e3e556148b4663f195ef22044c143a","modified":1700187362842},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/408.png","hash":"02b34a66020c9f3e5173702d30e5a7b69139bade","modified":1700187362841},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/409.png","hash":"aed269911d0249a700b4f3890c424f00f4a27e1e","modified":1700187362841},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/410.png","hash":"9170b32d6b7f644ce49116f3e35d35558bae6536","modified":1700187362841},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/456.png","hash":"dd16b957cc544730afab8d2712821dd6c77f5167","modified":1700187362840},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/499.png","hash":"2b51631144a7c0f813b6425d4daa30c4d4e8bd38","modified":1700187362840},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/500.png","hash":"ddb4712d8f19bb8c197e600000dd2d51049f970d","modified":1700187362840},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/501.png","hash":"ddb4712d8f19bb8c197e600000dd2d51049f970d","modified":1700187362839},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/457.png","hash":"5dffe7e9139bfb697b046c427b9ef0ed6ffa95c7","modified":1700187362840},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/504.png","hash":"defba93520719f72b217583062ccc79abd5b445e","modified":1700187362839},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/502.png","hash":"4146081a2635ff88fa14e38ed8d360d3b4fa74dd","modified":1700187362839},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/507.png","hash":"426d5a73a482ac5721e7da2141e4fe704f50b608","modified":1700187362839},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/508.png","hash":"ae98a3217df26021ec2f667f099d27575c912bf4","modified":1700187362838},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/509.png","hash":"3bc779bfbcab94a79c91b26288e2e7b67412d15e","modified":1700187362838},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/503.png","hash":"9415147c4bcebadd7f3089339064b8120c8d4089","modified":1700187362839},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/511.png","hash":"e2efe07a29446ebecd313ccd8a2c7d57a670f203","modified":1700187362838},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/510.png","hash":"524131e401d0b150dab2733af336f4649b8ade74","modified":1700187362838},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/512.png","hash":"64e11f225c35caa6ef2612d613026c52cfd3557e","modified":1700187362837},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/514.png","hash":"fdf992021ff20e1ffe1b19f0b918aa35204f22e7","modified":1700187362837},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/513.png","hash":"ceff3dfd109a990c9595276ad6b56061bb662e5e","modified":1700187362837},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/900.png","hash":"6eacf8df641c6096feb746c7544a825d3c65bf47","modified":1700187362836},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/515.png","hash":"fdf992021ff20e1ffe1b19f0b918aa35204f22e7","modified":1700187362837},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/901.png","hash":"f6b3f1cd64e9c325e9dc3ab42469baa5c5119c2d","modified":1700187362836},{"_id":"themes/hexo-theme-cosy/source/img/qweather-color-icon/999.png","hash":"74e4fa5bdd815d988b55525d4e7f6d40bf1080d4","modified":1700187362836},{"_id":"source/img/shader_1.png","hash":"559caf74cb63637b58483773f72b7589408ceaf9","modified":1699247573639},{"_id":"source/img/hexo-theme-linear-dark.png","hash":"758aef1e3d9a83844d2871e8ceaccf6767e78284","modified":1698730623818},{"_id":"source/img/shader_13.png","hash":"e1eb1a13ffe9ed74b2f79f2e97c4134f10988657","modified":1699247573641},{"_id":"source/img/shader_21.png","hash":"a381d25344aaf2da7e29a7f157f3c2d1b0a8f813","modified":1699247573645},{"_id":"source/img/shader_27.png","hash":"285b22fb20437acaf07a9a3aa37770802d1b7202","modified":1699247573653},{"_id":"source/img/shader_6.png","hash":"7f96d58b93a0149993a7aa02d1d27c75e0ce0936","modified":1699247573657},{"_id":"source/img/shader_5.png","hash":"07822798c3008fe841798993f75b50f104de5e11","modified":1699247573657},{"_id":"source/img/shader_30.png","hash":"35610b6e8877b16bc2140ec1e13a104188ac2f6b","modified":1699247573656},{"_id":"source/img/shader_9.png","hash":"601cd60bc9837dc0603f82991334e084e7074aec","modified":1699247573660},{"_id":"themes/hexo-theme-cosy/source/lib/prism/one-dark.css","hash":"5967bad8dd34b1eb244956064ae7870f62e6cd75","modified":1700187362831},{"_id":"themes/hexo-theme-cosy/source/lib/prism/one-light.css","hash":"dd7660b35884866eee48f069e876f1c29661a150","modified":1700187362831},{"_id":"source/img/shader_29.png","hash":"17acdcaa715bc68eb654b1e6db4fe0b4364d6394","modified":1699247573654},{"_id":"source/img/shader_7.png","hash":"23388d247c00f832e544954f4fa6956c03fec16d","modified":1699247573658},{"_id":"themes/hexo-theme-cosy/source/img/logo.png","hash":"05730ab0dadd5036509f942834278eef683994dd","modified":1700187362856},{"_id":"source/img/algolia-api-keys.png","hash":"8b2f85a93b52f0590e9b8fe2d2405427c99e2280","modified":1697779286767},{"_id":"source/img/shader_24.png","hash":"8eba8647f182532ac37462c15aa11a6e2d4ee196","modified":1699247573648},{"_id":"source/img/shader_26.png","hash":"3433565461d058e381b1c5a13db978d6f470025a","modified":1699247573652},{"_id":"source/img/shader_25.png","hash":"9be25a68c628071e583e3985929c321ebe6d13cb","modified":1699247573651},{"_id":"source/img/mz10nq.png","hash":"e26097f94ae54967e68f36b13b6aca80c4d173de","modified":1699247796443},{"_id":"public/roadmap/index.html","hash":"10e8d863bbb3d718a3a19aceac193d5e65089fba","modified":1700545753296},{"_id":"public/2023/11/06/clp7x1954000zv3z39rtjhte9/index.html","hash":"052a920b36c2410156b88878406f07c6381aefef","modified":1700545753296},{"_id":"public/2023/11/06/clp7x1953000xv3z360nugo0n/index.html","hash":"a5cb5184708ed8cd123dda219e40d3551a843f56","modified":1700545753296},{"_id":"public/2023/11/06/clp7x1953000tv3z3chgh7c1y/index.html","hash":"3df6b5d0ff0316cee1b0ae659fc52547799a9323","modified":1700545753296},{"_id":"public/2023/11/06/clp7x19540012v3z38lxw0f8a/index.html","hash":"37a4a29296bb5eb52285691f9cbbe48805908da7","modified":1700545753296},{"_id":"public/2023/11/06/clp7x1952000qv3z3b1fm22sp/index.html","hash":"388ad782cd0968c6d4125cc9b6ad56a28c1d9688","modified":1700545753296},{"_id":"public/categories/Hexo/index.html","hash":"a89ea1fc0322bbe3f0e3e73eb61abf548dd9a4ea","modified":1700545753296},{"_id":"public/categories/Finance/index.html","hash":"dc88d03258b35bb0bba63388072aa65774332b5a","modified":1700545753296},{"_id":"public/categories/求学之路/index.html","hash":"c818839d2c2fff3d0597091aaaebe488ca68eac0","modified":1700545753296},{"_id":"public/categories/自媒体/index.html","hash":"b64d7e44a0ce192b5113465a279e6d5c059ab742","modified":1700545753296},{"_id":"public/resume/index.html","hash":"130970c5aa827f834bd6664ce0d85bfb73e653c6","modified":1700545753296},{"_id":"public/2023/11/21/clp7x1956001lv3z35ie105dj/index.html","hash":"d506178da5352430b62af297218c1d10444f1f18","modified":1700545753296},{"_id":"public/2023/11/14/clp7x1956001kv3z3a8182w5o/index.html","hash":"1d062bcd1bfc11672e9b7df115489c40e0a159da","modified":1700545753296},{"_id":"public/2023/11/21/clp7x1952000pv3z341mb8q1p/index.html","hash":"821907229d2c570919a5b687c18efce312111a4d","modified":1700545753296},{"_id":"public/2023/11/06/clp7x194x000av3z3bf895az5/index.html","hash":"1a59d7222ba58b1ecc93fe1c6ad2d71a588f6aba","modified":1700545753296},{"_id":"public/2023/11/06/clp7x194z000hv3z3dcryc9ir/index.html","hash":"aecadcf48fdf606b022bce5910c83babceb26208","modified":1700545753296},{"_id":"public/2023/11/06/clp7x194y000dv3z35mt50jup/index.html","hash":"e29863d06d1dadf180fce6eca354586c48a58bc4","modified":1700545753296},{"_id":"public/2023/11/06/clp7x194x0009v3z338b7g5v8/index.html","hash":"7557b58fcef5211eb0e6be14d3f1a2f1cee322e4","modified":1700545753296},{"_id":"public/2023/11/06/clp7x1951000iv3z3gvgqbt7a/index.html","hash":"a7c5059f05464b2d136f21915a4037b084af6dcf","modified":1700545753296},{"_id":"public/2023/11/06/clp7x1952000mv3z39lqc2ecv/index.html","hash":"b86f4d6d7f8b28c321e97fabe25ef843f0208622","modified":1700545753296},{"_id":"public/2023/11/06/clp7x194z000ev3z3ef2z8117/index.html","hash":"d13c8e0601beb5cf896935661482345089179fdd","modified":1700545753296},{"_id":"public/2023/11/06/clp7x194s0001v3z3dnez8x8d/index.html","hash":"f0cb4b066189fea54e5a27386dafe1c517b78889","modified":1700545753296},{"_id":"public/2023/11/06/clp7x194w0007v3z35r7f2ge1/index.html","hash":"a56b59c1bed174aa363d0c9b110c306a5fe6cebf","modified":1700545753296},{"_id":"public/2023/11/06/clp7x194w0006v3z3akxthpwo/index.html","hash":"6fd62833a58d6f68a928e559ffdbc01a45be1936","modified":1700545753296},{"_id":"public/2023/10/24/clp7x1953000uv3z39z0vh1ra/index.html","hash":"e23d69d8cd4e2f320785af3e3982ad82ac30959e","modified":1700545753296},{"_id":"public/2023/10/23/clp7x194u0003v3z3cgik2min/index.html","hash":"42470453cfa9302832c988a8c177ad3d5f474152","modified":1700545753296},{"_id":"public/2023/10/23/clp7x194w0005v3z30pgk9e5n/index.html","hash":"db0679959c5ff49e4a700854d8424d2045e72f02","modified":1700545753296},{"_id":"public/2023/10/20/clp7x1951000lv3z35336cspf/index.html","hash":"bf6a5d82d5c3741bd722508947eeb744c4e01c39","modified":1700545753296},{"_id":"public/archives/index.html","hash":"f8c60bcf8acd12234980b2e1942a987a6c2c0c27","modified":1700545753296},{"_id":"public/archives/page/2/index.html","hash":"f8c60bcf8acd12234980b2e1942a987a6c2c0c27","modified":1700545753296},{"_id":"public/archives/page/3/index.html","hash":"f8c60bcf8acd12234980b2e1942a987a6c2c0c27","modified":1700545753296},{"_id":"public/archives/2023/index.html","hash":"f8c60bcf8acd12234980b2e1942a987a6c2c0c27","modified":1700545753296},{"_id":"public/archives/2023/page/2/index.html","hash":"f8c60bcf8acd12234980b2e1942a987a6c2c0c27","modified":1700545753296},{"_id":"public/archives/2023/page/3/index.html","hash":"f8c60bcf8acd12234980b2e1942a987a6c2c0c27","modified":1700545753296},{"_id":"public/archives/2023/10/index.html","hash":"f8c60bcf8acd12234980b2e1942a987a6c2c0c27","modified":1700545753296},{"_id":"public/archives/2023/11/index.html","hash":"f8c60bcf8acd12234980b2e1942a987a6c2c0c27","modified":1700545753296},{"_id":"public/archives/2023/11/page/2/index.html","hash":"f8c60bcf8acd12234980b2e1942a987a6c2c0c27","modified":1700545753296},{"_id":"public/categories/EQ/index.html","hash":"8566abb0b5dd52351c9a278552cc2798c67b2fe8","modified":1700545753296},{"_id":"public/categories/Front-End/index.html","hash":"78b0a6707c538ae3dd3ed7b373f575e05657d76b","modified":1700545753296},{"_id":"public/index.html","hash":"298818b7aeba0b70df268f6db4a86a0e0a8973d1","modified":1700545753296},{"_id":"public/page/2/index.html","hash":"298818b7aeba0b70df268f6db4a86a0e0a8973d1","modified":1700545753296},{"_id":"public/page/3/index.html","hash":"298818b7aeba0b70df268f6db4a86a0e0a8973d1","modified":1700545753296},{"_id":"public/img/icon-click.svg","hash":"b10df8b886a8b2d44293b26f42b440c9aedb66a7","modified":1700545753296},{"_id":"public/img/favicon.svg","hash":"6e88f8a231bb0a7ae4cc4598f85b6d346a286095","modified":1700545753296},{"_id":"public/img/icon-arrow-left.svg","hash":"a36362d2555e8b836fcec1f7eeeae4588a871bdd","modified":1700545753296},{"_id":"public/img/icon-link.svg","hash":"436b5ee7ef2d28766e86ac1e65a567d1e786c1b3","modified":1700545753296},{"_id":"public/img/icon-book.svg","hash":"d5e4064468dde477bf9a630c3fababec41316708","modified":1700545753296},{"_id":"public/img/icon-date.svg","hash":"b391e34adafed83ef52d836fd6f81618494c4c6b","modified":1700545753296},{"_id":"public/img/icon-moon.svg","hash":"032be7ed3d2320f22069e2efb2fb7f60592d6212","modified":1700545753296},{"_id":"public/img/icon-layout.svg","hash":"108ef43073a5b92552dc00744a9f11db8e5ac0a2","modified":1700545753296},{"_id":"public/font/motto.woff","hash":"755620f3bad3ebdf683c074043104d4c11f8d23e","modified":1700545753296},{"_id":"public/img/icon1.svg","hash":"f15fbcecbaa00db99aeaca9807922514f6452d02","modified":1700545753296},{"_id":"public/img/icon2.svg","hash":"cc598540651110d977afd26dc0a1f01bbc95bf21","modified":1700545753296},{"_id":"public/js/b9c2be9c.js.LICENSE.txt","hash":"a8820a0de0a074d43cb1f7db8eee78c8dbfd4d87","modified":1700545753296},{"_id":"public/img/icon3.svg","hash":"a1a9dcee7703ec48f2d92b0d533b62fef16097fc","modified":1700545753296},{"_id":"public/img/qweather-color-icon/100.png","hash":"ad42001979a0dbb8807c128b871dc28161b8c191","modified":1700545753296},{"_id":"public/img/qweather-color-icon/101.png","hash":"890888efd8db7a3f29427e476ce3433f4a564321","modified":1700545753296},{"_id":"public/img/icon-sun.svg","hash":"57a0ce52ecce7188eaac5e06eab54609a8e572c8","modified":1700545753296},{"_id":"public/font/linear.woff2","hash":"57cac19ad34a50d5a4da5e471e08174c950ce5fb","modified":1700545753296},{"_id":"public/img/qweather-color-icon/102.png","hash":"74d7cfa4f23850a456c16d0e0956264e920f7a85","modified":1700545753296},{"_id":"public/img/qweather-color-icon/151.png","hash":"8041e428fc369be40f9e993f4f718ae99c7662fa","modified":1700545753296},{"_id":"public/img/qweather-color-icon/153.png","hash":"3eb50d325ec84e1248585d347471b1093a83909c","modified":1700545753296},{"_id":"public/img/qweather-color-icon/103.png","hash":"656d81bd50728c7ba62572e838db7c355d522e51","modified":1700545753296},{"_id":"public/img/qweather-color-icon/150.png","hash":"572b4a6e56271b89208daa538f02416d50249347","modified":1700545753296},{"_id":"public/img/qweather-color-icon/152.png","hash":"3ec0a1ac063ae068cc9d728eef88a9a6d7859153","modified":1700545753296},{"_id":"public/img/qweather-color-icon/302.png","hash":"371fa4d79b6a93573fde068f179829ed97c8f4f8","modified":1700545753296},{"_id":"public/img/qweather-color-icon/301.png","hash":"cc616d4c7ae6abbe0338f418fe35acce93c46164","modified":1700545753296},{"_id":"public/img/qweather-color-icon/304.png","hash":"3669eb04ee87dca604f70f7404c0ecb303d17af4","modified":1700545753296},{"_id":"public/img/qweather-color-icon/104.png","hash":"36add97ed90d57e691e6e15d3456898a42dccb16","modified":1700545753296},{"_id":"public/img/qweather-color-icon/303.png","hash":"453442f93e62ac98884aae173530f26272d97ad6","modified":1700545753296},{"_id":"public/img/qweather-color-icon/300.png","hash":"bd67445ff764f3f1d4eeb81625372585bb537b07","modified":1700545753296},{"_id":"public/img/qweather-color-icon/306.png","hash":"69b55e71e8da4e0fe223ff76b9a68f98e4134b3d","modified":1700545753296},{"_id":"public/img/qweather-color-icon/307.png","hash":"02a08e002d21a10e0854b1036556fb81b0308be3","modified":1700545753296},{"_id":"public/img/qweather-color-icon/305.png","hash":"618e18bf06b5d12d64269b30d6f044fc356d24b0","modified":1700545753296},{"_id":"public/img/qweather-color-icon/308.png","hash":"ace460a40174aa90dde6c2ef71663f30cec24193","modified":1700545753296},{"_id":"public/img/qweather-color-icon/310.png","hash":"9315203066fa97a7d10f2a6fb0fd3ce5b7805126","modified":1700545753296},{"_id":"public/img/qweather-color-icon/311.png","hash":"ab5c5059f354da2f9525b30ee81312fbfa4749bf","modified":1700545753296},{"_id":"public/img/qweather-color-icon/309.png","hash":"c5140be49b8936e5d30380f14f7dce49a7be7cb5","modified":1700545753296},{"_id":"public/img/qweather-color-icon/314.png","hash":"110f9fe81f80b8f372b7472fbcfd9c0071b33151","modified":1700545753296},{"_id":"public/img/qweather-color-icon/313.png","hash":"afa70386816623e847c09f51032212d2520d1740","modified":1700545753296},{"_id":"public/img/qweather-color-icon/315.png","hash":"2100a23ee6d584227715164263c43875e8d8946c","modified":1700545753296},{"_id":"public/img/qweather-color-icon/312.png","hash":"c672d7373fce89803b5e8d06420e158f11ea346f","modified":1700545753296},{"_id":"public/img/qweather-color-icon/317.png","hash":"7e60725abd3b91b9ba388ad9b9f642690c39ff9e","modified":1700545753296},{"_id":"public/img/qweather-color-icon/350.png","hash":"f29c1aa55c4afd3df17aef7885a35b0bd177d771","modified":1700545753296},{"_id":"public/img/qweather-color-icon/318.png","hash":"7a042920165671cdbfef6c58596a80f9a1f6f016","modified":1700545753296},{"_id":"public/img/qweather-color-icon/316.png","hash":"4de1ac0254507c5509fc4d04e208fd81d2af7b34","modified":1700545753296},{"_id":"public/img/qweather-color-icon/351.png","hash":"3ab30470a65acb8fe879aa7adc9064284abd8407","modified":1700545753296},{"_id":"public/img/qweather-color-icon/399.png","hash":"993ddcd386480d211ec9b2e56656d659bb6c453b","modified":1700545753296},{"_id":"public/img/qweather-color-icon/400.png","hash":"79444a48a0e416e4cac1eb319496f495091f4c86","modified":1700545753296},{"_id":"public/img/qweather-color-icon/401.png","hash":"279c8bc00b4e57db9b1095df64044ec80b37edb0","modified":1700545753296},{"_id":"public/img/qweather-color-icon/402.png","hash":"033fc236885a2bf7007d97eb054b0c5885035be5","modified":1700545753296},{"_id":"public/img/qweather-color-icon/403.png","hash":"dfad1a525c4467c3e95e281befdf813e145620df","modified":1700545753296},{"_id":"public/img/qweather-color-icon/404.png","hash":"112e73e641d1a30712993b5dca681a5e033831ee","modified":1700545753296},{"_id":"public/img/qweather-color-icon/406.png","hash":"b9cd0421518e0a76041285e5bf0c1666a93428a5","modified":1700545753296},{"_id":"public/img/qweather-color-icon/407.png","hash":"82223792a5e3e556148b4663f195ef22044c143a","modified":1700545753296},{"_id":"public/img/qweather-color-icon/409.png","hash":"aed269911d0249a700b4f3890c424f00f4a27e1e","modified":1700545753296},{"_id":"public/img/qweather-color-icon/410.png","hash":"9170b32d6b7f644ce49116f3e35d35558bae6536","modified":1700545753296},{"_id":"public/img/qweather-color-icon/408.png","hash":"02b34a66020c9f3e5173702d30e5a7b69139bade","modified":1700545753296},{"_id":"public/img/qweather-color-icon/456.png","hash":"dd16b957cc544730afab8d2712821dd6c77f5167","modified":1700545753296},{"_id":"public/img/qweather-color-icon/457.png","hash":"5dffe7e9139bfb697b046c427b9ef0ed6ffa95c7","modified":1700545753296},{"_id":"public/img/qweather-color-icon/499.png","hash":"2b51631144a7c0f813b6425d4daa30c4d4e8bd38","modified":1700545753296},{"_id":"public/img/qweather-color-icon/500.png","hash":"ddb4712d8f19bb8c197e600000dd2d51049f970d","modified":1700545753296},{"_id":"public/img/qweather-color-icon/501.png","hash":"ddb4712d8f19bb8c197e600000dd2d51049f970d","modified":1700545753296},{"_id":"public/img/qweather-color-icon/503.png","hash":"9415147c4bcebadd7f3089339064b8120c8d4089","modified":1700545753296},{"_id":"public/img/qweather-color-icon/504.png","hash":"defba93520719f72b217583062ccc79abd5b445e","modified":1700545753296},{"_id":"public/img/qweather-color-icon/508.png","hash":"ae98a3217df26021ec2f667f099d27575c912bf4","modified":1700545753296},{"_id":"public/img/qweather-color-icon/502.png","hash":"4146081a2635ff88fa14e38ed8d360d3b4fa74dd","modified":1700545753296},{"_id":"public/img/qweather-color-icon/509.png","hash":"3bc779bfbcab94a79c91b26288e2e7b67412d15e","modified":1700545753296},{"_id":"public/img/qweather-color-icon/510.png","hash":"524131e401d0b150dab2733af336f4649b8ade74","modified":1700545753296},{"_id":"public/img/qweather-color-icon/507.png","hash":"426d5a73a482ac5721e7da2141e4fe704f50b608","modified":1700545753296},{"_id":"public/img/qweather-color-icon/512.png","hash":"64e11f225c35caa6ef2612d613026c52cfd3557e","modified":1700545753296},{"_id":"public/img/qweather-color-icon/511.png","hash":"e2efe07a29446ebecd313ccd8a2c7d57a670f203","modified":1700545753296},{"_id":"public/img/qweather-color-icon/513.png","hash":"ceff3dfd109a990c9595276ad6b56061bb662e5e","modified":1700545753296},{"_id":"public/img/qweather-color-icon/514.png","hash":"fdf992021ff20e1ffe1b19f0b918aa35204f22e7","modified":1700545753296},{"_id":"public/img/qweather-color-icon/900.png","hash":"6eacf8df641c6096feb746c7544a825d3c65bf47","modified":1700545753296},{"_id":"public/img/qweather-color-icon/515.png","hash":"fdf992021ff20e1ffe1b19f0b918aa35204f22e7","modified":1700545753296},{"_id":"public/img/qweather-color-icon/999.png","hash":"74e4fa5bdd815d988b55525d4e7f6d40bf1080d4","modified":1700545753296},{"_id":"public/img/qweather-color-icon/901.png","hash":"f6b3f1cd64e9c325e9dc3ab42469baa5c5119c2d","modified":1700545753296},{"_id":"public/img/avatar.png","hash":"7c2da2939b1a36315c45489fbc9930bac73f0880","modified":1700545753296},{"_id":"public/img/grid_1.png","hash":"c464cd495e7b9fca7ea2eca75e8f3ba0376301ec","modified":1700545753296},{"_id":"public/img/shader_10.png","hash":"b680ce28ce113c09dc89aee5cf7b82efbb3d0feb","modified":1700545753296},{"_id":"public/img/shader_18.png","hash":"fd93d1fb5b4a7f1c3884bb8592bc4ecbeee75d2f","modified":1700545753296},{"_id":"public/img/shader_19.png","hash":"d5d252b8f676d9f6a81f9fdb1dbf869827294060","modified":1700545753296},{"_id":"public/img/shader_23.png","hash":"53456d8475ed2d70180de00e1869534369bf8ddb","modified":1700545753296},{"_id":"public/img/shader_4.png","hash":"5a6b452396cc87aadc4a03b1c44482a1d4ee2f0f","modified":1700545753296},{"_id":"public/img/shader_20.png","hash":"b5900021cd88644a8902d24aa04b2e940f766547","modified":1700545753296},{"_id":"public/css/0c63d269.css","hash":"6b9b9796cf8ff9349b724f4b789f1347eaa0d476","modified":1700545753296},{"_id":"public/css/3efc6cb5.css","hash":"1f1958bfd921ac3eea83a1e14ed97e8e83b31fb6","modified":1700545753296},{"_id":"public/css/3a4a90d1.css","hash":"4ffc2c5e9f1afd4fa74d7d9b785e0697cd7fb770","modified":1700545753296},{"_id":"public/css/5c728363.css","hash":"715635cbd3ce46fd0b82117bc757fab6d2277070","modified":1700545753296},{"_id":"public/css/5bfc518f.css","hash":"4b7c03f245b3d60c366321e6cad11ceee7c0a131","modified":1700545753296},{"_id":"public/css/68d4249e.css","hash":"1e3c7c24bbac6eb2b9377e318e6ffb7bc4b90ee1","modified":1700545753296},{"_id":"public/css/69c863a0.css","hash":"9047a6d26295eb0245c3e1dd9db3de600e0e4bda","modified":1700545753296},{"_id":"public/css/82dd7e5a.css","hash":"c9561c3c190498369201f1ed27b6c17d255e0314","modified":1700545753296},{"_id":"public/css/80d65618.css","hash":"3928a4a222e7353ed86a540aac9c9b6448ea9ff5","modified":1700545753296},{"_id":"public/css/c080db9c.css","hash":"7caf9f961e4c0a26c53e1e37ce3d88d93d0be5ff","modified":1700545753296},{"_id":"public/css/de5de8fb.css","hash":"a05682caa4e0b8a106578468f795dad472ee75da","modified":1700545753296},{"_id":"public/css/dba23209.css","hash":"c03ff22c4a332f16b5f4ce22e3394ee05010027a","modified":1700545753296},{"_id":"public/css/e7914205.css","hash":"121b14a58b519c3fc538173189372800dea0d3de","modified":1700545753296},{"_id":"public/css/f3729dde.css","hash":"2a7faa0c3816e3b653fb4540fa63504a26b2051f","modified":1700545753296},{"_id":"public/css/efca006a.css","hash":"97972d1b7ca177983b39a67e772736f661637468","modified":1700545753296},{"_id":"public/js/31d6cfe0.js","hash":"da39a3ee5e6b4b0d3255bfef95601890afd80709","modified":1700545753296},{"_id":"public/js/9f1cd854.js","hash":"070029c5073bc3e67d98f1ac377d12f585bb4a2f","modified":1700545753296},{"_id":"public/js/3cf4fd98.js","hash":"0d51155f6dc08d1d8c693d210c1407b2e26b5685","modified":1700545753296},{"_id":"public/js/58c91c4e.js","hash":"1935dee981143040a708a1144fd2a57894ce3137","modified":1700545753296},{"_id":"public/js/ae2a0e7b.js","hash":"d71a6f691ba45862066ad15bb7f8b3bd2ad9f5f5","modified":1700545753296},{"_id":"public/js/ca6b30b5.js","hash":"481a8f20c3d4e565c1ea5dafe4218597d051a9fc","modified":1700545753296},{"_id":"public/js/c413ebaa.js","hash":"dd282af849c44345ad3e577efc8fa291468de653","modified":1700545753296},{"_id":"public/js/f8b20eb9.js","hash":"86cff2cb169c614dedd464b373d94322328cb185","modified":1700545753296},{"_id":"public/lib/prism/one-dark.css","hash":"5967bad8dd34b1eb244956064ae7870f62e6cd75","modified":1700545753296},{"_id":"public/lib/prism/one-light.css","hash":"dd7660b35884866eee48f069e876f1c29661a150","modified":1700545753296},{"_id":"public/js/d3872ea1.js","hash":"2c81c164e89dddf76fcee864da25ecf8e074248e","modified":1700545753296},{"_id":"public/js/b9c2be9c.js","hash":"c03c1885896b368caf85bf273a8148428e649f59","modified":1700545753296},{"_id":"public/js/cdca7001.js","hash":"cd27e408febd15a99bafdf9da8a834bb1b6902c3","modified":1700545753296},{"_id":"public/img/bolt.png","hash":"9f178950034e1ad5c5248b9caaca36e2f784225d","modified":1700545753296},{"_id":"public/img/cvpilot.png","hash":"e10abaca3fd20488b0889a4da84d65312e5ac3ad","modified":1700545753296},{"_id":"public/img/shader_14.png","hash":"bde142cb701d1f17cc8733b66cdc8166b8334f32","modified":1700545753296},{"_id":"public/img/shader_12.png","hash":"9d303ac8621c91088f97834733aa7903c70eaa43","modified":1700545753296},{"_id":"public/img/shader_11.png","hash":"954022f8fb580fce66b491db72df770591746362","modified":1700545753296},{"_id":"public/img/shader_16.png","hash":"9bec75831c21d063f23502612f106bfa86a0b100","modified":1700545753296},{"_id":"public/img/shader_15.png","hash":"c3feca0caadb737da89ad5a5a509057d379952c7","modified":1700545753296},{"_id":"public/img/shader_17.png","hash":"6274e99ab2e3d2053bdf2c0b9e3cc9b2b786f086","modified":1700545753296},{"_id":"public/img/shader_2.png","hash":"767fe07c2ff75bdb58e1bd5487f373ea7b33a163","modified":1700545753296},{"_id":"public/img/shader_22.png","hash":"610a67538d93eb8c81da6c99b90fe751657f9266","modified":1700545753296},{"_id":"public/img/shader_3.png","hash":"1260a52b8fc6351c62c3bf9f1e2907f495dd2f4e","modified":1700545753296},{"_id":"public/img/shader_28.png","hash":"079730db179a7b1f9539b929c33fc2be2b4a5bb5","modified":1700545753296},{"_id":"public/img/shader_8.png","hash":"ebe473df6f8f19aec27a1e97301c533d82653096","modified":1700545753296},{"_id":"public/img/logo.png","hash":"05730ab0dadd5036509f942834278eef683994dd","modified":1700545753296},{"_id":"public/img/hexo-theme-linear-dark.png","hash":"758aef1e3d9a83844d2871e8ceaccf6767e78284","modified":1700545753296},{"_id":"public/img/shader_1.png","hash":"559caf74cb63637b58483773f72b7589408ceaf9","modified":1700545753296},{"_id":"public/img/shader_13.png","hash":"e1eb1a13ffe9ed74b2f79f2e97c4134f10988657","modified":1700545753296},{"_id":"public/img/shader_21.png","hash":"a381d25344aaf2da7e29a7f157f3c2d1b0a8f813","modified":1700545753296},{"_id":"public/img/shader_27.png","hash":"285b22fb20437acaf07a9a3aa37770802d1b7202","modified":1700545753296},{"_id":"public/img/shader_30.png","hash":"35610b6e8877b16bc2140ec1e13a104188ac2f6b","modified":1700545753296},{"_id":"public/img/shader_5.png","hash":"07822798c3008fe841798993f75b50f104de5e11","modified":1700545753296},{"_id":"public/img/shader_6.png","hash":"7f96d58b93a0149993a7aa02d1d27c75e0ce0936","modified":1700545753296},{"_id":"public/img/shader_9.png","hash":"601cd60bc9837dc0603f82991334e084e7074aec","modified":1700545753296},{"_id":"public/img/shader_29.png","hash":"17acdcaa715bc68eb654b1e6db4fe0b4364d6394","modified":1700545753296},{"_id":"public/img/shader_7.png","hash":"23388d247c00f832e544954f4fa6956c03fec16d","modified":1700545753296},{"_id":"public/img/algolia-api-keys.png","hash":"8b2f85a93b52f0590e9b8fe2d2405427c99e2280","modified":1700545753296},{"_id":"public/img/shader_24.png","hash":"8eba8647f182532ac37462c15aa11a6e2d4ee196","modified":1700545753296},{"_id":"public/img/shader_25.png","hash":"9be25a68c628071e583e3985929c321ebe6d13cb","modified":1700545753296},{"_id":"public/img/shader_26.png","hash":"3433565461d058e381b1c5a13db978d6f470025a","modified":1700545753296},{"_id":"public/img/mz10nq.png","hash":"e26097f94ae54967e68f36b13b6aca80c4d173de","modified":1700545753296}],"Category":[{"name":"EQ","_id":"clp7x194v0004v3z3d37w4a24"},{"name":"Hexo","_id":"clp7x194x0008v3z3ajcp3heh"},{"name":"Finance","_id":"clp7x194z000fv3z3apb0f4b9"},{"name":"Front-End","_id":"clp7x1952000nv3z374avbca4"},{"name":"求学之路","_id":"clp7x1955001gv3z3h637e0fm"},{"name":"自媒体","_id":"clp7x1956001mv3z3caxw71ul"}],"Data":[],"Page":[{"title":"简历 · 陈不渡Mozzie","layout":"resume","avatar":"/img/avatar.png","name":"Hi! Mozzie","role":"Full Stack","email":"himozzie@gmail.com","phone":"+86 180-xxxx-xxx","birth":"Jan 21, 1994","location":"Nanjing, China","social":[{"name":"github","link":"https://github.com/17px","icon":""}],"about":["熟练掌握Vue和React全家桶,包括状态管理和组件库,如ElementPlus 和 AntDesign。在前端工程化方面,有 Webpack 和 Vite 的配置经验。具备 SpringBoot、Egg、Koa2 和 Midway 的项目经验,并在微服务架构下使用 Nestjs。熟悉Linux基础命令和Nginx、Docker的使用。","在3D图形和医疗前端方面,具有 three.js 和 Vtk.js 的实践经验"],"skill":["熟悉常见的数据结构和算法,熟悉设计模式","熟悉前端基础 HTML5、CSS3、JS、ES6+、TypeScript 语法","熟悉 Vue2、Vue3 全家桶,熟悉 ElementPlus、NaiveUI,熟悉 Vue 原理","熟悉 React 技术栈,熟悉 Hooks 语法,熟悉 AntDesign,熟悉 React 原理","熟悉微信小程序开发,熟悉 Taro 框架的使用","熟悉 Webpack、Vite 常见配置,以及性能优化配置","熟悉 Node,具备后端开发能力,SpringBoot、Egg、Koa2、Midway 单体应用项目经验,Nestjs 微服务应用项目经验","有 Monorepo 工程化经验,具备单体前后端分离应用 DevOps 能力","熟悉 three.js、了解 Vtk.js,具备医疗 3D 前端、图形学相关的开发经验","掌握 Linux 基本命令,了解 Nginx、Docker 基本使用"],"education":[{"school":"南京信息工程大学滨江学院 / 本科","time":"2012-2016"}],"workExp":[{"inc":"拓微摹心数据科技(南京)有限公司 · 全栈","time":"2021.09 ~ 至今"},{"inc":"南京寻鹿网络科技有限公司 · 前端","time":"2016.09 ~ 2019.06"}],"projectExp":[{"name":"CVPilot算法、标注数据中台","desc":"具备高精度数据标注、强大的数据管理、算法集成、多级用户权限、标注流程协作、算法模型版本、训练测试、API接口和数据可视化等功能,全面支持公司内部医疗影像分析和诊断","list":["使用 Pnpm workspace 实现 Monorepo 多个代码仓库管理,支持多个框架并存,可共享公共组件库和能力","自行使用 rollup 沉淀了部分可复用基础模块,如:UI 组件库、对象存储、邮件、短信、企业微信机器人通知","使用 Nest.js 开发,基于事件驱动架构,重构后端服务体系","基于 rbac 设计了认证中心,解决业务应用中用户统一授权、鉴权的问题","使用 orthanc 搭建 Pacs 影像、并且基于标注、业务流程设计了影像上层的标签系统","使用 pdf.js 实现 3Mensio、FluoroCT 医疗报告关键字段的自动解析,Cover了 80%+ 的字段","设计了前后端一体开发的领域驱动设计(DDD)项目架构,并成功实践"]},{"name":"TAVR手术辅助决策系统 Tavigator™","desc":"系统实现了对CT影像主动脉根部解剖结构的全自动化、全流程手术规划测量,为瓣膜选型、术中并发症风险预测","list":["基于容器,解决算法输出 stl 模型增加血管壁厚等后处理需求","在 OHIF 基础上,重新规划项目结构,工程化相关优化,构建速度提升8倍","对 orthanc 配置进行了优化,使用 postgreSQL 存储影像数据","实践了 threejs 替换了 vtk.js 的默认三维渲染引擎,节省了某些场景下 Vtk.js占用内存过大的问题","使用了 chrome的 snapshot、调用栈,优化页面内存 3GB+,使用 Indexdb 与 Webworker 对 Dicom 读片速度优化了3倍以上","使用 webworker、请求队列,实现了在 http1.1 环境下,提升用户上传 Dicom影像 74% 上传效率。同时针对不同大小、类型的文件,设计了不同的 Hash 切片方案,提升了文件完整性验证速度提升","使用了 Http3 和 nginx-quic,使用 Brotli 替换 gzip,实现了静载资源传输速度提升 17% - 25%"]},{"name":"Bolt 组件库、文档设计系统","desc":"前端基建,Bolt 用于构建快速组件库、文档。你可以在开发react组件库的同时, 无缝地编写组件文档, 实时热更新","list":["React 组件的 Props 解析,自动生成组件的 API 表格","组件的开发阶段,实施更新,所见即所得","依赖 vite 插件机制,200+ 以上组件,开发阶段秒级热更新","智能生成组件说明文档,构建产物分离组件库、文档静态站,支持全局、局部引入"]},{"name":"瓣侣 - 微信小程序","desc":"一款支持医生、病人查看算法分割后数字孪生心脏模型的小程序","list":["使用 taro 对小程序端实现3d模型展示,进行了技术选型,移植了STLloader 到 three-platformize 中,解决了 stl 模型加载的问题","解决小程序内存占用过大,导致部分机型崩溃的问题","使用包围盒对多个分割模型组合及位置修正、空间测量计算,实现瓣环样条绘制、空间拾取高亮等功能"]}],"portfolio":[{"name":"CVPilot (原Tavigator)","desc":"TAVR手术辅助决策系统","iconSVG":"","link":"https://www.tavi.fit"},{"name":"Bolt Design","desc":"基于 Vite 开发的 React 组件、文档一体化系统","iconSVG":"","link":"https://amo.mozzie.cn/"},{"name":"hexo-theme-cosy","desc":"Minimalist, pursuing the ultimate loading speed for a Hexo theme","iconSVG":"","link":"https://github.com/17px/hexo-theme-cosy"}],"_content":"","source":"resume/index.md","raw":"---\ntitle: 简历 · 陈不渡Mozzie\nlayout: resume\navatar: /img/avatar.png\nname: Hi! Mozzie\nrole: Full Stack\nemail: himozzie@gmail.com\nphone: +86 180-xxxx-xxx\nbirth: Jan 21, 1994\nlocation: Nanjing, China\nsocial:\n - name: github\n link: https://github.com/17px\n icon: \nabout:\n - 熟练掌握Vue和React全家桶,包括状态管理和组件库,如ElementPlus 和 AntDesign。在前端工程化方面,有 Webpack 和 Vite 的配置经验。具备 SpringBoot、Egg、Koa2 和 Midway 的项目经验,并在微服务架构下使用 Nestjs。熟悉Linux基础命令和Nginx、Docker的使用。\n - 在3D图形和医疗前端方面,具有 three.js 和 Vtk.js 的实践经验\n\nskill:\n - 熟悉常见的数据结构和算法,熟悉设计模式\n - 熟悉前端基础 HTML5、CSS3、JS、ES6+、TypeScript 语法\n - 熟悉 Vue2、Vue3 全家桶,熟悉 ElementPlus、NaiveUI,熟悉 Vue 原理\n - 熟悉 React 技术栈,熟悉 Hooks 语法,熟悉 AntDesign,熟悉 React 原理\n - 熟悉微信小程序开发,熟悉 Taro 框架的使用\n - 熟悉 Webpack、Vite 常见配置,以及性能优化配置\n - 熟悉 Node,具备后端开发能力,SpringBoot、Egg、Koa2、Midway 单体应用项目经验,Nestjs 微服务应用项目经验\n - 有 Monorepo 工程化经验,具备单体前后端分离应用 DevOps 能力\n - 熟悉 three.js、了解 Vtk.js,具备医疗 3D 前端、图形学相关的开发经验\n - 掌握 Linux 基本命令,了解 Nginx、Docker 基本使用\n\neducation:\n - school: 南京信息工程大学滨江学院 / 本科\n time: 2012-2016\n\nworkExp:\n - inc: 拓微摹心数据科技(南京)有限公司 · 全栈\n time: 2021.09 ~ 至今\n - inc: 南京寻鹿网络科技有限公司 · 前端\n time: 2016.09 ~ 2019.06\n\nprojectExp:\n - name: CVPilot算法、标注数据中台\n desc: 具备高精度数据标注、强大的数据管理、算法集成、多级用户权限、标注流程协作、算法模型版本、训练测试、API接口和数据可视化等功能,全面支持公司内部医疗影像分析和诊断\n list:\n - 使用 Pnpm workspace 实现 Monorepo 多个代码仓库管理,支持多个框架并存,可共享公共组件库和能力\n - 自行使用 rollup 沉淀了部分可复用基础模块,如:UI 组件库、对象存储、邮件、短信、企业微信机器人通知\n - 使用 Nest.js 开发,基于事件驱动架构,重构后端服务体系\n - 基于 rbac 设计了认证中心,解决业务应用中用户统一授权、鉴权的问题\n - 使用 orthanc 搭建 Pacs 影像、并且基于标注、业务流程设计了影像上层的标签系统\n - 使用 pdf.js 实现 3Mensio、FluoroCT 医疗报告关键字段的自动解析,Cover了 80%+ 的字段\n - 设计了前后端一体开发的领域驱动设计(DDD)项目架构,并成功实践\n\n - name: TAVR手术辅助决策系统 Tavigator™\n desc: 系统实现了对CT影像主动脉根部解剖结构的全自动化、全流程手术规划测量,为瓣膜选型、术中并发症风险预测\n list:\n - 基于容器,解决算法输出 stl 模型增加血管壁厚等后处理需求\n - 在 OHIF 基础上,重新规划项目结构,工程化相关优化,构建速度提升8倍\n - 对 orthanc 配置进行了优化,使用 postgreSQL 存储影像数据\n - 实践了 threejs 替换了 vtk.js 的默认三维渲染引擎,节省了某些场景下 Vtk.js占用内存过大的问题\n - 使用了 chrome的 snapshot、调用栈,优化页面内存 3GB+,使用 Indexdb 与 Webworker 对 Dicom 读片速度优化了3倍以上\n - 使用 webworker、请求队列,实现了在 http1.1 环境下,提升用户上传 Dicom影像 74% 上传效率。同时针对不同大小、类型的文件,设计了不同的 Hash 切片方案,提升了文件完整性验证速度提升\n - 使用了 Http3 和 nginx-quic,使用 Brotli 替换 gzip,实现了静载资源传输速度提升 17% - 25%\n\n - name: Bolt 组件库、文档设计系统\n desc: 前端基建,Bolt 用于构建快速组件库、文档。你可以在开发react组件库的同时, 无缝地编写组件文档, 实时热更新\n list:\n - React 组件的 Props 解析,自动生成组件的 API 表格\n - 组件的开发阶段,实施更新,所见即所得\n - 依赖 vite 插件机制,200+ 以上组件,开发阶段秒级热更新\n - 智能生成组件说明文档,构建产物分离组件库、文档静态站,支持全局、局部引入\n\n - name: 瓣侣 - 微信小程序\n desc: 一款支持医生、病人查看算法分割后数字孪生心脏模型的小程序\n list:\n - 使用 taro 对小程序端实现3d模型展示,进行了技术选型,移植了STLloader 到 three-platformize 中,解决了 stl 模型加载的问题\n - 解决小程序内存占用过大,导致部分机型崩溃的问题\n - 使用包围盒对多个分割模型组合及位置修正、空间测量计算,实现瓣环样条绘制、空间拾取高亮等功能\n\nportfolio:\n - name: CVPilot (原Tavigator)\n desc: TAVR手术辅助决策系统\n iconSVG: \n link: https://www.tavi.fit\n\n - name: Bolt Design\n desc: 基于 Vite 开发的 React 组件、文档一体化系统\n iconSVG: \n link: https://amo.mozzie.cn/\n\n - name: hexo-theme-cosy\n desc: Minimalist, pursuing the ultimate loading speed for a Hexo theme\n iconSVG: \n link: https://github.com/17px/hexo-theme-cosy\n---\n","date":"2023-11-03T03:15:41.632Z","updated":"2023-11-03T03:15:41.632Z","path":"resume/index.html","comments":1,"_id":"clp7x194q0000v3z39y0t3syu","content":"","site":{"data":{}},"excerpt":"","more":""},{"title":"陈不渡 - roadmap","layout":"roadmap","initYear":2023,"years":{"2022":[{"title":"读书","start":"01-01","end":"1-5"},{"title":"还是读书","start":"02-01","end":"05-30"}],"2023":[{"title":"⛵️ 开发hexo-theme-cosy主题","start":"09-03","end":"10-03","content":"基本功能基本迭代完成,目前稳定在1.2.4版本"},{"title":"✅ 视频AI无闪烁转绘","start":"10-25","end":"11-04","content":"stable diffusion + Ebsynth 插帧"},{"title":"✅ 成为一名飞手","start":"11-04","end":"11-09","content":"UTC无人驾驶航空器系统操作手合格证"},{"title":"✅ hexo-theme-cosy 发包到 npm","start":"11-14","end":"11-16","content":"人生中第一个正儿八经的 npm 包,使用 github 的 action 白嫖一手 ci 资源"},{"title":"抖音千粉","start":"11-06","end":"12-30","content":"粉丝量突破1000,预算投入¥3000,每天至少生产1个小姐姐舞蹈 AI转绘 视频"},{"title":"📚 付鹏《见证逆潮》","start":"11-25","end":"12-30","content":"笔录书中核心观点,写一篇读后感"}]},"_content":"","source":"roadmap/index.md","raw":"---\ntitle: 陈不渡 - roadmap\nlayout: roadmap\ninitYear: 2023\nyears:\n 2022:\n - title: 读书\n start: 01-01\n end: 1-5\n - title: 还是读书\n start: 02-01\n end: 05-30\n\n 2023:\n - title: ⛵️ 开发hexo-theme-cosy主题\n start: 09-03\n end: 10-03\n content: 基本功能基本迭代完成,目前稳定在1.2.4版本\n\n - title: ✅ 视频AI无闪烁转绘\n start: 10-25\n end: 11-04\n content: stable diffusion + Ebsynth 插帧\n\n - title: ✅ 成为一名飞手\n start: 11-04\n end: 11-09\n content: UTC无人驾驶航空器系统操作手合格证\n\n - title: ✅ hexo-theme-cosy 发包到 npm\n start: 11-14\n end: 11-16\n content: 人生中第一个正儿八经的 npm 包,使用 github 的 action 白嫖一手 ci 资源\n\n - title: 抖音千粉\n start: 11-06\n end: 12-30\n content: 粉丝量突破1000,预算投入¥3000,每天至少生产1个小姐姐舞蹈 AI转绘 视频\n\n - title: 📚 付鹏《见证逆潮》\n start: 11-25\n end: 12-30\n content: 笔录书中核心观点,写一篇读后感\n---","date":"2023-11-17T04:52:39.950Z","updated":"2023-11-17T04:52:39.950Z","path":"roadmap/index.html","comments":1,"_id":"clp7x194u0002v3z3939w06pb","content":"","site":{"data":{}},"excerpt":"","more":""}],"Post":[{"title":"有效对话指南","top":0,"status":"done","_content":"\n卡耐基说过:要说服别人同意你的观点,你就要让他觉得这是他自己的观点。也就是说,本质上没有人真正被别人说服,听或者不听都是他们自己的主动选择\n\n# 目的\n\n沟通目的无非两个:\n\n- 让对方认同我们的观点\n- 或者按我们说的做\n\n# 搞定情绪\n\n大多数人的立场和行为,是由情绪决定的,对方不喜欢你,你再怎么努力也很难说服他,只有他认可你这个人你的话他才会听。所以动之以理之前要先小之以轻,你把他情绪搞定了,沟通也就成功了一半\n\n## 夸 - 细节\n\n不吝赞美,就一个字“夸”,具体怎么夸呢?\n\n细节就是不要太笼统:\n\n- 😭 你穿的真好看 \n- 😊 今天的上衣显得你很白,太美了\n\n## 夸 - 对比\n\n- 😭 你今天穿的真好看\n- 😊 你今天穿的真好看,一般人可传不出这种气质\n\n## 我能理解你,换我也生气\n\n接纳对方的负面情绪,把双方情绪拉回到安全范围内。\n\n## 肢体同步\n\n如果你的肢体动作跟对方同步,它会产生一种被共情的感觉\n\n- 对方说的火热,身体前倾,伺机凑近\n- 思考一到两秒钟再反馈,这个简单的停顿\n\n# 只陈述不评论\n\n事实不会引起争议\n\n比如:孩子考试没及格是事实,说他笨死了就是评论了。\n\n再比如:你这周上班迟到两次,这是事实,说你经常迟到就是评论了。\n\n## 评论副词\n\n尽量规避一些跟评论相关的糊的频率副词,比如\n- 笨\n- 懒\n- 坏\n- 总是\n- 永远\n- 每次\n\n# 保持开放性\n\n还拿上面的例子,这些话对方只能回答好或者不好,然后结束沟通\n\n- 孩子没及格,老爸说下次必须及格。\n- 你上班迟到,老板说再迟到扣薪水。\n\n如果换成开放式沟通:\n\n- 老爸说咱们聊聊哪些题没有搞清楚\n- 老板说生活上是不是遇到了什么问题,没着我可以帮你\n\n# 反馈事实\n\n这些话对方不仅听着舒服,而且能给你反馈事实。具体怎么开放呢?咱们只需要记住两个关键词\n\n- 问题开放:不要让对方只能回答是或否,例如:像愿闻其详、展开说说、欢迎补充之类\n- 让出话语权:真正的沟通高手,是让对方觉得自己很牛,主导聊天的往往是能提出问题的人,或者让别人多说的人\n\n","source":"_posts/A Guide to Effective Dialogue.md","raw":"---\ntitle: 有效对话指南\ntop: 0\ncategories:\n - EQ\nstatus: done\n---\n\n卡耐基说过:要说服别人同意你的观点,你就要让他觉得这是他自己的观点。也就是说,本质上没有人真正被别人说服,听或者不听都是他们自己的主动选择\n\n# 目的\n\n沟通目的无非两个:\n\n- 让对方认同我们的观点\n- 或者按我们说的做\n\n# 搞定情绪\n\n大多数人的立场和行为,是由情绪决定的,对方不喜欢你,你再怎么努力也很难说服他,只有他认可你这个人你的话他才会听。所以动之以理之前要先小之以轻,你把他情绪搞定了,沟通也就成功了一半\n\n## 夸 - 细节\n\n不吝赞美,就一个字“夸”,具体怎么夸呢?\n\n细节就是不要太笼统:\n\n- 😭 你穿的真好看 \n- 😊 今天的上衣显得你很白,太美了\n\n## 夸 - 对比\n\n- 😭 你今天穿的真好看\n- 😊 你今天穿的真好看,一般人可传不出这种气质\n\n## 我能理解你,换我也生气\n\n接纳对方的负面情绪,把双方情绪拉回到安全范围内。\n\n## 肢体同步\n\n如果你的肢体动作跟对方同步,它会产生一种被共情的感觉\n\n- 对方说的火热,身体前倾,伺机凑近\n- 思考一到两秒钟再反馈,这个简单的停顿\n\n# 只陈述不评论\n\n事实不会引起争议\n\n比如:孩子考试没及格是事实,说他笨死了就是评论了。\n\n再比如:你这周上班迟到两次,这是事实,说你经常迟到就是评论了。\n\n## 评论副词\n\n尽量规避一些跟评论相关的糊的频率副词,比如\n- 笨\n- 懒\n- 坏\n- 总是\n- 永远\n- 每次\n\n# 保持开放性\n\n还拿上面的例子,这些话对方只能回答好或者不好,然后结束沟通\n\n- 孩子没及格,老爸说下次必须及格。\n- 你上班迟到,老板说再迟到扣薪水。\n\n如果换成开放式沟通:\n\n- 老爸说咱们聊聊哪些题没有搞清楚\n- 老板说生活上是不是遇到了什么问题,没着我可以帮你\n\n# 反馈事实\n\n这些话对方不仅听着舒服,而且能给你反馈事实。具体怎么开放呢?咱们只需要记住两个关键词\n\n- 问题开放:不要让对方只能回答是或否,例如:像愿闻其详、展开说说、欢迎补充之类\n- 让出话语权:真正的沟通高手,是让对方觉得自己很牛,主导聊天的往往是能提出问题的人,或者让别人多说的人\n\n","slug":"A Guide to Effective Dialogue","published":1,"date":"2023-11-06T05:25:00.499Z","updated":"2023-11-06T05:25:00.499Z","comments":1,"layout":"post","photos":[],"link":"","_id":"clp7x194s0001v3z3dnez8x8d","content":"

卡耐基说过:要说服别人同意你的观点,你就要让他觉得这是他自己的观点。也就是说,本质上没有人真正被别人说服,听或者不听都是他们自己的主动选择

\n

目的

沟通目的无非两个:

\n\n

搞定情绪

大多数人的立场和行为,是由情绪决定的,对方不喜欢你,你再怎么努力也很难说服他,只有他认可你这个人你的话他才会听。所以动之以理之前要先小之以轻,你把他情绪搞定了,沟通也就成功了一半

\n

夸 - 细节

不吝赞美,就一个字“夸”,具体怎么夸呢?

\n

细节就是不要太笼统:

\n\n

夸 - 对比

\n

我能理解你,换我也生气

接纳对方的负面情绪,把双方情绪拉回到安全范围内。

\n

肢体同步

如果你的肢体动作跟对方同步,它会产生一种被共情的感觉

\n\n

只陈述不评论

事实不会引起争议

\n

比如:孩子考试没及格是事实,说他笨死了就是评论了。

\n

再比如:你这周上班迟到两次,这是事实,说你经常迟到就是评论了。

\n

评论副词

尽量规避一些跟评论相关的糊的频率副词,比如

\n\n

保持开放性

还拿上面的例子,这些话对方只能回答好或者不好,然后结束沟通

\n\n

如果换成开放式沟通:

\n\n

反馈事实

这些话对方不仅听着舒服,而且能给你反馈事实。具体怎么开放呢?咱们只需要记住两个关键词

\n\n","site":{"data":{}},"excerpt":"","more":"

卡耐基说过:要说服别人同意你的观点,你就要让他觉得这是他自己的观点。也就是说,本质上没有人真正被别人说服,听或者不听都是他们自己的主动选择

\n

目的

沟通目的无非两个:

\n\n

搞定情绪

大多数人的立场和行为,是由情绪决定的,对方不喜欢你,你再怎么努力也很难说服他,只有他认可你这个人你的话他才会听。所以动之以理之前要先小之以轻,你把他情绪搞定了,沟通也就成功了一半

\n

夸 - 细节

不吝赞美,就一个字“夸”,具体怎么夸呢?

\n

细节就是不要太笼统:

\n\n

夸 - 对比

\n

我能理解你,换我也生气

接纳对方的负面情绪,把双方情绪拉回到安全范围内。

\n

肢体同步

如果你的肢体动作跟对方同步,它会产生一种被共情的感觉

\n\n

只陈述不评论

事实不会引起争议

\n

比如:孩子考试没及格是事实,说他笨死了就是评论了。

\n

再比如:你这周上班迟到两次,这是事实,说你经常迟到就是评论了。

\n

评论副词

尽量规避一些跟评论相关的糊的频率副词,比如

\n\n

保持开放性

还拿上面的例子,这些话对方只能回答好或者不好,然后结束沟通

\n\n

如果换成开放式沟通:

\n\n

反馈事实

这些话对方不仅听着舒服,而且能给你反馈事实。具体怎么开放呢?咱们只需要记住两个关键词

\n\n"},{"title":"Cosy 入门","top":2,"status":"doing","_content":"\n# 安装\n\n## 使用 npm 安装\n\n在主题的根目录\n\n```yml\nnpm i hexo-theme-cosy\n```\n\n此外,需要新建一个 `_config.cosy.yml`,同时修改 `_config.yml`\n\n```yml\ntheme: cosy\n\nalgolia:\n appId: 你的\n apiKey: 你的\n adminApiKey: 你的\n SearchOnlyAPIKey: 你的\n chunkSize: 5000\n indexName: 你的algolia的index\n fields:\n - content\n - excerpt:strip\n - categories\n - title\n - permalink\n - slug\n - tags\n - title\n```\n\n## 传统安装 Hexo 安装主题\n\n将主题 `Hexo-theme-cosy` 文件夹复制目录的 `themes` 目录下,然后在 `Hexo` 的 `_config.yml` 中修改下主题配置即可\n\n```yml\n# 找到 theme 配置项\ntheme: Hexo-theme-cosy\n```\n\n## 获取 Cosy\n\n- 进入 [Cosy 代码仓库](https://github.com/17px/hexo-theme-cosy/tree/main),了解最新的主题开发进度\n- 在 [发布页面](https://github.com/17px/hexo-theme-cosy/releases) 下载 `hexo-theme-cosy.zip`\n- 解压至站点的 `themes` 目录下\n- 在 Hexo 的 `_config.yml` 中启用\n\n# hexo配置\n\n在 `Hexo` 的 `_config.yml` 中调整\n\n## 基础配置\n\n```yml\n# 网页标题\ntitle: 17px blog\n# 侧边栏顶部显示\nsubtitle: \"Mozzie\"\n# 用于SEO的html元描述\ndescription: \"\"\n# 用于SEO的html关键字\nkeywords:\n# 文章版权声明显示作者名称\nauthor: Mozzie\n\n# 在此处设置您的网站url\nurl: https://mozzie.cn\n```\n\n## 语言\n\n你可以在 `hexo-theme-cosy/languages` 中找到不同的语言文件,如果想切换语言,在配置文件中,填入 `yml` 的文件名\n\n```yml\nlanguage: en\n```\n\n## 文章语法高亮\n\n关闭hexo默认的highlight.js语法高亮\n\n```yml\nhighlight:\n enable: false\n line_number: true\n auto_detect: false\n tab_replace: \"\"\n wrap: true\n hljs: false\n\nprismjs:\n enable: true\n preprocess: true\n line_number: true\n line_threshold: 0\n tab_replace: \"\"\n```\n\n# Cosy 主题配置\n\n下面的配置基于 `Hexo-theme-cosy` 下的 `_config.yml` \n\n## 文章分类图标\n\n> 可以在 [xicon](https://www.xicons.org/) 获取丰富的图标\n\n在`主题_config.yml`文件中,添加`分类名称`和`图标svg`的映射,如值设置为 `false`,代表不显示该分类\n\n```yml\ncategory_meta:\n foo: \n bar: false\n```\n\n## 网站图标 favicon\n\n- 支持 `svg`\n- 支持 `图片url`,如:`/img/favicon.png`\n\n```yml\nfavicon: ''\n```\n\n## ICP备案号\n\n😁 如不需要备案号,可直接删除\n\n```yml\nicp: 苏ICP备xxxxxxx号-x\n```\n\n## 首页底部文字\n\n😁 如不需要,可直接删除,或者改为 `false` \n\n```yml\nmotto: false\n```\n\n## 文章版权申明\n\n默认开启,当 `enable: false`,默认关闭\n\n```yml\npostCopyright:\n enable: true\n license: CC BY-NC-SA 4.0\n license_url: https://creativecommons.org/licenses/by-nc-sa/4.0/\n```\n\n## katex 数学公式\n\n可自行配置 `cdn`,全局默认关闭该插件\n\n```yml\nkatex:\n jsCdn: //cdn.jsdelivr.net/npm/katex@0.13.18/dist/katex.min.js\n cssCdn: //cdn.jsdelivr.net/npm/katex@0.13.18/dist/katex.min.css\n```\n\n为了加载速度,如果文章需要用到插件,请在文章头部增加,如\n\n```markdown\n---\nuse: katex\n---\n```\n\n## mermaid 流程图\n\n在撰写时,请使用 `{% mermaid %}` 和 `{% endmermaid %}` 包裹,全局默认关闭该插件\n\n```markdown\n{% mermaid %}\ngraph TD;\n A --> B;\n A --> C;\n B --> D;\n C --> D;\n{% endmermaid %}\n```\n\n相应配置如下\n\n```yml\nmermaid:\n # 默认使用 neutral,可选配置:default | dark | forest | neutral\n theme: neutral\n cdn: //cdn.jsdelivr.net/npm/mermaid@10.4.0/dist/mermaid.min.js\n```\n\n为了加载速度,如果文章需要用到插件,请在文章头部增加,如\n\n```markdown\n---\nuse: mermaid,katex...\n---\n```\n\n## valine 文章评论\n\n首先需要注册 `LeanCloud` 国际区用户,创建数据库\n\n请根据相关地区法规,酌情,全局默认关闭该插件\n\n```yml\nvaline:\n # 替换\n appId: appId\n # 替换\n appKey: appKey\n avatar: monsterid\n cdn: //unpkg.com/valine@latest/dist/Valine.min.js\n # 替换\n serverURLs: //xxxxxxxx.api.lncldglobal.com\n```\n\n为了加载速度,如果文章需要用到插件,请在文章头部增加,如\n\n```markdown\n---\nuse: valine,mermaid...\n---\n```\n\n# algolia搜索\n\n博客自带的本地搜索,基于前端开发,存在或多或少的问题,建议换成 `algolia`,免费账户 总共有 `10,000` 条记录,每月有 `100,000` 的操作数\n\n## 关闭搜索\n\n主页左侧导航栏不再显示搜索,通知相关资源不会加载,在主题 `_config.yml` 中:\n\n```yml\nsearch: false\n```\n\n## 注册 & 获取 Key\n\n1. 创建一个新的 `Index`,例如 `hex-blog`\n\n2. 复制并保存:\n - Application ID\n - Search-Only API Key\n - Admin API Key\n - Usage API Key\n\n![API Keys](/img/algolia-api-keys.png)\n\n3. 替换配置\n\n在 `Hexo` 的 `_config.yml` 中加入\n\n```yml\nalgolia:\n # 替换\n appId: Application ID\n # 替换\n apiKey: Usage API Key\n # 请勿泄露,用于上报,替换\n adminApiKey: Admin API Key\n # 替换\n SearchOnlyAPIKey: Search-Only API Key\n chunkSize: 5000\n # 替换\n indexName: hex-blog\n fields:\n - content\n - excerpt:strip\n - categories\n - title\n - permalink\n - slug\n - tags\n - title\n```\n\n## 安装 hexo-algoliasearch-next\n\n这是 Hexo 博客帖子索引插件,自动化提交索引到 Algolia\n\n> 如出遇到问题,可阅读 [hexo-algoliasearch-next 仓库](https://github.com/Becavalier/hexo-algoliasearch-next) 最新说明\n\n安装命令:\n\n```bash\nnpm install hexo-algoliasearch-next --save\n```\n\n## 使用 algolia\n\n在每次博客发布之前,进行索引上传的操作,命令通常如下\n\n```bash\nhexo clean\nhexo generate\nhexo algolia\n``` \n\n\n# 🇨🇳 和风天气 Widget\n\nCosy 主要针对国内用户,在首页集成了和风天气的卡片,通过 [和风天气开发服务](https://dev.qweather.com/docs/start/) 注册\n\n注册完成后,在配置中填入你的 `appKey` 和 `cityCode(城市代码)`\n\n```yml\nweather:\n enable: true\n # 替换\n cityCode: cityCode\n appKey: appKey\n```\n\n其中 `cityCode` 可以在 [官方的地区列表仓库](https://github.com/qwd/LocationList) 中的 `China-City-List-latest.csv` 找到你所在城市的 `cityCode`\n\n# 前置元数据\n\n在 Hexo 的 Markdown 文件中,一个典型的 YAML 格式的 `Front Matter` 可能会是这样的:\n\n```markdown\n---\ntitle: 我的文章标题\ncategories:\n- javascript\ntags: \n- 编程\n- JavaScript\n---\n```\n\n## top\n\n实现文章置顶的功能,给定一个数值,可以进行排序,设定了 `top` 元数据的文章,会在分类列表中,使用 📌 标记\n\n例如有三篇文章:\n\n- 文章1\n\n```markdown\n---\ntitle: 文章1\ntop: 0\ncategories:\n- javascript\ntags: \n- 编程\n- JavaScript\n---\n```\n\n- 文章2\n\n```markdown\n---\ntitle: 文章2\ntop: 1\ncategories:\n- javascript\ntags: \n- 编程\n- JavaScript\n---\n```\n\n- 文章3\n\n```markdown\n---\ntitle: 文章3\ncategories:\n- javascript\ntags: \n- 编程\n- JavaScript\n---\n```\n\n那么在 `javascript` 分类下,排序的顺序依次为:`文章1 > 文章2 > 文章3`\n\n## status\n\n用于区分文章的状态,同时利用文章分类列表的`筛选`,进行快速筛选,Cosy 主题内置了 4 种状态\n\n- done:完成\n- doing:进行中\n- todo:待办\n- other:废弃\n\n例如:\n\n```markdown\n---\ntitle: 文章1\ncategories:\n- javascript\nstatus: done\n---\n```\n\n# 自定义页面\n\nHexo 使用 Markdown(或其他渲染引擎)解析你的文章,并生成静态文件以快速加载。除了默认生成的文章和归档页面之外,Hexo 还允许你创建自定义页面。\n\n## 基本配置\n\n可以通过配置 `nav_meta` 属性关闭页面、更改图标\n\n```yml\nnav_meta:\n # 不显示 timeline页面\n timeline: false\n # 自定义图标\n roadmap:\n \n \n \n \n \n \n \n resume:\n \n \n \n \n \n \n \n \n```\n\n## Roadmap路线图\n\n创建页面,你可以使用命令\n\n```bash\nhexo new page roadmap\n```\n\n成功后在 `source/` 文件夹下会生成一个新的文件夹 `/roadmap/index.md`\n\n你可以参照此模板,进行配置,参数说明:\n\n- title: roadmap页面 html 的标题\n- initYear: 默认显示的年份\n\n```markdown\n---\ntitle: 陈不渡 - roadmap\nlayout: roadmap\ninitYear: 2023\nyears:\n 2022:\n - title: 读书\n start: 01-01\n end: 1-5\n - title: 还是读书\n start: 02-01\n end: 05-30\n 2023:\n - title: 越陌度阡\n start: 01-01\n end: 1-2\n - title: 枉用相存\n start: 02-01\n end: 06-30\n - title: 短歌行\n start: 10-26\n end: 10-31\n content: 对酒当歌,人生几何!譬如朝露,去日苦多。慨当以慷,忧思难忘。何以解忧?唯有杜康。青青子衿,悠悠我心。但为君故,沉吟至今。呦呦鹿鸣,食野之苹。\n - title: 声律启蒙\n start: 11-01\n end: 11-31\n content: 花开红锦绣,水漾碧琉璃。去妇因探邻舍枣,出妻为种后园葵\n---\n```\n\n## Resume简历页面\n\n创建页面,你可以使用命令\n\n```bash\nhexo new page resume\n```\n\n成功后在 `source/` 文件夹下会生成一个新的文件夹 `/resume/index.md`\n\n你可以参照此模板,进行配置,参数说明:\n\n- title: resume 页面 html 的标题\n- avatar: 头像图片的地址\n\n```yml\ntitle: 页面标题document.title\nlayout: resume\navatar: /img/avatar.png\nname: Hi! Mozzie\nrole: Full Stack\nemail: himozzie@gmail.com\nphone: +86 180-xxxx-xxx\nbirth: Jan 21, 1994\nlocation: Nanjing, China\nsocial:\n - name: github\n link: https://github.com/17px\n icon: svg\n\nabout:\n - 我的工作是建立你的网站,使其功能强大,用户友好,但同时具有吸引力。\n - 此外,我为您的产品添加了个人风格,并确保其引人注目且易于使用。我的目标是以最有创意的方式传达你的信息和身份。我为许多知名品牌公司设计网页。\n\nskill:\n - 熟悉Node,具备后端开发能力,有SpringBoot、Egg、Koa2、Midway等单个应用项目经验,有Nestjs微服务应用项目经验\n - 具有Monoreo工程经验,能够分离单个单元的前端和后端并应用DevOps\n\neducation:\n - school: 大学艺术学院\n time: 2012-2016\n\nworkExp:\n - inc: 创意研发\n time: 2021.09 ~ 至今\n - inc: Web设计\n time: 2021.01 ~ 2021.09\n\nprojectExp:\n - name: PC/React• 标注 ,算法数据中心\n desc: 这是一个xxx系统。它主要包括一个bc函数。我主要负责x、y和z模块的开发和维护。\n list: \n - 使用pnpm工作区管理Monorepo中的多个代码库,支持多个框架共存,共享通用组件库和功能\n\n - name: PC/React•Tavigator主动脉根部/外周\n desc: 这是一个xxx系统。它主要包括一个bc函数。我主要负责x、y和z模块的开发和维护。\n\n\nportfolio:\n - name: 项目A\n desc: 项目A描述\n iconSVG: 复制svg的path到这里\n link: 网址,http(s)://...\n # 更多 ...\n\n```","source":"_posts/Cosy-Starter-Guide.md","raw":"---\ntitle: Cosy 入门\ntop: 2\ncategories:\n - Hexo\nstatus: doing\n---\n\n# 安装\n\n## 使用 npm 安装\n\n在主题的根目录\n\n```yml\nnpm i hexo-theme-cosy\n```\n\n此外,需要新建一个 `_config.cosy.yml`,同时修改 `_config.yml`\n\n```yml\ntheme: cosy\n\nalgolia:\n appId: 你的\n apiKey: 你的\n adminApiKey: 你的\n SearchOnlyAPIKey: 你的\n chunkSize: 5000\n indexName: 你的algolia的index\n fields:\n - content\n - excerpt:strip\n - categories\n - title\n - permalink\n - slug\n - tags\n - title\n```\n\n## 传统安装 Hexo 安装主题\n\n将主题 `Hexo-theme-cosy` 文件夹复制目录的 `themes` 目录下,然后在 `Hexo` 的 `_config.yml` 中修改下主题配置即可\n\n```yml\n# 找到 theme 配置项\ntheme: Hexo-theme-cosy\n```\n\n## 获取 Cosy\n\n- 进入 [Cosy 代码仓库](https://github.com/17px/hexo-theme-cosy/tree/main),了解最新的主题开发进度\n- 在 [发布页面](https://github.com/17px/hexo-theme-cosy/releases) 下载 `hexo-theme-cosy.zip`\n- 解压至站点的 `themes` 目录下\n- 在 Hexo 的 `_config.yml` 中启用\n\n# hexo配置\n\n在 `Hexo` 的 `_config.yml` 中调整\n\n## 基础配置\n\n```yml\n# 网页标题\ntitle: 17px blog\n# 侧边栏顶部显示\nsubtitle: \"Mozzie\"\n# 用于SEO的html元描述\ndescription: \"\"\n# 用于SEO的html关键字\nkeywords:\n# 文章版权声明显示作者名称\nauthor: Mozzie\n\n# 在此处设置您的网站url\nurl: https://mozzie.cn\n```\n\n## 语言\n\n你可以在 `hexo-theme-cosy/languages` 中找到不同的语言文件,如果想切换语言,在配置文件中,填入 `yml` 的文件名\n\n```yml\nlanguage: en\n```\n\n## 文章语法高亮\n\n关闭hexo默认的highlight.js语法高亮\n\n```yml\nhighlight:\n enable: false\n line_number: true\n auto_detect: false\n tab_replace: \"\"\n wrap: true\n hljs: false\n\nprismjs:\n enable: true\n preprocess: true\n line_number: true\n line_threshold: 0\n tab_replace: \"\"\n```\n\n# Cosy 主题配置\n\n下面的配置基于 `Hexo-theme-cosy` 下的 `_config.yml` \n\n## 文章分类图标\n\n> 可以在 [xicon](https://www.xicons.org/) 获取丰富的图标\n\n在`主题_config.yml`文件中,添加`分类名称`和`图标svg`的映射,如值设置为 `false`,代表不显示该分类\n\n```yml\ncategory_meta:\n foo: \n bar: false\n```\n\n## 网站图标 favicon\n\n- 支持 `svg`\n- 支持 `图片url`,如:`/img/favicon.png`\n\n```yml\nfavicon: ''\n```\n\n## ICP备案号\n\n😁 如不需要备案号,可直接删除\n\n```yml\nicp: 苏ICP备xxxxxxx号-x\n```\n\n## 首页底部文字\n\n😁 如不需要,可直接删除,或者改为 `false` \n\n```yml\nmotto: false\n```\n\n## 文章版权申明\n\n默认开启,当 `enable: false`,默认关闭\n\n```yml\npostCopyright:\n enable: true\n license: CC BY-NC-SA 4.0\n license_url: https://creativecommons.org/licenses/by-nc-sa/4.0/\n```\n\n## katex 数学公式\n\n可自行配置 `cdn`,全局默认关闭该插件\n\n```yml\nkatex:\n jsCdn: //cdn.jsdelivr.net/npm/katex@0.13.18/dist/katex.min.js\n cssCdn: //cdn.jsdelivr.net/npm/katex@0.13.18/dist/katex.min.css\n```\n\n为了加载速度,如果文章需要用到插件,请在文章头部增加,如\n\n```markdown\n---\nuse: katex\n---\n```\n\n## mermaid 流程图\n\n在撰写时,请使用 `{% mermaid %}` 和 `{% endmermaid %}` 包裹,全局默认关闭该插件\n\n```markdown\n{% mermaid %}\ngraph TD;\n A --> B;\n A --> C;\n B --> D;\n C --> D;\n{% endmermaid %}\n```\n\n相应配置如下\n\n```yml\nmermaid:\n # 默认使用 neutral,可选配置:default | dark | forest | neutral\n theme: neutral\n cdn: //cdn.jsdelivr.net/npm/mermaid@10.4.0/dist/mermaid.min.js\n```\n\n为了加载速度,如果文章需要用到插件,请在文章头部增加,如\n\n```markdown\n---\nuse: mermaid,katex...\n---\n```\n\n## valine 文章评论\n\n首先需要注册 `LeanCloud` 国际区用户,创建数据库\n\n请根据相关地区法规,酌情,全局默认关闭该插件\n\n```yml\nvaline:\n # 替换\n appId: appId\n # 替换\n appKey: appKey\n avatar: monsterid\n cdn: //unpkg.com/valine@latest/dist/Valine.min.js\n # 替换\n serverURLs: //xxxxxxxx.api.lncldglobal.com\n```\n\n为了加载速度,如果文章需要用到插件,请在文章头部增加,如\n\n```markdown\n---\nuse: valine,mermaid...\n---\n```\n\n# algolia搜索\n\n博客自带的本地搜索,基于前端开发,存在或多或少的问题,建议换成 `algolia`,免费账户 总共有 `10,000` 条记录,每月有 `100,000` 的操作数\n\n## 关闭搜索\n\n主页左侧导航栏不再显示搜索,通知相关资源不会加载,在主题 `_config.yml` 中:\n\n```yml\nsearch: false\n```\n\n## 注册 & 获取 Key\n\n1. 创建一个新的 `Index`,例如 `hex-blog`\n\n2. 复制并保存:\n - Application ID\n - Search-Only API Key\n - Admin API Key\n - Usage API Key\n\n![API Keys](/img/algolia-api-keys.png)\n\n3. 替换配置\n\n在 `Hexo` 的 `_config.yml` 中加入\n\n```yml\nalgolia:\n # 替换\n appId: Application ID\n # 替换\n apiKey: Usage API Key\n # 请勿泄露,用于上报,替换\n adminApiKey: Admin API Key\n # 替换\n SearchOnlyAPIKey: Search-Only API Key\n chunkSize: 5000\n # 替换\n indexName: hex-blog\n fields:\n - content\n - excerpt:strip\n - categories\n - title\n - permalink\n - slug\n - tags\n - title\n```\n\n## 安装 hexo-algoliasearch-next\n\n这是 Hexo 博客帖子索引插件,自动化提交索引到 Algolia\n\n> 如出遇到问题,可阅读 [hexo-algoliasearch-next 仓库](https://github.com/Becavalier/hexo-algoliasearch-next) 最新说明\n\n安装命令:\n\n```bash\nnpm install hexo-algoliasearch-next --save\n```\n\n## 使用 algolia\n\n在每次博客发布之前,进行索引上传的操作,命令通常如下\n\n```bash\nhexo clean\nhexo generate\nhexo algolia\n``` \n\n\n# 🇨🇳 和风天气 Widget\n\nCosy 主要针对国内用户,在首页集成了和风天气的卡片,通过 [和风天气开发服务](https://dev.qweather.com/docs/start/) 注册\n\n注册完成后,在配置中填入你的 `appKey` 和 `cityCode(城市代码)`\n\n```yml\nweather:\n enable: true\n # 替换\n cityCode: cityCode\n appKey: appKey\n```\n\n其中 `cityCode` 可以在 [官方的地区列表仓库](https://github.com/qwd/LocationList) 中的 `China-City-List-latest.csv` 找到你所在城市的 `cityCode`\n\n# 前置元数据\n\n在 Hexo 的 Markdown 文件中,一个典型的 YAML 格式的 `Front Matter` 可能会是这样的:\n\n```markdown\n---\ntitle: 我的文章标题\ncategories:\n- javascript\ntags: \n- 编程\n- JavaScript\n---\n```\n\n## top\n\n实现文章置顶的功能,给定一个数值,可以进行排序,设定了 `top` 元数据的文章,会在分类列表中,使用 📌 标记\n\n例如有三篇文章:\n\n- 文章1\n\n```markdown\n---\ntitle: 文章1\ntop: 0\ncategories:\n- javascript\ntags: \n- 编程\n- JavaScript\n---\n```\n\n- 文章2\n\n```markdown\n---\ntitle: 文章2\ntop: 1\ncategories:\n- javascript\ntags: \n- 编程\n- JavaScript\n---\n```\n\n- 文章3\n\n```markdown\n---\ntitle: 文章3\ncategories:\n- javascript\ntags: \n- 编程\n- JavaScript\n---\n```\n\n那么在 `javascript` 分类下,排序的顺序依次为:`文章1 > 文章2 > 文章3`\n\n## status\n\n用于区分文章的状态,同时利用文章分类列表的`筛选`,进行快速筛选,Cosy 主题内置了 4 种状态\n\n- done:完成\n- doing:进行中\n- todo:待办\n- other:废弃\n\n例如:\n\n```markdown\n---\ntitle: 文章1\ncategories:\n- javascript\nstatus: done\n---\n```\n\n# 自定义页面\n\nHexo 使用 Markdown(或其他渲染引擎)解析你的文章,并生成静态文件以快速加载。除了默认生成的文章和归档页面之外,Hexo 还允许你创建自定义页面。\n\n## 基本配置\n\n可以通过配置 `nav_meta` 属性关闭页面、更改图标\n\n```yml\nnav_meta:\n # 不显示 timeline页面\n timeline: false\n # 自定义图标\n roadmap:\n \n \n \n \n \n \n \n resume:\n \n \n \n \n \n \n \n \n```\n\n## Roadmap路线图\n\n创建页面,你可以使用命令\n\n```bash\nhexo new page roadmap\n```\n\n成功后在 `source/` 文件夹下会生成一个新的文件夹 `/roadmap/index.md`\n\n你可以参照此模板,进行配置,参数说明:\n\n- title: roadmap页面 html 的标题\n- initYear: 默认显示的年份\n\n```markdown\n---\ntitle: 陈不渡 - roadmap\nlayout: roadmap\ninitYear: 2023\nyears:\n 2022:\n - title: 读书\n start: 01-01\n end: 1-5\n - title: 还是读书\n start: 02-01\n end: 05-30\n 2023:\n - title: 越陌度阡\n start: 01-01\n end: 1-2\n - title: 枉用相存\n start: 02-01\n end: 06-30\n - title: 短歌行\n start: 10-26\n end: 10-31\n content: 对酒当歌,人生几何!譬如朝露,去日苦多。慨当以慷,忧思难忘。何以解忧?唯有杜康。青青子衿,悠悠我心。但为君故,沉吟至今。呦呦鹿鸣,食野之苹。\n - title: 声律启蒙\n start: 11-01\n end: 11-31\n content: 花开红锦绣,水漾碧琉璃。去妇因探邻舍枣,出妻为种后园葵\n---\n```\n\n## Resume简历页面\n\n创建页面,你可以使用命令\n\n```bash\nhexo new page resume\n```\n\n成功后在 `source/` 文件夹下会生成一个新的文件夹 `/resume/index.md`\n\n你可以参照此模板,进行配置,参数说明:\n\n- title: resume 页面 html 的标题\n- avatar: 头像图片的地址\n\n```yml\ntitle: 页面标题document.title\nlayout: resume\navatar: /img/avatar.png\nname: Hi! Mozzie\nrole: Full Stack\nemail: himozzie@gmail.com\nphone: +86 180-xxxx-xxx\nbirth: Jan 21, 1994\nlocation: Nanjing, China\nsocial:\n - name: github\n link: https://github.com/17px\n icon: svg\n\nabout:\n - 我的工作是建立你的网站,使其功能强大,用户友好,但同时具有吸引力。\n - 此外,我为您的产品添加了个人风格,并确保其引人注目且易于使用。我的目标是以最有创意的方式传达你的信息和身份。我为许多知名品牌公司设计网页。\n\nskill:\n - 熟悉Node,具备后端开发能力,有SpringBoot、Egg、Koa2、Midway等单个应用项目经验,有Nestjs微服务应用项目经验\n - 具有Monoreo工程经验,能够分离单个单元的前端和后端并应用DevOps\n\neducation:\n - school: 大学艺术学院\n time: 2012-2016\n\nworkExp:\n - inc: 创意研发\n time: 2021.09 ~ 至今\n - inc: Web设计\n time: 2021.01 ~ 2021.09\n\nprojectExp:\n - name: PC/React• 标注 ,算法数据中心\n desc: 这是一个xxx系统。它主要包括一个bc函数。我主要负责x、y和z模块的开发和维护。\n list: \n - 使用pnpm工作区管理Monorepo中的多个代码库,支持多个框架共存,共享通用组件库和功能\n\n - name: PC/React•Tavigator主动脉根部/外周\n desc: 这是一个xxx系统。它主要包括一个bc函数。我主要负责x、y和z模块的开发和维护。\n\n\nportfolio:\n - name: 项目A\n desc: 项目A描述\n iconSVG: 复制svg的path到这里\n link: 网址,http(s)://...\n # 更多 ...\n\n```","slug":"Cosy-Starter-Guide","published":1,"date":"2023-10-23T04:01:13.108Z","updated":"2023-11-17T02:57:03.295Z","comments":1,"layout":"post","photos":[],"link":"","_id":"clp7x194u0003v3z3cgik2min","content":"

安装

使用 npm 安装

在主题的根目录

\n
npm i hexo-theme-cosy
\n\n

此外,需要新建一个 _config.cosy.yml,同时修改 _config.yml

\n
theme: cosy\n\nalgolia:\n  appId: 你的\n  apiKey: 你的\n  adminApiKey: 你的\n  SearchOnlyAPIKey: 你的\n  chunkSize: 5000\n  indexName: 你的algolia的index\n  fields:\n    - content\n    - excerpt:strip\n    - categories\n    - title\n    - permalink\n    - slug\n    - tags\n    - title
\n\n

传统安装 Hexo 安装主题

将主题 Hexo-theme-cosy 文件夹复制目录的 themes 目录下,然后在 Hexo_config.yml 中修改下主题配置即可

\n
# 找到 theme 配置项\ntheme: Hexo-theme-cosy
\n\n

获取 Cosy

\n

hexo配置

Hexo_config.yml 中调整

\n

基础配置

# 网页标题\ntitle: 17px blog\n# 侧边栏顶部显示\nsubtitle: \"Mozzie\"\n# 用于SEO的html元描述\ndescription: \"\"\n# 用于SEO的html关键字\nkeywords:\n# 文章版权声明显示作者名称\nauthor: Mozzie\n\n# 在此处设置您的网站url\nurl: https://mozzie.cn
\n\n

语言

你可以在 hexo-theme-cosy/languages 中找到不同的语言文件,如果想切换语言,在配置文件中,填入 yml 的文件名

\n
language: en
\n\n

文章语法高亮

关闭hexo默认的highlight.js语法高亮

\n
highlight:\n  enable: false\n  line_number: true\n  auto_detect: false\n  tab_replace: \"\"\n  wrap: true\n  hljs: false\n\nprismjs:\n  enable: true\n  preprocess: true\n  line_number: true\n  line_threshold: 0\n  tab_replace: \"\"
\n\n

Cosy 主题配置

下面的配置基于 Hexo-theme-cosy 下的 _config.yml

\n

文章分类图标

\n

可以在 xicon 获取丰富的图标

\n
\n

主题_config.yml文件中,添加分类名称图标svg的映射,如值设置为 false,代表不显示该分类

\n
category_meta:\n  foo: <svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" viewBox=\"0 0 24 24\"><g fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 17v1a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-1\"></path><path d=\"M8 16h8\"></path><path d=\"M8.322 12.582l7.956.836\"></path><path d=\"M8.787 9.168l7.826 1.664\"></path><path d=\"M10.096 5.764l7.608 2.472\"></path></g></svg>\n  bar: false
\n\n

网站图标 favicon

\n
favicon: ''
\n\n

ICP备案号

😁 如不需要备案号,可直接删除

\n
icp: 苏ICP备xxxxxxx号-x
\n\n

首页底部文字

😁 如不需要,可直接删除,或者改为 false

\n
motto: false
\n\n

文章版权申明

默认开启,当 enable: false,默认关闭

\n
postCopyright:\n  enable: true\n  license: CC BY-NC-SA 4.0\n  license_url: https://creativecommons.org/licenses/by-nc-sa/4.0/
\n\n

katex 数学公式

可自行配置 cdn,全局默认关闭该插件

\n
katex:\n  jsCdn: //cdn.jsdelivr.net/npm/katex@0.13.18/dist/katex.min.js\n  cssCdn: //cdn.jsdelivr.net/npm/katex@0.13.18/dist/katex.min.css
\n\n

为了加载速度,如果文章需要用到插件,请在文章头部增加,如

\n
---\nuse: katex\n---
\n\n

mermaid 流程图

在撰写时,请使用 {% mermaid %}` 和 `{% endmermaid %} 包裹,全局默认关闭该插件

\n
{% mermaid %}\ngraph TD;\n    A --> B;\n    A --> C;\n    B --> D;\n    C --> D;\n{% endmermaid %}
\n\n

相应配置如下

\n
mermaid:\n  # 默认使用 neutral,可选配置:default | dark | forest | neutral\n  theme: neutral\n  cdn: //cdn.jsdelivr.net/npm/mermaid@10.4.0/dist/mermaid.min.js
\n\n

为了加载速度,如果文章需要用到插件,请在文章头部增加,如

\n
---\nuse: mermaid,katex...\n---
\n\n

valine 文章评论

首先需要注册 LeanCloud 国际区用户,创建数据库

\n

请根据相关地区法规,酌情,全局默认关闭该插件

\n
valine:\n  # 替换\n  appId: appId\n  # 替换\n  appKey: appKey\n  avatar: monsterid\n  cdn: //unpkg.com/valine@latest/dist/Valine.min.js\n  # 替换\n  serverURLs: //xxxxxxxx.api.lncldglobal.com
\n\n

为了加载速度,如果文章需要用到插件,请在文章头部增加,如

\n
---\nuse: valine,mermaid...\n---
\n\n

algolia搜索

博客自带的本地搜索,基于前端开发,存在或多或少的问题,建议换成 algolia,免费账户 总共有 10,000 条记录,每月有 100,000 的操作数

\n

关闭搜索

主页左侧导航栏不再显示搜索,通知相关资源不会加载,在主题 _config.yml 中:

\n
search: false
\n\n

注册 & 获取 Key

    \n
  1. 创建一个新的 Index,例如 hex-blog

    \n
  2. \n
  3. 复制并保存:

    \n
  4. \n
\n\n

\"API

\n
    \n
  1. 替换配置
  2. \n
\n

Hexo_config.yml 中加入

\n
algolia:\n  # 替换\n  appId: Application ID\n  # 替换\n  apiKey: Usage API Key\n  # 请勿泄露,用于上报,替换\n  adminApiKey: Admin API Key\n  # 替换\n  SearchOnlyAPIKey: Search-Only API Key\n  chunkSize: 5000\n  # 替换\n  indexName: hex-blog\n  fields:\n    - content\n    - excerpt:strip\n    - categories\n    - title\n    - permalink\n    - slug\n    - tags\n    - title
\n\n

安装 hexo-algoliasearch-next

这是 Hexo 博客帖子索引插件,自动化提交索引到 Algolia

\n
\n

如出遇到问题,可阅读 hexo-algoliasearch-next 仓库 最新说明

\n
\n

安装命令:

\n
npm install hexo-algoliasearch-next --save
\n\n

使用 algolia

在每次博客发布之前,进行索引上传的操作,命令通常如下

\n
hexo clean\nhexo generate\nhexo algolia
\n\n\n

🇨🇳 和风天气 Widget

Cosy 主要针对国内用户,在首页集成了和风天气的卡片,通过 和风天气开发服务 注册

\n

注册完成后,在配置中填入你的 appKeycityCode(城市代码)

\n
weather:\n  enable: true\n  # 替换\n  cityCode: cityCode\n  appKey: appKey
\n\n

其中 cityCode 可以在 官方的地区列表仓库 中的 China-City-List-latest.csv 找到你所在城市的 cityCode

\n

前置元数据

在 Hexo 的 Markdown 文件中,一个典型的 YAML 格式的 Front Matter 可能会是这样的:

\n
---\ntitle: 我的文章标题\ncategories:\n- javascript\ntags: \n- 编程\n- JavaScript\n---
\n\n

top

实现文章置顶的功能,给定一个数值,可以进行排序,设定了 top 元数据的文章,会在分类列表中,使用 📌 标记

\n

例如有三篇文章:

\n\n
---\ntitle: 文章1\ntop: 0\ncategories:\n- javascript\ntags: \n- 编程\n- JavaScript\n---
\n\n\n
---\ntitle: 文章2\ntop: 1\ncategories:\n- javascript\ntags: \n- 编程\n- JavaScript\n---
\n\n\n
---\ntitle: 文章3\ncategories:\n- javascript\ntags: \n- 编程\n- JavaScript\n---
\n\n

那么在 javascript 分类下,排序的顺序依次为:文章1 > 文章2 > 文章3

\n

status

用于区分文章的状态,同时利用文章分类列表的筛选,进行快速筛选,Cosy 主题内置了 4 种状态

\n\n

例如:

\n
---\ntitle: 文章1\ncategories:\n- javascript\nstatus: done\n---
\n\n

自定义页面

Hexo 使用 Markdown(或其他渲染引擎)解析你的文章,并生成静态文件以快速加载。除了默认生成的文章和归档页面之外,Hexo 还允许你创建自定义页面。

\n

基本配置

可以通过配置 nav_meta 属性关闭页面、更改图标

\n
nav_meta:\n  # 不显示 timeline页面\n  timeline: false\n  # 自定义图标\n  roadmap:\n    <svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" viewBox=\"0 0 24 24\">\n    <g fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n    <path d=\"M10.5 20.4l-6.9-6.9c-.781-.781-.781-2.219 0-3l6.9-6.9c.781-.781 2.219-.781 3 0l6.9 6.9c.781.781.781 2.219 0 3l-6.9 6.9c-.781.781-2.219.781-3 0z\"></path>\n    <path d=\"M9 14v-2c0-.59.414-1 1-1h5\"></path>\n    <path d=\"M13 9l2 2l-2 2\"></path>\n    </g>\n    </svg>\n  resume:\n    <svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" viewBox=\"0 0 24 24\">\n    <g fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n    <path d=\"M14 3v4a1 1 0 0 0 1 1h4\"></path>\n    <path d=\"M5 8V5a2 2 0 0 1 2-2h7l5 5v11a2 2 0 0 1-2 2h-5\"></path>\n    <circle cx=\"6\" cy=\"14\" r=\"3\"></circle>\n    <path d=\"M4.5 17L3 22l3-1.5L9 22l-1.5-5\"></path>\n    </g>\n    </svg>
\n\n

Roadmap路线图

创建页面,你可以使用命令

\n
hexo new page roadmap
\n\n

成功后在 source/ 文件夹下会生成一个新的文件夹 /roadmap/index.md

\n

你可以参照此模板,进行配置,参数说明:

\n\n
---\ntitle: 陈不渡 - roadmap\nlayout: roadmap\ninitYear: 2023\nyears:\n  2022:\n    - title: 读书\n      start: 01-01\n      end: 1-5\n    - title: 还是读书\n      start: 02-01\n      end: 05-30\n  2023:\n    - title: 越陌度阡\n      start: 01-01\n      end: 1-2\n    - title: 枉用相存\n      start: 02-01\n      end: 06-30\n    - title: 短歌行\n      start: 10-26\n      end: 10-31\n      content: 对酒当歌,人生几何!譬如朝露,去日苦多。慨当以慷,忧思难忘。何以解忧?唯有杜康。青青子衿,悠悠我心。但为君故,沉吟至今。呦呦鹿鸣,食野之苹。\n    - title: 声律启蒙\n      start: 11-01\n      end: 11-31\n      content: 花开红锦绣,水漾碧琉璃。去妇因探邻舍枣,出妻为种后园葵\n---
\n\n

Resume简历页面

创建页面,你可以使用命令

\n
hexo new page resume
\n\n

成功后在 source/ 文件夹下会生成一个新的文件夹 /resume/index.md

\n

你可以参照此模板,进行配置,参数说明:

\n\n
title: 页面标题document.title\nlayout: resume\navatar: /img/avatar.png\nname: Hi! Mozzie\nrole: Full Stack\nemail: himozzie@gmail.com\nphone: +86 180-xxxx-xxx\nbirth: Jan 21, 1994\nlocation: Nanjing, China\nsocial:\n  - name: github\n    link: https://github.com/17px\n    icon: svg\n\nabout:\n  - 我的工作是建立你的网站,使其功能强大,用户友好,但同时具有吸引力。\n  - 此外,我为您的产品添加了个人风格,并确保其引人注目且易于使用。我的目标是以最有创意的方式传达你的信息和身份。我为许多知名品牌公司设计网页。\n\nskill:\n  - 熟悉Node,具备后端开发能力,有SpringBoot、Egg、Koa2、Midway等单个应用项目经验,有Nestjs微服务应用项目经验\n  - 具有Monoreo工程经验,能够分离单个单元的前端和后端并应用DevOps\n\neducation:\n  - school: 大学艺术学院\n    time: 2012-2016\n\nworkExp:\n  - inc: 创意研发\n    time: 2021.09 ~ 至今\n  - inc: Web设计\n    time: 2021.01 ~ 2021.09\n\nprojectExp:\n  - name: PC/React• 标注 ,算法数据中心\n    desc: 这是一个xxx系统。它主要包括一个bc函数。我主要负责x、y和z模块的开发和维护。\n    list: \n      - 使用pnpm工作区管理Monorepo中的多个代码库,支持多个框架共存,共享通用组件库和功能\n\n  - name: PC/React•Tavigator主动脉根部/外周\n    desc: 这是一个xxx系统。它主要包括一个bc函数。我主要负责x、y和z模块的开发和维护。\n\n\nportfolio:\n  - name: 项目A\n    desc: 项目A描述\n    iconSVG: 复制svg的path到这里\n    link: 网址,http(s)://...\n  # 更多 ...\n
","site":{"data":{}},"excerpt":"","more":"

安装

使用 npm 安装

在主题的根目录

\n
npm i hexo-theme-cosy
\n\n

此外,需要新建一个 _config.cosy.yml,同时修改 _config.yml

\n
theme: cosy\n\nalgolia:\n  appId: 你的\n  apiKey: 你的\n  adminApiKey: 你的\n  SearchOnlyAPIKey: 你的\n  chunkSize: 5000\n  indexName: 你的algolia的index\n  fields:\n    - content\n    - excerpt:strip\n    - categories\n    - title\n    - permalink\n    - slug\n    - tags\n    - title
\n\n

传统安装 Hexo 安装主题

将主题 Hexo-theme-cosy 文件夹复制目录的 themes 目录下,然后在 Hexo_config.yml 中修改下主题配置即可

\n
# 找到 theme 配置项\ntheme: Hexo-theme-cosy
\n\n

获取 Cosy

\n

hexo配置

Hexo_config.yml 中调整

\n

基础配置

# 网页标题\ntitle: 17px blog\n# 侧边栏顶部显示\nsubtitle: \"Mozzie\"\n# 用于SEO的html元描述\ndescription: \"\"\n# 用于SEO的html关键字\nkeywords:\n# 文章版权声明显示作者名称\nauthor: Mozzie\n\n# 在此处设置您的网站url\nurl: https://mozzie.cn
\n\n

语言

你可以在 hexo-theme-cosy/languages 中找到不同的语言文件,如果想切换语言,在配置文件中,填入 yml 的文件名

\n
language: en
\n\n

文章语法高亮

关闭hexo默认的highlight.js语法高亮

\n
highlight:\n  enable: false\n  line_number: true\n  auto_detect: false\n  tab_replace: \"\"\n  wrap: true\n  hljs: false\n\nprismjs:\n  enable: true\n  preprocess: true\n  line_number: true\n  line_threshold: 0\n  tab_replace: \"\"
\n\n

Cosy 主题配置

下面的配置基于 Hexo-theme-cosy 下的 _config.yml

\n

文章分类图标

\n

可以在 xicon 获取丰富的图标

\n
\n

主题_config.yml文件中,添加分类名称图标svg的映射,如值设置为 false,代表不显示该分类

\n
category_meta:\n  foo: <svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" viewBox=\"0 0 24 24\"><g fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"><path d=\"M4 17v1a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-1\"></path><path d=\"M8 16h8\"></path><path d=\"M8.322 12.582l7.956.836\"></path><path d=\"M8.787 9.168l7.826 1.664\"></path><path d=\"M10.096 5.764l7.608 2.472\"></path></g></svg>\n  bar: false
\n\n

网站图标 favicon

\n
favicon: ''
\n\n

ICP备案号

😁 如不需要备案号,可直接删除

\n
icp: 苏ICP备xxxxxxx号-x
\n\n

首页底部文字

😁 如不需要,可直接删除,或者改为 false

\n
motto: false
\n\n

文章版权申明

默认开启,当 enable: false,默认关闭

\n
postCopyright:\n  enable: true\n  license: CC BY-NC-SA 4.0\n  license_url: https://creativecommons.org/licenses/by-nc-sa/4.0/
\n\n

katex 数学公式

可自行配置 cdn,全局默认关闭该插件

\n
katex:\n  jsCdn: //cdn.jsdelivr.net/npm/katex@0.13.18/dist/katex.min.js\n  cssCdn: //cdn.jsdelivr.net/npm/katex@0.13.18/dist/katex.min.css
\n\n

为了加载速度,如果文章需要用到插件,请在文章头部增加,如

\n
---\nuse: katex\n---
\n\n

mermaid 流程图

在撰写时,请使用 {% mermaid %}` 和 `{% endmermaid %} 包裹,全局默认关闭该插件

\n
{% mermaid %}\ngraph TD;\n    A --> B;\n    A --> C;\n    B --> D;\n    C --> D;\n{% endmermaid %}
\n\n

相应配置如下

\n
mermaid:\n  # 默认使用 neutral,可选配置:default | dark | forest | neutral\n  theme: neutral\n  cdn: //cdn.jsdelivr.net/npm/mermaid@10.4.0/dist/mermaid.min.js
\n\n

为了加载速度,如果文章需要用到插件,请在文章头部增加,如

\n
---\nuse: mermaid,katex...\n---
\n\n

valine 文章评论

首先需要注册 LeanCloud 国际区用户,创建数据库

\n

请根据相关地区法规,酌情,全局默认关闭该插件

\n
valine:\n  # 替换\n  appId: appId\n  # 替换\n  appKey: appKey\n  avatar: monsterid\n  cdn: //unpkg.com/valine@latest/dist/Valine.min.js\n  # 替换\n  serverURLs: //xxxxxxxx.api.lncldglobal.com
\n\n

为了加载速度,如果文章需要用到插件,请在文章头部增加,如

\n
---\nuse: valine,mermaid...\n---
\n\n

algolia搜索

博客自带的本地搜索,基于前端开发,存在或多或少的问题,建议换成 algolia,免费账户 总共有 10,000 条记录,每月有 100,000 的操作数

\n

关闭搜索

主页左侧导航栏不再显示搜索,通知相关资源不会加载,在主题 _config.yml 中:

\n
search: false
\n\n

注册 & 获取 Key

    \n
  1. 创建一个新的 Index,例如 hex-blog

    \n
  2. \n
  3. 复制并保存:

    \n
  4. \n
\n\n

\"API

\n
    \n
  1. 替换配置
  2. \n
\n

Hexo_config.yml 中加入

\n
algolia:\n  # 替换\n  appId: Application ID\n  # 替换\n  apiKey: Usage API Key\n  # 请勿泄露,用于上报,替换\n  adminApiKey: Admin API Key\n  # 替换\n  SearchOnlyAPIKey: Search-Only API Key\n  chunkSize: 5000\n  # 替换\n  indexName: hex-blog\n  fields:\n    - content\n    - excerpt:strip\n    - categories\n    - title\n    - permalink\n    - slug\n    - tags\n    - title
\n\n

安装 hexo-algoliasearch-next

这是 Hexo 博客帖子索引插件,自动化提交索引到 Algolia

\n
\n

如出遇到问题,可阅读 hexo-algoliasearch-next 仓库 最新说明

\n
\n

安装命令:

\n
npm install hexo-algoliasearch-next --save
\n\n

使用 algolia

在每次博客发布之前,进行索引上传的操作,命令通常如下

\n
hexo clean\nhexo generate\nhexo algolia
\n\n\n

🇨🇳 和风天气 Widget

Cosy 主要针对国内用户,在首页集成了和风天气的卡片,通过 和风天气开发服务 注册

\n

注册完成后,在配置中填入你的 appKeycityCode(城市代码)

\n
weather:\n  enable: true\n  # 替换\n  cityCode: cityCode\n  appKey: appKey
\n\n

其中 cityCode 可以在 官方的地区列表仓库 中的 China-City-List-latest.csv 找到你所在城市的 cityCode

\n

前置元数据

在 Hexo 的 Markdown 文件中,一个典型的 YAML 格式的 Front Matter 可能会是这样的:

\n
---\ntitle: 我的文章标题\ncategories:\n- javascript\ntags: \n- 编程\n- JavaScript\n---
\n\n

top

实现文章置顶的功能,给定一个数值,可以进行排序,设定了 top 元数据的文章,会在分类列表中,使用 📌 标记

\n

例如有三篇文章:

\n\n
---\ntitle: 文章1\ntop: 0\ncategories:\n- javascript\ntags: \n- 编程\n- JavaScript\n---
\n\n\n
---\ntitle: 文章2\ntop: 1\ncategories:\n- javascript\ntags: \n- 编程\n- JavaScript\n---
\n\n\n
---\ntitle: 文章3\ncategories:\n- javascript\ntags: \n- 编程\n- JavaScript\n---
\n\n

那么在 javascript 分类下,排序的顺序依次为:文章1 > 文章2 > 文章3

\n

status

用于区分文章的状态,同时利用文章分类列表的筛选,进行快速筛选,Cosy 主题内置了 4 种状态

\n\n

例如:

\n
---\ntitle: 文章1\ncategories:\n- javascript\nstatus: done\n---
\n\n

自定义页面

Hexo 使用 Markdown(或其他渲染引擎)解析你的文章,并生成静态文件以快速加载。除了默认生成的文章和归档页面之外,Hexo 还允许你创建自定义页面。

\n

基本配置

可以通过配置 nav_meta 属性关闭页面、更改图标

\n
nav_meta:\n  # 不显示 timeline页面\n  timeline: false\n  # 自定义图标\n  roadmap:\n    <svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" viewBox=\"0 0 24 24\">\n    <g fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n    <path d=\"M10.5 20.4l-6.9-6.9c-.781-.781-.781-2.219 0-3l6.9-6.9c.781-.781 2.219-.781 3 0l6.9 6.9c.781.781.781 2.219 0 3l-6.9 6.9c-.781.781-2.219.781-3 0z\"></path>\n    <path d=\"M9 14v-2c0-.59.414-1 1-1h5\"></path>\n    <path d=\"M13 9l2 2l-2 2\"></path>\n    </g>\n    </svg>\n  resume:\n    <svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" viewBox=\"0 0 24 24\">\n    <g fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n    <path d=\"M14 3v4a1 1 0 0 0 1 1h4\"></path>\n    <path d=\"M5 8V5a2 2 0 0 1 2-2h7l5 5v11a2 2 0 0 1-2 2h-5\"></path>\n    <circle cx=\"6\" cy=\"14\" r=\"3\"></circle>\n    <path d=\"M4.5 17L3 22l3-1.5L9 22l-1.5-5\"></path>\n    </g>\n    </svg>
\n\n

Roadmap路线图

创建页面,你可以使用命令

\n
hexo new page roadmap
\n\n

成功后在 source/ 文件夹下会生成一个新的文件夹 /roadmap/index.md

\n

你可以参照此模板,进行配置,参数说明:

\n\n
---\ntitle: 陈不渡 - roadmap\nlayout: roadmap\ninitYear: 2023\nyears:\n  2022:\n    - title: 读书\n      start: 01-01\n      end: 1-5\n    - title: 还是读书\n      start: 02-01\n      end: 05-30\n  2023:\n    - title: 越陌度阡\n      start: 01-01\n      end: 1-2\n    - title: 枉用相存\n      start: 02-01\n      end: 06-30\n    - title: 短歌行\n      start: 10-26\n      end: 10-31\n      content: 对酒当歌,人生几何!譬如朝露,去日苦多。慨当以慷,忧思难忘。何以解忧?唯有杜康。青青子衿,悠悠我心。但为君故,沉吟至今。呦呦鹿鸣,食野之苹。\n    - title: 声律启蒙\n      start: 11-01\n      end: 11-31\n      content: 花开红锦绣,水漾碧琉璃。去妇因探邻舍枣,出妻为种后园葵\n---
\n\n

Resume简历页面

创建页面,你可以使用命令

\n
hexo new page resume
\n\n

成功后在 source/ 文件夹下会生成一个新的文件夹 /resume/index.md

\n

你可以参照此模板,进行配置,参数说明:

\n\n
title: 页面标题document.title\nlayout: resume\navatar: /img/avatar.png\nname: Hi! Mozzie\nrole: Full Stack\nemail: himozzie@gmail.com\nphone: +86 180-xxxx-xxx\nbirth: Jan 21, 1994\nlocation: Nanjing, China\nsocial:\n  - name: github\n    link: https://github.com/17px\n    icon: svg\n\nabout:\n  - 我的工作是建立你的网站,使其功能强大,用户友好,但同时具有吸引力。\n  - 此外,我为您的产品添加了个人风格,并确保其引人注目且易于使用。我的目标是以最有创意的方式传达你的信息和身份。我为许多知名品牌公司设计网页。\n\nskill:\n  - 熟悉Node,具备后端开发能力,有SpringBoot、Egg、Koa2、Midway等单个应用项目经验,有Nestjs微服务应用项目经验\n  - 具有Monoreo工程经验,能够分离单个单元的前端和后端并应用DevOps\n\neducation:\n  - school: 大学艺术学院\n    time: 2012-2016\n\nworkExp:\n  - inc: 创意研发\n    time: 2021.09 ~ 至今\n  - inc: Web设计\n    time: 2021.01 ~ 2021.09\n\nprojectExp:\n  - name: PC/React• 标注 ,算法数据中心\n    desc: 这是一个xxx系统。它主要包括一个bc函数。我主要负责x、y和z模块的开发和维护。\n    list: \n      - 使用pnpm工作区管理Monorepo中的多个代码库,支持多个框架共存,共享通用组件库和功能\n\n  - name: PC/React•Tavigator主动脉根部/外周\n    desc: 这是一个xxx系统。它主要包括一个bc函数。我主要负责x、y和z模块的开发和维护。\n\n\nportfolio:\n  - name: 项目A\n    desc: 项目A描述\n    iconSVG: 复制svg的path到这里\n    link: 网址,http(s)://...\n  # 更多 ...\n
"},{"title":"Markdown Sample","description":"A very simple way to add structured data to a page.","status":"done","top":0,"keywords":"Markdown, 代码高亮 (Codes), 引用 (Blockquotes), 列表 (Lists), 图片 (Images), 表格 (Tables), Emoji, TeX(KaTeX), 流程图 (mermaid)","use":"mermaid,katex,valine","_content":"\n# Linear Markdown Sample\n\n![markdown](https://pandao.github.io/editor.md/images/logos/editormd-logo-180x180.png)\n\n\n# Heading 1 link [Heading link](https://github.com/pandao/editor.md \"Heading link\")\n## Heading 2 link [Heading link](https://github.com/pandao/editor.md \"Heading link\")\n### Heading 3 link [Heading link](https://github.com/pandao/editor.md \"Heading link\")\n#### Heading 4 link [Heading link](https://github.com/pandao/editor.md \"Heading link\") Heading link [Heading link](https://github.com/pandao/editor.md \"Heading link\")\n##### Heading 5 link [Heading link](https://github.com/pandao/editor.md \"Heading link\")\n###### Heading 6 link [Heading link](https://github.com/pandao/editor.md \"Heading link\")\n\n\n\n# 字符效果和横线等\n \n----\n\n~~删除线~~ 删除线(开启识别HTML标签时)\n*斜体字* _斜体字_\n**粗体** __粗体__\n***粗斜体*** ___粗斜体___\n\n上标:X2,下标:O2\n\n**缩写(同HTML的abbr标签)**\n\n> 即更长的单词或短语的缩写形式,前提是开启识别HTML标签时,已默认开启\n\nThe HTML specification is maintained by the W3C.\n\n# 引用 Blockquotes\n\n> 引用文本 Blockquotes\n\n引用的行内混合 Blockquotes\n \n> 引用:如果想要插入空白换行`即
标签`,在插入处先键入两个以上的空格然后回车即可,[普通链接](http://localhost/)。\n\n# 锚点与链接 Links\n\n[普通链接](http://localhost/)\n\n[普通链接带标题](http://localhost/ \"普通链接带标题\")\n\n直接链接:\n\nhttps://baidu.com\n\n[锚点链接][anchor-id] \n\n[anchor-id]: http://www.this-anchor-link.com/\n\n[mailto:test.test@gmail.com](mailto:test.test@gmail.com)\n\nGFM a-tail link [@pandao](https://my.oschina.net/u/3691274) 邮箱地址自动链接 test.test@gmail.com www@vip.qq.com\n\n> @pandao\n\n# 多语言代码高亮 Codes\n\n## 行内代码 Inline code\n\n执行命令:`npm install marked`\n\n## JS代码 \n\n```javascript\nfunction test() {\n\tconsole.log(\"Hello world!\");\n}\n \n(function(){\n var box = function() {\n return box.fn.init();\n };\n\n box.prototype = box.fn = {\n init : function(){\n console.log('box.init()');\n\n\t\t\treturn this;\n },\n\n\t\tadd : function(str) {\n\t\t\talert(\"add\", str);\n\n\t\t\treturn this;\n\t\t},\n\n\t\tremove : function(str) {\n\t\t\talert(\"remove\", str);\n\n\t\t\treturn this;\n\t\t}\n };\n \n box.fn.init.prototype = box.fn;\n \n window.box =box;\n})();\n\nvar testBox = box();\ntestBox.add(\"jQuery\").remove(\"jQuery\");\n```\n\n## HTML 代码 HTML codes\n\n```html\n\n\n \n \n \n Hello world!\n \n \n \n

Hello world!

\n

Plain text

\n \n\n```\n\n# 图片 Images\n\nImage:\n\n![](https://pandao.github.io/editor.md/examples/images/4.jpg)\n\n> Follow your heart.\n\n![](https://pandao.github.io/editor.md/examples/images/8.jpg)\n\n> 图为:厦门白城沙滩\n\n图片加链接 (Image + Link):\n\n[![](https://pandao.github.io/editor.md/examples/images/7.jpg)](https://pandao.github.io/editor.md/images/7.jpg \"李健首张专辑《似水流年》封面\")\n\n> 图为:李健首张专辑《似水流年》封面\n \n----\n\n# 列表 Lists\n\n## 无序列表(减号)Unordered Lists (-)\n \n- 列表一\n- 列表二\n- 列表三\n \n## 无序列表(星号)Unordered Lists (*)\n\n* 列表一\n* 列表二\n* 列表三\n\n## 无序列表(加号和嵌套)Unordered Lists (+)\n \n+ 列表一\n+ 列表二\n + 列表二-1\n + 列表二-2\n + 列表二-3\n+ 列表三\n * 列表一\n * 列表二\n * 列表三\n\n## 有序列表 Ordered Lists (-)\n \n1. 第一行\n2. 第二行\n3. 第三行\n\n## GFM task list\n\n- [x] GFM task list 1\n- [x] GFM task list 2\n- [ ] GFM task list 3\n - [ ] GFM task list 3-1\n - [ ] GFM task list 3-2\n - [ ] GFM task list 3-3\n- [ ] GFM task list 4\n - [ ] GFM task list 4-1\n - [ ] GFM task list 4-2\n \n----\n \n# 绘制表格 Tables\n\n| 项目 | 价格 | 数量 |\n| ------ | ----: | :---: |\n| 计算机 | $1600 | 5 |\n| 手机 | $12 | 12 |\n| 管线 | $1 | 234 |\n \n| First Header | Second Header |\n| ------------ | ------------- |\n| Content Cell | Content Cell |\n| Content Cell | Content Cell |\n\n| First Header | Second Header |\n| ------------ | ------------- |\n| Content Cell | Content Cell |\n| Content Cell | Content Cell |\n\n| Function name | Description |\n| ------------- | -------------------------- |\n| `help()` | Display the help window. |\n| `destroy()` | **Destroy your computer!** |\n\n| Left-Aligned | Center Aligned | Right Aligned |\n| :------------ | :-------------: | ------------: |\n| col 3 is | some wordy text | $1600 |\n| col 2 is | centered | $12 |\n| zebra stripes | are neat | $1 |\n\n| Item | Value |\n| -------- | ----: |\n| Computer | $1600 |\n| Phone | $12 |\n| Pipe | $1 |\n \n# 科学公式 TeX(KaTeX)\n\n$$E=mc^2$$\n\n$$x > y$$\n\n$$\\(\\sqrt{3x-1}+(1+x)^2\\)$$\n \n$$\\sin(\\alpha)^{\\theta}=\\sum_{i=0}^{n}(x^i + \\cos(f))$$\n\n\n# 绘制流程图 mermaid\n\n{% mermaid %}\ngraph TD;\n A --> B;\n A --> C;\n B --> D;\n C --> D;\n{% endmermaid %}\n\n \n \nEnd","source":"_posts/Markdown Sample.md","raw":"---\ntitle: Markdown Sample\ndescription: A very simple way to add structured data to a page.\nstatus: done\ntop: 0\ncategories: \n- Hexo\nkeywords: \"Markdown, 代码高亮 (Codes), 引用 (Blockquotes), 列表 (Lists), 图片 (Images), 表格 (Tables), Emoji, TeX(KaTeX), 流程图 (mermaid)\"\nuse: mermaid,katex,valine\n---\n\n# Linear Markdown Sample\n\n![markdown](https://pandao.github.io/editor.md/images/logos/editormd-logo-180x180.png)\n\n\n# Heading 1 link [Heading link](https://github.com/pandao/editor.md \"Heading link\")\n## Heading 2 link [Heading link](https://github.com/pandao/editor.md \"Heading link\")\n### Heading 3 link [Heading link](https://github.com/pandao/editor.md \"Heading link\")\n#### Heading 4 link [Heading link](https://github.com/pandao/editor.md \"Heading link\") Heading link [Heading link](https://github.com/pandao/editor.md \"Heading link\")\n##### Heading 5 link [Heading link](https://github.com/pandao/editor.md \"Heading link\")\n###### Heading 6 link [Heading link](https://github.com/pandao/editor.md \"Heading link\")\n\n\n\n# 字符效果和横线等\n \n----\n\n~~删除线~~ 删除线(开启识别HTML标签时)\n*斜体字* _斜体字_\n**粗体** __粗体__\n***粗斜体*** ___粗斜体___\n\n上标:X2,下标:O2\n\n**缩写(同HTML的abbr标签)**\n\n> 即更长的单词或短语的缩写形式,前提是开启识别HTML标签时,已默认开启\n\nThe HTML specification is maintained by the W3C.\n\n# 引用 Blockquotes\n\n> 引用文本 Blockquotes\n\n引用的行内混合 Blockquotes\n \n> 引用:如果想要插入空白换行`即
标签`,在插入处先键入两个以上的空格然后回车即可,[普通链接](http://localhost/)。\n\n# 锚点与链接 Links\n\n[普通链接](http://localhost/)\n\n[普通链接带标题](http://localhost/ \"普通链接带标题\")\n\n直接链接:\n\nhttps://baidu.com\n\n[锚点链接][anchor-id] \n\n[anchor-id]: http://www.this-anchor-link.com/\n\n[mailto:test.test@gmail.com](mailto:test.test@gmail.com)\n\nGFM a-tail link [@pandao](https://my.oschina.net/u/3691274) 邮箱地址自动链接 test.test@gmail.com www@vip.qq.com\n\n> @pandao\n\n# 多语言代码高亮 Codes\n\n## 行内代码 Inline code\n\n执行命令:`npm install marked`\n\n## JS代码 \n\n```javascript\nfunction test() {\n\tconsole.log(\"Hello world!\");\n}\n \n(function(){\n var box = function() {\n return box.fn.init();\n };\n\n box.prototype = box.fn = {\n init : function(){\n console.log('box.init()');\n\n\t\t\treturn this;\n },\n\n\t\tadd : function(str) {\n\t\t\talert(\"add\", str);\n\n\t\t\treturn this;\n\t\t},\n\n\t\tremove : function(str) {\n\t\t\talert(\"remove\", str);\n\n\t\t\treturn this;\n\t\t}\n };\n \n box.fn.init.prototype = box.fn;\n \n window.box =box;\n})();\n\nvar testBox = box();\ntestBox.add(\"jQuery\").remove(\"jQuery\");\n```\n\n## HTML 代码 HTML codes\n\n```html\n\n\n \n \n \n Hello world!\n \n \n \n

Hello world!

\n

Plain text

\n \n\n```\n\n# 图片 Images\n\nImage:\n\n![](https://pandao.github.io/editor.md/examples/images/4.jpg)\n\n> Follow your heart.\n\n![](https://pandao.github.io/editor.md/examples/images/8.jpg)\n\n> 图为:厦门白城沙滩\n\n图片加链接 (Image + Link):\n\n[![](https://pandao.github.io/editor.md/examples/images/7.jpg)](https://pandao.github.io/editor.md/images/7.jpg \"李健首张专辑《似水流年》封面\")\n\n> 图为:李健首张专辑《似水流年》封面\n \n----\n\n# 列表 Lists\n\n## 无序列表(减号)Unordered Lists (-)\n \n- 列表一\n- 列表二\n- 列表三\n \n## 无序列表(星号)Unordered Lists (*)\n\n* 列表一\n* 列表二\n* 列表三\n\n## 无序列表(加号和嵌套)Unordered Lists (+)\n \n+ 列表一\n+ 列表二\n + 列表二-1\n + 列表二-2\n + 列表二-3\n+ 列表三\n * 列表一\n * 列表二\n * 列表三\n\n## 有序列表 Ordered Lists (-)\n \n1. 第一行\n2. 第二行\n3. 第三行\n\n## GFM task list\n\n- [x] GFM task list 1\n- [x] GFM task list 2\n- [ ] GFM task list 3\n - [ ] GFM task list 3-1\n - [ ] GFM task list 3-2\n - [ ] GFM task list 3-3\n- [ ] GFM task list 4\n - [ ] GFM task list 4-1\n - [ ] GFM task list 4-2\n \n----\n \n# 绘制表格 Tables\n\n| 项目 | 价格 | 数量 |\n| ------ | ----: | :---: |\n| 计算机 | $1600 | 5 |\n| 手机 | $12 | 12 |\n| 管线 | $1 | 234 |\n \n| First Header | Second Header |\n| ------------ | ------------- |\n| Content Cell | Content Cell |\n| Content Cell | Content Cell |\n\n| First Header | Second Header |\n| ------------ | ------------- |\n| Content Cell | Content Cell |\n| Content Cell | Content Cell |\n\n| Function name | Description |\n| ------------- | -------------------------- |\n| `help()` | Display the help window. |\n| `destroy()` | **Destroy your computer!** |\n\n| Left-Aligned | Center Aligned | Right Aligned |\n| :------------ | :-------------: | ------------: |\n| col 3 is | some wordy text | $1600 |\n| col 2 is | centered | $12 |\n| zebra stripes | are neat | $1 |\n\n| Item | Value |\n| -------- | ----: |\n| Computer | $1600 |\n| Phone | $12 |\n| Pipe | $1 |\n \n# 科学公式 TeX(KaTeX)\n\n$$E=mc^2$$\n\n$$x > y$$\n\n$$\\(\\sqrt{3x-1}+(1+x)^2\\)$$\n \n$$\\sin(\\alpha)^{\\theta}=\\sum_{i=0}^{n}(x^i + \\cos(f))$$\n\n\n# 绘制流程图 mermaid\n\n{% mermaid %}\ngraph TD;\n A --> B;\n A --> C;\n B --> D;\n C --> D;\n{% endmermaid %}\n\n \n \nEnd","slug":"Markdown Sample","published":1,"date":"2023-10-23T04:01:13.108Z","updated":"2023-11-08T02:08:01.242Z","comments":1,"layout":"post","photos":[],"link":"","_id":"clp7x194w0005v3z30pgk9e5n","content":"

Linear Markdown Sample

\"markdown\"

\n

Heading 1 link Heading link

字符效果和横线等


\n

删除线 删除线(开启识别HTML标签时)
斜体字 斜体字
粗体 粗体
粗斜体 粗斜体

\n

上标:X2,下标:O2

\n

缩写(同HTML的abbr标签)

\n
\n

即更长的单词或短语的缩写形式,前提是开启识别HTML标签时,已默认开启

\n
\n

The HTML specification is maintained by the W3C.

\n

引用 Blockquotes

\n

引用文本 Blockquotes

\n
\n

引用的行内混合 Blockquotes

\n
\n

引用:如果想要插入空白换行即<br />标签,在插入处先键入两个以上的空格然后回车即可,普通链接

\n
\n

锚点与链接 Links

普通链接

\n

普通链接带标题

\n

直接链接:https://github.com

\n

https://baidu.com

\n

锚点链接

\n

mailto:test.test@gmail.com

\n

GFM a-tail link @pandao 邮箱地址自动链接 test.test@gmail.com www@vip.qq.com

\n
\n

@pandao

\n
\n

多语言代码高亮 Codes

行内代码 Inline code

执行命令:npm install marked

\n

JS代码

function test() {\n\tconsole.log(\"Hello world!\");\n}\n \n(function(){\n    var box = function() {\n        return box.fn.init();\n    };\n\n    box.prototype = box.fn = {\n        init : function(){\n            console.log('box.init()');\n\n\t\t\treturn this;\n        },\n\n\t\tadd : function(str) {\n\t\t\talert(\"add\", str);\n\n\t\t\treturn this;\n\t\t},\n\n\t\tremove : function(str) {\n\t\t\talert(\"remove\", str);\n\n\t\t\treturn this;\n\t\t}\n    };\n    \n    box.fn.init.prototype = box.fn;\n    \n    window.box =box;\n})();\n\nvar testBox = box();\ntestBox.add(\"jQuery\").remove(\"jQuery\");
\n\n

HTML 代码 HTML codes

<!DOCTYPE html>\n<html>\n    <head>\n        <mate charest=\"utf-8\" />\n        <meta name=\"keywords\" content=\"Editor.md, Markdown, Editor\" />\n        <title>Hello world!</title>\n        <style type=\"text/css\">\n            body{font-size:14px;color:#444;font-family: \"Microsoft Yahei\", Tahoma, \"Hiragino Sans GB\", Arial;background:#fff;}\n            ul{list-style: none;}\n            img{border:none;vertical-align: middle;}\n        </style>\n    </head>\n    <body>\n        <h1 class=\"text-xxl\">Hello world!</h1>\n        <p class=\"text-green\">Plain text</p>\n    </body>\n</html>
\n\n

图片 Images

Image:

\n

\n
\n

Follow your heart.

\n
\n

\n
\n

图为:厦门白城沙滩

\n
\n

图片加链接 (Image + Link):

\n

\n
\n

图为:李健首张专辑《似水流年》封面

\n
\n
\n

列表 Lists

无序列表(减号)Unordered Lists (-)

    \n
  • 列表一
  • \n
  • 列表二
  • \n
  • 列表三
  • \n
\n

无序列表(星号)Unordered Lists (*)

    \n
  • 列表一
  • \n
  • 列表二
  • \n
  • 列表三
  • \n
\n

无序列表(加号和嵌套)Unordered Lists (+)

    \n
  • 列表一
  • \n
  • 列表二
      \n
    • 列表二-1
    • \n
    • 列表二-2
    • \n
    • 列表二-3
    • \n
    \n
  • \n
  • 列表三
      \n
    • 列表一
    • \n
    • 列表二
    • \n
    • 列表三
    • \n
    \n
  • \n
\n

有序列表 Ordered Lists (-)

    \n
  1. 第一行
  2. \n
  3. 第二行
  4. \n
  5. 第三行
  6. \n
\n

GFM task list

    \n
  • GFM task list 1
  • \n
  • GFM task list 2
  • \n
  • GFM task list 3
      \n
    • GFM task list 3-1
    • \n
    • GFM task list 3-2
    • \n
    • GFM task list 3-3
    • \n
    \n
  • \n
  • GFM task list 4
      \n
    • GFM task list 4-1
    • \n
    • GFM task list 4-2
    • \n
    \n
  • \n
\n
\n

绘制表格 Tables

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
项目价格数量
计算机$16005
手机$1212
管线$1234
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
First HeaderSecond Header
Content CellContent Cell
Content CellContent Cell
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
First HeaderSecond Header
Content CellContent Cell
Content CellContent Cell
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Function nameDescription
help()Display the help window.
destroy()Destroy your computer!
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Left-AlignedCenter AlignedRight Aligned
col 3 issome wordy text$1600
col 2 iscentered$12
zebra stripesare neat$1
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
ItemValue
Computer$1600
Phone$12
Pipe$1
\n

科学公式 TeX(KaTeX)

$$E=mc^2$$

\n

$$x > y$$

\n

$$(\\sqrt{3x-1}+(1+x)^2)$$

\n

$$\\sin(\\alpha)^{\\theta}=\\sum_{i=0}^{n}(x^i + \\cos(f))$$

\n

绘制流程图 mermaid

\n graph TD;\n A --> B;\n A --> C;\n B --> D;\n C --> D;\n
\n\n

End

\n","site":{"data":{}},"excerpt":"","more":"

Linear Markdown Sample

\"markdown\"

\n

Heading 1 link Heading link

字符效果和横线等


\n

删除线 删除线(开启识别HTML标签时)
斜体字 斜体字
粗体 粗体
粗斜体 粗斜体

\n

上标:X2,下标:O2

\n

缩写(同HTML的abbr标签)

\n
\n

即更长的单词或短语的缩写形式,前提是开启识别HTML标签时,已默认开启

\n
\n

The HTML specification is maintained by the W3C.

\n

引用 Blockquotes

\n

引用文本 Blockquotes

\n
\n

引用的行内混合 Blockquotes

\n
\n

引用:如果想要插入空白换行即<br />标签,在插入处先键入两个以上的空格然后回车即可,普通链接

\n
\n

锚点与链接 Links

普通链接

\n

普通链接带标题

\n

直接链接:https://github.com

\n

https://baidu.com

\n

锚点链接

\n

mailto:test.test@gmail.com

\n

GFM a-tail link @pandao 邮箱地址自动链接 test.test@gmail.com www@vip.qq.com

\n
\n

@pandao

\n
\n

多语言代码高亮 Codes

行内代码 Inline code

执行命令:npm install marked

\n

JS代码

function test() {\n\tconsole.log(\"Hello world!\");\n}\n \n(function(){\n    var box = function() {\n        return box.fn.init();\n    };\n\n    box.prototype = box.fn = {\n        init : function(){\n            console.log('box.init()');\n\n\t\t\treturn this;\n        },\n\n\t\tadd : function(str) {\n\t\t\talert(\"add\", str);\n\n\t\t\treturn this;\n\t\t},\n\n\t\tremove : function(str) {\n\t\t\talert(\"remove\", str);\n\n\t\t\treturn this;\n\t\t}\n    };\n    \n    box.fn.init.prototype = box.fn;\n    \n    window.box =box;\n})();\n\nvar testBox = box();\ntestBox.add(\"jQuery\").remove(\"jQuery\");
\n\n

HTML 代码 HTML codes

<!DOCTYPE html>\n<html>\n    <head>\n        <mate charest=\"utf-8\" />\n        <meta name=\"keywords\" content=\"Editor.md, Markdown, Editor\" />\n        <title>Hello world!</title>\n        <style type=\"text/css\">\n            body{font-size:14px;color:#444;font-family: \"Microsoft Yahei\", Tahoma, \"Hiragino Sans GB\", Arial;background:#fff;}\n            ul{list-style: none;}\n            img{border:none;vertical-align: middle;}\n        </style>\n    </head>\n    <body>\n        <h1 class=\"text-xxl\">Hello world!</h1>\n        <p class=\"text-green\">Plain text</p>\n    </body>\n</html>
\n\n

图片 Images

Image:

\n

\n
\n

Follow your heart.

\n
\n

\n
\n

图为:厦门白城沙滩

\n
\n

图片加链接 (Image + Link):

\n

\n
\n

图为:李健首张专辑《似水流年》封面

\n
\n
\n

列表 Lists

无序列表(减号)Unordered Lists (-)

    \n
  • 列表一
  • \n
  • 列表二
  • \n
  • 列表三
  • \n
\n

无序列表(星号)Unordered Lists (*)

    \n
  • 列表一
  • \n
  • 列表二
  • \n
  • 列表三
  • \n
\n

无序列表(加号和嵌套)Unordered Lists (+)

    \n
  • 列表一
  • \n
  • 列表二
      \n
    • 列表二-1
    • \n
    • 列表二-2
    • \n
    • 列表二-3
    • \n
    \n
  • \n
  • 列表三
      \n
    • 列表一
    • \n
    • 列表二
    • \n
    • 列表三
    • \n
    \n
  • \n
\n

有序列表 Ordered Lists (-)

    \n
  1. 第一行
  2. \n
  3. 第二行
  4. \n
  5. 第三行
  6. \n
\n

GFM task list

    \n
  • GFM task list 1
  • \n
  • GFM task list 2
  • \n
  • GFM task list 3
      \n
    • GFM task list 3-1
    • \n
    • GFM task list 3-2
    • \n
    • GFM task list 3-3
    • \n
    \n
  • \n
  • GFM task list 4
      \n
    • GFM task list 4-1
    • \n
    • GFM task list 4-2
    • \n
    \n
  • \n
\n
\n

绘制表格 Tables

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
项目价格数量
计算机$16005
手机$1212
管线$1234
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
First HeaderSecond Header
Content CellContent Cell
Content CellContent Cell
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
First HeaderSecond Header
Content CellContent Cell
Content CellContent Cell
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Function nameDescription
help()Display the help window.
destroy()Destroy your computer!
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
Left-AlignedCenter AlignedRight Aligned
col 3 issome wordy text$1600
col 2 iscentered$12
zebra stripesare neat$1
\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
ItemValue
Computer$1600
Phone$12
Pipe$1
\n

科学公式 TeX(KaTeX)

$$E=mc^2$$

\n

$$x > y$$

\n

$$(\\sqrt{3x-1}+(1+x)^2)$$

\n

$$\\sin(\\alpha)^{\\theta}=\\sum_{i=0}^{n}(x^i + \\cos(f))$$

\n

绘制流程图 mermaid

\n graph TD;\n A --> B;\n A --> C;\n B --> D;\n C --> D;\n
\n\n

End

\n"},{"title":"付鹏:展望2023年下半年全球经济格局,异常的利差意味着什么","status":"done","_content":"\n![美债10年利差](/img/mz10nq.png)\n\n08年的金融危机,或者09年,是一个重要的时间节点,到今年(2023年),在座各位你们将迎来第二个重要的节点,可能对于很多年轻人来讲,最好的一个时代结束了,我们所有人的下半辈子的投资,注意,也非常简单,抄巴菲特作业。\n\n巴菲特刚去了日本(2011年也去了),你们就知道日本发生了什么。你看到了安倍晋三的三支箭,你看到了日本日元的大幅度的贬值。过去两年,巴菲特一直在买日本的这五大商社:三菱商社、三井物产、伊藤忠商事、住友、丸红,然后大家还看到巴菲特在过去的两年一直在买美孚和西方石油。有人说了巴菲特在赌油价上涨,我只能说如果你讲这种话的话,你金融基本没入门。\n\n我想说的是,任何的技术创新在某些环节中它是由资金来决定的,技术人员只是其中一环。没有钱,没有便宜的钱一切都是假的。所以技术的进步的背后有个很关键的因素就是钱,钱多钱少,钱贵钱便宜,你的投资将完全不同。比如说昨天还是前天你们看到软银的孙正义基本上把所有的阿里巴巴的资金卖掉了,开始往回抽回资金。我经常说,包括对我自己的子女,都讲一句话:99%的命运和1%的努力,而不是99%的努力,1%的命运。大势极其的重要,这个\"大势\"或许也就是我们在金融市场打拼,真正能够获得的。\n\n然后图上右侧我给大家放了最核心的美债利差,美债利差是什么东西?可以这么简单理解\"全球资金的成本\"。美国是一年期国债收益率和十年期国债收益率的差值,主要是差值。如果懂商品期货的话你们知道,我们的所有东西都有一个概念叫做当期价格和远期价格,那么资金一年期可以简单理解成当期资金价格,十年期你可以理解成十年拉长十年以后的资金价格。这个\"一\"和\"十\"的概念是什么?十年期大概等同于经济增长。我们过去的四十年的大部分时间短期利率低于经济增长。比如说你现在干了一个企业,你预期你未来的三到五年,你能获得10%左右的利润,而当期银行提供给你的借贷成本只有三个点,你知道你该干嘛了吗?借便宜的钱投高息的回报。\n\n本质上金融的核心除了研究经济,研究长期回报以外,还有个很重要的就是,从哪借钱?能不能借到便宜的钱?能不能在便宜的钱上面借更多的钱?但不是说便宜的钱就一定能够刺激起来借贷,日本90年房地产泡沫破裂以后,利率水平给你干到0你都不借,为什么?因为没有资产回报!这个怎么反映在我们的全球利率上呢?简单讲这个数字在0以上,它代表着你现在的借贷资金成本在这,你的投资回报在下头。\n\n你记住一点,你活多大年龄决定了你见过多大市面,我们现在在中国从事金融的大部分的人,有见过40年前国际市场是什么情况吗?巴菲特90多了,芒格明年就100了,对吧!用我的话说你要活到那个年龄,你啥都见过了!金融理念特别简单,借便宜的钱去赚高息的回报,就这么简单。\n\n一定要喜欢危机,可能是干实体的人不喜欢,但我可以告诉你,干金融的人我们超级喜欢危机,因为每一次危机的到来将带来一次年轻人特别喜欢的机会,赌一赌,单车变摩托,赌的是什么呢?你就赌危机爆发之后,全球的政府和央行永远只会干一件事:放水降息、放水降更多的息、放更多的水降更多的息。\n\n40年前平均的状态,实际上就是短期利率永远高于长期经济回报,这在经济学中是什么?有人会说通胀、通缩,其实还有一个状态,大家并不喜欢:\"滞胀\",这种状态距离我们已经40年了,换句话说全球已经40年,没太出现过这种情况。但对不起,你们有没有想过,现在或许来了,它导致的我们的所有投资完全不同。\n\n资金越便宜,长期投资回报越稳定,借越便宜的钱,买更高的高息资产借更多。一旦倒过来,我就请问你怎么交易?银行存款五点几,投所有的资产回报只有三点几,我就问你大部分人会怎么做?不是说就指现金在银行的才叫现金,只要在当期能够产生高息回报的资金或者资产都叫现金。我举个例子,比如说一个公司股息分10%,它算不算高息资产?它算不算高息现金?它每年都给你分跟你银行存到银行每年拿5%有区别?没区别!所以说这个短期高于长期从最直觉的理解就是资金应该放在现金上。\n\n为什么巴菲特买那些公司?那些公司跟上面的现金奶牛ETF之间是什么关系?08年金融危机后,美国把基准利率水平拉得非常低,09年两会口号是:\"做大做强金融,万众创业,万众创新\",之后出来的小贷公司,担保公司P2P对吧,一连串都是在这个大的宏观背景下去搞的。09年之后你真正应该获得的只有两个收益:一个卖房炒房卖房炒房、买更多的房炒更多的房。第二个投腾讯投阿里投万众创业投万众创新,就这两条路。在中国你记住有些路不是自然形成的,它在那个关键的节点上全是由政府这支无形的手在引导两会这个口号一提出来,你一看中国居民部门属于\"高储蓄低杠杆\",你知道人生的机会来了。\n\n真正投资的精髓不是买资产,是如何去负债。如何在钱便宜的时间点上去负债、付更多的债、如何去付更低的资金成本,然后右手才去考虑资产,很多人光想资产而不去考虑负债。\n\n房价上涨就一条,这个地方有没有人进来。第二,这个地方进来的人能不能借更多的钱,就是居民部门杠杆率。首先得有居民,第二得有杠杆率,如果这俩都没了,这个资产基本就到头了。09年就是让他们加杠杆扛着进去的,现在的经济为什么差?我可以告诉你,还有人说刺激,咋刺激?09年能刺激,是因为所有的居民部门有储蓄,稳定,企业低负债,现在是没储蓄、不稳定。更重要的是睁眼全是债,简单讲年轻人杠杆加不动,这才是最大的风险。核心城市核心区域除此之外都别碰,因为只有这个核心俩字才能带来人。香港特首前段时间天天在广深在那晃荡的,不就是邀请你们去吗?但是你跟他说我没钱,家里也没钱,我就是个穷小子,那对不起,你别过来。你来我也不要,因为我要你的根本是:你有钱,你能接得住,你能加得住杠杆。我认为最核心的一个是:房价到达一定程度,人自然没法生,隔壁韩国如此,日本如此,中国亦如此。中国现在的大体情况是:未来的五到十年,基本处在加完杠杆后还债的年代。低增长、低通胀、低利率,三低。而过去的三十年,中国一直是高增长,高通胀、高利率。高和低之间差距是什么?杠杆率到一定程度自然就定了,隔壁日本就是到了极致,崩了!\n\n老美的底层老百姓过得并不好,但老美的富人更好了,富人靠杠杆。记住一点,富人靠杠杆、靠金融资产,穷人才靠劳动力。在全球化的过程,美国居民保持的是最低的生存权利(美国底层人民),他的收入是不增长的。从1982年开始,美国的数字是多少呢?平均薪资增速3%,金字塔的底层25%和顶层25%的薪资性收入差是1%。啥意思呢?平均3%的薪资收入增长再减去1%的社会阶层差,底层25%的美国居民薪资性收入仅仅是2%。大家发现这个2%是什么?正好就是美国通胀水平。啥意思呢?你可以理解,这美国的底层居民过去的二三十年薪资扣掉通胀是不增长的。\n\n二十多年前,索罗斯写那本联名书的时候,他就给大家解释过这个话题,他说中国庞大的贸易顺差去补偿了美国底层居民。举个例子,一个美国人,薪资2万保持不变,但是呢,中国生产的笔记本电脑从5000美金降到了1000美金,我的购买力怎么样?增强了!这就是庞大的中国出口。中国出口的商品绕一圈转回来,变成中国的外汇储备,中国的外汇储备买的是什么,买的是美债。美债用于什么了?美债用于两件事:打仗、给底层居民补医疗和教育。所以你就会发现。如果你做美国的底层居民的话,出现什么情况呢?收入没增长,但是中国供应的商品的购买力降弱了,这个购买力让美国人的购买力增强了。同时中国大量的顺差和外汇储备补偿了美国的教育和医疗。\n\n美国真正发家致富的人,都是看明白了这套游戏的。一旦底层居民薪资收入不增长,我们教科书里学的货币银行学中,央行双目标志将失效,也就是说中央银行将永远不用去考虑薪资通胀,只需要去考虑经济。此时就会产生我们现在这个模型\"经济不行就降息\"。每一次经济不行的时候,就借更多的美元,买更多的美国金融性资产。但在82年,美国金融监管放开了一个重要的法案,金融自由化。啥意思,如果监管部门把银行管得死死的,银行不许给居民加杠杆,就算来了这样的机会,社会也没有财富效应。但如果我此时告诉银行金融监管你们可以去创造各种各样的金融衍生产品,去给居民部门加杠杆,举例子房贷可以变成18种花样,于是82年到2022年,搞了整整40年。\n\n到了02年的时候,美国居民部门突然间反应过来,哦!原来富人不是靠着劳动挣钱的,富人得去炒金融资产。02年小布什一句话:你们得做起美国梦来。什么意思?你们手上没有股票,得有!但问题是在于,这帮穷人的美国梦是最可怕的,因为他收入不增长,还借了更多的房贷,02年到08年的这种做法,结果就是08年崩掉了。但崩掉以后,美国的底层人民麻烦就大了,被迫还了15年的债。从08年到现在,美国居民部门一直在去杠杆还债,但是你知道一个人收入不增长,然后还要不停的还债,他最终会转成什么?欧美有两样东西会让整个社会颠覆的,一个是\"选票\"、\"上街大游行\"。他的选票会转化成什么?答案:\"特朗普\"。也就是要么民粹,要么你补偿我。补偿只有一个方法,提高他的薪资收入。你以为真的是像宏观经济上在跟你讲说,因为这场疫情导致供应链导致劳动力市场结构发生了变换?那都不是核心,真正的核心是:当居民部门还了12年、15年债以后,这部分人不想还了,你要么就是更多的补偿给我、要么就是给我加薪。\n\n目前美国出现的就一个结果:底层通胀、顶层通缩。知道美国现在谁最惨吗?美国西海岸。硅谷、洛杉矶、旧金山的富人,这15年是他们加了杠杆,08年后的这漫长的15年创造了我们近乎200年以来,全世界最低的资金年代,它也创造了全世界几乎是最大的资产。这个资产是什么?虚拟资产!你们知道为什么会出现虚拟性货币?如果这么庞大的流动性,这么便宜的资金,冲向了实物资产(粮食、房子),这个价格一旦失控,我们离战争就不远了。虚拟资产有个好处,它仅仅消灭富人财富。西海岸的这帮我们叫美国新贵,准确说叫做穷人日子很好,富人日子很难受。富人中间是老钱收割新钱,老钱是什么呢?华盛顿、纽约、休斯顿,新钱就是硅谷、洛杉矶。新钱靠的是什么?科技、技术、创新。你创造一个东西带来的社会价值,可能只创造了1万块。但是金融市场给予你200倍。一次性兑付了几百年的收益。这就是我们的金融市场,当利率水平过低的时候,它会使得所有资产的估值极高。而科技作为主要的推动力,会自然而然地迎来一场高估值的泡沫。\n\n干一级市场投资,我可以告诉你估值泡沫出现的时候,那特征都是一样的,简单说:瞎几把随便写PPT都能融到钱。有很多人的利益是建立在:利率和资产价格上的。经济行的时候,是可以承受高利率的。经济不行的时候,(日本)把利率降到负都没用。\n\n这四十年结束以后,逻辑是什么?科技肯定不行,深圳科创你就搞到天上去,你拉动整个社会的经济也远着呢!技术的进步,只有转化成全民生产率,才能拉动所有人。中国现在相反,是低增长、低通胀、低利率,而这3低的背后是高杠杆。有一种情况,可以让我们避开这种所谓的内卷型的环境,就是走出去。美国在六十年代到现在,不是盈利率的增长,它获得的是市场份额的扩大。中国现在所有的企业会内卷的,会卷得一塌糊涂,原因很简单,市场只有国内了。2018年开始的,中美贸易战逆全球化,鬼佬就是要把我们踢出去。因为不走出去,我们过去的二三十年所有的产能是给全世界走配套的。如果不走出去,单凭我们国内的居民部门消化不了。\n\n我们国内居民部门中还出现了一个问题,大部分的90后、95后是在缩的,他们是在降低欲望的,他们是在消费降级的。高端消费永远是给那些富人的。中产阶级的杠杆达到一定程度,就会进入一个很典型的低欲望社会。跟现在比较穷的年轻人打打交道,你们就会发现,不结婚挺好、不生孩子挺好、这个不买房也不错、这个手机也不用换。然后这个蜜雪冰城的咖啡。虽然难喝点,但还可以你。跟富二代富三代打交道,就看不出来。中国现在也是典型的K字型。\n\n过去四十年的游戏结束了,现金远远地甩开了长期经济增长。巴菲特已经教过大家了,最好的公司是什么?经济好也好。经济坏也好,是刚需、高股息类的公司,且不受经济低迷影响的公司。你们有没有发现在最近在炒中草药,为啥炒中炒药?就是刚需,好比通信行业的话费,你必须得按月充值。所以巴菲特在买的是什么?现金奶牛ETF的配置,看看都是啥?能源、医药、金融。这些行业就是典型的现金类资产,巴菲特想获得一样东西,知道是什么吗?股息率!美孚的几个财务数据叠加会发现第一种情况,低油价、经济差,它的股价上涨的。并不是巴菲特赌油价上涨。\n\n全球过去40年挣100块钱,只有40块钱来自于你们理解中的经济增长带来的回报,有60块钱是来自于低利率加杠杆的投资回报。所以这个世界有两个投资人:一个叫长期价值投资回报,就是获得经济增长带来的贴现。另外一个就是获得利率波动带来的加杠杆,房子准确说典型的就是杠杆性资产,如果房住不炒,那理论上它是没有价格波动的。\n\n今年是蹦了一个ChatGPT,明年还必定还会有个啥的。当然我只想说一句,当你听到今年AI的时候,你们还记得去年大明湖畔的元宇宙吗?你们还记得十年前的互联网加万物吗?过去几年全球利率低,咱们可以你炒一波咱们给90后,90后炒一波给95后,95后炒一波给00后,然后全球流动性一收,咱们把00后挂到高位上。为什么是这个路径?越年轻的越信技术,他们认为技术能改变世界,结果世界会残酷地教育你。技术能不能改变世界?能!但绝对不是炒技术,炒技术改变不了世界。\n\n从去年十一月份,所有的主题性投资只能这样炒,当你想给90后的时候,你发现90后的元宇宙还没解套呢!90后借不来更多的钱,所以周期会非常短,迅速炒迅速落。可人民币现在利率这么低,为什么会没有钱呢?因为所有科技性投资,不是跟着人民币利率走的。比如ChatGPT,是海外现有了这个东西,国内跟风,元宇宙亦然,新能源汽车也是现有了特斯拉。中国在技术方面采取的战略是:摸着石头过河、黑猫白猫带着老鼠就行。所以我们会出现一种情况,海外有个热点我们马上跟。那问题就来了,你的估值怎么定价?做过一级市场投资的,投资某家企业的时候,对标物是美国某某企业。美股最强大的地方,是把所有创新的Top级的领军人物均以美元定价,导致的一个结果就是,它是锚!定价了所有估值水平。如果人民币能对资产估值有影响,那就必须要求投这家企业的对标物是在中国上市。\n\n能挣钱的企业就是好企业?像国内某企业家讲的,我五年不给你分红,你能把我咋滴?这种企业就不是好企业。挣钱分钱,经济不好的时候能力还继续挣钱,这就是它的垄断性。所以你们会发现,别瞧不起老钱,老钱投的上游。改革开放之后,煤炭、电力、很多行业都是允许民营企业介入的,到现在为止我就请问,这些上游还有民营企业吗?\n\n回到日本的商社,跟中国国有企业本质是一样,它是家庭组织,不由董事会左右。巴菲特去了以后,和高管和家族的人员就谈了一个事,你是现金奶牛,因为它控制了全球的上游。所以在经济不好的时候,日本的五大商社就是中国的国有企业,也是美国的能源企业。能挣钱、有利润,然后有大量的自由现金流。它唯一的问题是:\"公司治理\",就是它不一定愿意给你分。现在股息率快到五了,你不分,我就把公司买成我的,然后日本人说的就是:巴菲特先生,你别把我控制,我愿意分钱给你。因此巴菲特干的事情,买了日本的这些高股息的资产,并且在日本发行了无抵押担保的日元债券。啥意思呢?巴菲特一毛钱没掏,用日本人的储蓄以及低利率水平借钱,买了日本人的资产,还强迫日本人分了高股息,这才是资本真正的收割。\n\n未来的5年是10年,真正的游戏是如何借低息买高息。美元不便宜了,但美元的高息依旧很吸引人。那么问题就来了,谁是低息?美元如果一直维持高增长,高通胀、高利率的话,人民币一定是贬值的。而倒过来的交易,借人民币转高息资产,这个高息资产可以是人民币里边不多有的高息资产,也可以是全球范围内的高股息现金奶牛,你会发现,这就又形成了类似于15年前的套利资本流动、交易机会。","source":"_posts/finance/付鹏:展望2023年下半年全球经济格局,异常的利差意味着什么.md","raw":"---\ntitle: 付鹏:展望2023年下半年全球经济格局,异常的利差意味着什么\ncategories:\n - Finance\nstatus: done\n---\n\n![美债10年利差](/img/mz10nq.png)\n\n08年的金融危机,或者09年,是一个重要的时间节点,到今年(2023年),在座各位你们将迎来第二个重要的节点,可能对于很多年轻人来讲,最好的一个时代结束了,我们所有人的下半辈子的投资,注意,也非常简单,抄巴菲特作业。\n\n巴菲特刚去了日本(2011年也去了),你们就知道日本发生了什么。你看到了安倍晋三的三支箭,你看到了日本日元的大幅度的贬值。过去两年,巴菲特一直在买日本的这五大商社:三菱商社、三井物产、伊藤忠商事、住友、丸红,然后大家还看到巴菲特在过去的两年一直在买美孚和西方石油。有人说了巴菲特在赌油价上涨,我只能说如果你讲这种话的话,你金融基本没入门。\n\n我想说的是,任何的技术创新在某些环节中它是由资金来决定的,技术人员只是其中一环。没有钱,没有便宜的钱一切都是假的。所以技术的进步的背后有个很关键的因素就是钱,钱多钱少,钱贵钱便宜,你的投资将完全不同。比如说昨天还是前天你们看到软银的孙正义基本上把所有的阿里巴巴的资金卖掉了,开始往回抽回资金。我经常说,包括对我自己的子女,都讲一句话:99%的命运和1%的努力,而不是99%的努力,1%的命运。大势极其的重要,这个\"大势\"或许也就是我们在金融市场打拼,真正能够获得的。\n\n然后图上右侧我给大家放了最核心的美债利差,美债利差是什么东西?可以这么简单理解\"全球资金的成本\"。美国是一年期国债收益率和十年期国债收益率的差值,主要是差值。如果懂商品期货的话你们知道,我们的所有东西都有一个概念叫做当期价格和远期价格,那么资金一年期可以简单理解成当期资金价格,十年期你可以理解成十年拉长十年以后的资金价格。这个\"一\"和\"十\"的概念是什么?十年期大概等同于经济增长。我们过去的四十年的大部分时间短期利率低于经济增长。比如说你现在干了一个企业,你预期你未来的三到五年,你能获得10%左右的利润,而当期银行提供给你的借贷成本只有三个点,你知道你该干嘛了吗?借便宜的钱投高息的回报。\n\n本质上金融的核心除了研究经济,研究长期回报以外,还有个很重要的就是,从哪借钱?能不能借到便宜的钱?能不能在便宜的钱上面借更多的钱?但不是说便宜的钱就一定能够刺激起来借贷,日本90年房地产泡沫破裂以后,利率水平给你干到0你都不借,为什么?因为没有资产回报!这个怎么反映在我们的全球利率上呢?简单讲这个数字在0以上,它代表着你现在的借贷资金成本在这,你的投资回报在下头。\n\n你记住一点,你活多大年龄决定了你见过多大市面,我们现在在中国从事金融的大部分的人,有见过40年前国际市场是什么情况吗?巴菲特90多了,芒格明年就100了,对吧!用我的话说你要活到那个年龄,你啥都见过了!金融理念特别简单,借便宜的钱去赚高息的回报,就这么简单。\n\n一定要喜欢危机,可能是干实体的人不喜欢,但我可以告诉你,干金融的人我们超级喜欢危机,因为每一次危机的到来将带来一次年轻人特别喜欢的机会,赌一赌,单车变摩托,赌的是什么呢?你就赌危机爆发之后,全球的政府和央行永远只会干一件事:放水降息、放水降更多的息、放更多的水降更多的息。\n\n40年前平均的状态,实际上就是短期利率永远高于长期经济回报,这在经济学中是什么?有人会说通胀、通缩,其实还有一个状态,大家并不喜欢:\"滞胀\",这种状态距离我们已经40年了,换句话说全球已经40年,没太出现过这种情况。但对不起,你们有没有想过,现在或许来了,它导致的我们的所有投资完全不同。\n\n资金越便宜,长期投资回报越稳定,借越便宜的钱,买更高的高息资产借更多。一旦倒过来,我就请问你怎么交易?银行存款五点几,投所有的资产回报只有三点几,我就问你大部分人会怎么做?不是说就指现金在银行的才叫现金,只要在当期能够产生高息回报的资金或者资产都叫现金。我举个例子,比如说一个公司股息分10%,它算不算高息资产?它算不算高息现金?它每年都给你分跟你银行存到银行每年拿5%有区别?没区别!所以说这个短期高于长期从最直觉的理解就是资金应该放在现金上。\n\n为什么巴菲特买那些公司?那些公司跟上面的现金奶牛ETF之间是什么关系?08年金融危机后,美国把基准利率水平拉得非常低,09年两会口号是:\"做大做强金融,万众创业,万众创新\",之后出来的小贷公司,担保公司P2P对吧,一连串都是在这个大的宏观背景下去搞的。09年之后你真正应该获得的只有两个收益:一个卖房炒房卖房炒房、买更多的房炒更多的房。第二个投腾讯投阿里投万众创业投万众创新,就这两条路。在中国你记住有些路不是自然形成的,它在那个关键的节点上全是由政府这支无形的手在引导两会这个口号一提出来,你一看中国居民部门属于\"高储蓄低杠杆\",你知道人生的机会来了。\n\n真正投资的精髓不是买资产,是如何去负债。如何在钱便宜的时间点上去负债、付更多的债、如何去付更低的资金成本,然后右手才去考虑资产,很多人光想资产而不去考虑负债。\n\n房价上涨就一条,这个地方有没有人进来。第二,这个地方进来的人能不能借更多的钱,就是居民部门杠杆率。首先得有居民,第二得有杠杆率,如果这俩都没了,这个资产基本就到头了。09年就是让他们加杠杆扛着进去的,现在的经济为什么差?我可以告诉你,还有人说刺激,咋刺激?09年能刺激,是因为所有的居民部门有储蓄,稳定,企业低负债,现在是没储蓄、不稳定。更重要的是睁眼全是债,简单讲年轻人杠杆加不动,这才是最大的风险。核心城市核心区域除此之外都别碰,因为只有这个核心俩字才能带来人。香港特首前段时间天天在广深在那晃荡的,不就是邀请你们去吗?但是你跟他说我没钱,家里也没钱,我就是个穷小子,那对不起,你别过来。你来我也不要,因为我要你的根本是:你有钱,你能接得住,你能加得住杠杆。我认为最核心的一个是:房价到达一定程度,人自然没法生,隔壁韩国如此,日本如此,中国亦如此。中国现在的大体情况是:未来的五到十年,基本处在加完杠杆后还债的年代。低增长、低通胀、低利率,三低。而过去的三十年,中国一直是高增长,高通胀、高利率。高和低之间差距是什么?杠杆率到一定程度自然就定了,隔壁日本就是到了极致,崩了!\n\n老美的底层老百姓过得并不好,但老美的富人更好了,富人靠杠杆。记住一点,富人靠杠杆、靠金融资产,穷人才靠劳动力。在全球化的过程,美国居民保持的是最低的生存权利(美国底层人民),他的收入是不增长的。从1982年开始,美国的数字是多少呢?平均薪资增速3%,金字塔的底层25%和顶层25%的薪资性收入差是1%。啥意思呢?平均3%的薪资收入增长再减去1%的社会阶层差,底层25%的美国居民薪资性收入仅仅是2%。大家发现这个2%是什么?正好就是美国通胀水平。啥意思呢?你可以理解,这美国的底层居民过去的二三十年薪资扣掉通胀是不增长的。\n\n二十多年前,索罗斯写那本联名书的时候,他就给大家解释过这个话题,他说中国庞大的贸易顺差去补偿了美国底层居民。举个例子,一个美国人,薪资2万保持不变,但是呢,中国生产的笔记本电脑从5000美金降到了1000美金,我的购买力怎么样?增强了!这就是庞大的中国出口。中国出口的商品绕一圈转回来,变成中国的外汇储备,中国的外汇储备买的是什么,买的是美债。美债用于什么了?美债用于两件事:打仗、给底层居民补医疗和教育。所以你就会发现。如果你做美国的底层居民的话,出现什么情况呢?收入没增长,但是中国供应的商品的购买力降弱了,这个购买力让美国人的购买力增强了。同时中国大量的顺差和外汇储备补偿了美国的教育和医疗。\n\n美国真正发家致富的人,都是看明白了这套游戏的。一旦底层居民薪资收入不增长,我们教科书里学的货币银行学中,央行双目标志将失效,也就是说中央银行将永远不用去考虑薪资通胀,只需要去考虑经济。此时就会产生我们现在这个模型\"经济不行就降息\"。每一次经济不行的时候,就借更多的美元,买更多的美国金融性资产。但在82年,美国金融监管放开了一个重要的法案,金融自由化。啥意思,如果监管部门把银行管得死死的,银行不许给居民加杠杆,就算来了这样的机会,社会也没有财富效应。但如果我此时告诉银行金融监管你们可以去创造各种各样的金融衍生产品,去给居民部门加杠杆,举例子房贷可以变成18种花样,于是82年到2022年,搞了整整40年。\n\n到了02年的时候,美国居民部门突然间反应过来,哦!原来富人不是靠着劳动挣钱的,富人得去炒金融资产。02年小布什一句话:你们得做起美国梦来。什么意思?你们手上没有股票,得有!但问题是在于,这帮穷人的美国梦是最可怕的,因为他收入不增长,还借了更多的房贷,02年到08年的这种做法,结果就是08年崩掉了。但崩掉以后,美国的底层人民麻烦就大了,被迫还了15年的债。从08年到现在,美国居民部门一直在去杠杆还债,但是你知道一个人收入不增长,然后还要不停的还债,他最终会转成什么?欧美有两样东西会让整个社会颠覆的,一个是\"选票\"、\"上街大游行\"。他的选票会转化成什么?答案:\"特朗普\"。也就是要么民粹,要么你补偿我。补偿只有一个方法,提高他的薪资收入。你以为真的是像宏观经济上在跟你讲说,因为这场疫情导致供应链导致劳动力市场结构发生了变换?那都不是核心,真正的核心是:当居民部门还了12年、15年债以后,这部分人不想还了,你要么就是更多的补偿给我、要么就是给我加薪。\n\n目前美国出现的就一个结果:底层通胀、顶层通缩。知道美国现在谁最惨吗?美国西海岸。硅谷、洛杉矶、旧金山的富人,这15年是他们加了杠杆,08年后的这漫长的15年创造了我们近乎200年以来,全世界最低的资金年代,它也创造了全世界几乎是最大的资产。这个资产是什么?虚拟资产!你们知道为什么会出现虚拟性货币?如果这么庞大的流动性,这么便宜的资金,冲向了实物资产(粮食、房子),这个价格一旦失控,我们离战争就不远了。虚拟资产有个好处,它仅仅消灭富人财富。西海岸的这帮我们叫美国新贵,准确说叫做穷人日子很好,富人日子很难受。富人中间是老钱收割新钱,老钱是什么呢?华盛顿、纽约、休斯顿,新钱就是硅谷、洛杉矶。新钱靠的是什么?科技、技术、创新。你创造一个东西带来的社会价值,可能只创造了1万块。但是金融市场给予你200倍。一次性兑付了几百年的收益。这就是我们的金融市场,当利率水平过低的时候,它会使得所有资产的估值极高。而科技作为主要的推动力,会自然而然地迎来一场高估值的泡沫。\n\n干一级市场投资,我可以告诉你估值泡沫出现的时候,那特征都是一样的,简单说:瞎几把随便写PPT都能融到钱。有很多人的利益是建立在:利率和资产价格上的。经济行的时候,是可以承受高利率的。经济不行的时候,(日本)把利率降到负都没用。\n\n这四十年结束以后,逻辑是什么?科技肯定不行,深圳科创你就搞到天上去,你拉动整个社会的经济也远着呢!技术的进步,只有转化成全民生产率,才能拉动所有人。中国现在相反,是低增长、低通胀、低利率,而这3低的背后是高杠杆。有一种情况,可以让我们避开这种所谓的内卷型的环境,就是走出去。美国在六十年代到现在,不是盈利率的增长,它获得的是市场份额的扩大。中国现在所有的企业会内卷的,会卷得一塌糊涂,原因很简单,市场只有国内了。2018年开始的,中美贸易战逆全球化,鬼佬就是要把我们踢出去。因为不走出去,我们过去的二三十年所有的产能是给全世界走配套的。如果不走出去,单凭我们国内的居民部门消化不了。\n\n我们国内居民部门中还出现了一个问题,大部分的90后、95后是在缩的,他们是在降低欲望的,他们是在消费降级的。高端消费永远是给那些富人的。中产阶级的杠杆达到一定程度,就会进入一个很典型的低欲望社会。跟现在比较穷的年轻人打打交道,你们就会发现,不结婚挺好、不生孩子挺好、这个不买房也不错、这个手机也不用换。然后这个蜜雪冰城的咖啡。虽然难喝点,但还可以你。跟富二代富三代打交道,就看不出来。中国现在也是典型的K字型。\n\n过去四十年的游戏结束了,现金远远地甩开了长期经济增长。巴菲特已经教过大家了,最好的公司是什么?经济好也好。经济坏也好,是刚需、高股息类的公司,且不受经济低迷影响的公司。你们有没有发现在最近在炒中草药,为啥炒中炒药?就是刚需,好比通信行业的话费,你必须得按月充值。所以巴菲特在买的是什么?现金奶牛ETF的配置,看看都是啥?能源、医药、金融。这些行业就是典型的现金类资产,巴菲特想获得一样东西,知道是什么吗?股息率!美孚的几个财务数据叠加会发现第一种情况,低油价、经济差,它的股价上涨的。并不是巴菲特赌油价上涨。\n\n全球过去40年挣100块钱,只有40块钱来自于你们理解中的经济增长带来的回报,有60块钱是来自于低利率加杠杆的投资回报。所以这个世界有两个投资人:一个叫长期价值投资回报,就是获得经济增长带来的贴现。另外一个就是获得利率波动带来的加杠杆,房子准确说典型的就是杠杆性资产,如果房住不炒,那理论上它是没有价格波动的。\n\n今年是蹦了一个ChatGPT,明年还必定还会有个啥的。当然我只想说一句,当你听到今年AI的时候,你们还记得去年大明湖畔的元宇宙吗?你们还记得十年前的互联网加万物吗?过去几年全球利率低,咱们可以你炒一波咱们给90后,90后炒一波给95后,95后炒一波给00后,然后全球流动性一收,咱们把00后挂到高位上。为什么是这个路径?越年轻的越信技术,他们认为技术能改变世界,结果世界会残酷地教育你。技术能不能改变世界?能!但绝对不是炒技术,炒技术改变不了世界。\n\n从去年十一月份,所有的主题性投资只能这样炒,当你想给90后的时候,你发现90后的元宇宙还没解套呢!90后借不来更多的钱,所以周期会非常短,迅速炒迅速落。可人民币现在利率这么低,为什么会没有钱呢?因为所有科技性投资,不是跟着人民币利率走的。比如ChatGPT,是海外现有了这个东西,国内跟风,元宇宙亦然,新能源汽车也是现有了特斯拉。中国在技术方面采取的战略是:摸着石头过河、黑猫白猫带着老鼠就行。所以我们会出现一种情况,海外有个热点我们马上跟。那问题就来了,你的估值怎么定价?做过一级市场投资的,投资某家企业的时候,对标物是美国某某企业。美股最强大的地方,是把所有创新的Top级的领军人物均以美元定价,导致的一个结果就是,它是锚!定价了所有估值水平。如果人民币能对资产估值有影响,那就必须要求投这家企业的对标物是在中国上市。\n\n能挣钱的企业就是好企业?像国内某企业家讲的,我五年不给你分红,你能把我咋滴?这种企业就不是好企业。挣钱分钱,经济不好的时候能力还继续挣钱,这就是它的垄断性。所以你们会发现,别瞧不起老钱,老钱投的上游。改革开放之后,煤炭、电力、很多行业都是允许民营企业介入的,到现在为止我就请问,这些上游还有民营企业吗?\n\n回到日本的商社,跟中国国有企业本质是一样,它是家庭组织,不由董事会左右。巴菲特去了以后,和高管和家族的人员就谈了一个事,你是现金奶牛,因为它控制了全球的上游。所以在经济不好的时候,日本的五大商社就是中国的国有企业,也是美国的能源企业。能挣钱、有利润,然后有大量的自由现金流。它唯一的问题是:\"公司治理\",就是它不一定愿意给你分。现在股息率快到五了,你不分,我就把公司买成我的,然后日本人说的就是:巴菲特先生,你别把我控制,我愿意分钱给你。因此巴菲特干的事情,买了日本的这些高股息的资产,并且在日本发行了无抵押担保的日元债券。啥意思呢?巴菲特一毛钱没掏,用日本人的储蓄以及低利率水平借钱,买了日本人的资产,还强迫日本人分了高股息,这才是资本真正的收割。\n\n未来的5年是10年,真正的游戏是如何借低息买高息。美元不便宜了,但美元的高息依旧很吸引人。那么问题就来了,谁是低息?美元如果一直维持高增长,高通胀、高利率的话,人民币一定是贬值的。而倒过来的交易,借人民币转高息资产,这个高息资产可以是人民币里边不多有的高息资产,也可以是全球范围内的高股息现金奶牛,你会发现,这就又形成了类似于15年前的套利资本流动、交易机会。","slug":"finance/付鹏:展望2023年下半年全球经济格局,异常的利差意味着什么","published":1,"date":"2023-11-06T05:14:30.824Z","updated":"2023-11-06T05:22:07.164Z","comments":1,"layout":"post","photos":[],"link":"","_id":"clp7x194w0006v3z3akxthpwo","content":"

\"美债10年利差\"

\n

08年的金融危机,或者09年,是一个重要的时间节点,到今年(2023年),在座各位你们将迎来第二个重要的节点,可能对于很多年轻人来讲,最好的一个时代结束了,我们所有人的下半辈子的投资,注意,也非常简单,抄巴菲特作业。

\n

巴菲特刚去了日本(2011年也去了),你们就知道日本发生了什么。你看到了安倍晋三的三支箭,你看到了日本日元的大幅度的贬值。过去两年,巴菲特一直在买日本的这五大商社:三菱商社、三井物产、伊藤忠商事、住友、丸红,然后大家还看到巴菲特在过去的两年一直在买美孚和西方石油。有人说了巴菲特在赌油价上涨,我只能说如果你讲这种话的话,你金融基本没入门。

\n

我想说的是,任何的技术创新在某些环节中它是由资金来决定的,技术人员只是其中一环。没有钱,没有便宜的钱一切都是假的。所以技术的进步的背后有个很关键的因素就是钱,钱多钱少,钱贵钱便宜,你的投资将完全不同。比如说昨天还是前天你们看到软银的孙正义基本上把所有的阿里巴巴的资金卖掉了,开始往回抽回资金。我经常说,包括对我自己的子女,都讲一句话:99%的命运和1%的努力,而不是99%的努力,1%的命运。大势极其的重要,这个”大势”或许也就是我们在金融市场打拼,真正能够获得的。

\n

然后图上右侧我给大家放了最核心的美债利差,美债利差是什么东西?可以这么简单理解”全球资金的成本”。美国是一年期国债收益率和十年期国债收益率的差值,主要是差值。如果懂商品期货的话你们知道,我们的所有东西都有一个概念叫做当期价格和远期价格,那么资金一年期可以简单理解成当期资金价格,十年期你可以理解成十年拉长十年以后的资金价格。这个”一”和”十”的概念是什么?十年期大概等同于经济增长。我们过去的四十年的大部分时间短期利率低于经济增长。比如说你现在干了一个企业,你预期你未来的三到五年,你能获得10%左右的利润,而当期银行提供给你的借贷成本只有三个点,你知道你该干嘛了吗?借便宜的钱投高息的回报。

\n

本质上金融的核心除了研究经济,研究长期回报以外,还有个很重要的就是,从哪借钱?能不能借到便宜的钱?能不能在便宜的钱上面借更多的钱?但不是说便宜的钱就一定能够刺激起来借贷,日本90年房地产泡沫破裂以后,利率水平给你干到0你都不借,为什么?因为没有资产回报!这个怎么反映在我们的全球利率上呢?简单讲这个数字在0以上,它代表着你现在的借贷资金成本在这,你的投资回报在下头。

\n

你记住一点,你活多大年龄决定了你见过多大市面,我们现在在中国从事金融的大部分的人,有见过40年前国际市场是什么情况吗?巴菲特90多了,芒格明年就100了,对吧!用我的话说你要活到那个年龄,你啥都见过了!金融理念特别简单,借便宜的钱去赚高息的回报,就这么简单。

\n

一定要喜欢危机,可能是干实体的人不喜欢,但我可以告诉你,干金融的人我们超级喜欢危机,因为每一次危机的到来将带来一次年轻人特别喜欢的机会,赌一赌,单车变摩托,赌的是什么呢?你就赌危机爆发之后,全球的政府和央行永远只会干一件事:放水降息、放水降更多的息、放更多的水降更多的息。

\n

40年前平均的状态,实际上就是短期利率永远高于长期经济回报,这在经济学中是什么?有人会说通胀、通缩,其实还有一个状态,大家并不喜欢:”滞胀”,这种状态距离我们已经40年了,换句话说全球已经40年,没太出现过这种情况。但对不起,你们有没有想过,现在或许来了,它导致的我们的所有投资完全不同。

\n

资金越便宜,长期投资回报越稳定,借越便宜的钱,买更高的高息资产借更多。一旦倒过来,我就请问你怎么交易?银行存款五点几,投所有的资产回报只有三点几,我就问你大部分人会怎么做?不是说就指现金在银行的才叫现金,只要在当期能够产生高息回报的资金或者资产都叫现金。我举个例子,比如说一个公司股息分10%,它算不算高息资产?它算不算高息现金?它每年都给你分跟你银行存到银行每年拿5%有区别?没区别!所以说这个短期高于长期从最直觉的理解就是资金应该放在现金上。

\n

为什么巴菲特买那些公司?那些公司跟上面的现金奶牛ETF之间是什么关系?08年金融危机后,美国把基准利率水平拉得非常低,09年两会口号是:”做大做强金融,万众创业,万众创新”,之后出来的小贷公司,担保公司P2P对吧,一连串都是在这个大的宏观背景下去搞的。09年之后你真正应该获得的只有两个收益:一个卖房炒房卖房炒房、买更多的房炒更多的房。第二个投腾讯投阿里投万众创业投万众创新,就这两条路。在中国你记住有些路不是自然形成的,它在那个关键的节点上全是由政府这支无形的手在引导两会这个口号一提出来,你一看中国居民部门属于”高储蓄低杠杆”,你知道人生的机会来了。

\n

真正投资的精髓不是买资产,是如何去负债。如何在钱便宜的时间点上去负债、付更多的债、如何去付更低的资金成本,然后右手才去考虑资产,很多人光想资产而不去考虑负债。

\n

房价上涨就一条,这个地方有没有人进来。第二,这个地方进来的人能不能借更多的钱,就是居民部门杠杆率。首先得有居民,第二得有杠杆率,如果这俩都没了,这个资产基本就到头了。09年就是让他们加杠杆扛着进去的,现在的经济为什么差?我可以告诉你,还有人说刺激,咋刺激?09年能刺激,是因为所有的居民部门有储蓄,稳定,企业低负债,现在是没储蓄、不稳定。更重要的是睁眼全是债,简单讲年轻人杠杆加不动,这才是最大的风险。核心城市核心区域除此之外都别碰,因为只有这个核心俩字才能带来人。香港特首前段时间天天在广深在那晃荡的,不就是邀请你们去吗?但是你跟他说我没钱,家里也没钱,我就是个穷小子,那对不起,你别过来。你来我也不要,因为我要你的根本是:你有钱,你能接得住,你能加得住杠杆。我认为最核心的一个是:房价到达一定程度,人自然没法生,隔壁韩国如此,日本如此,中国亦如此。中国现在的大体情况是:未来的五到十年,基本处在加完杠杆后还债的年代。低增长、低通胀、低利率,三低。而过去的三十年,中国一直是高增长,高通胀、高利率。高和低之间差距是什么?杠杆率到一定程度自然就定了,隔壁日本就是到了极致,崩了!

\n

老美的底层老百姓过得并不好,但老美的富人更好了,富人靠杠杆。记住一点,富人靠杠杆、靠金融资产,穷人才靠劳动力。在全球化的过程,美国居民保持的是最低的生存权利(美国底层人民),他的收入是不增长的。从1982年开始,美国的数字是多少呢?平均薪资增速3%,金字塔的底层25%和顶层25%的薪资性收入差是1%。啥意思呢?平均3%的薪资收入增长再减去1%的社会阶层差,底层25%的美国居民薪资性收入仅仅是2%。大家发现这个2%是什么?正好就是美国通胀水平。啥意思呢?你可以理解,这美国的底层居民过去的二三十年薪资扣掉通胀是不增长的。

\n

二十多年前,索罗斯写那本联名书的时候,他就给大家解释过这个话题,他说中国庞大的贸易顺差去补偿了美国底层居民。举个例子,一个美国人,薪资2万保持不变,但是呢,中国生产的笔记本电脑从5000美金降到了1000美金,我的购买力怎么样?增强了!这就是庞大的中国出口。中国出口的商品绕一圈转回来,变成中国的外汇储备,中国的外汇储备买的是什么,买的是美债。美债用于什么了?美债用于两件事:打仗、给底层居民补医疗和教育。所以你就会发现。如果你做美国的底层居民的话,出现什么情况呢?收入没增长,但是中国供应的商品的购买力降弱了,这个购买力让美国人的购买力增强了。同时中国大量的顺差和外汇储备补偿了美国的教育和医疗。

\n

美国真正发家致富的人,都是看明白了这套游戏的。一旦底层居民薪资收入不增长,我们教科书里学的货币银行学中,央行双目标志将失效,也就是说中央银行将永远不用去考虑薪资通胀,只需要去考虑经济。此时就会产生我们现在这个模型”经济不行就降息”。每一次经济不行的时候,就借更多的美元,买更多的美国金融性资产。但在82年,美国金融监管放开了一个重要的法案,金融自由化。啥意思,如果监管部门把银行管得死死的,银行不许给居民加杠杆,就算来了这样的机会,社会也没有财富效应。但如果我此时告诉银行金融监管你们可以去创造各种各样的金融衍生产品,去给居民部门加杠杆,举例子房贷可以变成18种花样,于是82年到2022年,搞了整整40年。

\n

到了02年的时候,美国居民部门突然间反应过来,哦!原来富人不是靠着劳动挣钱的,富人得去炒金融资产。02年小布什一句话:你们得做起美国梦来。什么意思?你们手上没有股票,得有!但问题是在于,这帮穷人的美国梦是最可怕的,因为他收入不增长,还借了更多的房贷,02年到08年的这种做法,结果就是08年崩掉了。但崩掉以后,美国的底层人民麻烦就大了,被迫还了15年的债。从08年到现在,美国居民部门一直在去杠杆还债,但是你知道一个人收入不增长,然后还要不停的还债,他最终会转成什么?欧美有两样东西会让整个社会颠覆的,一个是”选票”、”上街大游行”。他的选票会转化成什么?答案:”特朗普”。也就是要么民粹,要么你补偿我。补偿只有一个方法,提高他的薪资收入。你以为真的是像宏观经济上在跟你讲说,因为这场疫情导致供应链导致劳动力市场结构发生了变换?那都不是核心,真正的核心是:当居民部门还了12年、15年债以后,这部分人不想还了,你要么就是更多的补偿给我、要么就是给我加薪。

\n

目前美国出现的就一个结果:底层通胀、顶层通缩。知道美国现在谁最惨吗?美国西海岸。硅谷、洛杉矶、旧金山的富人,这15年是他们加了杠杆,08年后的这漫长的15年创造了我们近乎200年以来,全世界最低的资金年代,它也创造了全世界几乎是最大的资产。这个资产是什么?虚拟资产!你们知道为什么会出现虚拟性货币?如果这么庞大的流动性,这么便宜的资金,冲向了实物资产(粮食、房子),这个价格一旦失控,我们离战争就不远了。虚拟资产有个好处,它仅仅消灭富人财富。西海岸的这帮我们叫美国新贵,准确说叫做穷人日子很好,富人日子很难受。富人中间是老钱收割新钱,老钱是什么呢?华盛顿、纽约、休斯顿,新钱就是硅谷、洛杉矶。新钱靠的是什么?科技、技术、创新。你创造一个东西带来的社会价值,可能只创造了1万块。但是金融市场给予你200倍。一次性兑付了几百年的收益。这就是我们的金融市场,当利率水平过低的时候,它会使得所有资产的估值极高。而科技作为主要的推动力,会自然而然地迎来一场高估值的泡沫。

\n

干一级市场投资,我可以告诉你估值泡沫出现的时候,那特征都是一样的,简单说:瞎几把随便写PPT都能融到钱。有很多人的利益是建立在:利率和资产价格上的。经济行的时候,是可以承受高利率的。经济不行的时候,(日本)把利率降到负都没用。

\n

这四十年结束以后,逻辑是什么?科技肯定不行,深圳科创你就搞到天上去,你拉动整个社会的经济也远着呢!技术的进步,只有转化成全民生产率,才能拉动所有人。中国现在相反,是低增长、低通胀、低利率,而这3低的背后是高杠杆。有一种情况,可以让我们避开这种所谓的内卷型的环境,就是走出去。美国在六十年代到现在,不是盈利率的增长,它获得的是市场份额的扩大。中国现在所有的企业会内卷的,会卷得一塌糊涂,原因很简单,市场只有国内了。2018年开始的,中美贸易战逆全球化,鬼佬就是要把我们踢出去。因为不走出去,我们过去的二三十年所有的产能是给全世界走配套的。如果不走出去,单凭我们国内的居民部门消化不了。

\n

我们国内居民部门中还出现了一个问题,大部分的90后、95后是在缩的,他们是在降低欲望的,他们是在消费降级的。高端消费永远是给那些富人的。中产阶级的杠杆达到一定程度,就会进入一个很典型的低欲望社会。跟现在比较穷的年轻人打打交道,你们就会发现,不结婚挺好、不生孩子挺好、这个不买房也不错、这个手机也不用换。然后这个蜜雪冰城的咖啡。虽然难喝点,但还可以你。跟富二代富三代打交道,就看不出来。中国现在也是典型的K字型。

\n

过去四十年的游戏结束了,现金远远地甩开了长期经济增长。巴菲特已经教过大家了,最好的公司是什么?经济好也好。经济坏也好,是刚需、高股息类的公司,且不受经济低迷影响的公司。你们有没有发现在最近在炒中草药,为啥炒中炒药?就是刚需,好比通信行业的话费,你必须得按月充值。所以巴菲特在买的是什么?现金奶牛ETF的配置,看看都是啥?能源、医药、金融。这些行业就是典型的现金类资产,巴菲特想获得一样东西,知道是什么吗?股息率!美孚的几个财务数据叠加会发现第一种情况,低油价、经济差,它的股价上涨的。并不是巴菲特赌油价上涨。

\n

全球过去40年挣100块钱,只有40块钱来自于你们理解中的经济增长带来的回报,有60块钱是来自于低利率加杠杆的投资回报。所以这个世界有两个投资人:一个叫长期价值投资回报,就是获得经济增长带来的贴现。另外一个就是获得利率波动带来的加杠杆,房子准确说典型的就是杠杆性资产,如果房住不炒,那理论上它是没有价格波动的。

\n

今年是蹦了一个ChatGPT,明年还必定还会有个啥的。当然我只想说一句,当你听到今年AI的时候,你们还记得去年大明湖畔的元宇宙吗?你们还记得十年前的互联网加万物吗?过去几年全球利率低,咱们可以你炒一波咱们给90后,90后炒一波给95后,95后炒一波给00后,然后全球流动性一收,咱们把00后挂到高位上。为什么是这个路径?越年轻的越信技术,他们认为技术能改变世界,结果世界会残酷地教育你。技术能不能改变世界?能!但绝对不是炒技术,炒技术改变不了世界。

\n

从去年十一月份,所有的主题性投资只能这样炒,当你想给90后的时候,你发现90后的元宇宙还没解套呢!90后借不来更多的钱,所以周期会非常短,迅速炒迅速落。可人民币现在利率这么低,为什么会没有钱呢?因为所有科技性投资,不是跟着人民币利率走的。比如ChatGPT,是海外现有了这个东西,国内跟风,元宇宙亦然,新能源汽车也是现有了特斯拉。中国在技术方面采取的战略是:摸着石头过河、黑猫白猫带着老鼠就行。所以我们会出现一种情况,海外有个热点我们马上跟。那问题就来了,你的估值怎么定价?做过一级市场投资的,投资某家企业的时候,对标物是美国某某企业。美股最强大的地方,是把所有创新的Top级的领军人物均以美元定价,导致的一个结果就是,它是锚!定价了所有估值水平。如果人民币能对资产估值有影响,那就必须要求投这家企业的对标物是在中国上市。

\n

能挣钱的企业就是好企业?像国内某企业家讲的,我五年不给你分红,你能把我咋滴?这种企业就不是好企业。挣钱分钱,经济不好的时候能力还继续挣钱,这就是它的垄断性。所以你们会发现,别瞧不起老钱,老钱投的上游。改革开放之后,煤炭、电力、很多行业都是允许民营企业介入的,到现在为止我就请问,这些上游还有民营企业吗?

\n

回到日本的商社,跟中国国有企业本质是一样,它是家庭组织,不由董事会左右。巴菲特去了以后,和高管和家族的人员就谈了一个事,你是现金奶牛,因为它控制了全球的上游。所以在经济不好的时候,日本的五大商社就是中国的国有企业,也是美国的能源企业。能挣钱、有利润,然后有大量的自由现金流。它唯一的问题是:”公司治理”,就是它不一定愿意给你分。现在股息率快到五了,你不分,我就把公司买成我的,然后日本人说的就是:巴菲特先生,你别把我控制,我愿意分钱给你。因此巴菲特干的事情,买了日本的这些高股息的资产,并且在日本发行了无抵押担保的日元债券。啥意思呢?巴菲特一毛钱没掏,用日本人的储蓄以及低利率水平借钱,买了日本人的资产,还强迫日本人分了高股息,这才是资本真正的收割。

\n

未来的5年是10年,真正的游戏是如何借低息买高息。美元不便宜了,但美元的高息依旧很吸引人。那么问题就来了,谁是低息?美元如果一直维持高增长,高通胀、高利率的话,人民币一定是贬值的。而倒过来的交易,借人民币转高息资产,这个高息资产可以是人民币里边不多有的高息资产,也可以是全球范围内的高股息现金奶牛,你会发现,这就又形成了类似于15年前的套利资本流动、交易机会。

\n","site":{"data":{}},"excerpt":"","more":"

\"美债10年利差\"

\n

08年的金融危机,或者09年,是一个重要的时间节点,到今年(2023年),在座各位你们将迎来第二个重要的节点,可能对于很多年轻人来讲,最好的一个时代结束了,我们所有人的下半辈子的投资,注意,也非常简单,抄巴菲特作业。

\n

巴菲特刚去了日本(2011年也去了),你们就知道日本发生了什么。你看到了安倍晋三的三支箭,你看到了日本日元的大幅度的贬值。过去两年,巴菲特一直在买日本的这五大商社:三菱商社、三井物产、伊藤忠商事、住友、丸红,然后大家还看到巴菲特在过去的两年一直在买美孚和西方石油。有人说了巴菲特在赌油价上涨,我只能说如果你讲这种话的话,你金融基本没入门。

\n

我想说的是,任何的技术创新在某些环节中它是由资金来决定的,技术人员只是其中一环。没有钱,没有便宜的钱一切都是假的。所以技术的进步的背后有个很关键的因素就是钱,钱多钱少,钱贵钱便宜,你的投资将完全不同。比如说昨天还是前天你们看到软银的孙正义基本上把所有的阿里巴巴的资金卖掉了,开始往回抽回资金。我经常说,包括对我自己的子女,都讲一句话:99%的命运和1%的努力,而不是99%的努力,1%的命运。大势极其的重要,这个”大势”或许也就是我们在金融市场打拼,真正能够获得的。

\n

然后图上右侧我给大家放了最核心的美债利差,美债利差是什么东西?可以这么简单理解”全球资金的成本”。美国是一年期国债收益率和十年期国债收益率的差值,主要是差值。如果懂商品期货的话你们知道,我们的所有东西都有一个概念叫做当期价格和远期价格,那么资金一年期可以简单理解成当期资金价格,十年期你可以理解成十年拉长十年以后的资金价格。这个”一”和”十”的概念是什么?十年期大概等同于经济增长。我们过去的四十年的大部分时间短期利率低于经济增长。比如说你现在干了一个企业,你预期你未来的三到五年,你能获得10%左右的利润,而当期银行提供给你的借贷成本只有三个点,你知道你该干嘛了吗?借便宜的钱投高息的回报。

\n

本质上金融的核心除了研究经济,研究长期回报以外,还有个很重要的就是,从哪借钱?能不能借到便宜的钱?能不能在便宜的钱上面借更多的钱?但不是说便宜的钱就一定能够刺激起来借贷,日本90年房地产泡沫破裂以后,利率水平给你干到0你都不借,为什么?因为没有资产回报!这个怎么反映在我们的全球利率上呢?简单讲这个数字在0以上,它代表着你现在的借贷资金成本在这,你的投资回报在下头。

\n

你记住一点,你活多大年龄决定了你见过多大市面,我们现在在中国从事金融的大部分的人,有见过40年前国际市场是什么情况吗?巴菲特90多了,芒格明年就100了,对吧!用我的话说你要活到那个年龄,你啥都见过了!金融理念特别简单,借便宜的钱去赚高息的回报,就这么简单。

\n

一定要喜欢危机,可能是干实体的人不喜欢,但我可以告诉你,干金融的人我们超级喜欢危机,因为每一次危机的到来将带来一次年轻人特别喜欢的机会,赌一赌,单车变摩托,赌的是什么呢?你就赌危机爆发之后,全球的政府和央行永远只会干一件事:放水降息、放水降更多的息、放更多的水降更多的息。

\n

40年前平均的状态,实际上就是短期利率永远高于长期经济回报,这在经济学中是什么?有人会说通胀、通缩,其实还有一个状态,大家并不喜欢:”滞胀”,这种状态距离我们已经40年了,换句话说全球已经40年,没太出现过这种情况。但对不起,你们有没有想过,现在或许来了,它导致的我们的所有投资完全不同。

\n

资金越便宜,长期投资回报越稳定,借越便宜的钱,买更高的高息资产借更多。一旦倒过来,我就请问你怎么交易?银行存款五点几,投所有的资产回报只有三点几,我就问你大部分人会怎么做?不是说就指现金在银行的才叫现金,只要在当期能够产生高息回报的资金或者资产都叫现金。我举个例子,比如说一个公司股息分10%,它算不算高息资产?它算不算高息现金?它每年都给你分跟你银行存到银行每年拿5%有区别?没区别!所以说这个短期高于长期从最直觉的理解就是资金应该放在现金上。

\n

为什么巴菲特买那些公司?那些公司跟上面的现金奶牛ETF之间是什么关系?08年金融危机后,美国把基准利率水平拉得非常低,09年两会口号是:”做大做强金融,万众创业,万众创新”,之后出来的小贷公司,担保公司P2P对吧,一连串都是在这个大的宏观背景下去搞的。09年之后你真正应该获得的只有两个收益:一个卖房炒房卖房炒房、买更多的房炒更多的房。第二个投腾讯投阿里投万众创业投万众创新,就这两条路。在中国你记住有些路不是自然形成的,它在那个关键的节点上全是由政府这支无形的手在引导两会这个口号一提出来,你一看中国居民部门属于”高储蓄低杠杆”,你知道人生的机会来了。

\n

真正投资的精髓不是买资产,是如何去负债。如何在钱便宜的时间点上去负债、付更多的债、如何去付更低的资金成本,然后右手才去考虑资产,很多人光想资产而不去考虑负债。

\n

房价上涨就一条,这个地方有没有人进来。第二,这个地方进来的人能不能借更多的钱,就是居民部门杠杆率。首先得有居民,第二得有杠杆率,如果这俩都没了,这个资产基本就到头了。09年就是让他们加杠杆扛着进去的,现在的经济为什么差?我可以告诉你,还有人说刺激,咋刺激?09年能刺激,是因为所有的居民部门有储蓄,稳定,企业低负债,现在是没储蓄、不稳定。更重要的是睁眼全是债,简单讲年轻人杠杆加不动,这才是最大的风险。核心城市核心区域除此之外都别碰,因为只有这个核心俩字才能带来人。香港特首前段时间天天在广深在那晃荡的,不就是邀请你们去吗?但是你跟他说我没钱,家里也没钱,我就是个穷小子,那对不起,你别过来。你来我也不要,因为我要你的根本是:你有钱,你能接得住,你能加得住杠杆。我认为最核心的一个是:房价到达一定程度,人自然没法生,隔壁韩国如此,日本如此,中国亦如此。中国现在的大体情况是:未来的五到十年,基本处在加完杠杆后还债的年代。低增长、低通胀、低利率,三低。而过去的三十年,中国一直是高增长,高通胀、高利率。高和低之间差距是什么?杠杆率到一定程度自然就定了,隔壁日本就是到了极致,崩了!

\n

老美的底层老百姓过得并不好,但老美的富人更好了,富人靠杠杆。记住一点,富人靠杠杆、靠金融资产,穷人才靠劳动力。在全球化的过程,美国居民保持的是最低的生存权利(美国底层人民),他的收入是不增长的。从1982年开始,美国的数字是多少呢?平均薪资增速3%,金字塔的底层25%和顶层25%的薪资性收入差是1%。啥意思呢?平均3%的薪资收入增长再减去1%的社会阶层差,底层25%的美国居民薪资性收入仅仅是2%。大家发现这个2%是什么?正好就是美国通胀水平。啥意思呢?你可以理解,这美国的底层居民过去的二三十年薪资扣掉通胀是不增长的。

\n

二十多年前,索罗斯写那本联名书的时候,他就给大家解释过这个话题,他说中国庞大的贸易顺差去补偿了美国底层居民。举个例子,一个美国人,薪资2万保持不变,但是呢,中国生产的笔记本电脑从5000美金降到了1000美金,我的购买力怎么样?增强了!这就是庞大的中国出口。中国出口的商品绕一圈转回来,变成中国的外汇储备,中国的外汇储备买的是什么,买的是美债。美债用于什么了?美债用于两件事:打仗、给底层居民补医疗和教育。所以你就会发现。如果你做美国的底层居民的话,出现什么情况呢?收入没增长,但是中国供应的商品的购买力降弱了,这个购买力让美国人的购买力增强了。同时中国大量的顺差和外汇储备补偿了美国的教育和医疗。

\n

美国真正发家致富的人,都是看明白了这套游戏的。一旦底层居民薪资收入不增长,我们教科书里学的货币银行学中,央行双目标志将失效,也就是说中央银行将永远不用去考虑薪资通胀,只需要去考虑经济。此时就会产生我们现在这个模型”经济不行就降息”。每一次经济不行的时候,就借更多的美元,买更多的美国金融性资产。但在82年,美国金融监管放开了一个重要的法案,金融自由化。啥意思,如果监管部门把银行管得死死的,银行不许给居民加杠杆,就算来了这样的机会,社会也没有财富效应。但如果我此时告诉银行金融监管你们可以去创造各种各样的金融衍生产品,去给居民部门加杠杆,举例子房贷可以变成18种花样,于是82年到2022年,搞了整整40年。

\n

到了02年的时候,美国居民部门突然间反应过来,哦!原来富人不是靠着劳动挣钱的,富人得去炒金融资产。02年小布什一句话:你们得做起美国梦来。什么意思?你们手上没有股票,得有!但问题是在于,这帮穷人的美国梦是最可怕的,因为他收入不增长,还借了更多的房贷,02年到08年的这种做法,结果就是08年崩掉了。但崩掉以后,美国的底层人民麻烦就大了,被迫还了15年的债。从08年到现在,美国居民部门一直在去杠杆还债,但是你知道一个人收入不增长,然后还要不停的还债,他最终会转成什么?欧美有两样东西会让整个社会颠覆的,一个是”选票”、”上街大游行”。他的选票会转化成什么?答案:”特朗普”。也就是要么民粹,要么你补偿我。补偿只有一个方法,提高他的薪资收入。你以为真的是像宏观经济上在跟你讲说,因为这场疫情导致供应链导致劳动力市场结构发生了变换?那都不是核心,真正的核心是:当居民部门还了12年、15年债以后,这部分人不想还了,你要么就是更多的补偿给我、要么就是给我加薪。

\n

目前美国出现的就一个结果:底层通胀、顶层通缩。知道美国现在谁最惨吗?美国西海岸。硅谷、洛杉矶、旧金山的富人,这15年是他们加了杠杆,08年后的这漫长的15年创造了我们近乎200年以来,全世界最低的资金年代,它也创造了全世界几乎是最大的资产。这个资产是什么?虚拟资产!你们知道为什么会出现虚拟性货币?如果这么庞大的流动性,这么便宜的资金,冲向了实物资产(粮食、房子),这个价格一旦失控,我们离战争就不远了。虚拟资产有个好处,它仅仅消灭富人财富。西海岸的这帮我们叫美国新贵,准确说叫做穷人日子很好,富人日子很难受。富人中间是老钱收割新钱,老钱是什么呢?华盛顿、纽约、休斯顿,新钱就是硅谷、洛杉矶。新钱靠的是什么?科技、技术、创新。你创造一个东西带来的社会价值,可能只创造了1万块。但是金融市场给予你200倍。一次性兑付了几百年的收益。这就是我们的金融市场,当利率水平过低的时候,它会使得所有资产的估值极高。而科技作为主要的推动力,会自然而然地迎来一场高估值的泡沫。

\n

干一级市场投资,我可以告诉你估值泡沫出现的时候,那特征都是一样的,简单说:瞎几把随便写PPT都能融到钱。有很多人的利益是建立在:利率和资产价格上的。经济行的时候,是可以承受高利率的。经济不行的时候,(日本)把利率降到负都没用。

\n

这四十年结束以后,逻辑是什么?科技肯定不行,深圳科创你就搞到天上去,你拉动整个社会的经济也远着呢!技术的进步,只有转化成全民生产率,才能拉动所有人。中国现在相反,是低增长、低通胀、低利率,而这3低的背后是高杠杆。有一种情况,可以让我们避开这种所谓的内卷型的环境,就是走出去。美国在六十年代到现在,不是盈利率的增长,它获得的是市场份额的扩大。中国现在所有的企业会内卷的,会卷得一塌糊涂,原因很简单,市场只有国内了。2018年开始的,中美贸易战逆全球化,鬼佬就是要把我们踢出去。因为不走出去,我们过去的二三十年所有的产能是给全世界走配套的。如果不走出去,单凭我们国内的居民部门消化不了。

\n

我们国内居民部门中还出现了一个问题,大部分的90后、95后是在缩的,他们是在降低欲望的,他们是在消费降级的。高端消费永远是给那些富人的。中产阶级的杠杆达到一定程度,就会进入一个很典型的低欲望社会。跟现在比较穷的年轻人打打交道,你们就会发现,不结婚挺好、不生孩子挺好、这个不买房也不错、这个手机也不用换。然后这个蜜雪冰城的咖啡。虽然难喝点,但还可以你。跟富二代富三代打交道,就看不出来。中国现在也是典型的K字型。

\n

过去四十年的游戏结束了,现金远远地甩开了长期经济增长。巴菲特已经教过大家了,最好的公司是什么?经济好也好。经济坏也好,是刚需、高股息类的公司,且不受经济低迷影响的公司。你们有没有发现在最近在炒中草药,为啥炒中炒药?就是刚需,好比通信行业的话费,你必须得按月充值。所以巴菲特在买的是什么?现金奶牛ETF的配置,看看都是啥?能源、医药、金融。这些行业就是典型的现金类资产,巴菲特想获得一样东西,知道是什么吗?股息率!美孚的几个财务数据叠加会发现第一种情况,低油价、经济差,它的股价上涨的。并不是巴菲特赌油价上涨。

\n

全球过去40年挣100块钱,只有40块钱来自于你们理解中的经济增长带来的回报,有60块钱是来自于低利率加杠杆的投资回报。所以这个世界有两个投资人:一个叫长期价值投资回报,就是获得经济增长带来的贴现。另外一个就是获得利率波动带来的加杠杆,房子准确说典型的就是杠杆性资产,如果房住不炒,那理论上它是没有价格波动的。

\n

今年是蹦了一个ChatGPT,明年还必定还会有个啥的。当然我只想说一句,当你听到今年AI的时候,你们还记得去年大明湖畔的元宇宙吗?你们还记得十年前的互联网加万物吗?过去几年全球利率低,咱们可以你炒一波咱们给90后,90后炒一波给95后,95后炒一波给00后,然后全球流动性一收,咱们把00后挂到高位上。为什么是这个路径?越年轻的越信技术,他们认为技术能改变世界,结果世界会残酷地教育你。技术能不能改变世界?能!但绝对不是炒技术,炒技术改变不了世界。

\n

从去年十一月份,所有的主题性投资只能这样炒,当你想给90后的时候,你发现90后的元宇宙还没解套呢!90后借不来更多的钱,所以周期会非常短,迅速炒迅速落。可人民币现在利率这么低,为什么会没有钱呢?因为所有科技性投资,不是跟着人民币利率走的。比如ChatGPT,是海外现有了这个东西,国内跟风,元宇宙亦然,新能源汽车也是现有了特斯拉。中国在技术方面采取的战略是:摸着石头过河、黑猫白猫带着老鼠就行。所以我们会出现一种情况,海外有个热点我们马上跟。那问题就来了,你的估值怎么定价?做过一级市场投资的,投资某家企业的时候,对标物是美国某某企业。美股最强大的地方,是把所有创新的Top级的领军人物均以美元定价,导致的一个结果就是,它是锚!定价了所有估值水平。如果人民币能对资产估值有影响,那就必须要求投这家企业的对标物是在中国上市。

\n

能挣钱的企业就是好企业?像国内某企业家讲的,我五年不给你分红,你能把我咋滴?这种企业就不是好企业。挣钱分钱,经济不好的时候能力还继续挣钱,这就是它的垄断性。所以你们会发现,别瞧不起老钱,老钱投的上游。改革开放之后,煤炭、电力、很多行业都是允许民营企业介入的,到现在为止我就请问,这些上游还有民营企业吗?

\n

回到日本的商社,跟中国国有企业本质是一样,它是家庭组织,不由董事会左右。巴菲特去了以后,和高管和家族的人员就谈了一个事,你是现金奶牛,因为它控制了全球的上游。所以在经济不好的时候,日本的五大商社就是中国的国有企业,也是美国的能源企业。能挣钱、有利润,然后有大量的自由现金流。它唯一的问题是:”公司治理”,就是它不一定愿意给你分。现在股息率快到五了,你不分,我就把公司买成我的,然后日本人说的就是:巴菲特先生,你别把我控制,我愿意分钱给你。因此巴菲特干的事情,买了日本的这些高股息的资产,并且在日本发行了无抵押担保的日元债券。啥意思呢?巴菲特一毛钱没掏,用日本人的储蓄以及低利率水平借钱,买了日本人的资产,还强迫日本人分了高股息,这才是资本真正的收割。

\n

未来的5年是10年,真正的游戏是如何借低息买高息。美元不便宜了,但美元的高息依旧很吸引人。那么问题就来了,谁是低息?美元如果一直维持高增长,高通胀、高利率的话,人民币一定是贬值的。而倒过来的交易,借人民币转高息资产,这个高息资产可以是人民币里边不多有的高息资产,也可以是全球范围内的高股息现金奶牛,你会发现,这就又形成了类似于15年前的套利资本流动、交易机会。

\n"},{"title":"付鹏:逆全球化下的全球资产配置","status":"done","_content":"\n为什么会有中美贸易战?为什么会有特朗普?他们手上有个重要的东西,选票。法国人民手上有个重要的东西,什么东西?公会。财富分配,到最后是需要权力去平衡的,也就简单讲,富者恒富,穷者不富的时候,他手上会用一个最简单东西投出民粹主义的路线,就是选票。哪个领导人愿意带著我们把工资从十块钱降到二十,我就选他。有两种做法:第一种,让企业吐回来。第二种,让中国吐回来。中国拿走的劳动收入增速,现在要吐回来,这就是逆全球化。企业部门拿走的高杠杆,高负债,获得的财富积累要吐回来。然后你就会发现,他的收入增速上来,他会长期的怎么样?通胀。利率水平增高,导致企业部门(美国制造业)变差,其实就是代表著美国的左右两边。\n\n在这种背景下,既然不能从市场层面上获得收入增速,就得从你自身提高收入增速,答案是什么?技术进步。现在工人工资我是压不下去了,怎么办?三个工人变两工人+机器人。所以你猜美国现在在干嘛?右侧是这帮企业,左侧是美国的这帮中底层的,这些老百姓小日子过得太Happy了。左侧这帮企业,收入增速7.5,通胀5个点。开心吧?挣的钱存到银行,现在5个点,开心吧?然后08年美国次贷危机,破裂了以后,美国锁定在了0-0.25的这个低息区域,老百姓买了低息贷款的房子,储蓄多了,开心吧?中国出口的赚,买了美国国债,相当于还要回储给你们,最后发现,美国挣得多、存的多、债务还低。从08年到23年,这15年,就相当于去了15年的杠杆。企业部门要借贷,5.25的利率高于目前3点几的增速,被迫进入到利润下降,未来要提高增长,只有一个方案,就是技术进步。\n\n硅谷大裁员是不影响就业的,这帮生产率高的人,转化再就业的能力是极强的。生产率低的人,出现大规模失业才是最麻烦的。简单讲,美国这帮黑人们如果全都失业了,那美国就完了。08年后,企业,银行,居民,这三者的分配关系,就有问题了,一直拖到现在,已经无解了。全世界从过去的十年,只有一个大的变量:\"技术进步\"。我个人有一种感觉,当前的这个时间点,在炒的人工智能,跟过去的十年浮现出来的不太一样。美国股市现在就那几个科技公司拉着每股涨,是好事。几家公司是在5.25的利率环境下,拉动每股涨的。它不是那个0到0.25的利率拉动的,能够扛得住资金成本5到5.25,还能带来增长,意味著AI可能是真的。\n\n08年到2028年,整20年的周期,足够一个新技术带动全社会变动了,这几家公司可能在未来3到5年拉着美股,创新高 继续创新高。后面的涨是正儿八经的生产率提高的涨,前两年是啥?是低利率推著泡沫在涨。如果美国完成了这条线,既保证了企业在技术进步下维持高利润增长,又保证底层居民获得了高收入增速,你猜美国的利率会是什么情况?他的利率会一直很高,进入到高增长、高通胀、高利率,三高。\n\n那么中国会事什么情况?美国不给技术,转化生产也不是中国制造了,逆全球化嘛!国内会低增长、低通胀、低利率。从全球分工来看,美国底层是技术研发,中间生产,下面是市场。如果生产环节不给中国,高端制造给谁?大概率是日本。\n\n日本的全球分工的角色会跟过去的二三十年不一样,英国的老牌家族的基金家族办公开始跑到日本租办公室招人了,二三十年前的时候跑到香港租办公室招人,跑到北京设立办事处,为什么?答案只有一个,看好该国未来20年30年的增长。\n\n在过去的20年发展过程中,我们的生产环节是针对全世界的,如果出不去。只能转化成内需。我们的一个大问题出来了,过去的10年中国经济最大的不同,就是现在的年轻人债务过高,内需是背著重重的负债前行的,有一些人不准备生人再去背了。直到疫情过恢复以后,到现在为止不缺生产,缺谁买单。内需要么是政府部门,要么是居民部门,一个是投资不动,一个消费不动,有钱的有钱死,没钱的负债死。昨天我看又新能源车下乡了,然后你去看看那个评论区,90后崛起了,不像十年前那么好骗了,他们咋回答呢?啥好事都想著我们农村人。十年前,什么家电下乡,卖不掉的冰箱……就是电器,卖给我们农村人,这韭菜不能照着一个地儿割啊。\n\n笃定了老美不会给我们这条科技树,所以经济当前压根就不是我们最大的问题,只要能守住底线,也就是你的居民,仍然是可爱的人,底线思维即可。当下最大的目标是重建这个秩序,美国不带我玩,我就去拉着ABC,去重构这种国际分工、全球化市场结构,重构我们的科技树。中国现在的投资,跟老美一样,都在反映科技树,但是\"市场决定的科技树(市场经济)\"和\"政府主导的科技树(计划经济)\"结局是不一样的。所以低增长、低利率的国内必须要点科技树,但体现在股票上,国内是什么光景,美股是什么光景?\n\n很多人说A股弱,美股强?因为人家现在高增长、高通胀、高利率、和消费市场。08年之后。老美去了15年杠杆。居民部门老老实实还了15年的债,日本人老老实实还了30年。美国的利率水平绝对不再是以前的路径,不再会靠降息加杠杆的方式去推动增长,而是要靠生产校长的扩张,技术在两三年后我们有可能会迎来一个\"iPhone时刻\"。\n\n我们现在所有人依旧想的是如果企业不行,最终还得是降息。美国会出现一种情况就是企业的技术进步,把它带上去,整个美元的长短端利率都在5-5.25,那我就请问你光现金和资产回报都5-5.25了,你觉得黄金会奔三千四千五千吗?黄金的之所以在高价,因为长期利率在地位,加上杠杆,但是如果长短端的投资回报率在5-5.25,你还会持有黄金吗?\n\n08年金融危机以后,老美痛定思痛,开始在极低的利率下,去除杠杆,也就是讲现在你看见的美国是08年的美国导致的一个结果。而08年的中国,在干什么呢?所有人熟知的叫四万亿(可不止),总结一句话,就是给居民部门加杠杆。中国拖起了全球经济,代价是这一代居民部门的储蓄和杠杆,就是老百姓讲的掏空了几个钱包。\n\n真正的大交易者,用什么东西做负债,用什么东西当资产。如果现在世界格局是这样,请问我们个人该怎么办?举个例子:我09年回国,2010年开始到2016年股灾,人民币单边每年升值3%,美元官方利率0到0.2%,加上手续费,美元借款资金成本大约2%。就造成了实际借美元利率是-1%,那时候北上广的房价是多少?房子的背后就是人口、收入、杠杆,而其中最重要的吗,在当时,不是人口、也不是收入,而是杠杆。我的借贷成本如果-1%的话,这不就是热钱吗?借-1%的钱来中国就好了,干什么东西,买房呗!\n\n所以放到现在的今天,个人该怎么办呢?美国进入到三高的状态,中国进入到这种三低的状态。当下经营贷3.2%,人民币贬值,实际人民币借贷成就是负的!所以答案就是,人民币长期利率水平会非常的低,借贷低息的人民币,人民币贬值,官方的借贷成本会从3个点掉到2个点,并且维持在2个点,三到五年让你借贷人民币的真实利率一定在0以下。所以从大交易者的角度,负债解决了,资产端该买啥?能够给你二字头融资成本,一定说明国内的资产端没有高息回报,我们不出境去哪儿找高息的资产?我偷偷摸摸告诉大家过一段时间你们会发现某个类型的ETF手续费还会增加,什么东西用人民币结算但是持有的是境外高息资产(QDII)?美国光银行存款利率在5%,你该干嘛?你们会发现巴菲特买的西方石油公司、买的日本的五大商社股息率在10以上,你们知道该干嘛?如果这个东西一直是这样倒着,咱们哪来的信心觉得外资会流入?\n\n所以这就是一个重要的分水岭,这个最核心的原理理解,超级的重要!绝对对你后面三五年巨大的受益。","source":"_posts/finance/付鹏:逆全球化下的全球资产配置.md","raw":"---\ntitle: 付鹏:逆全球化下的全球资产配置\ncategories:\n - Finance\nstatus: done\n---\n\n为什么会有中美贸易战?为什么会有特朗普?他们手上有个重要的东西,选票。法国人民手上有个重要的东西,什么东西?公会。财富分配,到最后是需要权力去平衡的,也就简单讲,富者恒富,穷者不富的时候,他手上会用一个最简单东西投出民粹主义的路线,就是选票。哪个领导人愿意带著我们把工资从十块钱降到二十,我就选他。有两种做法:第一种,让企业吐回来。第二种,让中国吐回来。中国拿走的劳动收入增速,现在要吐回来,这就是逆全球化。企业部门拿走的高杠杆,高负债,获得的财富积累要吐回来。然后你就会发现,他的收入增速上来,他会长期的怎么样?通胀。利率水平增高,导致企业部门(美国制造业)变差,其实就是代表著美国的左右两边。\n\n在这种背景下,既然不能从市场层面上获得收入增速,就得从你自身提高收入增速,答案是什么?技术进步。现在工人工资我是压不下去了,怎么办?三个工人变两工人+机器人。所以你猜美国现在在干嘛?右侧是这帮企业,左侧是美国的这帮中底层的,这些老百姓小日子过得太Happy了。左侧这帮企业,收入增速7.5,通胀5个点。开心吧?挣的钱存到银行,现在5个点,开心吧?然后08年美国次贷危机,破裂了以后,美国锁定在了0-0.25的这个低息区域,老百姓买了低息贷款的房子,储蓄多了,开心吧?中国出口的赚,买了美国国债,相当于还要回储给你们,最后发现,美国挣得多、存的多、债务还低。从08年到23年,这15年,就相当于去了15年的杠杆。企业部门要借贷,5.25的利率高于目前3点几的增速,被迫进入到利润下降,未来要提高增长,只有一个方案,就是技术进步。\n\n硅谷大裁员是不影响就业的,这帮生产率高的人,转化再就业的能力是极强的。生产率低的人,出现大规模失业才是最麻烦的。简单讲,美国这帮黑人们如果全都失业了,那美国就完了。08年后,企业,银行,居民,这三者的分配关系,就有问题了,一直拖到现在,已经无解了。全世界从过去的十年,只有一个大的变量:\"技术进步\"。我个人有一种感觉,当前的这个时间点,在炒的人工智能,跟过去的十年浮现出来的不太一样。美国股市现在就那几个科技公司拉着每股涨,是好事。几家公司是在5.25的利率环境下,拉动每股涨的。它不是那个0到0.25的利率拉动的,能够扛得住资金成本5到5.25,还能带来增长,意味著AI可能是真的。\n\n08年到2028年,整20年的周期,足够一个新技术带动全社会变动了,这几家公司可能在未来3到5年拉着美股,创新高 继续创新高。后面的涨是正儿八经的生产率提高的涨,前两年是啥?是低利率推著泡沫在涨。如果美国完成了这条线,既保证了企业在技术进步下维持高利润增长,又保证底层居民获得了高收入增速,你猜美国的利率会是什么情况?他的利率会一直很高,进入到高增长、高通胀、高利率,三高。\n\n那么中国会事什么情况?美国不给技术,转化生产也不是中国制造了,逆全球化嘛!国内会低增长、低通胀、低利率。从全球分工来看,美国底层是技术研发,中间生产,下面是市场。如果生产环节不给中国,高端制造给谁?大概率是日本。\n\n日本的全球分工的角色会跟过去的二三十年不一样,英国的老牌家族的基金家族办公开始跑到日本租办公室招人了,二三十年前的时候跑到香港租办公室招人,跑到北京设立办事处,为什么?答案只有一个,看好该国未来20年30年的增长。\n\n在过去的20年发展过程中,我们的生产环节是针对全世界的,如果出不去。只能转化成内需。我们的一个大问题出来了,过去的10年中国经济最大的不同,就是现在的年轻人债务过高,内需是背著重重的负债前行的,有一些人不准备生人再去背了。直到疫情过恢复以后,到现在为止不缺生产,缺谁买单。内需要么是政府部门,要么是居民部门,一个是投资不动,一个消费不动,有钱的有钱死,没钱的负债死。昨天我看又新能源车下乡了,然后你去看看那个评论区,90后崛起了,不像十年前那么好骗了,他们咋回答呢?啥好事都想著我们农村人。十年前,什么家电下乡,卖不掉的冰箱……就是电器,卖给我们农村人,这韭菜不能照着一个地儿割啊。\n\n笃定了老美不会给我们这条科技树,所以经济当前压根就不是我们最大的问题,只要能守住底线,也就是你的居民,仍然是可爱的人,底线思维即可。当下最大的目标是重建这个秩序,美国不带我玩,我就去拉着ABC,去重构这种国际分工、全球化市场结构,重构我们的科技树。中国现在的投资,跟老美一样,都在反映科技树,但是\"市场决定的科技树(市场经济)\"和\"政府主导的科技树(计划经济)\"结局是不一样的。所以低增长、低利率的国内必须要点科技树,但体现在股票上,国内是什么光景,美股是什么光景?\n\n很多人说A股弱,美股强?因为人家现在高增长、高通胀、高利率、和消费市场。08年之后。老美去了15年杠杆。居民部门老老实实还了15年的债,日本人老老实实还了30年。美国的利率水平绝对不再是以前的路径,不再会靠降息加杠杆的方式去推动增长,而是要靠生产校长的扩张,技术在两三年后我们有可能会迎来一个\"iPhone时刻\"。\n\n我们现在所有人依旧想的是如果企业不行,最终还得是降息。美国会出现一种情况就是企业的技术进步,把它带上去,整个美元的长短端利率都在5-5.25,那我就请问你光现金和资产回报都5-5.25了,你觉得黄金会奔三千四千五千吗?黄金的之所以在高价,因为长期利率在地位,加上杠杆,但是如果长短端的投资回报率在5-5.25,你还会持有黄金吗?\n\n08年金融危机以后,老美痛定思痛,开始在极低的利率下,去除杠杆,也就是讲现在你看见的美国是08年的美国导致的一个结果。而08年的中国,在干什么呢?所有人熟知的叫四万亿(可不止),总结一句话,就是给居民部门加杠杆。中国拖起了全球经济,代价是这一代居民部门的储蓄和杠杆,就是老百姓讲的掏空了几个钱包。\n\n真正的大交易者,用什么东西做负债,用什么东西当资产。如果现在世界格局是这样,请问我们个人该怎么办?举个例子:我09年回国,2010年开始到2016年股灾,人民币单边每年升值3%,美元官方利率0到0.2%,加上手续费,美元借款资金成本大约2%。就造成了实际借美元利率是-1%,那时候北上广的房价是多少?房子的背后就是人口、收入、杠杆,而其中最重要的吗,在当时,不是人口、也不是收入,而是杠杆。我的借贷成本如果-1%的话,这不就是热钱吗?借-1%的钱来中国就好了,干什么东西,买房呗!\n\n所以放到现在的今天,个人该怎么办呢?美国进入到三高的状态,中国进入到这种三低的状态。当下经营贷3.2%,人民币贬值,实际人民币借贷成就是负的!所以答案就是,人民币长期利率水平会非常的低,借贷低息的人民币,人民币贬值,官方的借贷成本会从3个点掉到2个点,并且维持在2个点,三到五年让你借贷人民币的真实利率一定在0以下。所以从大交易者的角度,负债解决了,资产端该买啥?能够给你二字头融资成本,一定说明国内的资产端没有高息回报,我们不出境去哪儿找高息的资产?我偷偷摸摸告诉大家过一段时间你们会发现某个类型的ETF手续费还会增加,什么东西用人民币结算但是持有的是境外高息资产(QDII)?美国光银行存款利率在5%,你该干嘛?你们会发现巴菲特买的西方石油公司、买的日本的五大商社股息率在10以上,你们知道该干嘛?如果这个东西一直是这样倒着,咱们哪来的信心觉得外资会流入?\n\n所以这就是一个重要的分水岭,这个最核心的原理理解,超级的重要!绝对对你后面三五年巨大的受益。","slug":"finance/付鹏:逆全球化下的全球资产配置","published":1,"date":"2023-11-06T05:17:08.678Z","updated":"2023-11-06T05:17:47.196Z","comments":1,"layout":"post","photos":[],"link":"","_id":"clp7x194w0007v3z35r7f2ge1","content":"

为什么会有中美贸易战?为什么会有特朗普?他们手上有个重要的东西,选票。法国人民手上有个重要的东西,什么东西?公会。财富分配,到最后是需要权力去平衡的,也就简单讲,富者恒富,穷者不富的时候,他手上会用一个最简单东西投出民粹主义的路线,就是选票。哪个领导人愿意带著我们把工资从十块钱降到二十,我就选他。有两种做法:第一种,让企业吐回来。第二种,让中国吐回来。中国拿走的劳动收入增速,现在要吐回来,这就是逆全球化。企业部门拿走的高杠杆,高负债,获得的财富积累要吐回来。然后你就会发现,他的收入增速上来,他会长期的怎么样?通胀。利率水平增高,导致企业部门(美国制造业)变差,其实就是代表著美国的左右两边。

\n

在这种背景下,既然不能从市场层面上获得收入增速,就得从你自身提高收入增速,答案是什么?技术进步。现在工人工资我是压不下去了,怎么办?三个工人变两工人+机器人。所以你猜美国现在在干嘛?右侧是这帮企业,左侧是美国的这帮中底层的,这些老百姓小日子过得太Happy了。左侧这帮企业,收入增速7.5,通胀5个点。开心吧?挣的钱存到银行,现在5个点,开心吧?然后08年美国次贷危机,破裂了以后,美国锁定在了0-0.25的这个低息区域,老百姓买了低息贷款的房子,储蓄多了,开心吧?中国出口的赚,买了美国国债,相当于还要回储给你们,最后发现,美国挣得多、存的多、债务还低。从08年到23年,这15年,就相当于去了15年的杠杆。企业部门要借贷,5.25的利率高于目前3点几的增速,被迫进入到利润下降,未来要提高增长,只有一个方案,就是技术进步。

\n

硅谷大裁员是不影响就业的,这帮生产率高的人,转化再就业的能力是极强的。生产率低的人,出现大规模失业才是最麻烦的。简单讲,美国这帮黑人们如果全都失业了,那美国就完了。08年后,企业,银行,居民,这三者的分配关系,就有问题了,一直拖到现在,已经无解了。全世界从过去的十年,只有一个大的变量:”技术进步”。我个人有一种感觉,当前的这个时间点,在炒的人工智能,跟过去的十年浮现出来的不太一样。美国股市现在就那几个科技公司拉着每股涨,是好事。几家公司是在5.25的利率环境下,拉动每股涨的。它不是那个0到0.25的利率拉动的,能够扛得住资金成本5到5.25,还能带来增长,意味著AI可能是真的。

\n

08年到2028年,整20年的周期,足够一个新技术带动全社会变动了,这几家公司可能在未来3到5年拉着美股,创新高 继续创新高。后面的涨是正儿八经的生产率提高的涨,前两年是啥?是低利率推著泡沫在涨。如果美国完成了这条线,既保证了企业在技术进步下维持高利润增长,又保证底层居民获得了高收入增速,你猜美国的利率会是什么情况?他的利率会一直很高,进入到高增长、高通胀、高利率,三高。

\n

那么中国会事什么情况?美国不给技术,转化生产也不是中国制造了,逆全球化嘛!国内会低增长、低通胀、低利率。从全球分工来看,美国底层是技术研发,中间生产,下面是市场。如果生产环节不给中国,高端制造给谁?大概率是日本。

\n

日本的全球分工的角色会跟过去的二三十年不一样,英国的老牌家族的基金家族办公开始跑到日本租办公室招人了,二三十年前的时候跑到香港租办公室招人,跑到北京设立办事处,为什么?答案只有一个,看好该国未来20年30年的增长。

\n

在过去的20年发展过程中,我们的生产环节是针对全世界的,如果出不去。只能转化成内需。我们的一个大问题出来了,过去的10年中国经济最大的不同,就是现在的年轻人债务过高,内需是背著重重的负债前行的,有一些人不准备生人再去背了。直到疫情过恢复以后,到现在为止不缺生产,缺谁买单。内需要么是政府部门,要么是居民部门,一个是投资不动,一个消费不动,有钱的有钱死,没钱的负债死。昨天我看又新能源车下乡了,然后你去看看那个评论区,90后崛起了,不像十年前那么好骗了,他们咋回答呢?啥好事都想著我们农村人。十年前,什么家电下乡,卖不掉的冰箱……就是电器,卖给我们农村人,这韭菜不能照着一个地儿割啊。

\n

笃定了老美不会给我们这条科技树,所以经济当前压根就不是我们最大的问题,只要能守住底线,也就是你的居民,仍然是可爱的人,底线思维即可。当下最大的目标是重建这个秩序,美国不带我玩,我就去拉着ABC,去重构这种国际分工、全球化市场结构,重构我们的科技树。中国现在的投资,跟老美一样,都在反映科技树,但是”市场决定的科技树(市场经济)”和”政府主导的科技树(计划经济)”结局是不一样的。所以低增长、低利率的国内必须要点科技树,但体现在股票上,国内是什么光景,美股是什么光景?

\n

很多人说A股弱,美股强?因为人家现在高增长、高通胀、高利率、和消费市场。08年之后。老美去了15年杠杆。居民部门老老实实还了15年的债,日本人老老实实还了30年。美国的利率水平绝对不再是以前的路径,不再会靠降息加杠杆的方式去推动增长,而是要靠生产校长的扩张,技术在两三年后我们有可能会迎来一个”iPhone时刻”。

\n

我们现在所有人依旧想的是如果企业不行,最终还得是降息。美国会出现一种情况就是企业的技术进步,把它带上去,整个美元的长短端利率都在5-5.25,那我就请问你光现金和资产回报都5-5.25了,你觉得黄金会奔三千四千五千吗?黄金的之所以在高价,因为长期利率在地位,加上杠杆,但是如果长短端的投资回报率在5-5.25,你还会持有黄金吗?

\n

08年金融危机以后,老美痛定思痛,开始在极低的利率下,去除杠杆,也就是讲现在你看见的美国是08年的美国导致的一个结果。而08年的中国,在干什么呢?所有人熟知的叫四万亿(可不止),总结一句话,就是给居民部门加杠杆。中国拖起了全球经济,代价是这一代居民部门的储蓄和杠杆,就是老百姓讲的掏空了几个钱包。

\n

真正的大交易者,用什么东西做负债,用什么东西当资产。如果现在世界格局是这样,请问我们个人该怎么办?举个例子:我09年回国,2010年开始到2016年股灾,人民币单边每年升值3%,美元官方利率0到0.2%,加上手续费,美元借款资金成本大约2%。就造成了实际借美元利率是-1%,那时候北上广的房价是多少?房子的背后就是人口、收入、杠杆,而其中最重要的吗,在当时,不是人口、也不是收入,而是杠杆。我的借贷成本如果-1%的话,这不就是热钱吗?借-1%的钱来中国就好了,干什么东西,买房呗!

\n

所以放到现在的今天,个人该怎么办呢?美国进入到三高的状态,中国进入到这种三低的状态。当下经营贷3.2%,人民币贬值,实际人民币借贷成就是负的!所以答案就是,人民币长期利率水平会非常的低,借贷低息的人民币,人民币贬值,官方的借贷成本会从3个点掉到2个点,并且维持在2个点,三到五年让你借贷人民币的真实利率一定在0以下。所以从大交易者的角度,负债解决了,资产端该买啥?能够给你二字头融资成本,一定说明国内的资产端没有高息回报,我们不出境去哪儿找高息的资产?我偷偷摸摸告诉大家过一段时间你们会发现某个类型的ETF手续费还会增加,什么东西用人民币结算但是持有的是境外高息资产(QDII)?美国光银行存款利率在5%,你该干嘛?你们会发现巴菲特买的西方石油公司、买的日本的五大商社股息率在10以上,你们知道该干嘛?如果这个东西一直是这样倒着,咱们哪来的信心觉得外资会流入?

\n

所以这就是一个重要的分水岭,这个最核心的原理理解,超级的重要!绝对对你后面三五年巨大的受益。

\n","site":{"data":{}},"excerpt":"","more":"

为什么会有中美贸易战?为什么会有特朗普?他们手上有个重要的东西,选票。法国人民手上有个重要的东西,什么东西?公会。财富分配,到最后是需要权力去平衡的,也就简单讲,富者恒富,穷者不富的时候,他手上会用一个最简单东西投出民粹主义的路线,就是选票。哪个领导人愿意带著我们把工资从十块钱降到二十,我就选他。有两种做法:第一种,让企业吐回来。第二种,让中国吐回来。中国拿走的劳动收入增速,现在要吐回来,这就是逆全球化。企业部门拿走的高杠杆,高负债,获得的财富积累要吐回来。然后你就会发现,他的收入增速上来,他会长期的怎么样?通胀。利率水平增高,导致企业部门(美国制造业)变差,其实就是代表著美国的左右两边。

\n

在这种背景下,既然不能从市场层面上获得收入增速,就得从你自身提高收入增速,答案是什么?技术进步。现在工人工资我是压不下去了,怎么办?三个工人变两工人+机器人。所以你猜美国现在在干嘛?右侧是这帮企业,左侧是美国的这帮中底层的,这些老百姓小日子过得太Happy了。左侧这帮企业,收入增速7.5,通胀5个点。开心吧?挣的钱存到银行,现在5个点,开心吧?然后08年美国次贷危机,破裂了以后,美国锁定在了0-0.25的这个低息区域,老百姓买了低息贷款的房子,储蓄多了,开心吧?中国出口的赚,买了美国国债,相当于还要回储给你们,最后发现,美国挣得多、存的多、债务还低。从08年到23年,这15年,就相当于去了15年的杠杆。企业部门要借贷,5.25的利率高于目前3点几的增速,被迫进入到利润下降,未来要提高增长,只有一个方案,就是技术进步。

\n

硅谷大裁员是不影响就业的,这帮生产率高的人,转化再就业的能力是极强的。生产率低的人,出现大规模失业才是最麻烦的。简单讲,美国这帮黑人们如果全都失业了,那美国就完了。08年后,企业,银行,居民,这三者的分配关系,就有问题了,一直拖到现在,已经无解了。全世界从过去的十年,只有一个大的变量:”技术进步”。我个人有一种感觉,当前的这个时间点,在炒的人工智能,跟过去的十年浮现出来的不太一样。美国股市现在就那几个科技公司拉着每股涨,是好事。几家公司是在5.25的利率环境下,拉动每股涨的。它不是那个0到0.25的利率拉动的,能够扛得住资金成本5到5.25,还能带来增长,意味著AI可能是真的。

\n

08年到2028年,整20年的周期,足够一个新技术带动全社会变动了,这几家公司可能在未来3到5年拉着美股,创新高 继续创新高。后面的涨是正儿八经的生产率提高的涨,前两年是啥?是低利率推著泡沫在涨。如果美国完成了这条线,既保证了企业在技术进步下维持高利润增长,又保证底层居民获得了高收入增速,你猜美国的利率会是什么情况?他的利率会一直很高,进入到高增长、高通胀、高利率,三高。

\n

那么中国会事什么情况?美国不给技术,转化生产也不是中国制造了,逆全球化嘛!国内会低增长、低通胀、低利率。从全球分工来看,美国底层是技术研发,中间生产,下面是市场。如果生产环节不给中国,高端制造给谁?大概率是日本。

\n

日本的全球分工的角色会跟过去的二三十年不一样,英国的老牌家族的基金家族办公开始跑到日本租办公室招人了,二三十年前的时候跑到香港租办公室招人,跑到北京设立办事处,为什么?答案只有一个,看好该国未来20年30年的增长。

\n

在过去的20年发展过程中,我们的生产环节是针对全世界的,如果出不去。只能转化成内需。我们的一个大问题出来了,过去的10年中国经济最大的不同,就是现在的年轻人债务过高,内需是背著重重的负债前行的,有一些人不准备生人再去背了。直到疫情过恢复以后,到现在为止不缺生产,缺谁买单。内需要么是政府部门,要么是居民部门,一个是投资不动,一个消费不动,有钱的有钱死,没钱的负债死。昨天我看又新能源车下乡了,然后你去看看那个评论区,90后崛起了,不像十年前那么好骗了,他们咋回答呢?啥好事都想著我们农村人。十年前,什么家电下乡,卖不掉的冰箱……就是电器,卖给我们农村人,这韭菜不能照着一个地儿割啊。

\n

笃定了老美不会给我们这条科技树,所以经济当前压根就不是我们最大的问题,只要能守住底线,也就是你的居民,仍然是可爱的人,底线思维即可。当下最大的目标是重建这个秩序,美国不带我玩,我就去拉着ABC,去重构这种国际分工、全球化市场结构,重构我们的科技树。中国现在的投资,跟老美一样,都在反映科技树,但是”市场决定的科技树(市场经济)”和”政府主导的科技树(计划经济)”结局是不一样的。所以低增长、低利率的国内必须要点科技树,但体现在股票上,国内是什么光景,美股是什么光景?

\n

很多人说A股弱,美股强?因为人家现在高增长、高通胀、高利率、和消费市场。08年之后。老美去了15年杠杆。居民部门老老实实还了15年的债,日本人老老实实还了30年。美国的利率水平绝对不再是以前的路径,不再会靠降息加杠杆的方式去推动增长,而是要靠生产校长的扩张,技术在两三年后我们有可能会迎来一个”iPhone时刻”。

\n

我们现在所有人依旧想的是如果企业不行,最终还得是降息。美国会出现一种情况就是企业的技术进步,把它带上去,整个美元的长短端利率都在5-5.25,那我就请问你光现金和资产回报都5-5.25了,你觉得黄金会奔三千四千五千吗?黄金的之所以在高价,因为长期利率在地位,加上杠杆,但是如果长短端的投资回报率在5-5.25,你还会持有黄金吗?

\n

08年金融危机以后,老美痛定思痛,开始在极低的利率下,去除杠杆,也就是讲现在你看见的美国是08年的美国导致的一个结果。而08年的中国,在干什么呢?所有人熟知的叫四万亿(可不止),总结一句话,就是给居民部门加杠杆。中国拖起了全球经济,代价是这一代居民部门的储蓄和杠杆,就是老百姓讲的掏空了几个钱包。

\n

真正的大交易者,用什么东西做负债,用什么东西当资产。如果现在世界格局是这样,请问我们个人该怎么办?举个例子:我09年回国,2010年开始到2016年股灾,人民币单边每年升值3%,美元官方利率0到0.2%,加上手续费,美元借款资金成本大约2%。就造成了实际借美元利率是-1%,那时候北上广的房价是多少?房子的背后就是人口、收入、杠杆,而其中最重要的吗,在当时,不是人口、也不是收入,而是杠杆。我的借贷成本如果-1%的话,这不就是热钱吗?借-1%的钱来中国就好了,干什么东西,买房呗!

\n

所以放到现在的今天,个人该怎么办呢?美国进入到三高的状态,中国进入到这种三低的状态。当下经营贷3.2%,人民币贬值,实际人民币借贷成就是负的!所以答案就是,人民币长期利率水平会非常的低,借贷低息的人民币,人民币贬值,官方的借贷成本会从3个点掉到2个点,并且维持在2个点,三到五年让你借贷人民币的真实利率一定在0以下。所以从大交易者的角度,负债解决了,资产端该买啥?能够给你二字头融资成本,一定说明国内的资产端没有高息回报,我们不出境去哪儿找高息的资产?我偷偷摸摸告诉大家过一段时间你们会发现某个类型的ETF手续费还会增加,什么东西用人民币结算但是持有的是境外高息资产(QDII)?美国光银行存款利率在5%,你该干嘛?你们会发现巴菲特买的西方石油公司、买的日本的五大商社股息率在10以上,你们知道该干嘛?如果这个东西一直是这样倒着,咱们哪来的信心觉得外资会流入?

\n

所以这就是一个重要的分水岭,这个最核心的原理理解,超级的重要!绝对对你后面三五年巨大的受益。

\n"},{"title":"Unity Shader入门精要","status":"done","_content":"\n代码基于`c#`,书籍 `Unity Shader`入门精要\n\n# 什么是 OpenGL、DirectX\n\n用于渲染二维或三维图形。可以说,这些接口架起了上层应用程序和底层 GPU 的沟通桥梁。一个应用程序向这些接口发送渲染命令,而这些接口会依次向显卡驱动(Graphics Driver)发送渲染命令,这些显卡驱动是真正知道如何和 GPU 通信的角色,正是它们把 OpenGL 或者 DirectX 的函数调用翻译成了 GPU 能够听懂的语言,同时它们也负责把纹理等数据转换成 GPU 所支持的格式。一个比喻是,显卡驱动就是显卡的操作系统。\n\n![shader_7](/img/shader_7.png)\n\n# 什么是 HLSL、GLSL、CG\n\n如顶点着色器、片元着色器等。这些着色器的可编程性在于,我们可以使用一种特定的语言来编写程序,就好比我们可以用 C#来写游戏逻辑一样。\n\n着色语言是专门用于编写着色器的,常见的着色语言有\n\n- DirectX 的 HLSL(High Level Shading Language)\n\n- OpenGL 的 GLSL(OpenGL Shading Language)\n\n- NVIDIA 的 CG(C for Graphic)。\n\nHLSL、GLSL、CG 都是“高级(High-Level)”语言,但这种高级是相对于汇编语言来说的,而不是像 C#相对于 C 的高级那样。这些语言会被编译成与机器无关的汇编语言,也被称为中间语言(Intermediate Language, IL)。这些中间语言再交给显卡驱动来翻译成真正的机器语言,即 GPU 可以理解的语言。\n\n# GPU 流水线\n\n当 GPU 从 CPU 那里得到渲染命令后,就会进行一系列流水线操作,最终把图元渲染到屏幕上。\n\n![shader_1](/img/shader_1.png)\n\n- 顶点着色器(Vertex Shader)是完全可编程的,它通常用于实现顶点的空间变换、顶点着色等功能\n\n- 曲面细分着色器(Tessellation Shader)是一个可选的着色器,它用于细分图元\n\n- 几何着色器(Geometry Shader)同样是一个可选的着色器,它可以被用于执行逐图元(Per-Primitive)的着色操作,或者被用于产生更多的图元\n\n- 裁剪(Clipping),这一阶段的目的是将那些不在摄像机视野内的顶点裁剪掉,并剔除某些三角图元的面片。例如,我们可以使用自定义的裁剪平面来配置裁剪区域,也可以通过指令控制裁剪三角图元的正面还是背面。\n\n- 片元着色器(Fragment Shader),则是完全可编程的,它用于实现逐片元(Per-Fragment)的着色操作\n\n## 顶点着色器 Vertex Shader\n\nCPU 输入进来的每个顶点都会调用一次顶点着色器。顶点着色器本身不可以创建或者销毁任何顶点,而且无法得到顶点与顶点之间的关系。例如,我们无法得知两个顶点是否属于同一个三角网格。但正是因为这样的相互独立性,GPU 可以利用本身的特性并行化处理每一个顶点,这意味着这一阶段的处理速度会很快。\n\n顶点着色器需要完成的工作主要有:\n\n- 坐标变换:把顶点坐标转换到齐次裁剪坐标系下,接着通常再由硬件做透视除法后,最终得到归一化的设备坐标(NDC)\n\n![shader_2](/img/shader_2.png)\n\n> 在 DirectX 中,NDC 的 z 方向取值范围是[0,1],在 OpenGL 环境下是-1.0,DirectX 中是 0.0\n\n- 逐顶点光照\n\n## 裁剪\n\n只有在单位立方体的图元才需要被继续处\n\n![shader_3](/img/shader_3.png)\n\n## 屏幕映射\n\nOpenGL 和 DirectX 之间的差异问题。OpenGL 把屏幕的左下角当成最小的窗口坐标值,而 DirectX 则定义了屏幕的左上角为最小的窗口坐标值\n\n![shader_4](/img/shader_4.png)\n\n## 光栅阶段\n\n从上一个阶段输出的信息:屏幕坐标系下的顶点位置以及和它们相关的额外信息,如深度值(z 坐标)、法线方向、视角方向等。光栅化阶段有两个最重要的目标:计算每个图元覆盖了哪些像素,以及为这些像素计算它们的颜色。\n\n### 三角形设置\n\n如果要得到整个三角网格对像素的覆盖情况,我们就必须计算每条边上的像素坐标。为了能够计算边界像素的坐标信息,我们就需要得到三角形边界的表示方式。\n\n### 三角形遍历()\n\n1. 扫描变换:三角形遍历(Triangle Traversal)阶段将会检查每个像素是否被一个三角网格所覆盖。如果被覆盖的话,就会生成一个片元(fragment)、\n\n2. 使用三角网格 3 个顶点的顶点信息对整个覆盖区域的像素进行插值\n\n![shader_5](/img/shader_5.png)\n\n这一步的输出就是得到一个片元序列。需要注意的是,一个片元并不是真正意义上的像素,而是包含了很多状态的集合,这些状态用于计算每个像素的最终颜色。这些状态包括了(但不限于)它的屏幕坐标、深度信息,以及其他从几何阶段输出的顶点信息,例如法线、纹理坐标等。\n\n## 片元着色器 Fragment Shader\n\n前面的光栅化阶段实际上并不会影响屏幕上每个像素的颜色值,而是会产生一系列的数据信息,用来表述一个三角网格是怎样覆盖每个像素的。而每个片元就负责存储这样一系列数据。真正会对像素产生影响的阶段是下一个流水线阶段——逐片元操作\n\n> 在 DirectX 中,片元着色器被称为像素着色器(Pixel Shader)但片元着色器是一个更合适的名字,因为此时的片元并不是一个真正意义上的像素。\n\n![shader_6](/img/shader_6.png)\n\n## 逐片元操作\n\n这一阶段有几个主要任务:\n\n- 决定每个片元的可见性。这涉及了很多测试工作,例如深度测试、模板测试等。\n\n- 如果一个片元通过了所有的测试,就需要把这个片元的颜色值和已经存储在颜色缓冲区中的颜色进行合并,或者说是混合。\n\n> !! 对于不透明物体,开发者可以关闭混合(Blend)操作。这样片元着色器计算得到的颜色值就会直接覆盖掉颜色缓冲区中的像素值。但对于半透明物体,我们就需要使用混合操作来让这个物体看起来是透明的。\n\n## Draw Call\n\nCPU 调用图像编程接口,如 OpenGL 中的 `glDrawElements` 命令或者 DirectX 中的 `DrawIndexedPrimitive`命令,以命令 GPU 进行渲染的操作。\n\n### CPU 和 GPU 是如何实现并行工作的?\n\n如果没有流水线化,那么 CPU 需要等到 GPU 完成上一个渲染任务才能再次发送渲染命令。但这种方法显然会造成效率低下。解决方法就是使用一个命令缓冲区(Command Buffer)。\n\n由 CPU 向其中添加命令,而由 GPU 从中读取命令,添加和读取的过程是互相独立的。\n\n### 为什么 Draw Call 多了会影响帧率?\n\n每一个复制动作需要很多额外的操作,例如分配内存、创建各种元数据等。如你所见,这些操作将造成很多额外的性能开销,如果我们复制了很多小文件,那么这个开销将会很大\n\n### 如何减少 Draw Call?\n\n尽管减少 Draw Call 的方法有很多,但我们这里仅讨论使用批处理(Batching)的方法。我们讲过,提交大量很小的 Draw Call 会造成 CPU 的性能瓶颈,即 CPU 把时间都花费在准备 Draw Call 的工作上了。那么,一个很显然的优化想法就是把很多小的 DrawCall 合并成一个大的 Draw Call,这就是批处理的思想。\n\n在游戏开发过程中,为了减少 Draw Call 的开销,有两点需要注意。\n\n- 避免使用大量很小的网格。当不可避免地需要使用很小的网格结构时,考虑是否可以合并它们。\n\n- 避免使用过多的材质。尽量在不同的网格之间共用同一个材质。\n\n# 你明白什么是 Shader\n\n- GPU 流水线上一些可高度编程的阶段,而由着色器编译出来的最终代码是会在 GPU 上运行的(对于固定管线的渲染来说,着色器有时等同于一些特定的渲染设置)\n\n- 有一些特定类型的着色器,如顶点着色器、片元着色器等\n\n- 依靠着色器我们可以控制流水线中的渲染细节,例如用顶点着色器来进行顶点变换以及传递数据,用片元着色器来进行逐像素的渲染。\n\n# Unity Shader\n\n> !! Unity Shader ! = 真正的 Shader\n\nUnity Shader 实际上指的就是一个 ShaderLab 文件——硬盘上以.shader 作为文件后缀的一种文件,提供了一种让开发者同时控制渲染流水线中多个阶段的一种方式,不仅仅是提供 Shader 代码。\n\n作为开发者而言,我们绝大部分时候只需要和 Unity Shader 打交道,而不需要关心渲染引擎底层的实现细节\n\nUnity 编辑器会把这些 CG 片段编译成低级语言,如汇编语言等。通常,Unity 会自动把这些 CG 片段编译到所有相关平台(这里的平台是指不同的渲染平台,例如 Direct3D 9、OpenGL、Direct3D 11、OpenGL ES 等)上\n\n## 基础\n\n在没有 Unity 这类编辑器的情况下,如果我们想要对某个模型设置渲染状态,可能需要类似下面的代码:\n\n上述伪代码仅仅是简化后的版本, 当渲染的模型数目、需要调整的着色器属性不断增多时,上述过程将变得更加复杂和冗长。\n\n而且,当涉及透明物体等多物体的渲染时,如果没有编辑器的帮助,我们要非常小心如渲染顺序等问题。\n\n```js\n// 初始化渲染设置\nvoid Initialization() {\n // 从硬盘上加载顶点着色器的代码\n string vertexShaderCode = LoadShaderFromFile(VertexShader.shader);\n // 从硬盘上加载片元着色器的代码\n string fragmentShaderCode = LoadShaderFromFile(FragmentShader.shader);\n // 把顶点着色器加载到GPU中\n LoadVertexShaderFromString(vertexShaderCode);\n // 把片元着色器加载到GPU中\n LoadFragmentShaderFromString(fragmentShaderCode);\n // 设置名为\"vertexPosition\"的属性的输入,即模型顶点坐标\n SetVertexShaderProperty(\"vertexPosition\", vertices);\n // 设置名为\"MainTex\"的属性的输入,someTexture是某张已加载的纹理\n SetVertexShaderProperty(\"MainTex\", someTexture);\n // 设置名为\"MVP\"的属性的输入,MVP是之前由开发者计算好的变换矩阵\n SetVertexShaderProperty(\"MVP\", MVP);\n // 关闭混合\n Disable(Blend);\n // 设置深度测试\n Enable(ZText);\n SetZTestFunction(LessOrEqual);\n // 其他设置\n …\n}\n// 每一帧迚行渲染\nvoid OnRendering() {\n // 调用渲染命令\n DrawCall();\n // 当涉及多种渲染设置时,我们可能还需要在这里改变各种渲染设置\n ...\n}\n```\n\nVertexShader.shader:\n\n```js\n// 输入:顶点位置、纹理、MVP变换矩阵\nin float3 vertexPosition;\nin sampler2D MainTex;\nin Matrix4x4 MVP;\n// 输出:顶点经过MVP变换后的位置\nout float4 position;\nvoid main() {\n // 使用MVP对模型顶点坐标迚行变换\n position = MVP * vertexPosition;\n}\n```\n\nFragmentShader.shader:\n\n```js\n// 输入:VertexShader输出的position、经过光栅化程序插值后的该片元对应的position\nin float4 position;\n// 输出:该片元的颜色值\nout float4 fragColor;\nvoid main() {\n // 将片元颜色设为白色\n fragColor = float4(1.0, 1.0, 1.0, 1.0);\n}\n```\n\n## 材质和 Unity Shader\n\n总体来说,在 Unity 中我们需要配合使用材质(Material)和 Unity Shader 才能达到需要的效果。一个最常见的流程是:\n\n- 创建一个材质\n\n- 创建一个 Unity Shader,并把它赋给上一步中创建的材质\n\n- 把材质赋给要渲染的对象\n\n- 在材质面板中调整 Unity Shader 的属性,如使用的纹理、漫反射系数\n\n## Unity 表面着色器\n\n表面着色器(Surface Shader)是 Unity 自己创造的一种着色器代码类型。它需要的代码量很少,Unity 在背后做了很多工作,但渲染的代价比较大。它在本质上和下面要讲到的顶点/片元着色器是一样的。也就是说,当给 Unity 提供一个表面着色器的时候,它在背后仍旧把它转换成对应的顶点/片元着色器。我们可以理解成,表面着色器是 Unity 对顶点/片元着色器的更高一层的抽象。它存在的价值在于,Unity 为我们处理了很多光照细节,使得我们不需要再操心这些“烦人的事情”。\n\n```js\nShader \"Custom/Simple Surface Shader\" {\n SubShader {\n Tags { \"RenderType\" = \"Opaque\" }\n CGPROGRAM\n #pragma surface surf Lambert\n struct Input {\n float4 color : COLOR;\n };\n void surf (Input IN, inout SurfaceOutput o) {\n o.Albedo = 1;\n }\n ENDCG\n }\n Fallback \"Diffuse\"\n}\n```\n\n## 顶点/片元着色器\n\n```js\nShader \"Custom/Simple VertexFragment Shader\" {\n SubShader {\n Pass {\n CGPROGRAM\n #pragma vertex vert\n #pragma fragment frag\n float4 vert(float4 v : POSITION) : SV_POSITION {\n return mul (UNITY_MATRIX_MVP, v);\n }\n fixed4 frag() : SV_Target {\n return fixed4(1.0,0.0,0.0,1.0);\n }\n ENDCG\n }\n }\n}\n```\n\n# 坐标系\n\nUnity 使用的是左手坐标系\n\n## 左右手\n\n![shader_8](/img/shader_8.png)\n\n![shader_9](/img/shader_9.png)\n\n## 向量(矢量)\n\n### 模\n\n矢量的模是一个标量,可以理解为是矢量在空间中的长度。它的表示符号通常是在矢量两旁分别加上一条垂直线\n\n![shader_10](/img/shader_10.png)\n\n### 单位矢量\n\n在很多情况下,我们只关心矢量的方向而不是模。例如,在计算光照模型时,我们往往需要得到顶点的法线方向和光源方向,此时我们不关心这些矢量有多长。在这些情况下,我们就需要计算单位矢量(unit vector)\n\n单位矢量指的是那些模为 1 的矢量。单位矢量也被称为被归一化的矢量(normalized vector)。对任何给定的非零矢量,把它转换成单位矢量的过程就被称为归一化\n\n![shader_11](/img/shader_11.png)\n\n### 点积\n\n一个向量在另一个向量方向上投影的长度,是一个标量\n\n公式:`a·b = |a||b|cosθ`\n\n### 向量积 ∧ ×\n\n> 向量积,数学中又称外积、叉积,物理中称矢积、叉乘\n\n几何意义,一个和已有两个向量都垂直的向量,法向量\n\n![shader_16](/img/shader_16.png)\n\n向量 a 和向量 b:\n\n![shader_12](/img/shader_12.png)\n\n叉乘公式为:\n\n![shader_13](/img/shader_13.png)\n\n其中:\n\n![shader_14](/img/shader_14.png)\n\n根据 i、j、k 间关系,有:\n\n![shader_15](/img/shader_15.png)\n\n例如\n\n(1,2,3)×(-2,-1,4) = (2 × 4 - 3 × -1, 3 × -2 - 1 × 4, 1 × -1 - 2 × -2) = (11,10,3)\n\n# 矩阵变换 Vec4\n\n由于 3×3 矩阵不能表示平移操作,我们就把其扩展到了 4×4 的矩阵\n\n## 平移矩阵\n\n点的 x、y、z 分量分别增加了一个位置偏移。在 3D 中的可视化效果是,把点(x, y, z)在空间中平移了(tx, ty, tz)个单位\n\n![shader_17](/img/shader_17.png)\n\n## 缩放矩阵\n\n## 旋转矩阵\n\n旋转操作需要指定一个旋转轴,这个旋转轴不一定是空间中的坐标轴,但本节所讲的旋转就是指绕着空间中的 x 轴、y 轴或 z 轴进行旋转\n\n如果我们需要把点绕着 x 轴旋转 θ 度\n\n![shader_18](/img/shader_18.png)\n\n绕 y 轴的旋转\n\n![shader_19](/img/shader_19.png)\n\n绕 z 轴的旋转\n\n![shader_20](/img/shader_20.png)\n\n## 复合变换\n\n先进行大小为(2, 2, 2)的缩放,再绕 y 轴旋转 30°,最后向 z 轴平移 4 个单位。\n\n> 矩阵乘法注意顺序\n\n![shader_21](/img/shader_21.png)\n\n> ?? 如果我们需要同时绕着 3 个轴进行旋转,是先绕 x 轴、再绕 y 轴最后绕 z 轴旋转还是按其他的旋转顺序呢?\n\n根据坐标系,需要调整轴的顺序\n\n![shader_22](/img/shader_22.png)\n\n## 法线变换\n\n在游戏中,模型的一个顶点往往会携带额外的信息,而顶点法线就是其中一种信息。当我们变换一个模型的时候,不仅需要变换它的顶点,还需要变换顶点法线,以便在后续处理(如片元着色器)中计算光照等。\n\n# 坐标空间\n\n事实上,在我们的生活中,我们也总是使用不同的坐标空间来交流。现在正在读这本书的你,很可能正坐在办公室或书房中。如果问你:“办公室的饮水机在哪里?”你大概会回答:“在办公室门的左方 3 米处。”这里,你很自然地使用了以门为原点的坐标空间。\n\n要想定义一个坐标空间,必须指明其原点位置和 3 个坐标轴的方向。而这些数值实际上是相对于另一个坐标空间的(读者需要记住,所有的都是相对的)。也就是说,坐标空间会形成一个层次结构——每个坐标空间都是另一个坐标空间的子空间,反过来说,每个空间都有一个父(parent)坐标空间。对坐标空间的变换实际上就是在父空间和子空间之间对点和矢量进行变换。\n\n## 模型空间\n\n每个模型都有自己独立的坐标空间,当它移动或旋转的时候,模型空间也会跟着它移动和旋转。\n\n> Unity 在模型空间中使用的是左手坐标系,因此在模型空间中,+x 轴、+y 轴、+z 轴分别对应的是模型的右、上和前向。\n\n![shader_24](/img/shader_24.png)\n\n## 世界空间\n\n世界空间(world space)是一个特殊的坐标系,因为它建立了我们所关心的最大的空间。一些读者可能会指出,空间可以是无限大的,怎么会有“最大”这一说呢?这里说的最大指的是一个宏观的概念,也就是说它是我们所关心的`最外层`的坐标空间。\n\n## 摄像机空间\n\n摄像机决定了我们渲染游戏所使用的视角。在观察空间中,摄像机位于原点\n\n> Unity 中观察空间的坐标轴选择是:+x 轴指向右方,+y 轴指向上方,而+z 轴指向的是摄像机的后方\n\nQ:模型空间和世界空间中+z 轴指的都是物体的前方,为什么这里不一样了呢?\n\nA:Unity 在模型空间和世界空间中选用的都是左手坐标系,而在观察空间中使用的是右手坐标系。这是符合 OpenGL 传统的,在这样的观察空间中,摄像机的正前方指向的是-z 轴方向。\n\n## 裁剪空间\n\n用于变换的矩阵叫做裁剪矩阵(clip matrix),也被称为投影矩阵(projection matrix)。位于这块空间内部的图元将会被保留,否则他剔除。由视锥体(view frustum)来决定。\n\n![shader_26](/img/shader_26.png)\n\n视锥体:指的是空间中的一块区域,这块区域决定了摄像机可以看到的空间。视锥体由六个平面包围而成,这些平面也被称为裁剪平面(clip planes)。视锥体有两种类型,这涉及两种投影类型:\n\n- 正交投影(orthographic projection):所有的网格大小都一样,而且平行线会一直保持平行\n- 透视投影(perspective projection):地板上的平行线并不会保持平行,离摄像机越近网格越大,离摄像机越远网格越小。\n\n![shader_25](/img/shader_25.png)\n\n> 追求真实感的 3D 游戏中我们往往会使用透视投影,而在一些 2D 游戏或渲染小地图等其他 HUD 元素时,我们会使用正交投影\n\n### 透视摄像机视锥体模型\n\n在 Unity 中,一个摄像机的横纵比由 Game 视图的横纵比和 Viewport Rect 中的 `W` 和 `H` 属性共同决定(实际上,Unity 允许我们在脚本里通过 `Camera.aspect` 进行更改,但这里不做讨论)\n\n![shader_27](/img/shader_27.png)\n\n### 正交摄像机视锥体模型\n\n通过 Camera 组件的 `Size` 属性来改变视锥体竖直方向上高度的一半,而 Clipping Planes 中的 Near 和 Far 参数可以控制视锥体的近裁剪平面和远裁剪平面距离摄像机的远近。\n\n![shader_28](/img/shader_28.png)\n\n## 屏幕空间\n\n> 在 Unity 中,从裁剪空间到屏幕空间的转换是由 Unity 帮我们完成的。\n\n屏幕空间是一个二维空间,因此,我们必须把顶点从裁剪空间投影到屏幕空间中,来生成对应的 2D 坐标。这个过程可以理解成有两个步骤:\n\n1. 标准齐次除法(透视除法):用齐次坐标系的 w 分量去除以 x、y、z 分量,在 OpenGL 中,得到的坐标叫做归一化的设备坐标(Normalized Device Coordinates, NDC)\n\n经过透视投影变换后的裁剪空间,经过齐次除法后会变换到一个立方体内。按照 OpenGL 的传统,这个立方体的 x、y、z 分量的范围都是[-1, 1]。但在 DirectX 这样的 API 中,z 分量的范围会是[0, 1]。而 Unity 选择了 OpenGL 这样的齐次裁剪空间,如图所示\n\n![shader_29](/img/shader_29.png)\n\n而对于正交投影来说,它的裁剪空间实际已经是一个立方体了,而且由于经过正交投影矩阵变换后的顶点的 w 分量是 1,因此齐次除法并不会对顶点的 x、y、z 坐标产生影响,如图所示\n\n![shader_30](/img/shader_30.png)\n\n2. 映射输出窗口的对应像素坐标\n\n## Unity 内置变换矩阵\n\n| 变量名 | 描述 |\n| ------------------ | ---------------------------------------------------------------------------------------------------- |\n| UNITY MATRIX MVP | 当前的模型·观察·投影矩阵,用于将顶点/方向矢量从模型空间变换到裁剪空间 |\n| UNITY MATRIX MV | 当前的模型·观察矩阵,用于将顶点/方向矢量从模型空间变换到观察空间 |\n| UNITY MATRIX V | 当前的观察矩阵,用于将顶点/方向矢量从世界空间变换到观察空间 |\n| UNITY MATRIX P | 当前的投影矩阵,用于将顶点/方向矢量从观察空间变换到裁剪空间 |\n| UNITY MATRIX VP | 当前的观察投影矩阵,用于将顶点/方向矢量从世界空间变换到裁剪空间 |\n| UNITY MATRIX T MV | UNITYMATRIX MV 的转置矩阵 |\n| UNITY MATRIX IT MV | UNITYMATRIX MV 的逆转置矩阵,用于将法线从模型空间变换到观察空间,也可用于得到 UNITYMATRIXMV 的逆矩阵 |\n| Object2World | 当前的模型矩阵,用于将顶点/方向矢量从模型空间变换到世界空间 |\n| World2Object | Object2World 的逆矩阵,用于将顶点/方向矢量从世界空间变换到模型空间 |\n\n\n","source":"_posts/front-end/Unity Shader入门精要.md","raw":"---\ntitle: Unity Shader入门精要\ncategories:\n - Front-End\nstatus: done\n---\n\n代码基于`c#`,书籍 `Unity Shader`入门精要\n\n# 什么是 OpenGL、DirectX\n\n用于渲染二维或三维图形。可以说,这些接口架起了上层应用程序和底层 GPU 的沟通桥梁。一个应用程序向这些接口发送渲染命令,而这些接口会依次向显卡驱动(Graphics Driver)发送渲染命令,这些显卡驱动是真正知道如何和 GPU 通信的角色,正是它们把 OpenGL 或者 DirectX 的函数调用翻译成了 GPU 能够听懂的语言,同时它们也负责把纹理等数据转换成 GPU 所支持的格式。一个比喻是,显卡驱动就是显卡的操作系统。\n\n![shader_7](/img/shader_7.png)\n\n# 什么是 HLSL、GLSL、CG\n\n如顶点着色器、片元着色器等。这些着色器的可编程性在于,我们可以使用一种特定的语言来编写程序,就好比我们可以用 C#来写游戏逻辑一样。\n\n着色语言是专门用于编写着色器的,常见的着色语言有\n\n- DirectX 的 HLSL(High Level Shading Language)\n\n- OpenGL 的 GLSL(OpenGL Shading Language)\n\n- NVIDIA 的 CG(C for Graphic)。\n\nHLSL、GLSL、CG 都是“高级(High-Level)”语言,但这种高级是相对于汇编语言来说的,而不是像 C#相对于 C 的高级那样。这些语言会被编译成与机器无关的汇编语言,也被称为中间语言(Intermediate Language, IL)。这些中间语言再交给显卡驱动来翻译成真正的机器语言,即 GPU 可以理解的语言。\n\n# GPU 流水线\n\n当 GPU 从 CPU 那里得到渲染命令后,就会进行一系列流水线操作,最终把图元渲染到屏幕上。\n\n![shader_1](/img/shader_1.png)\n\n- 顶点着色器(Vertex Shader)是完全可编程的,它通常用于实现顶点的空间变换、顶点着色等功能\n\n- 曲面细分着色器(Tessellation Shader)是一个可选的着色器,它用于细分图元\n\n- 几何着色器(Geometry Shader)同样是一个可选的着色器,它可以被用于执行逐图元(Per-Primitive)的着色操作,或者被用于产生更多的图元\n\n- 裁剪(Clipping),这一阶段的目的是将那些不在摄像机视野内的顶点裁剪掉,并剔除某些三角图元的面片。例如,我们可以使用自定义的裁剪平面来配置裁剪区域,也可以通过指令控制裁剪三角图元的正面还是背面。\n\n- 片元着色器(Fragment Shader),则是完全可编程的,它用于实现逐片元(Per-Fragment)的着色操作\n\n## 顶点着色器 Vertex Shader\n\nCPU 输入进来的每个顶点都会调用一次顶点着色器。顶点着色器本身不可以创建或者销毁任何顶点,而且无法得到顶点与顶点之间的关系。例如,我们无法得知两个顶点是否属于同一个三角网格。但正是因为这样的相互独立性,GPU 可以利用本身的特性并行化处理每一个顶点,这意味着这一阶段的处理速度会很快。\n\n顶点着色器需要完成的工作主要有:\n\n- 坐标变换:把顶点坐标转换到齐次裁剪坐标系下,接着通常再由硬件做透视除法后,最终得到归一化的设备坐标(NDC)\n\n![shader_2](/img/shader_2.png)\n\n> 在 DirectX 中,NDC 的 z 方向取值范围是[0,1],在 OpenGL 环境下是-1.0,DirectX 中是 0.0\n\n- 逐顶点光照\n\n## 裁剪\n\n只有在单位立方体的图元才需要被继续处\n\n![shader_3](/img/shader_3.png)\n\n## 屏幕映射\n\nOpenGL 和 DirectX 之间的差异问题。OpenGL 把屏幕的左下角当成最小的窗口坐标值,而 DirectX 则定义了屏幕的左上角为最小的窗口坐标值\n\n![shader_4](/img/shader_4.png)\n\n## 光栅阶段\n\n从上一个阶段输出的信息:屏幕坐标系下的顶点位置以及和它们相关的额外信息,如深度值(z 坐标)、法线方向、视角方向等。光栅化阶段有两个最重要的目标:计算每个图元覆盖了哪些像素,以及为这些像素计算它们的颜色。\n\n### 三角形设置\n\n如果要得到整个三角网格对像素的覆盖情况,我们就必须计算每条边上的像素坐标。为了能够计算边界像素的坐标信息,我们就需要得到三角形边界的表示方式。\n\n### 三角形遍历()\n\n1. 扫描变换:三角形遍历(Triangle Traversal)阶段将会检查每个像素是否被一个三角网格所覆盖。如果被覆盖的话,就会生成一个片元(fragment)、\n\n2. 使用三角网格 3 个顶点的顶点信息对整个覆盖区域的像素进行插值\n\n![shader_5](/img/shader_5.png)\n\n这一步的输出就是得到一个片元序列。需要注意的是,一个片元并不是真正意义上的像素,而是包含了很多状态的集合,这些状态用于计算每个像素的最终颜色。这些状态包括了(但不限于)它的屏幕坐标、深度信息,以及其他从几何阶段输出的顶点信息,例如法线、纹理坐标等。\n\n## 片元着色器 Fragment Shader\n\n前面的光栅化阶段实际上并不会影响屏幕上每个像素的颜色值,而是会产生一系列的数据信息,用来表述一个三角网格是怎样覆盖每个像素的。而每个片元就负责存储这样一系列数据。真正会对像素产生影响的阶段是下一个流水线阶段——逐片元操作\n\n> 在 DirectX 中,片元着色器被称为像素着色器(Pixel Shader)但片元着色器是一个更合适的名字,因为此时的片元并不是一个真正意义上的像素。\n\n![shader_6](/img/shader_6.png)\n\n## 逐片元操作\n\n这一阶段有几个主要任务:\n\n- 决定每个片元的可见性。这涉及了很多测试工作,例如深度测试、模板测试等。\n\n- 如果一个片元通过了所有的测试,就需要把这个片元的颜色值和已经存储在颜色缓冲区中的颜色进行合并,或者说是混合。\n\n> !! 对于不透明物体,开发者可以关闭混合(Blend)操作。这样片元着色器计算得到的颜色值就会直接覆盖掉颜色缓冲区中的像素值。但对于半透明物体,我们就需要使用混合操作来让这个物体看起来是透明的。\n\n## Draw Call\n\nCPU 调用图像编程接口,如 OpenGL 中的 `glDrawElements` 命令或者 DirectX 中的 `DrawIndexedPrimitive`命令,以命令 GPU 进行渲染的操作。\n\n### CPU 和 GPU 是如何实现并行工作的?\n\n如果没有流水线化,那么 CPU 需要等到 GPU 完成上一个渲染任务才能再次发送渲染命令。但这种方法显然会造成效率低下。解决方法就是使用一个命令缓冲区(Command Buffer)。\n\n由 CPU 向其中添加命令,而由 GPU 从中读取命令,添加和读取的过程是互相独立的。\n\n### 为什么 Draw Call 多了会影响帧率?\n\n每一个复制动作需要很多额外的操作,例如分配内存、创建各种元数据等。如你所见,这些操作将造成很多额外的性能开销,如果我们复制了很多小文件,那么这个开销将会很大\n\n### 如何减少 Draw Call?\n\n尽管减少 Draw Call 的方法有很多,但我们这里仅讨论使用批处理(Batching)的方法。我们讲过,提交大量很小的 Draw Call 会造成 CPU 的性能瓶颈,即 CPU 把时间都花费在准备 Draw Call 的工作上了。那么,一个很显然的优化想法就是把很多小的 DrawCall 合并成一个大的 Draw Call,这就是批处理的思想。\n\n在游戏开发过程中,为了减少 Draw Call 的开销,有两点需要注意。\n\n- 避免使用大量很小的网格。当不可避免地需要使用很小的网格结构时,考虑是否可以合并它们。\n\n- 避免使用过多的材质。尽量在不同的网格之间共用同一个材质。\n\n# 你明白什么是 Shader\n\n- GPU 流水线上一些可高度编程的阶段,而由着色器编译出来的最终代码是会在 GPU 上运行的(对于固定管线的渲染来说,着色器有时等同于一些特定的渲染设置)\n\n- 有一些特定类型的着色器,如顶点着色器、片元着色器等\n\n- 依靠着色器我们可以控制流水线中的渲染细节,例如用顶点着色器来进行顶点变换以及传递数据,用片元着色器来进行逐像素的渲染。\n\n# Unity Shader\n\n> !! Unity Shader ! = 真正的 Shader\n\nUnity Shader 实际上指的就是一个 ShaderLab 文件——硬盘上以.shader 作为文件后缀的一种文件,提供了一种让开发者同时控制渲染流水线中多个阶段的一种方式,不仅仅是提供 Shader 代码。\n\n作为开发者而言,我们绝大部分时候只需要和 Unity Shader 打交道,而不需要关心渲染引擎底层的实现细节\n\nUnity 编辑器会把这些 CG 片段编译成低级语言,如汇编语言等。通常,Unity 会自动把这些 CG 片段编译到所有相关平台(这里的平台是指不同的渲染平台,例如 Direct3D 9、OpenGL、Direct3D 11、OpenGL ES 等)上\n\n## 基础\n\n在没有 Unity 这类编辑器的情况下,如果我们想要对某个模型设置渲染状态,可能需要类似下面的代码:\n\n上述伪代码仅仅是简化后的版本, 当渲染的模型数目、需要调整的着色器属性不断增多时,上述过程将变得更加复杂和冗长。\n\n而且,当涉及透明物体等多物体的渲染时,如果没有编辑器的帮助,我们要非常小心如渲染顺序等问题。\n\n```js\n// 初始化渲染设置\nvoid Initialization() {\n // 从硬盘上加载顶点着色器的代码\n string vertexShaderCode = LoadShaderFromFile(VertexShader.shader);\n // 从硬盘上加载片元着色器的代码\n string fragmentShaderCode = LoadShaderFromFile(FragmentShader.shader);\n // 把顶点着色器加载到GPU中\n LoadVertexShaderFromString(vertexShaderCode);\n // 把片元着色器加载到GPU中\n LoadFragmentShaderFromString(fragmentShaderCode);\n // 设置名为\"vertexPosition\"的属性的输入,即模型顶点坐标\n SetVertexShaderProperty(\"vertexPosition\", vertices);\n // 设置名为\"MainTex\"的属性的输入,someTexture是某张已加载的纹理\n SetVertexShaderProperty(\"MainTex\", someTexture);\n // 设置名为\"MVP\"的属性的输入,MVP是之前由开发者计算好的变换矩阵\n SetVertexShaderProperty(\"MVP\", MVP);\n // 关闭混合\n Disable(Blend);\n // 设置深度测试\n Enable(ZText);\n SetZTestFunction(LessOrEqual);\n // 其他设置\n …\n}\n// 每一帧迚行渲染\nvoid OnRendering() {\n // 调用渲染命令\n DrawCall();\n // 当涉及多种渲染设置时,我们可能还需要在这里改变各种渲染设置\n ...\n}\n```\n\nVertexShader.shader:\n\n```js\n// 输入:顶点位置、纹理、MVP变换矩阵\nin float3 vertexPosition;\nin sampler2D MainTex;\nin Matrix4x4 MVP;\n// 输出:顶点经过MVP变换后的位置\nout float4 position;\nvoid main() {\n // 使用MVP对模型顶点坐标迚行变换\n position = MVP * vertexPosition;\n}\n```\n\nFragmentShader.shader:\n\n```js\n// 输入:VertexShader输出的position、经过光栅化程序插值后的该片元对应的position\nin float4 position;\n// 输出:该片元的颜色值\nout float4 fragColor;\nvoid main() {\n // 将片元颜色设为白色\n fragColor = float4(1.0, 1.0, 1.0, 1.0);\n}\n```\n\n## 材质和 Unity Shader\n\n总体来说,在 Unity 中我们需要配合使用材质(Material)和 Unity Shader 才能达到需要的效果。一个最常见的流程是:\n\n- 创建一个材质\n\n- 创建一个 Unity Shader,并把它赋给上一步中创建的材质\n\n- 把材质赋给要渲染的对象\n\n- 在材质面板中调整 Unity Shader 的属性,如使用的纹理、漫反射系数\n\n## Unity 表面着色器\n\n表面着色器(Surface Shader)是 Unity 自己创造的一种着色器代码类型。它需要的代码量很少,Unity 在背后做了很多工作,但渲染的代价比较大。它在本质上和下面要讲到的顶点/片元着色器是一样的。也就是说,当给 Unity 提供一个表面着色器的时候,它在背后仍旧把它转换成对应的顶点/片元着色器。我们可以理解成,表面着色器是 Unity 对顶点/片元着色器的更高一层的抽象。它存在的价值在于,Unity 为我们处理了很多光照细节,使得我们不需要再操心这些“烦人的事情”。\n\n```js\nShader \"Custom/Simple Surface Shader\" {\n SubShader {\n Tags { \"RenderType\" = \"Opaque\" }\n CGPROGRAM\n #pragma surface surf Lambert\n struct Input {\n float4 color : COLOR;\n };\n void surf (Input IN, inout SurfaceOutput o) {\n o.Albedo = 1;\n }\n ENDCG\n }\n Fallback \"Diffuse\"\n}\n```\n\n## 顶点/片元着色器\n\n```js\nShader \"Custom/Simple VertexFragment Shader\" {\n SubShader {\n Pass {\n CGPROGRAM\n #pragma vertex vert\n #pragma fragment frag\n float4 vert(float4 v : POSITION) : SV_POSITION {\n return mul (UNITY_MATRIX_MVP, v);\n }\n fixed4 frag() : SV_Target {\n return fixed4(1.0,0.0,0.0,1.0);\n }\n ENDCG\n }\n }\n}\n```\n\n# 坐标系\n\nUnity 使用的是左手坐标系\n\n## 左右手\n\n![shader_8](/img/shader_8.png)\n\n![shader_9](/img/shader_9.png)\n\n## 向量(矢量)\n\n### 模\n\n矢量的模是一个标量,可以理解为是矢量在空间中的长度。它的表示符号通常是在矢量两旁分别加上一条垂直线\n\n![shader_10](/img/shader_10.png)\n\n### 单位矢量\n\n在很多情况下,我们只关心矢量的方向而不是模。例如,在计算光照模型时,我们往往需要得到顶点的法线方向和光源方向,此时我们不关心这些矢量有多长。在这些情况下,我们就需要计算单位矢量(unit vector)\n\n单位矢量指的是那些模为 1 的矢量。单位矢量也被称为被归一化的矢量(normalized vector)。对任何给定的非零矢量,把它转换成单位矢量的过程就被称为归一化\n\n![shader_11](/img/shader_11.png)\n\n### 点积\n\n一个向量在另一个向量方向上投影的长度,是一个标量\n\n公式:`a·b = |a||b|cosθ`\n\n### 向量积 ∧ ×\n\n> 向量积,数学中又称外积、叉积,物理中称矢积、叉乘\n\n几何意义,一个和已有两个向量都垂直的向量,法向量\n\n![shader_16](/img/shader_16.png)\n\n向量 a 和向量 b:\n\n![shader_12](/img/shader_12.png)\n\n叉乘公式为:\n\n![shader_13](/img/shader_13.png)\n\n其中:\n\n![shader_14](/img/shader_14.png)\n\n根据 i、j、k 间关系,有:\n\n![shader_15](/img/shader_15.png)\n\n例如\n\n(1,2,3)×(-2,-1,4) = (2 × 4 - 3 × -1, 3 × -2 - 1 × 4, 1 × -1 - 2 × -2) = (11,10,3)\n\n# 矩阵变换 Vec4\n\n由于 3×3 矩阵不能表示平移操作,我们就把其扩展到了 4×4 的矩阵\n\n## 平移矩阵\n\n点的 x、y、z 分量分别增加了一个位置偏移。在 3D 中的可视化效果是,把点(x, y, z)在空间中平移了(tx, ty, tz)个单位\n\n![shader_17](/img/shader_17.png)\n\n## 缩放矩阵\n\n## 旋转矩阵\n\n旋转操作需要指定一个旋转轴,这个旋转轴不一定是空间中的坐标轴,但本节所讲的旋转就是指绕着空间中的 x 轴、y 轴或 z 轴进行旋转\n\n如果我们需要把点绕着 x 轴旋转 θ 度\n\n![shader_18](/img/shader_18.png)\n\n绕 y 轴的旋转\n\n![shader_19](/img/shader_19.png)\n\n绕 z 轴的旋转\n\n![shader_20](/img/shader_20.png)\n\n## 复合变换\n\n先进行大小为(2, 2, 2)的缩放,再绕 y 轴旋转 30°,最后向 z 轴平移 4 个单位。\n\n> 矩阵乘法注意顺序\n\n![shader_21](/img/shader_21.png)\n\n> ?? 如果我们需要同时绕着 3 个轴进行旋转,是先绕 x 轴、再绕 y 轴最后绕 z 轴旋转还是按其他的旋转顺序呢?\n\n根据坐标系,需要调整轴的顺序\n\n![shader_22](/img/shader_22.png)\n\n## 法线变换\n\n在游戏中,模型的一个顶点往往会携带额外的信息,而顶点法线就是其中一种信息。当我们变换一个模型的时候,不仅需要变换它的顶点,还需要变换顶点法线,以便在后续处理(如片元着色器)中计算光照等。\n\n# 坐标空间\n\n事实上,在我们的生活中,我们也总是使用不同的坐标空间来交流。现在正在读这本书的你,很可能正坐在办公室或书房中。如果问你:“办公室的饮水机在哪里?”你大概会回答:“在办公室门的左方 3 米处。”这里,你很自然地使用了以门为原点的坐标空间。\n\n要想定义一个坐标空间,必须指明其原点位置和 3 个坐标轴的方向。而这些数值实际上是相对于另一个坐标空间的(读者需要记住,所有的都是相对的)。也就是说,坐标空间会形成一个层次结构——每个坐标空间都是另一个坐标空间的子空间,反过来说,每个空间都有一个父(parent)坐标空间。对坐标空间的变换实际上就是在父空间和子空间之间对点和矢量进行变换。\n\n## 模型空间\n\n每个模型都有自己独立的坐标空间,当它移动或旋转的时候,模型空间也会跟着它移动和旋转。\n\n> Unity 在模型空间中使用的是左手坐标系,因此在模型空间中,+x 轴、+y 轴、+z 轴分别对应的是模型的右、上和前向。\n\n![shader_24](/img/shader_24.png)\n\n## 世界空间\n\n世界空间(world space)是一个特殊的坐标系,因为它建立了我们所关心的最大的空间。一些读者可能会指出,空间可以是无限大的,怎么会有“最大”这一说呢?这里说的最大指的是一个宏观的概念,也就是说它是我们所关心的`最外层`的坐标空间。\n\n## 摄像机空间\n\n摄像机决定了我们渲染游戏所使用的视角。在观察空间中,摄像机位于原点\n\n> Unity 中观察空间的坐标轴选择是:+x 轴指向右方,+y 轴指向上方,而+z 轴指向的是摄像机的后方\n\nQ:模型空间和世界空间中+z 轴指的都是物体的前方,为什么这里不一样了呢?\n\nA:Unity 在模型空间和世界空间中选用的都是左手坐标系,而在观察空间中使用的是右手坐标系。这是符合 OpenGL 传统的,在这样的观察空间中,摄像机的正前方指向的是-z 轴方向。\n\n## 裁剪空间\n\n用于变换的矩阵叫做裁剪矩阵(clip matrix),也被称为投影矩阵(projection matrix)。位于这块空间内部的图元将会被保留,否则他剔除。由视锥体(view frustum)来决定。\n\n![shader_26](/img/shader_26.png)\n\n视锥体:指的是空间中的一块区域,这块区域决定了摄像机可以看到的空间。视锥体由六个平面包围而成,这些平面也被称为裁剪平面(clip planes)。视锥体有两种类型,这涉及两种投影类型:\n\n- 正交投影(orthographic projection):所有的网格大小都一样,而且平行线会一直保持平行\n- 透视投影(perspective projection):地板上的平行线并不会保持平行,离摄像机越近网格越大,离摄像机越远网格越小。\n\n![shader_25](/img/shader_25.png)\n\n> 追求真实感的 3D 游戏中我们往往会使用透视投影,而在一些 2D 游戏或渲染小地图等其他 HUD 元素时,我们会使用正交投影\n\n### 透视摄像机视锥体模型\n\n在 Unity 中,一个摄像机的横纵比由 Game 视图的横纵比和 Viewport Rect 中的 `W` 和 `H` 属性共同决定(实际上,Unity 允许我们在脚本里通过 `Camera.aspect` 进行更改,但这里不做讨论)\n\n![shader_27](/img/shader_27.png)\n\n### 正交摄像机视锥体模型\n\n通过 Camera 组件的 `Size` 属性来改变视锥体竖直方向上高度的一半,而 Clipping Planes 中的 Near 和 Far 参数可以控制视锥体的近裁剪平面和远裁剪平面距离摄像机的远近。\n\n![shader_28](/img/shader_28.png)\n\n## 屏幕空间\n\n> 在 Unity 中,从裁剪空间到屏幕空间的转换是由 Unity 帮我们完成的。\n\n屏幕空间是一个二维空间,因此,我们必须把顶点从裁剪空间投影到屏幕空间中,来生成对应的 2D 坐标。这个过程可以理解成有两个步骤:\n\n1. 标准齐次除法(透视除法):用齐次坐标系的 w 分量去除以 x、y、z 分量,在 OpenGL 中,得到的坐标叫做归一化的设备坐标(Normalized Device Coordinates, NDC)\n\n经过透视投影变换后的裁剪空间,经过齐次除法后会变换到一个立方体内。按照 OpenGL 的传统,这个立方体的 x、y、z 分量的范围都是[-1, 1]。但在 DirectX 这样的 API 中,z 分量的范围会是[0, 1]。而 Unity 选择了 OpenGL 这样的齐次裁剪空间,如图所示\n\n![shader_29](/img/shader_29.png)\n\n而对于正交投影来说,它的裁剪空间实际已经是一个立方体了,而且由于经过正交投影矩阵变换后的顶点的 w 分量是 1,因此齐次除法并不会对顶点的 x、y、z 坐标产生影响,如图所示\n\n![shader_30](/img/shader_30.png)\n\n2. 映射输出窗口的对应像素坐标\n\n## Unity 内置变换矩阵\n\n| 变量名 | 描述 |\n| ------------------ | ---------------------------------------------------------------------------------------------------- |\n| UNITY MATRIX MVP | 当前的模型·观察·投影矩阵,用于将顶点/方向矢量从模型空间变换到裁剪空间 |\n| UNITY MATRIX MV | 当前的模型·观察矩阵,用于将顶点/方向矢量从模型空间变换到观察空间 |\n| UNITY MATRIX V | 当前的观察矩阵,用于将顶点/方向矢量从世界空间变换到观察空间 |\n| UNITY MATRIX P | 当前的投影矩阵,用于将顶点/方向矢量从观察空间变换到裁剪空间 |\n| UNITY MATRIX VP | 当前的观察投影矩阵,用于将顶点/方向矢量从世界空间变换到裁剪空间 |\n| UNITY MATRIX T MV | UNITYMATRIX MV 的转置矩阵 |\n| UNITY MATRIX IT MV | UNITYMATRIX MV 的逆转置矩阵,用于将法线从模型空间变换到观察空间,也可用于得到 UNITYMATRIXMV 的逆矩阵 |\n| Object2World | 当前的模型矩阵,用于将顶点/方向矢量从模型空间变换到世界空间 |\n| World2Object | Object2World 的逆矩阵,用于将顶点/方向矢量从世界空间变换到模型空间 |\n\n\n","slug":"front-end/Unity Shader入门精要","published":1,"date":"2023-11-06T08:00:24.546Z","updated":"2023-11-06T08:08:10.905Z","comments":1,"layout":"post","photos":[],"link":"","_id":"clp7x194x0009v3z338b7g5v8","content":"

代码基于c#,书籍 Unity Shader入门精要

\n

什么是 OpenGL、DirectX

用于渲染二维或三维图形。可以说,这些接口架起了上层应用程序和底层 GPU 的沟通桥梁。一个应用程序向这些接口发送渲染命令,而这些接口会依次向显卡驱动(Graphics Driver)发送渲染命令,这些显卡驱动是真正知道如何和 GPU 通信的角色,正是它们把 OpenGL 或者 DirectX 的函数调用翻译成了 GPU 能够听懂的语言,同时它们也负责把纹理等数据转换成 GPU 所支持的格式。一个比喻是,显卡驱动就是显卡的操作系统。

\n

\"shader_7\"

\n

什么是 HLSL、GLSL、CG

如顶点着色器、片元着色器等。这些着色器的可编程性在于,我们可以使用一种特定的语言来编写程序,就好比我们可以用 C#来写游戏逻辑一样。

\n

着色语言是专门用于编写着色器的,常见的着色语言有

\n
    \n
  • DirectX 的 HLSL(High Level Shading Language)

    \n
  • \n
  • OpenGL 的 GLSL(OpenGL Shading Language)

    \n
  • \n
  • NVIDIA 的 CG(C for Graphic)。

    \n
  • \n
\n

HLSL、GLSL、CG 都是“高级(High-Level)”语言,但这种高级是相对于汇编语言来说的,而不是像 C#相对于 C 的高级那样。这些语言会被编译成与机器无关的汇编语言,也被称为中间语言(Intermediate Language, IL)。这些中间语言再交给显卡驱动来翻译成真正的机器语言,即 GPU 可以理解的语言。

\n

GPU 流水线

当 GPU 从 CPU 那里得到渲染命令后,就会进行一系列流水线操作,最终把图元渲染到屏幕上。

\n

\"shader_1\"

\n
    \n
  • 顶点着色器(Vertex Shader)是完全可编程的,它通常用于实现顶点的空间变换、顶点着色等功能

    \n
  • \n
  • 曲面细分着色器(Tessellation Shader)是一个可选的着色器,它用于细分图元

    \n
  • \n
  • 几何着色器(Geometry Shader)同样是一个可选的着色器,它可以被用于执行逐图元(Per-Primitive)的着色操作,或者被用于产生更多的图元

    \n
  • \n
  • 裁剪(Clipping),这一阶段的目的是将那些不在摄像机视野内的顶点裁剪掉,并剔除某些三角图元的面片。例如,我们可以使用自定义的裁剪平面来配置裁剪区域,也可以通过指令控制裁剪三角图元的正面还是背面。

    \n
  • \n
  • 片元着色器(Fragment Shader),则是完全可编程的,它用于实现逐片元(Per-Fragment)的着色操作

    \n
  • \n
\n

顶点着色器 Vertex Shader

CPU 输入进来的每个顶点都会调用一次顶点着色器。顶点着色器本身不可以创建或者销毁任何顶点,而且无法得到顶点与顶点之间的关系。例如,我们无法得知两个顶点是否属于同一个三角网格。但正是因为这样的相互独立性,GPU 可以利用本身的特性并行化处理每一个顶点,这意味着这一阶段的处理速度会很快。

\n

顶点着色器需要完成的工作主要有:

\n
    \n
  • 坐标变换:把顶点坐标转换到齐次裁剪坐标系下,接着通常再由硬件做透视除法后,最终得到归一化的设备坐标(NDC)
  • \n
\n

\"shader_2\"

\n
\n

在 DirectX 中,NDC 的 z 方向取值范围是[0,1],在 OpenGL 环境下是-1.0,DirectX 中是 0.0

\n
\n
    \n
  • 逐顶点光照
  • \n
\n

裁剪

只有在单位立方体的图元才需要被继续处

\n

\"shader_3\"

\n

屏幕映射

OpenGL 和 DirectX 之间的差异问题。OpenGL 把屏幕的左下角当成最小的窗口坐标值,而 DirectX 则定义了屏幕的左上角为最小的窗口坐标值

\n

\"shader_4\"

\n

光栅阶段

从上一个阶段输出的信息:屏幕坐标系下的顶点位置以及和它们相关的额外信息,如深度值(z 坐标)、法线方向、视角方向等。光栅化阶段有两个最重要的目标:计算每个图元覆盖了哪些像素,以及为这些像素计算它们的颜色。

\n

三角形设置

如果要得到整个三角网格对像素的覆盖情况,我们就必须计算每条边上的像素坐标。为了能够计算边界像素的坐标信息,我们就需要得到三角形边界的表示方式。

\n

三角形遍历()

    \n
  1. 扫描变换:三角形遍历(Triangle Traversal)阶段将会检查每个像素是否被一个三角网格所覆盖。如果被覆盖的话,就会生成一个片元(fragment)、

    \n
  2. \n
  3. 使用三角网格 3 个顶点的顶点信息对整个覆盖区域的像素进行插值

    \n
  4. \n
\n

\"shader_5\"

\n

这一步的输出就是得到一个片元序列。需要注意的是,一个片元并不是真正意义上的像素,而是包含了很多状态的集合,这些状态用于计算每个像素的最终颜色。这些状态包括了(但不限于)它的屏幕坐标、深度信息,以及其他从几何阶段输出的顶点信息,例如法线、纹理坐标等。

\n

片元着色器 Fragment Shader

前面的光栅化阶段实际上并不会影响屏幕上每个像素的颜色值,而是会产生一系列的数据信息,用来表述一个三角网格是怎样覆盖每个像素的。而每个片元就负责存储这样一系列数据。真正会对像素产生影响的阶段是下一个流水线阶段——逐片元操作

\n
\n

在 DirectX 中,片元着色器被称为像素着色器(Pixel Shader)但片元着色器是一个更合适的名字,因为此时的片元并不是一个真正意义上的像素。

\n
\n

\"shader_6\"

\n

逐片元操作

这一阶段有几个主要任务:

\n
    \n
  • 决定每个片元的可见性。这涉及了很多测试工作,例如深度测试、模板测试等。

    \n
  • \n
  • 如果一个片元通过了所有的测试,就需要把这个片元的颜色值和已经存储在颜色缓冲区中的颜色进行合并,或者说是混合。

    \n
  • \n
\n
\n

!! 对于不透明物体,开发者可以关闭混合(Blend)操作。这样片元着色器计算得到的颜色值就会直接覆盖掉颜色缓冲区中的像素值。但对于半透明物体,我们就需要使用混合操作来让这个物体看起来是透明的。

\n
\n

Draw Call

CPU 调用图像编程接口,如 OpenGL 中的 glDrawElements 命令或者 DirectX 中的 DrawIndexedPrimitive命令,以命令 GPU 进行渲染的操作。

\n

CPU 和 GPU 是如何实现并行工作的?

如果没有流水线化,那么 CPU 需要等到 GPU 完成上一个渲染任务才能再次发送渲染命令。但这种方法显然会造成效率低下。解决方法就是使用一个命令缓冲区(Command Buffer)。

\n

由 CPU 向其中添加命令,而由 GPU 从中读取命令,添加和读取的过程是互相独立的。

\n

为什么 Draw Call 多了会影响帧率?

每一个复制动作需要很多额外的操作,例如分配内存、创建各种元数据等。如你所见,这些操作将造成很多额外的性能开销,如果我们复制了很多小文件,那么这个开销将会很大

\n

如何减少 Draw Call?

尽管减少 Draw Call 的方法有很多,但我们这里仅讨论使用批处理(Batching)的方法。我们讲过,提交大量很小的 Draw Call 会造成 CPU 的性能瓶颈,即 CPU 把时间都花费在准备 Draw Call 的工作上了。那么,一个很显然的优化想法就是把很多小的 DrawCall 合并成一个大的 Draw Call,这就是批处理的思想。

\n

在游戏开发过程中,为了减少 Draw Call 的开销,有两点需要注意。

\n
    \n
  • 避免使用大量很小的网格。当不可避免地需要使用很小的网格结构时,考虑是否可以合并它们。

    \n
  • \n
  • 避免使用过多的材质。尽量在不同的网格之间共用同一个材质。

    \n
  • \n
\n

你明白什么是 Shader

    \n
  • GPU 流水线上一些可高度编程的阶段,而由着色器编译出来的最终代码是会在 GPU 上运行的(对于固定管线的渲染来说,着色器有时等同于一些特定的渲染设置)

    \n
  • \n
  • 有一些特定类型的着色器,如顶点着色器、片元着色器等

    \n
  • \n
  • 依靠着色器我们可以控制流水线中的渲染细节,例如用顶点着色器来进行顶点变换以及传递数据,用片元着色器来进行逐像素的渲染。

    \n
  • \n
\n

Unity Shader

\n

!! Unity Shader ! = 真正的 Shader

\n
\n

Unity Shader 实际上指的就是一个 ShaderLab 文件——硬盘上以.shader 作为文件后缀的一种文件,提供了一种让开发者同时控制渲染流水线中多个阶段的一种方式,不仅仅是提供 Shader 代码。

\n

作为开发者而言,我们绝大部分时候只需要和 Unity Shader 打交道,而不需要关心渲染引擎底层的实现细节

\n

Unity 编辑器会把这些 CG 片段编译成低级语言,如汇编语言等。通常,Unity 会自动把这些 CG 片段编译到所有相关平台(这里的平台是指不同的渲染平台,例如 Direct3D 9、OpenGL、Direct3D 11、OpenGL ES 等)上

\n

基础

在没有 Unity 这类编辑器的情况下,如果我们想要对某个模型设置渲染状态,可能需要类似下面的代码:

\n

上述伪代码仅仅是简化后的版本, 当渲染的模型数目、需要调整的着色器属性不断增多时,上述过程将变得更加复杂和冗长。

\n

而且,当涉及透明物体等多物体的渲染时,如果没有编辑器的帮助,我们要非常小心如渲染顺序等问题。

\n
// 初始化渲染设置\nvoid  Initialization()  {\n    // 从硬盘上加载顶点着色器的代码\n    string  vertexShaderCode  =  LoadShaderFromFile(VertexShader.shader);\n    // 从硬盘上加载片元着色器的代码\n    string  fragmentShaderCode  =  LoadShaderFromFile(FragmentShader.shader);\n    // 把顶点着色器加载到GPU中\n    LoadVertexShaderFromString(vertexShaderCode);\n    // 把片元着色器加载到GPU中\n    LoadFragmentShaderFromString(fragmentShaderCode);\n    // 设置名为\"vertexPosition\"的属性的输入,即模型顶点坐标\n    SetVertexShaderProperty(\"vertexPosition\",  vertices);\n    // 设置名为\"MainTex\"的属性的输入,someTexture是某张已加载的纹理\n    SetVertexShaderProperty(\"MainTex\",  someTexture);\n    // 设置名为\"MVP\"的属性的输入,MVP是之前由开发者计算好的变换矩阵\n    SetVertexShaderProperty(\"MVP\",  MVP);\n    // 关闭混合\n    Disable(Blend);\n    // 设置深度测试\n    Enable(ZText);\n    SetZTestFunction(LessOrEqual);\n    // 其他设置\n    …\n}\n// 每一帧迚行渲染\nvoid  OnRendering()  {\n    // 调用渲染命令\n    DrawCall();\n    // 当涉及多种渲染设置时,我们可能还需要在这里改变各种渲染设置\n    ...\n}
\n\n

VertexShader.shader:

\n
// 输入:顶点位置、纹理、MVP变换矩阵\nin  float3  vertexPosition;\nin  sampler2D  MainTex;\nin  Matrix4x4  MVP;\n// 输出:顶点经过MVP变换后的位置\nout  float4  position;\nvoid  main()  {\n    // 使用MVP对模型顶点坐标迚行变换\n    position  =  MVP  *  vertexPosition;\n}
\n\n

FragmentShader.shader:

\n
// 输入:VertexShader输出的position、经过光栅化程序插值后的该片元对应的position\nin  float4  position;\n// 输出:该片元的颜色值\nout  float4  fragColor;\nvoid  main()  {\n    // 将片元颜色设为白色\n    fragColor  =  float4(1.0,  1.0,  1.0,  1.0);\n}
\n\n

材质和 Unity Shader

总体来说,在 Unity 中我们需要配合使用材质(Material)和 Unity Shader 才能达到需要的效果。一个最常见的流程是:

\n
    \n
  • 创建一个材质

    \n
  • \n
  • 创建一个 Unity Shader,并把它赋给上一步中创建的材质

    \n
  • \n
  • 把材质赋给要渲染的对象

    \n
  • \n
  • 在材质面板中调整 Unity Shader 的属性,如使用的纹理、漫反射系数

    \n
  • \n
\n

Unity 表面着色器

表面着色器(Surface Shader)是 Unity 自己创造的一种着色器代码类型。它需要的代码量很少,Unity 在背后做了很多工作,但渲染的代价比较大。它在本质上和下面要讲到的顶点/片元着色器是一样的。也就是说,当给 Unity 提供一个表面着色器的时候,它在背后仍旧把它转换成对应的顶点/片元着色器。我们可以理解成,表面着色器是 Unity 对顶点/片元着色器的更高一层的抽象。它存在的价值在于,Unity 为我们处理了很多光照细节,使得我们不需要再操心这些“烦人的事情”。

\n
Shader  \"Custom/Simple  Surface  Shader\"  {\n    SubShader  {\n      Tags  {  \"RenderType\"  =  \"Opaque\"  }\n      CGPROGRAM\n      #pragma  surface  surf  Lambert\n      struct  Input  {\n          float4  color  :  COLOR;\n      };\n      void  surf  (Input  IN,  inout  SurfaceOutput  o)  {\n          o.Albedo  =  1;\n      }\n      ENDCG\n    }\n    Fallback  \"Diffuse\"\n}
\n\n

顶点/片元着色器

Shader  \"Custom/Simple  VertexFragment  Shader\"  {\n    SubShader  {\n      Pass  {\n          CGPROGRAM\n          #pragma  vertex  vert\n          #pragma  fragment  frag\n          float4  vert(float4  v  :  POSITION)  :  SV_POSITION  {\n              return  mul  (UNITY_MATRIX_MVP,  v);\n          }\n          fixed4  frag()  :  SV_Target  {\n              return  fixed4(1.0,0.0,0.0,1.0);\n          }\n          ENDCG\n      }\n    }\n}
\n\n

坐标系

Unity 使用的是左手坐标系

\n

左右手

\"shader_8\"

\n

\"shader_9\"

\n

向量(矢量)

矢量的模是一个标量,可以理解为是矢量在空间中的长度。它的表示符号通常是在矢量两旁分别加上一条垂直线

\n

\"shader_10\"

\n

单位矢量

在很多情况下,我们只关心矢量的方向而不是模。例如,在计算光照模型时,我们往往需要得到顶点的法线方向和光源方向,此时我们不关心这些矢量有多长。在这些情况下,我们就需要计算单位矢量(unit vector)

\n

单位矢量指的是那些模为 1 的矢量。单位矢量也被称为被归一化的矢量(normalized vector)。对任何给定的非零矢量,把它转换成单位矢量的过程就被称为归一化

\n

\"shader_11\"

\n

点积

一个向量在另一个向量方向上投影的长度,是一个标量

\n

公式:a·b = |a||b|cosθ

\n

向量积 ∧ ×

\n

向量积,数学中又称外积、叉积,物理中称矢积、叉乘

\n
\n

几何意义,一个和已有两个向量都垂直的向量,法向量

\n

\"shader_16\"

\n

向量 a 和向量 b:

\n

\"shader_12\"

\n

叉乘公式为:

\n

\"shader_13\"

\n

其中:

\n

\"shader_14\"

\n

根据 i、j、k 间关系,有:

\n

\"shader_15\"

\n

例如

\n

(1,2,3)×(-2,-1,4) = (2 × 4 - 3 × -1, 3 × -2 - 1 × 4, 1 × -1 - 2 × -2) = (11,10,3)

\n

矩阵变换 Vec4

由于 3×3 矩阵不能表示平移操作,我们就把其扩展到了 4×4 的矩阵

\n

平移矩阵

点的 x、y、z 分量分别增加了一个位置偏移。在 3D 中的可视化效果是,把点(x, y, z)在空间中平移了(tx, ty, tz)个单位

\n

\"shader_17\"

\n

缩放矩阵

旋转矩阵

旋转操作需要指定一个旋转轴,这个旋转轴不一定是空间中的坐标轴,但本节所讲的旋转就是指绕着空间中的 x 轴、y 轴或 z 轴进行旋转

\n

如果我们需要把点绕着 x 轴旋转 θ 度

\n

\"shader_18\"

\n

绕 y 轴的旋转

\n

\"shader_19\"

\n

绕 z 轴的旋转

\n

\"shader_20\"

\n

复合变换

先进行大小为(2, 2, 2)的缩放,再绕 y 轴旋转 30°,最后向 z 轴平移 4 个单位。

\n
\n

矩阵乘法注意顺序

\n
\n

\"shader_21\"

\n
\n

?? 如果我们需要同时绕着 3 个轴进行旋转,是先绕 x 轴、再绕 y 轴最后绕 z 轴旋转还是按其他的旋转顺序呢?

\n
\n

根据坐标系,需要调整轴的顺序

\n

\"shader_22\"

\n

法线变换

在游戏中,模型的一个顶点往往会携带额外的信息,而顶点法线就是其中一种信息。当我们变换一个模型的时候,不仅需要变换它的顶点,还需要变换顶点法线,以便在后续处理(如片元着色器)中计算光照等。

\n

坐标空间

事实上,在我们的生活中,我们也总是使用不同的坐标空间来交流。现在正在读这本书的你,很可能正坐在办公室或书房中。如果问你:“办公室的饮水机在哪里?”你大概会回答:“在办公室门的左方 3 米处。”这里,你很自然地使用了以门为原点的坐标空间。

\n

要想定义一个坐标空间,必须指明其原点位置和 3 个坐标轴的方向。而这些数值实际上是相对于另一个坐标空间的(读者需要记住,所有的都是相对的)。也就是说,坐标空间会形成一个层次结构——每个坐标空间都是另一个坐标空间的子空间,反过来说,每个空间都有一个父(parent)坐标空间。对坐标空间的变换实际上就是在父空间和子空间之间对点和矢量进行变换。

\n

模型空间

每个模型都有自己独立的坐标空间,当它移动或旋转的时候,模型空间也会跟着它移动和旋转。

\n
\n

Unity 在模型空间中使用的是左手坐标系,因此在模型空间中,+x 轴、+y 轴、+z 轴分别对应的是模型的右、上和前向。

\n
\n

\"shader_24\"

\n

世界空间

世界空间(world space)是一个特殊的坐标系,因为它建立了我们所关心的最大的空间。一些读者可能会指出,空间可以是无限大的,怎么会有“最大”这一说呢?这里说的最大指的是一个宏观的概念,也就是说它是我们所关心的最外层的坐标空间。

\n

摄像机空间

摄像机决定了我们渲染游戏所使用的视角。在观察空间中,摄像机位于原点

\n
\n

Unity 中观察空间的坐标轴选择是:+x 轴指向右方,+y 轴指向上方,而+z 轴指向的是摄像机的后方

\n
\n

Q:模型空间和世界空间中+z 轴指的都是物体的前方,为什么这里不一样了呢?

\n

A:Unity 在模型空间和世界空间中选用的都是左手坐标系,而在观察空间中使用的是右手坐标系。这是符合 OpenGL 传统的,在这样的观察空间中,摄像机的正前方指向的是-z 轴方向。

\n

裁剪空间

用于变换的矩阵叫做裁剪矩阵(clip matrix),也被称为投影矩阵(projection matrix)。位于这块空间内部的图元将会被保留,否则他剔除。由视锥体(view frustum)来决定。

\n

\"shader_26\"

\n

视锥体:指的是空间中的一块区域,这块区域决定了摄像机可以看到的空间。视锥体由六个平面包围而成,这些平面也被称为裁剪平面(clip planes)。视锥体有两种类型,这涉及两种投影类型:

\n
    \n
  • 正交投影(orthographic projection):所有的网格大小都一样,而且平行线会一直保持平行
  • \n
  • 透视投影(perspective projection):地板上的平行线并不会保持平行,离摄像机越近网格越大,离摄像机越远网格越小。
  • \n
\n

\"shader_25\"

\n
\n

追求真实感的 3D 游戏中我们往往会使用透视投影,而在一些 2D 游戏或渲染小地图等其他 HUD 元素时,我们会使用正交投影

\n
\n

透视摄像机视锥体模型

在 Unity 中,一个摄像机的横纵比由 Game 视图的横纵比和 Viewport Rect 中的 WH 属性共同决定(实际上,Unity 允许我们在脚本里通过 Camera.aspect 进行更改,但这里不做讨论)

\n

\"shader_27\"

\n

正交摄像机视锥体模型

通过 Camera 组件的 Size 属性来改变视锥体竖直方向上高度的一半,而 Clipping Planes 中的 Near 和 Far 参数可以控制视锥体的近裁剪平面和远裁剪平面距离摄像机的远近。

\n

\"shader_28\"

\n

屏幕空间

\n

在 Unity 中,从裁剪空间到屏幕空间的转换是由 Unity 帮我们完成的。

\n
\n

屏幕空间是一个二维空间,因此,我们必须把顶点从裁剪空间投影到屏幕空间中,来生成对应的 2D 坐标。这个过程可以理解成有两个步骤:

\n
    \n
  1. 标准齐次除法(透视除法):用齐次坐标系的 w 分量去除以 x、y、z 分量,在 OpenGL 中,得到的坐标叫做归一化的设备坐标(Normalized Device Coordinates, NDC)
  2. \n
\n

经过透视投影变换后的裁剪空间,经过齐次除法后会变换到一个立方体内。按照 OpenGL 的传统,这个立方体的 x、y、z 分量的范围都是[-1, 1]。但在 DirectX 这样的 API 中,z 分量的范围会是[0, 1]。而 Unity 选择了 OpenGL 这样的齐次裁剪空间,如图所示

\n

\"shader_29\"

\n

而对于正交投影来说,它的裁剪空间实际已经是一个立方体了,而且由于经过正交投影矩阵变换后的顶点的 w 分量是 1,因此齐次除法并不会对顶点的 x、y、z 坐标产生影响,如图所示

\n

\"shader_30\"

\n
    \n
  1. 映射输出窗口的对应像素坐标
  2. \n
\n

Unity 内置变换矩阵

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
变量名描述
UNITY MATRIX MVP当前的模型·观察·投影矩阵,用于将顶点/方向矢量从模型空间变换到裁剪空间
UNITY MATRIX MV当前的模型·观察矩阵,用于将顶点/方向矢量从模型空间变换到观察空间
UNITY MATRIX V当前的观察矩阵,用于将顶点/方向矢量从世界空间变换到观察空间
UNITY MATRIX P当前的投影矩阵,用于将顶点/方向矢量从观察空间变换到裁剪空间
UNITY MATRIX VP当前的观察投影矩阵,用于将顶点/方向矢量从世界空间变换到裁剪空间
UNITY MATRIX T MVUNITYMATRIX MV 的转置矩阵
UNITY MATRIX IT MVUNITYMATRIX MV 的逆转置矩阵,用于将法线从模型空间变换到观察空间,也可用于得到 UNITYMATRIXMV 的逆矩阵
Object2World当前的模型矩阵,用于将顶点/方向矢量从模型空间变换到世界空间
World2ObjectObject2World 的逆矩阵,用于将顶点/方向矢量从世界空间变换到模型空间
\n","site":{"data":{}},"excerpt":"","more":"

代码基于c#,书籍 Unity Shader入门精要

\n

什么是 OpenGL、DirectX

用于渲染二维或三维图形。可以说,这些接口架起了上层应用程序和底层 GPU 的沟通桥梁。一个应用程序向这些接口发送渲染命令,而这些接口会依次向显卡驱动(Graphics Driver)发送渲染命令,这些显卡驱动是真正知道如何和 GPU 通信的角色,正是它们把 OpenGL 或者 DirectX 的函数调用翻译成了 GPU 能够听懂的语言,同时它们也负责把纹理等数据转换成 GPU 所支持的格式。一个比喻是,显卡驱动就是显卡的操作系统。

\n

\"shader_7\"

\n

什么是 HLSL、GLSL、CG

如顶点着色器、片元着色器等。这些着色器的可编程性在于,我们可以使用一种特定的语言来编写程序,就好比我们可以用 C#来写游戏逻辑一样。

\n

着色语言是专门用于编写着色器的,常见的着色语言有

\n
    \n
  • DirectX 的 HLSL(High Level Shading Language)

    \n
  • \n
  • OpenGL 的 GLSL(OpenGL Shading Language)

    \n
  • \n
  • NVIDIA 的 CG(C for Graphic)。

    \n
  • \n
\n

HLSL、GLSL、CG 都是“高级(High-Level)”语言,但这种高级是相对于汇编语言来说的,而不是像 C#相对于 C 的高级那样。这些语言会被编译成与机器无关的汇编语言,也被称为中间语言(Intermediate Language, IL)。这些中间语言再交给显卡驱动来翻译成真正的机器语言,即 GPU 可以理解的语言。

\n

GPU 流水线

当 GPU 从 CPU 那里得到渲染命令后,就会进行一系列流水线操作,最终把图元渲染到屏幕上。

\n

\"shader_1\"

\n
    \n
  • 顶点着色器(Vertex Shader)是完全可编程的,它通常用于实现顶点的空间变换、顶点着色等功能

    \n
  • \n
  • 曲面细分着色器(Tessellation Shader)是一个可选的着色器,它用于细分图元

    \n
  • \n
  • 几何着色器(Geometry Shader)同样是一个可选的着色器,它可以被用于执行逐图元(Per-Primitive)的着色操作,或者被用于产生更多的图元

    \n
  • \n
  • 裁剪(Clipping),这一阶段的目的是将那些不在摄像机视野内的顶点裁剪掉,并剔除某些三角图元的面片。例如,我们可以使用自定义的裁剪平面来配置裁剪区域,也可以通过指令控制裁剪三角图元的正面还是背面。

    \n
  • \n
  • 片元着色器(Fragment Shader),则是完全可编程的,它用于实现逐片元(Per-Fragment)的着色操作

    \n
  • \n
\n

顶点着色器 Vertex Shader

CPU 输入进来的每个顶点都会调用一次顶点着色器。顶点着色器本身不可以创建或者销毁任何顶点,而且无法得到顶点与顶点之间的关系。例如,我们无法得知两个顶点是否属于同一个三角网格。但正是因为这样的相互独立性,GPU 可以利用本身的特性并行化处理每一个顶点,这意味着这一阶段的处理速度会很快。

\n

顶点着色器需要完成的工作主要有:

\n
    \n
  • 坐标变换:把顶点坐标转换到齐次裁剪坐标系下,接着通常再由硬件做透视除法后,最终得到归一化的设备坐标(NDC)
  • \n
\n

\"shader_2\"

\n
\n

在 DirectX 中,NDC 的 z 方向取值范围是[0,1],在 OpenGL 环境下是-1.0,DirectX 中是 0.0

\n
\n
    \n
  • 逐顶点光照
  • \n
\n

裁剪

只有在单位立方体的图元才需要被继续处

\n

\"shader_3\"

\n

屏幕映射

OpenGL 和 DirectX 之间的差异问题。OpenGL 把屏幕的左下角当成最小的窗口坐标值,而 DirectX 则定义了屏幕的左上角为最小的窗口坐标值

\n

\"shader_4\"

\n

光栅阶段

从上一个阶段输出的信息:屏幕坐标系下的顶点位置以及和它们相关的额外信息,如深度值(z 坐标)、法线方向、视角方向等。光栅化阶段有两个最重要的目标:计算每个图元覆盖了哪些像素,以及为这些像素计算它们的颜色。

\n

三角形设置

如果要得到整个三角网格对像素的覆盖情况,我们就必须计算每条边上的像素坐标。为了能够计算边界像素的坐标信息,我们就需要得到三角形边界的表示方式。

\n

三角形遍历()

    \n
  1. 扫描变换:三角形遍历(Triangle Traversal)阶段将会检查每个像素是否被一个三角网格所覆盖。如果被覆盖的话,就会生成一个片元(fragment)、

    \n
  2. \n
  3. 使用三角网格 3 个顶点的顶点信息对整个覆盖区域的像素进行插值

    \n
  4. \n
\n

\"shader_5\"

\n

这一步的输出就是得到一个片元序列。需要注意的是,一个片元并不是真正意义上的像素,而是包含了很多状态的集合,这些状态用于计算每个像素的最终颜色。这些状态包括了(但不限于)它的屏幕坐标、深度信息,以及其他从几何阶段输出的顶点信息,例如法线、纹理坐标等。

\n

片元着色器 Fragment Shader

前面的光栅化阶段实际上并不会影响屏幕上每个像素的颜色值,而是会产生一系列的数据信息,用来表述一个三角网格是怎样覆盖每个像素的。而每个片元就负责存储这样一系列数据。真正会对像素产生影响的阶段是下一个流水线阶段——逐片元操作

\n
\n

在 DirectX 中,片元着色器被称为像素着色器(Pixel Shader)但片元着色器是一个更合适的名字,因为此时的片元并不是一个真正意义上的像素。

\n
\n

\"shader_6\"

\n

逐片元操作

这一阶段有几个主要任务:

\n
    \n
  • 决定每个片元的可见性。这涉及了很多测试工作,例如深度测试、模板测试等。

    \n
  • \n
  • 如果一个片元通过了所有的测试,就需要把这个片元的颜色值和已经存储在颜色缓冲区中的颜色进行合并,或者说是混合。

    \n
  • \n
\n
\n

!! 对于不透明物体,开发者可以关闭混合(Blend)操作。这样片元着色器计算得到的颜色值就会直接覆盖掉颜色缓冲区中的像素值。但对于半透明物体,我们就需要使用混合操作来让这个物体看起来是透明的。

\n
\n

Draw Call

CPU 调用图像编程接口,如 OpenGL 中的 glDrawElements 命令或者 DirectX 中的 DrawIndexedPrimitive命令,以命令 GPU 进行渲染的操作。

\n

CPU 和 GPU 是如何实现并行工作的?

如果没有流水线化,那么 CPU 需要等到 GPU 完成上一个渲染任务才能再次发送渲染命令。但这种方法显然会造成效率低下。解决方法就是使用一个命令缓冲区(Command Buffer)。

\n

由 CPU 向其中添加命令,而由 GPU 从中读取命令,添加和读取的过程是互相独立的。

\n

为什么 Draw Call 多了会影响帧率?

每一个复制动作需要很多额外的操作,例如分配内存、创建各种元数据等。如你所见,这些操作将造成很多额外的性能开销,如果我们复制了很多小文件,那么这个开销将会很大

\n

如何减少 Draw Call?

尽管减少 Draw Call 的方法有很多,但我们这里仅讨论使用批处理(Batching)的方法。我们讲过,提交大量很小的 Draw Call 会造成 CPU 的性能瓶颈,即 CPU 把时间都花费在准备 Draw Call 的工作上了。那么,一个很显然的优化想法就是把很多小的 DrawCall 合并成一个大的 Draw Call,这就是批处理的思想。

\n

在游戏开发过程中,为了减少 Draw Call 的开销,有两点需要注意。

\n
    \n
  • 避免使用大量很小的网格。当不可避免地需要使用很小的网格结构时,考虑是否可以合并它们。

    \n
  • \n
  • 避免使用过多的材质。尽量在不同的网格之间共用同一个材质。

    \n
  • \n
\n

你明白什么是 Shader

    \n
  • GPU 流水线上一些可高度编程的阶段,而由着色器编译出来的最终代码是会在 GPU 上运行的(对于固定管线的渲染来说,着色器有时等同于一些特定的渲染设置)

    \n
  • \n
  • 有一些特定类型的着色器,如顶点着色器、片元着色器等

    \n
  • \n
  • 依靠着色器我们可以控制流水线中的渲染细节,例如用顶点着色器来进行顶点变换以及传递数据,用片元着色器来进行逐像素的渲染。

    \n
  • \n
\n

Unity Shader

\n

!! Unity Shader ! = 真正的 Shader

\n
\n

Unity Shader 实际上指的就是一个 ShaderLab 文件——硬盘上以.shader 作为文件后缀的一种文件,提供了一种让开发者同时控制渲染流水线中多个阶段的一种方式,不仅仅是提供 Shader 代码。

\n

作为开发者而言,我们绝大部分时候只需要和 Unity Shader 打交道,而不需要关心渲染引擎底层的实现细节

\n

Unity 编辑器会把这些 CG 片段编译成低级语言,如汇编语言等。通常,Unity 会自动把这些 CG 片段编译到所有相关平台(这里的平台是指不同的渲染平台,例如 Direct3D 9、OpenGL、Direct3D 11、OpenGL ES 等)上

\n

基础

在没有 Unity 这类编辑器的情况下,如果我们想要对某个模型设置渲染状态,可能需要类似下面的代码:

\n

上述伪代码仅仅是简化后的版本, 当渲染的模型数目、需要调整的着色器属性不断增多时,上述过程将变得更加复杂和冗长。

\n

而且,当涉及透明物体等多物体的渲染时,如果没有编辑器的帮助,我们要非常小心如渲染顺序等问题。

\n
// 初始化渲染设置\nvoid  Initialization()  {\n    // 从硬盘上加载顶点着色器的代码\n    string  vertexShaderCode  =  LoadShaderFromFile(VertexShader.shader);\n    // 从硬盘上加载片元着色器的代码\n    string  fragmentShaderCode  =  LoadShaderFromFile(FragmentShader.shader);\n    // 把顶点着色器加载到GPU中\n    LoadVertexShaderFromString(vertexShaderCode);\n    // 把片元着色器加载到GPU中\n    LoadFragmentShaderFromString(fragmentShaderCode);\n    // 设置名为\"vertexPosition\"的属性的输入,即模型顶点坐标\n    SetVertexShaderProperty(\"vertexPosition\",  vertices);\n    // 设置名为\"MainTex\"的属性的输入,someTexture是某张已加载的纹理\n    SetVertexShaderProperty(\"MainTex\",  someTexture);\n    // 设置名为\"MVP\"的属性的输入,MVP是之前由开发者计算好的变换矩阵\n    SetVertexShaderProperty(\"MVP\",  MVP);\n    // 关闭混合\n    Disable(Blend);\n    // 设置深度测试\n    Enable(ZText);\n    SetZTestFunction(LessOrEqual);\n    // 其他设置\n    …\n}\n// 每一帧迚行渲染\nvoid  OnRendering()  {\n    // 调用渲染命令\n    DrawCall();\n    // 当涉及多种渲染设置时,我们可能还需要在这里改变各种渲染设置\n    ...\n}
\n\n

VertexShader.shader:

\n
// 输入:顶点位置、纹理、MVP变换矩阵\nin  float3  vertexPosition;\nin  sampler2D  MainTex;\nin  Matrix4x4  MVP;\n// 输出:顶点经过MVP变换后的位置\nout  float4  position;\nvoid  main()  {\n    // 使用MVP对模型顶点坐标迚行变换\n    position  =  MVP  *  vertexPosition;\n}
\n\n

FragmentShader.shader:

\n
// 输入:VertexShader输出的position、经过光栅化程序插值后的该片元对应的position\nin  float4  position;\n// 输出:该片元的颜色值\nout  float4  fragColor;\nvoid  main()  {\n    // 将片元颜色设为白色\n    fragColor  =  float4(1.0,  1.0,  1.0,  1.0);\n}
\n\n

材质和 Unity Shader

总体来说,在 Unity 中我们需要配合使用材质(Material)和 Unity Shader 才能达到需要的效果。一个最常见的流程是:

\n
    \n
  • 创建一个材质

    \n
  • \n
  • 创建一个 Unity Shader,并把它赋给上一步中创建的材质

    \n
  • \n
  • 把材质赋给要渲染的对象

    \n
  • \n
  • 在材质面板中调整 Unity Shader 的属性,如使用的纹理、漫反射系数

    \n
  • \n
\n

Unity 表面着色器

表面着色器(Surface Shader)是 Unity 自己创造的一种着色器代码类型。它需要的代码量很少,Unity 在背后做了很多工作,但渲染的代价比较大。它在本质上和下面要讲到的顶点/片元着色器是一样的。也就是说,当给 Unity 提供一个表面着色器的时候,它在背后仍旧把它转换成对应的顶点/片元着色器。我们可以理解成,表面着色器是 Unity 对顶点/片元着色器的更高一层的抽象。它存在的价值在于,Unity 为我们处理了很多光照细节,使得我们不需要再操心这些“烦人的事情”。

\n
Shader  \"Custom/Simple  Surface  Shader\"  {\n    SubShader  {\n      Tags  {  \"RenderType\"  =  \"Opaque\"  }\n      CGPROGRAM\n      #pragma  surface  surf  Lambert\n      struct  Input  {\n          float4  color  :  COLOR;\n      };\n      void  surf  (Input  IN,  inout  SurfaceOutput  o)  {\n          o.Albedo  =  1;\n      }\n      ENDCG\n    }\n    Fallback  \"Diffuse\"\n}
\n\n

顶点/片元着色器

Shader  \"Custom/Simple  VertexFragment  Shader\"  {\n    SubShader  {\n      Pass  {\n          CGPROGRAM\n          #pragma  vertex  vert\n          #pragma  fragment  frag\n          float4  vert(float4  v  :  POSITION)  :  SV_POSITION  {\n              return  mul  (UNITY_MATRIX_MVP,  v);\n          }\n          fixed4  frag()  :  SV_Target  {\n              return  fixed4(1.0,0.0,0.0,1.0);\n          }\n          ENDCG\n      }\n    }\n}
\n\n

坐标系

Unity 使用的是左手坐标系

\n

左右手

\"shader_8\"

\n

\"shader_9\"

\n

向量(矢量)

矢量的模是一个标量,可以理解为是矢量在空间中的长度。它的表示符号通常是在矢量两旁分别加上一条垂直线

\n

\"shader_10\"

\n

单位矢量

在很多情况下,我们只关心矢量的方向而不是模。例如,在计算光照模型时,我们往往需要得到顶点的法线方向和光源方向,此时我们不关心这些矢量有多长。在这些情况下,我们就需要计算单位矢量(unit vector)

\n

单位矢量指的是那些模为 1 的矢量。单位矢量也被称为被归一化的矢量(normalized vector)。对任何给定的非零矢量,把它转换成单位矢量的过程就被称为归一化

\n

\"shader_11\"

\n

点积

一个向量在另一个向量方向上投影的长度,是一个标量

\n

公式:a·b = |a||b|cosθ

\n

向量积 ∧ ×

\n

向量积,数学中又称外积、叉积,物理中称矢积、叉乘

\n
\n

几何意义,一个和已有两个向量都垂直的向量,法向量

\n

\"shader_16\"

\n

向量 a 和向量 b:

\n

\"shader_12\"

\n

叉乘公式为:

\n

\"shader_13\"

\n

其中:

\n

\"shader_14\"

\n

根据 i、j、k 间关系,有:

\n

\"shader_15\"

\n

例如

\n

(1,2,3)×(-2,-1,4) = (2 × 4 - 3 × -1, 3 × -2 - 1 × 4, 1 × -1 - 2 × -2) = (11,10,3)

\n

矩阵变换 Vec4

由于 3×3 矩阵不能表示平移操作,我们就把其扩展到了 4×4 的矩阵

\n

平移矩阵

点的 x、y、z 分量分别增加了一个位置偏移。在 3D 中的可视化效果是,把点(x, y, z)在空间中平移了(tx, ty, tz)个单位

\n

\"shader_17\"

\n

缩放矩阵

旋转矩阵

旋转操作需要指定一个旋转轴,这个旋转轴不一定是空间中的坐标轴,但本节所讲的旋转就是指绕着空间中的 x 轴、y 轴或 z 轴进行旋转

\n

如果我们需要把点绕着 x 轴旋转 θ 度

\n

\"shader_18\"

\n

绕 y 轴的旋转

\n

\"shader_19\"

\n

绕 z 轴的旋转

\n

\"shader_20\"

\n

复合变换

先进行大小为(2, 2, 2)的缩放,再绕 y 轴旋转 30°,最后向 z 轴平移 4 个单位。

\n
\n

矩阵乘法注意顺序

\n
\n

\"shader_21\"

\n
\n

?? 如果我们需要同时绕着 3 个轴进行旋转,是先绕 x 轴、再绕 y 轴最后绕 z 轴旋转还是按其他的旋转顺序呢?

\n
\n

根据坐标系,需要调整轴的顺序

\n

\"shader_22\"

\n

法线变换

在游戏中,模型的一个顶点往往会携带额外的信息,而顶点法线就是其中一种信息。当我们变换一个模型的时候,不仅需要变换它的顶点,还需要变换顶点法线,以便在后续处理(如片元着色器)中计算光照等。

\n

坐标空间

事实上,在我们的生活中,我们也总是使用不同的坐标空间来交流。现在正在读这本书的你,很可能正坐在办公室或书房中。如果问你:“办公室的饮水机在哪里?”你大概会回答:“在办公室门的左方 3 米处。”这里,你很自然地使用了以门为原点的坐标空间。

\n

要想定义一个坐标空间,必须指明其原点位置和 3 个坐标轴的方向。而这些数值实际上是相对于另一个坐标空间的(读者需要记住,所有的都是相对的)。也就是说,坐标空间会形成一个层次结构——每个坐标空间都是另一个坐标空间的子空间,反过来说,每个空间都有一个父(parent)坐标空间。对坐标空间的变换实际上就是在父空间和子空间之间对点和矢量进行变换。

\n

模型空间

每个模型都有自己独立的坐标空间,当它移动或旋转的时候,模型空间也会跟着它移动和旋转。

\n
\n

Unity 在模型空间中使用的是左手坐标系,因此在模型空间中,+x 轴、+y 轴、+z 轴分别对应的是模型的右、上和前向。

\n
\n

\"shader_24\"

\n

世界空间

世界空间(world space)是一个特殊的坐标系,因为它建立了我们所关心的最大的空间。一些读者可能会指出,空间可以是无限大的,怎么会有“最大”这一说呢?这里说的最大指的是一个宏观的概念,也就是说它是我们所关心的最外层的坐标空间。

\n

摄像机空间

摄像机决定了我们渲染游戏所使用的视角。在观察空间中,摄像机位于原点

\n
\n

Unity 中观察空间的坐标轴选择是:+x 轴指向右方,+y 轴指向上方,而+z 轴指向的是摄像机的后方

\n
\n

Q:模型空间和世界空间中+z 轴指的都是物体的前方,为什么这里不一样了呢?

\n

A:Unity 在模型空间和世界空间中选用的都是左手坐标系,而在观察空间中使用的是右手坐标系。这是符合 OpenGL 传统的,在这样的观察空间中,摄像机的正前方指向的是-z 轴方向。

\n

裁剪空间

用于变换的矩阵叫做裁剪矩阵(clip matrix),也被称为投影矩阵(projection matrix)。位于这块空间内部的图元将会被保留,否则他剔除。由视锥体(view frustum)来决定。

\n

\"shader_26\"

\n

视锥体:指的是空间中的一块区域,这块区域决定了摄像机可以看到的空间。视锥体由六个平面包围而成,这些平面也被称为裁剪平面(clip planes)。视锥体有两种类型,这涉及两种投影类型:

\n
    \n
  • 正交投影(orthographic projection):所有的网格大小都一样,而且平行线会一直保持平行
  • \n
  • 透视投影(perspective projection):地板上的平行线并不会保持平行,离摄像机越近网格越大,离摄像机越远网格越小。
  • \n
\n

\"shader_25\"

\n
\n

追求真实感的 3D 游戏中我们往往会使用透视投影,而在一些 2D 游戏或渲染小地图等其他 HUD 元素时,我们会使用正交投影

\n
\n

透视摄像机视锥体模型

在 Unity 中,一个摄像机的横纵比由 Game 视图的横纵比和 Viewport Rect 中的 WH 属性共同决定(实际上,Unity 允许我们在脚本里通过 Camera.aspect 进行更改,但这里不做讨论)

\n

\"shader_27\"

\n

正交摄像机视锥体模型

通过 Camera 组件的 Size 属性来改变视锥体竖直方向上高度的一半,而 Clipping Planes 中的 Near 和 Far 参数可以控制视锥体的近裁剪平面和远裁剪平面距离摄像机的远近。

\n

\"shader_28\"

\n

屏幕空间

\n

在 Unity 中,从裁剪空间到屏幕空间的转换是由 Unity 帮我们完成的。

\n
\n

屏幕空间是一个二维空间,因此,我们必须把顶点从裁剪空间投影到屏幕空间中,来生成对应的 2D 坐标。这个过程可以理解成有两个步骤:

\n
    \n
  1. 标准齐次除法(透视除法):用齐次坐标系的 w 分量去除以 x、y、z 分量,在 OpenGL 中,得到的坐标叫做归一化的设备坐标(Normalized Device Coordinates, NDC)
  2. \n
\n

经过透视投影变换后的裁剪空间,经过齐次除法后会变换到一个立方体内。按照 OpenGL 的传统,这个立方体的 x、y、z 分量的范围都是[-1, 1]。但在 DirectX 这样的 API 中,z 分量的范围会是[0, 1]。而 Unity 选择了 OpenGL 这样的齐次裁剪空间,如图所示

\n

\"shader_29\"

\n

而对于正交投影来说,它的裁剪空间实际已经是一个立方体了,而且由于经过正交投影矩阵变换后的顶点的 w 分量是 1,因此齐次除法并不会对顶点的 x、y、z 坐标产生影响,如图所示

\n

\"shader_30\"

\n
    \n
  1. 映射输出窗口的对应像素坐标
  2. \n
\n

Unity 内置变换矩阵

\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
变量名描述
UNITY MATRIX MVP当前的模型·观察·投影矩阵,用于将顶点/方向矢量从模型空间变换到裁剪空间
UNITY MATRIX MV当前的模型·观察矩阵,用于将顶点/方向矢量从模型空间变换到观察空间
UNITY MATRIX V当前的观察矩阵,用于将顶点/方向矢量从世界空间变换到观察空间
UNITY MATRIX P当前的投影矩阵,用于将顶点/方向矢量从观察空间变换到裁剪空间
UNITY MATRIX VP当前的观察投影矩阵,用于将顶点/方向矢量从世界空间变换到裁剪空间
UNITY MATRIX T MVUNITYMATRIX MV 的转置矩阵
UNITY MATRIX IT MVUNITYMATRIX MV 的逆转置矩阵,用于将法线从模型空间变换到观察空间,也可用于得到 UNITYMATRIXMV 的逆矩阵
Object2World当前的模型矩阵,用于将顶点/方向矢量从模型空间变换到世界空间
World2ObjectObject2World 的逆矩阵,用于将顶点/方向矢量从世界空间变换到模型空间
\n"},{"title":"css奇技淫巧","status":"doing","_content":"\n# grid 布局\n\n> flex 布局操纵的是一维、一行/一列,grid 布局具备操纵二维的能力\n\n设为网格布局以后,容器子元素(项目)的`float`、`display: inline-block`、`display: table-cell`、`vertical-align`和 `column-`等设置都将失效。\n\n- grid-template-columns: 定义每一列的列宽\n- grid-template-rows: 定义每一行的行高\n- grid-row-gap: 行间距\n- grid-column-gap: 列间距\n- grid-gap: 行列间距合并\n- grid-template-areas: 一个区域由单个或多个单元格组成\n- grid-auto-flow: 默认值是 row,即\"先行后列\",即先填满第一行,再开始放入第二行\n- justify-items: `单元格内容`的水平对齐\n- align-items: `单元格内容`垂直对齐\n- place-items: align-items 属性和 justify-items 属性的合并简写形式\n- justify-content: `整个内容区域`水平对齐\n- align-content: `整个内容区域`垂直对齐\n- place-content: align-content 属性和 justify-content 属性的合并简写形式\n\n> 设置的行或者列比较多的时候,可以使用 repeat()这个函数简化重复的值\n\n## repeat()\n\n第一个参数是重复的次数,第二个参数是所要重复的值、也可以是某个模式\n\n```css\n/* 重复 3个100px的列 */\ngrid-template-columns: repeat(3, 100px);\n/* 重复这种布局2次 */\ngrid-template-columns: repeat(2, 100px 20px 80px);\n/* 自动填充,直到容器放不下为止 */\ngrid-template-columns: repeat(auto-fill, 100px);\n```\n\n## fr\n\n方便表示比例关系,网格布局提供了 fr 关键字\n\n```css\ngrid-template-columns: 1fr 1fr;\n```\n\n## grid-template-areas\n\n```css\ngrid-template-areas:\n \"header header header\"\n \"main main sidebar\"\n \"footer footer footer\";\n```\n\n## item 定位\n\n![grid_1](/img/grid_1.png)\n\n```css\n/* 1号项目就是从第二根垂直网格线开始第四根结束 */\n.item1 {\n grid-column-start: 2;\n grid-column-end: 4;\n background: red;\n}\n```\n\n# 硬件加速(IE9+)\n\n> 移动端开启,吃内存、增加耗电\n\n`animation`、`transform`、`transition`不会自动开启`GPU`加速,利用`transform: translateZ(0)` 就可以开启`3D变换`,出发硬件加速\n\n场景:`webKit内核`偶尔页面闪烁:`transform: translate3d(0, 0, 0);`\n\n# 控制台打印 shield 徽章\n\n```js\nconsole.log(\n \"%c\" +\n eval(\"'mozzie.com'\") +\n \"%cv\" +\n eval(\"'2.0.0'\") +\n \"%c\\r\\n\" +\n eval(\"'了解更多:https://www.mozzie.com'\"),\n \"color: #fff; background: #5281FA; font-size: 12px;border-radius:2px 0 0 2px;padding:3px 6px;\",\n \"border-radius:0 2px 2px 0;padding:3px 6px;color:#333;background:#EBEBEB\",\n \"margin-top:10px;\"\n);\n```\n\n# css 判断 input 是否为空\n\n`:placeholder-shown`:占位符是否显示的伪类,配合 `:not()` (不是必须,反过来也可以)\n\n```html\n
\n \n \n
\n```\n\n```css\n#demo {\n width: 90%;\n max-width: 450px;\n position: relative;\n}\n#demo-input {\n width: 100%;\n height: 60px;\n line-height: 60px;\n font-size: 20px;\n border-bottom: 1px solid #ffa500;\n}\n#demo-input::placeholder {\n font-size: 0;\n}\n#demo-input:focus + label,\n#demo-input:not(:placeholder-shown) + label {\n top: 0;\n font-size: 12px;\n}\n#demo-label {\n font-size: 22px;\n color: #ffa500;\n position: absolute;\n left: 0;\n top: 50%;\n transform: translateY(-50%);\n transition: all 0.3s;\n}\n/*\n* Default\n*/\nbody {\n height: 100vh;\n display: flex;\n}\nbody div {\n margin: auto;\n}\nbody div input {\n border: 0;\n outline: 0;\n}\n```\n\n# 居中\n\n## transform 大法\n\n```css\n#wrapper {\n position: relative;\n}\n#box {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n}\n```\n\n## 0000 大法\n\n```css\n#wrapper {\n position: relative;\n}\n#box {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n}\n```\n\n## 如何让 img 垂直居中\n\n```html\n
\n \n
\n```\n\n```css\n.imgWrapper {\n display: table-cell;\n text-align: center;\n vertical-align: middle;\n}\n```\n\n## ul 下 li 居中\n\n```html\n\n
\n
    \n
  • 1
  • \n
  • 2
  • \n
\n
\n```\n\n```css\ndiv{\n text-align: center\n}\nul{\n display: inline-block\n}\nli{\n display: inline\n float: left\n}\n```\n\n# js 随机渐变背景\n\n```javascript\nfunction getRandomRangeNum(min, max) {\n return min + Math.floor(Math.random() * (max - min));\n}\n\nfunction setRandomBg(el) {\n var left = getRandomRangeNum(0, 255);\n var bottom = getRandomRangeNum(0, 255);\n var css = [\n \"linear-gradient(to left bottom,hsl(\",\n left,\n \", 100%, 85%) 0%,hsl(\",\n bottom,\n \", 100%, 85%) 100%)\",\n ];\n el.style.background = css.join(\"\");\n}\n```\n\n# css 三角形\n\n```css\nspan {\n width: 0;\n height: 0;\n border-top: 40px solid transparent;\n border-left: 40px solid transparent;\n border-right: 40px solid transparent;\n border-bottom: 40px solid #ff0000;\n}\n```\n\n# 1px 神迹\n\n## pc 端最优解\n\n```html\n
\n```\n\n## 移动端 - 媒体查询 + transform\n\n```css\n@media only screen and (-webkit-min-device-pixel-ratio: 2) {\n .border-bottom::after {\n -webkit-transform: scaleY(0.5);\n transform: scaleY(0.5);\n }\n}\n```\n\n## 移动端 - 媒体查询 + 伪类\n\n```css\n.border__1px:before {\n content: '';\n position: absolute;\n top: 0;\n height: 1px;\n width: 100%\n background-color: #000;\n transform-origin: 50% 0%;\n}\n@media only screen and (-webkit-min-device-pixel-ratio:2) {\n .border__1px:before {\n transform: scaleY(0.5)\n }\n}\n@media only screen and (-webkit-min-device-pixel-ratio:3) {\n .border__1px:before {\n transform: scaleY(0.33)\n }\n}\n```\n\n# 横竖屏适配\n\njs 检测\n\n```js\nwindow.addEventListener(\"resize\", () => {\n if (window.orientation === 180 || window.orientation === 0) {\n // 正常方向或屏幕旋转180度\n console.log(\"竖屏\");\n }\n if (window.orientation === 90 || window.orientation === -90) {\n // 屏幕顺时钟旋转90度或屏幕逆时针旋转90度\n console.log(\"横屏\");\n }\n});\n```\n\ncss 检测\n\n```css\n@media screen and (orientation: portrait) {\n /*竖屏...*/\n}\n@media screen and (orientation: landscape) {\n /*横屏...*/\n}\n```\n\n# 像素\n\n- 物理像素: 物理设备真实的像素\n- 独立像素: 平时开发写的 px\n- 设备像素比(DPR): = 物理像素 / 设备独立像素\n\n```js\n// 获取 DPR\nwindow.devicePixelRatio;\n```\n\n也可以使用媒体查询\n\n```css\n@media (-webkit-min-device-pixel-ratio: 2), (min-device-pixel-ratio: 2) {\n}\n```\n\n# srcset\n\n使用 img 标签的 `srcset` 属性,浏览器会自动根据像素密度匹配最佳显示图片:\n\n```html\n\n```\n\n# 字体小于 12px\n\ncss3 的 `transform` 属性,设置值为 `scale(x, y)` 定义 2D 缩放转换\n\n\n# css 清浮动\n\n```css\n.clearfix::after {\n content: \"\";\n display: block;\n clear: both;\n}\n```\n\n# ellipsis\n\n## 单行\n\n```css\n.ellipsis {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n```\n\n## 多行\n\n```css\n.ellipsis {\n display: -webkit-box;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 3; // 最多显示几行\n overflow: hidden;\n}\n```\n\n# 页面导入样式时,使用 link 和 @import\n\n> 你可能不信,ie5+就支持@import 了\n\n- link: 与`dom`异步加载\n- @import: 先加载`dom`\n\n\n# em | rem 换算\n\n相对于HTML根元素 `font-size` 来确定的,浏览器的默认字体高是 `16px`,因此:\n\n- 16px = 1em\n- 12px = .75em\n- 10px = .625em\n\n简化 rem 到 px 的换算,因为每个 rem 单位都是 10px 的倍数\n\n```css\nhtml {\n font-size: 62.5%; /* 这会将默认的 16px 缩小为 10px */\n}\n```\n\n也可以使用 js 检测设置\n\n```js\n// set 1rem = viewWidth / 10\nfunction setRemUnit() {\n var docEl = document.documentElement;\n var rem = docEl.clientWidth / 10;\n docEl.style.fontSize = rem + \"px\";\n}\n\nwindow.addEventListener(\"resize\", setRemUnit);\nwindow.addEventListener(\"pageShow\", function (e) {\n if (e.persisted) setRemUnit()\n});\n```\n\n# less 常用\n\n## 变量\n\nLESS 中的变量为完全的 `常量` ,所以只能定义一次\n\n```less\n@nice-blue: #5B83AD;\n@light-blue: @nice-blue + #111;\n\n#header {\n color: @light-blue;\n}\n```\n\n输出\n\n```less\n#header {\n color: #6c94be;\n}\n```\n\n## 混合\n\n```less\n.bordered {\n border-top: dotted 1px black;\n border-bottom: solid 2px black;\n}\n```\n\n使用\n\n```less\n#menu a {\n color: #111;\n .bordered;\n}\n.post a {\n color: red;\n .bordered;\n}\n```\n\n带参\n\n```less\n.border-radius (@radius) {\n border-radius: @radius;\n -moz-border-radius: @radius;\n -webkit-border-radius: @radius;\n}\n```\n\n#header {\n.border-radius(4px);\n}\n\n`@arguments`变量,包含了所有传递进来的参数. 如果你不想单独处理每一个参数\n\n```less\n.box-shadow (@x: 0, @y: 0, @blur: 1px, @color: #000) {\n box-shadow: @arguments;\n -moz-box-shadow: @arguments;\n -webkit-box-shadow: @arguments;\n}\n.box-shadow(2px, 5px);\n```\n\n无参\n\n```less\n.wrap () {\n text-wrap: wrap;\n white-space: pre-wrap;\n white-space: -moz-pre-wrap;\n word-wrap: break-word;\n}\n\npre {\n .wrap;\n}\n```\n\n## lighten 和 darken\n\n```less\n@color-base: #3bafda;\n@color-hover:lighten (@color-primary,10%);\n@color-focus:darken (@color-primary,10%);\n```\n\n## contrast\n\n选择两种颜色中哪一种颜色与另一种颜色形成最大对比。\n\n```less\np {\n a: contrast(#bbbbbb); //output: black\n b: contrast(#222222, #101010); //output: white\n}\n```\n\n## JavaScript 表达式\n\n```less\n@var: ` \"hello\" .toUpperCase() + \"!\" `;\n@height: `document.body.clientHeight`;\n```\n\n## & 父选择器\n\n`&` 只能代表父元素,不能代表父亲的父辈元素,施加改性类或伪类\n\n```less\na {\n color: blue;\n &:hover {\n color: green;\n }\n}\n```\n\n## 重复父类名\n\n```less\n.button {\n &-ok {\n background-image: url(\"ok.png\");\n }\n &-cancel {\n background-image: url(\"cancel.png\");\n }\n\n &-custom {\n background-image: url(\"custom.png\");\n }\n}\n```\n","source":"_posts/front-end/css奇技淫巧.md","raw":"---\ntitle: css奇技淫巧\ncategories:\n - Front-End\nstatus: doing\n---\n\n# grid 布局\n\n> flex 布局操纵的是一维、一行/一列,grid 布局具备操纵二维的能力\n\n设为网格布局以后,容器子元素(项目)的`float`、`display: inline-block`、`display: table-cell`、`vertical-align`和 `column-`等设置都将失效。\n\n- grid-template-columns: 定义每一列的列宽\n- grid-template-rows: 定义每一行的行高\n- grid-row-gap: 行间距\n- grid-column-gap: 列间距\n- grid-gap: 行列间距合并\n- grid-template-areas: 一个区域由单个或多个单元格组成\n- grid-auto-flow: 默认值是 row,即\"先行后列\",即先填满第一行,再开始放入第二行\n- justify-items: `单元格内容`的水平对齐\n- align-items: `单元格内容`垂直对齐\n- place-items: align-items 属性和 justify-items 属性的合并简写形式\n- justify-content: `整个内容区域`水平对齐\n- align-content: `整个内容区域`垂直对齐\n- place-content: align-content 属性和 justify-content 属性的合并简写形式\n\n> 设置的行或者列比较多的时候,可以使用 repeat()这个函数简化重复的值\n\n## repeat()\n\n第一个参数是重复的次数,第二个参数是所要重复的值、也可以是某个模式\n\n```css\n/* 重复 3个100px的列 */\ngrid-template-columns: repeat(3, 100px);\n/* 重复这种布局2次 */\ngrid-template-columns: repeat(2, 100px 20px 80px);\n/* 自动填充,直到容器放不下为止 */\ngrid-template-columns: repeat(auto-fill, 100px);\n```\n\n## fr\n\n方便表示比例关系,网格布局提供了 fr 关键字\n\n```css\ngrid-template-columns: 1fr 1fr;\n```\n\n## grid-template-areas\n\n```css\ngrid-template-areas:\n \"header header header\"\n \"main main sidebar\"\n \"footer footer footer\";\n```\n\n## item 定位\n\n![grid_1](/img/grid_1.png)\n\n```css\n/* 1号项目就是从第二根垂直网格线开始第四根结束 */\n.item1 {\n grid-column-start: 2;\n grid-column-end: 4;\n background: red;\n}\n```\n\n# 硬件加速(IE9+)\n\n> 移动端开启,吃内存、增加耗电\n\n`animation`、`transform`、`transition`不会自动开启`GPU`加速,利用`transform: translateZ(0)` 就可以开启`3D变换`,出发硬件加速\n\n场景:`webKit内核`偶尔页面闪烁:`transform: translate3d(0, 0, 0);`\n\n# 控制台打印 shield 徽章\n\n```js\nconsole.log(\n \"%c\" +\n eval(\"'mozzie.com'\") +\n \"%cv\" +\n eval(\"'2.0.0'\") +\n \"%c\\r\\n\" +\n eval(\"'了解更多:https://www.mozzie.com'\"),\n \"color: #fff; background: #5281FA; font-size: 12px;border-radius:2px 0 0 2px;padding:3px 6px;\",\n \"border-radius:0 2px 2px 0;padding:3px 6px;color:#333;background:#EBEBEB\",\n \"margin-top:10px;\"\n);\n```\n\n# css 判断 input 是否为空\n\n`:placeholder-shown`:占位符是否显示的伪类,配合 `:not()` (不是必须,反过来也可以)\n\n```html\n
\n \n \n
\n```\n\n```css\n#demo {\n width: 90%;\n max-width: 450px;\n position: relative;\n}\n#demo-input {\n width: 100%;\n height: 60px;\n line-height: 60px;\n font-size: 20px;\n border-bottom: 1px solid #ffa500;\n}\n#demo-input::placeholder {\n font-size: 0;\n}\n#demo-input:focus + label,\n#demo-input:not(:placeholder-shown) + label {\n top: 0;\n font-size: 12px;\n}\n#demo-label {\n font-size: 22px;\n color: #ffa500;\n position: absolute;\n left: 0;\n top: 50%;\n transform: translateY(-50%);\n transition: all 0.3s;\n}\n/*\n* Default\n*/\nbody {\n height: 100vh;\n display: flex;\n}\nbody div {\n margin: auto;\n}\nbody div input {\n border: 0;\n outline: 0;\n}\n```\n\n# 居中\n\n## transform 大法\n\n```css\n#wrapper {\n position: relative;\n}\n#box {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n}\n```\n\n## 0000 大法\n\n```css\n#wrapper {\n position: relative;\n}\n#box {\n position: absolute;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n}\n```\n\n## 如何让 img 垂直居中\n\n```html\n
\n \n
\n```\n\n```css\n.imgWrapper {\n display: table-cell;\n text-align: center;\n vertical-align: middle;\n}\n```\n\n## ul 下 li 居中\n\n```html\n\n
\n
    \n
  • 1
  • \n
  • 2
  • \n
\n
\n```\n\n```css\ndiv{\n text-align: center\n}\nul{\n display: inline-block\n}\nli{\n display: inline\n float: left\n}\n```\n\n# js 随机渐变背景\n\n```javascript\nfunction getRandomRangeNum(min, max) {\n return min + Math.floor(Math.random() * (max - min));\n}\n\nfunction setRandomBg(el) {\n var left = getRandomRangeNum(0, 255);\n var bottom = getRandomRangeNum(0, 255);\n var css = [\n \"linear-gradient(to left bottom,hsl(\",\n left,\n \", 100%, 85%) 0%,hsl(\",\n bottom,\n \", 100%, 85%) 100%)\",\n ];\n el.style.background = css.join(\"\");\n}\n```\n\n# css 三角形\n\n```css\nspan {\n width: 0;\n height: 0;\n border-top: 40px solid transparent;\n border-left: 40px solid transparent;\n border-right: 40px solid transparent;\n border-bottom: 40px solid #ff0000;\n}\n```\n\n# 1px 神迹\n\n## pc 端最优解\n\n```html\n
\n```\n\n## 移动端 - 媒体查询 + transform\n\n```css\n@media only screen and (-webkit-min-device-pixel-ratio: 2) {\n .border-bottom::after {\n -webkit-transform: scaleY(0.5);\n transform: scaleY(0.5);\n }\n}\n```\n\n## 移动端 - 媒体查询 + 伪类\n\n```css\n.border__1px:before {\n content: '';\n position: absolute;\n top: 0;\n height: 1px;\n width: 100%\n background-color: #000;\n transform-origin: 50% 0%;\n}\n@media only screen and (-webkit-min-device-pixel-ratio:2) {\n .border__1px:before {\n transform: scaleY(0.5)\n }\n}\n@media only screen and (-webkit-min-device-pixel-ratio:3) {\n .border__1px:before {\n transform: scaleY(0.33)\n }\n}\n```\n\n# 横竖屏适配\n\njs 检测\n\n```js\nwindow.addEventListener(\"resize\", () => {\n if (window.orientation === 180 || window.orientation === 0) {\n // 正常方向或屏幕旋转180度\n console.log(\"竖屏\");\n }\n if (window.orientation === 90 || window.orientation === -90) {\n // 屏幕顺时钟旋转90度或屏幕逆时针旋转90度\n console.log(\"横屏\");\n }\n});\n```\n\ncss 检测\n\n```css\n@media screen and (orientation: portrait) {\n /*竖屏...*/\n}\n@media screen and (orientation: landscape) {\n /*横屏...*/\n}\n```\n\n# 像素\n\n- 物理像素: 物理设备真实的像素\n- 独立像素: 平时开发写的 px\n- 设备像素比(DPR): = 物理像素 / 设备独立像素\n\n```js\n// 获取 DPR\nwindow.devicePixelRatio;\n```\n\n也可以使用媒体查询\n\n```css\n@media (-webkit-min-device-pixel-ratio: 2), (min-device-pixel-ratio: 2) {\n}\n```\n\n# srcset\n\n使用 img 标签的 `srcset` 属性,浏览器会自动根据像素密度匹配最佳显示图片:\n\n```html\n\n```\n\n# 字体小于 12px\n\ncss3 的 `transform` 属性,设置值为 `scale(x, y)` 定义 2D 缩放转换\n\n\n# css 清浮动\n\n```css\n.clearfix::after {\n content: \"\";\n display: block;\n clear: both;\n}\n```\n\n# ellipsis\n\n## 单行\n\n```css\n.ellipsis {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n```\n\n## 多行\n\n```css\n.ellipsis {\n display: -webkit-box;\n -webkit-box-orient: vertical;\n -webkit-line-clamp: 3; // 最多显示几行\n overflow: hidden;\n}\n```\n\n# 页面导入样式时,使用 link 和 @import\n\n> 你可能不信,ie5+就支持@import 了\n\n- link: 与`dom`异步加载\n- @import: 先加载`dom`\n\n\n# em | rem 换算\n\n相对于HTML根元素 `font-size` 来确定的,浏览器的默认字体高是 `16px`,因此:\n\n- 16px = 1em\n- 12px = .75em\n- 10px = .625em\n\n简化 rem 到 px 的换算,因为每个 rem 单位都是 10px 的倍数\n\n```css\nhtml {\n font-size: 62.5%; /* 这会将默认的 16px 缩小为 10px */\n}\n```\n\n也可以使用 js 检测设置\n\n```js\n// set 1rem = viewWidth / 10\nfunction setRemUnit() {\n var docEl = document.documentElement;\n var rem = docEl.clientWidth / 10;\n docEl.style.fontSize = rem + \"px\";\n}\n\nwindow.addEventListener(\"resize\", setRemUnit);\nwindow.addEventListener(\"pageShow\", function (e) {\n if (e.persisted) setRemUnit()\n});\n```\n\n# less 常用\n\n## 变量\n\nLESS 中的变量为完全的 `常量` ,所以只能定义一次\n\n```less\n@nice-blue: #5B83AD;\n@light-blue: @nice-blue + #111;\n\n#header {\n color: @light-blue;\n}\n```\n\n输出\n\n```less\n#header {\n color: #6c94be;\n}\n```\n\n## 混合\n\n```less\n.bordered {\n border-top: dotted 1px black;\n border-bottom: solid 2px black;\n}\n```\n\n使用\n\n```less\n#menu a {\n color: #111;\n .bordered;\n}\n.post a {\n color: red;\n .bordered;\n}\n```\n\n带参\n\n```less\n.border-radius (@radius) {\n border-radius: @radius;\n -moz-border-radius: @radius;\n -webkit-border-radius: @radius;\n}\n```\n\n#header {\n.border-radius(4px);\n}\n\n`@arguments`变量,包含了所有传递进来的参数. 如果你不想单独处理每一个参数\n\n```less\n.box-shadow (@x: 0, @y: 0, @blur: 1px, @color: #000) {\n box-shadow: @arguments;\n -moz-box-shadow: @arguments;\n -webkit-box-shadow: @arguments;\n}\n.box-shadow(2px, 5px);\n```\n\n无参\n\n```less\n.wrap () {\n text-wrap: wrap;\n white-space: pre-wrap;\n white-space: -moz-pre-wrap;\n word-wrap: break-word;\n}\n\npre {\n .wrap;\n}\n```\n\n## lighten 和 darken\n\n```less\n@color-base: #3bafda;\n@color-hover:lighten (@color-primary,10%);\n@color-focus:darken (@color-primary,10%);\n```\n\n## contrast\n\n选择两种颜色中哪一种颜色与另一种颜色形成最大对比。\n\n```less\np {\n a: contrast(#bbbbbb); //output: black\n b: contrast(#222222, #101010); //output: white\n}\n```\n\n## JavaScript 表达式\n\n```less\n@var: ` \"hello\" .toUpperCase() + \"!\" `;\n@height: `document.body.clientHeight`;\n```\n\n## & 父选择器\n\n`&` 只能代表父元素,不能代表父亲的父辈元素,施加改性类或伪类\n\n```less\na {\n color: blue;\n &:hover {\n color: green;\n }\n}\n```\n\n## 重复父类名\n\n```less\n.button {\n &-ok {\n background-image: url(\"ok.png\");\n }\n &-cancel {\n background-image: url(\"cancel.png\");\n }\n\n &-custom {\n background-image: url(\"custom.png\");\n }\n}\n```\n","slug":"front-end/css奇技淫巧","published":1,"date":"2023-11-06T08:02:18.176Z","updated":"2023-11-08T01:33:30.288Z","comments":1,"layout":"post","photos":[],"link":"","_id":"clp7x194x000av3z3bf895az5","content":"

grid 布局

\n

flex 布局操纵的是一维、一行/一列,grid 布局具备操纵二维的能力

\n
\n

设为网格布局以后,容器子元素(项目)的floatdisplay: inline-blockdisplay: table-cellvertical-aligncolumn-等设置都将失效。

\n
    \n
  • grid-template-columns: 定义每一列的列宽
  • \n
  • grid-template-rows: 定义每一行的行高
  • \n
  • grid-row-gap: 行间距
  • \n
  • grid-column-gap: 列间距
  • \n
  • grid-gap: 行列间距合并
  • \n
  • grid-template-areas: 一个区域由单个或多个单元格组成
  • \n
  • grid-auto-flow: 默认值是 row,即”先行后列”,即先填满第一行,再开始放入第二行
  • \n
  • justify-items: 单元格内容的水平对齐
  • \n
  • align-items: 单元格内容垂直对齐
  • \n
  • place-items: align-items 属性和 justify-items 属性的合并简写形式
  • \n
  • justify-content: 整个内容区域水平对齐
  • \n
  • align-content: 整个内容区域垂直对齐
  • \n
  • place-content: align-content 属性和 justify-content 属性的合并简写形式
  • \n
\n
\n

设置的行或者列比较多的时候,可以使用 repeat()这个函数简化重复的值

\n
\n

repeat()

第一个参数是重复的次数,第二个参数是所要重复的值、也可以是某个模式

\n
/* 重复 3个100px的列 */\ngrid-template-columns: repeat(3, 100px);\n/* 重复这种布局2次 */\ngrid-template-columns: repeat(2, 100px 20px 80px);\n/* 自动填充,直到容器放不下为止 */\ngrid-template-columns: repeat(auto-fill, 100px);
\n\n

fr

方便表示比例关系,网格布局提供了 fr 关键字

\n
grid-template-columns: 1fr 1fr;
\n\n

grid-template-areas

grid-template-areas:\n  \"header header header\"\n  \"main main sidebar\"\n  \"footer footer footer\";
\n\n

item 定位

\"grid_1\"

\n
/* 1号项目就是从第二根垂直网格线开始第四根结束 */\n.item1 {\n  grid-column-start: 2;\n  grid-column-end: 4;\n  background: red;\n}
\n\n

硬件加速(IE9+)

\n

移动端开启,吃内存、增加耗电

\n
\n

animationtransformtransition不会自动开启GPU加速,利用transform: translateZ(0) 就可以开启3D变换,出发硬件加速

\n

场景:webKit内核偶尔页面闪烁:transform: translate3d(0, 0, 0);

\n

控制台打印 shield 徽章

console.log(\n  \"%c\" +\n    eval(\"'mozzie.com'\") +\n    \"%cv\" +\n    eval(\"'2.0.0'\") +\n    \"%c\\r\\n\" +\n    eval(\"'了解更多:https://www.mozzie.com'\"),\n  \"color: #fff; background: #5281FA; font-size: 12px;border-radius:2px 0 0 2px;padding:3px 6px;\",\n  \"border-radius:0 2px 2px 0;padding:3px 6px;color:#333;background:#EBEBEB\",\n  \"margin-top:10px;\"\n);
\n\n

css 判断 input 是否为空

:placeholder-shown:占位符是否显示的伪类,配合 :not() (不是必须,反过来也可以)

\n
<div id=\"demo\">\n  <input id=\"demo-input\" type=\"text\" placeholder=\"name\" />\n  <label id=\"demo-label\">Name</label>\n</div>
\n\n
#demo {\n  width: 90%;\n  max-width: 450px;\n  position: relative;\n}\n#demo-input {\n  width: 100%;\n  height: 60px;\n  line-height: 60px;\n  font-size: 20px;\n  border-bottom: 1px solid #ffa500;\n}\n#demo-input::placeholder {\n  font-size: 0;\n}\n#demo-input:focus + label,\n#demo-input:not(:placeholder-shown) + label {\n  top: 0;\n  font-size: 12px;\n}\n#demo-label {\n  font-size: 22px;\n  color: #ffa500;\n  position: absolute;\n  left: 0;\n  top: 50%;\n  transform: translateY(-50%);\n  transition: all 0.3s;\n}\n/*\n* Default\n*/\nbody {\n  height: 100vh;\n  display: flex;\n}\nbody div {\n  margin: auto;\n}\nbody div input {\n  border: 0;\n  outline: 0;\n}
\n\n

居中

transform 大法

#wrapper {\n  position: relative;\n}\n#box {\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  transform: translate(-50%, -50%);\n}
\n\n

0000 大法

#wrapper {\n  position: relative;\n}\n#box {\n  position: absolute;\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 0;\n}
\n\n

如何让 img 垂直居中

<div id=\"imgWrapper\">\n  <img src=\"xxx.png\" />\n</div>
\n\n
.imgWrapper {\n  display: table-cell;\n  text-align: center;\n  vertical-align: middle;\n}
\n\n

ul 下 li 居中

<!--外层包个div/section block元素 -->\n<div>\n  <ul class=\"clearfix\">\n    <li>1</li>\n    <li>2</li>\n  </ul>\n</div>
\n\n
div{\n  text-align: center\n}\nul{\n  display: inline-block\n}\nli{\n  display: inline\n  float: left\n}
\n\n

js 随机渐变背景

function getRandomRangeNum(min, max) {\n  return min + Math.floor(Math.random() * (max - min));\n}\n\nfunction setRandomBg(el) {\n  var left = getRandomRangeNum(0, 255);\n  var bottom = getRandomRangeNum(0, 255);\n  var css = [\n    \"linear-gradient(to left bottom,hsl(\",\n    left,\n    \", 100%, 85%) 0%,hsl(\",\n    bottom,\n    \", 100%, 85%) 100%)\",\n  ];\n  el.style.background = css.join(\"\");\n}
\n\n

css 三角形

span {\n  width: 0;\n  height: 0;\n  border-top: 40px solid transparent;\n  border-left: 40px solid transparent;\n  border-right: 40px solid transparent;\n  border-bottom: 40px solid #ff0000;\n}
\n\n

1px 神迹

pc 端最优解

<div style=\"height:1px;overflow:hidden;background:red\"></div>
\n\n

移动端 - 媒体查询 + transform

@media only screen and (-webkit-min-device-pixel-ratio: 2) {\n  .border-bottom::after {\n    -webkit-transform: scaleY(0.5);\n    transform: scaleY(0.5);\n  }\n}
\n\n

移动端 - 媒体查询 + 伪类

.border__1px:before {\n    content: '';\n    position: absolute;\n    top: 0;\n    height: 1px;\n    width: 100%\n    background-color: #000;\n    transform-origin: 50% 0%;\n}\n@media only screen and (-webkit-min-device-pixel-ratio:2) {\n    .border__1px:before {\n        transform: scaleY(0.5)\n    }\n}\n@media only screen and (-webkit-min-device-pixel-ratio:3) {\n    .border__1px:before {\n        transform: scaleY(0.33)\n    }\n}
\n\n

横竖屏适配

js 检测

\n
window.addEventListener(\"resize\", () => {\n  if (window.orientation === 180 || window.orientation === 0) {\n    // 正常方向或屏幕旋转180度\n    console.log(\"竖屏\");\n  }\n  if (window.orientation === 90 || window.orientation === -90) {\n    // 屏幕顺时钟旋转90度或屏幕逆时针旋转90度\n    console.log(\"横屏\");\n  }\n});
\n\n

css 检测

\n
@media screen and (orientation: portrait) {\n  /*竖屏...*/\n}\n@media screen and (orientation: landscape) {\n  /*横屏...*/\n}
\n\n

像素

    \n
  • 物理像素: 物理设备真实的像素
  • \n
  • 独立像素: 平时开发写的 px
  • \n
  • 设备像素比(DPR): = 物理像素 / 设备独立像素
  • \n
\n
// 获取 DPR\nwindow.devicePixelRatio;
\n\n

也可以使用媒体查询

\n
@media (-webkit-min-device-pixel-ratio: 2), (min-device-pixel-ratio: 2) {\n}
\n\n

srcset

使用 img 标签的 srcset 属性,浏览器会自动根据像素密度匹配最佳显示图片:

\n
<img src=\"conardLi_1x.png\" srcset=\"conardLi_2x.png 2x, conardLi_3x.png 3x\" />
\n\n

字体小于 12px

css3 的 transform 属性,设置值为 scale(x, y) 定义 2D 缩放转换

\n

css 清浮动

.clearfix::after {\n  content: \"\";\n  display: block;\n  clear: both;\n}
\n\n

ellipsis

单行

.ellipsis {\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}
\n\n

多行

.ellipsis {\n  display: -webkit-box;\n  -webkit-box-orient: vertical;\n  -webkit-line-clamp: 3; // 最多显示几行\n  overflow: hidden;\n}
\n\n

页面导入样式时,使用 link 和 @import

\n

你可能不信,ie5+就支持@import 了

\n
\n
    \n
  • link: 与dom异步加载
  • \n
  • @import: 先加载dom
  • \n
\n

em | rem 换算

相对于HTML根元素 font-size 来确定的,浏览器的默认字体高是 16px,因此:

\n
    \n
  • 16px = 1em
  • \n
  • 12px = .75em
  • \n
  • 10px = .625em
  • \n
\n

简化 rem 到 px 的换算,因为每个 rem 单位都是 10px 的倍数

\n
html {\n  font-size: 62.5%; /* 这会将默认的 16px 缩小为 10px */\n}
\n\n

也可以使用 js 检测设置

\n
// set 1rem = viewWidth / 10\nfunction setRemUnit() {\n  var docEl = document.documentElement;\n  var rem = docEl.clientWidth / 10;\n  docEl.style.fontSize = rem + \"px\";\n}\n\nwindow.addEventListener(\"resize\", setRemUnit);\nwindow.addEventListener(\"pageShow\", function (e) {\n  if (e.persisted) setRemUnit()\n});
\n\n

less 常用

变量

LESS 中的变量为完全的 常量 ,所以只能定义一次

\n
@nice-blue: #5B83AD;\n@light-blue: @nice-blue + #111;\n\n#header {\n  color: @light-blue;\n}
\n\n

输出

\n
#header {\n  color: #6c94be;\n}
\n\n

混合

.bordered {\n  border-top: dotted 1px black;\n  border-bottom: solid 2px black;\n}
\n\n

使用

\n
#menu a {\n  color: #111;\n  .bordered;\n}\n.post a {\n  color: red;\n  .bordered;\n}
\n\n

带参

\n
.border-radius (@radius) {\n  border-radius: @radius;\n  -moz-border-radius: @radius;\n  -webkit-border-radius: @radius;\n}
\n\n

#header {
.border-radius(4px);
}

\n

@arguments变量,包含了所有传递进来的参数. 如果你不想单独处理每一个参数

\n
.box-shadow (@x: 0, @y: 0, @blur: 1px, @color: #000) {\n  box-shadow: @arguments;\n  -moz-box-shadow: @arguments;\n  -webkit-box-shadow: @arguments;\n}\n.box-shadow(2px, 5px);
\n\n

无参

\n
.wrap () {\n  text-wrap: wrap;\n  white-space: pre-wrap;\n  white-space: -moz-pre-wrap;\n  word-wrap: break-word;\n}\n\npre {\n  .wrap;\n}
\n\n

lighten 和 darken

@color-base: #3bafda;\n@color-hover:lighten (@color-primary,10%);\n@color-focus:darken (@color-primary,10%);
\n\n

contrast

选择两种颜色中哪一种颜色与另一种颜色形成最大对比。

\n
p {\n  a: contrast(#bbbbbb); //output: black\n  b: contrast(#222222, #101010); //output: white\n}
\n\n

JavaScript 表达式

@var: ` \"hello\" .toUpperCase() + \"!\" `;\n@height: `document.body.clientHeight`;
\n\n

& 父选择器

& 只能代表父元素,不能代表父亲的父辈元素,施加改性类或伪类

\n
a {\n  color: blue;\n  &:hover {\n    color: green;\n  }\n}
\n\n

重复父类名

.button {\n  &-ok {\n    background-image: url(\"ok.png\");\n  }\n  &-cancel {\n    background-image: url(\"cancel.png\");\n  }\n\n  &-custom {\n    background-image: url(\"custom.png\");\n  }\n}
\n","site":{"data":{}},"excerpt":"","more":"

grid 布局

\n

flex 布局操纵的是一维、一行/一列,grid 布局具备操纵二维的能力

\n
\n

设为网格布局以后,容器子元素(项目)的floatdisplay: inline-blockdisplay: table-cellvertical-aligncolumn-等设置都将失效。

\n
    \n
  • grid-template-columns: 定义每一列的列宽
  • \n
  • grid-template-rows: 定义每一行的行高
  • \n
  • grid-row-gap: 行间距
  • \n
  • grid-column-gap: 列间距
  • \n
  • grid-gap: 行列间距合并
  • \n
  • grid-template-areas: 一个区域由单个或多个单元格组成
  • \n
  • grid-auto-flow: 默认值是 row,即”先行后列”,即先填满第一行,再开始放入第二行
  • \n
  • justify-items: 单元格内容的水平对齐
  • \n
  • align-items: 单元格内容垂直对齐
  • \n
  • place-items: align-items 属性和 justify-items 属性的合并简写形式
  • \n
  • justify-content: 整个内容区域水平对齐
  • \n
  • align-content: 整个内容区域垂直对齐
  • \n
  • place-content: align-content 属性和 justify-content 属性的合并简写形式
  • \n
\n
\n

设置的行或者列比较多的时候,可以使用 repeat()这个函数简化重复的值

\n
\n

repeat()

第一个参数是重复的次数,第二个参数是所要重复的值、也可以是某个模式

\n
/* 重复 3个100px的列 */\ngrid-template-columns: repeat(3, 100px);\n/* 重复这种布局2次 */\ngrid-template-columns: repeat(2, 100px 20px 80px);\n/* 自动填充,直到容器放不下为止 */\ngrid-template-columns: repeat(auto-fill, 100px);
\n\n

fr

方便表示比例关系,网格布局提供了 fr 关键字

\n
grid-template-columns: 1fr 1fr;
\n\n

grid-template-areas

grid-template-areas:\n  \"header header header\"\n  \"main main sidebar\"\n  \"footer footer footer\";
\n\n

item 定位

\"grid_1\"

\n
/* 1号项目就是从第二根垂直网格线开始第四根结束 */\n.item1 {\n  grid-column-start: 2;\n  grid-column-end: 4;\n  background: red;\n}
\n\n

硬件加速(IE9+)

\n

移动端开启,吃内存、增加耗电

\n
\n

animationtransformtransition不会自动开启GPU加速,利用transform: translateZ(0) 就可以开启3D变换,出发硬件加速

\n

场景:webKit内核偶尔页面闪烁:transform: translate3d(0, 0, 0);

\n

控制台打印 shield 徽章

console.log(\n  \"%c\" +\n    eval(\"'mozzie.com'\") +\n    \"%cv\" +\n    eval(\"'2.0.0'\") +\n    \"%c\\r\\n\" +\n    eval(\"'了解更多:https://www.mozzie.com'\"),\n  \"color: #fff; background: #5281FA; font-size: 12px;border-radius:2px 0 0 2px;padding:3px 6px;\",\n  \"border-radius:0 2px 2px 0;padding:3px 6px;color:#333;background:#EBEBEB\",\n  \"margin-top:10px;\"\n);
\n\n

css 判断 input 是否为空

:placeholder-shown:占位符是否显示的伪类,配合 :not() (不是必须,反过来也可以)

\n
<div id=\"demo\">\n  <input id=\"demo-input\" type=\"text\" placeholder=\"name\" />\n  <label id=\"demo-label\">Name</label>\n</div>
\n\n
#demo {\n  width: 90%;\n  max-width: 450px;\n  position: relative;\n}\n#demo-input {\n  width: 100%;\n  height: 60px;\n  line-height: 60px;\n  font-size: 20px;\n  border-bottom: 1px solid #ffa500;\n}\n#demo-input::placeholder {\n  font-size: 0;\n}\n#demo-input:focus + label,\n#demo-input:not(:placeholder-shown) + label {\n  top: 0;\n  font-size: 12px;\n}\n#demo-label {\n  font-size: 22px;\n  color: #ffa500;\n  position: absolute;\n  left: 0;\n  top: 50%;\n  transform: translateY(-50%);\n  transition: all 0.3s;\n}\n/*\n* Default\n*/\nbody {\n  height: 100vh;\n  display: flex;\n}\nbody div {\n  margin: auto;\n}\nbody div input {\n  border: 0;\n  outline: 0;\n}
\n\n

居中

transform 大法

#wrapper {\n  position: relative;\n}\n#box {\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  transform: translate(-50%, -50%);\n}
\n\n

0000 大法

#wrapper {\n  position: relative;\n}\n#box {\n  position: absolute;\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 0;\n}
\n\n

如何让 img 垂直居中

<div id=\"imgWrapper\">\n  <img src=\"xxx.png\" />\n</div>
\n\n
.imgWrapper {\n  display: table-cell;\n  text-align: center;\n  vertical-align: middle;\n}
\n\n

ul 下 li 居中

<!--外层包个div/section block元素 -->\n<div>\n  <ul class=\"clearfix\">\n    <li>1</li>\n    <li>2</li>\n  </ul>\n</div>
\n\n
div{\n  text-align: center\n}\nul{\n  display: inline-block\n}\nli{\n  display: inline\n  float: left\n}
\n\n

js 随机渐变背景

function getRandomRangeNum(min, max) {\n  return min + Math.floor(Math.random() * (max - min));\n}\n\nfunction setRandomBg(el) {\n  var left = getRandomRangeNum(0, 255);\n  var bottom = getRandomRangeNum(0, 255);\n  var css = [\n    \"linear-gradient(to left bottom,hsl(\",\n    left,\n    \", 100%, 85%) 0%,hsl(\",\n    bottom,\n    \", 100%, 85%) 100%)\",\n  ];\n  el.style.background = css.join(\"\");\n}
\n\n

css 三角形

span {\n  width: 0;\n  height: 0;\n  border-top: 40px solid transparent;\n  border-left: 40px solid transparent;\n  border-right: 40px solid transparent;\n  border-bottom: 40px solid #ff0000;\n}
\n\n

1px 神迹

pc 端最优解

<div style=\"height:1px;overflow:hidden;background:red\"></div>
\n\n

移动端 - 媒体查询 + transform

@media only screen and (-webkit-min-device-pixel-ratio: 2) {\n  .border-bottom::after {\n    -webkit-transform: scaleY(0.5);\n    transform: scaleY(0.5);\n  }\n}
\n\n

移动端 - 媒体查询 + 伪类

.border__1px:before {\n    content: '';\n    position: absolute;\n    top: 0;\n    height: 1px;\n    width: 100%\n    background-color: #000;\n    transform-origin: 50% 0%;\n}\n@media only screen and (-webkit-min-device-pixel-ratio:2) {\n    .border__1px:before {\n        transform: scaleY(0.5)\n    }\n}\n@media only screen and (-webkit-min-device-pixel-ratio:3) {\n    .border__1px:before {\n        transform: scaleY(0.33)\n    }\n}
\n\n

横竖屏适配

js 检测

\n
window.addEventListener(\"resize\", () => {\n  if (window.orientation === 180 || window.orientation === 0) {\n    // 正常方向或屏幕旋转180度\n    console.log(\"竖屏\");\n  }\n  if (window.orientation === 90 || window.orientation === -90) {\n    // 屏幕顺时钟旋转90度或屏幕逆时针旋转90度\n    console.log(\"横屏\");\n  }\n});
\n\n

css 检测

\n
@media screen and (orientation: portrait) {\n  /*竖屏...*/\n}\n@media screen and (orientation: landscape) {\n  /*横屏...*/\n}
\n\n

像素

    \n
  • 物理像素: 物理设备真实的像素
  • \n
  • 独立像素: 平时开发写的 px
  • \n
  • 设备像素比(DPR): = 物理像素 / 设备独立像素
  • \n
\n
// 获取 DPR\nwindow.devicePixelRatio;
\n\n

也可以使用媒体查询

\n
@media (-webkit-min-device-pixel-ratio: 2), (min-device-pixel-ratio: 2) {\n}
\n\n

srcset

使用 img 标签的 srcset 属性,浏览器会自动根据像素密度匹配最佳显示图片:

\n
<img src=\"conardLi_1x.png\" srcset=\"conardLi_2x.png 2x, conardLi_3x.png 3x\" />
\n\n

字体小于 12px

css3 的 transform 属性,设置值为 scale(x, y) 定义 2D 缩放转换

\n

css 清浮动

.clearfix::after {\n  content: \"\";\n  display: block;\n  clear: both;\n}
\n\n

ellipsis

单行

.ellipsis {\n  overflow: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}
\n\n

多行

.ellipsis {\n  display: -webkit-box;\n  -webkit-box-orient: vertical;\n  -webkit-line-clamp: 3; // 最多显示几行\n  overflow: hidden;\n}
\n\n

页面导入样式时,使用 link 和 @import

\n

你可能不信,ie5+就支持@import 了

\n
\n
    \n
  • link: 与dom异步加载
  • \n
  • @import: 先加载dom
  • \n
\n

em | rem 换算

相对于HTML根元素 font-size 来确定的,浏览器的默认字体高是 16px,因此:

\n
    \n
  • 16px = 1em
  • \n
  • 12px = .75em
  • \n
  • 10px = .625em
  • \n
\n

简化 rem 到 px 的换算,因为每个 rem 单位都是 10px 的倍数

\n
html {\n  font-size: 62.5%; /* 这会将默认的 16px 缩小为 10px */\n}
\n\n

也可以使用 js 检测设置

\n
// set 1rem = viewWidth / 10\nfunction setRemUnit() {\n  var docEl = document.documentElement;\n  var rem = docEl.clientWidth / 10;\n  docEl.style.fontSize = rem + \"px\";\n}\n\nwindow.addEventListener(\"resize\", setRemUnit);\nwindow.addEventListener(\"pageShow\", function (e) {\n  if (e.persisted) setRemUnit()\n});
\n\n

less 常用

变量

LESS 中的变量为完全的 常量 ,所以只能定义一次

\n
@nice-blue: #5B83AD;\n@light-blue: @nice-blue + #111;\n\n#header {\n  color: @light-blue;\n}
\n\n

输出

\n
#header {\n  color: #6c94be;\n}
\n\n

混合

.bordered {\n  border-top: dotted 1px black;\n  border-bottom: solid 2px black;\n}
\n\n

使用

\n
#menu a {\n  color: #111;\n  .bordered;\n}\n.post a {\n  color: red;\n  .bordered;\n}
\n\n

带参

\n
.border-radius (@radius) {\n  border-radius: @radius;\n  -moz-border-radius: @radius;\n  -webkit-border-radius: @radius;\n}
\n\n

#header {
.border-radius(4px);
}

\n

@arguments变量,包含了所有传递进来的参数. 如果你不想单独处理每一个参数

\n
.box-shadow (@x: 0, @y: 0, @blur: 1px, @color: #000) {\n  box-shadow: @arguments;\n  -moz-box-shadow: @arguments;\n  -webkit-box-shadow: @arguments;\n}\n.box-shadow(2px, 5px);
\n\n

无参

\n
.wrap () {\n  text-wrap: wrap;\n  white-space: pre-wrap;\n  white-space: -moz-pre-wrap;\n  word-wrap: break-word;\n}\n\npre {\n  .wrap;\n}
\n\n

lighten 和 darken

@color-base: #3bafda;\n@color-hover:lighten (@color-primary,10%);\n@color-focus:darken (@color-primary,10%);
\n\n

contrast

选择两种颜色中哪一种颜色与另一种颜色形成最大对比。

\n
p {\n  a: contrast(#bbbbbb); //output: black\n  b: contrast(#222222, #101010); //output: white\n}
\n\n

JavaScript 表达式

@var: ` \"hello\" .toUpperCase() + \"!\" `;\n@height: `document.body.clientHeight`;
\n\n

& 父选择器

& 只能代表父元素,不能代表父亲的父辈元素,施加改性类或伪类

\n
a {\n  color: blue;\n  &:hover {\n    color: green;\n  }\n}
\n\n

重复父类名

.button {\n  &-ok {\n    background-image: url(\"ok.png\");\n  }\n  &-cancel {\n    background-image: url(\"cancel.png\");\n  }\n\n  &-custom {\n    background-image: url(\"custom.png\");\n  }\n}
\n"},{"title":"jQuery","status":"done","_content":"\n# 基础\n\n在 `window` 对象中挂载了 `$`和`jQuery`\n\n```js\nwindow.jQuery();\nwindow.$ = window.jQuery;\n```\n\n# 模块化\n\n## 原始写法\n\n只要把不同的函数(以及记录状态的变量)简单地放在一起,就算是一个模块。\n\n> 缺点很明显:\"污染\"了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接关系。\n\n```js\nfunction m1() {\n //...\n}\n\nfunction m2() {\n //...\n}\n```\n\n## 对象写法\n\n把模块写成一个对象\n\n> 写法会暴露所有模块成员,内部状态可以被外部改写。\n\n```js\nvar module1 = new Object({\n _count: 0,\n\n m1: function () {\n //...\n },\n\n m2: function () {\n //...\n },\n});\n\n// 使用\nmodule1.m1();\n```\n\n## IIFE 立即执行函数写法[推荐]\n\n> 外部代码无法读取内部的成员变量\n\n```js\nvar module1 = (function () {\n var _count = 0;\n\n var m1 = function () {\n //...\n };\n\n var m2 = function () {\n //...\n };\n\n return {\n m1: m1,\n m2: m2,\n };\n})();\n\nconsole.info(module1._count); //undefined\n```\n\n## (宽)放大模式\n\n如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块,这时就有必要采用\"放大模式\"\n\n```js\nvar module1 = (function (mod) {\n mod.m3 = function () {\n //...\n };\n return mod;\n})(module1);\n```\n\nmod 可能存在异步,不知道内部哪个部分会先加载,如果采用上面的写法,第一个执行的部分有可能加载一个不存在空对象,这时就要采用\"宽放大模式\"。\n\n```js\nvar module1 = (function (mod) {\n //...\n return mod;\n})(window.module1 || {});\n```\n\n## 输入全局变量[推荐]\n\n为了在模块内部调用全局变量,必须显式地将其他变量输入模块\n\n```js\nvar module1 = (function ($, YAHOO) {\n //...\n})(jQuery, YAHOO);\n```\n\n# 链式操作\n\n```js\n$('div').find('#child').addClass('red')\n// end() 函数返回上一级\n$('div').find('#child').addClass('red').end().addClass('yellow')\n```\n\n# 选择器\n\n## 筛选器\n\n```js\n// $('li').first()\n$(\"li:first\"); //第一个元素\n// $('li').last()\n$(\"li:last\"); //最后一个元素\n$(\"tr:even\"); //索引为偶数的元素,从 0 开始\n$(\"tr:odd\"); //索引为奇数的元素,从 0 开始\n// $(\"tr\").eq(1)\n$(\"tr:eq(1)\"); //给定索引值的元素\n$(\"tr:gt(0)\"); //大于给定索引值的元素\n$(\"tr:lt(2)\"); //小于给定索引值的元素\n$(\":focus\"); //当前获取焦点的元素\n$(\":animated\"); //正在执行动画效果的元素\n```\n\n## 内容选择器\n\n```js\n$(\"div:contains('nick')\"); //包含nick文本的元素\n\n$(\"td:empty\"); //不包含子元素或者文本的空元素\n\n$(\"div:has(p)\"); //含有选择器所匹配的元素\n\n$(\"td:parent\"); //含有子元素或者文本的元素\n```\n\n## 表单选择器\n\n```js\n$(\":input\"); //匹配所有 input, textarea, select 和 button 元素\n\n$(\":text\"); //所有的单行文本框\n\n$(\":password\"); //所有密码框\n\n$(\":radio\"); //所有单选按钮\n\n$(\":checkbox\"); //所有复选框\n\n$(\":submit\"); //所有提交按钮\n\n$(\":reset\"); //所有重置按钮\n\n$(\":button\"); //所有button按钮\n\n$(\":file\"); //所有文件域\n\n$(\"input:radio[name=sex]:checked\"); //所有name 尾input选中的元素\n\n$(\"select option:selected\"); //select中所有选中的option元素\n```\n\n## 查找\n\n```js\n$(\"#xxx\"); //返回 一个 jquery 对象\n\n$(\"#xxx\").find(\".xxx\"); //查找#xxx里的.xxx元素\n\n$(\"#xxx\").parent(); //获取父类\n\n$(\"#xxx\").parents(\"xxx\"); //获取xxx祖先\n\n$(\"#xxx\").children(); //获取子类\n\n$(\"#xxx\").siblings(); //获取同一父类的其他元素\n\n$(\"#xxx\").next(); //获取下一个元素\n\n$(\"#xxx\").prev(); //获取上一个元素\n```\n\n## 属性操作\n\n```js\n$(\"img\").attr(\"src\"); //返回文档中所有图像的src属性值\n\n$(\"img\").attr(\"src\", \"test.jpg\"); //设置所有图像的src属性\n\n$(\"img\").removeAttr(\"src\"); //将文档中图像的src属性删除\n\n$(\"input[type='checkbox']\").prop(\"checked\", true); //选中复选框\n\n$(\"input[type='checkbox']\").prop(\"checked\", false);\n\n$(\"img\").removeProp(\"src\"); //删除img的src属性\n```\n\n## css 操作\n\n```js\n$(\"p\").addClass(\"selected\"); //为p元素加上 'selected' 类\n\n$(\"p\").removeClass(\"selected\"); //从p元素中删除 'selected' 类\n\n$(\"p\").toggleClass(\"selected\"); //如果存在就删除,否则就添加\n\n$(\"p\").css(\"color\"); //访问查看p元素的color属性\n\n$(\"p\").css(\"color\", \"red\"); //设置p元素的color属性为red\n\n$(\"p\").css({ color: \"red\", background: \"yellow\" }); // 设置多个属性\n```\n\n## 文档处理\n\n内部插入\n\n```js\n$(\"p\").append(\"nick\"); //每个p元素内后面追加内容\n\n$(\"p\").appendTo(\"div\"); //p元素追加到div内后\n\n$(\"p\").prepend(\"Hello\"); //每个p元素内前面追加内容\n\n$(\"p\").prependTo(\"div\"); //p元素追加到div内前\n```\n\n外部插入\n\n```js\n$(\"p\").after(\"nick\"); //每个p元素同级之后插入内容\n\n$(\"p\").before(\"nick\"); //在每个p元素同级之前插入内容\n\n$(\"p\").insertAfter(\"#test\"); //所有p元素插入到id为test元素的后面\n\n$(\"p\").insertBefore(\"#test\"); //所有p元素插入到id为test元素的前面\n```\n\n## 复制\n\n```js\n$(\"p\").clone(); //克隆元素并选中克隆的副本\n\n$(\"p\").clone(true); //布尔值指事件处理函数是否会被复制\n```\n\n# attr 和 prop 区别\n\nprop 和 attr 均可获取属性值,但 prop 获取的是 `DOM 对象内置属性`。\n\n> !! 例如 input,radio,slect 元素,请使用 prop 获取\n\n```html\n\n

\n```\n\n```js\n// 因为页面源代码中没有设置 width 属性\n$(\"img\").attr(\"width\"); // undefined\n$(\"img\").prop(\"width\"); // 284\n\n// 由于 tinyval 并非 HTML 标准属性\n$(\"p\").prop(\"tinyval\"); // undefined\n$(\"p\").attr(\"tinyval\"); // 12\n```\n\n# 事件\n\n## 事件委托\n\n- 可极大减少事件绑定次数,提高性能\n- 可让动态加入的子元素绑定相同的命令\n\n```js\nfunction handleClick(event) {\n // this 表示当前单击的元素\n alert($(this).html());\n}\n// 把子级li元素的单击事件委托在父级ul身上\n$(\"ul\").delegate(\"li\", \"click\", handleClick);\n```\n\n## 单事件单元素\n\n```js\nfunction handleClick() {}\n$xxx.on(\"click\", handleClick);\n```\n\n## 单事件多元素\n\n```js\nfunction handleClick() {}\n$xxx.on(\"click\", \"#aaa, .bbb\", handleClick);\n```\n\n## 多事件多元素\n\n```js\nfunction handleClick() {}\nfunction handleMounseEnter() {}\n$xxx.on(\n {\n click: handleClick,\n mounseenter: handleMounseEnter,\n },\n \"#aaa, .bbb\"\n);\n```\n\n## 一夜情事件\n\n```js\nfunction handleClick() {}\n$xxx.one(\"click\", handleClick);\n```\n\n# 对象操作\n\n```js\n$.trim(); //去除字符串两端的空格\n\n$.each(); //遍历一个数组或对象,for循环\n\n$.inArray(); //返回一个值在数组中的索引位置,不存在返回-1\n\n$.grep(); //返回数组中符合某种标准的元素\n\n$.extend(true, {}, a, b); // 深浅拷贝合并 a、b 到 {} 上\n\n$.makeArray(); //将对象转化为数组\n\n$.type(); //判断对象的类别(函数对象、日期对象、数组对象、正则对象等等\n\n$.isArray(); //判断某个参数是否为数组\n\n$.isEmptyObject(); //判断某个对象是否为空(不含有任何属性)\n\n$.isFunction(); //判断某个参数是否为函数\n\n$.isPlainObject(); //判断某个参数是否为用\"{}\"或\"new Object\"建立的对象\n\n$.support(); //判断浏览器是否支持某个特性\n```\n\n# 禁止右键功能菜单\n\n```js\n$(window).on({\n contextmenu: function () {\n return false;\n },\n keydown: function (e) {\n if (e.ctrlKey || (e.metaKey && (e.keyCode === 67 || e.keyCode === 8)))\n return false;\n },\n});\n```\n\n# 自动修改破损图像\n\n```js\n$(\"img\").on(\"error\", function () {\n $(this).prop(\"src\", \"img/broken.png\");\n});\n```\n\n# 模块化 jquery 最佳实践\n\n```js\n// xx 视图模块 #xx\n(function ($, $view) {\n console.log($ + \"has been imported\");\n\n // 缓存 dom需要用到的 dom,提高性能\n var $partA = $view.find(\".partA\"),\n $partB = $view.find(\".partB\");\n\n // 链式表结构,事件委托\n $view\n .on(\"focus\", \"#btnFocusPartA\", handleBtnFocusPartA)\n .on(\"click\", \".btnClickPartB\", handleBtnClickPartB);\n\n // 方法,建议函数式\n function handleBtnFocusPartA() {\n console.log($partA + \"handler\");\n }\n\n function handleBtnClickPartB(e) {\n console.log($partB + \"handler\", \",click 的 e.target\" + e.target);\n }\n})(jQuery || window.jQuery || window.$, $(\"#viewModule\"));\n```\n\n更骚的`路由表`的写法,搭配模块化开发更优雅,更方便管理\n\n```js\n// xx 视图模块 #xx\n(function ($, $view) {\n console.log($ + \"has been imported\");\n\n // 缓存 dom需要用到的 dom,提高性能\n var $partA = $view.find(\".partA\"),\n $partB = $view.find(\".partB\");\n\n // $view 视图的事件委托表\n var eventTable = [\n {\n name: \"focus\",\n elem: \"#btnFocusPartA\",\n handler: handleBtnFocusPartA,\n },\n {\n name: \"click\",\n elem: \".btnClickPartB\",\n handler: handleBtnClickPartB,\n },\n ];\n\n // 注册事件\n eventTable.forEach(function (item) {\n $view.on(item.name, item.elem, item.handler);\n });\n\n // 方法,建议函数式\n function handleBtnFocusPartA() {\n console.log($partA + \"handler\");\n }\n\n function handleBtnClickPartB(e) {\n console.log($partB + \"handler\", \",click 的 e.target\" + e.target);\n }\n})(jQuery || window.jQuery || window.$, $(\"#viewModule\"));\n```\n","source":"_posts/front-end/jquery.md","raw":"---\ntitle: jQuery\ncategories:\n - Front-End\nstatus: done\n---\n\n# 基础\n\n在 `window` 对象中挂载了 `$`和`jQuery`\n\n```js\nwindow.jQuery();\nwindow.$ = window.jQuery;\n```\n\n# 模块化\n\n## 原始写法\n\n只要把不同的函数(以及记录状态的变量)简单地放在一起,就算是一个模块。\n\n> 缺点很明显:\"污染\"了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接关系。\n\n```js\nfunction m1() {\n //...\n}\n\nfunction m2() {\n //...\n}\n```\n\n## 对象写法\n\n把模块写成一个对象\n\n> 写法会暴露所有模块成员,内部状态可以被外部改写。\n\n```js\nvar module1 = new Object({\n _count: 0,\n\n m1: function () {\n //...\n },\n\n m2: function () {\n //...\n },\n});\n\n// 使用\nmodule1.m1();\n```\n\n## IIFE 立即执行函数写法[推荐]\n\n> 外部代码无法读取内部的成员变量\n\n```js\nvar module1 = (function () {\n var _count = 0;\n\n var m1 = function () {\n //...\n };\n\n var m2 = function () {\n //...\n };\n\n return {\n m1: m1,\n m2: m2,\n };\n})();\n\nconsole.info(module1._count); //undefined\n```\n\n## (宽)放大模式\n\n如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块,这时就有必要采用\"放大模式\"\n\n```js\nvar module1 = (function (mod) {\n mod.m3 = function () {\n //...\n };\n return mod;\n})(module1);\n```\n\nmod 可能存在异步,不知道内部哪个部分会先加载,如果采用上面的写法,第一个执行的部分有可能加载一个不存在空对象,这时就要采用\"宽放大模式\"。\n\n```js\nvar module1 = (function (mod) {\n //...\n return mod;\n})(window.module1 || {});\n```\n\n## 输入全局变量[推荐]\n\n为了在模块内部调用全局变量,必须显式地将其他变量输入模块\n\n```js\nvar module1 = (function ($, YAHOO) {\n //...\n})(jQuery, YAHOO);\n```\n\n# 链式操作\n\n```js\n$('div').find('#child').addClass('red')\n// end() 函数返回上一级\n$('div').find('#child').addClass('red').end().addClass('yellow')\n```\n\n# 选择器\n\n## 筛选器\n\n```js\n// $('li').first()\n$(\"li:first\"); //第一个元素\n// $('li').last()\n$(\"li:last\"); //最后一个元素\n$(\"tr:even\"); //索引为偶数的元素,从 0 开始\n$(\"tr:odd\"); //索引为奇数的元素,从 0 开始\n// $(\"tr\").eq(1)\n$(\"tr:eq(1)\"); //给定索引值的元素\n$(\"tr:gt(0)\"); //大于给定索引值的元素\n$(\"tr:lt(2)\"); //小于给定索引值的元素\n$(\":focus\"); //当前获取焦点的元素\n$(\":animated\"); //正在执行动画效果的元素\n```\n\n## 内容选择器\n\n```js\n$(\"div:contains('nick')\"); //包含nick文本的元素\n\n$(\"td:empty\"); //不包含子元素或者文本的空元素\n\n$(\"div:has(p)\"); //含有选择器所匹配的元素\n\n$(\"td:parent\"); //含有子元素或者文本的元素\n```\n\n## 表单选择器\n\n```js\n$(\":input\"); //匹配所有 input, textarea, select 和 button 元素\n\n$(\":text\"); //所有的单行文本框\n\n$(\":password\"); //所有密码框\n\n$(\":radio\"); //所有单选按钮\n\n$(\":checkbox\"); //所有复选框\n\n$(\":submit\"); //所有提交按钮\n\n$(\":reset\"); //所有重置按钮\n\n$(\":button\"); //所有button按钮\n\n$(\":file\"); //所有文件域\n\n$(\"input:radio[name=sex]:checked\"); //所有name 尾input选中的元素\n\n$(\"select option:selected\"); //select中所有选中的option元素\n```\n\n## 查找\n\n```js\n$(\"#xxx\"); //返回 一个 jquery 对象\n\n$(\"#xxx\").find(\".xxx\"); //查找#xxx里的.xxx元素\n\n$(\"#xxx\").parent(); //获取父类\n\n$(\"#xxx\").parents(\"xxx\"); //获取xxx祖先\n\n$(\"#xxx\").children(); //获取子类\n\n$(\"#xxx\").siblings(); //获取同一父类的其他元素\n\n$(\"#xxx\").next(); //获取下一个元素\n\n$(\"#xxx\").prev(); //获取上一个元素\n```\n\n## 属性操作\n\n```js\n$(\"img\").attr(\"src\"); //返回文档中所有图像的src属性值\n\n$(\"img\").attr(\"src\", \"test.jpg\"); //设置所有图像的src属性\n\n$(\"img\").removeAttr(\"src\"); //将文档中图像的src属性删除\n\n$(\"input[type='checkbox']\").prop(\"checked\", true); //选中复选框\n\n$(\"input[type='checkbox']\").prop(\"checked\", false);\n\n$(\"img\").removeProp(\"src\"); //删除img的src属性\n```\n\n## css 操作\n\n```js\n$(\"p\").addClass(\"selected\"); //为p元素加上 'selected' 类\n\n$(\"p\").removeClass(\"selected\"); //从p元素中删除 'selected' 类\n\n$(\"p\").toggleClass(\"selected\"); //如果存在就删除,否则就添加\n\n$(\"p\").css(\"color\"); //访问查看p元素的color属性\n\n$(\"p\").css(\"color\", \"red\"); //设置p元素的color属性为red\n\n$(\"p\").css({ color: \"red\", background: \"yellow\" }); // 设置多个属性\n```\n\n## 文档处理\n\n内部插入\n\n```js\n$(\"p\").append(\"nick\"); //每个p元素内后面追加内容\n\n$(\"p\").appendTo(\"div\"); //p元素追加到div内后\n\n$(\"p\").prepend(\"Hello\"); //每个p元素内前面追加内容\n\n$(\"p\").prependTo(\"div\"); //p元素追加到div内前\n```\n\n外部插入\n\n```js\n$(\"p\").after(\"nick\"); //每个p元素同级之后插入内容\n\n$(\"p\").before(\"nick\"); //在每个p元素同级之前插入内容\n\n$(\"p\").insertAfter(\"#test\"); //所有p元素插入到id为test元素的后面\n\n$(\"p\").insertBefore(\"#test\"); //所有p元素插入到id为test元素的前面\n```\n\n## 复制\n\n```js\n$(\"p\").clone(); //克隆元素并选中克隆的副本\n\n$(\"p\").clone(true); //布尔值指事件处理函数是否会被复制\n```\n\n# attr 和 prop 区别\n\nprop 和 attr 均可获取属性值,但 prop 获取的是 `DOM 对象内置属性`。\n\n> !! 例如 input,radio,slect 元素,请使用 prop 获取\n\n```html\n\n

\n```\n\n```js\n// 因为页面源代码中没有设置 width 属性\n$(\"img\").attr(\"width\"); // undefined\n$(\"img\").prop(\"width\"); // 284\n\n// 由于 tinyval 并非 HTML 标准属性\n$(\"p\").prop(\"tinyval\"); // undefined\n$(\"p\").attr(\"tinyval\"); // 12\n```\n\n# 事件\n\n## 事件委托\n\n- 可极大减少事件绑定次数,提高性能\n- 可让动态加入的子元素绑定相同的命令\n\n```js\nfunction handleClick(event) {\n // this 表示当前单击的元素\n alert($(this).html());\n}\n// 把子级li元素的单击事件委托在父级ul身上\n$(\"ul\").delegate(\"li\", \"click\", handleClick);\n```\n\n## 单事件单元素\n\n```js\nfunction handleClick() {}\n$xxx.on(\"click\", handleClick);\n```\n\n## 单事件多元素\n\n```js\nfunction handleClick() {}\n$xxx.on(\"click\", \"#aaa, .bbb\", handleClick);\n```\n\n## 多事件多元素\n\n```js\nfunction handleClick() {}\nfunction handleMounseEnter() {}\n$xxx.on(\n {\n click: handleClick,\n mounseenter: handleMounseEnter,\n },\n \"#aaa, .bbb\"\n);\n```\n\n## 一夜情事件\n\n```js\nfunction handleClick() {}\n$xxx.one(\"click\", handleClick);\n```\n\n# 对象操作\n\n```js\n$.trim(); //去除字符串两端的空格\n\n$.each(); //遍历一个数组或对象,for循环\n\n$.inArray(); //返回一个值在数组中的索引位置,不存在返回-1\n\n$.grep(); //返回数组中符合某种标准的元素\n\n$.extend(true, {}, a, b); // 深浅拷贝合并 a、b 到 {} 上\n\n$.makeArray(); //将对象转化为数组\n\n$.type(); //判断对象的类别(函数对象、日期对象、数组对象、正则对象等等\n\n$.isArray(); //判断某个参数是否为数组\n\n$.isEmptyObject(); //判断某个对象是否为空(不含有任何属性)\n\n$.isFunction(); //判断某个参数是否为函数\n\n$.isPlainObject(); //判断某个参数是否为用\"{}\"或\"new Object\"建立的对象\n\n$.support(); //判断浏览器是否支持某个特性\n```\n\n# 禁止右键功能菜单\n\n```js\n$(window).on({\n contextmenu: function () {\n return false;\n },\n keydown: function (e) {\n if (e.ctrlKey || (e.metaKey && (e.keyCode === 67 || e.keyCode === 8)))\n return false;\n },\n});\n```\n\n# 自动修改破损图像\n\n```js\n$(\"img\").on(\"error\", function () {\n $(this).prop(\"src\", \"img/broken.png\");\n});\n```\n\n# 模块化 jquery 最佳实践\n\n```js\n// xx 视图模块 #xx\n(function ($, $view) {\n console.log($ + \"has been imported\");\n\n // 缓存 dom需要用到的 dom,提高性能\n var $partA = $view.find(\".partA\"),\n $partB = $view.find(\".partB\");\n\n // 链式表结构,事件委托\n $view\n .on(\"focus\", \"#btnFocusPartA\", handleBtnFocusPartA)\n .on(\"click\", \".btnClickPartB\", handleBtnClickPartB);\n\n // 方法,建议函数式\n function handleBtnFocusPartA() {\n console.log($partA + \"handler\");\n }\n\n function handleBtnClickPartB(e) {\n console.log($partB + \"handler\", \",click 的 e.target\" + e.target);\n }\n})(jQuery || window.jQuery || window.$, $(\"#viewModule\"));\n```\n\n更骚的`路由表`的写法,搭配模块化开发更优雅,更方便管理\n\n```js\n// xx 视图模块 #xx\n(function ($, $view) {\n console.log($ + \"has been imported\");\n\n // 缓存 dom需要用到的 dom,提高性能\n var $partA = $view.find(\".partA\"),\n $partB = $view.find(\".partB\");\n\n // $view 视图的事件委托表\n var eventTable = [\n {\n name: \"focus\",\n elem: \"#btnFocusPartA\",\n handler: handleBtnFocusPartA,\n },\n {\n name: \"click\",\n elem: \".btnClickPartB\",\n handler: handleBtnClickPartB,\n },\n ];\n\n // 注册事件\n eventTable.forEach(function (item) {\n $view.on(item.name, item.elem, item.handler);\n });\n\n // 方法,建议函数式\n function handleBtnFocusPartA() {\n console.log($partA + \"handler\");\n }\n\n function handleBtnClickPartB(e) {\n console.log($partB + \"handler\", \",click 的 e.target\" + e.target);\n }\n})(jQuery || window.jQuery || window.$, $(\"#viewModule\"));\n```\n","slug":"front-end/jquery","published":1,"date":"2023-11-06T08:01:17.912Z","updated":"2023-11-06T08:02:35.529Z","comments":1,"layout":"post","photos":[],"link":"","_id":"clp7x194y000dv3z35mt50jup","content":"

基础

window 对象中挂载了 $jQuery

\n
window.jQuery();\nwindow.$ = window.jQuery;
\n\n

模块化

原始写法

只要把不同的函数(以及记录状态的变量)简单地放在一起,就算是一个模块。

\n
\n

缺点很明显:”污染”了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接关系。

\n
\n
function m1() {\n  //...\n}\n\nfunction m2() {\n  //...\n}
\n\n

对象写法

把模块写成一个对象

\n
\n

写法会暴露所有模块成员,内部状态可以被外部改写。

\n
\n
var module1 = new Object({\n  _count: 0,\n\n  m1: function () {\n    //...\n  },\n\n  m2: function () {\n    //...\n  },\n});\n\n// 使用\nmodule1.m1();
\n\n

IIFE 立即执行函数写法[推荐]

\n

外部代码无法读取内部的成员变量

\n
\n
var module1 = (function () {\n  var _count = 0;\n\n  var m1 = function () {\n    //...\n  };\n\n  var m2 = function () {\n    //...\n  };\n\n  return {\n    m1: m1,\n    m2: m2,\n  };\n})();\n\nconsole.info(module1._count); //undefined
\n\n

(宽)放大模式

如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块,这时就有必要采用”放大模式”

\n
var module1 = (function (mod) {\n  mod.m3 = function () {\n    //...\n  };\n  return mod;\n})(module1);
\n\n

mod 可能存在异步,不知道内部哪个部分会先加载,如果采用上面的写法,第一个执行的部分有可能加载一个不存在空对象,这时就要采用”宽放大模式”。

\n
var module1 = (function (mod) {\n  //...\n  return mod;\n})(window.module1 || {});
\n\n

输入全局变量[推荐]

为了在模块内部调用全局变量,必须显式地将其他变量输入模块

\n
var module1 = (function ($, YAHOO) {\n  //...\n})(jQuery, YAHOO);
\n\n

链式操作

$('div').find('#child').addClass('red')\n// end() 函数返回上一级\n$('div').find('#child').addClass('red').end().addClass('yellow'
\n\n

选择器

筛选器

// $('li').first()\n$(\"li:first\"); //第一个元素\n// $('li').last()\n$(\"li:last\"); //最后一个元素\n$(\"tr:even\"); //索引为偶数的元素,从 0 开始\n$(\"tr:odd\"); //索引为奇数的元素,从 0 开始\n// $(\"tr\").eq(1)\n$(\"tr:eq(1)\"); //给定索引值的元素\n$(\"tr:gt(0)\"); //大于给定索引值的元素\n$(\"tr:lt(2)\"); //小于给定索引值的元素\n$(\":focus\"); //当前获取焦点的元素\n$(\":animated\"); //正在执行动画效果的元素
\n\n

内容选择器

$(\"div:contains('nick')\"); //包含nick文本的元素\n\n$(\"td:empty\"); //不包含子元素或者文本的空元素\n\n$(\"div:has(p)\"); //含有选择器所匹配的元素\n\n$(\"td:parent\"); //含有子元素或者文本的元素
\n\n

表单选择器

$(\":input\"); //匹配所有 input, textarea, select 和 button 元素\n\n$(\":text\"); //所有的单行文本框\n\n$(\":password\"); //所有密码框\n\n$(\":radio\"); //所有单选按钮\n\n$(\":checkbox\"); //所有复选框\n\n$(\":submit\"); //所有提交按钮\n\n$(\":reset\"); //所有重置按钮\n\n$(\":button\"); //所有button按钮\n\n$(\":file\"); //所有文件域\n\n$(\"input:radio[name=sex]:checked\"); //所有name 尾input选中的元素\n\n$(\"select option:selected\"); //select中所有选中的option元素
\n\n

查找

$(\"#xxx\"); //返回 一个 jquery 对象\n\n$(\"#xxx\").find(\".xxx\"); //查找#xxx里的.xxx元素\n\n$(\"#xxx\").parent(); //获取父类\n\n$(\"#xxx\").parents(\"xxx\"); //获取xxx祖先\n\n$(\"#xxx\").children(); //获取子类\n\n$(\"#xxx\").siblings(); //获取同一父类的其他元素\n\n$(\"#xxx\").next(); //获取下一个元素\n\n$(\"#xxx\").prev(); //获取上一个元素
\n\n

属性操作

$(\"img\").attr(\"src\"); //返回文档中所有图像的src属性值\n\n$(\"img\").attr(\"src\", \"test.jpg\"); //设置所有图像的src属性\n\n$(\"img\").removeAttr(\"src\"); //将文档中图像的src属性删除\n\n$(\"input[type='checkbox']\").prop(\"checked\", true); //选中复选框\n\n$(\"input[type='checkbox']\").prop(\"checked\", false);\n\n$(\"img\").removeProp(\"src\"); //删除img的src属性
\n\n

css 操作

$(\"p\").addClass(\"selected\"); //为p元素加上 'selected' 类\n\n$(\"p\").removeClass(\"selected\"); //从p元素中删除 'selected' 类\n\n$(\"p\").toggleClass(\"selected\"); //如果存在就删除,否则就添加\n\n$(\"p\").css(\"color\"); //访问查看p元素的color属性\n\n$(\"p\").css(\"color\", \"red\"); //设置p元素的color属性为red\n\n$(\"p\").css({ color: \"red\", background: \"yellow\" }); // 设置多个属性
\n\n

文档处理

内部插入

\n
$(\"p\").append(\"<b>nick</b>\"); //每个p元素内后面追加内容\n\n$(\"p\").appendTo(\"div\"); //p元素追加到div内后\n\n$(\"p\").prepend(\"<b>Hello</b>\"); //每个p元素内前面追加内容\n\n$(\"p\").prependTo(\"div\"); //p元素追加到div内前
\n\n

外部插入

\n
$(\"p\").after(\"<b>nick</b>\"); //每个p元素同级之后插入内容\n\n$(\"p\").before(\"<b>nick</b>\"); //在每个p元素同级之前插入内容\n\n$(\"p\").insertAfter(\"#test\"); //所有p元素插入到id为test元素的后面\n\n$(\"p\").insertBefore(\"#test\"); //所有p元素插入到id为test元素的前面
\n\n

复制

$(\"p\").clone(); //克隆元素并选中克隆的副本\n\n$(\"p\").clone(true); //布尔值指事件处理函数是否会被复制
\n\n

attr 和 prop 区别

prop 和 attr 均可获取属性值,但 prop 获取的是 DOM 对象内置属性

\n
\n

!! 例如 input,radio,slect 元素,请使用 prop 获取

\n
\n
<img src=\"https://www.runoob.com/images/pulpit.jpg\" />\n<p tinyval=\"12\"></p>
\n\n
// 因为页面源代码中没有设置 width 属性\n$(\"img\").attr(\"width\"); // undefined\n$(\"img\").prop(\"width\"); // 284\n\n// 由于 tinyval 并非 HTML 标准属性\n$(\"p\").prop(\"tinyval\"); // undefined\n$(\"p\").attr(\"tinyval\"); // 12
\n\n

事件

事件委托

    \n
  • 可极大减少事件绑定次数,提高性能
  • \n
  • 可让动态加入的子元素绑定相同的命令
  • \n
\n
function handleClick(event) {\n  // this 表示当前单击的元素\n  alert($(this).html());\n}\n// 把子级li元素的单击事件委托在父级ul身上\n$(\"ul\").delegate(\"li\", \"click\", handleClick);
\n\n

单事件单元素

function handleClick() {}\n$xxx.on(\"click\", handleClick);
\n\n

单事件多元素

function handleClick() {}\n$xxx.on(\"click\", \"#aaa, .bbb\", handleClick);
\n\n

多事件多元素

function handleClick() {}\nfunction handleMounseEnter() {}\n$xxx.on(\n  {\n    click: handleClick,\n    mounseenter: handleMounseEnter,\n  },\n  \"#aaa, .bbb\"\n);
\n\n

一夜情事件

function handleClick() {}\n$xxx.one(\"click\", handleClick);
\n\n

对象操作

$.trim(); //去除字符串两端的空格\n\n$.each(); //遍历一个数组或对象,for循环\n\n$.inArray(); //返回一个值在数组中的索引位置,不存在返回-1\n\n$.grep(); //返回数组中符合某种标准的元素\n\n$.extend(true, {}, a, b); // 深浅拷贝合并 a、b 到 {} 上\n\n$.makeArray(); //将对象转化为数组\n\n$.type(); //判断对象的类别(函数对象、日期对象、数组对象、正则对象等等\n\n$.isArray(); //判断某个参数是否为数组\n\n$.isEmptyObject(); //判断某个对象是否为空(不含有任何属性)\n\n$.isFunction(); //判断某个参数是否为函数\n\n$.isPlainObject(); //判断某个参数是否为用\"{}\"或\"new Object\"建立的对象\n\n$.support(); //判断浏览器是否支持某个特性
\n\n

禁止右键功能菜单

$(window).on({\n  contextmenu: function () {\n    return false;\n  },\n  keydown: function (e) {\n    if (e.ctrlKey || (e.metaKey && (e.keyCode === 67 || e.keyCode === 8)))\n      return false;\n  },\n});
\n\n

自动修改破损图像

$(\"img\").on(\"error\", function () {\n  $(this).prop(\"src\", \"img/broken.png\");\n});
\n\n

模块化 jquery 最佳实践

// xx 视图模块 #xx\n(function ($, $view) {\n  console.log($ + \"has been imported\");\n\n  // 缓存 dom需要用到的 dom,提高性能\n  var $partA = $view.find(\".partA\"),\n    $partB = $view.find(\".partB\");\n\n  // 链式表结构,事件委托\n  $view\n    .on(\"focus\", \"#btnFocusPartA\", handleBtnFocusPartA)\n    .on(\"click\", \".btnClickPartB\", handleBtnClickPartB);\n\n  // 方法,建议函数式\n  function handleBtnFocusPartA() {\n    console.log($partA + \"handler\");\n  }\n\n  function handleBtnClickPartB(e) {\n    console.log($partB + \"handler\", \",click 的 e.target\" + e.target);\n  }\n})(jQuery || window.jQuery || window.$, $(\"#viewModule\"));
\n\n

更骚的路由表的写法,搭配模块化开发更优雅,更方便管理

\n
// xx 视图模块 #xx\n(function ($, $view) {\n  console.log($ + \"has been imported\");\n\n  // 缓存 dom需要用到的 dom,提高性能\n  var $partA = $view.find(\".partA\"),\n    $partB = $view.find(\".partB\");\n\n  // $view 视图的事件委托表\n  var eventTable = [\n    {\n      name: \"focus\",\n      elem: \"#btnFocusPartA\",\n      handler: handleBtnFocusPartA,\n    },\n    {\n      name: \"click\",\n      elem: \".btnClickPartB\",\n      handler: handleBtnClickPartB,\n    },\n  ];\n\n  // 注册事件\n  eventTable.forEach(function (item) {\n    $view.on(item.name, item.elem, item.handler);\n  });\n\n  // 方法,建议函数式\n  function handleBtnFocusPartA() {\n    console.log($partA + \"handler\");\n  }\n\n  function handleBtnClickPartB(e) {\n    console.log($partB + \"handler\", \",click 的 e.target\" + e.target);\n  }\n})(jQuery || window.jQuery || window.$, $(\"#viewModule\"));
\n","site":{"data":{}},"excerpt":"","more":"

基础

window 对象中挂载了 $jQuery

\n
window.jQuery();\nwindow.$ = window.jQuery;
\n\n

模块化

原始写法

只要把不同的函数(以及记录状态的变量)简单地放在一起,就算是一个模块。

\n
\n

缺点很明显:”污染”了全局变量,无法保证不与其他模块发生变量名冲突,而且模块成员之间看不出直接关系。

\n
\n
function m1() {\n  //...\n}\n\nfunction m2() {\n  //...\n}
\n\n

对象写法

把模块写成一个对象

\n
\n

写法会暴露所有模块成员,内部状态可以被外部改写。

\n
\n
var module1 = new Object({\n  _count: 0,\n\n  m1: function () {\n    //...\n  },\n\n  m2: function () {\n    //...\n  },\n});\n\n// 使用\nmodule1.m1();
\n\n

IIFE 立即执行函数写法[推荐]

\n

外部代码无法读取内部的成员变量

\n
\n
var module1 = (function () {\n  var _count = 0;\n\n  var m1 = function () {\n    //...\n  };\n\n  var m2 = function () {\n    //...\n  };\n\n  return {\n    m1: m1,\n    m2: m2,\n  };\n})();\n\nconsole.info(module1._count); //undefined
\n\n

(宽)放大模式

如果一个模块很大,必须分成几个部分,或者一个模块需要继承另一个模块,这时就有必要采用”放大模式”

\n
var module1 = (function (mod) {\n  mod.m3 = function () {\n    //...\n  };\n  return mod;\n})(module1);
\n\n

mod 可能存在异步,不知道内部哪个部分会先加载,如果采用上面的写法,第一个执行的部分有可能加载一个不存在空对象,这时就要采用”宽放大模式”。

\n
var module1 = (function (mod) {\n  //...\n  return mod;\n})(window.module1 || {});
\n\n

输入全局变量[推荐]

为了在模块内部调用全局变量,必须显式地将其他变量输入模块

\n
var module1 = (function ($, YAHOO) {\n  //...\n})(jQuery, YAHOO);
\n\n

链式操作

$('div').find('#child').addClass('red')\n// end() 函数返回上一级\n$('div').find('#child').addClass('red').end().addClass('yellow'
\n\n

选择器

筛选器

// $('li').first()\n$(\"li:first\"); //第一个元素\n// $('li').last()\n$(\"li:last\"); //最后一个元素\n$(\"tr:even\"); //索引为偶数的元素,从 0 开始\n$(\"tr:odd\"); //索引为奇数的元素,从 0 开始\n// $(\"tr\").eq(1)\n$(\"tr:eq(1)\"); //给定索引值的元素\n$(\"tr:gt(0)\"); //大于给定索引值的元素\n$(\"tr:lt(2)\"); //小于给定索引值的元素\n$(\":focus\"); //当前获取焦点的元素\n$(\":animated\"); //正在执行动画效果的元素
\n\n

内容选择器

$(\"div:contains('nick')\"); //包含nick文本的元素\n\n$(\"td:empty\"); //不包含子元素或者文本的空元素\n\n$(\"div:has(p)\"); //含有选择器所匹配的元素\n\n$(\"td:parent\"); //含有子元素或者文本的元素
\n\n

表单选择器

$(\":input\"); //匹配所有 input, textarea, select 和 button 元素\n\n$(\":text\"); //所有的单行文本框\n\n$(\":password\"); //所有密码框\n\n$(\":radio\"); //所有单选按钮\n\n$(\":checkbox\"); //所有复选框\n\n$(\":submit\"); //所有提交按钮\n\n$(\":reset\"); //所有重置按钮\n\n$(\":button\"); //所有button按钮\n\n$(\":file\"); //所有文件域\n\n$(\"input:radio[name=sex]:checked\"); //所有name 尾input选中的元素\n\n$(\"select option:selected\"); //select中所有选中的option元素
\n\n

查找

$(\"#xxx\"); //返回 一个 jquery 对象\n\n$(\"#xxx\").find(\".xxx\"); //查找#xxx里的.xxx元素\n\n$(\"#xxx\").parent(); //获取父类\n\n$(\"#xxx\").parents(\"xxx\"); //获取xxx祖先\n\n$(\"#xxx\").children(); //获取子类\n\n$(\"#xxx\").siblings(); //获取同一父类的其他元素\n\n$(\"#xxx\").next(); //获取下一个元素\n\n$(\"#xxx\").prev(); //获取上一个元素
\n\n

属性操作

$(\"img\").attr(\"src\"); //返回文档中所有图像的src属性值\n\n$(\"img\").attr(\"src\", \"test.jpg\"); //设置所有图像的src属性\n\n$(\"img\").removeAttr(\"src\"); //将文档中图像的src属性删除\n\n$(\"input[type='checkbox']\").prop(\"checked\", true); //选中复选框\n\n$(\"input[type='checkbox']\").prop(\"checked\", false);\n\n$(\"img\").removeProp(\"src\"); //删除img的src属性
\n\n

css 操作

$(\"p\").addClass(\"selected\"); //为p元素加上 'selected' 类\n\n$(\"p\").removeClass(\"selected\"); //从p元素中删除 'selected' 类\n\n$(\"p\").toggleClass(\"selected\"); //如果存在就删除,否则就添加\n\n$(\"p\").css(\"color\"); //访问查看p元素的color属性\n\n$(\"p\").css(\"color\", \"red\"); //设置p元素的color属性为red\n\n$(\"p\").css({ color: \"red\", background: \"yellow\" }); // 设置多个属性
\n\n

文档处理

内部插入

\n
$(\"p\").append(\"<b>nick</b>\"); //每个p元素内后面追加内容\n\n$(\"p\").appendTo(\"div\"); //p元素追加到div内后\n\n$(\"p\").prepend(\"<b>Hello</b>\"); //每个p元素内前面追加内容\n\n$(\"p\").prependTo(\"div\"); //p元素追加到div内前
\n\n

外部插入

\n
$(\"p\").after(\"<b>nick</b>\"); //每个p元素同级之后插入内容\n\n$(\"p\").before(\"<b>nick</b>\"); //在每个p元素同级之前插入内容\n\n$(\"p\").insertAfter(\"#test\"); //所有p元素插入到id为test元素的后面\n\n$(\"p\").insertBefore(\"#test\"); //所有p元素插入到id为test元素的前面
\n\n

复制

$(\"p\").clone(); //克隆元素并选中克隆的副本\n\n$(\"p\").clone(true); //布尔值指事件处理函数是否会被复制
\n\n

attr 和 prop 区别

prop 和 attr 均可获取属性值,但 prop 获取的是 DOM 对象内置属性

\n
\n

!! 例如 input,radio,slect 元素,请使用 prop 获取

\n
\n
<img src=\"https://www.runoob.com/images/pulpit.jpg\" />\n<p tinyval=\"12\"></p>
\n\n
// 因为页面源代码中没有设置 width 属性\n$(\"img\").attr(\"width\"); // undefined\n$(\"img\").prop(\"width\"); // 284\n\n// 由于 tinyval 并非 HTML 标准属性\n$(\"p\").prop(\"tinyval\"); // undefined\n$(\"p\").attr(\"tinyval\"); // 12
\n\n

事件

事件委托

    \n
  • 可极大减少事件绑定次数,提高性能
  • \n
  • 可让动态加入的子元素绑定相同的命令
  • \n
\n
function handleClick(event) {\n  // this 表示当前单击的元素\n  alert($(this).html());\n}\n// 把子级li元素的单击事件委托在父级ul身上\n$(\"ul\").delegate(\"li\", \"click\", handleClick);
\n\n

单事件单元素

function handleClick() {}\n$xxx.on(\"click\", handleClick);
\n\n

单事件多元素

function handleClick() {}\n$xxx.on(\"click\", \"#aaa, .bbb\", handleClick);
\n\n

多事件多元素

function handleClick() {}\nfunction handleMounseEnter() {}\n$xxx.on(\n  {\n    click: handleClick,\n    mounseenter: handleMounseEnter,\n  },\n  \"#aaa, .bbb\"\n);
\n\n

一夜情事件

function handleClick() {}\n$xxx.one(\"click\", handleClick);
\n\n

对象操作

$.trim(); //去除字符串两端的空格\n\n$.each(); //遍历一个数组或对象,for循环\n\n$.inArray(); //返回一个值在数组中的索引位置,不存在返回-1\n\n$.grep(); //返回数组中符合某种标准的元素\n\n$.extend(true, {}, a, b); // 深浅拷贝合并 a、b 到 {} 上\n\n$.makeArray(); //将对象转化为数组\n\n$.type(); //判断对象的类别(函数对象、日期对象、数组对象、正则对象等等\n\n$.isArray(); //判断某个参数是否为数组\n\n$.isEmptyObject(); //判断某个对象是否为空(不含有任何属性)\n\n$.isFunction(); //判断某个参数是否为函数\n\n$.isPlainObject(); //判断某个参数是否为用\"{}\"或\"new Object\"建立的对象\n\n$.support(); //判断浏览器是否支持某个特性
\n\n

禁止右键功能菜单

$(window).on({\n  contextmenu: function () {\n    return false;\n  },\n  keydown: function (e) {\n    if (e.ctrlKey || (e.metaKey && (e.keyCode === 67 || e.keyCode === 8)))\n      return false;\n  },\n});
\n\n

自动修改破损图像

$(\"img\").on(\"error\", function () {\n  $(this).prop(\"src\", \"img/broken.png\");\n});
\n\n

模块化 jquery 最佳实践

// xx 视图模块 #xx\n(function ($, $view) {\n  console.log($ + \"has been imported\");\n\n  // 缓存 dom需要用到的 dom,提高性能\n  var $partA = $view.find(\".partA\"),\n    $partB = $view.find(\".partB\");\n\n  // 链式表结构,事件委托\n  $view\n    .on(\"focus\", \"#btnFocusPartA\", handleBtnFocusPartA)\n    .on(\"click\", \".btnClickPartB\", handleBtnClickPartB);\n\n  // 方法,建议函数式\n  function handleBtnFocusPartA() {\n    console.log($partA + \"handler\");\n  }\n\n  function handleBtnClickPartB(e) {\n    console.log($partB + \"handler\", \",click 的 e.target\" + e.target);\n  }\n})(jQuery || window.jQuery || window.$, $(\"#viewModule\"));
\n\n

更骚的路由表的写法,搭配模块化开发更优雅,更方便管理

\n
// xx 视图模块 #xx\n(function ($, $view) {\n  console.log($ + \"has been imported\");\n\n  // 缓存 dom需要用到的 dom,提高性能\n  var $partA = $view.find(\".partA\"),\n    $partB = $view.find(\".partB\");\n\n  // $view 视图的事件委托表\n  var eventTable = [\n    {\n      name: \"focus\",\n      elem: \"#btnFocusPartA\",\n      handler: handleBtnFocusPartA,\n    },\n    {\n      name: \"click\",\n      elem: \".btnClickPartB\",\n      handler: handleBtnClickPartB,\n    },\n  ];\n\n  // 注册事件\n  eventTable.forEach(function (item) {\n    $view.on(item.name, item.elem, item.handler);\n  });\n\n  // 方法,建议函数式\n  function handleBtnFocusPartA() {\n    console.log($partA + \"handler\");\n  }\n\n  function handleBtnClickPartB(e) {\n    console.log($partB + \"handler\", \",click 的 e.target\" + e.target);\n  }\n})(jQuery || window.jQuery || window.$, $(\"#viewModule\"));
\n"},{"title":"领域驱动设计","status":"doing","_content":"\n\n# 参考文献\n\n- [蚂蚁金服数据体验技术团队 - 领域驱动设计](https://juejin.cn/post/6844903618680881165)\n- [美团 - 领域驱动设计在互联网业务开发中的实践](https://tech.meituan.com/2017/12/22/ddd-in-practice.html)\n- [领域驱动实战思考](https://huhao.dev/posts/61190ae2/#%E9%97%AE%E9%A2%98%E5%AD%90%E5%9F%9F%E8%AF%86%E5%88%AB)\n- [基于 DDD 的前端项目架构设计与实战](https://www.tangshuang.net/8663.html)\n- [React 语境下前端 DDD 的思考](https://www.tangshuang.net/8212.html)\n\n# 架构对比\n\n从后端视角看,对比传统的三层架构,领域驱动\n\n- `充血`复用了:领域对外的接口\n- `领域服务`封装了:领域之间的联系\n\n贫血模型->充血模型,降低了`service层`的负担,同时保证了业务迭代,`entity`的独立性\n\n## MVC\n\n从数据库视角、分析实体出发,进行系统的构建\n\n- DAO 层\n- Service 层\n- Controller 层\n\n## 后端 DDD\n\n> 针对基础设施层,也可以考虑加入`防腐层`、`工厂`\n\n一个领域基本四层架构\n\n1. 用户层(user interface): 对标 controller,对外提供 web 服务、接口\n2. 应用层(application): 业务层,定义领域可以解决的问题 `domainService`\n3. 领域层(domain): 纯粹的描述业务实体\n4. 基础设施层(infra): 持久化层(Builder+repository)\n\n同时,领域拆分带来如下两个致命问题\n\n- 领域化(微服务)后,数据库是分开的,导致实体调用链复杂,避免跨库查询\n- 避免分布式事务同步\n\n## 前端 DDD\n\n> ?? 这里存在一个问题,api 网关到底要不要挪到前端领域目录下来开发\n\n1. 用户层: web、mobile、mini-program ...多端\n2. service: 暴露给 UI 组件的 `domainSerivce`,组织实体的状态流转\n3. entity: 实体、聚合实体、事件\n4. infra: api 请求、缓存、工具\n\n## 工厂 Factory / Builder\n\n- 数据库表设计层面,如果要`多对多`的关系,只能很别扭的设计一个中间表。\n- 实体 A 适合 `mongo`文档形存储,实体 B 适合`mysql`关系型存储。对于 `Builder` 而言,直接在这个领域搓出来 `A 和 B` 的 `CRUD`的实现方法\n\n## 防腐层 Facade / Adaptor\n\n也被成为适配器,隔离第三方/外部服务,例如场景,用户上传文件到阿里云、腾讯云\n\n```js\nclass UserUploadServiceFacade extends UserService {\n /**\n * 上传\n */\n async upload(oss) {\n if (oss === \"ali\") return \"OK\";\n if (oss === \"tx\") return \"FAIL\";\n }\n}\n```\n\n# 领域模型\n\n实体的`贫血->充血`是一个随着业务发展过程演变的结果,初期业务场景不明确,很难充血,瞎几把充血,纯属找难受。\n\n是否需要引入`聚合根(aggregate root)`的概念,解决领域内实体独立、平铺关系带来的不方便,这个可能比较看心情\n\n## 贫血模型(POJO)的问题\n\n> 脱离了业务复杂度谈分层,好比抛开剂量谈毒性\n\n领域对象里只有`get/set`方法,所有的业务逻辑都不包含在内,从而造成`失忆症`,从实体中,无法知道发生了那些业务,需要去 `service层` 里挨个梳理,一旦业务迭代、`service 层`直接开始变成屎山\n\n## 充血模型 && 领域服务的关系\n\n> 确保领域之间独立的,随着不断的充血,保证领域(实体)是独立发展的\n\n假定两个 domain(实体):\n\n- 学生: 上课、做作业\n- 老师、全体起立、布置作业\n\n假定`老师`调用`全体起立`,对应肯定要`学生`调用`上课`,这个就是 `领域服务(domainService)` 需要去处理的,他俩不能建立直接的联系。\n\n- 增加新的业务逻辑:在 `domainService` 增加新的方法。\n- 调整旧的业务逻辑:修改`学生`,`老师`,内部具体的方法,`domainService` 完全不需要变化。\n","source":"_posts/front-end/ddd.md","raw":"---\ntitle: 领域驱动设计\ncategories:\n - Front-End\nstatus: doing\n---\n\n\n# 参考文献\n\n- [蚂蚁金服数据体验技术团队 - 领域驱动设计](https://juejin.cn/post/6844903618680881165)\n- [美团 - 领域驱动设计在互联网业务开发中的实践](https://tech.meituan.com/2017/12/22/ddd-in-practice.html)\n- [领域驱动实战思考](https://huhao.dev/posts/61190ae2/#%E9%97%AE%E9%A2%98%E5%AD%90%E5%9F%9F%E8%AF%86%E5%88%AB)\n- [基于 DDD 的前端项目架构设计与实战](https://www.tangshuang.net/8663.html)\n- [React 语境下前端 DDD 的思考](https://www.tangshuang.net/8212.html)\n\n# 架构对比\n\n从后端视角看,对比传统的三层架构,领域驱动\n\n- `充血`复用了:领域对外的接口\n- `领域服务`封装了:领域之间的联系\n\n贫血模型->充血模型,降低了`service层`的负担,同时保证了业务迭代,`entity`的独立性\n\n## MVC\n\n从数据库视角、分析实体出发,进行系统的构建\n\n- DAO 层\n- Service 层\n- Controller 层\n\n## 后端 DDD\n\n> 针对基础设施层,也可以考虑加入`防腐层`、`工厂`\n\n一个领域基本四层架构\n\n1. 用户层(user interface): 对标 controller,对外提供 web 服务、接口\n2. 应用层(application): 业务层,定义领域可以解决的问题 `domainService`\n3. 领域层(domain): 纯粹的描述业务实体\n4. 基础设施层(infra): 持久化层(Builder+repository)\n\n同时,领域拆分带来如下两个致命问题\n\n- 领域化(微服务)后,数据库是分开的,导致实体调用链复杂,避免跨库查询\n- 避免分布式事务同步\n\n## 前端 DDD\n\n> ?? 这里存在一个问题,api 网关到底要不要挪到前端领域目录下来开发\n\n1. 用户层: web、mobile、mini-program ...多端\n2. service: 暴露给 UI 组件的 `domainSerivce`,组织实体的状态流转\n3. entity: 实体、聚合实体、事件\n4. infra: api 请求、缓存、工具\n\n## 工厂 Factory / Builder\n\n- 数据库表设计层面,如果要`多对多`的关系,只能很别扭的设计一个中间表。\n- 实体 A 适合 `mongo`文档形存储,实体 B 适合`mysql`关系型存储。对于 `Builder` 而言,直接在这个领域搓出来 `A 和 B` 的 `CRUD`的实现方法\n\n## 防腐层 Facade / Adaptor\n\n也被成为适配器,隔离第三方/外部服务,例如场景,用户上传文件到阿里云、腾讯云\n\n```js\nclass UserUploadServiceFacade extends UserService {\n /**\n * 上传\n */\n async upload(oss) {\n if (oss === \"ali\") return \"OK\";\n if (oss === \"tx\") return \"FAIL\";\n }\n}\n```\n\n# 领域模型\n\n实体的`贫血->充血`是一个随着业务发展过程演变的结果,初期业务场景不明确,很难充血,瞎几把充血,纯属找难受。\n\n是否需要引入`聚合根(aggregate root)`的概念,解决领域内实体独立、平铺关系带来的不方便,这个可能比较看心情\n\n## 贫血模型(POJO)的问题\n\n> 脱离了业务复杂度谈分层,好比抛开剂量谈毒性\n\n领域对象里只有`get/set`方法,所有的业务逻辑都不包含在内,从而造成`失忆症`,从实体中,无法知道发生了那些业务,需要去 `service层` 里挨个梳理,一旦业务迭代、`service 层`直接开始变成屎山\n\n## 充血模型 && 领域服务的关系\n\n> 确保领域之间独立的,随着不断的充血,保证领域(实体)是独立发展的\n\n假定两个 domain(实体):\n\n- 学生: 上课、做作业\n- 老师、全体起立、布置作业\n\n假定`老师`调用`全体起立`,对应肯定要`学生`调用`上课`,这个就是 `领域服务(domainService)` 需要去处理的,他俩不能建立直接的联系。\n\n- 增加新的业务逻辑:在 `domainService` 增加新的方法。\n- 调整旧的业务逻辑:修改`学生`,`老师`,内部具体的方法,`domainService` 完全不需要变化。\n","slug":"front-end/ddd","published":1,"date":"2023-11-06T07:56:40.047Z","updated":"2023-11-06T08:02:39.167Z","comments":1,"layout":"post","photos":[],"link":"","_id":"clp7x194z000ev3z3ef2z8117","content":"

参考文献

\n

架构对比

从后端视角看,对比传统的三层架构,领域驱动

\n
    \n
  • 充血复用了:领域对外的接口
  • \n
  • 领域服务封装了:领域之间的联系
  • \n
\n

贫血模型->充血模型,降低了service层的负担,同时保证了业务迭代,entity的独立性

\n

MVC

从数据库视角、分析实体出发,进行系统的构建

\n
    \n
  • DAO 层
  • \n
  • Service 层
  • \n
  • Controller 层
  • \n
\n

后端 DDD

\n

针对基础设施层,也可以考虑加入防腐层工厂

\n
\n

一个领域基本四层架构

\n
    \n
  1. 用户层(user interface): 对标 controller,对外提供 web 服务、接口
  2. \n
  3. 应用层(application): 业务层,定义领域可以解决的问题 domainService
  4. \n
  5. 领域层(domain): 纯粹的描述业务实体
  6. \n
  7. 基础设施层(infra): 持久化层(Builder+repository)
  8. \n
\n

同时,领域拆分带来如下两个致命问题

\n
    \n
  • 领域化(微服务)后,数据库是分开的,导致实体调用链复杂,避免跨库查询
  • \n
  • 避免分布式事务同步
  • \n
\n

前端 DDD

\n

?? 这里存在一个问题,api 网关到底要不要挪到前端领域目录下来开发

\n
\n
    \n
  1. 用户层: web、mobile、mini-program …多端
  2. \n
  3. service: 暴露给 UI 组件的 domainSerivce,组织实体的状态流转
  4. \n
  5. entity: 实体、聚合实体、事件
  6. \n
  7. infra: api 请求、缓存、工具
  8. \n
\n

工厂 Factory / Builder

    \n
  • 数据库表设计层面,如果要多对多的关系,只能很别扭的设计一个中间表。
  • \n
  • 实体 A 适合 mongo文档形存储,实体 B 适合mysql关系型存储。对于 Builder 而言,直接在这个领域搓出来 A 和 BCRUD的实现方法
  • \n
\n

防腐层 Facade / Adaptor

也被成为适配器,隔离第三方/外部服务,例如场景,用户上传文件到阿里云、腾讯云

\n
class UserUploadServiceFacade extends UserService {\n  /**\n   * 上传\n   */\n  async upload(oss) {\n    if (oss === \"ali\") return \"OK\";\n    if (oss === \"tx\") return \"FAIL\";\n  }\n}
\n\n

领域模型

实体的贫血->充血是一个随着业务发展过程演变的结果,初期业务场景不明确,很难充血,瞎几把充血,纯属找难受。

\n

是否需要引入聚合根(aggregate root)的概念,解决领域内实体独立、平铺关系带来的不方便,这个可能比较看心情

\n

贫血模型(POJO)的问题

\n

脱离了业务复杂度谈分层,好比抛开剂量谈毒性

\n
\n

领域对象里只有get/set方法,所有的业务逻辑都不包含在内,从而造成失忆症,从实体中,无法知道发生了那些业务,需要去 service层 里挨个梳理,一旦业务迭代、service 层直接开始变成屎山

\n

充血模型 && 领域服务的关系

\n

确保领域之间独立的,随着不断的充血,保证领域(实体)是独立发展的

\n
\n

假定两个 domain(实体):

\n
    \n
  • 学生: 上课、做作业
  • \n
  • 老师、全体起立、布置作业
  • \n
\n

假定老师调用全体起立,对应肯定要学生调用上课,这个就是 领域服务(domainService) 需要去处理的,他俩不能建立直接的联系。

\n
    \n
  • 增加新的业务逻辑:在 domainService 增加新的方法。
  • \n
  • 调整旧的业务逻辑:修改学生老师,内部具体的方法,domainService 完全不需要变化。
  • \n
\n","site":{"data":{}},"excerpt":"","more":"

参考文献

\n

架构对比

从后端视角看,对比传统的三层架构,领域驱动

\n
    \n
  • 充血复用了:领域对外的接口
  • \n
  • 领域服务封装了:领域之间的联系
  • \n
\n

贫血模型->充血模型,降低了service层的负担,同时保证了业务迭代,entity的独立性

\n

MVC

从数据库视角、分析实体出发,进行系统的构建

\n
    \n
  • DAO 层
  • \n
  • Service 层
  • \n
  • Controller 层
  • \n
\n

后端 DDD

\n

针对基础设施层,也可以考虑加入防腐层工厂

\n
\n

一个领域基本四层架构

\n
    \n
  1. 用户层(user interface): 对标 controller,对外提供 web 服务、接口
  2. \n
  3. 应用层(application): 业务层,定义领域可以解决的问题 domainService
  4. \n
  5. 领域层(domain): 纯粹的描述业务实体
  6. \n
  7. 基础设施层(infra): 持久化层(Builder+repository)
  8. \n
\n

同时,领域拆分带来如下两个致命问题

\n
    \n
  • 领域化(微服务)后,数据库是分开的,导致实体调用链复杂,避免跨库查询
  • \n
  • 避免分布式事务同步
  • \n
\n

前端 DDD

\n

?? 这里存在一个问题,api 网关到底要不要挪到前端领域目录下来开发

\n
\n
    \n
  1. 用户层: web、mobile、mini-program …多端
  2. \n
  3. service: 暴露给 UI 组件的 domainSerivce,组织实体的状态流转
  4. \n
  5. entity: 实体、聚合实体、事件
  6. \n
  7. infra: api 请求、缓存、工具
  8. \n
\n

工厂 Factory / Builder

    \n
  • 数据库表设计层面,如果要多对多的关系,只能很别扭的设计一个中间表。
  • \n
  • 实体 A 适合 mongo文档形存储,实体 B 适合mysql关系型存储。对于 Builder 而言,直接在这个领域搓出来 A 和 BCRUD的实现方法
  • \n
\n

防腐层 Facade / Adaptor

也被成为适配器,隔离第三方/外部服务,例如场景,用户上传文件到阿里云、腾讯云

\n
class UserUploadServiceFacade extends UserService {\n  /**\n   * 上传\n   */\n  async upload(oss) {\n    if (oss === \"ali\") return \"OK\";\n    if (oss === \"tx\") return \"FAIL\";\n  }\n}
\n\n

领域模型

实体的贫血->充血是一个随着业务发展过程演变的结果,初期业务场景不明确,很难充血,瞎几把充血,纯属找难受。

\n

是否需要引入聚合根(aggregate root)的概念,解决领域内实体独立、平铺关系带来的不方便,这个可能比较看心情

\n

贫血模型(POJO)的问题

\n

脱离了业务复杂度谈分层,好比抛开剂量谈毒性

\n
\n

领域对象里只有get/set方法,所有的业务逻辑都不包含在内,从而造成失忆症,从实体中,无法知道发生了那些业务,需要去 service层 里挨个梳理,一旦业务迭代、service 层直接开始变成屎山

\n

充血模型 && 领域服务的关系

\n

确保领域之间独立的,随着不断的充血,保证领域(实体)是独立发展的

\n
\n

假定两个 domain(实体):

\n
    \n
  • 学生: 上课、做作业
  • \n
  • 老师、全体起立、布置作业
  • \n
\n

假定老师调用全体起立,对应肯定要学生调用上课,这个就是 领域服务(domainService) 需要去处理的,他俩不能建立直接的联系。

\n
    \n
  • 增加新的业务逻辑:在 domainService 增加新的方法。
  • \n
  • 调整旧的业务逻辑:修改学生老师,内部具体的方法,domainService 完全不需要变化。
  • \n
\n"},{"title":"Git","status":"done","_content":"\n\n# GIT 最佳实践\n\nGIT 本质是一个数据库,用来存代码的\n\n- 工作区:一个沙箱环境,GIT 不负责管理,你尽管在沙箱里面对文件进行操作\n- 暂存区:`工作区`文件变动先不急着提交,暂存到一定数量,在提交到版本库\n- 版本库:\n\n> !! Linus 永远的神!\n\n## 配置用户\n\n```bash\ngit config --global user.name \"mozzie\"\ngit config --global user.email himozzie@foxmail.com\n```\n\n## alias 别名\n\n解决参数太多,记不住的问题\n\n> !! HEAD -> master HEAD 相当于一个指针,指向当前所在分支\n\n```bash\n# 查看项目分支图,\ngit config --global alias.lo \"log --oneline --decorate --graph --all\"\n```\n\n## .git 文件结构\n\n- hooks:提交代码前,检查代码格式……\n- info:包含一个排除性文件\n- logs:保存日志信息,不太需要\n- `objects`:相当于数据库,存储所有数据内容\n- `refs`:存放提交对象指针,管理分支的\n- config:配置文件\n- description:仓库描述信息\n- `HEAD`:指示目前被检出的分支\n- `index`:文件保存暂存区信息\n\n## 修改远程仓库\n\n```bash\n# way 1\ngit remote set-url origin [url]\n# way 2\ngit remote rm origin\ngit remote add origin [url]\n# way3\n修改 config 文件\n```\n\n## 高层命令\n\n### 初始化仓库\n\n`git init`\n\n### 修改添加到暂存区\n\n`git add ./`,相当于如下操作:\n\n```bash\n# 有多少文件改动,就执行多少次\ngit hash-object -w 文件名\ngit update-index\n```\n\n> !! git add ./ 先把工作区生成 git 对象,放到版本库,然后再放到暂存区\n\n### 暂存区提交到版本库\n\n`git commit -m 'comment'`,相当于如下操作:\n\n```bash\ngit write-tree\ngit commit-tree\n```\n\n也可以跳过暂存区提交,`git commit -a -m `\n\n### 查看哪些修改没有暂存\n\n`git diff`:没有暂存\n\n`git diff --staged`:查看哪些修改以及被暂存了,但没有提交\n\n### 查看提交历史记录\n\n`git log --oneline`,打印出`hash值`是提交对象\n\n## 分支\n\n本质是一个`提交对象`,每次`git branch name`中的`name`,指针`HEAD`,就会根据`name`指向提交对象\n\n如果要开发新功能,就新建一个`分支A`,写完再合`master`分支,正常来说`master`分支不会轻易给修改权限。\n\n如果另一个新功能,和`分支A`同级、并行的,那就`切到master`分支,在`master`分支基础上,开`分支B`,进行新功能开发\n\n> 一般来说,master 分支没有权限,需要自己重新写开一个分支,分支名用 nickname\n\n```bash\nC1 master\n|——C2——C3——C4 mozzie\n```\n\n### 分支列表\n\n`git branch`\n\n### 创建分支\n\n`git branch 分支名`,并不会自动切换到分支\n\n### 切换分支\n\n> !! 最佳实践:每次切换分之前,git status 查一下,当前分支一定要是干净的\n\n`git checkout 分支名`\n\n### 合并分支\n\n> !! 做任何事情,确保做完了,再合并到 master 分支\n\n场景:需要增加功能`feat:#53`\n\n```bash\n# HEAD -> master,新开一个分支\ngit checkout -b 'feat53'\n```\n\n突然发现 bug,需要修复`bug:#52`\n\n```bash\n# HEAD -> feat53,先提交#53分支的工作\ngit commit -a -m 'feat53 完成50%'\n# 切回 master 分支\ngit checkout master\n# HEAD -> master,创建 issue52 分支\ngit checkout -b 'issue52'\n# 改完了issue52\ngit commit -a -m 'fix:issue52'\n# HEAD -> master\ngit checkout master\n# 合\ngit merge issue52\n# 删除 issue52分支(hash还在)\ngit branch -d issue52\n```\n\n> 此时,由于`issue52`是在之前的 master 分支上生成的,故而`feat53`的分支仍然存在`issue52`的 bug,所以有可能存在冲突,需要手动解决\n\n```bash\n# HEAD -> master\ngit merge feat53\n# 此处省略解决冲突\ngit add ./\ngit commit -m 'fix:merge conflict'\n# 删除 feat53\ngit branch -d feat53\n```\n\n### 删除分支\n\n查看哪些分支合并到当前分支,`git branch --merged`,这个列表中分支名字前没有\\*号的分支通常可以使用`git branch -d 分支名`删掉\n\n`git branch -D 分支名`,强制删除\n\n### 新建分支并指向指定提交对象\n\n`git branch name commitHash`,例`git log --oneline`如下:\n\n```bash\n* hasfh2asd 1.txt\n* 1shfd2zsw 2.txt\n* 67rf73has 3.txt\n* 03uhr4rug 4.txt\n```\n\n输入`git branch CCC 03uhr4rug`,那么会创建一个名为`CCC`的分支,并且`CCC`分支有`4.txt`\n\n> 通常想看原来的某个版本的代码,就可以这样操作,看完,把这个分支删了\n\n### 远程分支\n\n> `git clone`下来的分支,默认就会建立一个`远程跟踪分支`(同步关系),例如 master 分支\n\n- 本地分支\n\n场景一:如果想公开一个`share`分支 ,与他人共同写作:\n\n```bash\n# 过程中会生成生成一个远程跟踪分支 origin/share\ngit push origin share\n```\n\n场景二:创建一个本地分支`b1`,直接跟踪远程分支`orgin/b1`\n\n```bash\ngit checkout -b 'b1' 'origin/b1'\n```\n\n场景三:已存在一个本地分支`dev`,改成远程跟踪分支\n\n```bash\n# HEAD -> dev,建立 本地分支 dev 与 远程分支 origin/dev 关系\ngit branch -u origin/dev\n# 这样就可以直接\ngit push / git pull\n```\n\n- 远程分支\n\n查看远程分支:`git remote -v`\n\n查看当前本地分支的远程跟踪分支:`git branch -vv`\n\n## 远程分支删除, 本地更新 --prune\n\n```bash\n# 不加 --prune,和 fetch 等价, 远程被删除的分支不会同步删除本地origin的分支\ngit remote update origin --prune\n```\n\n# 提交规范\n\n```bash\ntype(scope): subject\n# 例如\nfeat(miniprogram): 增加了小程序模板消息相关功能\n```\n\n通常`type`有如下:\n\n- feat - 新功能 feature\n- fix - 修复 bug\n- docs - 文档注释\n- style - 代码格式(不影响代码运行的变动)\n- refactor - 重构、优化(既不增加新功能,也不是修复 bug)\n- perf - 性能优化\n- test - 增加测试\n- chore - 构建过程或辅助工具的变动\n- revert - 回退\n- build - 打包\n\n## 自动生成 Change log\n\n原理:利用 `child_process`获取 `git log`内容,处理字符串\n\n```js\nconst execSync = require(\"child_process\").execSync; //同步子进程\nconst fs = require(\"fs\");\nconst process = require(\"process\");\nconst path = require(\"path\");\nconst inquirer = require(\"inquirer\");\nconst dayjs = require(\"dayjs\");\nconst axios = require(\"axios\");\n\n// env\nconst isForCommanHuman = process.argv.includes(\"--common\");\n\n// changelog.md生成路径\nconst outputpath = path.resolve(process.cwd(), \"./changelog.md\");\n// 非空检测\nif (!fs.existsSync(outputpath)) fs.writeFile(outputpath, \"\", (err) => {});\n// 华丽的gitlog日志\nconst perfectGitLog = (startTime, endTime) =>\n isForCommanHuman\n ? `git log --since=\"${startTime}\" --until=\"${endTime}\" --no-merges --pretty=format:\"%cr %C(cyan)%s\"`\n : `git log --since=\"${startTime}\" --until=\"${endTime}\" --no-merges --pretty=format:\"%C(yellow)%h %C(green)%cn %C(redz)(%cr:%ci) %C(cyan)%s\"`;\n\nconst wxrobotHook =\n \"https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=be4d4473-c290-4ddd-a089-41df3ed1d601\";\n\n// 当前时间\nconst now = Date.now();\n// 2周前\nconst weeks_2_ago = now - 14 * 24 * 60 * 60 * 1000;\n\ninquirer\n .prompt([\n {\n type: \"input\",\n name: \"startDate\",\n message: `起始时间,默认13天前 \\n`,\n default: dayjs(weeks_2_ago).format(\"YYYY.MM.DD\"),\n validate: (val) => /\\d{4}.\\d{2}.\\d{2}/.test(val),\n },\n {\n type: \"input\",\n name: \"endDate\",\n message: `结束时间,默认今天 \\n`,\n default: dayjs(now).format(\"YYYY.MM.DD\"),\n validate: (val) => /\\d{4}.\\d{2}.\\d{2}/.test(val),\n },\n {\n type: \"rawlist\",\n message: \"是否通知到微信机器人:\",\n name: \"notifyWxrobot\",\n choices: [\"Y\", \"N\"],\n },\n ])\n .then((answers) => {\n const { startDate, endDate, notifyWxrobot } = answers;\n const rowTemplate = perfectGitLog(startDate, endDate);\n let fmt = execSync(rowTemplate)\n .toString()\n .trim()\n .replace(/feat: /gi, \"✅: \")\n .replace(/fix: /gi, \"🐛: \")\n .replace(/chore: /gi, \"🎨: \")\n .replace(/perf: /gi, \"⚡: \")\n .replace(/docs: /gi, \"📝: \")\n .replace(/refactor: /gi, \"🔨: \")\n .replace(/anno: /gi, \"🔖: \")\n .replace(/style: /gi, \"👷: \");\n fs.writeFileSync(outputpath, fmt, (err) => {});\n // 通知微信群聊机器人\n if (notifyWxrobot === \"Y\") {\n axios.post(wxrobotHook, {\n msgtype: \"markdown\",\n markdown: {\n content: fmt,\n },\n });\n }\n })\n .catch((error) => {\n if (error.isTtyError) {\n // Prompt couldn't be rendered in the current environment\n } else {\n // Something else went wrong\n }\n });\n```\n\n## 使用 husky+eslint 规范提交\n\n> 需要配合 eslint\n\n`git init`后,`yarn add husky`,在`package.json`配置\n\n```json\n{\n \"husky\": {\n \"hooks\": {\n \"pre-commit\": \"npm run lint\"\n }\n }\n}\n```\n\n## 生成 ssh key\n\n```bash\nssh-keygen -t rsa -C \"himozzie@foxmail.com\"\n```\n\n## 查看 ssh 公钥\n\n```bash\ncat ~/.ssh/id_rsa.pub\n```\n\n# git 代理\n\n> 前提条件是开了扶墙工具\n\ngit clone 拉取方式选择 http/https(默认会让你输入账号密码,比较蛋疼),不要选择 ssl 拉取\n\n```bash\ngit config --global http.proxy 'http://127.0.0.1:1081'\ngit config --global https.proxy 'https://127.0.0.1:1081'\n# 清除\ngit config --global --unset http.proxy\ngit config --global --unset https.proxy\n```\n\n# 终端临时代理\n\n```bash\n# cmd临时代理方案(cmd窗口关闭,则代理失效)\nset http_proxy=http://127.0.0.1:50015\nset https_proxy=http://127.0.0.1:50015\n```\n\n# git 钩子(hooks)\n\n原理:项目 git push 到远程仓库,远程仓库的钩子 post 通知 www/wwwroot 下的站点 pull 远程仓库\n\n> git 钩子需要 git 服务和 pull 在同一环境中\n\n- 创建远程仓库,配置钩子,git post-receive 钩子\n\n```bash\n#!/bin/bash\nunset $(git rev-parse --local-env-vars);\n# post-receive接收到pull指令后,执行bash命令\ncd /www/wwwroot/doc.mozzie.cn/ && git pull origin master\n```\n\n> web 目录下 doc.mozzie.cn 是网站目录,本地项目编译打包后,直接 git push 的目录\n\n## web 钩子\n\n待耍\n\n# 项目\n\n## 统计项目代码行数\n\n统计所有人代码增删量,拷贝如下命令, git bash 终端,git 项目某分支下执行\n\n```bash\ngit log --format='%aN' | sort -u | while read name; do echo -en \"$name\\t\"; git log --author=\"$name\" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf \"added lines: %s, removed lines: %s, total lines: %s\\n\", add, subs, loc }' -; done\n\n```\n\n## 统计制定提交者代码量\n\n替换`username`为提交者的名称\n\n```bash\ngit log --author=\"username\" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf \"added lines: %s, removed lines: %s, total lines: %s\\n\", add, subs, loc }' -\n```\n\n# 搭建 gogs\n\ngogs 官网下载,压缩包解压到`www/wwwroot/`下\n\n```bash\ncd /www/wwwroot/gogs\n# 启服务,默认3000的端口,被占用则 ./gogs web -port 3001\n./gogs web\n```\n\n浏览器访问`http://yourip:3000/install`,注意服务器安全组放行 3000 端口\n\n- 为 gogs 添加 mysql 数据库\n- 配置 mysql\n- 配置 gogs 相关信息\n- 配置其他信息\n\n停掉`./gogs web`\n\n在刚安装的 gogs 路径下,找到`/gogs/scripts/systemd/gogs.service` 文件复制一份\n\n```bash\nUser=root\nGroup=root\nWorkingDirectory=/www/wwwroot/git.mozzie.cn\nExecStart=/www/wwwroot/git.mozzie.cn/gogs web\nRestart=always\nEnvironment=USER=root HOME=/www/wwwroot/git.mozzie.cn\n```\n\n将修改好的`gogs.service`文件上传到`/etc/systemd/system`下,并执行以下命令来激活 gogs\n\n```bash\nsudo systemctl enable gogs\n# 启动gogs\nsudo systemctl start gogs\n```\n\n`/gogs/custom/conf/app.ini`,修改该文件可以自定义配置,安装步骤填错了,可以这里修改,重启 gogs 服务即可\n\n## 配置 gogs GIT 钩子\n\n场景:以 Gogs 为例,先 git push `A项目` 到远程仓库 `REPO`,服务端 web 目录`webB`文件夹触发钩子执行 git pull\n\n1、gogs 初始化一个仓库`REPO`,仓库设置=>管理 GIT 钩子=>post-receive,填入\n\n```bash\n#!/bin/sh\nexport GIT_WORK_TREE=/www/wwwroot/webB\nexport GIT_DIR=${GIT_WORK_TREE}/.git\ncd ${GIT_WORK_TREE}\ngit pull\n```\n\n2、利用 ssh 工具登录服务器\n\n```bash\nssh root@mozzie.cn\n```\n\n3、生成 ssh key\n\n```bash\nssh-keygen -t rsa -C \"himozzie@foxmail.com\"\n```\n\n4、配置公钥到 gogs\n\n```bash\ncat ~/.ssh/id_rsa.pub\n```\n\n把打印出的公钥,配置到 ssh 密钥\n\n5、最后一步\n\n配置好服务端的公钥后,就可以无需用户名密码,`cd /webB`目录下,执行`git pull`,此后每次`git push A项目`,服务端都会触发`GIT钩子`,自动从`REPO`拉取最新的仓库文件\n","source":"_posts/front-end/git.md","raw":"---\ntitle: Git\ncategories:\n - Front-End\nstatus: done\n---\n\n\n# GIT 最佳实践\n\nGIT 本质是一个数据库,用来存代码的\n\n- 工作区:一个沙箱环境,GIT 不负责管理,你尽管在沙箱里面对文件进行操作\n- 暂存区:`工作区`文件变动先不急着提交,暂存到一定数量,在提交到版本库\n- 版本库:\n\n> !! Linus 永远的神!\n\n## 配置用户\n\n```bash\ngit config --global user.name \"mozzie\"\ngit config --global user.email himozzie@foxmail.com\n```\n\n## alias 别名\n\n解决参数太多,记不住的问题\n\n> !! HEAD -> master HEAD 相当于一个指针,指向当前所在分支\n\n```bash\n# 查看项目分支图,\ngit config --global alias.lo \"log --oneline --decorate --graph --all\"\n```\n\n## .git 文件结构\n\n- hooks:提交代码前,检查代码格式……\n- info:包含一个排除性文件\n- logs:保存日志信息,不太需要\n- `objects`:相当于数据库,存储所有数据内容\n- `refs`:存放提交对象指针,管理分支的\n- config:配置文件\n- description:仓库描述信息\n- `HEAD`:指示目前被检出的分支\n- `index`:文件保存暂存区信息\n\n## 修改远程仓库\n\n```bash\n# way 1\ngit remote set-url origin [url]\n# way 2\ngit remote rm origin\ngit remote add origin [url]\n# way3\n修改 config 文件\n```\n\n## 高层命令\n\n### 初始化仓库\n\n`git init`\n\n### 修改添加到暂存区\n\n`git add ./`,相当于如下操作:\n\n```bash\n# 有多少文件改动,就执行多少次\ngit hash-object -w 文件名\ngit update-index\n```\n\n> !! git add ./ 先把工作区生成 git 对象,放到版本库,然后再放到暂存区\n\n### 暂存区提交到版本库\n\n`git commit -m 'comment'`,相当于如下操作:\n\n```bash\ngit write-tree\ngit commit-tree\n```\n\n也可以跳过暂存区提交,`git commit -a -m `\n\n### 查看哪些修改没有暂存\n\n`git diff`:没有暂存\n\n`git diff --staged`:查看哪些修改以及被暂存了,但没有提交\n\n### 查看提交历史记录\n\n`git log --oneline`,打印出`hash值`是提交对象\n\n## 分支\n\n本质是一个`提交对象`,每次`git branch name`中的`name`,指针`HEAD`,就会根据`name`指向提交对象\n\n如果要开发新功能,就新建一个`分支A`,写完再合`master`分支,正常来说`master`分支不会轻易给修改权限。\n\n如果另一个新功能,和`分支A`同级、并行的,那就`切到master`分支,在`master`分支基础上,开`分支B`,进行新功能开发\n\n> 一般来说,master 分支没有权限,需要自己重新写开一个分支,分支名用 nickname\n\n```bash\nC1 master\n|——C2——C3——C4 mozzie\n```\n\n### 分支列表\n\n`git branch`\n\n### 创建分支\n\n`git branch 分支名`,并不会自动切换到分支\n\n### 切换分支\n\n> !! 最佳实践:每次切换分之前,git status 查一下,当前分支一定要是干净的\n\n`git checkout 分支名`\n\n### 合并分支\n\n> !! 做任何事情,确保做完了,再合并到 master 分支\n\n场景:需要增加功能`feat:#53`\n\n```bash\n# HEAD -> master,新开一个分支\ngit checkout -b 'feat53'\n```\n\n突然发现 bug,需要修复`bug:#52`\n\n```bash\n# HEAD -> feat53,先提交#53分支的工作\ngit commit -a -m 'feat53 完成50%'\n# 切回 master 分支\ngit checkout master\n# HEAD -> master,创建 issue52 分支\ngit checkout -b 'issue52'\n# 改完了issue52\ngit commit -a -m 'fix:issue52'\n# HEAD -> master\ngit checkout master\n# 合\ngit merge issue52\n# 删除 issue52分支(hash还在)\ngit branch -d issue52\n```\n\n> 此时,由于`issue52`是在之前的 master 分支上生成的,故而`feat53`的分支仍然存在`issue52`的 bug,所以有可能存在冲突,需要手动解决\n\n```bash\n# HEAD -> master\ngit merge feat53\n# 此处省略解决冲突\ngit add ./\ngit commit -m 'fix:merge conflict'\n# 删除 feat53\ngit branch -d feat53\n```\n\n### 删除分支\n\n查看哪些分支合并到当前分支,`git branch --merged`,这个列表中分支名字前没有\\*号的分支通常可以使用`git branch -d 分支名`删掉\n\n`git branch -D 分支名`,强制删除\n\n### 新建分支并指向指定提交对象\n\n`git branch name commitHash`,例`git log --oneline`如下:\n\n```bash\n* hasfh2asd 1.txt\n* 1shfd2zsw 2.txt\n* 67rf73has 3.txt\n* 03uhr4rug 4.txt\n```\n\n输入`git branch CCC 03uhr4rug`,那么会创建一个名为`CCC`的分支,并且`CCC`分支有`4.txt`\n\n> 通常想看原来的某个版本的代码,就可以这样操作,看完,把这个分支删了\n\n### 远程分支\n\n> `git clone`下来的分支,默认就会建立一个`远程跟踪分支`(同步关系),例如 master 分支\n\n- 本地分支\n\n场景一:如果想公开一个`share`分支 ,与他人共同写作:\n\n```bash\n# 过程中会生成生成一个远程跟踪分支 origin/share\ngit push origin share\n```\n\n场景二:创建一个本地分支`b1`,直接跟踪远程分支`orgin/b1`\n\n```bash\ngit checkout -b 'b1' 'origin/b1'\n```\n\n场景三:已存在一个本地分支`dev`,改成远程跟踪分支\n\n```bash\n# HEAD -> dev,建立 本地分支 dev 与 远程分支 origin/dev 关系\ngit branch -u origin/dev\n# 这样就可以直接\ngit push / git pull\n```\n\n- 远程分支\n\n查看远程分支:`git remote -v`\n\n查看当前本地分支的远程跟踪分支:`git branch -vv`\n\n## 远程分支删除, 本地更新 --prune\n\n```bash\n# 不加 --prune,和 fetch 等价, 远程被删除的分支不会同步删除本地origin的分支\ngit remote update origin --prune\n```\n\n# 提交规范\n\n```bash\ntype(scope): subject\n# 例如\nfeat(miniprogram): 增加了小程序模板消息相关功能\n```\n\n通常`type`有如下:\n\n- feat - 新功能 feature\n- fix - 修复 bug\n- docs - 文档注释\n- style - 代码格式(不影响代码运行的变动)\n- refactor - 重构、优化(既不增加新功能,也不是修复 bug)\n- perf - 性能优化\n- test - 增加测试\n- chore - 构建过程或辅助工具的变动\n- revert - 回退\n- build - 打包\n\n## 自动生成 Change log\n\n原理:利用 `child_process`获取 `git log`内容,处理字符串\n\n```js\nconst execSync = require(\"child_process\").execSync; //同步子进程\nconst fs = require(\"fs\");\nconst process = require(\"process\");\nconst path = require(\"path\");\nconst inquirer = require(\"inquirer\");\nconst dayjs = require(\"dayjs\");\nconst axios = require(\"axios\");\n\n// env\nconst isForCommanHuman = process.argv.includes(\"--common\");\n\n// changelog.md生成路径\nconst outputpath = path.resolve(process.cwd(), \"./changelog.md\");\n// 非空检测\nif (!fs.existsSync(outputpath)) fs.writeFile(outputpath, \"\", (err) => {});\n// 华丽的gitlog日志\nconst perfectGitLog = (startTime, endTime) =>\n isForCommanHuman\n ? `git log --since=\"${startTime}\" --until=\"${endTime}\" --no-merges --pretty=format:\"%cr %C(cyan)%s\"`\n : `git log --since=\"${startTime}\" --until=\"${endTime}\" --no-merges --pretty=format:\"%C(yellow)%h %C(green)%cn %C(redz)(%cr:%ci) %C(cyan)%s\"`;\n\nconst wxrobotHook =\n \"https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=be4d4473-c290-4ddd-a089-41df3ed1d601\";\n\n// 当前时间\nconst now = Date.now();\n// 2周前\nconst weeks_2_ago = now - 14 * 24 * 60 * 60 * 1000;\n\ninquirer\n .prompt([\n {\n type: \"input\",\n name: \"startDate\",\n message: `起始时间,默认13天前 \\n`,\n default: dayjs(weeks_2_ago).format(\"YYYY.MM.DD\"),\n validate: (val) => /\\d{4}.\\d{2}.\\d{2}/.test(val),\n },\n {\n type: \"input\",\n name: \"endDate\",\n message: `结束时间,默认今天 \\n`,\n default: dayjs(now).format(\"YYYY.MM.DD\"),\n validate: (val) => /\\d{4}.\\d{2}.\\d{2}/.test(val),\n },\n {\n type: \"rawlist\",\n message: \"是否通知到微信机器人:\",\n name: \"notifyWxrobot\",\n choices: [\"Y\", \"N\"],\n },\n ])\n .then((answers) => {\n const { startDate, endDate, notifyWxrobot } = answers;\n const rowTemplate = perfectGitLog(startDate, endDate);\n let fmt = execSync(rowTemplate)\n .toString()\n .trim()\n .replace(/feat: /gi, \"✅: \")\n .replace(/fix: /gi, \"🐛: \")\n .replace(/chore: /gi, \"🎨: \")\n .replace(/perf: /gi, \"⚡: \")\n .replace(/docs: /gi, \"📝: \")\n .replace(/refactor: /gi, \"🔨: \")\n .replace(/anno: /gi, \"🔖: \")\n .replace(/style: /gi, \"👷: \");\n fs.writeFileSync(outputpath, fmt, (err) => {});\n // 通知微信群聊机器人\n if (notifyWxrobot === \"Y\") {\n axios.post(wxrobotHook, {\n msgtype: \"markdown\",\n markdown: {\n content: fmt,\n },\n });\n }\n })\n .catch((error) => {\n if (error.isTtyError) {\n // Prompt couldn't be rendered in the current environment\n } else {\n // Something else went wrong\n }\n });\n```\n\n## 使用 husky+eslint 规范提交\n\n> 需要配合 eslint\n\n`git init`后,`yarn add husky`,在`package.json`配置\n\n```json\n{\n \"husky\": {\n \"hooks\": {\n \"pre-commit\": \"npm run lint\"\n }\n }\n}\n```\n\n## 生成 ssh key\n\n```bash\nssh-keygen -t rsa -C \"himozzie@foxmail.com\"\n```\n\n## 查看 ssh 公钥\n\n```bash\ncat ~/.ssh/id_rsa.pub\n```\n\n# git 代理\n\n> 前提条件是开了扶墙工具\n\ngit clone 拉取方式选择 http/https(默认会让你输入账号密码,比较蛋疼),不要选择 ssl 拉取\n\n```bash\ngit config --global http.proxy 'http://127.0.0.1:1081'\ngit config --global https.proxy 'https://127.0.0.1:1081'\n# 清除\ngit config --global --unset http.proxy\ngit config --global --unset https.proxy\n```\n\n# 终端临时代理\n\n```bash\n# cmd临时代理方案(cmd窗口关闭,则代理失效)\nset http_proxy=http://127.0.0.1:50015\nset https_proxy=http://127.0.0.1:50015\n```\n\n# git 钩子(hooks)\n\n原理:项目 git push 到远程仓库,远程仓库的钩子 post 通知 www/wwwroot 下的站点 pull 远程仓库\n\n> git 钩子需要 git 服务和 pull 在同一环境中\n\n- 创建远程仓库,配置钩子,git post-receive 钩子\n\n```bash\n#!/bin/bash\nunset $(git rev-parse --local-env-vars);\n# post-receive接收到pull指令后,执行bash命令\ncd /www/wwwroot/doc.mozzie.cn/ && git pull origin master\n```\n\n> web 目录下 doc.mozzie.cn 是网站目录,本地项目编译打包后,直接 git push 的目录\n\n## web 钩子\n\n待耍\n\n# 项目\n\n## 统计项目代码行数\n\n统计所有人代码增删量,拷贝如下命令, git bash 终端,git 项目某分支下执行\n\n```bash\ngit log --format='%aN' | sort -u | while read name; do echo -en \"$name\\t\"; git log --author=\"$name\" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf \"added lines: %s, removed lines: %s, total lines: %s\\n\", add, subs, loc }' -; done\n\n```\n\n## 统计制定提交者代码量\n\n替换`username`为提交者的名称\n\n```bash\ngit log --author=\"username\" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf \"added lines: %s, removed lines: %s, total lines: %s\\n\", add, subs, loc }' -\n```\n\n# 搭建 gogs\n\ngogs 官网下载,压缩包解压到`www/wwwroot/`下\n\n```bash\ncd /www/wwwroot/gogs\n# 启服务,默认3000的端口,被占用则 ./gogs web -port 3001\n./gogs web\n```\n\n浏览器访问`http://yourip:3000/install`,注意服务器安全组放行 3000 端口\n\n- 为 gogs 添加 mysql 数据库\n- 配置 mysql\n- 配置 gogs 相关信息\n- 配置其他信息\n\n停掉`./gogs web`\n\n在刚安装的 gogs 路径下,找到`/gogs/scripts/systemd/gogs.service` 文件复制一份\n\n```bash\nUser=root\nGroup=root\nWorkingDirectory=/www/wwwroot/git.mozzie.cn\nExecStart=/www/wwwroot/git.mozzie.cn/gogs web\nRestart=always\nEnvironment=USER=root HOME=/www/wwwroot/git.mozzie.cn\n```\n\n将修改好的`gogs.service`文件上传到`/etc/systemd/system`下,并执行以下命令来激活 gogs\n\n```bash\nsudo systemctl enable gogs\n# 启动gogs\nsudo systemctl start gogs\n```\n\n`/gogs/custom/conf/app.ini`,修改该文件可以自定义配置,安装步骤填错了,可以这里修改,重启 gogs 服务即可\n\n## 配置 gogs GIT 钩子\n\n场景:以 Gogs 为例,先 git push `A项目` 到远程仓库 `REPO`,服务端 web 目录`webB`文件夹触发钩子执行 git pull\n\n1、gogs 初始化一个仓库`REPO`,仓库设置=>管理 GIT 钩子=>post-receive,填入\n\n```bash\n#!/bin/sh\nexport GIT_WORK_TREE=/www/wwwroot/webB\nexport GIT_DIR=${GIT_WORK_TREE}/.git\ncd ${GIT_WORK_TREE}\ngit pull\n```\n\n2、利用 ssh 工具登录服务器\n\n```bash\nssh root@mozzie.cn\n```\n\n3、生成 ssh key\n\n```bash\nssh-keygen -t rsa -C \"himozzie@foxmail.com\"\n```\n\n4、配置公钥到 gogs\n\n```bash\ncat ~/.ssh/id_rsa.pub\n```\n\n把打印出的公钥,配置到 ssh 密钥\n\n5、最后一步\n\n配置好服务端的公钥后,就可以无需用户名密码,`cd /webB`目录下,执行`git pull`,此后每次`git push A项目`,服务端都会触发`GIT钩子`,自动从`REPO`拉取最新的仓库文件\n","slug":"front-end/git","published":1,"date":"2023-11-06T08:01:46.362Z","updated":"2023-11-06T08:02:34.511Z","comments":1,"layout":"post","photos":[],"link":"","_id":"clp7x194z000hv3z3dcryc9ir","content":"

GIT 最佳实践

GIT 本质是一个数据库,用来存代码的

\n
    \n
  • 工作区:一个沙箱环境,GIT 不负责管理,你尽管在沙箱里面对文件进行操作
  • \n
  • 暂存区:工作区文件变动先不急着提交,暂存到一定数量,在提交到版本库
  • \n
  • 版本库:
  • \n
\n
\n

!! Linus 永远的神!

\n
\n

配置用户

git config --global user.name \"mozzie\"\ngit config --global user.email himozzie@foxmail.com
\n\n

alias 别名

解决参数太多,记不住的问题

\n
\n

!! HEAD -> master HEAD 相当于一个指针,指向当前所在分支

\n
\n
# 查看项目分支图,\ngit config --global alias.lo \"log --oneline --decorate --graph --all\"
\n\n

.git 文件结构

    \n
  • hooks:提交代码前,检查代码格式……
  • \n
  • info:包含一个排除性文件
  • \n
  • logs:保存日志信息,不太需要
  • \n
  • objects:相当于数据库,存储所有数据内容
  • \n
  • refs:存放提交对象指针,管理分支的
  • \n
  • config:配置文件
  • \n
  • description:仓库描述信息
  • \n
  • HEAD:指示目前被检出的分支
  • \n
  • index:文件保存暂存区信息
  • \n
\n

修改远程仓库

# way 1\ngit remote set-url origin [url]\n# way 2\ngit remote rm origin\ngit remote add origin [url]\n# way3\n修改 config 文件
\n\n

高层命令

初始化仓库

git init

\n

修改添加到暂存区

git add ./,相当于如下操作:

\n
# 有多少文件改动,就执行多少次\ngit hash-object -w 文件名\ngit update-index
\n\n
\n

!! git add ./ 先把工作区生成 git 对象,放到版本库,然后再放到暂存区

\n
\n

暂存区提交到版本库

git commit -m 'comment',相当于如下操作:

\n
git write-tree\ngit commit-tree
\n\n

也可以跳过暂存区提交,git commit -a -m

\n

查看哪些修改没有暂存

git diff:没有暂存

\n

git diff --staged:查看哪些修改以及被暂存了,但没有提交

\n

查看提交历史记录

git log --oneline,打印出hash值是提交对象

\n

分支

本质是一个提交对象,每次git branch name中的name,指针HEAD,就会根据name指向提交对象

\n

如果要开发新功能,就新建一个分支A,写完再合master分支,正常来说master分支不会轻易给修改权限。

\n

如果另一个新功能,和分支A同级、并行的,那就切到master分支,在master分支基础上,开分支B,进行新功能开发

\n
\n

一般来说,master 分支没有权限,需要自己重新写开一个分支,分支名用 nickname

\n
\n
C1              master\n|——C2——C3——C4   mozzie
\n\n

分支列表

git branch

\n

创建分支

git branch 分支名,并不会自动切换到分支

\n

切换分支

\n

!! 最佳实践:每次切换分之前,git status 查一下,当前分支一定要是干净的

\n
\n

git checkout 分支名

\n

合并分支

\n

!! 做任何事情,确保做完了,再合并到 master 分支

\n
\n

场景:需要增加功能feat:#53

\n
# HEAD -> master,新开一个分支\ngit checkout -b 'feat53'
\n\n

突然发现 bug,需要修复bug:#52

\n
# HEAD -> feat53,先提交#53分支的工作\ngit commit -a -m 'feat53 完成50%'\n# 切回 master 分支\ngit checkout master\n# HEAD -> master,创建 issue52 分支\ngit checkout -b 'issue52'\n# 改完了issue52\ngit commit -a -m 'fix:issue52'\n# HEAD -> master\ngit checkout master\n# 合\ngit merge issue52\n# 删除 issue52分支(hash还在)\ngit branch -d issue52
\n\n
\n

此时,由于issue52是在之前的 master 分支上生成的,故而feat53的分支仍然存在issue52的 bug,所以有可能存在冲突,需要手动解决

\n
\n
# HEAD -> master\ngit merge feat53\n# 此处省略解决冲突\ngit add ./\ngit commit -m 'fix:merge conflict'\n# 删除 feat53\ngit branch -d feat53
\n\n

删除分支

查看哪些分支合并到当前分支,git branch --merged,这个列表中分支名字前没有*号的分支通常可以使用git branch -d 分支名删掉

\n

git branch -D 分支名,强制删除

\n

新建分支并指向指定提交对象

git branch name commitHash,例git log --oneline如下:

\n
* hasfh2asd 1.txt\n* 1shfd2zsw 2.txt\n* 67rf73has 3.txt\n* 03uhr4rug 4.txt
\n\n

输入git branch CCC 03uhr4rug,那么会创建一个名为CCC的分支,并且CCC分支有4.txt

\n
\n

通常想看原来的某个版本的代码,就可以这样操作,看完,把这个分支删了

\n
\n

远程分支

\n

git clone下来的分支,默认就会建立一个远程跟踪分支(同步关系),例如 master 分支

\n
\n
    \n
  • 本地分支
  • \n
\n

场景一:如果想公开一个share分支 ,与他人共同写作:

\n
# 过程中会生成生成一个远程跟踪分支 origin/share\ngit push origin share
\n\n

场景二:创建一个本地分支b1,直接跟踪远程分支orgin/b1

\n
git checkout -b 'b1' 'origin/b1'
\n\n

场景三:已存在一个本地分支dev,改成远程跟踪分支

\n
# HEAD -> dev,建立 本地分支 dev 与 远程分支 origin/dev 关系\ngit branch -u origin/dev\n# 这样就可以直接\ngit push / git pull
\n\n
    \n
  • 远程分支
  • \n
\n

查看远程分支:git remote -v

\n

查看当前本地分支的远程跟踪分支:git branch -vv

\n

远程分支删除, 本地更新 –prune

# 不加 --prune,和 fetch 等价, 远程被删除的分支不会同步删除本地origin的分支\ngit remote update origin --prune
\n\n

提交规范

type(scope): subject\n# 例如\nfeat(miniprogram): 增加了小程序模板消息相关功能
\n\n

通常type有如下:

\n
    \n
  • feat - 新功能 feature
  • \n
  • fix - 修复 bug
  • \n
  • docs - 文档注释
  • \n
  • style - 代码格式(不影响代码运行的变动)
  • \n
  • refactor - 重构、优化(既不增加新功能,也不是修复 bug)
  • \n
  • perf - 性能优化
  • \n
  • test - 增加测试
  • \n
  • chore - 构建过程或辅助工具的变动
  • \n
  • revert - 回退
  • \n
  • build - 打包
  • \n
\n

自动生成 Change log

原理:利用 child_process获取 git log内容,处理字符串

\n
const execSync = require(\"child_process\").execSync; //同步子进程\nconst fs = require(\"fs\");\nconst process = require(\"process\");\nconst path = require(\"path\");\nconst inquirer = require(\"inquirer\");\nconst dayjs = require(\"dayjs\");\nconst axios = require(\"axios\");\n\n// env\nconst isForCommanHuman = process.argv.includes(\"--common\");\n\n// changelog.md生成路径\nconst outputpath = path.resolve(process.cwd(), \"./changelog.md\");\n// 非空检测\nif (!fs.existsSync(outputpath)) fs.writeFile(outputpath, \"\", (err) => {});\n// 华丽的gitlog日志\nconst perfectGitLog = (startTime, endTime) =>\n  isForCommanHuman\n    ? `git log --since=\"${startTime}\" --until=\"${endTime}\" --no-merges  --pretty=format:\"%cr %C(cyan)%s\"`\n    : `git log --since=\"${startTime}\" --until=\"${endTime}\" --no-merges  --pretty=format:\"%C(yellow)%h %C(green)%cn %C(redz)(%cr:%ci) %C(cyan)%s\"`;\n\nconst wxrobotHook =\n  \"https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=be4d4473-c290-4ddd-a089-41df3ed1d601\";\n\n// 当前时间\nconst now = Date.now();\n// 2周前\nconst weeks_2_ago = now - 14 * 24 * 60 * 60 * 1000;\n\ninquirer\n  .prompt([\n    {\n      type: \"input\",\n      name: \"startDate\",\n      message: `起始时间,默认13天前 \\n`,\n      default: dayjs(weeks_2_ago).format(\"YYYY.MM.DD\"),\n      validate: (val) => /\\d{4}.\\d{2}.\\d{2}/.test(val),\n    },\n    {\n      type: \"input\",\n      name: \"endDate\",\n      message: `结束时间,默认今天 \\n`,\n      default: dayjs(now).format(\"YYYY.MM.DD\"),\n      validate: (val) => /\\d{4}.\\d{2}.\\d{2}/.test(val),\n    },\n    {\n      type: \"rawlist\",\n      message: \"是否通知到微信机器人:\",\n      name: \"notifyWxrobot\",\n      choices: [\"Y\", \"N\"],\n    },\n  ])\n  .then((answers) => {\n    const { startDate, endDate, notifyWxrobot } = answers;\n    const rowTemplate = perfectGitLog(startDate, endDate);\n    let fmt = execSync(rowTemplate)\n      .toString()\n      .trim()\n      .replace(/feat: /gi, \"✅: \")\n      .replace(/fix: /gi, \"🐛: \")\n      .replace(/chore: /gi, \"🎨: \")\n      .replace(/perf: /gi, \"⚡: \")\n      .replace(/docs: /gi, \"📝: \")\n      .replace(/refactor: /gi, \"🔨: \")\n      .replace(/anno: /gi, \"🔖: \")\n      .replace(/style: /gi, \"👷: \");\n    fs.writeFileSync(outputpath, fmt, (err) => {});\n    // 通知微信群聊机器人\n    if (notifyWxrobot === \"Y\") {\n      axios.post(wxrobotHook, {\n        msgtype: \"markdown\",\n        markdown: {\n          content: fmt,\n        },\n      });\n    }\n  })\n  .catch((error) => {\n    if (error.isTtyError) {\n      // Prompt couldn't be rendered in the current environment\n    } else {\n      // Something else went wrong\n    }\n  });
\n\n

使用 husky+eslint 规范提交

\n

需要配合 eslint

\n
\n

git init后,yarn add husky,在package.json配置

\n
{\n  \"husky\": {\n    \"hooks\": {\n      \"pre-commit\": \"npm run lint\"\n    }\n  }\n}
\n\n

生成 ssh key

ssh-keygen -t rsa -C \"himozzie@foxmail.com\"
\n\n

查看 ssh 公钥

cat ~/.ssh/id_rsa.pub
\n\n

git 代理

\n

前提条件是开了扶墙工具

\n
\n

git clone 拉取方式选择 http/https(默认会让你输入账号密码,比较蛋疼),不要选择 ssl 拉取

\n
git config --global http.proxy 'http://127.0.0.1:1081'\ngit config --global https.proxy 'https://127.0.0.1:1081'\n# 清除\ngit config --global --unset http.proxy\ngit config --global --unset https.proxy
\n\n

终端临时代理

# cmd临时代理方案(cmd窗口关闭,则代理失效)\nset http_proxy=http://127.0.0.1:50015\nset https_proxy=http://127.0.0.1:50015
\n\n

git 钩子(hooks)

原理:项目 git push 到远程仓库,远程仓库的钩子 post 通知 www/wwwroot 下的站点 pull 远程仓库

\n
\n

git 钩子需要 git 服务和 pull 在同一环境中

\n
\n
    \n
  • 创建远程仓库,配置钩子,git post-receive 钩子
  • \n
\n
#!/bin/bash\nunset $(git rev-parse --local-env-vars);\n# post-receive接收到pull指令后,执行bash命令\ncd /www/wwwroot/doc.mozzie.cn/  &&  git pull origin master
\n\n
\n

web 目录下 doc.mozzie.cn 是网站目录,本地项目编译打包后,直接 git push 的目录

\n
\n

web 钩子

待耍

\n

项目

统计项目代码行数

统计所有人代码增删量,拷贝如下命令, git bash 终端,git 项目某分支下执行

\n
git log --format='%aN' | sort -u | while read name; do echo -en \"$name\\t\"; git log --author=\"$name\" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf \"added lines: %s, removed lines: %s, total lines: %s\\n\", add, subs, loc }' -; done\n
\n\n

统计制定提交者代码量

替换username为提交者的名称

\n
git log --author=\"username\" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf \"added lines: %s, removed lines: %s, total lines: %s\\n\", add, subs, loc }' -
\n\n

搭建 gogs

gogs 官网下载,压缩包解压到www/wwwroot/

\n
cd /www/wwwroot/gogs\n# 启服务,默认3000的端口,被占用则 ./gogs web -port 3001\n./gogs web
\n\n

浏览器访问http://yourip:3000/install,注意服务器安全组放行 3000 端口

\n
    \n
  • 为 gogs 添加 mysql 数据库
  • \n
  • 配置 mysql
  • \n
  • 配置 gogs 相关信息
  • \n
  • 配置其他信息
  • \n
\n

停掉./gogs web

\n

在刚安装的 gogs 路径下,找到/gogs/scripts/systemd/gogs.service 文件复制一份

\n
User=root\nGroup=root\nWorkingDirectory=/www/wwwroot/git.mozzie.cn\nExecStart=/www/wwwroot/git.mozzie.cn/gogs web\nRestart=always\nEnvironment=USER=root HOME=/www/wwwroot/git.mozzie.cn
\n\n

将修改好的gogs.service文件上传到/etc/systemd/system下,并执行以下命令来激活 gogs

\n
sudo systemctl enable gogs\n# 启动gogs\nsudo systemctl start gogs
\n\n

/gogs/custom/conf/app.ini,修改该文件可以自定义配置,安装步骤填错了,可以这里修改,重启 gogs 服务即可

\n

配置 gogs GIT 钩子

场景:以 Gogs 为例,先 git push A项目 到远程仓库 REPO,服务端 web 目录webB文件夹触发钩子执行 git pull

\n

1、gogs 初始化一个仓库REPO,仓库设置=>管理 GIT 钩子=>post-receive,填入

\n
#!/bin/sh\nexport GIT_WORK_TREE=/www/wwwroot/webB\nexport GIT_DIR=${GIT_WORK_TREE}/.git\ncd ${GIT_WORK_TREE}\ngit pull
\n\n

2、利用 ssh 工具登录服务器

\n
ssh root@mozzie.cn
\n\n

3、生成 ssh key

\n
ssh-keygen -t rsa -C \"himozzie@foxmail.com\"
\n\n

4、配置公钥到 gogs

\n
cat ~/.ssh/id_rsa.pub
\n\n

把打印出的公钥,配置到 ssh 密钥

\n

5、最后一步

\n

配置好服务端的公钥后,就可以无需用户名密码,cd /webB目录下,执行git pull,此后每次git push A项目,服务端都会触发GIT钩子,自动从REPO拉取最新的仓库文件

\n","site":{"data":{}},"excerpt":"","more":"

GIT 最佳实践

GIT 本质是一个数据库,用来存代码的

\n
    \n
  • 工作区:一个沙箱环境,GIT 不负责管理,你尽管在沙箱里面对文件进行操作
  • \n
  • 暂存区:工作区文件变动先不急着提交,暂存到一定数量,在提交到版本库
  • \n
  • 版本库:
  • \n
\n
\n

!! Linus 永远的神!

\n
\n

配置用户

git config --global user.name \"mozzie\"\ngit config --global user.email himozzie@foxmail.com
\n\n

alias 别名

解决参数太多,记不住的问题

\n
\n

!! HEAD -> master HEAD 相当于一个指针,指向当前所在分支

\n
\n
# 查看项目分支图,\ngit config --global alias.lo \"log --oneline --decorate --graph --all\"
\n\n

.git 文件结构

    \n
  • hooks:提交代码前,检查代码格式……
  • \n
  • info:包含一个排除性文件
  • \n
  • logs:保存日志信息,不太需要
  • \n
  • objects:相当于数据库,存储所有数据内容
  • \n
  • refs:存放提交对象指针,管理分支的
  • \n
  • config:配置文件
  • \n
  • description:仓库描述信息
  • \n
  • HEAD:指示目前被检出的分支
  • \n
  • index:文件保存暂存区信息
  • \n
\n

修改远程仓库

# way 1\ngit remote set-url origin [url]\n# way 2\ngit remote rm origin\ngit remote add origin [url]\n# way3\n修改 config 文件
\n\n

高层命令

初始化仓库

git init

\n

修改添加到暂存区

git add ./,相当于如下操作:

\n
# 有多少文件改动,就执行多少次\ngit hash-object -w 文件名\ngit update-index
\n\n
\n

!! git add ./ 先把工作区生成 git 对象,放到版本库,然后再放到暂存区

\n
\n

暂存区提交到版本库

git commit -m 'comment',相当于如下操作:

\n
git write-tree\ngit commit-tree
\n\n

也可以跳过暂存区提交,git commit -a -m

\n

查看哪些修改没有暂存

git diff:没有暂存

\n

git diff --staged:查看哪些修改以及被暂存了,但没有提交

\n

查看提交历史记录

git log --oneline,打印出hash值是提交对象

\n

分支

本质是一个提交对象,每次git branch name中的name,指针HEAD,就会根据name指向提交对象

\n

如果要开发新功能,就新建一个分支A,写完再合master分支,正常来说master分支不会轻易给修改权限。

\n

如果另一个新功能,和分支A同级、并行的,那就切到master分支,在master分支基础上,开分支B,进行新功能开发

\n
\n

一般来说,master 分支没有权限,需要自己重新写开一个分支,分支名用 nickname

\n
\n
C1              master\n|——C2——C3——C4   mozzie
\n\n

分支列表

git branch

\n

创建分支

git branch 分支名,并不会自动切换到分支

\n

切换分支

\n

!! 最佳实践:每次切换分之前,git status 查一下,当前分支一定要是干净的

\n
\n

git checkout 分支名

\n

合并分支

\n

!! 做任何事情,确保做完了,再合并到 master 分支

\n
\n

场景:需要增加功能feat:#53

\n
# HEAD -> master,新开一个分支\ngit checkout -b 'feat53'
\n\n

突然发现 bug,需要修复bug:#52

\n
# HEAD -> feat53,先提交#53分支的工作\ngit commit -a -m 'feat53 完成50%'\n# 切回 master 分支\ngit checkout master\n# HEAD -> master,创建 issue52 分支\ngit checkout -b 'issue52'\n# 改完了issue52\ngit commit -a -m 'fix:issue52'\n# HEAD -> master\ngit checkout master\n# 合\ngit merge issue52\n# 删除 issue52分支(hash还在)\ngit branch -d issue52
\n\n
\n

此时,由于issue52是在之前的 master 分支上生成的,故而feat53的分支仍然存在issue52的 bug,所以有可能存在冲突,需要手动解决

\n
\n
# HEAD -> master\ngit merge feat53\n# 此处省略解决冲突\ngit add ./\ngit commit -m 'fix:merge conflict'\n# 删除 feat53\ngit branch -d feat53
\n\n

删除分支

查看哪些分支合并到当前分支,git branch --merged,这个列表中分支名字前没有*号的分支通常可以使用git branch -d 分支名删掉

\n

git branch -D 分支名,强制删除

\n

新建分支并指向指定提交对象

git branch name commitHash,例git log --oneline如下:

\n
* hasfh2asd 1.txt\n* 1shfd2zsw 2.txt\n* 67rf73has 3.txt\n* 03uhr4rug 4.txt
\n\n

输入git branch CCC 03uhr4rug,那么会创建一个名为CCC的分支,并且CCC分支有4.txt

\n
\n

通常想看原来的某个版本的代码,就可以这样操作,看完,把这个分支删了

\n
\n

远程分支

\n

git clone下来的分支,默认就会建立一个远程跟踪分支(同步关系),例如 master 分支

\n
\n
    \n
  • 本地分支
  • \n
\n

场景一:如果想公开一个share分支 ,与他人共同写作:

\n
# 过程中会生成生成一个远程跟踪分支 origin/share\ngit push origin share
\n\n

场景二:创建一个本地分支b1,直接跟踪远程分支orgin/b1

\n
git checkout -b 'b1' 'origin/b1'
\n\n

场景三:已存在一个本地分支dev,改成远程跟踪分支

\n
# HEAD -> dev,建立 本地分支 dev 与 远程分支 origin/dev 关系\ngit branch -u origin/dev\n# 这样就可以直接\ngit push / git pull
\n\n
    \n
  • 远程分支
  • \n
\n

查看远程分支:git remote -v

\n

查看当前本地分支的远程跟踪分支:git branch -vv

\n

远程分支删除, 本地更新 –prune

# 不加 --prune,和 fetch 等价, 远程被删除的分支不会同步删除本地origin的分支\ngit remote update origin --prune
\n\n

提交规范

type(scope): subject\n# 例如\nfeat(miniprogram): 增加了小程序模板消息相关功能
\n\n

通常type有如下:

\n
    \n
  • feat - 新功能 feature
  • \n
  • fix - 修复 bug
  • \n
  • docs - 文档注释
  • \n
  • style - 代码格式(不影响代码运行的变动)
  • \n
  • refactor - 重构、优化(既不增加新功能,也不是修复 bug)
  • \n
  • perf - 性能优化
  • \n
  • test - 增加测试
  • \n
  • chore - 构建过程或辅助工具的变动
  • \n
  • revert - 回退
  • \n
  • build - 打包
  • \n
\n

自动生成 Change log

原理:利用 child_process获取 git log内容,处理字符串

\n
const execSync = require(\"child_process\").execSync; //同步子进程\nconst fs = require(\"fs\");\nconst process = require(\"process\");\nconst path = require(\"path\");\nconst inquirer = require(\"inquirer\");\nconst dayjs = require(\"dayjs\");\nconst axios = require(\"axios\");\n\n// env\nconst isForCommanHuman = process.argv.includes(\"--common\");\n\n// changelog.md生成路径\nconst outputpath = path.resolve(process.cwd(), \"./changelog.md\");\n// 非空检测\nif (!fs.existsSync(outputpath)) fs.writeFile(outputpath, \"\", (err) => {});\n// 华丽的gitlog日志\nconst perfectGitLog = (startTime, endTime) =>\n  isForCommanHuman\n    ? `git log --since=\"${startTime}\" --until=\"${endTime}\" --no-merges  --pretty=format:\"%cr %C(cyan)%s\"`\n    : `git log --since=\"${startTime}\" --until=\"${endTime}\" --no-merges  --pretty=format:\"%C(yellow)%h %C(green)%cn %C(redz)(%cr:%ci) %C(cyan)%s\"`;\n\nconst wxrobotHook =\n  \"https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=be4d4473-c290-4ddd-a089-41df3ed1d601\";\n\n// 当前时间\nconst now = Date.now();\n// 2周前\nconst weeks_2_ago = now - 14 * 24 * 60 * 60 * 1000;\n\ninquirer\n  .prompt([\n    {\n      type: \"input\",\n      name: \"startDate\",\n      message: `起始时间,默认13天前 \\n`,\n      default: dayjs(weeks_2_ago).format(\"YYYY.MM.DD\"),\n      validate: (val) => /\\d{4}.\\d{2}.\\d{2}/.test(val),\n    },\n    {\n      type: \"input\",\n      name: \"endDate\",\n      message: `结束时间,默认今天 \\n`,\n      default: dayjs(now).format(\"YYYY.MM.DD\"),\n      validate: (val) => /\\d{4}.\\d{2}.\\d{2}/.test(val),\n    },\n    {\n      type: \"rawlist\",\n      message: \"是否通知到微信机器人:\",\n      name: \"notifyWxrobot\",\n      choices: [\"Y\", \"N\"],\n    },\n  ])\n  .then((answers) => {\n    const { startDate, endDate, notifyWxrobot } = answers;\n    const rowTemplate = perfectGitLog(startDate, endDate);\n    let fmt = execSync(rowTemplate)\n      .toString()\n      .trim()\n      .replace(/feat: /gi, \"✅: \")\n      .replace(/fix: /gi, \"🐛: \")\n      .replace(/chore: /gi, \"🎨: \")\n      .replace(/perf: /gi, \"⚡: \")\n      .replace(/docs: /gi, \"📝: \")\n      .replace(/refactor: /gi, \"🔨: \")\n      .replace(/anno: /gi, \"🔖: \")\n      .replace(/style: /gi, \"👷: \");\n    fs.writeFileSync(outputpath, fmt, (err) => {});\n    // 通知微信群聊机器人\n    if (notifyWxrobot === \"Y\") {\n      axios.post(wxrobotHook, {\n        msgtype: \"markdown\",\n        markdown: {\n          content: fmt,\n        },\n      });\n    }\n  })\n  .catch((error) => {\n    if (error.isTtyError) {\n      // Prompt couldn't be rendered in the current environment\n    } else {\n      // Something else went wrong\n    }\n  });
\n\n

使用 husky+eslint 规范提交

\n

需要配合 eslint

\n
\n

git init后,yarn add husky,在package.json配置

\n
{\n  \"husky\": {\n    \"hooks\": {\n      \"pre-commit\": \"npm run lint\"\n    }\n  }\n}
\n\n

生成 ssh key

ssh-keygen -t rsa -C \"himozzie@foxmail.com\"
\n\n

查看 ssh 公钥

cat ~/.ssh/id_rsa.pub
\n\n

git 代理

\n

前提条件是开了扶墙工具

\n
\n

git clone 拉取方式选择 http/https(默认会让你输入账号密码,比较蛋疼),不要选择 ssl 拉取

\n
git config --global http.proxy 'http://127.0.0.1:1081'\ngit config --global https.proxy 'https://127.0.0.1:1081'\n# 清除\ngit config --global --unset http.proxy\ngit config --global --unset https.proxy
\n\n

终端临时代理

# cmd临时代理方案(cmd窗口关闭,则代理失效)\nset http_proxy=http://127.0.0.1:50015\nset https_proxy=http://127.0.0.1:50015
\n\n

git 钩子(hooks)

原理:项目 git push 到远程仓库,远程仓库的钩子 post 通知 www/wwwroot 下的站点 pull 远程仓库

\n
\n

git 钩子需要 git 服务和 pull 在同一环境中

\n
\n
    \n
  • 创建远程仓库,配置钩子,git post-receive 钩子
  • \n
\n
#!/bin/bash\nunset $(git rev-parse --local-env-vars);\n# post-receive接收到pull指令后,执行bash命令\ncd /www/wwwroot/doc.mozzie.cn/  &&  git pull origin master
\n\n
\n

web 目录下 doc.mozzie.cn 是网站目录,本地项目编译打包后,直接 git push 的目录

\n
\n

web 钩子

待耍

\n

项目

统计项目代码行数

统计所有人代码增删量,拷贝如下命令, git bash 终端,git 项目某分支下执行

\n
git log --format='%aN' | sort -u | while read name; do echo -en \"$name\\t\"; git log --author=\"$name\" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf \"added lines: %s, removed lines: %s, total lines: %s\\n\", add, subs, loc }' -; done\n
\n\n

统计制定提交者代码量

替换username为提交者的名称

\n
git log --author=\"username\" --pretty=tformat: --numstat | awk '{ add += $1; subs += $2; loc += $1 - $2 } END { printf \"added lines: %s, removed lines: %s, total lines: %s\\n\", add, subs, loc }' -
\n\n

搭建 gogs

gogs 官网下载,压缩包解压到www/wwwroot/

\n
cd /www/wwwroot/gogs\n# 启服务,默认3000的端口,被占用则 ./gogs web -port 3001\n./gogs web
\n\n

浏览器访问http://yourip:3000/install,注意服务器安全组放行 3000 端口

\n
    \n
  • 为 gogs 添加 mysql 数据库
  • \n
  • 配置 mysql
  • \n
  • 配置 gogs 相关信息
  • \n
  • 配置其他信息
  • \n
\n

停掉./gogs web

\n

在刚安装的 gogs 路径下,找到/gogs/scripts/systemd/gogs.service 文件复制一份

\n
User=root\nGroup=root\nWorkingDirectory=/www/wwwroot/git.mozzie.cn\nExecStart=/www/wwwroot/git.mozzie.cn/gogs web\nRestart=always\nEnvironment=USER=root HOME=/www/wwwroot/git.mozzie.cn
\n\n

将修改好的gogs.service文件上传到/etc/systemd/system下,并执行以下命令来激活 gogs

\n
sudo systemctl enable gogs\n# 启动gogs\nsudo systemctl start gogs
\n\n

/gogs/custom/conf/app.ini,修改该文件可以自定义配置,安装步骤填错了,可以这里修改,重启 gogs 服务即可

\n

配置 gogs GIT 钩子

场景:以 Gogs 为例,先 git push A项目 到远程仓库 REPO,服务端 web 目录webB文件夹触发钩子执行 git pull

\n

1、gogs 初始化一个仓库REPO,仓库设置=>管理 GIT 钩子=>post-receive,填入

\n
#!/bin/sh\nexport GIT_WORK_TREE=/www/wwwroot/webB\nexport GIT_DIR=${GIT_WORK_TREE}/.git\ncd ${GIT_WORK_TREE}\ngit pull
\n\n

2、利用 ssh 工具登录服务器

\n
ssh root@mozzie.cn
\n\n

3、生成 ssh key

\n
ssh-keygen -t rsa -C \"himozzie@foxmail.com\"
\n\n

4、配置公钥到 gogs

\n
cat ~/.ssh/id_rsa.pub
\n\n

把打印出的公钥,配置到 ssh 密钥

\n

5、最后一步

\n

配置好服务端的公钥后,就可以无需用户名密码,cd /webB目录下,执行git pull,此后每次git push A项目,服务端都会触发GIT钩子,自动从REPO拉取最新的仓库文件

\n"},{"title":"wsl2","status":"done","_content":"\n# 安装 wsl\n\n前置条件,主板 bios 开启 `intel 虚拟化`\n\n> ms app store如果打不开、转圈 -> 关闭小飞机,也可以试试 改 ipv4 host 4.2.2.2\n\n- windows terminal 必备,ms store 下载\n\n[巨硬官方文档](https://docs.microsoft.com/en-us/windows/wsl/install)\n\n# ubuntu软件源\n\n[阿里开源镜像站](https://developer.aliyun.com/mirror/)\n\n下面是 ubuntu20.04 用的\n\n```bash\n# 备份apt默认源\nsudo cp /etc/apt/sources.list /etc/apt/sources.list.bak\nsudo vim /etc/apt/sources.list\n# 替换 /srouces.list\ndeb http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse\n\ndeb http://mirrors.aliyun.com/ubuntu/ focal-security main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ focal-security main restricted universe multiverse\n\ndeb http://mirrors.aliyun.com/ubuntu/ focal-updates main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ focal-updates main restricted universe multiverse\n\n# deb http://mirrors.aliyun.com/ubuntu/ focal-proposed main restricted universe multiverse\n# deb-src http://mirrors.aliyun.com/ubuntu/ focal-proposed main restricted universe multiverse\n\ndeb http://mirrors.aliyun.com/ubuntu/ focal-backports main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ focal-backports main restricted universe multiverse\n\n\n# 更新apt\nsudo apt upgrade\nsudo apt update\n```\n\n# 安装 zsh\n\n> 注意`.zshrc`在安装的`用户`目录下,别和`root`搞混了\n\n```bash\n# 安装\nsudo apt install zsh\n# 将 zsh 设置为默认 shell\nchsh -s /bin/zsh\n# 检查,若没成功,重启试试看\necho $SHELL\n```\n\n# 安装 oh-my-zsh\n\n```bash\n# 443 confused 手动 vim oh-my-zsh.sh 然后 bash ./oh-my-zsh.sh \nsh -c \"$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)\"\n```\n\n# 配置 oh-my-zsh 主题/插件/alias\n\n安装插件\n\n- 自动补全: \n\n```bash\ngit clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions\n```\n\n- 代码高亮: \n\n```bash\ngit clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting\n```\n\n修改`当前用户`目录下的 `.zshrc`\n\n```bash\n# 主题\nZSH_THEME=\"ys\"\n# 引入插件\nplugins=(git zsh-autosuggestions zsh-syntax-highlighting)\n# alias\nalias cls='clear'\nalias ga='git add'\nalias gc='git commit -m'\nalias gp='git push'\nalias gf='git fetch'\nalias update='sudo apt update'\nalias upgrade='sudo apt upgrade'\nalias install='sudo apt install'\n# windows文件管理器打开wsl文件\nalias open='explorer.exe'\n```\n\n# 安装 Nodejs\n\n[巨硬文档赛高](https://docs.microsoft.com/en-us/windows/dev-environment/javascript/nodejs-on-wsl)\n\n安装完注意文字提示\n\n```bash\n# =>Appending nvm source string to /home/mozzie/.zshrc\n# => Appending bash_completion source string to /home/mozzie/.zshrc\n# => Close and reopen your terminal to start using nvm or run the following to use it now:\n```\n照它说的做\n\n```bash\nexport NVM_DIR=\"$HOME/.nvm\"\n[ -s \"$NVM_DIR/nvm.sh\" ] && \\. \"$NVM_DIR/nvm.sh\" # This loads nvm\n[ -s \"$NVM_DIR/bash_completion\" ] && \\. \"$NVM_DIR/bash_completion\" # This loads nvm bash_completion\n```\n\n验证`nvm`安装 `nvm ls`,会看见类似\n\n```bash\niojs -> N/A (default)\nnode -> stable (-> N/A) (default)\nunstable -> N/A (default)\n```\n\n安装 `nodejs stable`\n\n```bash\nnvm install node\n# node -v | npm -v 验证安装版本\n```\n\n# 外网/LAN 访问 wsl2 服务\n\n[巨硬官方解释](https://docs.microsoft.com/en-us/windows/wsl/networking)\n\nWhen using a WSL 1 distribution, if your computer was set up to be accessed by your LAN, then applications run in WSL could be accessed on your LAN as well.\n\nThis isn't the default case in WSL 2. WSL 2 has a virtualized ethernet adapter with its own unique IP address. Currently, to enable this workflow you will need to go through the same steps as you would for a regular virtual machine. (We are looking into ways to improve this experience.)\n\nHere's an example PowerShell command to add a port proxy that listens on port 4000 on the host and connects it to port 4000 to the WSL 2 VM with IP address 192.168.101.100\n\n```bash\nnetsh interface portproxy add v4tov4 listenport=4000 listenaddress=0.0.0.0 connectport=4000 connectaddress=192.168.101.100\n```\n> 注意端口覆盖的问题,避免 windows 端口和端口 wsl2 冲突\n\n## netsh 端口映射\n\n- listenaddress: 监听地址, 0.0.0.0 表示匹配所有地址\n- listenport: 监听的 windows 端口\n\n- connectaddress: 转发到 wsl2 的 ip地址, 这里设置为localhost,默认从 windows 可以通过localhost 访问 wsl2\n- connectport: 转发到 wsl2 的端口\n\n例如 windows 的 ip 为 `192.168.1.100`,监听 windows 的 3000 端口,转发到 wsl2 ip localhost 的 3000 端口\n\n```bash\n# windows-terminal 管理员权限执行\nnetsh interface portproxy add v4tov4 listenport=3000 listenaddress=0.0.0.0 connectport=3000 connectaddress=localhost\n# 删除端口监听\nnetsh interface portproxy delete v4tov4 listenaddress=0.0.0.0 listenport=3000\n```\n\n## 配置 windows defender 防火墙入站规则\n\n新建规则 -> 端口 -> TCP / 特定本地端口(3000) -> 允许链接 -> 下一步 -> 取个名字 -> Done\n\n# docker \n\nwindows 宿主机安装 `docker desktop`,`设置 -> 资源 -> WSL INTEGRATION` 打开 对应的 linux发行版,即使用\n\n## mysql 容器\n\n```bash\n# brdige\ndocker network create --driver bridge --subnet=172.21.0.0/16 wsl2\n# pull\ndocker pull mysql:5.7.38\n# 生产 mysql 5.7.38 容器\ndocker run --restart=always --privileged=true -p 3306:3306 --name mysql --net wsl2 --ip 172.21.0.5 -e MYSQL_ROOT_PASSWORD=root -d mysql:5.7.38\n```\n\n## mongo 容器\n\n```bash\ndocker pull mongo:5.0\n# 宿主机 /mongo/data\ndocker run --restart=always -d -p 27017:27017 -v /mongo/data:/data --name mongo --net wsl2 --ip 172.21.0.6 mongo:5.0 --auth\n# 初始化\ndocker exec -it mongo /bin/bash\n# 进入 mongo shell\nmongo\n# admin\nuse admin\n# 创建root用户,管理全部数据库的权限,这会可以navicat等gui链接数据库,用户名密码root,验证数据库admin\ndb.createUser({\n user: \"root\",\n pwd: \"root\",\n roles: [ { role: \"userAdminAnyDatabase\", db: \"admin\" } ]\n})\n# 退出\nexit\n# 再次进入\nmongo\n# 使用root登录授权,正确返回 1\ndb.auth(\"root\",\"root\")\n# 创建 testDB 数据库\nuse testDB\n# 创建 test 用户 管理 testDB\ndb.createUser({user:'test',pwd:'test',roles:[{role:'dbOwner',db:'testDB'}]})\n```\n\n使用 navicat 登录,验证数据库 `admin` ,用户名密码 `test`\n\n\n# 默认 wsl root 用户登录\n\n```bash\n# wsl -l 查看 ubuntu版本,例如 Ubuntu-20.04\nUbuntu2004 config --default-user root\n```\n","source":"_posts/front-end/wsl2.md","raw":"---\ntitle: wsl2\ncategories:\n - Front-End\nstatus: done\n---\n\n# 安装 wsl\n\n前置条件,主板 bios 开启 `intel 虚拟化`\n\n> ms app store如果打不开、转圈 -> 关闭小飞机,也可以试试 改 ipv4 host 4.2.2.2\n\n- windows terminal 必备,ms store 下载\n\n[巨硬官方文档](https://docs.microsoft.com/en-us/windows/wsl/install)\n\n# ubuntu软件源\n\n[阿里开源镜像站](https://developer.aliyun.com/mirror/)\n\n下面是 ubuntu20.04 用的\n\n```bash\n# 备份apt默认源\nsudo cp /etc/apt/sources.list /etc/apt/sources.list.bak\nsudo vim /etc/apt/sources.list\n# 替换 /srouces.list\ndeb http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse\n\ndeb http://mirrors.aliyun.com/ubuntu/ focal-security main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ focal-security main restricted universe multiverse\n\ndeb http://mirrors.aliyun.com/ubuntu/ focal-updates main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ focal-updates main restricted universe multiverse\n\n# deb http://mirrors.aliyun.com/ubuntu/ focal-proposed main restricted universe multiverse\n# deb-src http://mirrors.aliyun.com/ubuntu/ focal-proposed main restricted universe multiverse\n\ndeb http://mirrors.aliyun.com/ubuntu/ focal-backports main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ focal-backports main restricted universe multiverse\n\n\n# 更新apt\nsudo apt upgrade\nsudo apt update\n```\n\n# 安装 zsh\n\n> 注意`.zshrc`在安装的`用户`目录下,别和`root`搞混了\n\n```bash\n# 安装\nsudo apt install zsh\n# 将 zsh 设置为默认 shell\nchsh -s /bin/zsh\n# 检查,若没成功,重启试试看\necho $SHELL\n```\n\n# 安装 oh-my-zsh\n\n```bash\n# 443 confused 手动 vim oh-my-zsh.sh 然后 bash ./oh-my-zsh.sh \nsh -c \"$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)\"\n```\n\n# 配置 oh-my-zsh 主题/插件/alias\n\n安装插件\n\n- 自动补全: \n\n```bash\ngit clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions\n```\n\n- 代码高亮: \n\n```bash\ngit clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting\n```\n\n修改`当前用户`目录下的 `.zshrc`\n\n```bash\n# 主题\nZSH_THEME=\"ys\"\n# 引入插件\nplugins=(git zsh-autosuggestions zsh-syntax-highlighting)\n# alias\nalias cls='clear'\nalias ga='git add'\nalias gc='git commit -m'\nalias gp='git push'\nalias gf='git fetch'\nalias update='sudo apt update'\nalias upgrade='sudo apt upgrade'\nalias install='sudo apt install'\n# windows文件管理器打开wsl文件\nalias open='explorer.exe'\n```\n\n# 安装 Nodejs\n\n[巨硬文档赛高](https://docs.microsoft.com/en-us/windows/dev-environment/javascript/nodejs-on-wsl)\n\n安装完注意文字提示\n\n```bash\n# =>Appending nvm source string to /home/mozzie/.zshrc\n# => Appending bash_completion source string to /home/mozzie/.zshrc\n# => Close and reopen your terminal to start using nvm or run the following to use it now:\n```\n照它说的做\n\n```bash\nexport NVM_DIR=\"$HOME/.nvm\"\n[ -s \"$NVM_DIR/nvm.sh\" ] && \\. \"$NVM_DIR/nvm.sh\" # This loads nvm\n[ -s \"$NVM_DIR/bash_completion\" ] && \\. \"$NVM_DIR/bash_completion\" # This loads nvm bash_completion\n```\n\n验证`nvm`安装 `nvm ls`,会看见类似\n\n```bash\niojs -> N/A (default)\nnode -> stable (-> N/A) (default)\nunstable -> N/A (default)\n```\n\n安装 `nodejs stable`\n\n```bash\nnvm install node\n# node -v | npm -v 验证安装版本\n```\n\n# 外网/LAN 访问 wsl2 服务\n\n[巨硬官方解释](https://docs.microsoft.com/en-us/windows/wsl/networking)\n\nWhen using a WSL 1 distribution, if your computer was set up to be accessed by your LAN, then applications run in WSL could be accessed on your LAN as well.\n\nThis isn't the default case in WSL 2. WSL 2 has a virtualized ethernet adapter with its own unique IP address. Currently, to enable this workflow you will need to go through the same steps as you would for a regular virtual machine. (We are looking into ways to improve this experience.)\n\nHere's an example PowerShell command to add a port proxy that listens on port 4000 on the host and connects it to port 4000 to the WSL 2 VM with IP address 192.168.101.100\n\n```bash\nnetsh interface portproxy add v4tov4 listenport=4000 listenaddress=0.0.0.0 connectport=4000 connectaddress=192.168.101.100\n```\n> 注意端口覆盖的问题,避免 windows 端口和端口 wsl2 冲突\n\n## netsh 端口映射\n\n- listenaddress: 监听地址, 0.0.0.0 表示匹配所有地址\n- listenport: 监听的 windows 端口\n\n- connectaddress: 转发到 wsl2 的 ip地址, 这里设置为localhost,默认从 windows 可以通过localhost 访问 wsl2\n- connectport: 转发到 wsl2 的端口\n\n例如 windows 的 ip 为 `192.168.1.100`,监听 windows 的 3000 端口,转发到 wsl2 ip localhost 的 3000 端口\n\n```bash\n# windows-terminal 管理员权限执行\nnetsh interface portproxy add v4tov4 listenport=3000 listenaddress=0.0.0.0 connectport=3000 connectaddress=localhost\n# 删除端口监听\nnetsh interface portproxy delete v4tov4 listenaddress=0.0.0.0 listenport=3000\n```\n\n## 配置 windows defender 防火墙入站规则\n\n新建规则 -> 端口 -> TCP / 特定本地端口(3000) -> 允许链接 -> 下一步 -> 取个名字 -> Done\n\n# docker \n\nwindows 宿主机安装 `docker desktop`,`设置 -> 资源 -> WSL INTEGRATION` 打开 对应的 linux发行版,即使用\n\n## mysql 容器\n\n```bash\n# brdige\ndocker network create --driver bridge --subnet=172.21.0.0/16 wsl2\n# pull\ndocker pull mysql:5.7.38\n# 生产 mysql 5.7.38 容器\ndocker run --restart=always --privileged=true -p 3306:3306 --name mysql --net wsl2 --ip 172.21.0.5 -e MYSQL_ROOT_PASSWORD=root -d mysql:5.7.38\n```\n\n## mongo 容器\n\n```bash\ndocker pull mongo:5.0\n# 宿主机 /mongo/data\ndocker run --restart=always -d -p 27017:27017 -v /mongo/data:/data --name mongo --net wsl2 --ip 172.21.0.6 mongo:5.0 --auth\n# 初始化\ndocker exec -it mongo /bin/bash\n# 进入 mongo shell\nmongo\n# admin\nuse admin\n# 创建root用户,管理全部数据库的权限,这会可以navicat等gui链接数据库,用户名密码root,验证数据库admin\ndb.createUser({\n user: \"root\",\n pwd: \"root\",\n roles: [ { role: \"userAdminAnyDatabase\", db: \"admin\" } ]\n})\n# 退出\nexit\n# 再次进入\nmongo\n# 使用root登录授权,正确返回 1\ndb.auth(\"root\",\"root\")\n# 创建 testDB 数据库\nuse testDB\n# 创建 test 用户 管理 testDB\ndb.createUser({user:'test',pwd:'test',roles:[{role:'dbOwner',db:'testDB'}]})\n```\n\n使用 navicat 登录,验证数据库 `admin` ,用户名密码 `test`\n\n\n# 默认 wsl root 用户登录\n\n```bash\n# wsl -l 查看 ubuntu版本,例如 Ubuntu-20.04\nUbuntu2004 config --default-user root\n```\n","slug":"front-end/wsl2","published":1,"date":"2023-11-06T07:59:45.720Z","updated":"2023-11-06T08:02:38.239Z","comments":1,"layout":"post","photos":[],"link":"","_id":"clp7x1951000iv3z3gvgqbt7a","content":"

安装 wsl

前置条件,主板 bios 开启 intel 虚拟化

\n
\n

ms app store如果打不开、转圈 -> 关闭小飞机,也可以试试 改 ipv4 host 4.2.2.2

\n
\n
    \n
  • windows terminal 必备,ms store 下载
  • \n
\n

巨硬官方文档

\n

ubuntu软件源

阿里开源镜像站

\n

下面是 ubuntu20.04 用的

\n
# 备份apt默认源\nsudo cp /etc/apt/sources.list /etc/apt/sources.list.bak\nsudo vim /etc/apt/sources.list\n# 替换 /srouces.list\ndeb http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse\n\ndeb http://mirrors.aliyun.com/ubuntu/ focal-security main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ focal-security main restricted universe multiverse\n\ndeb http://mirrors.aliyun.com/ubuntu/ focal-updates main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ focal-updates main restricted universe multiverse\n\n# deb http://mirrors.aliyun.com/ubuntu/ focal-proposed main restricted universe multiverse\n# deb-src http://mirrors.aliyun.com/ubuntu/ focal-proposed main restricted universe multiverse\n\ndeb http://mirrors.aliyun.com/ubuntu/ focal-backports main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ focal-backports main restricted universe multiverse\n\n\n# 更新apt\nsudo apt upgrade\nsudo apt update
\n\n

安装 zsh

\n

注意.zshrc在安装的用户目录下,别和root搞混了

\n
\n
# 安装\nsudo apt install zsh\n# 将 zsh 设置为默认 shell\nchsh -s /bin/zsh\n# 检查,若没成功,重启试试看\necho $SHELL
\n\n

安装 oh-my-zsh

# 443 confused 手动 vim oh-my-zsh.sh 然后 bash ./oh-my-zsh.sh \nsh -c \"$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)\"
\n\n

配置 oh-my-zsh 主题/插件/alias

安装插件

\n
    \n
  • 自动补全:
  • \n
\n
git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
\n\n
    \n
  • 代码高亮:
  • \n
\n
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting
\n\n

修改当前用户目录下的 .zshrc

\n
# 主题\nZSH_THEME=\"ys\"\n# 引入插件\nplugins=(git zsh-autosuggestions zsh-syntax-highlighting)\n# alias\nalias cls='clear'\nalias ga='git add'\nalias gc='git commit -m'\nalias gp='git push'\nalias gf='git fetch'\nalias update='sudo apt update'\nalias upgrade='sudo apt upgrade'\nalias install='sudo apt install'\n# windows文件管理器打开wsl文件\nalias open='explorer.exe'
\n\n

安装 Nodejs

巨硬文档赛高

\n

安装完注意文字提示

\n
# =>Appending nvm source string to /home/mozzie/.zshrc\n# => Appending bash_completion source string to /home/mozzie/.zshrc\n# => Close and reopen your terminal to start using nvm or run the following to use it now:
\n

照它说的做

\n
export NVM_DIR=\"$HOME/.nvm\"\n[ -s \"$NVM_DIR/nvm.sh\" ] && \\. \"$NVM_DIR/nvm.sh\"  # This loads nvm\n[ -s \"$NVM_DIR/bash_completion\" ] && \\. \"$NVM_DIR/bash_completion\"  # This loads nvm bash_completion
\n\n

验证nvm安装 nvm ls,会看见类似

\n
iojs -> N/A (default)\nnode -> stable (-> N/A) (default)\nunstable -> N/A (default)
\n\n

安装 nodejs stable

\n
nvm install node\n# node -v | npm -v 验证安装版本
\n\n

外网/LAN 访问 wsl2 服务

巨硬官方解释

\n

When using a WSL 1 distribution, if your computer was set up to be accessed by your LAN, then applications run in WSL could be accessed on your LAN as well.

\n

This isn’t the default case in WSL 2. WSL 2 has a virtualized ethernet adapter with its own unique IP address. Currently, to enable this workflow you will need to go through the same steps as you would for a regular virtual machine. (We are looking into ways to improve this experience.)

\n

Here’s an example PowerShell command to add a port proxy that listens on port 4000 on the host and connects it to port 4000 to the WSL 2 VM with IP address 192.168.101.100

\n
netsh interface portproxy add v4tov4 listenport=4000 listenaddress=0.0.0.0 connectport=4000 connectaddress=192.168.101.100
\n
\n

注意端口覆盖的问题,避免 windows 端口和端口 wsl2 冲突

\n
\n

netsh 端口映射

    \n
  • listenaddress: 监听地址, 0.0.0.0 表示匹配所有地址

    \n
  • \n
  • listenport: 监听的 windows 端口

    \n
  • \n
  • connectaddress: 转发到 wsl2 的 ip地址, 这里设置为localhost,默认从 windows 可以通过localhost 访问 wsl2

    \n
  • \n
  • connectport: 转发到 wsl2 的端口

    \n
  • \n
\n

例如 windows 的 ip 为 192.168.1.100,监听 windows 的 3000 端口,转发到 wsl2 ip localhost 的 3000 端口

\n
# windows-terminal 管理员权限执行\nnetsh interface portproxy add v4tov4 listenport=3000 listenaddress=0.0.0.0 connectport=3000 connectaddress=localhost\n# 删除端口监听\nnetsh interface portproxy delete v4tov4 listenaddress=0.0.0.0 listenport=3000
\n\n

配置 windows defender 防火墙入站规则

新建规则 -> 端口 -> TCP / 特定本地端口(3000) -> 允许链接 -> 下一步 -> 取个名字 -> Done

\n

docker

windows 宿主机安装 docker desktop设置 -> 资源 -> WSL INTEGRATION 打开 对应的 linux发行版,即使用

\n

mysql 容器

# brdige\ndocker network create --driver bridge --subnet=172.21.0.0/16 wsl2\n# pull\ndocker pull mysql:5.7.38\n# 生产 mysql 5.7.38 容器\ndocker run --restart=always --privileged=true -p 3306:3306 --name mysql --net wsl2 --ip 172.21.0.5 -e MYSQL_ROOT_PASSWORD=root -d mysql:5.7.38
\n\n

mongo 容器

docker pull mongo:5.0\n# 宿主机 /mongo/data\ndocker run --restart=always -d -p 27017:27017 -v /mongo/data:/data --name mongo --net wsl2 --ip 172.21.0.6 mongo:5.0 --auth\n# 初始化\ndocker exec -it mongo /bin/bash\n# 进入 mongo shell\nmongo\n# admin\nuse admin\n# 创建root用户,管理全部数据库的权限,这会可以navicat等gui链接数据库,用户名密码root,验证数据库admin\ndb.createUser({\n   user: \"root\",\n   pwd: \"root\",\n   roles: [ { role: \"userAdminAnyDatabase\", db: \"admin\" } ]\n})\n# 退出\nexit\n# 再次进入\nmongo\n# 使用root登录授权,正确返回 1\ndb.auth(\"root\",\"root\")\n# 创建 testDB 数据库\nuse testDB\n# 创建 test 用户 管理 testDB\ndb.createUser({user:'test',pwd:'test',roles:[{role:'dbOwner',db:'testDB'}]})
\n\n

使用 navicat 登录,验证数据库 admin ,用户名密码 test

\n

默认 wsl root 用户登录

# wsl -l 查看 ubuntu版本,例如 Ubuntu-20.04\nUbuntu2004 config --default-user root
\n","site":{"data":{}},"excerpt":"","more":"

安装 wsl

前置条件,主板 bios 开启 intel 虚拟化

\n
\n

ms app store如果打不开、转圈 -> 关闭小飞机,也可以试试 改 ipv4 host 4.2.2.2

\n
\n
    \n
  • windows terminal 必备,ms store 下载
  • \n
\n

巨硬官方文档

\n

ubuntu软件源

阿里开源镜像站

\n

下面是 ubuntu20.04 用的

\n
# 备份apt默认源\nsudo cp /etc/apt/sources.list /etc/apt/sources.list.bak\nsudo vim /etc/apt/sources.list\n# 替换 /srouces.list\ndeb http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse\n\ndeb http://mirrors.aliyun.com/ubuntu/ focal-security main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ focal-security main restricted universe multiverse\n\ndeb http://mirrors.aliyun.com/ubuntu/ focal-updates main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ focal-updates main restricted universe multiverse\n\n# deb http://mirrors.aliyun.com/ubuntu/ focal-proposed main restricted universe multiverse\n# deb-src http://mirrors.aliyun.com/ubuntu/ focal-proposed main restricted universe multiverse\n\ndeb http://mirrors.aliyun.com/ubuntu/ focal-backports main restricted universe multiverse\ndeb-src http://mirrors.aliyun.com/ubuntu/ focal-backports main restricted universe multiverse\n\n\n# 更新apt\nsudo apt upgrade\nsudo apt update
\n\n

安装 zsh

\n

注意.zshrc在安装的用户目录下,别和root搞混了

\n
\n
# 安装\nsudo apt install zsh\n# 将 zsh 设置为默认 shell\nchsh -s /bin/zsh\n# 检查,若没成功,重启试试看\necho $SHELL
\n\n

安装 oh-my-zsh

# 443 confused 手动 vim oh-my-zsh.sh 然后 bash ./oh-my-zsh.sh \nsh -c \"$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)\"
\n\n

配置 oh-my-zsh 主题/插件/alias

安装插件

\n
    \n
  • 自动补全:
  • \n
\n
git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions
\n\n
    \n
  • 代码高亮:
  • \n
\n
git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting
\n\n

修改当前用户目录下的 .zshrc

\n
# 主题\nZSH_THEME=\"ys\"\n# 引入插件\nplugins=(git zsh-autosuggestions zsh-syntax-highlighting)\n# alias\nalias cls='clear'\nalias ga='git add'\nalias gc='git commit -m'\nalias gp='git push'\nalias gf='git fetch'\nalias update='sudo apt update'\nalias upgrade='sudo apt upgrade'\nalias install='sudo apt install'\n# windows文件管理器打开wsl文件\nalias open='explorer.exe'
\n\n

安装 Nodejs

巨硬文档赛高

\n

安装完注意文字提示

\n
# =>Appending nvm source string to /home/mozzie/.zshrc\n# => Appending bash_completion source string to /home/mozzie/.zshrc\n# => Close and reopen your terminal to start using nvm or run the following to use it now:
\n

照它说的做

\n
export NVM_DIR=\"$HOME/.nvm\"\n[ -s \"$NVM_DIR/nvm.sh\" ] && \\. \"$NVM_DIR/nvm.sh\"  # This loads nvm\n[ -s \"$NVM_DIR/bash_completion\" ] && \\. \"$NVM_DIR/bash_completion\"  # This loads nvm bash_completion
\n\n

验证nvm安装 nvm ls,会看见类似

\n
iojs -> N/A (default)\nnode -> stable (-> N/A) (default)\nunstable -> N/A (default)
\n\n

安装 nodejs stable

\n
nvm install node\n# node -v | npm -v 验证安装版本
\n\n

外网/LAN 访问 wsl2 服务

巨硬官方解释

\n

When using a WSL 1 distribution, if your computer was set up to be accessed by your LAN, then applications run in WSL could be accessed on your LAN as well.

\n

This isn’t the default case in WSL 2. WSL 2 has a virtualized ethernet adapter with its own unique IP address. Currently, to enable this workflow you will need to go through the same steps as you would for a regular virtual machine. (We are looking into ways to improve this experience.)

\n

Here’s an example PowerShell command to add a port proxy that listens on port 4000 on the host and connects it to port 4000 to the WSL 2 VM with IP address 192.168.101.100

\n
netsh interface portproxy add v4tov4 listenport=4000 listenaddress=0.0.0.0 connectport=4000 connectaddress=192.168.101.100
\n
\n

注意端口覆盖的问题,避免 windows 端口和端口 wsl2 冲突

\n
\n

netsh 端口映射

    \n
  • listenaddress: 监听地址, 0.0.0.0 表示匹配所有地址

    \n
  • \n
  • listenport: 监听的 windows 端口

    \n
  • \n
  • connectaddress: 转发到 wsl2 的 ip地址, 这里设置为localhost,默认从 windows 可以通过localhost 访问 wsl2

    \n
  • \n
  • connectport: 转发到 wsl2 的端口

    \n
  • \n
\n

例如 windows 的 ip 为 192.168.1.100,监听 windows 的 3000 端口,转发到 wsl2 ip localhost 的 3000 端口

\n
# windows-terminal 管理员权限执行\nnetsh interface portproxy add v4tov4 listenport=3000 listenaddress=0.0.0.0 connectport=3000 connectaddress=localhost\n# 删除端口监听\nnetsh interface portproxy delete v4tov4 listenaddress=0.0.0.0 listenport=3000
\n\n

配置 windows defender 防火墙入站规则

新建规则 -> 端口 -> TCP / 特定本地端口(3000) -> 允许链接 -> 下一步 -> 取个名字 -> Done

\n

docker

windows 宿主机安装 docker desktop设置 -> 资源 -> WSL INTEGRATION 打开 对应的 linux发行版,即使用

\n

mysql 容器

# brdige\ndocker network create --driver bridge --subnet=172.21.0.0/16 wsl2\n# pull\ndocker pull mysql:5.7.38\n# 生产 mysql 5.7.38 容器\ndocker run --restart=always --privileged=true -p 3306:3306 --name mysql --net wsl2 --ip 172.21.0.5 -e MYSQL_ROOT_PASSWORD=root -d mysql:5.7.38
\n\n

mongo 容器

docker pull mongo:5.0\n# 宿主机 /mongo/data\ndocker run --restart=always -d -p 27017:27017 -v /mongo/data:/data --name mongo --net wsl2 --ip 172.21.0.6 mongo:5.0 --auth\n# 初始化\ndocker exec -it mongo /bin/bash\n# 进入 mongo shell\nmongo\n# admin\nuse admin\n# 创建root用户,管理全部数据库的权限,这会可以navicat等gui链接数据库,用户名密码root,验证数据库admin\ndb.createUser({\n   user: \"root\",\n   pwd: \"root\",\n   roles: [ { role: \"userAdminAnyDatabase\", db: \"admin\" } ]\n})\n# 退出\nexit\n# 再次进入\nmongo\n# 使用root登录授权,正确返回 1\ndb.auth(\"root\",\"root\")\n# 创建 testDB 数据库\nuse testDB\n# 创建 test 用户 管理 testDB\ndb.createUser({user:'test',pwd:'test',roles:[{role:'dbOwner',db:'testDB'}]})
\n\n

使用 navicat 登录,验证数据库 admin ,用户名密码 test

\n

默认 wsl root 用户登录

# wsl -l 查看 ubuntu版本,例如 Ubuntu-20.04\nUbuntu2004 config --default-user root
\n"},{"title":"verdaccio 搭建 npm私库","status":"done","_content":"\n# 使用 docker 搭建 verdaccio\n\n创建 & 配置`config.yaml`文件\n\n```yaml\n# Read about the best practices\n# https://verdaccio.org/docs/best\n\n# path to a directory with all packages\nstorage: /verdaccio/storage/data\n# path to a directory with plugins to include\nplugins: /verdaccio/plugins\n\n# 包体积上限,默认10mb\nmax_body_size: 1024mb\n\n\nweb:\n enable: true\n title: Mozzie-NPM\n # gravatar: false\n # login: true\n pkgManagers:\n - npm\n - yarn\n - pnpm\n html_cache: true\n showFooter: false\n\nauth:\n htpasswd:\n file: /verdaccio/storage/htpasswd\n # 关闭注册,手动添加用户,默认Bcrypt算法,随便找个网页生成个密码,使用账号:密码添加到 htpasswd 文件中,例如 test:$2a$10$0xPGVnpcdxcfmFxtWyWDx./TRtm/W/gSzib/jck3w.sF9x.Ur8t8W\n max_users: -1\n\n\ni18n:\n web: zh-CN\n\n# notify: # 配置 Webhook 推送到钉钉,记得修改 access_token 和 atMobiles\n# method: POST\n# headers: [{ \"Content-Type\": \"application/json\" }]\n# endpoint: https://oapi.dingtalk.com/robot/send?access_token=xxxx\n# content: '{\"msgtype\":\"text\", \"at\": {\"atMobiles\": [\"13000000000\"] }, \"text\":{\"content\":\"NPM 发布新包:\\n > 包名称:{{name}} \\n > 版本号:{{#each versions}}{{version}}{{/each}} \\n > 发布者:{{publisher.name}} \"}}'\n\nuplinks:\n npmjs:\n url: https://registry.npmjs.org/\n yarn:\n url: https://registry.yarnpkg.com/\n timeout: 10s\n taobao:\n url: https://registry.npmmirror.com/\n timeout: 10s\n\npackages:\n \"@*/*\":\n # 可访问权限,web界面看不见,不登陆,也无法 install 包\n access: $authenticated # $all\n # 发布权限, $authenticated 表示只有通过验证的人\n publish: $authenticated\n # 可取消发布权限\n unpublish: $authenticated\n # 包不存在时的代理\n proxy: npmjs yarn taobao\n \"**\":\n access: $authenticated # $all\n publish: $authenticated\n unpublish: $authenticated\n proxy: npmjs yarn taobao\n\nmiddlewares:\n audit:\n enabled: true\nlisten: 0.0.0.0:4873\nlog: { type: stdout, format: pretty, level: http }\n```\n\n创建容器,环境变量,`VERDACCIO_PUBLIC_URL`是静态资源的前缀地址,由于nginx挂了`ssl`,如果使用`http`可以不添加\n\n```bash\ndocker run \\\n-p 4873:4873 \\\n--restart=always \\\n--network mozzie.cn-net \\\n--network-alias verdaccio \\\n--env VERDACCIO_PORT=4873 \\\n--env VERDACCIO_PUBLIC_URL=https://npm.mozzie.cn \\\n--ip 172.21.0.196 \\\n--name verdaccio \\\n-v /www/wwwroot/nginx/html/verdaccio/storage:/verdaccio/storage \\\n-v /www/wwwroot/nginx/html/verdaccio/config:/verdaccio/conf \\\n-v /www/wwwroot/nginx/html/verdaccio/plugins:/verdaccio/plugins \\\n-d verdaccio/verdaccio\n```\n\n\n\n配置nginx的反向代理conf,注意所在的docker网络,使用`container_name`\n\n\n\n```conf\nserver {\n # listen 80;\n listen 443 ssl;\n server_name npm.mozzie.cn;\n ssl_certificate /etc/nginx/ssl/npm.mozzie.cn_bundle.pem;\n ssl_certificate_key /etc/nginx/ssl/npm.mozzie.cn.key;\n gzip on;\n\n location / {\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header Host $host;\n proxy_set_header X-NginX-Proxy true;\n proxy_pass http://verdaccio:4873/;\n proxy_redirect off;\n }\n}\n```\n\n运行添加用户,报错,因为 `htpasswd`默认创建在宿主机,也就是上面挂载的`/www/wwwroot/nginx/html/verdaccio/storage`目录中\n\n```bash\nnpm adduser --registry https://npm.mozzie.cn/\n```\n\n配置`htpasswd`、`storage` 文件夹权限\n\n```bash\n# 宿主机中执行\ncd /www/wwwroot/nginx/html/verdaccio/storage\ntouch htpasswd\nsudo chown 10001:65533 htpasswd\nsudo chown -R 10001:65533 /www/wwwroot/nginx/html/verdaccio/storage\n```\n\n# verdaccio 用户管理\n\n由于在 `config.yml` 中关闭了可访问权限\n\n```yaml\nauth:\n htpasswd:\n file: /verdaccio/storage/htpasswd\n # 关闭注册,手动添加用户,默认Bcrypt算法,\n max_users: -1\n\npackages:\n \"@*/*\":\n # 可访问权限,web界面看不见,不登陆,也无法 install 包\n access: $authenticated # $all\n # 发布权限, $authenticated 表示只有通过验证的人\n publish: $authenticated\n # 可取消发布权限\n unpublish: $authenticated\n \"**\":\n access: $authenticated\n publish: $authenticated\n unpublish: $authenticated\n```\n\n默认的 `addUser` 策略是 `Bcrypt` 生成密码,随便找个网页生成个密码,使用`账号:密码`添加到 `htpasswd` 文件中,例如 \n\n```bash\ntest:$2a$10$0xPGVnpcdxcfmFxtWyWDx./TRtm/W/gSzib/jck3w.sF9x.Ur8t8W\n```\n\n因此在实际开发中,管理员手动给用户创建好账号,然后根据用户的包管理工具,进行登录,例如以 `npm` 为例\n\n```bash\nnpm adduser --registry https://npm.mozzie.cn/\n# 输入 Username: mozzie | Password: xxx | Email: (this IS public) himozzie@foxmail.com\n# 提示登陆成功 Logged in as mozzie on https://npm.mozzie.cn/.\n```\n\n在系统的 `cat ~/.npmrc` 中会增加一行,就可以正常的进行以来的安装了\n\n```bash\n//npm.mozzie.cn/:_authToken=\"Do/wrh5QzsnYaNU4x3ZlVA==\"\n```\n\n# 项目 .npmrc Scope区分\n\n需要指定 `.npmrc` 来区别 `Scope` 的安装地址,例如一个包名为 `@mozzie/hook`,对应的私库为 `https://npm.mozzie.cn/`\n\n```bash\nregistry=http://registry.npm.taobao.org/\n@mozzie:registry=https://npm.mozzie.cn\n# npm拉包的校验\n//https://npm.mozzie.cn/:_authToken=xxxxxxxxxxxxx\n```","source":"_posts/front-end/verdaccio.md","raw":"---\ntitle: verdaccio 搭建 npm私库\ncategories:\n - Front-End\nstatus: done\n---\n\n# 使用 docker 搭建 verdaccio\n\n创建 & 配置`config.yaml`文件\n\n```yaml\n# Read about the best practices\n# https://verdaccio.org/docs/best\n\n# path to a directory with all packages\nstorage: /verdaccio/storage/data\n# path to a directory with plugins to include\nplugins: /verdaccio/plugins\n\n# 包体积上限,默认10mb\nmax_body_size: 1024mb\n\n\nweb:\n enable: true\n title: Mozzie-NPM\n # gravatar: false\n # login: true\n pkgManagers:\n - npm\n - yarn\n - pnpm\n html_cache: true\n showFooter: false\n\nauth:\n htpasswd:\n file: /verdaccio/storage/htpasswd\n # 关闭注册,手动添加用户,默认Bcrypt算法,随便找个网页生成个密码,使用账号:密码添加到 htpasswd 文件中,例如 test:$2a$10$0xPGVnpcdxcfmFxtWyWDx./TRtm/W/gSzib/jck3w.sF9x.Ur8t8W\n max_users: -1\n\n\ni18n:\n web: zh-CN\n\n# notify: # 配置 Webhook 推送到钉钉,记得修改 access_token 和 atMobiles\n# method: POST\n# headers: [{ \"Content-Type\": \"application/json\" }]\n# endpoint: https://oapi.dingtalk.com/robot/send?access_token=xxxx\n# content: '{\"msgtype\":\"text\", \"at\": {\"atMobiles\": [\"13000000000\"] }, \"text\":{\"content\":\"NPM 发布新包:\\n > 包名称:{{name}} \\n > 版本号:{{#each versions}}{{version}}{{/each}} \\n > 发布者:{{publisher.name}} \"}}'\n\nuplinks:\n npmjs:\n url: https://registry.npmjs.org/\n yarn:\n url: https://registry.yarnpkg.com/\n timeout: 10s\n taobao:\n url: https://registry.npmmirror.com/\n timeout: 10s\n\npackages:\n \"@*/*\":\n # 可访问权限,web界面看不见,不登陆,也无法 install 包\n access: $authenticated # $all\n # 发布权限, $authenticated 表示只有通过验证的人\n publish: $authenticated\n # 可取消发布权限\n unpublish: $authenticated\n # 包不存在时的代理\n proxy: npmjs yarn taobao\n \"**\":\n access: $authenticated # $all\n publish: $authenticated\n unpublish: $authenticated\n proxy: npmjs yarn taobao\n\nmiddlewares:\n audit:\n enabled: true\nlisten: 0.0.0.0:4873\nlog: { type: stdout, format: pretty, level: http }\n```\n\n创建容器,环境变量,`VERDACCIO_PUBLIC_URL`是静态资源的前缀地址,由于nginx挂了`ssl`,如果使用`http`可以不添加\n\n```bash\ndocker run \\\n-p 4873:4873 \\\n--restart=always \\\n--network mozzie.cn-net \\\n--network-alias verdaccio \\\n--env VERDACCIO_PORT=4873 \\\n--env VERDACCIO_PUBLIC_URL=https://npm.mozzie.cn \\\n--ip 172.21.0.196 \\\n--name verdaccio \\\n-v /www/wwwroot/nginx/html/verdaccio/storage:/verdaccio/storage \\\n-v /www/wwwroot/nginx/html/verdaccio/config:/verdaccio/conf \\\n-v /www/wwwroot/nginx/html/verdaccio/plugins:/verdaccio/plugins \\\n-d verdaccio/verdaccio\n```\n\n\n\n配置nginx的反向代理conf,注意所在的docker网络,使用`container_name`\n\n\n\n```conf\nserver {\n # listen 80;\n listen 443 ssl;\n server_name npm.mozzie.cn;\n ssl_certificate /etc/nginx/ssl/npm.mozzie.cn_bundle.pem;\n ssl_certificate_key /etc/nginx/ssl/npm.mozzie.cn.key;\n gzip on;\n\n location / {\n proxy_set_header X-Real-IP $remote_addr;\n proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n proxy_set_header Host $host;\n proxy_set_header X-NginX-Proxy true;\n proxy_pass http://verdaccio:4873/;\n proxy_redirect off;\n }\n}\n```\n\n运行添加用户,报错,因为 `htpasswd`默认创建在宿主机,也就是上面挂载的`/www/wwwroot/nginx/html/verdaccio/storage`目录中\n\n```bash\nnpm adduser --registry https://npm.mozzie.cn/\n```\n\n配置`htpasswd`、`storage` 文件夹权限\n\n```bash\n# 宿主机中执行\ncd /www/wwwroot/nginx/html/verdaccio/storage\ntouch htpasswd\nsudo chown 10001:65533 htpasswd\nsudo chown -R 10001:65533 /www/wwwroot/nginx/html/verdaccio/storage\n```\n\n# verdaccio 用户管理\n\n由于在 `config.yml` 中关闭了可访问权限\n\n```yaml\nauth:\n htpasswd:\n file: /verdaccio/storage/htpasswd\n # 关闭注册,手动添加用户,默认Bcrypt算法,\n max_users: -1\n\npackages:\n \"@*/*\":\n # 可访问权限,web界面看不见,不登陆,也无法 install 包\n access: $authenticated # $all\n # 发布权限, $authenticated 表示只有通过验证的人\n publish: $authenticated\n # 可取消发布权限\n unpublish: $authenticated\n \"**\":\n access: $authenticated\n publish: $authenticated\n unpublish: $authenticated\n```\n\n默认的 `addUser` 策略是 `Bcrypt` 生成密码,随便找个网页生成个密码,使用`账号:密码`添加到 `htpasswd` 文件中,例如 \n\n```bash\ntest:$2a$10$0xPGVnpcdxcfmFxtWyWDx./TRtm/W/gSzib/jck3w.sF9x.Ur8t8W\n```\n\n因此在实际开发中,管理员手动给用户创建好账号,然后根据用户的包管理工具,进行登录,例如以 `npm` 为例\n\n```bash\nnpm adduser --registry https://npm.mozzie.cn/\n# 输入 Username: mozzie | Password: xxx | Email: (this IS public) himozzie@foxmail.com\n# 提示登陆成功 Logged in as mozzie on https://npm.mozzie.cn/.\n```\n\n在系统的 `cat ~/.npmrc` 中会增加一行,就可以正常的进行以来的安装了\n\n```bash\n//npm.mozzie.cn/:_authToken=\"Do/wrh5QzsnYaNU4x3ZlVA==\"\n```\n\n# 项目 .npmrc Scope区分\n\n需要指定 `.npmrc` 来区别 `Scope` 的安装地址,例如一个包名为 `@mozzie/hook`,对应的私库为 `https://npm.mozzie.cn/`\n\n```bash\nregistry=http://registry.npm.taobao.org/\n@mozzie:registry=https://npm.mozzie.cn\n# npm拉包的校验\n//https://npm.mozzie.cn/:_authToken=xxxxxxxxxxxxx\n```","slug":"front-end/verdaccio","published":1,"date":"2023-10-20T02:25:55.131Z","updated":"2023-11-06T08:02:39.971Z","comments":1,"layout":"post","photos":[],"link":"","_id":"clp7x1951000lv3z35336cspf","content":"

使用 docker 搭建 verdaccio

创建 & 配置config.yaml文件

\n
# Read about the best practices\n# https://verdaccio.org/docs/best\n\n# path to a directory with all packages\nstorage: /verdaccio/storage/data\n# path to a directory with plugins to include\nplugins: /verdaccio/plugins\n\n# 包体积上限,默认10mb\nmax_body_size: 1024mb\n\n\nweb:\n  enable: true\n  title: Mozzie-NPM\n  # gravatar: false\n  # login: true\n  pkgManagers:\n    - npm\n    - yarn\n    - pnpm\n  html_cache: true\n  showFooter: false\n\nauth:\n  htpasswd:\n    file: /verdaccio/storage/htpasswd\n    # 关闭注册,手动添加用户,默认Bcrypt算法,随便找个网页生成个密码,使用账号:密码添加到 htpasswd 文件中,例如 test:$2a$10$0xPGVnpcdxcfmFxtWyWDx./TRtm/W/gSzib/jck3w.sF9x.Ur8t8W\n    max_users: -1\n\n\ni18n:\n  web: zh-CN\n\n# notify: # 配置 Webhook 推送到钉钉,记得修改 access_token 和 atMobiles\n#  method: POST\n#  headers: [{ \"Content-Type\": \"application/json\" }]\n#  endpoint: https://oapi.dingtalk.com/robot/send?access_token=xxxx\n#  content: '{\"msgtype\":\"text\", \"at\": {\"atMobiles\": [\"13000000000\"] }, \"text\":{\"content\":\"NPM 发布新包:\\n > 包名称:{{name}} \\n > 版本号:{{#each versions}}{{version}}{{/each}} \\n > 发布者:{{publisher.name}} \"}}'\n\nuplinks:\n  npmjs:\n    url: https://registry.npmjs.org/\n  yarn:\n    url: https://registry.yarnpkg.com/\n    timeout: 10s\n  taobao:\n    url: https://registry.npmmirror.com/\n    timeout: 10s\n\npackages:\n  \"@*/*\":\n    # 可访问权限,web界面看不见,不登陆,也无法 install 包\n    access: $authenticated # $all\n    # 发布权限, $authenticated 表示只有通过验证的人\n    publish: $authenticated\n    # 可取消发布权限\n    unpublish: $authenticated\n    # 包不存在时的代理\n    proxy: npmjs yarn taobao\n  \"**\":\n    access: $authenticated # $all\n    publish: $authenticated\n    unpublish: $authenticated\n    proxy: npmjs yarn taobao\n\nmiddlewares:\n  audit:\n    enabled: true\nlisten: 0.0.0.0:4873\nlog: { type: stdout, format: pretty, level: http }
\n\n

创建容器,环境变量,VERDACCIO_PUBLIC_URL是静态资源的前缀地址,由于nginx挂了ssl,如果使用http可以不添加

\n
docker run \\\n-p 4873:4873 \\\n--restart=always \\\n--network mozzie.cn-net \\\n--network-alias verdaccio \\\n--env VERDACCIO_PORT=4873 \\\n--env VERDACCIO_PUBLIC_URL=https://npm.mozzie.cn \\\n--ip 172.21.0.196 \\\n--name verdaccio \\\n-v /www/wwwroot/nginx/html/verdaccio/storage:/verdaccio/storage \\\n-v /www/wwwroot/nginx/html/verdaccio/config:/verdaccio/conf \\\n-v /www/wwwroot/nginx/html/verdaccio/plugins:/verdaccio/plugins \\\n-d verdaccio/verdaccio
\n\n\n\n

配置nginx的反向代理conf,注意所在的docker网络,使用container_name

\n
server {\n    # listen 80;\n    listen 443 ssl;\n    server_name npm.mozzie.cn;\n    ssl_certificate  /etc/nginx/ssl/npm.mozzie.cn_bundle.pem;\n    ssl_certificate_key  /etc/nginx/ssl/npm.mozzie.cn.key;\n    gzip on;\n\n    location / {\n      proxy_set_header X-Real-IP $remote_addr;\n      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n      proxy_set_header Host $host;\n      proxy_set_header X-NginX-Proxy true;\n      proxy_pass http://verdaccio:4873/;\n      proxy_redirect off;\n    }\n}
\n\n

运行添加用户,报错,因为 htpasswd默认创建在宿主机,也就是上面挂载的/www/wwwroot/nginx/html/verdaccio/storage目录中

\n
npm adduser --registry https://npm.mozzie.cn/
\n\n

配置htpasswdstorage 文件夹权限

\n
# 宿主机中执行\ncd /www/wwwroot/nginx/html/verdaccio/storage\ntouch htpasswd\nsudo chown 10001:65533 htpasswd\nsudo chown -R 10001:65533 /www/wwwroot/nginx/html/verdaccio/storage
\n\n

verdaccio 用户管理

由于在 config.yml 中关闭了可访问权限

\n
auth:\n  htpasswd:\n    file: /verdaccio/storage/htpasswd\n    # 关闭注册,手动添加用户,默认Bcrypt算法,\n    max_users: -1\n\npackages:\n  \"@*/*\":\n    # 可访问权限,web界面看不见,不登陆,也无法 install 包\n    access: $authenticated # $all\n    # 发布权限, $authenticated 表示只有通过验证的人\n    publish: $authenticated\n    # 可取消发布权限\n    unpublish: $authenticated\n  \"**\":\n    access: $authenticated\n    publish: $authenticated\n    unpublish: $authenticated
\n\n

默认的 addUser 策略是 Bcrypt 生成密码,随便找个网页生成个密码,使用账号:密码添加到 htpasswd 文件中,例如

\n
test:$2a$10$0xPGVnpcdxcfmFxtWyWDx./TRtm/W/gSzib/jck3w.sF9x.Ur8t8W
\n\n

因此在实际开发中,管理员手动给用户创建好账号,然后根据用户的包管理工具,进行登录,例如以 npm 为例

\n
npm adduser --registry https://npm.mozzie.cn/\n# 输入 Username: mozzie | Password: xxx | Email: (this IS public) himozzie@foxmail.com\n# 提示登陆成功 Logged in as mozzie on https://npm.mozzie.cn/.
\n\n

在系统的 cat ~/.npmrc 中会增加一行,就可以正常的进行以来的安装了

\n
//npm.mozzie.cn/:_authToken=\"Do/wrh5QzsnYaNU4x3ZlVA==\"
\n\n

项目 .npmrc Scope区分

需要指定 .npmrc 来区别 Scope 的安装地址,例如一个包名为 @mozzie/hook,对应的私库为 https://npm.mozzie.cn/

\n
registry=http://registry.npm.taobao.org/\n@mozzie:registry=https://npm.mozzie.cn\n# npm拉包的校验\n//https://npm.mozzie.cn/:_authToken=xxxxxxxxxxxxx
","site":{"data":{}},"excerpt":"","more":"

使用 docker 搭建 verdaccio

创建 & 配置config.yaml文件

\n
# Read about the best practices\n# https://verdaccio.org/docs/best\n\n# path to a directory with all packages\nstorage: /verdaccio/storage/data\n# path to a directory with plugins to include\nplugins: /verdaccio/plugins\n\n# 包体积上限,默认10mb\nmax_body_size: 1024mb\n\n\nweb:\n  enable: true\n  title: Mozzie-NPM\n  # gravatar: false\n  # login: true\n  pkgManagers:\n    - npm\n    - yarn\n    - pnpm\n  html_cache: true\n  showFooter: false\n\nauth:\n  htpasswd:\n    file: /verdaccio/storage/htpasswd\n    # 关闭注册,手动添加用户,默认Bcrypt算法,随便找个网页生成个密码,使用账号:密码添加到 htpasswd 文件中,例如 test:$2a$10$0xPGVnpcdxcfmFxtWyWDx./TRtm/W/gSzib/jck3w.sF9x.Ur8t8W\n    max_users: -1\n\n\ni18n:\n  web: zh-CN\n\n# notify: # 配置 Webhook 推送到钉钉,记得修改 access_token 和 atMobiles\n#  method: POST\n#  headers: [{ \"Content-Type\": \"application/json\" }]\n#  endpoint: https://oapi.dingtalk.com/robot/send?access_token=xxxx\n#  content: '{\"msgtype\":\"text\", \"at\": {\"atMobiles\": [\"13000000000\"] }, \"text\":{\"content\":\"NPM 发布新包:\\n > 包名称:{{name}} \\n > 版本号:{{#each versions}}{{version}}{{/each}} \\n > 发布者:{{publisher.name}} \"}}'\n\nuplinks:\n  npmjs:\n    url: https://registry.npmjs.org/\n  yarn:\n    url: https://registry.yarnpkg.com/\n    timeout: 10s\n  taobao:\n    url: https://registry.npmmirror.com/\n    timeout: 10s\n\npackages:\n  \"@*/*\":\n    # 可访问权限,web界面看不见,不登陆,也无法 install 包\n    access: $authenticated # $all\n    # 发布权限, $authenticated 表示只有通过验证的人\n    publish: $authenticated\n    # 可取消发布权限\n    unpublish: $authenticated\n    # 包不存在时的代理\n    proxy: npmjs yarn taobao\n  \"**\":\n    access: $authenticated # $all\n    publish: $authenticated\n    unpublish: $authenticated\n    proxy: npmjs yarn taobao\n\nmiddlewares:\n  audit:\n    enabled: true\nlisten: 0.0.0.0:4873\nlog: { type: stdout, format: pretty, level: http }
\n\n

创建容器,环境变量,VERDACCIO_PUBLIC_URL是静态资源的前缀地址,由于nginx挂了ssl,如果使用http可以不添加

\n
docker run \\\n-p 4873:4873 \\\n--restart=always \\\n--network mozzie.cn-net \\\n--network-alias verdaccio \\\n--env VERDACCIO_PORT=4873 \\\n--env VERDACCIO_PUBLIC_URL=https://npm.mozzie.cn \\\n--ip 172.21.0.196 \\\n--name verdaccio \\\n-v /www/wwwroot/nginx/html/verdaccio/storage:/verdaccio/storage \\\n-v /www/wwwroot/nginx/html/verdaccio/config:/verdaccio/conf \\\n-v /www/wwwroot/nginx/html/verdaccio/plugins:/verdaccio/plugins \\\n-d verdaccio/verdaccio
\n\n\n\n

配置nginx的反向代理conf,注意所在的docker网络,使用container_name

\n
server {\n    # listen 80;\n    listen 443 ssl;\n    server_name npm.mozzie.cn;\n    ssl_certificate  /etc/nginx/ssl/npm.mozzie.cn_bundle.pem;\n    ssl_certificate_key  /etc/nginx/ssl/npm.mozzie.cn.key;\n    gzip on;\n\n    location / {\n      proxy_set_header X-Real-IP $remote_addr;\n      proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n      proxy_set_header Host $host;\n      proxy_set_header X-NginX-Proxy true;\n      proxy_pass http://verdaccio:4873/;\n      proxy_redirect off;\n    }\n}
\n\n

运行添加用户,报错,因为 htpasswd默认创建在宿主机,也就是上面挂载的/www/wwwroot/nginx/html/verdaccio/storage目录中

\n
npm adduser --registry https://npm.mozzie.cn/
\n\n

配置htpasswdstorage 文件夹权限

\n
# 宿主机中执行\ncd /www/wwwroot/nginx/html/verdaccio/storage\ntouch htpasswd\nsudo chown 10001:65533 htpasswd\nsudo chown -R 10001:65533 /www/wwwroot/nginx/html/verdaccio/storage
\n\n

verdaccio 用户管理

由于在 config.yml 中关闭了可访问权限

\n
auth:\n  htpasswd:\n    file: /verdaccio/storage/htpasswd\n    # 关闭注册,手动添加用户,默认Bcrypt算法,\n    max_users: -1\n\npackages:\n  \"@*/*\":\n    # 可访问权限,web界面看不见,不登陆,也无法 install 包\n    access: $authenticated # $all\n    # 发布权限, $authenticated 表示只有通过验证的人\n    publish: $authenticated\n    # 可取消发布权限\n    unpublish: $authenticated\n  \"**\":\n    access: $authenticated\n    publish: $authenticated\n    unpublish: $authenticated
\n\n

默认的 addUser 策略是 Bcrypt 生成密码,随便找个网页生成个密码,使用账号:密码添加到 htpasswd 文件中,例如

\n
test:$2a$10$0xPGVnpcdxcfmFxtWyWDx./TRtm/W/gSzib/jck3w.sF9x.Ur8t8W
\n\n

因此在实际开发中,管理员手动给用户创建好账号,然后根据用户的包管理工具,进行登录,例如以 npm 为例

\n
npm adduser --registry https://npm.mozzie.cn/\n# 输入 Username: mozzie | Password: xxx | Email: (this IS public) himozzie@foxmail.com\n# 提示登陆成功 Logged in as mozzie on https://npm.mozzie.cn/.
\n\n

在系统的 cat ~/.npmrc 中会增加一行,就可以正常的进行以来的安装了

\n
//npm.mozzie.cn/:_authToken=\"Do/wrh5QzsnYaNU4x3ZlVA==\"
\n\n

项目 .npmrc Scope区分

需要指定 .npmrc 来区别 Scope 的安装地址,例如一个包名为 @mozzie/hook,对应的私库为 https://npm.mozzie.cn/

\n
registry=http://registry.npm.taobao.org/\n@mozzie:registry=https://npm.mozzie.cn\n# npm拉包的校验\n//https://npm.mozzie.cn/:_authToken=xxxxxxxxxxxxx
"},{"title":"码场悟道","status":"doing","_content":"\n# 模板引擎\n\n严格的模板引擎的定义,输入模板字符串 + 数据,得到渲染过的字符串。实现上,从正则替换到拼 function 字符串到正经的 AST 解析各种各样,但从定义上来说都是差不多的。字符串渲染的性能其实也就在后端比较有意义,毕竟每一次渲染都是在消耗服务器资源,但在前端,用户只有一个,几十毫秒的渲染时间跟请求延迟比起来根本不算瓶颈。倒是前端的后续更新是字符串模板引擎的软肋,因为用渲染出来的字符串整个替换 innerHTML 是一个效率很低的更新方式。所以这样的模板引擎如今在纯前端情境下已经不再是好的选择,意义更多是在于方便前后端共用模板。\n\n# 古老数据渲染 vm 的方式\n\n这种写法,弊端太多了,玩具车\n\n```html\n\n\n \n \n \n \n Document\n \n \n
    \n \n \n\n```\n\n# mustache 原理\n\n- 1、先把模板字符串编译成 tokens(代号)\n- 2、根据 tokens,结合数据渲染成 dom\n\n> 本质上,tokens 是一个 js 嵌套数组没事模板字符串 js 的表示,他是`抽象语法树`,`虚拟节点`的开山鼻祖\n\n假设有这么一个模板字符串\n\n```html\n

    我买了一个{{thing}},好{{mood}}啊

    \n```\n\n会编译成 tokens,如下:\n\n```js\n// 这里面每一个数组行都是一个 token,组起来就是 tokens\n// html 标签也会被看成纯文本\n[\n [\"text\", \"

    我买了一个\"],\n [\"name\", \"thing\"],\n [\"text\", \"好\"],\n [\"name\", \"mood\"],\n [\"text\", \"啊

    \"],\n];\n```\n\n当模板存在循环式,带层级嵌套,如下:\n\n```html\n
    \n
      \n {{#arr}}\n
    • {{.}}
    • \n {{/arr}}\n
    \n
    \n```\n\n会被编译成\n\n```js\n[\n [\"text\", \"
      \"],\n [\n \"#\",\n \"arr\",\n [\n [\"text\", \"li\"],\n [\"name\", \".\"],\n [\"text\", \"\"],\n ],\n ],\n [\"text\", \"
    \"],\n];\n```\n\n如果是双重循环,带层级嵌套继续加一层,例如:\n\n```html\n
    \n
      \n {{#students}}\n
    1. \n 学生{{item.name}}的爱好是\n
        \n {{#item.hobbies}}\n
      1. {{.}}
      2. \n {{/#item.hobbies}}\n
      \n
    2. \n {{/#students}}\n
    \n
    \n```\n\n会被编译成\n\n```js\n[\n [\"text\", \"
      \"],\n [\n \"#\",\n \"students\",\n null,\n null,\n [[\"text\", \"
    1. 学生\"], [\"name\", \"name\"], [\"text\", \"的爱好是
        \"], [\"#\", \"hobbies\", null, null], [\n ['text','
      1. '],\n ['name','.'],\n ['text','
      2. ']\n ]],\n ['text','
          '],\n ]],\n ['text','
    ']\n ],\n];\n```\n\n> 在`mustache.js`中完成上述这一过程的函数`parseTemplate`,可以去找源代码看\n\n## tokens 生成算法\n\n用简单的模板字符串举例:\n\n```html\n我买了一个{{thing}},好{{mood}}啊\n```\n\n有一个指针往右遍历,从`我`开始,遍历到`啊`结束,如下:\n\n```js\n/**\n *\n * 我买了一个{{thing}},好{{mood}}啊\n * ↑\n *\n * Step1:指针位置 = 1\n *\n * 我买了一个{{thing}},好{{mood}}啊\n * ↑\n *\n * Step2:指针位置 = 2\n *\n * 我买了一个{{thing}},好{{mood}}啊\n * ↑\n *\n * Step3:指针位置 = 4\n *\n * 我买了一个{{thing}},好{{mood}}啊\n * ↑\n *\n * Step4:指针位置 = 11\n *\n * 我买了一个{{thing}},好{{mood}}啊\n * ↑\n *\n * Step5:指针位置 = 15\n *\n * 我买了一个{{thing}},好{{mood}}啊\n * ↑\n *\n * ... while(指针位置 >= 模板字符串.length)\n *\n * /\n```\n\n- Step1:\n\n指针右移 1 个长度,以指针位置切割,字符串被分成`我`+`买了一个{{thing}},好{{mood}}啊`\n\n- Step2:\n\n指针右移 1 个长度,以指针位置切割,字符串被分成`我买`+`了一个{{thing}},好{{mood}}啊`\n\n- Step3:\n\n第一次遇到,通过 `indexOf(\"{{\") == 0` 判断\n\n```js\n// 标记为 text 放到 tokens 中\ntoken.push([\"text\", \"我买了一个\"]); // tokens: [['text', '我买了一个']]\n```\n\n结束,此时指针位置 = 4\n\n- Step4:\n\n指针右移 2 个长度,跳过`{{`,暂存此时`pos_last = 6`\n\n此时,右边字符串(尾字符串)`thing}},好{{mood}}啊`\n\n右移 5 个长度,识别`模板内部数据对象`:\n\n```js\nsubstring(post_last, 6 + 5); // thing 5个长度\n// 标记为 name 放到 tokens 中\ntoken.push([\"name\", \"thing\"]); // tokens: [['text', '我买了一个']], ['name', 'thing']]\n```\n\n结束,此时指针位置 = 11\n\n- Step5:\n\n> 遇到 `}}`,通过 `indexOf(\"}}\") == 0`判断\n\n指针右移 2 个长度,跳过`}}`,暂存此时`post_last = 13`,继续右移 2 个长度\n\n```js\nsubstring(post_last, 13 + 2); // ,好 2个长度\n// 标记为 text 放到 tokens 中\ntoken.push([\"text\", \",好\"]); // tokens: [['text', '我买了一个']], ['name', 'thing'],['text', ',好' ]]\n```\n\n> 第二次,遇到 `{{`\n\n剩下循环执行就行了,这个过程,我们可以称作`扫描 Scan`\n\n## 扫描器 Scanner\n\n新建一个 `Scanner.js`,用来扫描模板字符串,实现上面的原理\n\n```js\n/**\n * 模板字符串扫描器\n */\n\nclass Scanner {\n constructor(templ) {\n this.templ = templ; // 模板字符串\n this.tail = templ; // 尾字符串\n this.pPos = 0; // 指针位置\n }\n\n /**\n * 指针跳过模板标签\n * @param {模板语法包围标签} tag\n */\n jumpTag(tag) {\n if (this.tail.indexOf(tag) === 0) {\n this.pPos += tag.length; // 指针右移 tag.length 个长度\n this.tail = this.templ.substring(this.pPos); // 尾字符串更新\n }\n }\n\n /**\n * 指针遇见模板标签 {{\n * @param {模板语法包围标签} tag\n */\n missTag(tag) {\n let pPos_last = this.pPos;\n while (!this.eof() && this.tail.indexOf(tag) !== 0) {\n this.pPos++;\n this.tail = this.templ.substring(this.pPos);\n }\n return this.templ.substring(pPos_last, this.pPos);\n }\n\n eof() {\n return this.pPos >= this.templ.length;\n }\n}\n```\n\n## 分析器 Parser\n\n调用`Scanner.js`\n\n```js\nlet tmpl = `我买了一个{{thing}},好{{mood}}啊`;\n// 编译 模板字符串 => tokens\nconst Parser = {\n createTokens: (tmpl) => {\n let scanner = new Scanner(tmpl);\n let tokens = [];\n // scanner 循环执行\n while (!scanner.eof()) {\n ctx = scanner.missTag(\"{{\"); // 返回 头字符串\n if (ctx != \"\") {\n tokens.push([\"text\", ctx]);\n }\n scanner.jumpTag(\"{{\"); // 跳过 模板字符\n ctx = scanner.missTag(\"}}\"); // 返回 {{ x }}\n if (ctx != \"\") {\n tokens.push([\"name\", ctx]);\n }\n scanner.jumpTag(\"}}\");\n }\n return tokens;\n },\n};\nconsole.log(Parser.createTokens(tmpl));\n// 输出,非常的 奈一丝\n// [\"text\", \"我买了一个\"]\n// [\"name\", \"thing\"]\n// [\"text\", \",好\"]\n// [\"name\", \"mood\"]\n// [\"text\", \"啊\"]\n```\n\n## 扫描器 Scanner 增强\n\n上面的`Parser`只能识别`{{`和`}}`,如果模板语法复杂一点,比如加入 `{{#list}}...{{/list}}`,需要增强`Parser`\n\n```js\nconst template = `\n 哈哈哈\n {{#students}}\n 我买了一个 {{ thing }},好{{mood}}啊{{a}}\n {{item.name}}\n {{/students}}\n `;\nconst Parser = {\n createTokens: (tmpl) => {\n let scanner = new Scanner(tmpl);\n let tokens = [];\n let ctx = \"\";\n // scanner 循环执行\n while (!scanner.eof()) {\n ctx = scanner.missTag(\"{{\"); // 返回 头字符串\n if (ctx != \"\") {\n tokens.push([\"text\", ctx]);\n }\n scanner.jumpTag(\"{{\"); // 跳过 模板字符\n ctx = scanner.missTag(\"}}\"); // 返回 {{ x }}\n if (ctx != \"\") {\n switch (ctx[0]) {\n case \"#\":\n tokens.push([\"#\", ctx.substr(1)]); // {{# x }}\n break;\n case \"/\":\n tokens.push([\"/\", ctx.substr(1)]);\n break;\n default:\n tokens.push([\"name\", ctx]);\n break;\n }\n }\n scanner.jumpTag(\"}}\");\n }\n return tokens;\n },\n};\nconsole.log(Parser.createTokens(template));\n\n// 输出\n// [\"text\", \"↵ 哈哈哈↵ \"]\n// [\"#\", \"students\"]\n// [\"text\", \"↵ 我买了一个 \"]\n// [\"name\", \" thing \"]\n// [\"text\", \",好\"]\n// [\"name\", \"mood\"]\n// [\"text\", \"啊\"]\n// [\"name\", \"a\"]\n// [\"text\", \"↵ \"]\n// [\"name\", \"item.name\"]\n// [\"text\", \"↵ \"]\n// [\"/\", \"students\"]\n// [\"text\", \"↵ \"]\n```\n\n## 栈队列算法\n\n上一步最后的输出,只有单层嵌套,如果是两层嵌套怎么办?\n\n例如模板语法如下:\n\n```js\nvar template = `\n 哈哈哈\n {{#students}}\n {{#stu}}\n {{stu.name}}买了一个 {{ thing }},好{{mood}}啊{{a}}\n {{/stu}}\n {{item.name}}\n {{/students}}\n `;\n```\n\n经过`Parser`处理得到:\n\n```js\n/**\n *\n * [\"text\", \"↵ 哈哈哈↵ \"]\n * [\"#\", \"students\"]\n * [\"text\", \"↵ \"]\n * [\"#\", \"stu\"]\n * [\"text\", \"↵ \"]\n * [\"name\", \"stu.name\"]\n * [\"text\", \"买了一个 \"]\n * [\"name\", \" thing \"]\n * [\"text\", \",好\"]\n * [\"name\", \"mood\"]\n * [\"text\", \"啊\"]\n * [\"name\", \"a\"]\n * [\"text\", \"↵ \"]\n * [\"/\", \"stu\"]\n * [\"text\", \"↵ \"]\n * [\"name\", \"item.name\"]\n * [\"text\", \"↵ \"]\n * [\"/\", \"students\"]\n * [\"text\", \"↵ \"]\n *\n * /\n```\n\n此时`students`和`stu`都是`#`标记,我们需要利用算法处理他们的嵌套结构,处理成大约如下这样的结构:\n\n```js\n/**\n *\n * [\"text\", \"↵ 哈哈哈↵ \"]\n * Array(3)\n * \"#\"\n * \"students\"\n * Array(5)\n * [\"text\", \"↵ \"]\n * [\"#\", \"stu\", Array(9)]\n * [\"text\", \"↵ \"]\n * [\"name\", \"item.name\"]\n * [\"text\", \"↵ \"]\n * [\"text\", \"↵ \"]\n *\n * /\n```\n\n# 常用工具类\n\n## 递归\n\n```js\n/**\n * {string} dir 递归根目录\n * {object} list 暂存参数\n */\nconst deep = async (dir, list = []) => {\n const dirs = await fs.promises.readdir(dir)\n for (let i = 0; i < dirs.length; i++) {\n const item = dirs[i]\n const itemPath = path.join(dir, item)\n const isDir = fs.statSync(itemPath).isDirectory()\n isDir ? await deep(itemPath, list) : list.push(itemPath)\n }\n return list\n}\n```\n\n## 自增id短码\n\n用于连接分享\n\n```typescript\nconst createAscString = (id) => {\n const dictionary = [\n \"0123456789\",\n \"abcdefghigklmnopqrstuvwxyz\",\n \"ABCDEFGHIGKLMNOPQRSTUVWXYZ\",\n ];\n let chars = dictionary.join(\"\").split(\"\"),\n radix = chars.length,\n qutient = 1000 * 1000 * 9999 + +id,\n arr = [];\n while (qutient) {\n mod = qutient % radix;\n qutient = (qutient - mod) / radix;\n arr.unshift(chars[mod]);\n }\n return arr.join(\"\");\n};\n\nconsole.log(createAscString(100000000));\n```\n\n## 手动实现 eventBus\n\n```js\nexport default class EventBus {\n constructor() {\n // key-value : eventName-date\n this.callbacks = {};\n }\n\n /**\n * 监听事件\n * @param {事件名} eventName\n * @param {回调函数} callback\n */\n on(eventName, callback) {\n this.checkType(eventName).callbacks[eventName]\n ? callback(this.callbacks[eventName])\n : this.error(`The event has not been declared`);\n }\n\n /**\n * 注册一个事件\n * @param {事件名} eventName\n * @param {传递的对象} data\n */\n emit(eventName, data) {\n this.checkType(eventName).callbacks[eventName] = data;\n }\n\n /**\n * 注销事件,不传参数默认注销全部事件\n * @param {事件名} eventName\n */\n off(eventName) {\n eventName\n ? this.checkType(eventName).removeEvent(eventName)\n : this.emptyEvent();\n }\n\n /**\n * 移出事件\n * @param {事件名} eventName\n */\n removeEvent(eventName) {\n Reflect.deleteProperty(this.callbacks, eventName);\n }\n\n /**\n * 清空全部事件\n */\n emptyEvent() {\n this.callbacks = [];\n }\n\n /**\n * 参数类型校验\n * @param {参数} param\n * @param {合法的类型} validType\n */\n checkType(param, validType = \"string\") {\n if (typeof param !== validType)\n this.error(`(param, ${param}) should be of ${validType} type`);\n return this; // 缅怀jQ链式调用\n }\n\n /**\n * 错误提示\n * @param {提示文字} text\n */\n error(text) {\n throw new Error(text);\n }\n}\n```\n\n调用\n\n```js\n// 省略import\nconst eventBus = new EventBus();\neventBus.emit(\"login\", [{ a: 1, d: 2 }]);\neventBus.on(123, (d) => console.log(d)); // [{...}]\n```\n\n## 判断对象是否有某个 key\n\n```javascript\nlet obj = { alias: \"es6\" };\n\"alias\" in obj; // true\nReflect.has(obj, \"alias\"); // true\n```\n\n## 浏览器\n\n## 版本信息\n\n```javascript\nwindow.navigator.userAgent;\n```\n\n## 兼容事件绑定\n\n```javascript\n/*\n兼容低版本IE,ele为需要绑定事件的元素,\neventName为事件名(保持addEventListener语法,去掉on),fun为事件响应函数\n*/\n\nfunction addEvent(ele, eventName, fun) {\n ele.addEventListener\n ? ele.addEventListener(eventName, fun, false)\n : ele.attachEvent(\"on\" + eventNme, fun);\n}\n```\n\n## 数组对象\n\n## reduce\n\n```javascript\nvar arr = [1, 2, 3, 4];\nvar sum = arr.reduce(function(prev, cur, index, arr) {\n console.log(prev, cur, index);\n return prev + cur;\n},0) //注意这里设置了初始值\nconsole.log(arr, sum);\n\n// 求和\nconst sum = arr.reduce((p,c) => p+c)\n```\n\n## 对象内部根据 key 对 value 进行排序,取前 3\n\n```javascript\nlet datasource = [\n { price: 1, alias: \"watermelon\" },\n { price: 3, alias: \"orange\" },\n { price: 2, alias: \"banana\" },\n { price: 4, alias: \"apple\" },\n];\n// 降序排列\nlet compare = (key) => (a, b) => b[key] - a[key];\nlet sorted = datasource\n .sort(compare(\"price\"))\n .slice(0, 3)\n .map((i) => i[\"alias\"]);\n// 返回 [\"apple\", \"orange\", \"banana\"]\n```\n\n## 随机字符串\n\n```javascript\nconst getRandomRangeNum = (len = 32) => {\n // 略去不宜辨识字符\n let dictionary = \"ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz\";\n let maxPos = dictionary.length;\n let res = \"\";\n for (let i = 0; i < len; i++) {\n res += dictionary.charAt(Math.floor(Math.random() * maxPos));\n }\n return res;\n};\n```\n\n## 类型检测\n\nQ:使用`typeof foo === \"object\"`检测`foo`是否为对象有什么缺点?如何避免?\n\nA:用 `typeof` 是否能准确判断一个对象变量,答案是否定的,`null` 的结果也是 `object`,`Array` 的结果也是 `object`,有时候我们需要的是 \"纯粹\" 的 `object` 对象\n\n```js\nObject.prototype.toString.call(obj) === \"[object Object]\";\n```\n\n## 倒计时\n\n```javascript\nclass Countdown {\n constructor(startNum, endNum, interval) {\n [this.startNum, this.endNum, this.interval] = [startNum, endNum, interval];\n }\n execute() {\n var timer = setTimeout(() => {\n if (this.startNum >= this.endNum) {\n console.log(this.startNum);\n this.startNum -= 1;\n this.execute();\n } else {\n clearTimeout(timer);\n }\n }, this.interval);\n }\n}\n// 实例化调用\nvar countdown = new Countdown(5, 0, 1000).execute();\n```\n\n## 范围随机数\n\n```javascript\n// 能取到 min,取不到 max\nfunction getRandomRangeNum(min, max) {\n return min + Math.floor(Math.random() * (max - min));\n}\n```\n\n## 获取当前月的天数\n\n```javascript\nconst getCurMonthDays = new Date(\n new Date().getFullYear(),\n new Date().getMonth() + 1,\n 0\n).getDate();\n```\n\n\n# 对象小操作\n\n## 去虚假值\n\n```js\nlet arr4 = [\"小明\", \"小蓝\", \"\", false, \" \", undefined, null, 0, NaN, true];\nconsole.log(arr4.filter(Boolean)); // => ['小明', '小蓝', ' ', true]\n```\n\n## 头尾插入\n\n效率比 `unshift()` 高\n\n```js\nlet arr = [1, 2, 3];\n// 头插入\n[\"haha\"].concat(arr);\n// 尾插入\narr.concat([\"haha\"]);\n```\n\n\n## 删除属性\n\n```js\nfunction deleteA(obj) {\n delete obj.A;\n return obj;\n}\n\n// 使用解构赋值\nconst deleteA = ({ A, ...rest } = {}) => rest;\n```\n\n# 生产、加工、消费分离\n\n- 从接口拿数据到视图 fetch api\n- 加工 computed\n- 消费 v-for\n\n# 元数据\n\n```js\nimport \"reflect-metadata\"; // npm install reflect-metadata\n\nfunction Role(name: string): ClassDecorator {\n return (target) => {\n Reflect.defineMetadata(\"role\", name, target);\n };\n}\n\n@Role(\"admin\")\nclass Post {}\n\nconst metadata = Reflect.getMetadata(\"role\", Post);\n\nReflect.set(Post, \"role2\", metadata);\n\nconsole.log(Reflect.get(Post, \"role2\")); // admin\n```\n\n# 防抖与节流\n\n在页面上监听诸如`scroll`(页面滚动),`mousemove`(鼠标移动) ,`keydown`, `keyup`, `keypress`(按下键盘)等等一系列事件的时候,我们并不希望频繁的触发这类监听,尤其当请求非常消耗资源时,这种操作会导致服务器性能急剧下降。\n\n## Debounce\n\n把触发非常频繁的事件合并成一次延迟执行,如果对监听函数使用 100ms 的容忍时间,那么时间在第 3.1s 的时候执行\n\n```javascript\n// 默认延时100ms\nfunction debounce(func, dealy = 100) {\n let timer;\n return function () {\n // 暂存this和参数\n let _this = this;\n let args = arguments;\n // 清除定时器,确保不执行func\n clearTimeout(timer);\n timer = setTimeout(function () {\n func.apply(_this, args);\n }, dealy);\n };\n}\n// 执行函数\nfunction handler() {\n console.log(`delay 100ms ,then handle`);\n}\n// dom添加监听\ndocument\n .querySelector(\"#someNode\")\n .addEventListener(\"scroll\", debounce(handler));\n```\n\n## Throttle\n\n固定函数执行的速率,即所谓的“节流”。设置一个阀值,在阀值内,把触发的事件合并成一次执行;当到达阀值,必定执行一次事件。\n\n```javascript\nfunction throttle(func, delay) {\n let statTime = 0;\n return function () {\n let currentTime = +new Date();\n if (currentTime - statTime > delay) {\n func.apply(this, arguments);\n statTime = currentTime;\n }\n };\n}\n// 执行函数\nfunction resizeHandler() {\n console.log(`resize`);\n}\n// window添加监听\nwindow.onresize = throttle(resizeHandler, 300);\n```\n\n# this 指向\n\n## 全局环境\n\n全局环境下,this 始终指向全局对象(window),无论是否严格模式\n\n```javascript\nconsole.log(this === window); // true\nthis.a = 37;\nconsole.log(window.a); // 37\n```\n\n## 函数上下文调用\n\n- 非严格模式\n\n没有被上一级的对象所调用, this 默认指向全局对象 window\n\n```javascript\nfunction f1() {\n return this;\n}\nf1() === window; // true\n```\n\n- 严格模式\n\nthis 指向 undefined\n\n```javascript\nfunction f2() {\n \"use strict\"; // 这里是严格模式\n return this;\n}\nf2() === undefined; // true\n```\n\n## 箭头函数\n\n> 箭头函数中,call()、apply()、bind()方法无效\n\n在全局代码中,箭头函数被设置为全局对象,总之箭头函数不改变 this 指向\n\n```javascript\nvar globalObject = this;\nvar foo = () => this;\nconsole.log(foo() === globalObject); // true\n```\n\n箭头函数作为对象的方法使用,指向全局 window 对象\n\n```javascript\nvar obj = {\n i: 10,\n b: () => console.log(this.i, this),\n c: function () {\n console.log(this.i, this);\n },\n};\nobj.b(); // undefined window{...}\nobj.c(); // 10 Object {...}\n```\n\n箭头函数可以让 this 指向固化,这种特性很有利于封装回调函数\n\n```javascript\n// 总是指向 handler 对象。如果不使用箭头函数则指向全局 document 对象\nvar handler = {\n id: \"123456\",\n\n init: function () {\n document.addEventListener(\n \"click\",\n (event) => this.doSomething(event.type),\n false\n );\n },\n\n doSomething: function (type) {\n console.log(\"Handling \" + type + \" for \" + this.id);\n },\n};\n```\n\n# call, apply, bind 与 es6\n\njs 的函数继承于`Function.prototype`对象,因此每个函数都会有 apply、call、bind 方法\n\n> call 和 apply 的作用,完全一样,唯一的区别就是在参数上面。\n\n`call, apply, bind`改变函数中 `this 指向` 的三兄弟,把`this`绑定到第一个参数对象上\n\n```javascript\nfunction displayHobbies(...hobbies) {\n console.log(`${this.name} likes ${hobbies.join(\", \")}.`);\n}\n// 下面两个等价\ndisplayHobbies.call({ name: \"Bob\" }, \"swimming\", \"basketball\", \"anime\"); // Bob likes swimming, basketball, anime.\ndisplayHobbies.apply({ name: \"Bob\" }, [\"swimming\", \"basketball\", \"anime\"]); // Bob likes swimming, basketball, anime.\n```\n\n`bind`返回的是一个函数,需要手动执行\n\n```js\nvar p1 = {\n name: \"张三\",\n age: 12,\n func: function () {\n console.log(`姓名:${this.name},年龄:${this.age}`);\n },\n};\n\nvar p2 = {\n name: \"李四\",\n age: 15,\n};\n\np1.func.bind(p2)(); //姓名:李四,年龄:15\n```\n\n# for 循环优化\n\n```javascript\n// 每次都要计算array.length\nfor (let i = 0; i < array.length; i++) {\n console.log(i);\n}\n\n// 使用leng缓存array长度\nfor (let i = 0, length = array.length; i < length; i++) {\n console.log(i);\n}\n```\n\n# 数组\n\n## 扁平化去重升序排列\n\n```javascript\nlet arr = [[1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10];\narr.flat(Infinity); // [1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9, 11, 12, 12, 13, 14, 10]\n\nlet result = Array.from(new Set(arr.flat(Infinity)).sort((a, b) => a - b));\n```\n\n# 前端页面埋点 - 1x1.gif\n\n通常用在,统计页面点击,曝光,停留时间,签发……等场景\n\n- 比 PNG/JPG 体积小\n- 天然跨域\n\n```html\n\n\n```\n\n# 事件委托\n\n利用冒泡原理,委托父元素执行\n\n```html\n
      \n
    • 苹果
    • \n
    • 香蕉
    • \n
    • 凤梨
    • \n
    \n```\n\n```javascript\ndocument.querySelector(\"ul\").onclick = (event) => {\n let target = event.target;\n if (target.nodeName === \"LI\") {\n console.log(target.innerHTML);\n }\n};\n```\n\n# 构造函数 + 原型模式\n\n```javascript\nfunction Person(name, age, job) {\n this.name = name;\n this.age = age;\n this.job = job;\n}\nPerson.prototype.say = function (text) {\n console.log(`${this.name}say:${text}`);\n};\n```\n\n# 剩余参数...args\n\n剩余参数`args`数个数组,`...`解构符\n\n```javascript\nfunction fun1(param, ...args) {\n alert(args.length);\n}\n```\n\n# 跨页面通信\n\n- cookie\n- web worker\n- localstorage\n\n# iframe 跨域通信和不跨域通信\n\n## 不跨域\n\n```javascript\n// fatherSay是父页面全局方法\nwindow.parent.fatherSay();\n// 父页面Dom\nwindow.parent.document.getElementById(\"元素id\");\n// 副业页面获取frameID为`iframe_ID`的子页面的Dom\nwindow.frames[\"iframe_ID\"].document.getElementById(\"元素id\");\n```\n\n## 跨域 postMessage\n\n子页面\n\n```javascript\nwindow.parent.postMessage(\"hello\", \"http://127.0.0.1:8089\");\n```\n\n父页面接受\n\n```javascript\nwindow.addEventListener(\"message\", function (event) {\n alert(123);\n});\n```\n\n# 对象类型判断\n\n## 数组\n\n```javascript\nlet arr = [];\narr instanceof Array; // true\nArray.isArray(arr); // true\nObject.prototype.toString.call(arr); // \"[object Array]\"\n```\n\n# js 单线程,如何异步\n\n- 主线程 执行 js 中所有的代码。\n\n- 主线程 在执行过程中发现了需要异步的任务任务后扔给浏览器(浏览器创建多个线程执行,顺便创造一个`回调队列`。\n\n- 主线程 已经执行完毕所有同步代码。监听`回调队列`一旦 浏览器 中某个线程任务完成将会改变回调函数的状态。主线程查看到某个函数的状态为已完成,就会执行该`回调队列`中对应的回调函数。\n\n# 移动端最小触控区域\n\n苹果推荐是 44pt x 44pt 「具体看 WWDC 14」,通过`padding`、`margin`、`height`等方式进行点击区域扩展\n\n# js 精度问题\n\n常用类库:`Math.js`、`Big.js`、`decimal.js`\n\n# 冻结 Object. freeze()\n\n`const`生命的简单变量不可修改,但是复杂对象可以被修改,`Object. freeze()`:可以冻结对象\n\n- 不能添加新属性\n- 不能删除已有属性\n- 不能修改已有属性的可枚举性、可配置性、可写性\n- 不能修改已有属性的值\n- 不能修改原型\n\n浅冻结\n\n```javascript\nconst obj1 = {\n internal: {},\n};\n\nObject.freeze(obj1);\nobj1.internal.a = \"aValue\";\nconsole.log(obj1.internal.a); // aValue\n```\n\n递归冻结\n\n```javascript\nfunction deepFreeze(obj) {\n // 获取定义在obj上的属性名\n var propNames = Object.getOwnPropertyNames(obj);\n // 在冻结自身之前冻结属性\n propNames.forEach(function (name) {\n var prop = obj[name];\n // 如果prop是个对象,冻结它\n if (typeof prop == \"object\" && prop !== null) deepFreeze(prop);\n });\n return Object.freeze(obj);\n}\n```\n\n# Reflect\n\n## Reflect.get(target, propertyKey, value[receiver])\n\n获取对象身上某个属性的值,类似于 target[name]。\n\n## Reflect.set(target, propertyKey, value[receiver])\n\n将值分配给属性的函数。返回一个 Boolean,如果更新成功,则返回 true。\n\n## Reflect.has(target, propertyKey)\n\n判断一个对象是否存在某个属性,和 in 运算符 的功能完全相同。\n\n\n# if else 优化\n\n## 表驱动编程\n\n空间换时间,设置`obj={key:value}`,通过`obj[key]`取值\n\n```javascript\ncalculateGrade(score){\n const table = {\n 100: 'A',\n 90: 'A',\n 80: 'B',\n 70: 'C',\n 60: 'D',\n others: 'E'\n }\n return table[Math.floor(score/10)*10] || table['others']\n}\n```\n\n## 短路运算\n\nreact 没有`v-if`,运用比较频繁\n\n```js\n// 函数组件\nconst Home = () => {\n return
    home
    ;\n};\n{\n true && ;\n}\n```\n\n# 使用有意义且易读的变量名\n\n```js\n👎 const yyyymmdstr = moment().format(\"YYYY/MM/DD\");\n\n👍 const currentDate = moment().format(\"YYYY/MM/DD\");\n```\n\n# 使用有意义的变量代替数组下标\n\n```js\n👎\nconst address = \"One Infinite Loop, Cupertino 95014\";\nconst cityZipCodeRegex = /^[^,\\\\]+[,\\\\\\s]+(.+?)\\s*(\\d{5})?$/;\nsaveCityZipCode(\n address.match(cityZipCodeRegex)[1],\n address.match(cityZipCodeRegex)[2]\n);\n\n👍\nconst address = \"One Infinite Loop, Cupertino 95014\";\nconst cityZipCodeRegex = /^[^,\\\\]+[,\\\\\\s]+(.+?)\\s*(\\d{5})?$/;\nconst [_, city, zipCode] = address.match(cityZipCodeRegex) || [];\nsaveCityZipCode(city, zipCode);\n```\n\n# 变量名要简洁\n\n```js\n👎\nconst Car = {\n carMake: \"Honda\",\n carModel: \"Accord\",\n carColor: \"Blue\"\n};\nfunction paintCar(car, color) {\n car.carColor = color;\n}\n\n👍\nconst Car = {\n make: \"Honda\",\n model: \"Accord\",\n color: \"Blue\"\n};\nfunction paintCar(car, color) {\n car.color = color;\n}\n```\n\n# 消除魔术字符串\n\n```js\n👎 setTimeout(blastOff, 86400000);\n\n👍 const MILLISECONDS_PER_DAY = 60 * 60 * 24 * 1000; //86400000;\nsetTimeout(blastOff, MILLISECONDS_PER_DAY);\n```\n\n# 使用默认参数替代短路运算符\n\n```js\n👎\nfunction createMicrobrewery(name) {\n const breweryName = name || \"Hipster Brew Co.\";\n // ...\n}\n\n👍\nfunction createMicrobrewery(name = \"Hipster Brew Co.\") {\n // ...\n}\n```\n\n# 一个函数\n\n```js\n👎\nfunction emailClients(clients) {\n clients.forEach(client => {\n const clientRecord = database.lookup(client);\n if (clientRecord.isActive()) {\n email(client);\n }\n });\n}\n\n👍\nfunction emailActiveClients(clients) {\n clients.filter(isActiveClient).forEach(email);\n}\nfunction isActiveClient(client) {\n const clientRecord = database.lookup(client);\n return clientRecord.isActive();\n}\n\n---------------------分割线-----------------------\n\n👎\nfunction createFile(name, temp) {\n if (temp) {\n fs.create(`./temp/${name}`);\n } else {\n fs.create(name);\n }\n}\n\n👍\nfunction createFile(name) {\n fs.create(name);\n}\nfunction createTempFile(name) {\n createFile(`./temp/${name}`);\n}\n```\n\n# 函数参数不多于 2 个,如果有很多参数就利用 object 传递,并使用解构\n\n```js\n👎\nfunction createMenu(title, body, buttonText, cancellable) {\n // ...\n}\ncreateMenu(\"Foo\", \"Bar\", \"Baz\", true);\n\n👍\nfunction createMenu({ title, body, buttonText, cancellable }) {\n // ...\n}\ncreateMenu({\n title: \"Foo\",\n body: \"Bar\",\n buttonText: \"Baz\",\n cancellable: true\n});\n```\n\n# 函数名应该直接反映函数的作用\n\n```js\n👎\nfunction addToDate(date, month) {\n // ...\n}\nconst date = new Date();\n// It's hard to tell from the function name what is added\naddToDate(date, 1);\n\n👍\nfunction addMonthToDate(month, date) {\n // ...\n}\nconst date = new Date();\naddMonthToDate(1, date);\n```\n\n# 尽量使用纯函数\n\n```js\n👎\nconst programmerOutput = [\n {\n name: \"Uncle Bobby\",\n linesOfCode: 500\n },\n {\n name: \"Suzie Q\",\n linesOfCode: 1500\n },\n {\n name: \"Jimmy Gosling\",\n linesOfCode: 150\n },\n {\n name: \"Gracie Hopper\",\n linesOfCode: 1000\n }\n];\nlet totalOutput = 0;\nfor (let i = 0; i < programmerOutput.length; i++) {\n totalOutput += programmerOutput[i].linesOfCode;\n}\n\n👍\nconst programmerOutput = [\n {\n name: \"Uncle Bobby\",\n linesOfCode: 500\n },\n {\n name: \"Suzie Q\",\n linesOfCode: 1500\n },\n {\n name: \"Jimmy Gosling\",\n linesOfCode: 150\n },\n {\n name: \"Gracie Hopper\",\n linesOfCode: 1000\n }\n];\nconst totalOutput = programmerOutput.reduce(\n (totalLines, output) => totalLines + output.linesOfCode,\n 0\n);\n```\n\n# 不要过度优化\n\n```js\n👎\n// 现代浏览器对于迭代器做了内部优化\nfor (let i = 0, len = list.length; i < len; i++) {\n // ...\n}\n\n👍\nfor (let i = 0; i < list.length; i++) {\n // ...\n}\n```\n\n\n# 关于模块化\n\n## 无模块化\n\n> 污染全局作用域、维护成本高、依赖关系不明显\n\nscript 标签引入 js 文件,相互罗列,但是被依赖的放在前面,否则使用就会报错。如下:\n\n```js\n\n\n\n\n\n```\n\n## CommonJS 规范(同步)\n\n该规范最初是用在服务器端的 node 的,它有四个重要的环境变量为模块化的实现提供支持:module、exports、require、global。实际使用时,用 module.exports 定义当前模块对外输出的接口(不推荐直接用 exports),用 require 加载模块(同步)\n\n> module.exports 本身就是一个对象\n\n```js\nmodule.exports = { foo: \"bar\" }; //true\nmodule.exports.foo = \"bar\"; //true。\n```\n\nCommonJS 用同步的方式加载模块。在服务端,模块文件都存在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,限于网络原因,CommonJS 不适合浏览器端模块加载,更合理的方案是使用异步加载,比如下边 AMD 规范。\n\n## AMD 规范(RequireJS)\n\n承接上文,AMD 规范则是非同步加载模块,允许指定回调函数,AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。\n\n- `require([module], callback)`:加载模块\n- `define(id, [depends], callback)`:定义模块\n- `require.config()`:配置路径、依赖关系\n\n## CMD 规范(SeaJS)\n\nCMD 是 在推广过程中对模块定义的规范化产出\n\n## ES6 import/export\n\n通过 babel 将不被支持的 import 编译为当前受到广泛支持的 AMD 规范\n\n# AMD模块化实现\n\n## define 函数\n\n用来声明模块名`module`,依赖数组`deps`,以及模块的作用`callback`\n\n```js\nvar module = (function () {\n var stack = {}; //模块存储栈,存的是模块执行后的结果\n\n function define(module, deps, callback) {\n stack[module] = callback.apply(null, deps); // 压栈\n console.log(stack); // 查看栈存储的模块\n }\n\n return { define: define };\n})();\n```\n\n调用`define`试试,发现 `calc`模块被压入了`stack 模块栈中`\n\n```js\nmodule.define(\"calc\", [], function () {\n return {\n first: function (arr) {\n return arr[0];\n },\n };\n});\n// {calc: { first: ƒ }}\n```\n\n## deps 依赖(导入)\n\n对上面的 `define`函数稍加改造,这一步过程的本质,就是对 deps\n\n```js\nvar module = (function (window) {\n var stack = {}; //模块存储栈,\n\n function define(module, deps, callback) {\n // 把 define 函数依赖数组中的模块从 stack 模块中拿出\n deps.map(function (mod, index) {\n deps[index] = stack[mod]; // 赋值给当前模块的 deps\n });\n stack[module] = callback.apply(null, deps);\n console.log(stack); // 查看栈存储的模块\n }\n\n return { define: define };\n})(window);\n\nmodule.define(\"calc\", [], function () {\n return {\n first: function (arr) {\n return arr[0];\n },\n };\n});\n\nmodule.define(\"number\", [\"calc\"], function (calc) {\n return {\n res: calc.first([4, 3, 2, 1]), //4\n };\n});\n\n// 此时 stack 打印的结果为\n//> calc: {first: ƒ}\n//> number: {res: 4}\n```\n\n> 从本质上看,define 函数的第二个参数 deps 数组,相当于 import 导入,并且如果第三个参数 callback 采用 `return { }`,也就相当于 export 导出\n\n## 老生常谈的指针\n\n说到底,`stack`中 `a模块` export 是一个指针,`{a:value}`(内存地址),所以,`b 模块`会改变`a.a`的值,这点和 `cmd`不同\n\n```js\nmodule.define(\"a\", [], function () {\n return {\n a: 1,\n };\n});\nmodule.define(\"b\", [\"a\"], function (a) {\n a.a = 2;\n});\nmodule.define(\"c\", [\"a\"], function (a) {\n console.log(a.a); // 2\n});\n```\n\n\n# 缘起 Object.defineProperty()\n\n给`目标对象`上定义一个新属性,或者修改`目标对象`属性,并且返回新对象\n\n# 上帝的钥匙 get & set\n\n属性的`getter`函数,如果没有 `getter`则尾 `undefined`。当访问该属性时,会调用此函数.\n\n```js\nlet obj = {};\n\nObject.defineProperty(obj, \"a\", {\n get() {\n return 7;\n },\n set(val) {\n console.log(`FAILED!改变 a 属性,新值为:${val},但是被重写 set 劫持了`);\n },\n});\n\nconsole.log(obj.a);\nobj.a = 4;\n```\n\n> 上帝的钥匙被找到了\n\n劫持!劫持!还是 TMD 劫持!\n\n```js\nlet obj = {};\nlet tempValue = 0;\n\nObject.defineProperty(obj, \"a\", {\n get() {\n return tempValue;\n },\n set(newValue) {\n tempValue = newValue;\n },\n});\n\nconsole.log(obj.a); // 0\nobj.a = 4;\nconsole.log(obj.a); // 4\n```\n\n# 封装 defineReactive(obj, prop, val)\n\n> 这里 defineReactive 第三个参数 val 替代了上一步中全局变量`tempValue`,对于 get()、set()来说,访问到了其他函数内部的变量,所以形成了闭包\n\n```js\nfunction defineReactive(obj, prop, val) {\n Object.defineProperty(obj, prop, {\n get() {\n console.log(`劫持,你访问了${prop}属性`);\n return val;\n },\n set(newValue) {\n if (val === newValue) return;\n console.log(`劫持,你改变了${prop}属性`);\n val = newValue;\n },\n });\n}\n\nlet obj = {};\ndefineReactive(obj, \"a\", 4);\n\nconsole.log(obj.a); // 劫持,你访问了a属性 4\nobj.a = 7; // 劫持,你改变了a属性\nconsole.log(obj.a); // 劫持,你访问了a属性 7\n```\n\n# 递归侦测\n\n```js\nlet obj = { a: { b: { c: {} } } };\ndefineReactive(obj, \"a\", 4);\n```\n\n> 如何自动让`obj`对象的全部属性都`reactive`呢?\n\n```js\nlet obj = {\n a: {\n b: {\n c: 5,\n },\n },\n d: 4,\n};\n```\n\n定义个方法`observe`,递归 `obj` 的每一层的每个 `prop`,检测是否有`__ob__`,如果没有,`defineReactive`,并且挂一个`Observer`实例在这个`props`上,例如:\n\n`Observer对象`\n\n```js\nclass Observer {\n constructor(value) {\n def(value, \"__ob__\", this, false); // 不可枚举,不能给__ob__添加__ob__\n this.walk(value);\n }\n // 遍历每一个 prop的 value\n walk(value) {\n for (let prop in value) {\n defineReactive(value, prop);\n }\n }\n}\n```\n\n`defineReactive.js`\n\n```js\nexport default function defineReactive(obj, prop, val) {\n if (arguments.length === 2) val = obj[prop]; // 如果2个参数\n let childNode = observe(val);\n Object.defineProperty(obj, prop, {\n enumerable: true,\n configurable: true,\n get() {\n console.log(`你访问了${prop}属性`);\n return val;\n },\n set(newValue) {\n if (val === newValue) return;\n console.log(`你改变了${prop}属性`);\n val = newValue;\n childNode = observe(newValue);\n },\n });\n}\n```\n\n`observe.js`\n\n```js\n/**\n * 检测 obj 身上有没有 __ob__(Observer 实例)\n * @param {*} value\n * @returns\n */\nexport default function observe(value) {\n if (typeof value != \"object\") return;\n let ob;\n //! 用__ob__是为了属性不重名,被覆盖\n if (typeof value.__ob__ != \"undefined\") {\n ob = value.__ob__;\n } else {\n ob = new Observer(value);\n }\n return ob;\n}\n```\n","source":"_posts/front-end/码场悟道.md","raw":"---\ntitle: 码场悟道\ncategories:\n - Front-End\nstatus: doing\n---\n\n# 模板引擎\n\n严格的模板引擎的定义,输入模板字符串 + 数据,得到渲染过的字符串。实现上,从正则替换到拼 function 字符串到正经的 AST 解析各种各样,但从定义上来说都是差不多的。字符串渲染的性能其实也就在后端比较有意义,毕竟每一次渲染都是在消耗服务器资源,但在前端,用户只有一个,几十毫秒的渲染时间跟请求延迟比起来根本不算瓶颈。倒是前端的后续更新是字符串模板引擎的软肋,因为用渲染出来的字符串整个替换 innerHTML 是一个效率很低的更新方式。所以这样的模板引擎如今在纯前端情境下已经不再是好的选择,意义更多是在于方便前后端共用模板。\n\n# 古老数据渲染 vm 的方式\n\n这种写法,弊端太多了,玩具车\n\n```html\n\n\n \n \n \n \n Document\n \n \n
      \n \n \n\n```\n\n# mustache 原理\n\n- 1、先把模板字符串编译成 tokens(代号)\n- 2、根据 tokens,结合数据渲染成 dom\n\n> 本质上,tokens 是一个 js 嵌套数组没事模板字符串 js 的表示,他是`抽象语法树`,`虚拟节点`的开山鼻祖\n\n假设有这么一个模板字符串\n\n```html\n

      我买了一个{{thing}},好{{mood}}啊

      \n```\n\n会编译成 tokens,如下:\n\n```js\n// 这里面每一个数组行都是一个 token,组起来就是 tokens\n// html 标签也会被看成纯文本\n[\n [\"text\", \"

      我买了一个\"],\n [\"name\", \"thing\"],\n [\"text\", \"好\"],\n [\"name\", \"mood\"],\n [\"text\", \"啊

      \"],\n];\n```\n\n当模板存在循环式,带层级嵌套,如下:\n\n```html\n
      \n
        \n {{#arr}}\n
      • {{.}}
      • \n {{/arr}}\n
      \n
      \n```\n\n会被编译成\n\n```js\n[\n [\"text\", \"
        \"],\n [\n \"#\",\n \"arr\",\n [\n [\"text\", \"li\"],\n [\"name\", \".\"],\n [\"text\", \"\"],\n ],\n ],\n [\"text\", \"
      \"],\n];\n```\n\n如果是双重循环,带层级嵌套继续加一层,例如:\n\n```html\n
      \n
        \n {{#students}}\n
      1. \n 学生{{item.name}}的爱好是\n
          \n {{#item.hobbies}}\n
        1. {{.}}
        2. \n {{/#item.hobbies}}\n
        \n
      2. \n {{/#students}}\n
      \n
      \n```\n\n会被编译成\n\n```js\n[\n [\"text\", \"
        \"],\n [\n \"#\",\n \"students\",\n null,\n null,\n [[\"text\", \"
      1. 学生\"], [\"name\", \"name\"], [\"text\", \"的爱好是
          \"], [\"#\", \"hobbies\", null, null], [\n ['text','
        1. '],\n ['name','.'],\n ['text','
        2. ']\n ]],\n ['text','
            '],\n ]],\n ['text','
      ']\n ],\n];\n```\n\n> 在`mustache.js`中完成上述这一过程的函数`parseTemplate`,可以去找源代码看\n\n## tokens 生成算法\n\n用简单的模板字符串举例:\n\n```html\n我买了一个{{thing}},好{{mood}}啊\n```\n\n有一个指针往右遍历,从`我`开始,遍历到`啊`结束,如下:\n\n```js\n/**\n *\n * 我买了一个{{thing}},好{{mood}}啊\n * ↑\n *\n * Step1:指针位置 = 1\n *\n * 我买了一个{{thing}},好{{mood}}啊\n * ↑\n *\n * Step2:指针位置 = 2\n *\n * 我买了一个{{thing}},好{{mood}}啊\n * ↑\n *\n * Step3:指针位置 = 4\n *\n * 我买了一个{{thing}},好{{mood}}啊\n * ↑\n *\n * Step4:指针位置 = 11\n *\n * 我买了一个{{thing}},好{{mood}}啊\n * ↑\n *\n * Step5:指针位置 = 15\n *\n * 我买了一个{{thing}},好{{mood}}啊\n * ↑\n *\n * ... while(指针位置 >= 模板字符串.length)\n *\n * /\n```\n\n- Step1:\n\n指针右移 1 个长度,以指针位置切割,字符串被分成`我`+`买了一个{{thing}},好{{mood}}啊`\n\n- Step2:\n\n指针右移 1 个长度,以指针位置切割,字符串被分成`我买`+`了一个{{thing}},好{{mood}}啊`\n\n- Step3:\n\n第一次遇到,通过 `indexOf(\"{{\") == 0` 判断\n\n```js\n// 标记为 text 放到 tokens 中\ntoken.push([\"text\", \"我买了一个\"]); // tokens: [['text', '我买了一个']]\n```\n\n结束,此时指针位置 = 4\n\n- Step4:\n\n指针右移 2 个长度,跳过`{{`,暂存此时`pos_last = 6`\n\n此时,右边字符串(尾字符串)`thing}},好{{mood}}啊`\n\n右移 5 个长度,识别`模板内部数据对象`:\n\n```js\nsubstring(post_last, 6 + 5); // thing 5个长度\n// 标记为 name 放到 tokens 中\ntoken.push([\"name\", \"thing\"]); // tokens: [['text', '我买了一个']], ['name', 'thing']]\n```\n\n结束,此时指针位置 = 11\n\n- Step5:\n\n> 遇到 `}}`,通过 `indexOf(\"}}\") == 0`判断\n\n指针右移 2 个长度,跳过`}}`,暂存此时`post_last = 13`,继续右移 2 个长度\n\n```js\nsubstring(post_last, 13 + 2); // ,好 2个长度\n// 标记为 text 放到 tokens 中\ntoken.push([\"text\", \",好\"]); // tokens: [['text', '我买了一个']], ['name', 'thing'],['text', ',好' ]]\n```\n\n> 第二次,遇到 `{{`\n\n剩下循环执行就行了,这个过程,我们可以称作`扫描 Scan`\n\n## 扫描器 Scanner\n\n新建一个 `Scanner.js`,用来扫描模板字符串,实现上面的原理\n\n```js\n/**\n * 模板字符串扫描器\n */\n\nclass Scanner {\n constructor(templ) {\n this.templ = templ; // 模板字符串\n this.tail = templ; // 尾字符串\n this.pPos = 0; // 指针位置\n }\n\n /**\n * 指针跳过模板标签\n * @param {模板语法包围标签} tag\n */\n jumpTag(tag) {\n if (this.tail.indexOf(tag) === 0) {\n this.pPos += tag.length; // 指针右移 tag.length 个长度\n this.tail = this.templ.substring(this.pPos); // 尾字符串更新\n }\n }\n\n /**\n * 指针遇见模板标签 {{\n * @param {模板语法包围标签} tag\n */\n missTag(tag) {\n let pPos_last = this.pPos;\n while (!this.eof() && this.tail.indexOf(tag) !== 0) {\n this.pPos++;\n this.tail = this.templ.substring(this.pPos);\n }\n return this.templ.substring(pPos_last, this.pPos);\n }\n\n eof() {\n return this.pPos >= this.templ.length;\n }\n}\n```\n\n## 分析器 Parser\n\n调用`Scanner.js`\n\n```js\nlet tmpl = `我买了一个{{thing}},好{{mood}}啊`;\n// 编译 模板字符串 => tokens\nconst Parser = {\n createTokens: (tmpl) => {\n let scanner = new Scanner(tmpl);\n let tokens = [];\n // scanner 循环执行\n while (!scanner.eof()) {\n ctx = scanner.missTag(\"{{\"); // 返回 头字符串\n if (ctx != \"\") {\n tokens.push([\"text\", ctx]);\n }\n scanner.jumpTag(\"{{\"); // 跳过 模板字符\n ctx = scanner.missTag(\"}}\"); // 返回 {{ x }}\n if (ctx != \"\") {\n tokens.push([\"name\", ctx]);\n }\n scanner.jumpTag(\"}}\");\n }\n return tokens;\n },\n};\nconsole.log(Parser.createTokens(tmpl));\n// 输出,非常的 奈一丝\n// [\"text\", \"我买了一个\"]\n// [\"name\", \"thing\"]\n// [\"text\", \",好\"]\n// [\"name\", \"mood\"]\n// [\"text\", \"啊\"]\n```\n\n## 扫描器 Scanner 增强\n\n上面的`Parser`只能识别`{{`和`}}`,如果模板语法复杂一点,比如加入 `{{#list}}...{{/list}}`,需要增强`Parser`\n\n```js\nconst template = `\n 哈哈哈\n {{#students}}\n 我买了一个 {{ thing }},好{{mood}}啊{{a}}\n {{item.name}}\n {{/students}}\n `;\nconst Parser = {\n createTokens: (tmpl) => {\n let scanner = new Scanner(tmpl);\n let tokens = [];\n let ctx = \"\";\n // scanner 循环执行\n while (!scanner.eof()) {\n ctx = scanner.missTag(\"{{\"); // 返回 头字符串\n if (ctx != \"\") {\n tokens.push([\"text\", ctx]);\n }\n scanner.jumpTag(\"{{\"); // 跳过 模板字符\n ctx = scanner.missTag(\"}}\"); // 返回 {{ x }}\n if (ctx != \"\") {\n switch (ctx[0]) {\n case \"#\":\n tokens.push([\"#\", ctx.substr(1)]); // {{# x }}\n break;\n case \"/\":\n tokens.push([\"/\", ctx.substr(1)]);\n break;\n default:\n tokens.push([\"name\", ctx]);\n break;\n }\n }\n scanner.jumpTag(\"}}\");\n }\n return tokens;\n },\n};\nconsole.log(Parser.createTokens(template));\n\n// 输出\n// [\"text\", \"↵ 哈哈哈↵ \"]\n// [\"#\", \"students\"]\n// [\"text\", \"↵ 我买了一个 \"]\n// [\"name\", \" thing \"]\n// [\"text\", \",好\"]\n// [\"name\", \"mood\"]\n// [\"text\", \"啊\"]\n// [\"name\", \"a\"]\n// [\"text\", \"↵ \"]\n// [\"name\", \"item.name\"]\n// [\"text\", \"↵ \"]\n// [\"/\", \"students\"]\n// [\"text\", \"↵ \"]\n```\n\n## 栈队列算法\n\n上一步最后的输出,只有单层嵌套,如果是两层嵌套怎么办?\n\n例如模板语法如下:\n\n```js\nvar template = `\n 哈哈哈\n {{#students}}\n {{#stu}}\n {{stu.name}}买了一个 {{ thing }},好{{mood}}啊{{a}}\n {{/stu}}\n {{item.name}}\n {{/students}}\n `;\n```\n\n经过`Parser`处理得到:\n\n```js\n/**\n *\n * [\"text\", \"↵ 哈哈哈↵ \"]\n * [\"#\", \"students\"]\n * [\"text\", \"↵ \"]\n * [\"#\", \"stu\"]\n * [\"text\", \"↵ \"]\n * [\"name\", \"stu.name\"]\n * [\"text\", \"买了一个 \"]\n * [\"name\", \" thing \"]\n * [\"text\", \",好\"]\n * [\"name\", \"mood\"]\n * [\"text\", \"啊\"]\n * [\"name\", \"a\"]\n * [\"text\", \"↵ \"]\n * [\"/\", \"stu\"]\n * [\"text\", \"↵ \"]\n * [\"name\", \"item.name\"]\n * [\"text\", \"↵ \"]\n * [\"/\", \"students\"]\n * [\"text\", \"↵ \"]\n *\n * /\n```\n\n此时`students`和`stu`都是`#`标记,我们需要利用算法处理他们的嵌套结构,处理成大约如下这样的结构:\n\n```js\n/**\n *\n * [\"text\", \"↵ 哈哈哈↵ \"]\n * Array(3)\n * \"#\"\n * \"students\"\n * Array(5)\n * [\"text\", \"↵ \"]\n * [\"#\", \"stu\", Array(9)]\n * [\"text\", \"↵ \"]\n * [\"name\", \"item.name\"]\n * [\"text\", \"↵ \"]\n * [\"text\", \"↵ \"]\n *\n * /\n```\n\n# 常用工具类\n\n## 递归\n\n```js\n/**\n * {string} dir 递归根目录\n * {object} list 暂存参数\n */\nconst deep = async (dir, list = []) => {\n const dirs = await fs.promises.readdir(dir)\n for (let i = 0; i < dirs.length; i++) {\n const item = dirs[i]\n const itemPath = path.join(dir, item)\n const isDir = fs.statSync(itemPath).isDirectory()\n isDir ? await deep(itemPath, list) : list.push(itemPath)\n }\n return list\n}\n```\n\n## 自增id短码\n\n用于连接分享\n\n```typescript\nconst createAscString = (id) => {\n const dictionary = [\n \"0123456789\",\n \"abcdefghigklmnopqrstuvwxyz\",\n \"ABCDEFGHIGKLMNOPQRSTUVWXYZ\",\n ];\n let chars = dictionary.join(\"\").split(\"\"),\n radix = chars.length,\n qutient = 1000 * 1000 * 9999 + +id,\n arr = [];\n while (qutient) {\n mod = qutient % radix;\n qutient = (qutient - mod) / radix;\n arr.unshift(chars[mod]);\n }\n return arr.join(\"\");\n};\n\nconsole.log(createAscString(100000000));\n```\n\n## 手动实现 eventBus\n\n```js\nexport default class EventBus {\n constructor() {\n // key-value : eventName-date\n this.callbacks = {};\n }\n\n /**\n * 监听事件\n * @param {事件名} eventName\n * @param {回调函数} callback\n */\n on(eventName, callback) {\n this.checkType(eventName).callbacks[eventName]\n ? callback(this.callbacks[eventName])\n : this.error(`The event has not been declared`);\n }\n\n /**\n * 注册一个事件\n * @param {事件名} eventName\n * @param {传递的对象} data\n */\n emit(eventName, data) {\n this.checkType(eventName).callbacks[eventName] = data;\n }\n\n /**\n * 注销事件,不传参数默认注销全部事件\n * @param {事件名} eventName\n */\n off(eventName) {\n eventName\n ? this.checkType(eventName).removeEvent(eventName)\n : this.emptyEvent();\n }\n\n /**\n * 移出事件\n * @param {事件名} eventName\n */\n removeEvent(eventName) {\n Reflect.deleteProperty(this.callbacks, eventName);\n }\n\n /**\n * 清空全部事件\n */\n emptyEvent() {\n this.callbacks = [];\n }\n\n /**\n * 参数类型校验\n * @param {参数} param\n * @param {合法的类型} validType\n */\n checkType(param, validType = \"string\") {\n if (typeof param !== validType)\n this.error(`(param, ${param}) should be of ${validType} type`);\n return this; // 缅怀jQ链式调用\n }\n\n /**\n * 错误提示\n * @param {提示文字} text\n */\n error(text) {\n throw new Error(text);\n }\n}\n```\n\n调用\n\n```js\n// 省略import\nconst eventBus = new EventBus();\neventBus.emit(\"login\", [{ a: 1, d: 2 }]);\neventBus.on(123, (d) => console.log(d)); // [{...}]\n```\n\n## 判断对象是否有某个 key\n\n```javascript\nlet obj = { alias: \"es6\" };\n\"alias\" in obj; // true\nReflect.has(obj, \"alias\"); // true\n```\n\n## 浏览器\n\n## 版本信息\n\n```javascript\nwindow.navigator.userAgent;\n```\n\n## 兼容事件绑定\n\n```javascript\n/*\n兼容低版本IE,ele为需要绑定事件的元素,\neventName为事件名(保持addEventListener语法,去掉on),fun为事件响应函数\n*/\n\nfunction addEvent(ele, eventName, fun) {\n ele.addEventListener\n ? ele.addEventListener(eventName, fun, false)\n : ele.attachEvent(\"on\" + eventNme, fun);\n}\n```\n\n## 数组对象\n\n## reduce\n\n```javascript\nvar arr = [1, 2, 3, 4];\nvar sum = arr.reduce(function(prev, cur, index, arr) {\n console.log(prev, cur, index);\n return prev + cur;\n},0) //注意这里设置了初始值\nconsole.log(arr, sum);\n\n// 求和\nconst sum = arr.reduce((p,c) => p+c)\n```\n\n## 对象内部根据 key 对 value 进行排序,取前 3\n\n```javascript\nlet datasource = [\n { price: 1, alias: \"watermelon\" },\n { price: 3, alias: \"orange\" },\n { price: 2, alias: \"banana\" },\n { price: 4, alias: \"apple\" },\n];\n// 降序排列\nlet compare = (key) => (a, b) => b[key] - a[key];\nlet sorted = datasource\n .sort(compare(\"price\"))\n .slice(0, 3)\n .map((i) => i[\"alias\"]);\n// 返回 [\"apple\", \"orange\", \"banana\"]\n```\n\n## 随机字符串\n\n```javascript\nconst getRandomRangeNum = (len = 32) => {\n // 略去不宜辨识字符\n let dictionary = \"ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz\";\n let maxPos = dictionary.length;\n let res = \"\";\n for (let i = 0; i < len; i++) {\n res += dictionary.charAt(Math.floor(Math.random() * maxPos));\n }\n return res;\n};\n```\n\n## 类型检测\n\nQ:使用`typeof foo === \"object\"`检测`foo`是否为对象有什么缺点?如何避免?\n\nA:用 `typeof` 是否能准确判断一个对象变量,答案是否定的,`null` 的结果也是 `object`,`Array` 的结果也是 `object`,有时候我们需要的是 \"纯粹\" 的 `object` 对象\n\n```js\nObject.prototype.toString.call(obj) === \"[object Object]\";\n```\n\n## 倒计时\n\n```javascript\nclass Countdown {\n constructor(startNum, endNum, interval) {\n [this.startNum, this.endNum, this.interval] = [startNum, endNum, interval];\n }\n execute() {\n var timer = setTimeout(() => {\n if (this.startNum >= this.endNum) {\n console.log(this.startNum);\n this.startNum -= 1;\n this.execute();\n } else {\n clearTimeout(timer);\n }\n }, this.interval);\n }\n}\n// 实例化调用\nvar countdown = new Countdown(5, 0, 1000).execute();\n```\n\n## 范围随机数\n\n```javascript\n// 能取到 min,取不到 max\nfunction getRandomRangeNum(min, max) {\n return min + Math.floor(Math.random() * (max - min));\n}\n```\n\n## 获取当前月的天数\n\n```javascript\nconst getCurMonthDays = new Date(\n new Date().getFullYear(),\n new Date().getMonth() + 1,\n 0\n).getDate();\n```\n\n\n# 对象小操作\n\n## 去虚假值\n\n```js\nlet arr4 = [\"小明\", \"小蓝\", \"\", false, \" \", undefined, null, 0, NaN, true];\nconsole.log(arr4.filter(Boolean)); // => ['小明', '小蓝', ' ', true]\n```\n\n## 头尾插入\n\n效率比 `unshift()` 高\n\n```js\nlet arr = [1, 2, 3];\n// 头插入\n[\"haha\"].concat(arr);\n// 尾插入\narr.concat([\"haha\"]);\n```\n\n\n## 删除属性\n\n```js\nfunction deleteA(obj) {\n delete obj.A;\n return obj;\n}\n\n// 使用解构赋值\nconst deleteA = ({ A, ...rest } = {}) => rest;\n```\n\n# 生产、加工、消费分离\n\n- 从接口拿数据到视图 fetch api\n- 加工 computed\n- 消费 v-for\n\n# 元数据\n\n```js\nimport \"reflect-metadata\"; // npm install reflect-metadata\n\nfunction Role(name: string): ClassDecorator {\n return (target) => {\n Reflect.defineMetadata(\"role\", name, target);\n };\n}\n\n@Role(\"admin\")\nclass Post {}\n\nconst metadata = Reflect.getMetadata(\"role\", Post);\n\nReflect.set(Post, \"role2\", metadata);\n\nconsole.log(Reflect.get(Post, \"role2\")); // admin\n```\n\n# 防抖与节流\n\n在页面上监听诸如`scroll`(页面滚动),`mousemove`(鼠标移动) ,`keydown`, `keyup`, `keypress`(按下键盘)等等一系列事件的时候,我们并不希望频繁的触发这类监听,尤其当请求非常消耗资源时,这种操作会导致服务器性能急剧下降。\n\n## Debounce\n\n把触发非常频繁的事件合并成一次延迟执行,如果对监听函数使用 100ms 的容忍时间,那么时间在第 3.1s 的时候执行\n\n```javascript\n// 默认延时100ms\nfunction debounce(func, dealy = 100) {\n let timer;\n return function () {\n // 暂存this和参数\n let _this = this;\n let args = arguments;\n // 清除定时器,确保不执行func\n clearTimeout(timer);\n timer = setTimeout(function () {\n func.apply(_this, args);\n }, dealy);\n };\n}\n// 执行函数\nfunction handler() {\n console.log(`delay 100ms ,then handle`);\n}\n// dom添加监听\ndocument\n .querySelector(\"#someNode\")\n .addEventListener(\"scroll\", debounce(handler));\n```\n\n## Throttle\n\n固定函数执行的速率,即所谓的“节流”。设置一个阀值,在阀值内,把触发的事件合并成一次执行;当到达阀值,必定执行一次事件。\n\n```javascript\nfunction throttle(func, delay) {\n let statTime = 0;\n return function () {\n let currentTime = +new Date();\n if (currentTime - statTime > delay) {\n func.apply(this, arguments);\n statTime = currentTime;\n }\n };\n}\n// 执行函数\nfunction resizeHandler() {\n console.log(`resize`);\n}\n// window添加监听\nwindow.onresize = throttle(resizeHandler, 300);\n```\n\n# this 指向\n\n## 全局环境\n\n全局环境下,this 始终指向全局对象(window),无论是否严格模式\n\n```javascript\nconsole.log(this === window); // true\nthis.a = 37;\nconsole.log(window.a); // 37\n```\n\n## 函数上下文调用\n\n- 非严格模式\n\n没有被上一级的对象所调用, this 默认指向全局对象 window\n\n```javascript\nfunction f1() {\n return this;\n}\nf1() === window; // true\n```\n\n- 严格模式\n\nthis 指向 undefined\n\n```javascript\nfunction f2() {\n \"use strict\"; // 这里是严格模式\n return this;\n}\nf2() === undefined; // true\n```\n\n## 箭头函数\n\n> 箭头函数中,call()、apply()、bind()方法无效\n\n在全局代码中,箭头函数被设置为全局对象,总之箭头函数不改变 this 指向\n\n```javascript\nvar globalObject = this;\nvar foo = () => this;\nconsole.log(foo() === globalObject); // true\n```\n\n箭头函数作为对象的方法使用,指向全局 window 对象\n\n```javascript\nvar obj = {\n i: 10,\n b: () => console.log(this.i, this),\n c: function () {\n console.log(this.i, this);\n },\n};\nobj.b(); // undefined window{...}\nobj.c(); // 10 Object {...}\n```\n\n箭头函数可以让 this 指向固化,这种特性很有利于封装回调函数\n\n```javascript\n// 总是指向 handler 对象。如果不使用箭头函数则指向全局 document 对象\nvar handler = {\n id: \"123456\",\n\n init: function () {\n document.addEventListener(\n \"click\",\n (event) => this.doSomething(event.type),\n false\n );\n },\n\n doSomething: function (type) {\n console.log(\"Handling \" + type + \" for \" + this.id);\n },\n};\n```\n\n# call, apply, bind 与 es6\n\njs 的函数继承于`Function.prototype`对象,因此每个函数都会有 apply、call、bind 方法\n\n> call 和 apply 的作用,完全一样,唯一的区别就是在参数上面。\n\n`call, apply, bind`改变函数中 `this 指向` 的三兄弟,把`this`绑定到第一个参数对象上\n\n```javascript\nfunction displayHobbies(...hobbies) {\n console.log(`${this.name} likes ${hobbies.join(\", \")}.`);\n}\n// 下面两个等价\ndisplayHobbies.call({ name: \"Bob\" }, \"swimming\", \"basketball\", \"anime\"); // Bob likes swimming, basketball, anime.\ndisplayHobbies.apply({ name: \"Bob\" }, [\"swimming\", \"basketball\", \"anime\"]); // Bob likes swimming, basketball, anime.\n```\n\n`bind`返回的是一个函数,需要手动执行\n\n```js\nvar p1 = {\n name: \"张三\",\n age: 12,\n func: function () {\n console.log(`姓名:${this.name},年龄:${this.age}`);\n },\n};\n\nvar p2 = {\n name: \"李四\",\n age: 15,\n};\n\np1.func.bind(p2)(); //姓名:李四,年龄:15\n```\n\n# for 循环优化\n\n```javascript\n// 每次都要计算array.length\nfor (let i = 0; i < array.length; i++) {\n console.log(i);\n}\n\n// 使用leng缓存array长度\nfor (let i = 0, length = array.length; i < length; i++) {\n console.log(i);\n}\n```\n\n# 数组\n\n## 扁平化去重升序排列\n\n```javascript\nlet arr = [[1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10];\narr.flat(Infinity); // [1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9, 11, 12, 12, 13, 14, 10]\n\nlet result = Array.from(new Set(arr.flat(Infinity)).sort((a, b) => a - b));\n```\n\n# 前端页面埋点 - 1x1.gif\n\n通常用在,统计页面点击,曝光,停留时间,签发……等场景\n\n- 比 PNG/JPG 体积小\n- 天然跨域\n\n```html\n\n\n```\n\n# 事件委托\n\n利用冒泡原理,委托父元素执行\n\n```html\n
        \n
      • 苹果
      • \n
      • 香蕉
      • \n
      • 凤梨
      • \n
      \n```\n\n```javascript\ndocument.querySelector(\"ul\").onclick = (event) => {\n let target = event.target;\n if (target.nodeName === \"LI\") {\n console.log(target.innerHTML);\n }\n};\n```\n\n# 构造函数 + 原型模式\n\n```javascript\nfunction Person(name, age, job) {\n this.name = name;\n this.age = age;\n this.job = job;\n}\nPerson.prototype.say = function (text) {\n console.log(`${this.name}say:${text}`);\n};\n```\n\n# 剩余参数...args\n\n剩余参数`args`数个数组,`...`解构符\n\n```javascript\nfunction fun1(param, ...args) {\n alert(args.length);\n}\n```\n\n# 跨页面通信\n\n- cookie\n- web worker\n- localstorage\n\n# iframe 跨域通信和不跨域通信\n\n## 不跨域\n\n```javascript\n// fatherSay是父页面全局方法\nwindow.parent.fatherSay();\n// 父页面Dom\nwindow.parent.document.getElementById(\"元素id\");\n// 副业页面获取frameID为`iframe_ID`的子页面的Dom\nwindow.frames[\"iframe_ID\"].document.getElementById(\"元素id\");\n```\n\n## 跨域 postMessage\n\n子页面\n\n```javascript\nwindow.parent.postMessage(\"hello\", \"http://127.0.0.1:8089\");\n```\n\n父页面接受\n\n```javascript\nwindow.addEventListener(\"message\", function (event) {\n alert(123);\n});\n```\n\n# 对象类型判断\n\n## 数组\n\n```javascript\nlet arr = [];\narr instanceof Array; // true\nArray.isArray(arr); // true\nObject.prototype.toString.call(arr); // \"[object Array]\"\n```\n\n# js 单线程,如何异步\n\n- 主线程 执行 js 中所有的代码。\n\n- 主线程 在执行过程中发现了需要异步的任务任务后扔给浏览器(浏览器创建多个线程执行,顺便创造一个`回调队列`。\n\n- 主线程 已经执行完毕所有同步代码。监听`回调队列`一旦 浏览器 中某个线程任务完成将会改变回调函数的状态。主线程查看到某个函数的状态为已完成,就会执行该`回调队列`中对应的回调函数。\n\n# 移动端最小触控区域\n\n苹果推荐是 44pt x 44pt 「具体看 WWDC 14」,通过`padding`、`margin`、`height`等方式进行点击区域扩展\n\n# js 精度问题\n\n常用类库:`Math.js`、`Big.js`、`decimal.js`\n\n# 冻结 Object. freeze()\n\n`const`生命的简单变量不可修改,但是复杂对象可以被修改,`Object. freeze()`:可以冻结对象\n\n- 不能添加新属性\n- 不能删除已有属性\n- 不能修改已有属性的可枚举性、可配置性、可写性\n- 不能修改已有属性的值\n- 不能修改原型\n\n浅冻结\n\n```javascript\nconst obj1 = {\n internal: {},\n};\n\nObject.freeze(obj1);\nobj1.internal.a = \"aValue\";\nconsole.log(obj1.internal.a); // aValue\n```\n\n递归冻结\n\n```javascript\nfunction deepFreeze(obj) {\n // 获取定义在obj上的属性名\n var propNames = Object.getOwnPropertyNames(obj);\n // 在冻结自身之前冻结属性\n propNames.forEach(function (name) {\n var prop = obj[name];\n // 如果prop是个对象,冻结它\n if (typeof prop == \"object\" && prop !== null) deepFreeze(prop);\n });\n return Object.freeze(obj);\n}\n```\n\n# Reflect\n\n## Reflect.get(target, propertyKey, value[receiver])\n\n获取对象身上某个属性的值,类似于 target[name]。\n\n## Reflect.set(target, propertyKey, value[receiver])\n\n将值分配给属性的函数。返回一个 Boolean,如果更新成功,则返回 true。\n\n## Reflect.has(target, propertyKey)\n\n判断一个对象是否存在某个属性,和 in 运算符 的功能完全相同。\n\n\n# if else 优化\n\n## 表驱动编程\n\n空间换时间,设置`obj={key:value}`,通过`obj[key]`取值\n\n```javascript\ncalculateGrade(score){\n const table = {\n 100: 'A',\n 90: 'A',\n 80: 'B',\n 70: 'C',\n 60: 'D',\n others: 'E'\n }\n return table[Math.floor(score/10)*10] || table['others']\n}\n```\n\n## 短路运算\n\nreact 没有`v-if`,运用比较频繁\n\n```js\n// 函数组件\nconst Home = () => {\n return
      home
      ;\n};\n{\n true && ;\n}\n```\n\n# 使用有意义且易读的变量名\n\n```js\n👎 const yyyymmdstr = moment().format(\"YYYY/MM/DD\");\n\n👍 const currentDate = moment().format(\"YYYY/MM/DD\");\n```\n\n# 使用有意义的变量代替数组下标\n\n```js\n👎\nconst address = \"One Infinite Loop, Cupertino 95014\";\nconst cityZipCodeRegex = /^[^,\\\\]+[,\\\\\\s]+(.+?)\\s*(\\d{5})?$/;\nsaveCityZipCode(\n address.match(cityZipCodeRegex)[1],\n address.match(cityZipCodeRegex)[2]\n);\n\n👍\nconst address = \"One Infinite Loop, Cupertino 95014\";\nconst cityZipCodeRegex = /^[^,\\\\]+[,\\\\\\s]+(.+?)\\s*(\\d{5})?$/;\nconst [_, city, zipCode] = address.match(cityZipCodeRegex) || [];\nsaveCityZipCode(city, zipCode);\n```\n\n# 变量名要简洁\n\n```js\n👎\nconst Car = {\n carMake: \"Honda\",\n carModel: \"Accord\",\n carColor: \"Blue\"\n};\nfunction paintCar(car, color) {\n car.carColor = color;\n}\n\n👍\nconst Car = {\n make: \"Honda\",\n model: \"Accord\",\n color: \"Blue\"\n};\nfunction paintCar(car, color) {\n car.color = color;\n}\n```\n\n# 消除魔术字符串\n\n```js\n👎 setTimeout(blastOff, 86400000);\n\n👍 const MILLISECONDS_PER_DAY = 60 * 60 * 24 * 1000; //86400000;\nsetTimeout(blastOff, MILLISECONDS_PER_DAY);\n```\n\n# 使用默认参数替代短路运算符\n\n```js\n👎\nfunction createMicrobrewery(name) {\n const breweryName = name || \"Hipster Brew Co.\";\n // ...\n}\n\n👍\nfunction createMicrobrewery(name = \"Hipster Brew Co.\") {\n // ...\n}\n```\n\n# 一个函数\n\n```js\n👎\nfunction emailClients(clients) {\n clients.forEach(client => {\n const clientRecord = database.lookup(client);\n if (clientRecord.isActive()) {\n email(client);\n }\n });\n}\n\n👍\nfunction emailActiveClients(clients) {\n clients.filter(isActiveClient).forEach(email);\n}\nfunction isActiveClient(client) {\n const clientRecord = database.lookup(client);\n return clientRecord.isActive();\n}\n\n---------------------分割线-----------------------\n\n👎\nfunction createFile(name, temp) {\n if (temp) {\n fs.create(`./temp/${name}`);\n } else {\n fs.create(name);\n }\n}\n\n👍\nfunction createFile(name) {\n fs.create(name);\n}\nfunction createTempFile(name) {\n createFile(`./temp/${name}`);\n}\n```\n\n# 函数参数不多于 2 个,如果有很多参数就利用 object 传递,并使用解构\n\n```js\n👎\nfunction createMenu(title, body, buttonText, cancellable) {\n // ...\n}\ncreateMenu(\"Foo\", \"Bar\", \"Baz\", true);\n\n👍\nfunction createMenu({ title, body, buttonText, cancellable }) {\n // ...\n}\ncreateMenu({\n title: \"Foo\",\n body: \"Bar\",\n buttonText: \"Baz\",\n cancellable: true\n});\n```\n\n# 函数名应该直接反映函数的作用\n\n```js\n👎\nfunction addToDate(date, month) {\n // ...\n}\nconst date = new Date();\n// It's hard to tell from the function name what is added\naddToDate(date, 1);\n\n👍\nfunction addMonthToDate(month, date) {\n // ...\n}\nconst date = new Date();\naddMonthToDate(1, date);\n```\n\n# 尽量使用纯函数\n\n```js\n👎\nconst programmerOutput = [\n {\n name: \"Uncle Bobby\",\n linesOfCode: 500\n },\n {\n name: \"Suzie Q\",\n linesOfCode: 1500\n },\n {\n name: \"Jimmy Gosling\",\n linesOfCode: 150\n },\n {\n name: \"Gracie Hopper\",\n linesOfCode: 1000\n }\n];\nlet totalOutput = 0;\nfor (let i = 0; i < programmerOutput.length; i++) {\n totalOutput += programmerOutput[i].linesOfCode;\n}\n\n👍\nconst programmerOutput = [\n {\n name: \"Uncle Bobby\",\n linesOfCode: 500\n },\n {\n name: \"Suzie Q\",\n linesOfCode: 1500\n },\n {\n name: \"Jimmy Gosling\",\n linesOfCode: 150\n },\n {\n name: \"Gracie Hopper\",\n linesOfCode: 1000\n }\n];\nconst totalOutput = programmerOutput.reduce(\n (totalLines, output) => totalLines + output.linesOfCode,\n 0\n);\n```\n\n# 不要过度优化\n\n```js\n👎\n// 现代浏览器对于迭代器做了内部优化\nfor (let i = 0, len = list.length; i < len; i++) {\n // ...\n}\n\n👍\nfor (let i = 0; i < list.length; i++) {\n // ...\n}\n```\n\n\n# 关于模块化\n\n## 无模块化\n\n> 污染全局作用域、维护成本高、依赖关系不明显\n\nscript 标签引入 js 文件,相互罗列,但是被依赖的放在前面,否则使用就会报错。如下:\n\n```js\n\n\n\n\n\n```\n\n## CommonJS 规范(同步)\n\n该规范最初是用在服务器端的 node 的,它有四个重要的环境变量为模块化的实现提供支持:module、exports、require、global。实际使用时,用 module.exports 定义当前模块对外输出的接口(不推荐直接用 exports),用 require 加载模块(同步)\n\n> module.exports 本身就是一个对象\n\n```js\nmodule.exports = { foo: \"bar\" }; //true\nmodule.exports.foo = \"bar\"; //true。\n```\n\nCommonJS 用同步的方式加载模块。在服务端,模块文件都存在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,限于网络原因,CommonJS 不适合浏览器端模块加载,更合理的方案是使用异步加载,比如下边 AMD 规范。\n\n## AMD 规范(RequireJS)\n\n承接上文,AMD 规范则是非同步加载模块,允许指定回调函数,AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。\n\n- `require([module], callback)`:加载模块\n- `define(id, [depends], callback)`:定义模块\n- `require.config()`:配置路径、依赖关系\n\n## CMD 规范(SeaJS)\n\nCMD 是 在推广过程中对模块定义的规范化产出\n\n## ES6 import/export\n\n通过 babel 将不被支持的 import 编译为当前受到广泛支持的 AMD 规范\n\n# AMD模块化实现\n\n## define 函数\n\n用来声明模块名`module`,依赖数组`deps`,以及模块的作用`callback`\n\n```js\nvar module = (function () {\n var stack = {}; //模块存储栈,存的是模块执行后的结果\n\n function define(module, deps, callback) {\n stack[module] = callback.apply(null, deps); // 压栈\n console.log(stack); // 查看栈存储的模块\n }\n\n return { define: define };\n})();\n```\n\n调用`define`试试,发现 `calc`模块被压入了`stack 模块栈中`\n\n```js\nmodule.define(\"calc\", [], function () {\n return {\n first: function (arr) {\n return arr[0];\n },\n };\n});\n// {calc: { first: ƒ }}\n```\n\n## deps 依赖(导入)\n\n对上面的 `define`函数稍加改造,这一步过程的本质,就是对 deps\n\n```js\nvar module = (function (window) {\n var stack = {}; //模块存储栈,\n\n function define(module, deps, callback) {\n // 把 define 函数依赖数组中的模块从 stack 模块中拿出\n deps.map(function (mod, index) {\n deps[index] = stack[mod]; // 赋值给当前模块的 deps\n });\n stack[module] = callback.apply(null, deps);\n console.log(stack); // 查看栈存储的模块\n }\n\n return { define: define };\n})(window);\n\nmodule.define(\"calc\", [], function () {\n return {\n first: function (arr) {\n return arr[0];\n },\n };\n});\n\nmodule.define(\"number\", [\"calc\"], function (calc) {\n return {\n res: calc.first([4, 3, 2, 1]), //4\n };\n});\n\n// 此时 stack 打印的结果为\n//> calc: {first: ƒ}\n//> number: {res: 4}\n```\n\n> 从本质上看,define 函数的第二个参数 deps 数组,相当于 import 导入,并且如果第三个参数 callback 采用 `return { }`,也就相当于 export 导出\n\n## 老生常谈的指针\n\n说到底,`stack`中 `a模块` export 是一个指针,`{a:value}`(内存地址),所以,`b 模块`会改变`a.a`的值,这点和 `cmd`不同\n\n```js\nmodule.define(\"a\", [], function () {\n return {\n a: 1,\n };\n});\nmodule.define(\"b\", [\"a\"], function (a) {\n a.a = 2;\n});\nmodule.define(\"c\", [\"a\"], function (a) {\n console.log(a.a); // 2\n});\n```\n\n\n# 缘起 Object.defineProperty()\n\n给`目标对象`上定义一个新属性,或者修改`目标对象`属性,并且返回新对象\n\n# 上帝的钥匙 get & set\n\n属性的`getter`函数,如果没有 `getter`则尾 `undefined`。当访问该属性时,会调用此函数.\n\n```js\nlet obj = {};\n\nObject.defineProperty(obj, \"a\", {\n get() {\n return 7;\n },\n set(val) {\n console.log(`FAILED!改变 a 属性,新值为:${val},但是被重写 set 劫持了`);\n },\n});\n\nconsole.log(obj.a);\nobj.a = 4;\n```\n\n> 上帝的钥匙被找到了\n\n劫持!劫持!还是 TMD 劫持!\n\n```js\nlet obj = {};\nlet tempValue = 0;\n\nObject.defineProperty(obj, \"a\", {\n get() {\n return tempValue;\n },\n set(newValue) {\n tempValue = newValue;\n },\n});\n\nconsole.log(obj.a); // 0\nobj.a = 4;\nconsole.log(obj.a); // 4\n```\n\n# 封装 defineReactive(obj, prop, val)\n\n> 这里 defineReactive 第三个参数 val 替代了上一步中全局变量`tempValue`,对于 get()、set()来说,访问到了其他函数内部的变量,所以形成了闭包\n\n```js\nfunction defineReactive(obj, prop, val) {\n Object.defineProperty(obj, prop, {\n get() {\n console.log(`劫持,你访问了${prop}属性`);\n return val;\n },\n set(newValue) {\n if (val === newValue) return;\n console.log(`劫持,你改变了${prop}属性`);\n val = newValue;\n },\n });\n}\n\nlet obj = {};\ndefineReactive(obj, \"a\", 4);\n\nconsole.log(obj.a); // 劫持,你访问了a属性 4\nobj.a = 7; // 劫持,你改变了a属性\nconsole.log(obj.a); // 劫持,你访问了a属性 7\n```\n\n# 递归侦测\n\n```js\nlet obj = { a: { b: { c: {} } } };\ndefineReactive(obj, \"a\", 4);\n```\n\n> 如何自动让`obj`对象的全部属性都`reactive`呢?\n\n```js\nlet obj = {\n a: {\n b: {\n c: 5,\n },\n },\n d: 4,\n};\n```\n\n定义个方法`observe`,递归 `obj` 的每一层的每个 `prop`,检测是否有`__ob__`,如果没有,`defineReactive`,并且挂一个`Observer`实例在这个`props`上,例如:\n\n`Observer对象`\n\n```js\nclass Observer {\n constructor(value) {\n def(value, \"__ob__\", this, false); // 不可枚举,不能给__ob__添加__ob__\n this.walk(value);\n }\n // 遍历每一个 prop的 value\n walk(value) {\n for (let prop in value) {\n defineReactive(value, prop);\n }\n }\n}\n```\n\n`defineReactive.js`\n\n```js\nexport default function defineReactive(obj, prop, val) {\n if (arguments.length === 2) val = obj[prop]; // 如果2个参数\n let childNode = observe(val);\n Object.defineProperty(obj, prop, {\n enumerable: true,\n configurable: true,\n get() {\n console.log(`你访问了${prop}属性`);\n return val;\n },\n set(newValue) {\n if (val === newValue) return;\n console.log(`你改变了${prop}属性`);\n val = newValue;\n childNode = observe(newValue);\n },\n });\n}\n```\n\n`observe.js`\n\n```js\n/**\n * 检测 obj 身上有没有 __ob__(Observer 实例)\n * @param {*} value\n * @returns\n */\nexport default function observe(value) {\n if (typeof value != \"object\") return;\n let ob;\n //! 用__ob__是为了属性不重名,被覆盖\n if (typeof value.__ob__ != \"undefined\") {\n ob = value.__ob__;\n } else {\n ob = new Observer(value);\n }\n return ob;\n}\n```\n","slug":"front-end/码场悟道","published":1,"date":"2023-11-06T07:57:50.766Z","updated":"2023-11-07T06:48:08.822Z","comments":1,"layout":"post","photos":[],"link":"","_id":"clp7x1952000mv3z39lqc2ecv","content":"

      模板引擎

      严格的模板引擎的定义,输入模板字符串 + 数据,得到渲染过的字符串。实现上,从正则替换到拼 function 字符串到正经的 AST 解析各种各样,但从定义上来说都是差不多的。字符串渲染的性能其实也就在后端比较有意义,毕竟每一次渲染都是在消耗服务器资源,但在前端,用户只有一个,几十毫秒的渲染时间跟请求延迟比起来根本不算瓶颈。倒是前端的后续更新是字符串模板引擎的软肋,因为用渲染出来的字符串整个替换 innerHTML 是一个效率很低的更新方式。所以这样的模板引擎如今在纯前端情境下已经不再是好的选择,意义更多是在于方便前后端共用模板。

      \n

      古老数据渲染 vm 的方式

      这种写法,弊端太多了,玩具车

      \n
      <!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Document</title>\n  </head>\n  <body>\n    <ul id=\"app\"></ul>\n  </body>\n  <script>\n    var arr = [\n      { name: \"小明\", age: 11, sex: \"男\" },\n      { name: \"小红\", age: 22, sex: \"女\" },\n    ];\n    var listDOM = document.getElementById(\"app\");\n    arr.forEach(function (item) {\n      let _li = document.createElement(\"li\");\n      _li.innerText = item.name;\n      listDOM.appendChild(_li);\n    });\n  </script>\n</html>
      \n\n

      mustache 原理

        \n
      • 1、先把模板字符串编译成 tokens(代号)
      • \n
      • 2、根据 tokens,结合数据渲染成 dom
      • \n
      \n
      \n

      本质上,tokens 是一个 js 嵌套数组没事模板字符串 js 的表示,他是抽象语法树虚拟节点的开山鼻祖

      \n
      \n

      假设有这么一个模板字符串

      \n
      <h1>我买了一个{{thing}},好{{mood}}啊</h1>
      \n\n

      会编译成 tokens,如下:

      \n
      // 这里面每一个数组行都是一个 token,组起来就是 tokens\n// html 标签也会被看成纯文本\n[\n  [\"text\", \"<h1>我买了一个\"],\n  [\"name\", \"thing\"],\n  [\"text\", \"好\"],\n  [\"name\", \"mood\"],\n  [\"text\", \"啊</h1>\"],\n];
      \n\n

      当模板存在循环式,带层级嵌套,如下:

      \n
      <div>\n  <ul>\n    {{#arr}}\n    <li>{{.}}</li>\n    {{/arr}}\n  </ul>\n</div>
      \n\n

      会被编译成

      \n
      [\n  [\"text\", \"<div><ul>\"],\n  [\n    \"#\",\n    \"arr\",\n    [\n      [\"text\", \"li\"],\n      [\"name\", \".\"],\n      [\"text\", \"</li>\"],\n    ],\n  ],\n  [\"text\", \"</ul></div>\"],\n];
      \n\n

      如果是双重循环,带层级嵌套继续加一层,例如:

      \n
      <div>\n  <ol>\n    {{#students}}\n    <li>\n      学生{{item.name}}的爱好是\n      <ol>\n        {{#item.hobbies}}\n        <li>{{.}}</li>\n        {{/#item.hobbies}}\n      </ol>\n    </li>\n    {{/#students}}\n  </ol>\n</div>
      \n\n

      会被编译成

      \n
      [\n  [\"text\", \"<div><ol>\"],\n  [\n    \"#\",\n    \"students\",\n    null,\n    null,\n    [[\"text\", \"<li>学生\"], [\"name\", \"name\"], [\"text\", \"的爱好是<ol>\"], [\"#\", \"hobbies\", null, null], [\n        ['text','<li>'],\n        ['name','.'],\n        ['text','</li>']\n    ]],\n    ['text','<ol></li>'],\n  ]],\n  ['text','</ol></div>']\n  ],\n];
      \n\n
      \n

      mustache.js中完成上述这一过程的函数parseTemplate,可以去找源代码看

      \n
      \n

      tokens 生成算法

      用简单的模板字符串举例:

      \n
      我买了一个{{thing}},好{{mood}}啊
      \n\n

      有一个指针往右遍历,从开始,遍历到结束,如下:

      \n
      /**\n *\n * 我买了一个{{thing}},好{{mood}}啊\n * ↑\n *\n * Step1:指针位置 = 1\n *\n * 我买了一个{{thing}},好{{mood}}啊\n *   ↑\n *\n * Step2:指针位置 = 2\n *\n * 我买了一个{{thing}},好{{mood}}啊\n *     ↑\n *\n * Step3:指针位置 = 4\n *\n * 我买了一个{{thing}},好{{mood}}啊\n *        ↑\n *\n * Step4:指针位置 = 11\n *\n * 我买了一个{{thing}},好{{mood}}啊\n *               ↑\n *\n * Step5:指针位置 = 15\n *\n * 我买了一个{{thing}},好{{mood}}啊\n *                    ↑\n *\n * ... while(指针位置 >= 模板字符串.length)\n *\n * /
      \n\n
        \n
      • Step1:
      • \n
      \n

      指针右移 1 个长度,以指针位置切割,字符串被分成+买了一个{{thing}},好{{mood}}啊

      \n
        \n
      • Step2:
      • \n
      \n

      指针右移 1 个长度,以指针位置切割,字符串被分成我买+了一个{{thing}},好{{mood}}啊

      \n
        \n
      • Step3:
      • \n
      \n

      第一次遇到,通过 indexOf("{{\") == 0` 判断\n\n

      // 标记为 text 放到 tokens 中\ntoken.push([\"text\", \"我买了一个\"]); // tokens: [['text', '我买了一个']]
      \n\n结束,此时指针位置 = 4\n\n- Step4:\n\n指针右移 2 个长度,跳过`{{`,暂存此时`pos_last = 6`\n\n此时,右边字符串(尾字符串)`thing}},好{{mood}}啊

      \n

      右移 5 个长度,识别模板内部数据对象

      \n
      substring(post_last, 6 + 5); // thing  5个长度\n// 标记为 name 放到 tokens 中\ntoken.push([\"name\", \"thing\"]); // tokens: [['text', '我买了一个']], ['name', 'thing']]
      \n\n

      结束,此时指针位置 = 11

      \n
        \n
      • Step5:
      • \n
      \n
      \n

      遇到 }},通过 indexOf("}}") == 0判断

      \n
      \n

      指针右移 2 个长度,跳过}},暂存此时post_last = 13,继续右移 2 个长度

      \n
      substring(post_last, 13 + 2); // ,好  2个长度\n// 标记为 text 放到 tokens 中\ntoken.push([\"text\", \",好\"]); // tokens: [['text', '我买了一个']], ['name', 'thing'],['text', ',好' ]]
      \n\n
      \n

      第二次,遇到 {{`\n\n剩下循环执行就行了,这个过程,我们可以称作`扫描 Scan`\n\n## 扫描器 Scanner\n\n新建一个 `Scanner.js`,用来扫描模板字符串,实现上面的原理\n\n

      /**\n * 模板字符串扫描器\n */\n\nclass Scanner {\n  constructor(templ) {\n    this.templ = templ; // 模板字符串\n    this.tail = templ; // 尾字符串\n    this.pPos = 0; // 指针位置\n  }\n\n  /**\n   *  指针跳过模板标签\n   * @param {模板语法包围标签} tag\n   */\n  jumpTag(tag) {\n    if (this.tail.indexOf(tag) === 0) {\n      this.pPos += tag.length; // 指针右移 tag.length 个长度\n      this.tail = this.templ.substring(this.pPos); // 尾字符串更新\n    }\n  }\n\n  /**\n   * 指针遇见模板标签 {{\n   * @param {模板语法包围标签} tag\n   */\n  missTag(tag) {\n    let pPos_last = this.pPos;\n    while (!this.eof() && this.tail.indexOf(tag) !== 0) {\n      this.pPos++;\n      this.tail = this.templ.substring(this.pPos);\n    }\n    return this.templ.substring(pPos_last, this.pPos);\n  }\n\n  eof() {\n    return this.pPos >= this.templ.length;\n  }\n}
      \n\n## 分析器 Parser\n\n调用`Scanner.js`\n\n
      let tmpl = `我买了一个{{thing}},好{{mood}}啊`;\n// 编译 模板字符串 => tokens\nconst Parser = {\n  createTokens: (tmpl) => {\n    let scanner = new Scanner(tmpl);\n    let tokens = [];\n    // scanner 循环执行\n    while (!scanner.eof()) {\n      ctx = scanner.missTag(\"{{\"); // 返回 头字符串\n      if (ctx != \"\") {\n        tokens.push([\"text\", ctx]);\n      }\n      scanner.jumpTag(\"{{\"); // 跳过 模板字符\n      ctx = scanner.missTag(\"}}\"); // 返回 {{ x }}\n      if (ctx != \"\") {\n        tokens.push([\"name\", ctx]);\n      }\n      scanner.jumpTag(\"}}\");\n    }\n    return tokens;\n  },\n};\nconsole.log(Parser.createTokens(tmpl));\n// 输出,非常的 奈一丝\n// [\"text\", \"我买了一个\"]\n// [\"name\", \"thing\"]\n// [\"text\", \",好\"]\n// [\"name\", \"mood\"]\n// [\"text\", \"啊\"]
      \n\n## 扫描器 Scanner 增强\n\n上面的`Parser`只能识别`{{`和`}}
      ,如果模板语法复杂一点,比如加入 {{#list}}...{{/list}},需要增强Parser

      \n
      \n
      const template = `\n      哈哈哈\n        {{#students}}\n            我买了一个  {{ thing  }},好{{mood}}啊{{a}}\n          {{item.name}}\n        {{/students}}\n    `;\nconst Parser = {\n  createTokens: (tmpl) => {\n    let scanner = new Scanner(tmpl);\n    let tokens = [];\n    let ctx = \"\";\n    // scanner 循环执行\n    while (!scanner.eof()) {\n      ctx = scanner.missTag(\"{{\"); // 返回 头字符串\n      if (ctx != \"\") {\n        tokens.push([\"text\", ctx]);\n      }\n      scanner.jumpTag(\"{{\"); // 跳过 模板字符\n      ctx = scanner.missTag(\"}}\"); // 返回 {{ x }}\n      if (ctx != \"\") {\n        switch (ctx[0]) {\n          case \"#\":\n            tokens.push([\"#\", ctx.substr(1)]); // {{# x }}\n            break;\n          case \"/\":\n            tokens.push([\"/\", ctx.substr(1)]);\n            break;\n          default:\n            tokens.push([\"name\", ctx]);\n            break;\n        }\n      }\n      scanner.jumpTag(\"}}\");\n    }\n    return tokens;\n  },\n};\nconsole.log(Parser.createTokens(template));\n\n// 输出\n// [\"text\", \"↵      哈哈哈↵        \"]\n// [\"#\", \"students\"]\n// [\"text\", \"↵            我买了一个  \"]\n// [\"name\", \" thing  \"]\n// [\"text\", \",好\"]\n// [\"name\", \"mood\"]\n// [\"text\", \"啊\"]\n// [\"name\", \"a\"]\n// [\"text\", \"↵          \"]\n// [\"name\", \"item.name\"]\n// [\"text\", \"↵        \"]\n// [\"/\", \"students\"]\n// [\"text\", \"↵    \"]
      \n\n

      栈队列算法

      上一步最后的输出,只有单层嵌套,如果是两层嵌套怎么办?

      \n

      例如模板语法如下:

      \n
      var template = `\n      哈哈哈\n        {{#students}}\n            {{#stu}}\n              {{stu.name}}买了一个  {{ thing  }},好{{mood}}啊{{a}}\n            {{/stu}}\n          {{item.name}}\n        {{/students}}\n    `;
      \n\n

      经过Parser处理得到:

      \n
      /**\n *\n * [\"text\", \"↵      哈哈哈↵        \"]\n * [\"#\", \"students\"]\n * [\"text\", \"↵            \"]\n * [\"#\", \"stu\"]\n * [\"text\", \"↵              \"]\n * [\"name\", \"stu.name\"]\n * [\"text\", \"买了一个  \"]\n * [\"name\", \" thing  \"]\n * [\"text\", \",好\"]\n * [\"name\", \"mood\"]\n * [\"text\", \"啊\"]\n * [\"name\", \"a\"]\n * [\"text\", \"↵            \"]\n * [\"/\", \"stu\"]\n * [\"text\", \"↵          \"]\n * [\"name\", \"item.name\"]\n * [\"text\", \"↵        \"]\n * [\"/\", \"students\"]\n * [\"text\", \"↵    \"]\n *\n * /
      \n\n

      此时studentsstu都是#标记,我们需要利用算法处理他们的嵌套结构,处理成大约如下这样的结构:

      \n
      /**\n *\n *  [\"text\", \"↵      哈哈哈↵        \"]\n *  Array(3)\n *  \"#\"\n *  \"students\"\n *  Array(5)\n *      [\"text\", \"↵            \"]\n *      [\"#\", \"stu\", Array(9)]\n *      [\"text\", \"↵          \"]\n *      [\"name\", \"item.name\"]\n *      [\"text\", \"↵        \"]\n *  [\"text\", \"↵    \"]\n *\n * /
      \n\n

      常用工具类

      递归

      /**\n * {string} dir 递归根目录\n * {object} list 暂存参数\n */\nconst deep = async (dir, list = []) => {\n  const dirs = await fs.promises.readdir(dir)\n  for (let i = 0; i < dirs.length; i++) {\n    const item = dirs[i]\n    const itemPath = path.join(dir, item)\n    const isDir = fs.statSync(itemPath).isDirectory()\n    isDir ? await deep(itemPath, list) : list.push(itemPath)\n  }\n  return list\n}
      \n\n

      自增id短码

      用于连接分享

      \n
      const createAscString = (id) => {\n  const dictionary = [\n    \"0123456789\",\n    \"abcdefghigklmnopqrstuvwxyz\",\n    \"ABCDEFGHIGKLMNOPQRSTUVWXYZ\",\n  ];\n  let chars = dictionary.join(\"\").split(\"\"),\n    radix = chars.length,\n    qutient = 1000 * 1000 * 9999 + +id,\n    arr = [];\n  while (qutient) {\n    mod = qutient % radix;\n    qutient = (qutient - mod) / radix;\n    arr.unshift(chars[mod]);\n  }\n  return arr.join(\"\");\n};\n\nconsole.log(createAscString(100000000));
      \n\n

      手动实现 eventBus

      export default class EventBus {\n  constructor() {\n    // key-value : eventName-date\n    this.callbacks = {};\n  }\n\n  /**\n   * 监听事件\n   * @param {事件名} eventName\n   * @param {回调函数} callback\n   */\n  on(eventName, callback) {\n    this.checkType(eventName).callbacks[eventName]\n      ? callback(this.callbacks[eventName])\n      : this.error(`The event has not been declared`);\n  }\n\n  /**\n   * 注册一个事件\n   * @param {事件名} eventName\n   * @param {传递的对象} data\n   */\n  emit(eventName, data) {\n    this.checkType(eventName).callbacks[eventName] = data;\n  }\n\n  /**\n   * 注销事件,不传参数默认注销全部事件\n   * @param {事件名} eventName\n   */\n  off(eventName) {\n    eventName\n      ? this.checkType(eventName).removeEvent(eventName)\n      : this.emptyEvent();\n  }\n\n  /**\n   * 移出事件\n   * @param {事件名} eventName\n   */\n  removeEvent(eventName) {\n    Reflect.deleteProperty(this.callbacks, eventName);\n  }\n\n  /**\n   * 清空全部事件\n   */\n  emptyEvent() {\n    this.callbacks = [];\n  }\n\n  /**\n   * 参数类型校验\n   * @param {参数} param\n   * @param {合法的类型} validType\n   */\n  checkType(param, validType = \"string\") {\n    if (typeof param !== validType)\n      this.error(`(param, ${param}) should be of ${validType} type`);\n    return this; // 缅怀jQ链式调用\n  }\n\n  /**\n   * 错误提示\n   * @param {提示文字} text\n   */\n  error(text) {\n    throw new Error(text);\n  }\n}
      \n\n

      调用

      \n
      // 省略import\nconst eventBus = new EventBus();\neventBus.emit(\"login\", [{ a: 1, d: 2 }]);\neventBus.on(123, (d) => console.log(d)); // [{...}]
      \n\n

      判断对象是否有某个 key

      let obj = { alias: \"es6\" };\n\"alias\" in obj; // true\nReflect.has(obj, \"alias\"); // true
      \n\n

      浏览器

      版本信息

      window.navigator.userAgent;
      \n\n

      兼容事件绑定

      /*\n兼容低版本IE,ele为需要绑定事件的元素,\neventName为事件名(保持addEventListener语法,去掉on),fun为事件响应函数\n*/\n\nfunction addEvent(ele, eventName, fun) {\n  ele.addEventListener\n    ? ele.addEventListener(eventName, fun, false)\n    : ele.attachEvent(\"on\" + eventNme, fun);\n}
      \n\n

      数组对象

      reduce

      var  arr = [1, 2, 3, 4];\nvar sum = arr.reduce(function(prev, cur, index, arr) {\n    console.log(prev, cur, index);\n    return prev + cur;\n}0) //注意这里设置了初始值\nconsole.log(arr, sum);\n\n// 求和\nconst sum = arr.reduce((p,c) => p+c)
      \n\n

      对象内部根据 key 对 value 进行排序,取前 3

      let datasource = [\n  { price: 1, alias: \"watermelon\" },\n  { price: 3, alias: \"orange\" },\n  { price: 2, alias: \"banana\" },\n  { price: 4, alias: \"apple\" },\n];\n// 降序排列\nlet compare = (key) => (a, b) => b[key] - a[key];\nlet sorted = datasource\n  .sort(compare(\"price\"))\n  .slice(0, 3)\n  .map((i) => i[\"alias\"]);\n// 返回 [\"apple\", \"orange\", \"banana\"]
      \n\n

      随机字符串

      const getRandomRangeNum = (len = 32) => {\n  // 略去不宜辨识字符\n  let dictionary = \"ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz\";\n  let maxPos = dictionary.length;\n  let res = \"\";\n  for (let i = 0; i < len; i++) {\n    res += dictionary.charAt(Math.floor(Math.random() * maxPos));\n  }\n  return res;\n};
      \n\n

      类型检测

      Q:使用typeof foo === "object"检测foo是否为对象有什么缺点?如何避免?

      \n

      A:用 typeof 是否能准确判断一个对象变量,答案是否定的,null 的结果也是 objectArray 的结果也是 object,有时候我们需要的是 “纯粹” 的 object 对象

      \n
      Object.prototype.toString.call(obj) === \"[object Object]\";
      \n\n

      倒计时

      class Countdown {\n  constructor(startNum, endNum, interval) {\n    [this.startNum, this.endNum, this.interval] = [startNum, endNum, interval];\n  }\n  execute() {\n    var timer = setTimeout(() => {\n      if (this.startNum >= this.endNum) {\n        console.log(this.startNum);\n        this.startNum -= 1;\n        this.execute();\n      } else {\n        clearTimeout(timer);\n      }\n    }, this.interval);\n  }\n}\n// 实例化调用\nvar countdown = new Countdown(5, 0, 1000).execute();
      \n\n

      范围随机数

      // 能取到 min,取不到 max\nfunction getRandomRangeNum(min, max) {\n  return min + Math.floor(Math.random() * (max - min));\n}
      \n\n

      获取当前月的天数

      const getCurMonthDays = new Date(\n  new Date().getFullYear(),\n  new Date().getMonth() + 1,\n  0\n).getDate();
      \n\n\n

      对象小操作

      去虚假值

      let arr4 = [\"小明\", \"小蓝\", \"\", false, \" \", undefined, null, 0, NaN, true];\nconsole.log(arr4.filter(Boolean)); // => ['小明', '小蓝', ' ', true]
      \n\n

      头尾插入

      效率比 unshift()

      \n
      let arr = [1, 2, 3];\n// 头插入\n[\"haha\"].concat(arr);\n// 尾插入\narr.concat([\"haha\"]);
      \n\n\n

      删除属性

      function deleteA(obj) {\n  delete obj.A;\n  return obj;\n}\n\n// 使用解构赋值\nconst deleteA = ({ A, ...rest } = {}) => rest;
      \n\n

      生产、加工、消费分离

        \n
      • 从接口拿数据到视图 fetch api
      • \n
      • 加工 computed
      • \n
      • 消费 v-for
      • \n
      \n

      元数据

      import \"reflect-metadata\"; // npm install reflect-metadata\n\nfunction Role(name: string): ClassDecorator {\n  return (target) => {\n    Reflect.defineMetadata(\"role\", name, target);\n  };\n}\n\n@Role(\"admin\")\nclass Post {}\n\nconst metadata = Reflect.getMetadata(\"role\", Post);\n\nReflect.set(Post, \"role2\", metadata);\n\nconsole.log(Reflect.get(Post, \"role2\")); // admin
      \n\n

      防抖与节流

      在页面上监听诸如scroll(页面滚动),mousemove(鼠标移动) ,keydownkeyupkeypress(按下键盘)等等一系列事件的时候,我们并不希望频繁的触发这类监听,尤其当请求非常消耗资源时,这种操作会导致服务器性能急剧下降。

      \n

      Debounce

      把触发非常频繁的事件合并成一次延迟执行,如果对监听函数使用 100ms 的容忍时间,那么时间在第 3.1s 的时候执行

      \n
      // 默认延时100ms\nfunction debounce(func, dealy = 100) {\n  let timer;\n  return function () {\n    // 暂存this和参数\n    let _this = this;\n    let args = arguments;\n    // 清除定时器,确保不执行func\n    clearTimeout(timer);\n    timer = setTimeout(function () {\n      func.apply(_this, args);\n    }, dealy);\n  };\n}\n// 执行函数\nfunction handler() {\n  console.log(`delay 100ms ,then handle`);\n}\n// dom添加监听\ndocument\n  .querySelector(\"#someNode\")\n  .addEventListener(\"scroll\", debounce(handler));
      \n\n

      Throttle

      固定函数执行的速率,即所谓的“节流”。设置一个阀值,在阀值内,把触发的事件合并成一次执行;当到达阀值,必定执行一次事件。

      \n
      function throttle(func, delay) {\n  let statTime = 0;\n  return function () {\n    let currentTime = +new Date();\n    if (currentTime - statTime > delay) {\n      func.apply(this, arguments);\n      statTime = currentTime;\n    }\n  };\n}\n// 执行函数\nfunction resizeHandler() {\n  console.log(`resize`);\n}\n// window添加监听\nwindow.onresize = throttle(resizeHandler, 300);
      \n\n

      this 指向

      全局环境

      全局环境下,this 始终指向全局对象(window),无论是否严格模式

      \n
      console.log(this === window); // true\nthis.a = 37;\nconsole.log(window.a); // 37
      \n\n

      函数上下文调用

        \n
      • 非严格模式
      • \n
      \n

      没有被上一级的对象所调用, this 默认指向全局对象 window

      \n
      function f1() {\n  return this;\n}\nf1() === window; // true
      \n\n
        \n
      • 严格模式
      • \n
      \n

      this 指向 undefined

      \n
      function f2() {\n  \"use strict\"; // 这里是严格模式\n  return this;\n}\nf2() === undefined; // true
      \n\n

      箭头函数

      \n

      箭头函数中,call()、apply()、bind()方法无效

      \n
      \n

      在全局代码中,箭头函数被设置为全局对象,总之箭头函数不改变 this 指向

      \n
      var globalObject = this;\nvar foo = () => this;\nconsole.log(foo() === globalObject); // true
      \n\n

      箭头函数作为对象的方法使用,指向全局 window 对象

      \n
      var obj = {\n  i: 10,\n  b: () => console.log(this.i, this),\n  c: function () {\n    console.log(this.i, this);\n  },\n};\nobj.b(); // undefined window{...}\nobj.c(); // 10 Object {...}
      \n\n

      箭头函数可以让 this 指向固化,这种特性很有利于封装回调函数

      \n
      // 总是指向 handler 对象。如果不使用箭头函数则指向全局 document 对象\nvar handler = {\n  id: \"123456\",\n\n  init: function () {\n    document.addEventListener(\n      \"click\",\n      (event) => this.doSomething(event.type),\n      false\n    );\n  },\n\n  doSomething: function (type) {\n    console.log(\"Handling \" + type + \" for \" + this.id);\n  },\n};
      \n\n

      call, apply, bind 与 es6

      js 的函数继承于Function.prototype对象,因此每个函数都会有 apply、call、bind 方法

      \n
      \n

      call 和 apply 的作用,完全一样,唯一的区别就是在参数上面。

      \n
      \n

      call, apply, bind改变函数中 this 指向 的三兄弟,把this绑定到第一个参数对象上

      \n
      function displayHobbies(...hobbies) {\n  console.log(`${this.name} likes ${hobbies.join(\", \")}.`);\n}\n// 下面两个等价\ndisplayHobbies.call({ name: \"Bob\" }, \"swimming\", \"basketball\", \"anime\"); // Bob likes swimming, basketball, anime.\ndisplayHobbies.apply({ name: \"Bob\" }, [\"swimming\", \"basketball\", \"anime\"]); // Bob likes swimming, basketball, anime.
      \n\n

      bind返回的是一个函数,需要手动执行

      \n
      var p1 = {\n  name: \"张三\",\n  age: 12,\n  func: function () {\n    console.log(`姓名:${this.name},年龄:${this.age}`);\n  },\n};\n\nvar p2 = {\n  name: \"李四\",\n  age: 15,\n};\n\np1.func.bind(p2)(); //姓名:李四,年龄:15
      \n\n

      for 循环优化

      // 每次都要计算array.length\nfor (let i = 0; i < array.length; i++) {\n  console.log(i);\n}\n\n// 使用leng缓存array长度\nfor (let i = 0, length = array.length; i < length; i++) {\n  console.log(i);\n}
      \n\n

      数组

      扁平化去重升序排列

      let arr = [[1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10];\narr.flat(Infinity); // [1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9, 11, 12, 12, 13, 14, 10]\n\nlet result = Array.from(new Set(arr.flat(Infinity)).sort((a, b) => a - b));
      \n\n

      前端页面埋点 - 1x1.gif

      通常用在,统计页面点击,曝光,停留时间,签发……等场景

      \n
        \n
      • 比 PNG/JPG 体积小
      • \n
      • 天然跨域
      • \n
      \n
      <button onClick=\"countClick()\">haorooms</button>\n<script>\n  function countClick() {\n    new Image().src = `./haorooms.gif?${key}=${value}&${Math.random()} `;\n  }\n</script>
      \n\n

      事件委托

      利用冒泡原理,委托父元素执行

      \n
      <ul>\n  <li>苹果</li>\n  <li>香蕉</li>\n  <li>凤梨</li>\n</ul>
      \n\n
      document.querySelector(\"ul\").onclick = (event) => {\n  let target = event.target;\n  if (target.nodeName === \"LI\") {\n    console.log(target.innerHTML);\n  }\n};
      \n\n

      构造函数 + 原型模式

      function Person(name, age, job) {\n  this.name = name;\n  this.age = age;\n  this.job = job;\n}\nPerson.prototype.say = function (text) {\n  console.log(`${this.name}say:${text}`);\n};
      \n\n

      剩余参数…args

      剩余参数args数个数组,...解构符

      \n
      function fun1(param, ...args) {\n  alert(args.length);\n}
      \n\n

      跨页面通信

        \n
      • cookie
      • \n
      • web worker
      • \n
      • localstorage
      • \n
      \n

      iframe 跨域通信和不跨域通信

      不跨域

      // fatherSay是父页面全局方法\nwindow.parent.fatherSay();\n// 父页面Dom\nwindow.parent.document.getElementById(\"元素id\");\n// 副业页面获取frameID为`iframe_ID`的子页面的Dom\nwindow.frames[\"iframe_ID\"].document.getElementById(\"元素id\");
      \n\n

      跨域 postMessage

      子页面

      \n
      window.parent.postMessage(\"hello\", \"http://127.0.0.1:8089\");
      \n\n

      父页面接受

      \n
      window.addEventListener(\"message\", function (event) {\n  alert(123);\n});
      \n\n

      对象类型判断

      数组

      let arr = [];\narr instanceof Array; // true\nArray.isArray(arr); // true\nObject.prototype.toString.call(arr); // \"[object Array]\"
      \n\n

      js 单线程,如何异步

        \n
      • 主线程 执行 js 中所有的代码。

        \n
      • \n
      • 主线程 在执行过程中发现了需要异步的任务任务后扔给浏览器(浏览器创建多个线程执行,顺便创造一个回调队列

        \n
      • \n
      • 主线程 已经执行完毕所有同步代码。监听回调队列一旦 浏览器 中某个线程任务完成将会改变回调函数的状态。主线程查看到某个函数的状态为已完成,就会执行该回调队列中对应的回调函数。

        \n
      • \n
      \n

      移动端最小触控区域

      苹果推荐是 44pt x 44pt 「具体看 WWDC 14」,通过paddingmarginheight等方式进行点击区域扩展

      \n

      js 精度问题

      常用类库:Math.jsBig.jsdecimal.js

      \n

      冻结 Object. freeze()

      const生命的简单变量不可修改,但是复杂对象可以被修改,Object. freeze():可以冻结对象

      \n
        \n
      • 不能添加新属性
      • \n
      • 不能删除已有属性
      • \n
      • 不能修改已有属性的可枚举性、可配置性、可写性
      • \n
      • 不能修改已有属性的值
      • \n
      • 不能修改原型
      • \n
      \n

      浅冻结

      \n
      const obj1 = {\n  internal: {},\n};\n\nObject.freeze(obj1);\nobj1.internal.a = \"aValue\";\nconsole.log(obj1.internal.a); // aValue
      \n\n

      递归冻结

      \n
      function deepFreeze(obj) {\n  // 获取定义在obj上的属性名\n  var propNames = Object.getOwnPropertyNames(obj);\n  // 在冻结自身之前冻结属性\n  propNames.forEach(function (name) {\n    var prop = obj[name];\n    // 如果prop是个对象,冻结它\n    if (typeof prop == \"object\" && prop !== null) deepFreeze(prop);\n  });\n  return Object.freeze(obj);\n}
      \n\n

      Reflect

      Reflect.get(target, propertyKey, value[receiver])

      获取对象身上某个属性的值,类似于 target[name]。

      \n

      Reflect.set(target, propertyKey, value[receiver])

      将值分配给属性的函数。返回一个 Boolean,如果更新成功,则返回 true。

      \n

      Reflect.has(target, propertyKey)

      判断一个对象是否存在某个属性,和 in 运算符 的功能完全相同。

      \n

      if else 优化

      表驱动编程

      空间换时间,设置obj={key:value},通过obj[key]取值

      \n
      calculateGrade(score){\n    const table = {\n        100: 'A',\n        90: 'A',\n        80: 'B',\n        70: 'C',\n        60: 'D',\n        others: 'E'\n    }\n    return table[Math.floor(score/10)*10] || table['others']\n}
      \n\n

      短路运算

      react 没有v-if,运用比较频繁

      \n
      // 函数组件\nconst Home = () => {\n  return <div>home</div>;\n};\n{\n  true && <Home />;\n}
      \n\n

      使用有意义且易读的变量名

      👎 const yyyymmdstr = moment().format(\"YYYY/MM/DD\");\n\n👍 const currentDate = moment().format(\"YYYY/MM/DD\");
      \n\n

      使用有意义的变量代替数组下标

      👎\nconst address = \"One Infinite Loop, Cupertino 95014\";\nconst cityZipCodeRegex = /^[^,\\\\]+[,\\\\\\s]+(.+?)\\s*(\\d{5})?$/;\nsaveCityZipCode(\n  address.match(cityZipCodeRegex)[1],\n  address.match(cityZipCodeRegex)[2]\n);\n\n👍\nconst address = \"One Infinite Loop, Cupertino 95014\";\nconst cityZipCodeRegex = /^[^,\\\\]+[,\\\\\\s]+(.+?)\\s*(\\d{5})?$/;\nconst [_, city, zipCode] = address.match(cityZipCodeRegex) || [];\nsaveCityZipCode(city, zipCode);
      \n\n

      变量名要简洁

      👎\nconst Car = {\n  carMake: \"Honda\",\n  carModel: \"Accord\",\n  carColor: \"Blue\"\n};\nfunction paintCar(car, color) {\n  car.carColor = color;\n}\n\n👍\nconst Car = {\n  make: \"Honda\",\n  model: \"Accord\",\n  color: \"Blue\"\n};\nfunction paintCar(car, color) {\n  car.color = color;\n}
      \n\n

      消除魔术字符串

      👎 setTimeout(blastOff, 86400000);\n\n👍 const MILLISECONDS_PER_DAY = 60 * 60 * 24 * 1000; //86400000;\nsetTimeout(blastOff, MILLISECONDS_PER_DAY);
      \n\n

      使用默认参数替代短路运算符

      👎\nfunction createMicrobrewery(name) {\n  const breweryName = name || \"Hipster Brew Co.\";\n  // ...\n}\n\n👍\nfunction createMicrobrewery(name = \"Hipster Brew Co.\") {\n  // ...\n}
      \n\n

      一个函数

      👎\nfunction emailClients(clients) {\n  clients.forEach(client => {\n    const clientRecord = database.lookup(client);\n    if (clientRecord.isActive()) {\n      email(client);\n    }\n  });\n}\n\n👍\nfunction emailActiveClients(clients) {\n  clients.filter(isActiveClient).forEach(email);\n}\nfunction isActiveClient(client) {\n  const clientRecord = database.lookup(client);\n  return clientRecord.isActive();\n}\n\n---------------------分割线-----------------------\n\n👎\nfunction createFile(name, temp) {\n  if (temp) {\n    fs.create(`./temp/${name}`);\n  } else {\n    fs.create(name);\n  }\n}\n\n👍\nfunction createFile(name) {\n  fs.create(name);\n}\nfunction createTempFile(name) {\n  createFile(`./temp/${name}`);\n}
      \n\n

      函数参数不多于 2 个,如果有很多参数就利用 object 传递,并使用解构

      👎\nfunction createMenu(title, body, buttonText, cancellable) {\n  // ...\n}\ncreateMenu(\"Foo\", \"Bar\", \"Baz\", true);\n\n👍\nfunction createMenu({ title, body, buttonText, cancellable }) {\n  // ...\n}\ncreateMenu({\n  title: \"Foo\",\n  body: \"Bar\",\n  buttonText: \"Baz\",\n  cancellable: true\n});
      \n\n

      函数名应该直接反映函数的作用

      👎\nfunction addToDate(date, month) {\n  // ...\n}\nconst date = new Date();\n// It's hard to tell from the function name what is added\naddToDate(date, 1);\n\n👍\nfunction addMonthToDate(month, date) {\n  // ...\n}\nconst date = new Date();\naddMonthToDate(1, date);
      \n\n

      尽量使用纯函数

      👎\nconst programmerOutput = [\n  {\n    name: \"Uncle Bobby\",\n    linesOfCode: 500\n  },\n  {\n    name: \"Suzie Q\",\n    linesOfCode: 1500\n  },\n  {\n    name: \"Jimmy Gosling\",\n    linesOfCode: 150\n  },\n  {\n    name: \"Gracie Hopper\",\n    linesOfCode: 1000\n  }\n];\nlet totalOutput = 0;\nfor (let i = 0; i < programmerOutput.length; i++) {\n  totalOutput += programmerOutput[i].linesOfCode;\n}\n\n👍\nconst programmerOutput = [\n  {\n    name: \"Uncle Bobby\",\n    linesOfCode: 500\n  },\n  {\n    name: \"Suzie Q\",\n    linesOfCode: 1500\n  },\n  {\n    name: \"Jimmy Gosling\",\n    linesOfCode: 150\n  },\n  {\n    name: \"Gracie Hopper\",\n    linesOfCode: 1000\n  }\n];\nconst totalOutput = programmerOutput.reduce(\n  (totalLines, output) => totalLines + output.linesOfCode,\n  0\n);
      \n\n

      不要过度优化

      👎\n// 现代浏览器对于迭代器做了内部优化\nfor (let i = 0, len = list.length; i < len; i++) {\n  // ...\n}\n\n👍\nfor (let i = 0; i < list.length; i++) {\n  // ...\n}
      \n\n\n

      关于模块化

      无模块化

      \n

      污染全局作用域、维护成本高、依赖关系不明显

      \n
      \n

      script 标签引入 js 文件,相互罗列,但是被依赖的放在前面,否则使用就会报错。如下:

      \n
      <script src=\"jquery.js\"></script>\n<script src=\"main.js\"></script>\n<script src=\"other1.js\"></script>\n<script src=\"other2.js\"></script>\n<script src=\"other3.js\"></script>
      \n\n

      CommonJS 规范(同步)

      该规范最初是用在服务器端的 node 的,它有四个重要的环境变量为模块化的实现提供支持:module、exports、require、global。实际使用时,用 module.exports 定义当前模块对外输出的接口(不推荐直接用 exports),用 require 加载模块(同步)

      \n
      \n

      module.exports 本身就是一个对象

      \n
      \n
      module.exports = { foo: \"bar\" }; //true\nmodule.exports.foo = \"bar\"; //true。
      \n\n

      CommonJS 用同步的方式加载模块。在服务端,模块文件都存在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,限于网络原因,CommonJS 不适合浏览器端模块加载,更合理的方案是使用异步加载,比如下边 AMD 规范。

      \n

      AMD 规范(RequireJS)

      承接上文,AMD 规范则是非同步加载模块,允许指定回调函数,AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。

      \n
        \n
      • require([module], callback):加载模块
      • \n
      • define(id, [depends], callback):定义模块
      • \n
      • require.config():配置路径、依赖关系
      • \n
      \n

      CMD 规范(SeaJS)

      CMD 是 在推广过程中对模块定义的规范化产出

      \n

      ES6 import/export

      通过 babel 将不被支持的 import 编译为当前受到广泛支持的 AMD 规范

      \n

      AMD模块化实现

      define 函数

      用来声明模块名module,依赖数组deps,以及模块的作用callback

      \n
      var module = (function () {\n  var stack = {}; //模块存储栈,存的是模块执行后的结果\n\n  function define(module, deps, callback) {\n    stack[module] = callback.apply(null, deps); // 压栈\n    console.log(stack); // 查看栈存储的模块\n  }\n\n  return { define: define };\n})();
      \n\n

      调用define试试,发现 calc模块被压入了stack 模块栈中

      \n
      module.define(\"calc\", [], function () {\n  return {\n    first: function (arr) {\n      return arr[0];\n    },\n  };\n});\n// {calc: { first: ƒ }}
      \n\n

      deps 依赖(导入)

      对上面的 define函数稍加改造,这一步过程的本质,就是对 deps

      \n
      var module = (function (window) {\n  var stack = {}; //模块存储栈,\n\n  function define(module, deps, callback) {\n    // 把 define 函数依赖数组中的模块从 stack 模块中拿出\n    deps.map(function (mod, index) {\n      deps[index] = stack[mod]; // 赋值给当前模块的 deps\n    });\n    stack[module] = callback.apply(null, deps);\n    console.log(stack); // 查看栈存储的模块\n  }\n\n  return { define: define };\n})(window);\n\nmodule.define(\"calc\", [], function () {\n  return {\n    first: function (arr) {\n      return arr[0];\n    },\n  };\n});\n\nmodule.define(\"number\", [\"calc\"], function (calc) {\n  return {\n    res: calc.first([4, 3, 2, 1]), //4\n  };\n});\n\n// 此时 stack 打印的结果为\n//> calc: {first: ƒ}\n//> number: {res: 4}
      \n\n
      \n

      从本质上看,define 函数的第二个参数 deps 数组,相当于 import 导入,并且如果第三个参数 callback 采用 return { },也就相当于 export 导出

      \n
      \n

      老生常谈的指针

      说到底,stacka模块 export 是一个指针,{a:value}(内存地址),所以,b 模块会改变a.a的值,这点和 cmd不同

      \n
      module.define(\"a\", [], function () {\n  return {\n    a: 1,\n  };\n});\nmodule.define(\"b\", [\"a\"], function (a) {\n  a.a = 2;\n});\nmodule.define(\"c\", [\"a\"], function (a) {\n  console.log(a.a); // 2\n});
      \n\n\n

      缘起 Object.defineProperty()

      目标对象上定义一个新属性,或者修改目标对象属性,并且返回新对象

      \n

      上帝的钥匙 get & set

      属性的getter函数,如果没有 getter则尾 undefined。当访问该属性时,会调用此函数.

      \n
      let obj = {};\n\nObject.defineProperty(obj, \"a\", {\n  get() {\n    return 7;\n  },\n  set(val) {\n    console.log(`FAILED!改变 a 属性,新值为:${val},但是被重写 set 劫持了`);\n  },\n});\n\nconsole.log(obj.a);\nobj.a = 4;
      \n\n
      \n

      上帝的钥匙被找到了

      \n
      \n

      劫持!劫持!还是 TMD 劫持!

      \n
      let obj = {};\nlet tempValue = 0;\n\nObject.defineProperty(obj, \"a\", {\n  get() {\n    return tempValue;\n  },\n  set(newValue) {\n    tempValue = newValue;\n  },\n});\n\nconsole.log(obj.a); // 0\nobj.a = 4;\nconsole.log(obj.a); // 4
      \n\n

      封装 defineReactive(obj, prop, val)

      \n

      这里 defineReactive 第三个参数 val 替代了上一步中全局变量tempValue,对于 get()、set()来说,访问到了其他函数内部的变量,所以形成了闭包

      \n
      \n
      function defineReactive(obj, prop, val) {\n  Object.defineProperty(obj, prop, {\n    get() {\n      console.log(`劫持,你访问了${prop}属性`);\n      return val;\n    },\n    set(newValue) {\n      if (val === newValue) return;\n      console.log(`劫持,你改变了${prop}属性`);\n      val = newValue;\n    },\n  });\n}\n\nlet obj = {};\ndefineReactive(obj, \"a\", 4);\n\nconsole.log(obj.a); // 劫持,你访问了a属性 4\nobj.a = 7; // 劫持,你改变了a属性\nconsole.log(obj.a); // 劫持,你访问了a属性 7
      \n\n

      递归侦测

      let obj = { a: { b: { c: {} } } };\ndefineReactive(obj, \"a\", 4);
      \n\n
      \n

      如何自动让obj对象的全部属性都reactive呢?

      \n
      \n
      let obj = {\n  a: {\n    b: {\n      c: 5,\n    },\n  },\n  d: 4,\n};
      \n\n

      定义个方法observe,递归 obj 的每一层的每个 prop,检测是否有__ob__,如果没有,defineReactive,并且挂一个Observer实例在这个props上,例如:

      \n

      Observer对象

      \n
      class Observer {\n  constructor(value) {\n    def(value, \"__ob__\", this, false); // 不可枚举,不能给__ob__添加__ob__\n    this.walk(value);\n  }\n  // 遍历每一个 prop的 value\n  walk(value) {\n    for (let prop in value) {\n      defineReactive(value, prop);\n    }\n  }\n}
      \n\n

      defineReactive.js

      \n
      export default function defineReactive(obj, prop, val) {\n  if (arguments.length === 2) val = obj[prop]; // 如果2个参数\n  let childNode = observe(val);\n  Object.defineProperty(obj, prop, {\n    enumerable: true,\n    configurable: true,\n    get() {\n      console.log(`你访问了${prop}属性`);\n      return val;\n    },\n    set(newValue) {\n      if (val === newValue) return;\n      console.log(`你改变了${prop}属性`);\n      val = newValue;\n      childNode = observe(newValue);\n    },\n  });\n}
      \n\n

      observe.js

      \n
      /**\n * 检测 obj 身上有没有 __ob__(Observer 实例)\n * @param {*} value\n * @returns\n */\nexport default function observe(value) {\n  if (typeof value != \"object\") return;\n  let ob;\n  //! 用__ob__是为了属性不重名,被覆盖\n  if (typeof value.__ob__ != \"undefined\") {\n    ob = value.__ob__;\n  } else {\n    ob = new Observer(value);\n  }\n  return ob;\n}
      \n","site":{"data":{}},"excerpt":"","more":"

      模板引擎

      严格的模板引擎的定义,输入模板字符串 + 数据,得到渲染过的字符串。实现上,从正则替换到拼 function 字符串到正经的 AST 解析各种各样,但从定义上来说都是差不多的。字符串渲染的性能其实也就在后端比较有意义,毕竟每一次渲染都是在消耗服务器资源,但在前端,用户只有一个,几十毫秒的渲染时间跟请求延迟比起来根本不算瓶颈。倒是前端的后续更新是字符串模板引擎的软肋,因为用渲染出来的字符串整个替换 innerHTML 是一个效率很低的更新方式。所以这样的模板引擎如今在纯前端情境下已经不再是好的选择,意义更多是在于方便前后端共用模板。

      \n

      古老数据渲染 vm 的方式

      这种写法,弊端太多了,玩具车

      \n
      <!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Document</title>\n  </head>\n  <body>\n    <ul id=\"app\"></ul>\n  </body>\n  <script>\n    var arr = [\n      { name: \"小明\", age: 11, sex: \"男\" },\n      { name: \"小红\", age: 22, sex: \"女\" },\n    ];\n    var listDOM = document.getElementById(\"app\");\n    arr.forEach(function (item) {\n      let _li = document.createElement(\"li\");\n      _li.innerText = item.name;\n      listDOM.appendChild(_li);\n    });\n  </script>\n</html>
      \n\n

      mustache 原理

        \n
      • 1、先把模板字符串编译成 tokens(代号)
      • \n
      • 2、根据 tokens,结合数据渲染成 dom
      • \n
      \n
      \n

      本质上,tokens 是一个 js 嵌套数组没事模板字符串 js 的表示,他是抽象语法树虚拟节点的开山鼻祖

      \n
      \n

      假设有这么一个模板字符串

      \n
      <h1>我买了一个{{thing}},好{{mood}}啊</h1>
      \n\n

      会编译成 tokens,如下:

      \n
      // 这里面每一个数组行都是一个 token,组起来就是 tokens\n// html 标签也会被看成纯文本\n[\n  [\"text\", \"<h1>我买了一个\"],\n  [\"name\", \"thing\"],\n  [\"text\", \"好\"],\n  [\"name\", \"mood\"],\n  [\"text\", \"啊</h1>\"],\n];
      \n\n

      当模板存在循环式,带层级嵌套,如下:

      \n
      <div>\n  <ul>\n    {{#arr}}\n    <li>{{.}}</li>\n    {{/arr}}\n  </ul>\n</div>
      \n\n

      会被编译成

      \n
      [\n  [\"text\", \"<div><ul>\"],\n  [\n    \"#\",\n    \"arr\",\n    [\n      [\"text\", \"li\"],\n      [\"name\", \".\"],\n      [\"text\", \"</li>\"],\n    ],\n  ],\n  [\"text\", \"</ul></div>\"],\n];
      \n\n

      如果是双重循环,带层级嵌套继续加一层,例如:

      \n
      <div>\n  <ol>\n    {{#students}}\n    <li>\n      学生{{item.name}}的爱好是\n      <ol>\n        {{#item.hobbies}}\n        <li>{{.}}</li>\n        {{/#item.hobbies}}\n      </ol>\n    </li>\n    {{/#students}}\n  </ol>\n</div>
      \n\n

      会被编译成

      \n
      [\n  [\"text\", \"<div><ol>\"],\n  [\n    \"#\",\n    \"students\",\n    null,\n    null,\n    [[\"text\", \"<li>学生\"], [\"name\", \"name\"], [\"text\", \"的爱好是<ol>\"], [\"#\", \"hobbies\", null, null], [\n        ['text','<li>'],\n        ['name','.'],\n        ['text','</li>']\n    ]],\n    ['text','<ol></li>'],\n  ]],\n  ['text','</ol></div>']\n  ],\n];
      \n\n
      \n

      mustache.js中完成上述这一过程的函数parseTemplate,可以去找源代码看

      \n
      \n

      tokens 生成算法

      用简单的模板字符串举例:

      \n
      我买了一个{{thing}},好{{mood}}啊
      \n\n

      有一个指针往右遍历,从开始,遍历到结束,如下:

      \n
      /**\n *\n * 我买了一个{{thing}},好{{mood}}啊\n * ↑\n *\n * Step1:指针位置 = 1\n *\n * 我买了一个{{thing}},好{{mood}}啊\n *   ↑\n *\n * Step2:指针位置 = 2\n *\n * 我买了一个{{thing}},好{{mood}}啊\n *     ↑\n *\n * Step3:指针位置 = 4\n *\n * 我买了一个{{thing}},好{{mood}}啊\n *        ↑\n *\n * Step4:指针位置 = 11\n *\n * 我买了一个{{thing}},好{{mood}}啊\n *               ↑\n *\n * Step5:指针位置 = 15\n *\n * 我买了一个{{thing}},好{{mood}}啊\n *                    ↑\n *\n * ... while(指针位置 >= 模板字符串.length)\n *\n * /
      \n\n
        \n
      • Step1:
      • \n
      \n

      指针右移 1 个长度,以指针位置切割,字符串被分成+买了一个{{thing}},好{{mood}}啊

      \n
        \n
      • Step2:
      • \n
      \n

      指针右移 1 个长度,以指针位置切割,字符串被分成我买+了一个{{thing}},好{{mood}}啊

      \n
        \n
      • Step3:
      • \n
      \n

      第一次遇到,通过 indexOf("{{\") == 0` 判断\n\n

      // 标记为 text 放到 tokens 中\ntoken.push([\"text\", \"我买了一个\"]); // tokens: [['text', '我买了一个']]
      \n\n结束,此时指针位置 = 4\n\n- Step4:\n\n指针右移 2 个长度,跳过`{{`,暂存此时`pos_last = 6`\n\n此时,右边字符串(尾字符串)`thing}},好{{mood}}啊

      \n

      右移 5 个长度,识别模板内部数据对象

      \n
      substring(post_last, 6 + 5); // thing  5个长度\n// 标记为 name 放到 tokens 中\ntoken.push([\"name\", \"thing\"]); // tokens: [['text', '我买了一个']], ['name', 'thing']]
      \n\n

      结束,此时指针位置 = 11

      \n
        \n
      • Step5:
      • \n
      \n
      \n

      遇到 }},通过 indexOf("}}") == 0判断

      \n
      \n

      指针右移 2 个长度,跳过}},暂存此时post_last = 13,继续右移 2 个长度

      \n
      substring(post_last, 13 + 2); // ,好  2个长度\n// 标记为 text 放到 tokens 中\ntoken.push([\"text\", \",好\"]); // tokens: [['text', '我买了一个']], ['name', 'thing'],['text', ',好' ]]
      \n\n
      \n

      第二次,遇到 {{`\n\n剩下循环执行就行了,这个过程,我们可以称作`扫描 Scan`\n\n## 扫描器 Scanner\n\n新建一个 `Scanner.js`,用来扫描模板字符串,实现上面的原理\n\n

      /**\n * 模板字符串扫描器\n */\n\nclass Scanner {\n  constructor(templ) {\n    this.templ = templ; // 模板字符串\n    this.tail = templ; // 尾字符串\n    this.pPos = 0; // 指针位置\n  }\n\n  /**\n   *  指针跳过模板标签\n   * @param {模板语法包围标签} tag\n   */\n  jumpTag(tag) {\n    if (this.tail.indexOf(tag) === 0) {\n      this.pPos += tag.length; // 指针右移 tag.length 个长度\n      this.tail = this.templ.substring(this.pPos); // 尾字符串更新\n    }\n  }\n\n  /**\n   * 指针遇见模板标签 {{\n   * @param {模板语法包围标签} tag\n   */\n  missTag(tag) {\n    let pPos_last = this.pPos;\n    while (!this.eof() && this.tail.indexOf(tag) !== 0) {\n      this.pPos++;\n      this.tail = this.templ.substring(this.pPos);\n    }\n    return this.templ.substring(pPos_last, this.pPos);\n  }\n\n  eof() {\n    return this.pPos >= this.templ.length;\n  }\n}
      \n\n## 分析器 Parser\n\n调用`Scanner.js`\n\n
      let tmpl = `我买了一个{{thing}},好{{mood}}啊`;\n// 编译 模板字符串 => tokens\nconst Parser = {\n  createTokens: (tmpl) => {\n    let scanner = new Scanner(tmpl);\n    let tokens = [];\n    // scanner 循环执行\n    while (!scanner.eof()) {\n      ctx = scanner.missTag(\"{{\"); // 返回 头字符串\n      if (ctx != \"\") {\n        tokens.push([\"text\", ctx]);\n      }\n      scanner.jumpTag(\"{{\"); // 跳过 模板字符\n      ctx = scanner.missTag(\"}}\"); // 返回 {{ x }}\n      if (ctx != \"\") {\n        tokens.push([\"name\", ctx]);\n      }\n      scanner.jumpTag(\"}}\");\n    }\n    return tokens;\n  },\n};\nconsole.log(Parser.createTokens(tmpl));\n// 输出,非常的 奈一丝\n// [\"text\", \"我买了一个\"]\n// [\"name\", \"thing\"]\n// [\"text\", \",好\"]\n// [\"name\", \"mood\"]\n// [\"text\", \"啊\"]
      \n\n## 扫描器 Scanner 增强\n\n上面的`Parser`只能识别`{{`和`}}
      ,如果模板语法复杂一点,比如加入 {{#list}}...{{/list}},需要增强Parser

      \n
      \n
      const template = `\n      哈哈哈\n        {{#students}}\n            我买了一个  {{ thing  }},好{{mood}}啊{{a}}\n          {{item.name}}\n        {{/students}}\n    `;\nconst Parser = {\n  createTokens: (tmpl) => {\n    let scanner = new Scanner(tmpl);\n    let tokens = [];\n    let ctx = \"\";\n    // scanner 循环执行\n    while (!scanner.eof()) {\n      ctx = scanner.missTag(\"{{\"); // 返回 头字符串\n      if (ctx != \"\") {\n        tokens.push([\"text\", ctx]);\n      }\n      scanner.jumpTag(\"{{\"); // 跳过 模板字符\n      ctx = scanner.missTag(\"}}\"); // 返回 {{ x }}\n      if (ctx != \"\") {\n        switch (ctx[0]) {\n          case \"#\":\n            tokens.push([\"#\", ctx.substr(1)]); // {{# x }}\n            break;\n          case \"/\":\n            tokens.push([\"/\", ctx.substr(1)]);\n            break;\n          default:\n            tokens.push([\"name\", ctx]);\n            break;\n        }\n      }\n      scanner.jumpTag(\"}}\");\n    }\n    return tokens;\n  },\n};\nconsole.log(Parser.createTokens(template));\n\n// 输出\n// [\"text\", \"↵      哈哈哈↵        \"]\n// [\"#\", \"students\"]\n// [\"text\", \"↵            我买了一个  \"]\n// [\"name\", \" thing  \"]\n// [\"text\", \",好\"]\n// [\"name\", \"mood\"]\n// [\"text\", \"啊\"]\n// [\"name\", \"a\"]\n// [\"text\", \"↵          \"]\n// [\"name\", \"item.name\"]\n// [\"text\", \"↵        \"]\n// [\"/\", \"students\"]\n// [\"text\", \"↵    \"]
      \n\n

      栈队列算法

      上一步最后的输出,只有单层嵌套,如果是两层嵌套怎么办?

      \n

      例如模板语法如下:

      \n
      var template = `\n      哈哈哈\n        {{#students}}\n            {{#stu}}\n              {{stu.name}}买了一个  {{ thing  }},好{{mood}}啊{{a}}\n            {{/stu}}\n          {{item.name}}\n        {{/students}}\n    `;
      \n\n

      经过Parser处理得到:

      \n
      /**\n *\n * [\"text\", \"↵      哈哈哈↵        \"]\n * [\"#\", \"students\"]\n * [\"text\", \"↵            \"]\n * [\"#\", \"stu\"]\n * [\"text\", \"↵              \"]\n * [\"name\", \"stu.name\"]\n * [\"text\", \"买了一个  \"]\n * [\"name\", \" thing  \"]\n * [\"text\", \",好\"]\n * [\"name\", \"mood\"]\n * [\"text\", \"啊\"]\n * [\"name\", \"a\"]\n * [\"text\", \"↵            \"]\n * [\"/\", \"stu\"]\n * [\"text\", \"↵          \"]\n * [\"name\", \"item.name\"]\n * [\"text\", \"↵        \"]\n * [\"/\", \"students\"]\n * [\"text\", \"↵    \"]\n *\n * /
      \n\n

      此时studentsstu都是#标记,我们需要利用算法处理他们的嵌套结构,处理成大约如下这样的结构:

      \n
      /**\n *\n *  [\"text\", \"↵      哈哈哈↵        \"]\n *  Array(3)\n *  \"#\"\n *  \"students\"\n *  Array(5)\n *      [\"text\", \"↵            \"]\n *      [\"#\", \"stu\", Array(9)]\n *      [\"text\", \"↵          \"]\n *      [\"name\", \"item.name\"]\n *      [\"text\", \"↵        \"]\n *  [\"text\", \"↵    \"]\n *\n * /
      \n\n

      常用工具类

      递归

      /**\n * {string} dir 递归根目录\n * {object} list 暂存参数\n */\nconst deep = async (dir, list = []) => {\n  const dirs = await fs.promises.readdir(dir)\n  for (let i = 0; i < dirs.length; i++) {\n    const item = dirs[i]\n    const itemPath = path.join(dir, item)\n    const isDir = fs.statSync(itemPath).isDirectory()\n    isDir ? await deep(itemPath, list) : list.push(itemPath)\n  }\n  return list\n}
      \n\n

      自增id短码

      用于连接分享

      \n
      const createAscString = (id) => {\n  const dictionary = [\n    \"0123456789\",\n    \"abcdefghigklmnopqrstuvwxyz\",\n    \"ABCDEFGHIGKLMNOPQRSTUVWXYZ\",\n  ];\n  let chars = dictionary.join(\"\").split(\"\"),\n    radix = chars.length,\n    qutient = 1000 * 1000 * 9999 + +id,\n    arr = [];\n  while (qutient) {\n    mod = qutient % radix;\n    qutient = (qutient - mod) / radix;\n    arr.unshift(chars[mod]);\n  }\n  return arr.join(\"\");\n};\n\nconsole.log(createAscString(100000000));
      \n\n

      手动实现 eventBus

      export default class EventBus {\n  constructor() {\n    // key-value : eventName-date\n    this.callbacks = {};\n  }\n\n  /**\n   * 监听事件\n   * @param {事件名} eventName\n   * @param {回调函数} callback\n   */\n  on(eventName, callback) {\n    this.checkType(eventName).callbacks[eventName]\n      ? callback(this.callbacks[eventName])\n      : this.error(`The event has not been declared`);\n  }\n\n  /**\n   * 注册一个事件\n   * @param {事件名} eventName\n   * @param {传递的对象} data\n   */\n  emit(eventName, data) {\n    this.checkType(eventName).callbacks[eventName] = data;\n  }\n\n  /**\n   * 注销事件,不传参数默认注销全部事件\n   * @param {事件名} eventName\n   */\n  off(eventName) {\n    eventName\n      ? this.checkType(eventName).removeEvent(eventName)\n      : this.emptyEvent();\n  }\n\n  /**\n   * 移出事件\n   * @param {事件名} eventName\n   */\n  removeEvent(eventName) {\n    Reflect.deleteProperty(this.callbacks, eventName);\n  }\n\n  /**\n   * 清空全部事件\n   */\n  emptyEvent() {\n    this.callbacks = [];\n  }\n\n  /**\n   * 参数类型校验\n   * @param {参数} param\n   * @param {合法的类型} validType\n   */\n  checkType(param, validType = \"string\") {\n    if (typeof param !== validType)\n      this.error(`(param, ${param}) should be of ${validType} type`);\n    return this; // 缅怀jQ链式调用\n  }\n\n  /**\n   * 错误提示\n   * @param {提示文字} text\n   */\n  error(text) {\n    throw new Error(text);\n  }\n}
      \n\n

      调用

      \n
      // 省略import\nconst eventBus = new EventBus();\neventBus.emit(\"login\", [{ a: 1, d: 2 }]);\neventBus.on(123, (d) => console.log(d)); // [{...}]
      \n\n

      判断对象是否有某个 key

      let obj = { alias: \"es6\" };\n\"alias\" in obj; // true\nReflect.has(obj, \"alias\"); // true
      \n\n

      浏览器

      版本信息

      window.navigator.userAgent;
      \n\n

      兼容事件绑定

      /*\n兼容低版本IE,ele为需要绑定事件的元素,\neventName为事件名(保持addEventListener语法,去掉on),fun为事件响应函数\n*/\n\nfunction addEvent(ele, eventName, fun) {\n  ele.addEventListener\n    ? ele.addEventListener(eventName, fun, false)\n    : ele.attachEvent(\"on\" + eventNme, fun);\n}
      \n\n

      数组对象

      reduce

      var  arr = [1, 2, 3, 4];\nvar sum = arr.reduce(function(prev, cur, index, arr) {\n    console.log(prev, cur, index);\n    return prev + cur;\n}0) //注意这里设置了初始值\nconsole.log(arr, sum);\n\n// 求和\nconst sum = arr.reduce((p,c) => p+c)
      \n\n

      对象内部根据 key 对 value 进行排序,取前 3

      let datasource = [\n  { price: 1, alias: \"watermelon\" },\n  { price: 3, alias: \"orange\" },\n  { price: 2, alias: \"banana\" },\n  { price: 4, alias: \"apple\" },\n];\n// 降序排列\nlet compare = (key) => (a, b) => b[key] - a[key];\nlet sorted = datasource\n  .sort(compare(\"price\"))\n  .slice(0, 3)\n  .map((i) => i[\"alias\"]);\n// 返回 [\"apple\", \"orange\", \"banana\"]
      \n\n

      随机字符串

      const getRandomRangeNum = (len = 32) => {\n  // 略去不宜辨识字符\n  let dictionary = \"ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz\";\n  let maxPos = dictionary.length;\n  let res = \"\";\n  for (let i = 0; i < len; i++) {\n    res += dictionary.charAt(Math.floor(Math.random() * maxPos));\n  }\n  return res;\n};
      \n\n

      类型检测

      Q:使用typeof foo === "object"检测foo是否为对象有什么缺点?如何避免?

      \n

      A:用 typeof 是否能准确判断一个对象变量,答案是否定的,null 的结果也是 objectArray 的结果也是 object,有时候我们需要的是 “纯粹” 的 object 对象

      \n
      Object.prototype.toString.call(obj) === \"[object Object]\";
      \n\n

      倒计时

      class Countdown {\n  constructor(startNum, endNum, interval) {\n    [this.startNum, this.endNum, this.interval] = [startNum, endNum, interval];\n  }\n  execute() {\n    var timer = setTimeout(() => {\n      if (this.startNum >= this.endNum) {\n        console.log(this.startNum);\n        this.startNum -= 1;\n        this.execute();\n      } else {\n        clearTimeout(timer);\n      }\n    }, this.interval);\n  }\n}\n// 实例化调用\nvar countdown = new Countdown(5, 0, 1000).execute();
      \n\n

      范围随机数

      // 能取到 min,取不到 max\nfunction getRandomRangeNum(min, max) {\n  return min + Math.floor(Math.random() * (max - min));\n}
      \n\n

      获取当前月的天数

      const getCurMonthDays = new Date(\n  new Date().getFullYear(),\n  new Date().getMonth() + 1,\n  0\n).getDate();
      \n\n\n

      对象小操作

      去虚假值

      let arr4 = [\"小明\", \"小蓝\", \"\", false, \" \", undefined, null, 0, NaN, true];\nconsole.log(arr4.filter(Boolean)); // => ['小明', '小蓝', ' ', true]
      \n\n

      头尾插入

      效率比 unshift()

      \n
      let arr = [1, 2, 3];\n// 头插入\n[\"haha\"].concat(arr);\n// 尾插入\narr.concat([\"haha\"]);
      \n\n\n

      删除属性

      function deleteA(obj) {\n  delete obj.A;\n  return obj;\n}\n\n// 使用解构赋值\nconst deleteA = ({ A, ...rest } = {}) => rest;
      \n\n

      生产、加工、消费分离

        \n
      • 从接口拿数据到视图 fetch api
      • \n
      • 加工 computed
      • \n
      • 消费 v-for
      • \n
      \n

      元数据

      import \"reflect-metadata\"; // npm install reflect-metadata\n\nfunction Role(name: string): ClassDecorator {\n  return (target) => {\n    Reflect.defineMetadata(\"role\", name, target);\n  };\n}\n\n@Role(\"admin\")\nclass Post {}\n\nconst metadata = Reflect.getMetadata(\"role\", Post);\n\nReflect.set(Post, \"role2\", metadata);\n\nconsole.log(Reflect.get(Post, \"role2\")); // admin
      \n\n

      防抖与节流

      在页面上监听诸如scroll(页面滚动),mousemove(鼠标移动) ,keydownkeyupkeypress(按下键盘)等等一系列事件的时候,我们并不希望频繁的触发这类监听,尤其当请求非常消耗资源时,这种操作会导致服务器性能急剧下降。

      \n

      Debounce

      把触发非常频繁的事件合并成一次延迟执行,如果对监听函数使用 100ms 的容忍时间,那么时间在第 3.1s 的时候执行

      \n
      // 默认延时100ms\nfunction debounce(func, dealy = 100) {\n  let timer;\n  return function () {\n    // 暂存this和参数\n    let _this = this;\n    let args = arguments;\n    // 清除定时器,确保不执行func\n    clearTimeout(timer);\n    timer = setTimeout(function () {\n      func.apply(_this, args);\n    }, dealy);\n  };\n}\n// 执行函数\nfunction handler() {\n  console.log(`delay 100ms ,then handle`);\n}\n// dom添加监听\ndocument\n  .querySelector(\"#someNode\")\n  .addEventListener(\"scroll\", debounce(handler));
      \n\n

      Throttle

      固定函数执行的速率,即所谓的“节流”。设置一个阀值,在阀值内,把触发的事件合并成一次执行;当到达阀值,必定执行一次事件。

      \n
      function throttle(func, delay) {\n  let statTime = 0;\n  return function () {\n    let currentTime = +new Date();\n    if (currentTime - statTime > delay) {\n      func.apply(this, arguments);\n      statTime = currentTime;\n    }\n  };\n}\n// 执行函数\nfunction resizeHandler() {\n  console.log(`resize`);\n}\n// window添加监听\nwindow.onresize = throttle(resizeHandler, 300);
      \n\n

      this 指向

      全局环境

      全局环境下,this 始终指向全局对象(window),无论是否严格模式

      \n
      console.log(this === window); // true\nthis.a = 37;\nconsole.log(window.a); // 37
      \n\n

      函数上下文调用

        \n
      • 非严格模式
      • \n
      \n

      没有被上一级的对象所调用, this 默认指向全局对象 window

      \n
      function f1() {\n  return this;\n}\nf1() === window; // true
      \n\n
        \n
      • 严格模式
      • \n
      \n

      this 指向 undefined

      \n
      function f2() {\n  \"use strict\"; // 这里是严格模式\n  return this;\n}\nf2() === undefined; // true
      \n\n

      箭头函数

      \n

      箭头函数中,call()、apply()、bind()方法无效

      \n
      \n

      在全局代码中,箭头函数被设置为全局对象,总之箭头函数不改变 this 指向

      \n
      var globalObject = this;\nvar foo = () => this;\nconsole.log(foo() === globalObject); // true
      \n\n

      箭头函数作为对象的方法使用,指向全局 window 对象

      \n
      var obj = {\n  i: 10,\n  b: () => console.log(this.i, this),\n  c: function () {\n    console.log(this.i, this);\n  },\n};\nobj.b(); // undefined window{...}\nobj.c(); // 10 Object {...}
      \n\n

      箭头函数可以让 this 指向固化,这种特性很有利于封装回调函数

      \n
      // 总是指向 handler 对象。如果不使用箭头函数则指向全局 document 对象\nvar handler = {\n  id: \"123456\",\n\n  init: function () {\n    document.addEventListener(\n      \"click\",\n      (event) => this.doSomething(event.type),\n      false\n    );\n  },\n\n  doSomething: function (type) {\n    console.log(\"Handling \" + type + \" for \" + this.id);\n  },\n};
      \n\n

      call, apply, bind 与 es6

      js 的函数继承于Function.prototype对象,因此每个函数都会有 apply、call、bind 方法

      \n
      \n

      call 和 apply 的作用,完全一样,唯一的区别就是在参数上面。

      \n
      \n

      call, apply, bind改变函数中 this 指向 的三兄弟,把this绑定到第一个参数对象上

      \n
      function displayHobbies(...hobbies) {\n  console.log(`${this.name} likes ${hobbies.join(\", \")}.`);\n}\n// 下面两个等价\ndisplayHobbies.call({ name: \"Bob\" }, \"swimming\", \"basketball\", \"anime\"); // Bob likes swimming, basketball, anime.\ndisplayHobbies.apply({ name: \"Bob\" }, [\"swimming\", \"basketball\", \"anime\"]); // Bob likes swimming, basketball, anime.
      \n\n

      bind返回的是一个函数,需要手动执行

      \n
      var p1 = {\n  name: \"张三\",\n  age: 12,\n  func: function () {\n    console.log(`姓名:${this.name},年龄:${this.age}`);\n  },\n};\n\nvar p2 = {\n  name: \"李四\",\n  age: 15,\n};\n\np1.func.bind(p2)(); //姓名:李四,年龄:15
      \n\n

      for 循环优化

      // 每次都要计算array.length\nfor (let i = 0; i < array.length; i++) {\n  console.log(i);\n}\n\n// 使用leng缓存array长度\nfor (let i = 0, length = array.length; i < length; i++) {\n  console.log(i);\n}
      \n\n

      数组

      扁平化去重升序排列

      let arr = [[1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14]]]], 10];\narr.flat(Infinity); // [1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9, 11, 12, 12, 13, 14, 10]\n\nlet result = Array.from(new Set(arr.flat(Infinity)).sort((a, b) => a - b));
      \n\n

      前端页面埋点 - 1x1.gif

      通常用在,统计页面点击,曝光,停留时间,签发……等场景

      \n
        \n
      • 比 PNG/JPG 体积小
      • \n
      • 天然跨域
      • \n
      \n
      <button onClick=\"countClick()\">haorooms</button>\n<script>\n  function countClick() {\n    new Image().src = `./haorooms.gif?${key}=${value}&${Math.random()} `;\n  }\n</script>
      \n\n

      事件委托

      利用冒泡原理,委托父元素执行

      \n
      <ul>\n  <li>苹果</li>\n  <li>香蕉</li>\n  <li>凤梨</li>\n</ul>
      \n\n
      document.querySelector(\"ul\").onclick = (event) => {\n  let target = event.target;\n  if (target.nodeName === \"LI\") {\n    console.log(target.innerHTML);\n  }\n};
      \n\n

      构造函数 + 原型模式

      function Person(name, age, job) {\n  this.name = name;\n  this.age = age;\n  this.job = job;\n}\nPerson.prototype.say = function (text) {\n  console.log(`${this.name}say:${text}`);\n};
      \n\n

      剩余参数…args

      剩余参数args数个数组,...解构符

      \n
      function fun1(param, ...args) {\n  alert(args.length);\n}
      \n\n

      跨页面通信

        \n
      • cookie
      • \n
      • web worker
      • \n
      • localstorage
      • \n
      \n

      iframe 跨域通信和不跨域通信

      不跨域

      // fatherSay是父页面全局方法\nwindow.parent.fatherSay();\n// 父页面Dom\nwindow.parent.document.getElementById(\"元素id\");\n// 副业页面获取frameID为`iframe_ID`的子页面的Dom\nwindow.frames[\"iframe_ID\"].document.getElementById(\"元素id\");
      \n\n

      跨域 postMessage

      子页面

      \n
      window.parent.postMessage(\"hello\", \"http://127.0.0.1:8089\");
      \n\n

      父页面接受

      \n
      window.addEventListener(\"message\", function (event) {\n  alert(123);\n});
      \n\n

      对象类型判断

      数组

      let arr = [];\narr instanceof Array; // true\nArray.isArray(arr); // true\nObject.prototype.toString.call(arr); // \"[object Array]\"
      \n\n

      js 单线程,如何异步

        \n
      • 主线程 执行 js 中所有的代码。

        \n
      • \n
      • 主线程 在执行过程中发现了需要异步的任务任务后扔给浏览器(浏览器创建多个线程执行,顺便创造一个回调队列

        \n
      • \n
      • 主线程 已经执行完毕所有同步代码。监听回调队列一旦 浏览器 中某个线程任务完成将会改变回调函数的状态。主线程查看到某个函数的状态为已完成,就会执行该回调队列中对应的回调函数。

        \n
      • \n
      \n

      移动端最小触控区域

      苹果推荐是 44pt x 44pt 「具体看 WWDC 14」,通过paddingmarginheight等方式进行点击区域扩展

      \n

      js 精度问题

      常用类库:Math.jsBig.jsdecimal.js

      \n

      冻结 Object. freeze()

      const生命的简单变量不可修改,但是复杂对象可以被修改,Object. freeze():可以冻结对象

      \n
        \n
      • 不能添加新属性
      • \n
      • 不能删除已有属性
      • \n
      • 不能修改已有属性的可枚举性、可配置性、可写性
      • \n
      • 不能修改已有属性的值
      • \n
      • 不能修改原型
      • \n
      \n

      浅冻结

      \n
      const obj1 = {\n  internal: {},\n};\n\nObject.freeze(obj1);\nobj1.internal.a = \"aValue\";\nconsole.log(obj1.internal.a); // aValue
      \n\n

      递归冻结

      \n
      function deepFreeze(obj) {\n  // 获取定义在obj上的属性名\n  var propNames = Object.getOwnPropertyNames(obj);\n  // 在冻结自身之前冻结属性\n  propNames.forEach(function (name) {\n    var prop = obj[name];\n    // 如果prop是个对象,冻结它\n    if (typeof prop == \"object\" && prop !== null) deepFreeze(prop);\n  });\n  return Object.freeze(obj);\n}
      \n\n

      Reflect

      Reflect.get(target, propertyKey, value[receiver])

      获取对象身上某个属性的值,类似于 target[name]。

      \n

      Reflect.set(target, propertyKey, value[receiver])

      将值分配给属性的函数。返回一个 Boolean,如果更新成功,则返回 true。

      \n

      Reflect.has(target, propertyKey)

      判断一个对象是否存在某个属性,和 in 运算符 的功能完全相同。

      \n

      if else 优化

      表驱动编程

      空间换时间,设置obj={key:value},通过obj[key]取值

      \n
      calculateGrade(score){\n    const table = {\n        100: 'A',\n        90: 'A',\n        80: 'B',\n        70: 'C',\n        60: 'D',\n        others: 'E'\n    }\n    return table[Math.floor(score/10)*10] || table['others']\n}
      \n\n

      短路运算

      react 没有v-if,运用比较频繁

      \n
      // 函数组件\nconst Home = () => {\n  return <div>home</div>;\n};\n{\n  true && <Home />;\n}
      \n\n

      使用有意义且易读的变量名

      👎 const yyyymmdstr = moment().format(\"YYYY/MM/DD\");\n\n👍 const currentDate = moment().format(\"YYYY/MM/DD\");
      \n\n

      使用有意义的变量代替数组下标

      👎\nconst address = \"One Infinite Loop, Cupertino 95014\";\nconst cityZipCodeRegex = /^[^,\\\\]+[,\\\\\\s]+(.+?)\\s*(\\d{5})?$/;\nsaveCityZipCode(\n  address.match(cityZipCodeRegex)[1],\n  address.match(cityZipCodeRegex)[2]\n);\n\n👍\nconst address = \"One Infinite Loop, Cupertino 95014\";\nconst cityZipCodeRegex = /^[^,\\\\]+[,\\\\\\s]+(.+?)\\s*(\\d{5})?$/;\nconst [_, city, zipCode] = address.match(cityZipCodeRegex) || [];\nsaveCityZipCode(city, zipCode);
      \n\n

      变量名要简洁

      👎\nconst Car = {\n  carMake: \"Honda\",\n  carModel: \"Accord\",\n  carColor: \"Blue\"\n};\nfunction paintCar(car, color) {\n  car.carColor = color;\n}\n\n👍\nconst Car = {\n  make: \"Honda\",\n  model: \"Accord\",\n  color: \"Blue\"\n};\nfunction paintCar(car, color) {\n  car.color = color;\n}
      \n\n

      消除魔术字符串

      👎 setTimeout(blastOff, 86400000);\n\n👍 const MILLISECONDS_PER_DAY = 60 * 60 * 24 * 1000; //86400000;\nsetTimeout(blastOff, MILLISECONDS_PER_DAY);
      \n\n

      使用默认参数替代短路运算符

      👎\nfunction createMicrobrewery(name) {\n  const breweryName = name || \"Hipster Brew Co.\";\n  // ...\n}\n\n👍\nfunction createMicrobrewery(name = \"Hipster Brew Co.\") {\n  // ...\n}
      \n\n

      一个函数

      👎\nfunction emailClients(clients) {\n  clients.forEach(client => {\n    const clientRecord = database.lookup(client);\n    if (clientRecord.isActive()) {\n      email(client);\n    }\n  });\n}\n\n👍\nfunction emailActiveClients(clients) {\n  clients.filter(isActiveClient).forEach(email);\n}\nfunction isActiveClient(client) {\n  const clientRecord = database.lookup(client);\n  return clientRecord.isActive();\n}\n\n---------------------分割线-----------------------\n\n👎\nfunction createFile(name, temp) {\n  if (temp) {\n    fs.create(`./temp/${name}`);\n  } else {\n    fs.create(name);\n  }\n}\n\n👍\nfunction createFile(name) {\n  fs.create(name);\n}\nfunction createTempFile(name) {\n  createFile(`./temp/${name}`);\n}
      \n\n

      函数参数不多于 2 个,如果有很多参数就利用 object 传递,并使用解构

      👎\nfunction createMenu(title, body, buttonText, cancellable) {\n  // ...\n}\ncreateMenu(\"Foo\", \"Bar\", \"Baz\", true);\n\n👍\nfunction createMenu({ title, body, buttonText, cancellable }) {\n  // ...\n}\ncreateMenu({\n  title: \"Foo\",\n  body: \"Bar\",\n  buttonText: \"Baz\",\n  cancellable: true\n});
      \n\n

      函数名应该直接反映函数的作用

      👎\nfunction addToDate(date, month) {\n  // ...\n}\nconst date = new Date();\n// It's hard to tell from the function name what is added\naddToDate(date, 1);\n\n👍\nfunction addMonthToDate(month, date) {\n  // ...\n}\nconst date = new Date();\naddMonthToDate(1, date);
      \n\n

      尽量使用纯函数

      👎\nconst programmerOutput = [\n  {\n    name: \"Uncle Bobby\",\n    linesOfCode: 500\n  },\n  {\n    name: \"Suzie Q\",\n    linesOfCode: 1500\n  },\n  {\n    name: \"Jimmy Gosling\",\n    linesOfCode: 150\n  },\n  {\n    name: \"Gracie Hopper\",\n    linesOfCode: 1000\n  }\n];\nlet totalOutput = 0;\nfor (let i = 0; i < programmerOutput.length; i++) {\n  totalOutput += programmerOutput[i].linesOfCode;\n}\n\n👍\nconst programmerOutput = [\n  {\n    name: \"Uncle Bobby\",\n    linesOfCode: 500\n  },\n  {\n    name: \"Suzie Q\",\n    linesOfCode: 1500\n  },\n  {\n    name: \"Jimmy Gosling\",\n    linesOfCode: 150\n  },\n  {\n    name: \"Gracie Hopper\",\n    linesOfCode: 1000\n  }\n];\nconst totalOutput = programmerOutput.reduce(\n  (totalLines, output) => totalLines + output.linesOfCode,\n  0\n);
      \n\n

      不要过度优化

      👎\n// 现代浏览器对于迭代器做了内部优化\nfor (let i = 0, len = list.length; i < len; i++) {\n  // ...\n}\n\n👍\nfor (let i = 0; i < list.length; i++) {\n  // ...\n}
      \n\n\n

      关于模块化

      无模块化

      \n

      污染全局作用域、维护成本高、依赖关系不明显

      \n
      \n

      script 标签引入 js 文件,相互罗列,但是被依赖的放在前面,否则使用就会报错。如下:

      \n
      <script src=\"jquery.js\"></script>\n<script src=\"main.js\"></script>\n<script src=\"other1.js\"></script>\n<script src=\"other2.js\"></script>\n<script src=\"other3.js\"></script>
      \n\n

      CommonJS 规范(同步)

      该规范最初是用在服务器端的 node 的,它有四个重要的环境变量为模块化的实现提供支持:module、exports、require、global。实际使用时,用 module.exports 定义当前模块对外输出的接口(不推荐直接用 exports),用 require 加载模块(同步)

      \n
      \n

      module.exports 本身就是一个对象

      \n
      \n
      module.exports = { foo: \"bar\" }; //true\nmodule.exports.foo = \"bar\"; //true。
      \n\n

      CommonJS 用同步的方式加载模块。在服务端,模块文件都存在本地磁盘,读取非常快,所以这样做不会有问题。但是在浏览器端,限于网络原因,CommonJS 不适合浏览器端模块加载,更合理的方案是使用异步加载,比如下边 AMD 规范。

      \n

      AMD 规范(RequireJS)

      承接上文,AMD 规范则是非同步加载模块,允许指定回调函数,AMD 是 RequireJS 在推广过程中对模块定义的规范化产出。

      \n
        \n
      • require([module], callback):加载模块
      • \n
      • define(id, [depends], callback):定义模块
      • \n
      • require.config():配置路径、依赖关系
      • \n
      \n

      CMD 规范(SeaJS)

      CMD 是 在推广过程中对模块定义的规范化产出

      \n

      ES6 import/export

      通过 babel 将不被支持的 import 编译为当前受到广泛支持的 AMD 规范

      \n

      AMD模块化实现

      define 函数

      用来声明模块名module,依赖数组deps,以及模块的作用callback

      \n
      var module = (function () {\n  var stack = {}; //模块存储栈,存的是模块执行后的结果\n\n  function define(module, deps, callback) {\n    stack[module] = callback.apply(null, deps); // 压栈\n    console.log(stack); // 查看栈存储的模块\n  }\n\n  return { define: define };\n})();
      \n\n

      调用define试试,发现 calc模块被压入了stack 模块栈中

      \n
      module.define(\"calc\", [], function () {\n  return {\n    first: function (arr) {\n      return arr[0];\n    },\n  };\n});\n// {calc: { first: ƒ }}
      \n\n

      deps 依赖(导入)

      对上面的 define函数稍加改造,这一步过程的本质,就是对 deps

      \n
      var module = (function (window) {\n  var stack = {}; //模块存储栈,\n\n  function define(module, deps, callback) {\n    // 把 define 函数依赖数组中的模块从 stack 模块中拿出\n    deps.map(function (mod, index) {\n      deps[index] = stack[mod]; // 赋值给当前模块的 deps\n    });\n    stack[module] = callback.apply(null, deps);\n    console.log(stack); // 查看栈存储的模块\n  }\n\n  return { define: define };\n})(window);\n\nmodule.define(\"calc\", [], function () {\n  return {\n    first: function (arr) {\n      return arr[0];\n    },\n  };\n});\n\nmodule.define(\"number\", [\"calc\"], function (calc) {\n  return {\n    res: calc.first([4, 3, 2, 1]), //4\n  };\n});\n\n// 此时 stack 打印的结果为\n//> calc: {first: ƒ}\n//> number: {res: 4}
      \n\n
      \n

      从本质上看,define 函数的第二个参数 deps 数组,相当于 import 导入,并且如果第三个参数 callback 采用 return { },也就相当于 export 导出

      \n
      \n

      老生常谈的指针

      说到底,stacka模块 export 是一个指针,{a:value}(内存地址),所以,b 模块会改变a.a的值,这点和 cmd不同

      \n
      module.define(\"a\", [], function () {\n  return {\n    a: 1,\n  };\n});\nmodule.define(\"b\", [\"a\"], function (a) {\n  a.a = 2;\n});\nmodule.define(\"c\", [\"a\"], function (a) {\n  console.log(a.a); // 2\n});
      \n\n\n

      缘起 Object.defineProperty()

      目标对象上定义一个新属性,或者修改目标对象属性,并且返回新对象

      \n

      上帝的钥匙 get & set

      属性的getter函数,如果没有 getter则尾 undefined。当访问该属性时,会调用此函数.

      \n
      let obj = {};\n\nObject.defineProperty(obj, \"a\", {\n  get() {\n    return 7;\n  },\n  set(val) {\n    console.log(`FAILED!改变 a 属性,新值为:${val},但是被重写 set 劫持了`);\n  },\n});\n\nconsole.log(obj.a);\nobj.a = 4;
      \n\n
      \n

      上帝的钥匙被找到了

      \n
      \n

      劫持!劫持!还是 TMD 劫持!

      \n
      let obj = {};\nlet tempValue = 0;\n\nObject.defineProperty(obj, \"a\", {\n  get() {\n    return tempValue;\n  },\n  set(newValue) {\n    tempValue = newValue;\n  },\n});\n\nconsole.log(obj.a); // 0\nobj.a = 4;\nconsole.log(obj.a); // 4
      \n\n

      封装 defineReactive(obj, prop, val)

      \n

      这里 defineReactive 第三个参数 val 替代了上一步中全局变量tempValue,对于 get()、set()来说,访问到了其他函数内部的变量,所以形成了闭包

      \n
      \n
      function defineReactive(obj, prop, val) {\n  Object.defineProperty(obj, prop, {\n    get() {\n      console.log(`劫持,你访问了${prop}属性`);\n      return val;\n    },\n    set(newValue) {\n      if (val === newValue) return;\n      console.log(`劫持,你改变了${prop}属性`);\n      val = newValue;\n    },\n  });\n}\n\nlet obj = {};\ndefineReactive(obj, \"a\", 4);\n\nconsole.log(obj.a); // 劫持,你访问了a属性 4\nobj.a = 7; // 劫持,你改变了a属性\nconsole.log(obj.a); // 劫持,你访问了a属性 7
      \n\n

      递归侦测

      let obj = { a: { b: { c: {} } } };\ndefineReactive(obj, \"a\", 4);
      \n\n
      \n

      如何自动让obj对象的全部属性都reactive呢?

      \n
      \n
      let obj = {\n  a: {\n    b: {\n      c: 5,\n    },\n  },\n  d: 4,\n};
      \n\n

      定义个方法observe,递归 obj 的每一层的每个 prop,检测是否有__ob__,如果没有,defineReactive,并且挂一个Observer实例在这个props上,例如:

      \n

      Observer对象

      \n
      class Observer {\n  constructor(value) {\n    def(value, \"__ob__\", this, false); // 不可枚举,不能给__ob__添加__ob__\n    this.walk(value);\n  }\n  // 遍历每一个 prop的 value\n  walk(value) {\n    for (let prop in value) {\n      defineReactive(value, prop);\n    }\n  }\n}
      \n\n

      defineReactive.js

      \n
      export default function defineReactive(obj, prop, val) {\n  if (arguments.length === 2) val = obj[prop]; // 如果2个参数\n  let childNode = observe(val);\n  Object.defineProperty(obj, prop, {\n    enumerable: true,\n    configurable: true,\n    get() {\n      console.log(`你访问了${prop}属性`);\n      return val;\n    },\n    set(newValue) {\n      if (val === newValue) return;\n      console.log(`你改变了${prop}属性`);\n      val = newValue;\n      childNode = observe(newValue);\n    },\n  });\n}
      \n\n

      observe.js

      \n
      /**\n * 检测 obj 身上有没有 __ob__(Observer 实例)\n * @param {*} value\n * @returns\n */\nexport default function observe(value) {\n  if (typeof value != \"object\") return;\n  let ob;\n  //! 用__ob__是为了属性不重名,被覆盖\n  if (typeof value.__ob__ != \"undefined\") {\n    ob = value.__ob__;\n  } else {\n    ob = new Observer(value);\n  }\n  return ob;\n}
      \n"},{"title":"数据结构","top":0,"status":"doing","use":"katex","_content":"\n# 算法\n\n## 时间复杂度\n\n事前预估算法时间开销 $$T(n)$$ 与问题规模 $$n$$ 的关系( $$T$$ 表示 “time”)","source":"_posts/ky/数据结构.md","raw":"---\ntitle: 数据结构\ntop: 0\ncategories:\n - 求学之路\nstatus: doing\nuse: katex\n---\n\n# 算法\n\n## 时间复杂度\n\n事前预估算法时间开销 $$T(n)$$ 与问题规模 $$n$$ 的关系( $$T$$ 表示 “time”)","slug":"ky/数据结构","published":1,"date":"2023-11-21T05:04:17.174Z","updated":"2023-11-21T05:24:55.895Z","comments":1,"layout":"post","photos":[],"link":"","_id":"clp7x1952000pv3z341mb8q1p","content":"

      算法

      时间复杂度

      事前预估算法时间开销 $$T(n)$$ 与问题规模 $$n$$ 的关系( $$T$$ 表示 “time”)

      \n","site":{"data":{}},"excerpt":"","more":"

      算法

      时间复杂度

      事前预估算法时间开销 $$T(n)$$ 与问题规模 $$n$$ 的关系( $$T$$ 表示 “time”)

      \n"},{"title":"内部循环与产出意识","status":"done","_content":"\n每天早起、跑步、读书、学习,身体和心灵都在路上,然而这些都是内部循环,不直接对外产出,坚持这些习惯可以让我们成为更好的人,但不会成为更厉害的人。享受努力奋斗的同时,必须要有作品产出,参与到社会价值体系的循环当中,当感受被他人强烈的需要时,就能感受到努力的希望了","source":"_posts/social/内部循环与产出意识.md","raw":"---\ntitle: 内部循环与产出意识\ncategories:\n - EQ\nstatus: done\n---\n\n每天早起、跑步、读书、学习,身体和心灵都在路上,然而这些都是内部循环,不直接对外产出,坚持这些习惯可以让我们成为更好的人,但不会成为更厉害的人。享受努力奋斗的同时,必须要有作品产出,参与到社会价值体系的循环当中,当感受被他人强烈的需要时,就能感受到努力的希望了","slug":"social/内部循环与产出意识","published":1,"date":"2023-11-06T05:18:22.419Z","updated":"2023-11-06T05:19:31.986Z","comments":1,"layout":"post","photos":[],"link":"","_id":"clp7x1952000qv3z3b1fm22sp","content":"

      每天早起、跑步、读书、学习,身体和心灵都在路上,然而这些都是内部循环,不直接对外产出,坚持这些习惯可以让我们成为更好的人,但不会成为更厉害的人。享受努力奋斗的同时,必须要有作品产出,参与到社会价值体系的循环当中,当感受被他人强烈的需要时,就能感受到努力的希望了

      \n","site":{"data":{}},"excerpt":"","more":"

      每天早起、跑步、读书、学习,身体和心灵都在路上,然而这些都是内部循环,不直接对外产出,坚持这些习惯可以让我们成为更好的人,但不会成为更厉害的人。享受努力奋斗的同时,必须要有作品产出,参与到社会价值体系的循环当中,当感受被他人强烈的需要时,就能感受到努力的希望了

      \n"},{"title":"改掉选择困难症","status":"done","_content":"\n对模糊零容忍。换句话说,就是想尽一切办法让自己找出那个最重要的、唯一的选项,让自己在某一个时间段里只有一条路可以走。如果不在这些选择的节点想清楚,我们就会陷入模糊的状态,它会使我们产生本能的反应——娱乐。因为,即时满足,避难趋易,是我们的天性","source":"_posts/social/改掉选择困难症.md","raw":"---\ntitle: 改掉选择困难症\ncategories:\n - EQ\nstatus: done\n---\n\n对模糊零容忍。换句话说,就是想尽一切办法让自己找出那个最重要的、唯一的选项,让自己在某一个时间段里只有一条路可以走。如果不在这些选择的节点想清楚,我们就会陷入模糊的状态,它会使我们产生本能的反应——娱乐。因为,即时满足,避难趋易,是我们的天性","slug":"social/改掉选择困难症","published":1,"date":"2023-11-06T05:19:17.107Z","updated":"2023-11-06T05:20:14.555Z","comments":1,"layout":"post","photos":[],"link":"","_id":"clp7x1953000tv3z3chgh7c1y","content":"

      对模糊零容忍。换句话说,就是想尽一切办法让自己找出那个最重要的、唯一的选项,让自己在某一个时间段里只有一条路可以走。如果不在这些选择的节点想清楚,我们就会陷入模糊的状态,它会使我们产生本能的反应——娱乐。因为,即时满足,避难趋易,是我们的天性

      \n","site":{"data":{}},"excerpt":"","more":"

      对模糊零容忍。换句话说,就是想尽一切办法让自己找出那个最重要的、唯一的选项,让自己在某一个时间段里只有一条路可以走。如果不在这些选择的节点想清楚,我们就会陷入模糊的状态,它会使我们产生本能的反应——娱乐。因为,即时满足,避难趋易,是我们的天性

      \n"},{"title":"有效对话指南","status":"done","_content":"\n卡耐基说过:要说服别人同意你的观点,你就要让他觉得这是他自己的观点。也就是说,本质上没有人真正被别人说服,听或者不听都是他们自己的主动选择\n\n# 目的\n\n沟通目的无非两个:\n\n- 让对方认同我们的观点\n- 或者按我们说的做\n\n# 搞定情绪\n\n大多数人的立场和行为,是由情绪决定的,对方不喜欢你,你再怎么努力也很难说服他,只有他认可你这个人你的话他才会听。所以动之以理之前要先小之以轻,你把他情绪搞定了,沟通也就成功了一半\n\n## 夸 - 细节\n\n不吝赞美,就一个字“夸”,具体怎么夸呢?\n\n细节就是不要太笼统:\n\n- 😭 你穿的真好看 \n- 😊 今天的上衣显得你很白,太美了\n\n## 夸 - 对比\n\n- 😭 你今天穿的真好看\n- 😊 你今天穿的真好看,一般人可传不出这种气质\n\n## 我能理解你,换我也生气\n\n接纳对方的负面情绪,把双方情绪拉回到安全范围内。\n\n## 肢体同步\n\n如果你的肢体动作跟对方同步,它会产生一种被共情的感觉\n\n- 对方说的火热,身体前倾,伺机凑近\n- 思考一到两秒钟再反馈,这个简单的停顿\n\n# 只陈述不评论\n\n事实不会引起争议\n\n比如:孩子考试没及格是事实,说他笨死了就是评论了。\n\n再比如:你这周上班迟到两次,这是事实,说你经常迟到就是评论了。\n\n## 评论副词\n\n尽量规避一些跟评论相关的糊的频率副词,比如\n- 笨\n- 懒\n- 坏\n- 总是\n- 永远\n- 每次\n\n# 保持开放性\n\n还拿上面的例子,这些话对方只能回答好或者不好,然后结束沟通\n\n- 孩子没及格,老爸说下次必须及格。\n- 你上班迟到,老板说再迟到扣薪水。\n\n如果换成开放式沟通:\n\n- 老爸说咱们聊聊哪些题没有搞清楚\n- 老板说生活上是不是遇到了什么问题,没着我可以帮你\n\n# 反馈事实\n\n这些话对方不仅听着舒服,而且能给你反馈事实。具体怎么开放呢?咱们只需要记住两个关键词\n\n- 问题开放:不要让对方只能回答是或否,例如:像愿闻其详、展开说说、欢迎补充之类\n- 让出话语权:真正的沟通高手,是让对方觉得自己很牛,主导聊天的往往是能提出问题的人,或者让别人多说的人\n\n","source":"_posts/social/有效对话指南.md","raw":"---\ntitle: 有效对话指南\ncategories:\n - EQ\nstatus: done\n---\n\n卡耐基说过:要说服别人同意你的观点,你就要让他觉得这是他自己的观点。也就是说,本质上没有人真正被别人说服,听或者不听都是他们自己的主动选择\n\n# 目的\n\n沟通目的无非两个:\n\n- 让对方认同我们的观点\n- 或者按我们说的做\n\n# 搞定情绪\n\n大多数人的立场和行为,是由情绪决定的,对方不喜欢你,你再怎么努力也很难说服他,只有他认可你这个人你的话他才会听。所以动之以理之前要先小之以轻,你把他情绪搞定了,沟通也就成功了一半\n\n## 夸 - 细节\n\n不吝赞美,就一个字“夸”,具体怎么夸呢?\n\n细节就是不要太笼统:\n\n- 😭 你穿的真好看 \n- 😊 今天的上衣显得你很白,太美了\n\n## 夸 - 对比\n\n- 😭 你今天穿的真好看\n- 😊 你今天穿的真好看,一般人可传不出这种气质\n\n## 我能理解你,换我也生气\n\n接纳对方的负面情绪,把双方情绪拉回到安全范围内。\n\n## 肢体同步\n\n如果你的肢体动作跟对方同步,它会产生一种被共情的感觉\n\n- 对方说的火热,身体前倾,伺机凑近\n- 思考一到两秒钟再反馈,这个简单的停顿\n\n# 只陈述不评论\n\n事实不会引起争议\n\n比如:孩子考试没及格是事实,说他笨死了就是评论了。\n\n再比如:你这周上班迟到两次,这是事实,说你经常迟到就是评论了。\n\n## 评论副词\n\n尽量规避一些跟评论相关的糊的频率副词,比如\n- 笨\n- 懒\n- 坏\n- 总是\n- 永远\n- 每次\n\n# 保持开放性\n\n还拿上面的例子,这些话对方只能回答好或者不好,然后结束沟通\n\n- 孩子没及格,老爸说下次必须及格。\n- 你上班迟到,老板说再迟到扣薪水。\n\n如果换成开放式沟通:\n\n- 老爸说咱们聊聊哪些题没有搞清楚\n- 老板说生活上是不是遇到了什么问题,没着我可以帮你\n\n# 反馈事实\n\n这些话对方不仅听着舒服,而且能给你反馈事实。具体怎么开放呢?咱们只需要记住两个关键词\n\n- 问题开放:不要让对方只能回答是或否,例如:像愿闻其详、展开说说、欢迎补充之类\n- 让出话语权:真正的沟通高手,是让对方觉得自己很牛,主导聊天的往往是能提出问题的人,或者让别人多说的人\n\n","slug":"social/有效对话指南","published":1,"date":"2023-10-24T01:47:38.891Z","updated":"2023-11-06T05:18:11.496Z","comments":1,"layout":"post","photos":[],"link":"","_id":"clp7x1953000uv3z39z0vh1ra","content":"

      卡耐基说过:要说服别人同意你的观点,你就要让他觉得这是他自己的观点。也就是说,本质上没有人真正被别人说服,听或者不听都是他们自己的主动选择

      \n

      目的

      沟通目的无非两个:

      \n
        \n
      • 让对方认同我们的观点
      • \n
      • 或者按我们说的做
      • \n
      \n

      搞定情绪

      大多数人的立场和行为,是由情绪决定的,对方不喜欢你,你再怎么努力也很难说服他,只有他认可你这个人你的话他才会听。所以动之以理之前要先小之以轻,你把他情绪搞定了,沟通也就成功了一半

      \n

      夸 - 细节

      不吝赞美,就一个字“夸”,具体怎么夸呢?

      \n

      细节就是不要太笼统:

      \n
        \n
      • 😭 你穿的真好看
      • \n
      • 😊 今天的上衣显得你很白,太美了
      • \n
      \n

      夸 - 对比

        \n
      • 😭 你今天穿的真好看
      • \n
      • 😊 你今天穿的真好看,一般人可传不出这种气质
      • \n
      \n

      我能理解你,换我也生气

      接纳对方的负面情绪,把双方情绪拉回到安全范围内。

      \n

      肢体同步

      如果你的肢体动作跟对方同步,它会产生一种被共情的感觉

      \n
        \n
      • 对方说的火热,身体前倾,伺机凑近
      • \n
      • 思考一到两秒钟再反馈,这个简单的停顿
      • \n
      \n

      只陈述不评论

      事实不会引起争议

      \n

      比如:孩子考试没及格是事实,说他笨死了就是评论了。

      \n

      再比如:你这周上班迟到两次,这是事实,说你经常迟到就是评论了。

      \n

      评论副词

      尽量规避一些跟评论相关的糊的频率副词,比如

      \n
        \n
      • \n
      • \n
      • \n
      • 总是
      • \n
      • 永远
      • \n
      • 每次
      • \n
      \n

      保持开放性

      还拿上面的例子,这些话对方只能回答好或者不好,然后结束沟通

      \n
        \n
      • 孩子没及格,老爸说下次必须及格。
      • \n
      • 你上班迟到,老板说再迟到扣薪水。
      • \n
      \n

      如果换成开放式沟通:

      \n
        \n
      • 老爸说咱们聊聊哪些题没有搞清楚
      • \n
      • 老板说生活上是不是遇到了什么问题,没着我可以帮你
      • \n
      \n

      反馈事实

      这些话对方不仅听着舒服,而且能给你反馈事实。具体怎么开放呢?咱们只需要记住两个关键词

      \n
        \n
      • 问题开放:不要让对方只能回答是或否,例如:像愿闻其详、展开说说、欢迎补充之类
      • \n
      • 让出话语权:真正的沟通高手,是让对方觉得自己很牛,主导聊天的往往是能提出问题的人,或者让别人多说的人
      • \n
      \n","site":{"data":{}},"excerpt":"","more":"

      卡耐基说过:要说服别人同意你的观点,你就要让他觉得这是他自己的观点。也就是说,本质上没有人真正被别人说服,听或者不听都是他们自己的主动选择

      \n

      目的

      沟通目的无非两个:

      \n
        \n
      • 让对方认同我们的观点
      • \n
      • 或者按我们说的做
      • \n
      \n

      搞定情绪

      大多数人的立场和行为,是由情绪决定的,对方不喜欢你,你再怎么努力也很难说服他,只有他认可你这个人你的话他才会听。所以动之以理之前要先小之以轻,你把他情绪搞定了,沟通也就成功了一半

      \n

      夸 - 细节

      不吝赞美,就一个字“夸”,具体怎么夸呢?

      \n

      细节就是不要太笼统:

      \n
        \n
      • 😭 你穿的真好看
      • \n
      • 😊 今天的上衣显得你很白,太美了
      • \n
      \n

      夸 - 对比

        \n
      • 😭 你今天穿的真好看
      • \n
      • 😊 你今天穿的真好看,一般人可传不出这种气质
      • \n
      \n

      我能理解你,换我也生气

      接纳对方的负面情绪,把双方情绪拉回到安全范围内。

      \n

      肢体同步

      如果你的肢体动作跟对方同步,它会产生一种被共情的感觉

      \n
        \n
      • 对方说的火热,身体前倾,伺机凑近
      • \n
      • 思考一到两秒钟再反馈,这个简单的停顿
      • \n
      \n

      只陈述不评论

      事实不会引起争议

      \n

      比如:孩子考试没及格是事实,说他笨死了就是评论了。

      \n

      再比如:你这周上班迟到两次,这是事实,说你经常迟到就是评论了。

      \n

      评论副词

      尽量规避一些跟评论相关的糊的频率副词,比如

      \n
        \n
      • \n
      • \n
      • \n
      • 总是
      • \n
      • 永远
      • \n
      • 每次
      • \n
      \n

      保持开放性

      还拿上面的例子,这些话对方只能回答好或者不好,然后结束沟通

      \n
        \n
      • 孩子没及格,老爸说下次必须及格。
      • \n
      • 你上班迟到,老板说再迟到扣薪水。
      • \n
      \n

      如果换成开放式沟通:

      \n
        \n
      • 老爸说咱们聊聊哪些题没有搞清楚
      • \n
      • 老板说生活上是不是遇到了什么问题,没着我可以帮你
      • \n
      \n

      反馈事实

      这些话对方不仅听着舒服,而且能给你反馈事实。具体怎么开放呢?咱们只需要记住两个关键词

      \n
        \n
      • 问题开放:不要让对方只能回答是或否,例如:像愿闻其详、展开说说、欢迎补充之类
      • \n
      • 让出话语权:真正的沟通高手,是让对方觉得自己很牛,主导聊天的往往是能提出问题的人,或者让别人多说的人
      • \n
      \n"},{"title":"烦恼的原因","status":"done","_content":"\n分心开小差,很多人感觉可能不是个问题,甚至还对自己的一心二用而感觉沾沾自喜。比如跑步的时候考虑明后天的安排,吃饭的时候在担忧与他人的关系,睡觉的时候思绪像瀑布一样倾泻而出,是我们在不知不觉中徒生烦恼、渐生愚钝。从某种意义上来说,它是我们烦恼低效的来源,因为无法专注。","source":"_posts/social/烦恼的原因.md","raw":"---\ntitle: 烦恼的原因\ncategories:\n - EQ\nstatus: done\n---\n\n分心开小差,很多人感觉可能不是个问题,甚至还对自己的一心二用而感觉沾沾自喜。比如跑步的时候考虑明后天的安排,吃饭的时候在担忧与他人的关系,睡觉的时候思绪像瀑布一样倾泻而出,是我们在不知不觉中徒生烦恼、渐生愚钝。从某种意义上来说,它是我们烦恼低效的来源,因为无法专注。","slug":"social/烦恼的原因","published":1,"date":"2023-11-06T05:19:41.350Z","updated":"2023-11-06T05:19:53.454Z","comments":1,"layout":"post","photos":[],"link":"","_id":"clp7x1953000xv3z360nugo0n","content":"

      分心开小差,很多人感觉可能不是个问题,甚至还对自己的一心二用而感觉沾沾自喜。比如跑步的时候考虑明后天的安排,吃饭的时候在担忧与他人的关系,睡觉的时候思绪像瀑布一样倾泻而出,是我们在不知不觉中徒生烦恼、渐生愚钝。从某种意义上来说,它是我们烦恼低效的来源,因为无法专注。

      \n","site":{"data":{}},"excerpt":"","more":"

      分心开小差,很多人感觉可能不是个问题,甚至还对自己的一心二用而感觉沾沾自喜。比如跑步的时候考虑明后天的安排,吃饭的时候在担忧与他人的关系,睡觉的时候思绪像瀑布一样倾泻而出,是我们在不知不觉中徒生烦恼、渐生愚钝。从某种意义上来说,它是我们烦恼低效的来源,因为无法专注。

      \n"},{"title":"走神","status":"done","_content":"\n走神可以让我们活在任何时候,唯独不能活在当下。分心走神的原因,无非\"当下太无聊、当下太痛苦\",因为身体受困于现实,只好让神思天马行空!","source":"_posts/social/走神.md","raw":"---\ntitle: 走神\ncategories:\n - EQ\nstatus: done\n---\n\n走神可以让我们活在任何时候,唯独不能活在当下。分心走神的原因,无非\"当下太无聊、当下太痛苦\",因为身体受困于现实,只好让神思天马行空!","slug":"social/走神","published":1,"date":"2023-11-06T05:20:01.272Z","updated":"2023-11-06T05:20:13.780Z","comments":1,"layout":"post","photos":[],"link":"","_id":"clp7x1954000zv3z39rtjhte9","content":"

      走神可以让我们活在任何时候,唯独不能活在当下。分心走神的原因,无非”当下太无聊、当下太痛苦”,因为身体受困于现实,只好让神思天马行空!

      \n","site":{"data":{}},"excerpt":"","more":"

      走神可以让我们活在任何时候,唯独不能活在当下。分心走神的原因,无非”当下太无聊、当下太痛苦”,因为身体受困于现实,只好让神思天马行空!

      \n"},{"title":"非暴力沟通","status":"done","_content":"\n心理学有个史诗级的剧作叫做非暴力沟通,它能让一个人温柔而坚定的去说服对方。非暴力沟通大白话讲就是对他人足够的尊重,换取他人对你一次积极倾听的机会,所以这个技巧的核心是怎么做到尊重。一共分为四步\n\n1.表达事实而非评价:比如把你怎么这么不靠谱,改成你今天开会迟到了10分钟。\n2.表达情绪而非想法:比如把你一点都不在乎我,改成你这样做我很伤心。\n3.表达需求而非指责:比如把这点小事都做不好,改成这个小事,我们以后不要再犯了。\n4.表达请求而非命令:比如把没做完不许下班,改成下班前可以先给我一个初版?\n\n所以还记得开头那句话吗?温柔是对他人尊重,而坚定是保护自己不被伤害,愿你温柔而坚定\n","source":"_posts/social/非暴力沟通.md","raw":"---\ntitle: 非暴力沟通\ncategories:\n - EQ\nstatus: done\n---\n\n心理学有个史诗级的剧作叫做非暴力沟通,它能让一个人温柔而坚定的去说服对方。非暴力沟通大白话讲就是对他人足够的尊重,换取他人对你一次积极倾听的机会,所以这个技巧的核心是怎么做到尊重。一共分为四步\n\n1.表达事实而非评价:比如把你怎么这么不靠谱,改成你今天开会迟到了10分钟。\n2.表达情绪而非想法:比如把你一点都不在乎我,改成你这样做我很伤心。\n3.表达需求而非指责:比如把这点小事都做不好,改成这个小事,我们以后不要再犯了。\n4.表达请求而非命令:比如把没做完不许下班,改成下班前可以先给我一个初版?\n\n所以还记得开头那句话吗?温柔是对他人尊重,而坚定是保护自己不被伤害,愿你温柔而坚定\n","slug":"social/非暴力沟通","published":1,"date":"2023-11-06T05:18:46.853Z","updated":"2023-11-06T05:18:59.336Z","comments":1,"layout":"post","photos":[],"link":"","_id":"clp7x19540012v3z38lxw0f8a","content":"

      心理学有个史诗级的剧作叫做非暴力沟通,它能让一个人温柔而坚定的去说服对方。非暴力沟通大白话讲就是对他人足够的尊重,换取他人对你一次积极倾听的机会,所以这个技巧的核心是怎么做到尊重。一共分为四步

      \n

      1.表达事实而非评价:比如把你怎么这么不靠谱,改成你今天开会迟到了10分钟。
      2.表达情绪而非想法:比如把你一点都不在乎我,改成你这样做我很伤心。
      3.表达需求而非指责:比如把这点小事都做不好,改成这个小事,我们以后不要再犯了。
      4.表达请求而非命令:比如把没做完不许下班,改成下班前可以先给我一个初版?

      \n

      所以还记得开头那句话吗?温柔是对他人尊重,而坚定是保护自己不被伤害,愿你温柔而坚定

      \n","site":{"data":{}},"excerpt":"","more":"

      心理学有个史诗级的剧作叫做非暴力沟通,它能让一个人温柔而坚定的去说服对方。非暴力沟通大白话讲就是对他人足够的尊重,换取他人对你一次积极倾听的机会,所以这个技巧的核心是怎么做到尊重。一共分为四步

      \n

      1.表达事实而非评价:比如把你怎么这么不靠谱,改成你今天开会迟到了10分钟。
      2.表达情绪而非想法:比如把你一点都不在乎我,改成你这样做我很伤心。
      3.表达需求而非指责:比如把这点小事都做不好,改成这个小事,我们以后不要再犯了。
      4.表达请求而非命令:比如把没做完不许下班,改成下班前可以先给我一个初版?

      \n

      所以还记得开头那句话吗?温柔是对他人尊重,而坚定是保护自己不被伤害,愿你温柔而坚定

      \n"},{"title":"短视频账号起号逻辑","top":0,"status":"done","_content":"\n# 抖音流量池\n\n| 级别 | 曝光次数 | 播放量范围 |\n| ---------- | -------------------- | ---------- |\n| 初级流量池 | 冷启动 | 0~500 |\n| | 二次曝光 | 3K~5K |\n| | 三次曝光 | 1W~2W |\n| | 四次曝光(人工复审) | 10W~15W |\n| 中级流量池 | 五次曝光 | 30W~70W |\n| 高级流量池 | 六次曝光 | 100W~300W |\n| 热门流量池 | 七次曝光 | 500W~1200W |\n| 全平台曝光 | 八次曝光 | - |\n\n\n# 如何起新号\n\n## 素材整理与发布\n\n- 准备 20 条内容,分成 5 组。\n- 连续 5 天,每天发布 4 条。\n- 每个短视频投入 100 元抖+(选择投放时长 6 小时或 12 小时)。\n- 选取同类账号,确定兴趣标签。\n- 主要关注点赞、评论数据。\n\n## 数据分析与追加投资\n\n- 第二天,基于前一天数据,忽略点赞和评论。\n- 选择完播率最高的视频,追加投资 200 元。\n- 投向粉丝量(选择投放时长 6 小时或 12 小时)。\n\n## 持续操作与评估\n\n- 在 5 天内完成 20 条视频的投放。\n- 总费用 3000 元,目标达到 7.5W 播放量视为合格。\n- 如果有视频完播率大于 40%,可达小热门;大于 50%,可达大热门。\n\n## 数据筛\n\n- 隐藏表现不佳的视频。\n- 选出最佳视频,追加投资 2000 元抖+,针对粉丝量。\n\n## 备注:兴趣标签选择\n\n- 关注二级兴趣标签(整理账号粉丝兴趣分布)。\n- 赛道长尾词:通过“创作灵感”,找到相关小账号(5K-10W 粉丝),避开大 V,直接投放,吸引其粉丝。\n","source":"_posts/we-media/douyin.md","raw":"---\ntitle: 短视频账号起号逻辑\ntop: 0\ncategories:\n - 自媒体\nstatus: done\n---\n\n# 抖音流量池\n\n| 级别 | 曝光次数 | 播放量范围 |\n| ---------- | -------------------- | ---------- |\n| 初级流量池 | 冷启动 | 0~500 |\n| | 二次曝光 | 3K~5K |\n| | 三次曝光 | 1W~2W |\n| | 四次曝光(人工复审) | 10W~15W |\n| 中级流量池 | 五次曝光 | 30W~70W |\n| 高级流量池 | 六次曝光 | 100W~300W |\n| 热门流量池 | 七次曝光 | 500W~1200W |\n| 全平台曝光 | 八次曝光 | - |\n\n\n# 如何起新号\n\n## 素材整理与发布\n\n- 准备 20 条内容,分成 5 组。\n- 连续 5 天,每天发布 4 条。\n- 每个短视频投入 100 元抖+(选择投放时长 6 小时或 12 小时)。\n- 选取同类账号,确定兴趣标签。\n- 主要关注点赞、评论数据。\n\n## 数据分析与追加投资\n\n- 第二天,基于前一天数据,忽略点赞和评论。\n- 选择完播率最高的视频,追加投资 200 元。\n- 投向粉丝量(选择投放时长 6 小时或 12 小时)。\n\n## 持续操作与评估\n\n- 在 5 天内完成 20 条视频的投放。\n- 总费用 3000 元,目标达到 7.5W 播放量视为合格。\n- 如果有视频完播率大于 40%,可达小热门;大于 50%,可达大热门。\n\n## 数据筛\n\n- 隐藏表现不佳的视频。\n- 选出最佳视频,追加投资 2000 元抖+,针对粉丝量。\n\n## 备注:兴趣标签选择\n\n- 关注二级兴趣标签(整理账号粉丝兴趣分布)。\n- 赛道长尾词:通过“创作灵感”,找到相关小账号(5K-10W 粉丝),避开大 V,直接投放,吸引其粉丝。\n","slug":"we-media/douyin","published":1,"date":"2023-11-14T07:33:17.858Z","updated":"2023-11-14T07:33:17.858Z","comments":1,"layout":"post","photos":[],"link":"","_id":"clp7x1956001kv3z3a8182w5o","content":"

      抖音流量池

      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
      级别曝光次数播放量范围
      初级流量池冷启动0~500
      二次曝光3K~5K
      三次曝光1W~2W
      四次曝光(人工复审)10W~15W
      中级流量池五次曝光30W~70W
      高级流量池六次曝光100W~300W
      热门流量池七次曝光500W~1200W
      全平台曝光八次曝光-
      \n

      如何起新号

      素材整理与发布

        \n
      • 准备 20 条内容,分成 5 组。
      • \n
      • 连续 5 天,每天发布 4 条。
      • \n
      • 每个短视频投入 100 元抖+(选择投放时长 6 小时或 12 小时)。
      • \n
      • 选取同类账号,确定兴趣标签。
      • \n
      • 主要关注点赞、评论数据。
      • \n
      \n

      数据分析与追加投资

        \n
      • 第二天,基于前一天数据,忽略点赞和评论。
      • \n
      • 选择完播率最高的视频,追加投资 200 元。
      • \n
      • 投向粉丝量(选择投放时长 6 小时或 12 小时)。
      • \n
      \n

      持续操作与评估

        \n
      • 在 5 天内完成 20 条视频的投放。
      • \n
      • 总费用 3000 元,目标达到 7.5W 播放量视为合格。
      • \n
      • 如果有视频完播率大于 40%,可达小热门;大于 50%,可达大热门。
      • \n
      \n

      数据筛

        \n
      • 隐藏表现不佳的视频。
      • \n
      • 选出最佳视频,追加投资 2000 元抖+,针对粉丝量。
      • \n
      \n

      备注:兴趣标签选择

        \n
      • 关注二级兴趣标签(整理账号粉丝兴趣分布)。
      • \n
      • 赛道长尾词:通过“创作灵感”,找到相关小账号(5K-10W 粉丝),避开大 V,直接投放,吸引其粉丝。
      • \n
      \n","site":{"data":{}},"excerpt":"","more":"

      抖音流量池

      \n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
      级别曝光次数播放量范围
      初级流量池冷启动0~500
      二次曝光3K~5K
      三次曝光1W~2W
      四次曝光(人工复审)10W~15W
      中级流量池五次曝光30W~70W
      高级流量池六次曝光100W~300W
      热门流量池七次曝光500W~1200W
      全平台曝光八次曝光-
      \n

      如何起新号

      素材整理与发布

        \n
      • 准备 20 条内容,分成 5 组。
      • \n
      • 连续 5 天,每天发布 4 条。
      • \n
      • 每个短视频投入 100 元抖+(选择投放时长 6 小时或 12 小时)。
      • \n
      • 选取同类账号,确定兴趣标签。
      • \n
      • 主要关注点赞、评论数据。
      • \n
      \n

      数据分析与追加投资

        \n
      • 第二天,基于前一天数据,忽略点赞和评论。
      • \n
      • 选择完播率最高的视频,追加投资 200 元。
      • \n
      • 投向粉丝量(选择投放时长 6 小时或 12 小时)。
      • \n
      \n

      持续操作与评估

        \n
      • 在 5 天内完成 20 条视频的投放。
      • \n
      • 总费用 3000 元,目标达到 7.5W 播放量视为合格。
      • \n
      • 如果有视频完播率大于 40%,可达小热门;大于 50%,可达大热门。
      • \n
      \n

      数据筛

        \n
      • 隐藏表现不佳的视频。
      • \n
      • 选出最佳视频,追加投资 2000 元抖+,针对粉丝量。
      • \n
      \n

      备注:兴趣标签选择

        \n
      • 关注二级兴趣标签(整理账号粉丝兴趣分布)。
      • \n
      • 赛道长尾词:通过“创作灵感”,找到相关小账号(5K-10W 粉丝),避开大 V,直接投放,吸引其粉丝。
      • \n
      \n"},{"title":"词汇 01","status":"done","_content":"\n\n| 序号 | 中文 | 英文 |\n| ---- | ----------------------------------------- | ---------------------------- |\n| 1 | 轻松的,不费力的 | effortless |\n| 2 | 极其的,极其大的 | dreadful |\n| 3 | 障碍,阻碍 | handicap |\n| 4 | 适当地 | properly |\n| 5 | 处理,应付 | handle |\n| 6 | 推动力,推动力 | driving force |\n| 7 | 光彩照人的,辉煌的 | glowing |\n| 8 | 竞争者 | competitor |\n| 9 | 无比的,空前的 | unparalleled |\n| 10 | 熟练的 | skilled |\n| 11 | 繁荣的 | prosperous |\n| 12 | 做梦也无法想象 | beyond the dreams of |\n| 13 | 必然的,不可避免的 | inevitable |\n| 14 | 第一位,首位,卓越 | primacy |\n| 15 | 变窄,变小 | narrow |\n| 16 | 后退 | retreat |\n| 17 | 主导地位,优势 | predominance |\n| 18 | 困惑,不知所措 | at a loss |\n| 19 | 逐渐消逝,逐渐消失 | fade |\n| 20 | 竞争力 | competitiveness |\n| 21 | 消费电子产品 | consumer electronics |\n| 22 | 变小,减少 | shrink |\n| 23 | 消失 | vanish |\n| 24 | 纺织品 | textile |\n| 25 | 涌向 | sweep into |\n| 26 | 国内的 | domestic |\n| 27 | 机床制造工业 | machine-tool industry |\n| 28 | 岌岌可危 | on the ropes |\n| 29 | 曾经有那么一段落时间 | for a while |\n| 30 | 半导体 | semiconductor |\n| 31 | 处于……的中心位置 | at the heart of |\n| 32 | 受害者,牺牲品 | casualty |\n| 33 | 引发一场信心危机 | cause a crisis of confidence |\n| 34 | 繁荣,成功 | prosperity |\n| 35 | 认为……理所当然 | take... for granted |\n| 36 | 商业经营方式 | way of doing business |\n| 37 | 不久,很快 | shortly |\n| 38 | 调查,查问 | inquiry |\n| 39 | 衰退 | decline |\n| 40 | 哗众取宠的,耸人听闻的 | sensational |\n| 41 | 警告,告诚 | warning |\n| 42 | 海外 | overseas |\n| 43 | 稳固的,稳定的 | solid |\n| 44 | 挣扎 | struggle |\n| 45 | 把………归因于 | attribute |\n| 46 | 仅仅 | solely |\n| 47 | 贬值的 | devalued |\n| 48 | 周期,循环期 | cycle |\n| 49 | 自我怀疑 | self-doubt |\n| 50 | 屈服,让步 | yield |\n| 51 | 盲目自豪 | blind pride |\n| 52 | 节食,引申为“精简” | go on a diet |\n| 53 | 机敏的,机智的 | quick-witted |\n| 54 | 提高生产力 | improve one's productivity |\n| 55 | 智囊团 | think-tank |\n| 56 | 黄金时代 | a golden age |\n| 57 | 比率 | ratio |\n| 58 | 成年 | maturity |\n| 59 | 普遍特征 | universal |\n| 60 | 死亡率 | mortality |\n| 61 | 过多,过量 | excess |\n| 62 | 关键性的 | crucial |\n| 63 | 使消失 | remove |\n| 64 | 尤其地 | particularly |\n| 65 | 公斤 | kilogram |\n| 66 | 变化:变异 | variation |\n| 67 | 由于;因………而产生 | due to |\n| 68 | 动因 | agent |\n| 69 | 进化 | evolution |\n| 70 | 能生育的 | fertile |\n| 71 | 大致,差不多 | roughly |\n| 72 | 子女 | offspring |\n| 73 | 利用 | take advantage of |\n| 74 | 缩小,减少 | diminish |\n| 75 | 财富,富裕 | wealth |\n| 76 | 贫穷 | poverty |\n| 77 | 部落的 | tribal |\n| 78 | 大规模的 | grand |\n| 79 | 凡;均一 | mediocrity |\n| 80 | 与……相比 | compare to |\n| 81 | 乌托邦,理想的完美境界 | Utopia |\n| 82 | 牵涉,涉及 | involve |\n| 83 | 彻底改变 | transform |\n| 84 | 进化 | evolve |\n| 85 | 无知的 | ignorant |\n| 86 | 未开化的人;野蛮人 | savage |\n| 87 | 完全地;全部地 | wholly |\n| 88 | 理解(力 | comprehension |\n| 89 | 丑陋 | ugliness |\n| 90 | 惊讶的 | amazed |\n| 91 | 后代;后裔 | descendant |\n| 92 | 运动 | movement |\n| 93 | 获得,成为 | attain |\n| 94 | 某种 | certain |\n| 95 | 时尚;风气 | fashion |\n| 96 | 明智的;可取的 | advisable |\n| 97 | 倡导者 | advocate |\n| 98 | 牵强的 | farfetched |\n| 99 | 无理的 | unreasonable |\n| 100 | 观念;信条 | principle |\n| 101 | 情况 | case |\n| 102 | 相当地 | rather |\n| 103 | 分类、归类 | class |\n| 104 | 未来主义者 | Futurist |\n| 105 | 状况 | condition |\n| 106 | 有条件地 | conditionally |\n| 107 | 暴力 | violence |\n| 108 | 因此,结果 | consequently |\n| 109 | 经历 | undergo |\n| 110 | 相应的,相关联的 | corresponding |\n| 111 | 文学 | literature |\n| 112 | 解释,诠释 | interpret |\n| 113 | 倾吐;畅所欲言 | pour out |\n| 114 | 基本的 | essential |\n| 115 | 不受阻碍的,不受限制的 | unhampered |\n| 116 | 句号 | stop |\n| 117 | 修饰性形容词 | qualifying adjective |\n| 118 | 限定动词(如 He opens the door.中的 opens) | finite verb |\n| 119 | 编造 | make up |\n| 120 | 模仿 | imitate |\n| 121 | 字体;宇号 | type |\n| 122 | 墨水 | ink |\n| 123 | 缩短 | shorten |\n| 124 | 加长 | lengthen |\n| 125 | 诚然,肯定地 | certainly |\n| 126 | 描述 | description |\n| 127 | 战斗 | battle |\n| 128 | 混乱的,难懂的 | confused |\n| 129 | 令人不快的 | upsetting |\n| 130 | 解释的;说明的 | explanatory |\n| 131 | 诗行 | line |\n| 132 | 土耳其的 | Turkish |\n| 133 | 保加利亚的 | Bulgarian |\n| 134 | 军官 | officer |\n| 135 | 由……组成 | consist of |\n| 136 | 体重 | weight |\n| 137 | 扑通,比喻掉入水中的声音 | pluff |\n| 138 | 公斤 | kilogram |\n| 139 | 符合,具备 | fulfill |\n| 140 | 要求 | requirement |\n| 141 | 拒绝 | refuse |\n| 142 | 主张 | proposition |\n| 143 | 情感的 | emotional |\n| 144 | 本质地,根本上地 | essentially |\n| 145 | 缺乏目的,漫无目的 | aimlessness |\n| 146 | 是…的典型(事例,特征等) | be typical of |\n| 147 | (尤指二战)战后的 | postwar |\n| 148 | 生产率 | productivity |\n| 149 | 社会和谐 | social harmony |\n| 150 | 羡慕目标,羡慕之处 | envy |\n| 151 | 日益,越来越 | increasingly |\n| 152 | 衰败,下降 | decline |\n| 153 | 工作道德价值观 | work-moral values |\n| 154 | 勤奋努力的 | hardworking |\n| 155 | 首要的 | primary |\n| 156 | 行在 | being |\n| 157 | 满足….的要求 | fulfill |\n| 158 | 达到黄金期 | coming of age |\n| 159 | 进入 | entry |\n| 160 | 男性主导的就业市场 | male-dominated job market |\n| 161 | 限制 | limit |\n| 162 | 机会,机遇 | opportunity |\n| 163 | 怀疑,质疑 | question |\n| 164 | 牺牲 | sacrifice |\n| 165 | 涉及,包含在....中 | involved in |\n| 166 | 严格死板的 | rigid |\n| 167 | 对(完全)满意 | be (fully) satisfied with |\n| 168 | 另外 | in addition |\n| 169 | 相应的人 | counterpart |\n| 170 | 调查 | survey |\n| 171 | 赞扬,赞赏 | praise |\n| 172 | 缺乏目的,漫无目的 | aimlessness |\n| 173 | 是…的典型(事例,特征等) | be typical of |\n| 174 | (尤指二战)战后的 | postwar |\n| 175 | 生产率 | productivity |\n| 176 | 社会和谐 | social harmony |\n| 177 | 羡慕目标,羡慕之处 | envy |\n| 178 | 日益,越来越 | increasingly |\n| 179 | 衰败,下降 | decline |\n| 180 | 工作道德价值观 | work-moral values |\n| 181 | 勤奋努力的 | hardworking |\n| 182 | 首要的 | primary |\n| 183 | 存在 | being |\n| 184 | 满足….的要求 | fulfill |\n| 185 | 达到黄金期 | coming of age |\n| 186 | 进入 | entry |\n| 187 | 男性主导的就业市场 | male-dominated job market |\n| 188 | 限制 | limit |\n| 189 | 机会,机遇 | opportunity |\n| 190 | 怀疑,质疑 | question |\n| 191 | 牺牲 | sacrifice |\n| 192 | 涉及,包含在....中 | involved in |\n| 193 | 严格死板的 | rigid |\n| 194 | 对(完全)满意 | be (fully) satisfied with |\n| 195 | 另外 | in addition |\n| 196 | 相应的人 | counterpart |\n| 197 | 调查 | survey |\n| 198 | 赞扬,赞赏 | praise |\n| 199 | 强调,重点 | emphasis |\n| 200 | 基础 | basics |\n| 201 | 倾向于,往往 | tend to |\n| 202 | 对A的强调超过 B | stress A over B |\n| 203 | 机械的,缺乏独创性的 | mechanical |\n| 204 | 创造力 | creativity |\n| 205 | 自我表现 | self-expression |\n| 206 | 显现,显示出来 | show up |\n| 207 | 品格 | personality |\n| 208 | 勇气 | courage |\n| 209 | 人性 | humanity |\n| 210 | 忽视 | ignore |\n| 211 | 统治的,执政的 | ruling |\n| 212 | 自由民主党 | Liberal Democratic Party |\n| 213 | 教育委员会 | education committee |\n| 214 | 懊恼,挫败感 | frustration |\n| 215 | 辍学,退学 | drop out |\n| 216 | 变得 | run |\n| 217 | 事件 | incident |\n| 218 | 暴力 | violence |\n| 219 | 突袭、袭击 | assault |\n| 220 | 在.当中 | amid |\n| 221 | 强烈抗议 | outcry |\n| 222 | 保守的 | conservative |\n| 223 | 战前的 | prewar |\n| 224 | 道德教育 | moral education |\n| 225 | 当时的 | then |\n| 226 | 教育大臣 | education minister |\n| 227 | 眉毛 | eyebrow |\n| 228 | 占领 | occupation |\n| 229 | 道德,美德 | morality |\n| 230 | 承受,忍受 | endure |\n| 231 | 集中化 | centralization |\n| 232 | 社区 | community |\n| 233 | 放弃,遗弃 | abandon |\n| 234 | 孤立的 | isolated |\n| 235 | 家庭 | household |\n| 236 | 城市的 | urban |\n| 237 | 漫长的 | lengthy |\n| 238 | 上下班路程 | commute |\n| 239 | 不适.不安 | discomfort |\n| 240 | 变得明显,产生能觉察出的效果 | tell |\n| 241 | 离婚率 | divorce rate |\n| 242 | 自杀 | suicide |\n| 243 | 四分之一 | one-quarter |\n| 244 | 雄心 | ambition |\n| 245 | 看待 | regard |\n| 246 | 回报 | reward |\n| 247 | 荣誉;卓越 | distinction |\n| 248 | 控制 | control over |\n| 249 | 命运 | destiny |\n| 250 | 认为 | deem |\n| 251 | 值得... | worthy of |\n| 252 | 牺牲 | sacrifice |\n| 253 | 为了 | on one's behalf |\n| 254 | 活力 | vitality |\n| 255 | 钦佩,赞赏,羨蒸 | admire |\n| 256 | 尤其是,特别是 | not least |\n| 257 | 奇特的,昇常的,古怪的 | odd |\n| 258 | 对...不再抱有希望 | give up on |\n| 259 | 理想 | ideal |\n| 260 | 从...中受益 | benefit from |\n| 261 | (言语或行为中所含的)感情色彩、特征 | note |\n| 262 | 伪善;虛伪 | hypocrisy |\n| 263 | 畜栅 | barn |\n| 264 | 著名的品牌 | name brand |\n| 265 | (一些物品中的)一项 | item |\n| 266 | 非常需要的,受欢迎的 | in demand |\n| 267 | 十年 | decade |\n| 268 | 坦白,承认 | confess |\n| 269 | 唯恐,以免 | lest |\n| 270 | 有进取心的,有事业心的 | pushing |\n| 271 | 渴求获取财物的,贪婪的 | acquisitive |\n| 272 | 庸俗的 | vulgar |\n| 273 | 很 | fine |\n| 274 | 虚伪的 | hypocritical |\n| 275 | 景象 | spectacle |\n| 276 | 供应充足 | in ample supply |\n| 277 | 批评家 | critic |\n| 278 | 物质主义 | materialism |\n| 279 | 出版商 | publisher |\n| 280 | 激进的 | radical |\n| 281 | 三星级的 | three-star |\n| 282 | 新闻记者 | journalist |\n| 283 | 鼓吹 | advocate |\n| 284 | 参与式民主制(整个团体的人参与事情的决定) | participatory democracy |\n| 285 | 招收,使入(学) | enroll |\n| 286 | 独特的 | exceptional |\n| 287 | 明确的表达,确切的阐述 | formulation |\n| 288 | 不惜任何代价 | at all cost |\n| 289 | (看问题或情況的)角度,立场 | angle |\n| 290 | 捍卫者;辩护者 | defender |\n| 291 | 普通的;毫无特色的 | unimpressive |\n| 292 | 极其 | extremely |\n| 293 | 没有吸引力的 | unattractive |\n| 294 | 动力 | impulse |\n| 295 | 结束 | be at an end |\n| 296 | 不再 | no longer |\n| 297 | 萌动 | stirring |\n| 298 | 驱使 | prompting |\n| 299 | 宣称,公开表明 | profess |\n| 300 | 驱动 | drive |\n| 301 | 狡猾的 | sly |\n| 302 | 站立 | stand |\n| 303 | 大多数 | majority |\n| 304 | 关注 | |\n| 305 | 认真的 | earnest |\n| 306 | 上进,取得进展 | get on |","source":"_posts/ky/eng/vocabulary.md","raw":"---\ntitle: 词汇 01\ncategories:\n - 求学之路\nstatus: done\n---\n\n\n| 序号 | 中文 | 英文 |\n| ---- | ----------------------------------------- | ---------------------------- |\n| 1 | 轻松的,不费力的 | effortless |\n| 2 | 极其的,极其大的 | dreadful |\n| 3 | 障碍,阻碍 | handicap |\n| 4 | 适当地 | properly |\n| 5 | 处理,应付 | handle |\n| 6 | 推动力,推动力 | driving force |\n| 7 | 光彩照人的,辉煌的 | glowing |\n| 8 | 竞争者 | competitor |\n| 9 | 无比的,空前的 | unparalleled |\n| 10 | 熟练的 | skilled |\n| 11 | 繁荣的 | prosperous |\n| 12 | 做梦也无法想象 | beyond the dreams of |\n| 13 | 必然的,不可避免的 | inevitable |\n| 14 | 第一位,首位,卓越 | primacy |\n| 15 | 变窄,变小 | narrow |\n| 16 | 后退 | retreat |\n| 17 | 主导地位,优势 | predominance |\n| 18 | 困惑,不知所措 | at a loss |\n| 19 | 逐渐消逝,逐渐消失 | fade |\n| 20 | 竞争力 | competitiveness |\n| 21 | 消费电子产品 | consumer electronics |\n| 22 | 变小,减少 | shrink |\n| 23 | 消失 | vanish |\n| 24 | 纺织品 | textile |\n| 25 | 涌向 | sweep into |\n| 26 | 国内的 | domestic |\n| 27 | 机床制造工业 | machine-tool industry |\n| 28 | 岌岌可危 | on the ropes |\n| 29 | 曾经有那么一段落时间 | for a while |\n| 30 | 半导体 | semiconductor |\n| 31 | 处于……的中心位置 | at the heart of |\n| 32 | 受害者,牺牲品 | casualty |\n| 33 | 引发一场信心危机 | cause a crisis of confidence |\n| 34 | 繁荣,成功 | prosperity |\n| 35 | 认为……理所当然 | take... for granted |\n| 36 | 商业经营方式 | way of doing business |\n| 37 | 不久,很快 | shortly |\n| 38 | 调查,查问 | inquiry |\n| 39 | 衰退 | decline |\n| 40 | 哗众取宠的,耸人听闻的 | sensational |\n| 41 | 警告,告诚 | warning |\n| 42 | 海外 | overseas |\n| 43 | 稳固的,稳定的 | solid |\n| 44 | 挣扎 | struggle |\n| 45 | 把………归因于 | attribute |\n| 46 | 仅仅 | solely |\n| 47 | 贬值的 | devalued |\n| 48 | 周期,循环期 | cycle |\n| 49 | 自我怀疑 | self-doubt |\n| 50 | 屈服,让步 | yield |\n| 51 | 盲目自豪 | blind pride |\n| 52 | 节食,引申为“精简” | go on a diet |\n| 53 | 机敏的,机智的 | quick-witted |\n| 54 | 提高生产力 | improve one's productivity |\n| 55 | 智囊团 | think-tank |\n| 56 | 黄金时代 | a golden age |\n| 57 | 比率 | ratio |\n| 58 | 成年 | maturity |\n| 59 | 普遍特征 | universal |\n| 60 | 死亡率 | mortality |\n| 61 | 过多,过量 | excess |\n| 62 | 关键性的 | crucial |\n| 63 | 使消失 | remove |\n| 64 | 尤其地 | particularly |\n| 65 | 公斤 | kilogram |\n| 66 | 变化:变异 | variation |\n| 67 | 由于;因………而产生 | due to |\n| 68 | 动因 | agent |\n| 69 | 进化 | evolution |\n| 70 | 能生育的 | fertile |\n| 71 | 大致,差不多 | roughly |\n| 72 | 子女 | offspring |\n| 73 | 利用 | take advantage of |\n| 74 | 缩小,减少 | diminish |\n| 75 | 财富,富裕 | wealth |\n| 76 | 贫穷 | poverty |\n| 77 | 部落的 | tribal |\n| 78 | 大规模的 | grand |\n| 79 | 凡;均一 | mediocrity |\n| 80 | 与……相比 | compare to |\n| 81 | 乌托邦,理想的完美境界 | Utopia |\n| 82 | 牵涉,涉及 | involve |\n| 83 | 彻底改变 | transform |\n| 84 | 进化 | evolve |\n| 85 | 无知的 | ignorant |\n| 86 | 未开化的人;野蛮人 | savage |\n| 87 | 完全地;全部地 | wholly |\n| 88 | 理解(力 | comprehension |\n| 89 | 丑陋 | ugliness |\n| 90 | 惊讶的 | amazed |\n| 91 | 后代;后裔 | descendant |\n| 92 | 运动 | movement |\n| 93 | 获得,成为 | attain |\n| 94 | 某种 | certain |\n| 95 | 时尚;风气 | fashion |\n| 96 | 明智的;可取的 | advisable |\n| 97 | 倡导者 | advocate |\n| 98 | 牵强的 | farfetched |\n| 99 | 无理的 | unreasonable |\n| 100 | 观念;信条 | principle |\n| 101 | 情况 | case |\n| 102 | 相当地 | rather |\n| 103 | 分类、归类 | class |\n| 104 | 未来主义者 | Futurist |\n| 105 | 状况 | condition |\n| 106 | 有条件地 | conditionally |\n| 107 | 暴力 | violence |\n| 108 | 因此,结果 | consequently |\n| 109 | 经历 | undergo |\n| 110 | 相应的,相关联的 | corresponding |\n| 111 | 文学 | literature |\n| 112 | 解释,诠释 | interpret |\n| 113 | 倾吐;畅所欲言 | pour out |\n| 114 | 基本的 | essential |\n| 115 | 不受阻碍的,不受限制的 | unhampered |\n| 116 | 句号 | stop |\n| 117 | 修饰性形容词 | qualifying adjective |\n| 118 | 限定动词(如 He opens the door.中的 opens) | finite verb |\n| 119 | 编造 | make up |\n| 120 | 模仿 | imitate |\n| 121 | 字体;宇号 | type |\n| 122 | 墨水 | ink |\n| 123 | 缩短 | shorten |\n| 124 | 加长 | lengthen |\n| 125 | 诚然,肯定地 | certainly |\n| 126 | 描述 | description |\n| 127 | 战斗 | battle |\n| 128 | 混乱的,难懂的 | confused |\n| 129 | 令人不快的 | upsetting |\n| 130 | 解释的;说明的 | explanatory |\n| 131 | 诗行 | line |\n| 132 | 土耳其的 | Turkish |\n| 133 | 保加利亚的 | Bulgarian |\n| 134 | 军官 | officer |\n| 135 | 由……组成 | consist of |\n| 136 | 体重 | weight |\n| 137 | 扑通,比喻掉入水中的声音 | pluff |\n| 138 | 公斤 | kilogram |\n| 139 | 符合,具备 | fulfill |\n| 140 | 要求 | requirement |\n| 141 | 拒绝 | refuse |\n| 142 | 主张 | proposition |\n| 143 | 情感的 | emotional |\n| 144 | 本质地,根本上地 | essentially |\n| 145 | 缺乏目的,漫无目的 | aimlessness |\n| 146 | 是…的典型(事例,特征等) | be typical of |\n| 147 | (尤指二战)战后的 | postwar |\n| 148 | 生产率 | productivity |\n| 149 | 社会和谐 | social harmony |\n| 150 | 羡慕目标,羡慕之处 | envy |\n| 151 | 日益,越来越 | increasingly |\n| 152 | 衰败,下降 | decline |\n| 153 | 工作道德价值观 | work-moral values |\n| 154 | 勤奋努力的 | hardworking |\n| 155 | 首要的 | primary |\n| 156 | 行在 | being |\n| 157 | 满足….的要求 | fulfill |\n| 158 | 达到黄金期 | coming of age |\n| 159 | 进入 | entry |\n| 160 | 男性主导的就业市场 | male-dominated job market |\n| 161 | 限制 | limit |\n| 162 | 机会,机遇 | opportunity |\n| 163 | 怀疑,质疑 | question |\n| 164 | 牺牲 | sacrifice |\n| 165 | 涉及,包含在....中 | involved in |\n| 166 | 严格死板的 | rigid |\n| 167 | 对(完全)满意 | be (fully) satisfied with |\n| 168 | 另外 | in addition |\n| 169 | 相应的人 | counterpart |\n| 170 | 调查 | survey |\n| 171 | 赞扬,赞赏 | praise |\n| 172 | 缺乏目的,漫无目的 | aimlessness |\n| 173 | 是…的典型(事例,特征等) | be typical of |\n| 174 | (尤指二战)战后的 | postwar |\n| 175 | 生产率 | productivity |\n| 176 | 社会和谐 | social harmony |\n| 177 | 羡慕目标,羡慕之处 | envy |\n| 178 | 日益,越来越 | increasingly |\n| 179 | 衰败,下降 | decline |\n| 180 | 工作道德价值观 | work-moral values |\n| 181 | 勤奋努力的 | hardworking |\n| 182 | 首要的 | primary |\n| 183 | 存在 | being |\n| 184 | 满足….的要求 | fulfill |\n| 185 | 达到黄金期 | coming of age |\n| 186 | 进入 | entry |\n| 187 | 男性主导的就业市场 | male-dominated job market |\n| 188 | 限制 | limit |\n| 189 | 机会,机遇 | opportunity |\n| 190 | 怀疑,质疑 | question |\n| 191 | 牺牲 | sacrifice |\n| 192 | 涉及,包含在....中 | involved in |\n| 193 | 严格死板的 | rigid |\n| 194 | 对(完全)满意 | be (fully) satisfied with |\n| 195 | 另外 | in addition |\n| 196 | 相应的人 | counterpart |\n| 197 | 调查 | survey |\n| 198 | 赞扬,赞赏 | praise |\n| 199 | 强调,重点 | emphasis |\n| 200 | 基础 | basics |\n| 201 | 倾向于,往往 | tend to |\n| 202 | 对A的强调超过 B | stress A over B |\n| 203 | 机械的,缺乏独创性的 | mechanical |\n| 204 | 创造力 | creativity |\n| 205 | 自我表现 | self-expression |\n| 206 | 显现,显示出来 | show up |\n| 207 | 品格 | personality |\n| 208 | 勇气 | courage |\n| 209 | 人性 | humanity |\n| 210 | 忽视 | ignore |\n| 211 | 统治的,执政的 | ruling |\n| 212 | 自由民主党 | Liberal Democratic Party |\n| 213 | 教育委员会 | education committee |\n| 214 | 懊恼,挫败感 | frustration |\n| 215 | 辍学,退学 | drop out |\n| 216 | 变得 | run |\n| 217 | 事件 | incident |\n| 218 | 暴力 | violence |\n| 219 | 突袭、袭击 | assault |\n| 220 | 在.当中 | amid |\n| 221 | 强烈抗议 | outcry |\n| 222 | 保守的 | conservative |\n| 223 | 战前的 | prewar |\n| 224 | 道德教育 | moral education |\n| 225 | 当时的 | then |\n| 226 | 教育大臣 | education minister |\n| 227 | 眉毛 | eyebrow |\n| 228 | 占领 | occupation |\n| 229 | 道德,美德 | morality |\n| 230 | 承受,忍受 | endure |\n| 231 | 集中化 | centralization |\n| 232 | 社区 | community |\n| 233 | 放弃,遗弃 | abandon |\n| 234 | 孤立的 | isolated |\n| 235 | 家庭 | household |\n| 236 | 城市的 | urban |\n| 237 | 漫长的 | lengthy |\n| 238 | 上下班路程 | commute |\n| 239 | 不适.不安 | discomfort |\n| 240 | 变得明显,产生能觉察出的效果 | tell |\n| 241 | 离婚率 | divorce rate |\n| 242 | 自杀 | suicide |\n| 243 | 四分之一 | one-quarter |\n| 244 | 雄心 | ambition |\n| 245 | 看待 | regard |\n| 246 | 回报 | reward |\n| 247 | 荣誉;卓越 | distinction |\n| 248 | 控制 | control over |\n| 249 | 命运 | destiny |\n| 250 | 认为 | deem |\n| 251 | 值得... | worthy of |\n| 252 | 牺牲 | sacrifice |\n| 253 | 为了 | on one's behalf |\n| 254 | 活力 | vitality |\n| 255 | 钦佩,赞赏,羨蒸 | admire |\n| 256 | 尤其是,特别是 | not least |\n| 257 | 奇特的,昇常的,古怪的 | odd |\n| 258 | 对...不再抱有希望 | give up on |\n| 259 | 理想 | ideal |\n| 260 | 从...中受益 | benefit from |\n| 261 | (言语或行为中所含的)感情色彩、特征 | note |\n| 262 | 伪善;虛伪 | hypocrisy |\n| 263 | 畜栅 | barn |\n| 264 | 著名的品牌 | name brand |\n| 265 | (一些物品中的)一项 | item |\n| 266 | 非常需要的,受欢迎的 | in demand |\n| 267 | 十年 | decade |\n| 268 | 坦白,承认 | confess |\n| 269 | 唯恐,以免 | lest |\n| 270 | 有进取心的,有事业心的 | pushing |\n| 271 | 渴求获取财物的,贪婪的 | acquisitive |\n| 272 | 庸俗的 | vulgar |\n| 273 | 很 | fine |\n| 274 | 虚伪的 | hypocritical |\n| 275 | 景象 | spectacle |\n| 276 | 供应充足 | in ample supply |\n| 277 | 批评家 | critic |\n| 278 | 物质主义 | materialism |\n| 279 | 出版商 | publisher |\n| 280 | 激进的 | radical |\n| 281 | 三星级的 | three-star |\n| 282 | 新闻记者 | journalist |\n| 283 | 鼓吹 | advocate |\n| 284 | 参与式民主制(整个团体的人参与事情的决定) | participatory democracy |\n| 285 | 招收,使入(学) | enroll |\n| 286 | 独特的 | exceptional |\n| 287 | 明确的表达,确切的阐述 | formulation |\n| 288 | 不惜任何代价 | at all cost |\n| 289 | (看问题或情況的)角度,立场 | angle |\n| 290 | 捍卫者;辩护者 | defender |\n| 291 | 普通的;毫无特色的 | unimpressive |\n| 292 | 极其 | extremely |\n| 293 | 没有吸引力的 | unattractive |\n| 294 | 动力 | impulse |\n| 295 | 结束 | be at an end |\n| 296 | 不再 | no longer |\n| 297 | 萌动 | stirring |\n| 298 | 驱使 | prompting |\n| 299 | 宣称,公开表明 | profess |\n| 300 | 驱动 | drive |\n| 301 | 狡猾的 | sly |\n| 302 | 站立 | stand |\n| 303 | 大多数 | majority |\n| 304 | 关注 | |\n| 305 | 认真的 | earnest |\n| 306 | 上进,取得进展 | get on |","slug":"ky/eng/vocabulary","published":1,"date":"2023-11-21T05:22:17.808Z","updated":"2023-11-21T05:49:07.159Z","comments":1,"layout":"post","photos":[],"link":"","_id":"clp7x1956001lv3z35ie105dj","content":"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
      序号中文英文
      1轻松的,不费力的effortless
      2极其的,极其大的dreadful
      3障碍,阻碍handicap
      4适当地properly
      5处理,应付handle
      6推动力,推动力driving force
      7光彩照人的,辉煌的glowing
      8竞争者competitor
      9无比的,空前的unparalleled
      10熟练的skilled
      11繁荣的prosperous
      12做梦也无法想象beyond the dreams of
      13必然的,不可避免的inevitable
      14第一位,首位,卓越primacy
      15变窄,变小narrow
      16后退retreat
      17主导地位,优势predominance
      18困惑,不知所措at a loss
      19逐渐消逝,逐渐消失fade
      20竞争力competitiveness
      21消费电子产品consumer electronics
      22变小,减少shrink
      23消失vanish
      24纺织品textile
      25涌向sweep into
      26国内的domestic
      27机床制造工业machine-tool industry
      28岌岌可危on the ropes
      29曾经有那么一段落时间for a while
      30半导体semiconductor
      31处于……的中心位置at the heart of
      32受害者,牺牲品casualty
      33引发一场信心危机cause a crisis of confidence
      34繁荣,成功prosperity
      35认为……理所当然take… for granted
      36商业经营方式way of doing business
      37不久,很快shortly
      38调查,查问inquiry
      39衰退decline
      40哗众取宠的,耸人听闻的sensational
      41警告,告诚warning
      42海外overseas
      43稳固的,稳定的solid
      44挣扎struggle
      45把………归因于attribute
      46仅仅solely
      47贬值的devalued
      48周期,循环期cycle
      49自我怀疑self-doubt
      50屈服,让步yield
      51盲目自豪blind pride
      52节食,引申为“精简”go on a diet
      53机敏的,机智的quick-witted
      54提高生产力improve one’s productivity
      55智囊团think-tank
      56黄金时代a golden age
      57比率ratio
      58成年maturity
      59普遍特征universal
      60死亡率mortality
      61过多,过量excess
      62关键性的crucial
      63使消失remove
      64尤其地particularly
      65公斤kilogram
      66变化:变异variation
      67由于;因………而产生due to
      68动因agent
      69进化evolution
      70能生育的fertile
      71大致,差不多roughly
      72子女offspring
      73利用take advantage of
      74缩小,减少diminish
      75财富,富裕wealth
      76贫穷poverty
      77部落的tribal
      78大规模的grand
      79凡;均一mediocrity
      80与……相比compare to
      81乌托邦,理想的完美境界Utopia
      82牵涉,涉及involve
      83彻底改变transform
      84进化evolve
      85无知的ignorant
      86未开化的人;野蛮人savage
      87完全地;全部地wholly
      88理解(力comprehension
      89丑陋ugliness
      90惊讶的amazed
      91后代;后裔descendant
      92运动movement
      93获得,成为attain
      94某种certain
      95时尚;风气fashion
      96明智的;可取的advisable
      97倡导者advocate
      98牵强的farfetched
      99无理的unreasonable
      100观念;信条principle
      101情况case
      102相当地rather
      103分类、归类class
      104未来主义者Futurist
      105状况condition
      106有条件地conditionally
      107暴力violence
      108因此,结果consequently
      109经历undergo
      110相应的,相关联的corresponding
      111文学literature
      112解释,诠释interpret
      113倾吐;畅所欲言pour out
      114基本的essential
      115不受阻碍的,不受限制的unhampered
      116句号stop
      117修饰性形容词qualifying adjective
      118限定动词(如 He opens the door.中的 opens)finite verb
      119编造make up
      120模仿imitate
      121字体;宇号type
      122墨水ink
      123缩短shorten
      124加长lengthen
      125诚然,肯定地certainly
      126描述description
      127战斗battle
      128混乱的,难懂的confused
      129令人不快的upsetting
      130解释的;说明的explanatory
      131诗行line
      132土耳其的Turkish
      133保加利亚的Bulgarian
      134军官officer
      135由……组成consist of
      136体重weight
      137扑通,比喻掉入水中的声音pluff
      138公斤kilogram
      139符合,具备fulfill
      140要求requirement
      141拒绝refuse
      142主张proposition
      143情感的emotional
      144本质地,根本上地essentially
      145缺乏目的,漫无目的aimlessness
      146是…的典型(事例,特征等)be typical of
      147(尤指二战)战后的postwar
      148生产率productivity
      149社会和谐social harmony
      150羡慕目标,羡慕之处envy
      151日益,越来越increasingly
      152衰败,下降decline
      153工作道德价值观work-moral values
      154勤奋努力的hardworking
      155首要的primary
      156行在being
      157满足….的要求fulfill
      158达到黄金期coming of age
      159进入entry
      160男性主导的就业市场male-dominated job market
      161限制limit
      162机会,机遇opportunity
      163怀疑,质疑question
      164牺牲sacrifice
      165涉及,包含在….中involved in
      166严格死板的rigid
      167对(完全)满意be (fully) satisfied with
      168另外in addition
      169相应的人counterpart
      170调查survey
      171赞扬,赞赏praise
      172缺乏目的,漫无目的aimlessness
      173是…的典型(事例,特征等)be typical of
      174(尤指二战)战后的postwar
      175生产率productivity
      176社会和谐social harmony
      177羡慕目标,羡慕之处envy
      178日益,越来越increasingly
      179衰败,下降decline
      180工作道德价值观work-moral values
      181勤奋努力的hardworking
      182首要的primary
      183存在being
      184满足….的要求fulfill
      185达到黄金期coming of age
      186进入entry
      187男性主导的就业市场male-dominated job market
      188限制limit
      189机会,机遇opportunity
      190怀疑,质疑question
      191牺牲sacrifice
      192涉及,包含在….中involved in
      193严格死板的rigid
      194对(完全)满意be (fully) satisfied with
      195另外in addition
      196相应的人counterpart
      197调查survey
      198赞扬,赞赏praise
      199强调,重点emphasis
      200基础basics
      201倾向于,往往tend to
      202对A的强调超过 Bstress A over B
      203机械的,缺乏独创性的mechanical
      204创造力creativity
      205自我表现self-expression
      206显现,显示出来show up
      207品格personality
      208勇气courage
      209人性humanity
      210忽视ignore
      211统治的,执政的ruling
      212自由民主党Liberal Democratic Party
      213教育委员会education committee
      214懊恼,挫败感frustration
      215辍学,退学drop out
      216变得run
      217事件incident
      218暴力violence
      219突袭、袭击assault
      220在.当中amid
      221强烈抗议outcry
      222保守的conservative
      223战前的prewar
      224道德教育moral education
      225当时的then
      226教育大臣education minister
      227眉毛eyebrow
      228占领occupation
      229道德,美德morality
      230承受,忍受endure
      231集中化centralization
      232社区community
      233放弃,遗弃abandon
      234孤立的isolated
      235家庭household
      236城市的urban
      237漫长的lengthy
      238上下班路程commute
      239不适.不安discomfort
      240变得明显,产生能觉察出的效果tell
      241离婚率divorce rate
      242自杀suicide
      243四分之一one-quarter
      244雄心ambition
      245看待regard
      246回报reward
      247荣誉;卓越distinction
      248控制control over
      249命运destiny
      250认为deem
      251值得…worthy of
      252牺牲sacrifice
      253为了on one’s behalf
      254活力vitality
      255钦佩,赞赏,羨蒸admire
      256尤其是,特别是not least
      257奇特的,昇常的,古怪的odd
      258对…不再抱有希望give up on
      259理想ideal
      260从…中受益benefit from
      261(言语或行为中所含的)感情色彩、特征note
      262伪善;虛伪hypocrisy
      263畜栅barn
      264著名的品牌name brand
      265(一些物品中的)一项item
      266非常需要的,受欢迎的in demand
      267十年decade
      268坦白,承认confess
      269唯恐,以免lest
      270有进取心的,有事业心的pushing
      271渴求获取财物的,贪婪的acquisitive
      272庸俗的vulgar
      273fine
      274虚伪的hypocritical
      275景象spectacle
      276供应充足in ample supply
      277批评家critic
      278物质主义materialism
      279出版商publisher
      280激进的radical
      281三星级的three-star
      282新闻记者journalist
      283鼓吹advocate
      284参与式民主制(整个团体的人参与事情的决定)participatory democracy
      285招收,使入(学)enroll
      286独特的exceptional
      287明确的表达,确切的阐述formulation
      288不惜任何代价at all cost
      289(看问题或情況的)角度,立场angle
      290捍卫者;辩护者defender
      291普通的;毫无特色的unimpressive
      292极其extremely
      293没有吸引力的unattractive
      294动力impulse
      295结束be at an end
      296不再no longer
      297萌动stirring
      298驱使prompting
      299宣称,公开表明profess
      300驱动drive
      301狡猾的sly
      302站立stand
      303大多数majority
      304关注
      305认真的earnest
      306上进,取得进展get on
      \n","site":{"data":{}},"excerpt":"","more":"\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n
      序号中文英文
      1轻松的,不费力的effortless
      2极其的,极其大的dreadful
      3障碍,阻碍handicap
      4适当地properly
      5处理,应付handle
      6推动力,推动力driving force
      7光彩照人的,辉煌的glowing
      8竞争者competitor
      9无比的,空前的unparalleled
      10熟练的skilled
      11繁荣的prosperous
      12做梦也无法想象beyond the dreams of
      13必然的,不可避免的inevitable
      14第一位,首位,卓越primacy
      15变窄,变小narrow
      16后退retreat
      17主导地位,优势predominance
      18困惑,不知所措at a loss
      19逐渐消逝,逐渐消失fade
      20竞争力competitiveness
      21消费电子产品consumer electronics
      22变小,减少shrink
      23消失vanish
      24纺织品textile
      25涌向sweep into
      26国内的domestic
      27机床制造工业machine-tool industry
      28岌岌可危on the ropes
      29曾经有那么一段落时间for a while
      30半导体semiconductor
      31处于……的中心位置at the heart of
      32受害者,牺牲品casualty
      33引发一场信心危机cause a crisis of confidence
      34繁荣,成功prosperity
      35认为……理所当然take… for granted
      36商业经营方式way of doing business
      37不久,很快shortly
      38调查,查问inquiry
      39衰退decline
      40哗众取宠的,耸人听闻的sensational
      41警告,告诚warning
      42海外overseas
      43稳固的,稳定的solid
      44挣扎struggle
      45把………归因于attribute
      46仅仅solely
      47贬值的devalued
      48周期,循环期cycle
      49自我怀疑self-doubt
      50屈服,让步yield
      51盲目自豪blind pride
      52节食,引申为“精简”go on a diet
      53机敏的,机智的quick-witted
      54提高生产力improve one’s productivity
      55智囊团think-tank
      56黄金时代a golden age
      57比率ratio
      58成年maturity
      59普遍特征universal
      60死亡率mortality
      61过多,过量excess
      62关键性的crucial
      63使消失remove
      64尤其地particularly
      65公斤kilogram
      66变化:变异variation
      67由于;因………而产生due to
      68动因agent
      69进化evolution
      70能生育的fertile
      71大致,差不多roughly
      72子女offspring
      73利用take advantage of
      74缩小,减少diminish
      75财富,富裕wealth
      76贫穷poverty
      77部落的tribal
      78大规模的grand
      79凡;均一mediocrity
      80与……相比compare to
      81乌托邦,理想的完美境界Utopia
      82牵涉,涉及involve
      83彻底改变transform
      84进化evolve
      85无知的ignorant
      86未开化的人;野蛮人savage
      87完全地;全部地wholly
      88理解(力comprehension
      89丑陋ugliness
      90惊讶的amazed
      91后代;后裔descendant
      92运动movement
      93获得,成为attain
      94某种certain
      95时尚;风气fashion
      96明智的;可取的advisable
      97倡导者advocate
      98牵强的farfetched
      99无理的unreasonable
      100观念;信条principle
      101情况case
      102相当地rather
      103分类、归类class
      104未来主义者Futurist
      105状况condition
      106有条件地conditionally
      107暴力violence
      108因此,结果consequently
      109经历undergo
      110相应的,相关联的corresponding
      111文学literature
      112解释,诠释interpret
      113倾吐;畅所欲言pour out
      114基本的essential
      115不受阻碍的,不受限制的unhampered
      116句号stop
      117修饰性形容词qualifying adjective
      118限定动词(如 He opens the door.中的 opens)finite verb
      119编造make up
      120模仿imitate
      121字体;宇号type
      122墨水ink
      123缩短shorten
      124加长lengthen
      125诚然,肯定地certainly
      126描述description
      127战斗battle
      128混乱的,难懂的confused
      129令人不快的upsetting
      130解释的;说明的explanatory
      131诗行line
      132土耳其的Turkish
      133保加利亚的Bulgarian
      134军官officer
      135由……组成consist of
      136体重weight
      137扑通,比喻掉入水中的声音pluff
      138公斤kilogram
      139符合,具备fulfill
      140要求requirement
      141拒绝refuse
      142主张proposition
      143情感的emotional
      144本质地,根本上地essentially
      145缺乏目的,漫无目的aimlessness
      146是…的典型(事例,特征等)be typical of
      147(尤指二战)战后的postwar
      148生产率productivity
      149社会和谐social harmony
      150羡慕目标,羡慕之处envy
      151日益,越来越increasingly
      152衰败,下降decline
      153工作道德价值观work-moral values
      154勤奋努力的hardworking
      155首要的primary
      156行在being
      157满足….的要求fulfill
      158达到黄金期coming of age
      159进入entry
      160男性主导的就业市场male-dominated job market
      161限制limit
      162机会,机遇opportunity
      163怀疑,质疑question
      164牺牲sacrifice
      165涉及,包含在….中involved in
      166严格死板的rigid
      167对(完全)满意be (fully) satisfied with
      168另外in addition
      169相应的人counterpart
      170调查survey
      171赞扬,赞赏praise
      172缺乏目的,漫无目的aimlessness
      173是…的典型(事例,特征等)be typical of
      174(尤指二战)战后的postwar
      175生产率productivity
      176社会和谐social harmony
      177羡慕目标,羡慕之处envy
      178日益,越来越increasingly
      179衰败,下降decline
      180工作道德价值观work-moral values
      181勤奋努力的hardworking
      182首要的primary
      183存在being
      184满足….的要求fulfill
      185达到黄金期coming of age
      186进入entry
      187男性主导的就业市场male-dominated job market
      188限制limit
      189机会,机遇opportunity
      190怀疑,质疑question
      191牺牲sacrifice
      192涉及,包含在….中involved in
      193严格死板的rigid
      194对(完全)满意be (fully) satisfied with
      195另外in addition
      196相应的人counterpart
      197调查survey
      198赞扬,赞赏praise
      199强调,重点emphasis
      200基础basics
      201倾向于,往往tend to
      202对A的强调超过 Bstress A over B
      203机械的,缺乏独创性的mechanical
      204创造力creativity
      205自我表现self-expression
      206显现,显示出来show up
      207品格personality
      208勇气courage
      209人性humanity
      210忽视ignore
      211统治的,执政的ruling
      212自由民主党Liberal Democratic Party
      213教育委员会education committee
      214懊恼,挫败感frustration
      215辍学,退学drop out
      216变得run
      217事件incident
      218暴力violence
      219突袭、袭击assault
      220在.当中amid
      221强烈抗议outcry
      222保守的conservative
      223战前的prewar
      224道德教育moral education
      225当时的then
      226教育大臣education minister
      227眉毛eyebrow
      228占领occupation
      229道德,美德morality
      230承受,忍受endure
      231集中化centralization
      232社区community
      233放弃,遗弃abandon
      234孤立的isolated
      235家庭household
      236城市的urban
      237漫长的lengthy
      238上下班路程commute
      239不适.不安discomfort
      240变得明显,产生能觉察出的效果tell
      241离婚率divorce rate
      242自杀suicide
      243四分之一one-quarter
      244雄心ambition
      245看待regard
      246回报reward
      247荣誉;卓越distinction
      248控制control over
      249命运destiny
      250认为deem
      251值得…worthy of
      252牺牲sacrifice
      253为了on one’s behalf
      254活力vitality
      255钦佩,赞赏,羨蒸admire
      256尤其是,特别是not least
      257奇特的,昇常的,古怪的odd
      258对…不再抱有希望give up on
      259理想ideal
      260从…中受益benefit from
      261(言语或行为中所含的)感情色彩、特征note
      262伪善;虛伪hypocrisy
      263畜栅barn
      264著名的品牌name brand
      265(一些物品中的)一项item
      266非常需要的,受欢迎的in demand
      267十年decade
      268坦白,承认confess
      269唯恐,以免lest
      270有进取心的,有事业心的pushing
      271渴求获取财物的,贪婪的acquisitive
      272庸俗的vulgar
      273fine
      274虚伪的hypocritical
      275景象spectacle
      276供应充足in ample supply
      277批评家critic
      278物质主义materialism
      279出版商publisher
      280激进的radical
      281三星级的three-star
      282新闻记者journalist
      283鼓吹advocate
      284参与式民主制(整个团体的人参与事情的决定)participatory democracy
      285招收,使入(学)enroll
      286独特的exceptional
      287明确的表达,确切的阐述formulation
      288不惜任何代价at all cost
      289(看问题或情況的)角度,立场angle
      290捍卫者;辩护者defender
      291普通的;毫无特色的unimpressive
      292极其extremely
      293没有吸引力的unattractive
      294动力impulse
      295结束be at an end
      296不再no longer
      297萌动stirring
      298驱使prompting
      299宣称,公开表明profess
      300驱动drive
      301狡猾的sly
      302站立stand
      303大多数majority
      304关注
      305认真的earnest
      306上进,取得进展get on
      \n"}],"PostAsset":[],"PostCategory":[{"post_id":"clp7x194s0001v3z3dnez8x8d","category_id":"clp7x194v0004v3z3d37w4a24","_id":"clp7x194y000bv3z3ewwr79j1"},{"post_id":"clp7x194u0003v3z3cgik2min","category_id":"clp7x194x0008v3z3ajcp3heh","_id":"clp7x194z000gv3z3fq0wd0yp"},{"post_id":"clp7x194w0005v3z30pgk9e5n","category_id":"clp7x194x0008v3z3ajcp3heh","_id":"clp7x1951000jv3z34s0wg0o0"},{"post_id":"clp7x194w0006v3z3akxthpwo","category_id":"clp7x194z000fv3z3apb0f4b9","_id":"clp7x1952000ov3z3hjyc0yz9"},{"post_id":"clp7x194w0007v3z35r7f2ge1","category_id":"clp7x194z000fv3z3apb0f4b9","_id":"clp7x1953000rv3z3112x7gn4"},{"post_id":"clp7x194x0009v3z338b7g5v8","category_id":"clp7x1952000nv3z374avbca4","_id":"clp7x1953000wv3z3b0juek7r"},{"post_id":"clp7x1952000qv3z3b1fm22sp","category_id":"clp7x194v0004v3z3d37w4a24","_id":"clp7x1954000yv3z3b7qd9lt6"},{"post_id":"clp7x1953000tv3z3chgh7c1y","category_id":"clp7x194v0004v3z3d37w4a24","_id":"clp7x19540011v3z3bm2he12y"},{"post_id":"clp7x194x000av3z3bf895az5","category_id":"clp7x1952000nv3z374avbca4","_id":"clp7x19540013v3z3a37v9x81"},{"post_id":"clp7x1953000uv3z39z0vh1ra","category_id":"clp7x194v0004v3z3d37w4a24","_id":"clp7x19540015v3z3e36747o9"},{"post_id":"clp7x1953000xv3z360nugo0n","category_id":"clp7x194v0004v3z3d37w4a24","_id":"clp7x19540016v3z3267t0omh"},{"post_id":"clp7x194y000dv3z35mt50jup","category_id":"clp7x1952000nv3z374avbca4","_id":"clp7x19550018v3z39i8a3qlr"},{"post_id":"clp7x1954000zv3z39rtjhte9","category_id":"clp7x194v0004v3z3d37w4a24","_id":"clp7x19550019v3z3b7xig84i"},{"post_id":"clp7x19540012v3z38lxw0f8a","category_id":"clp7x194v0004v3z3d37w4a24","_id":"clp7x1955001bv3z35eikat0t"},{"post_id":"clp7x194z000ev3z3ef2z8117","category_id":"clp7x1952000nv3z374avbca4","_id":"clp7x1955001cv3z39gz96g93"},{"post_id":"clp7x194z000hv3z3dcryc9ir","category_id":"clp7x1952000nv3z374avbca4","_id":"clp7x1955001ev3z3hma78oba"},{"post_id":"clp7x1951000iv3z3gvgqbt7a","category_id":"clp7x1952000nv3z374avbca4","_id":"clp7x1955001fv3z38bld4x7h"},{"post_id":"clp7x1951000lv3z35336cspf","category_id":"clp7x1952000nv3z374avbca4","_id":"clp7x1955001hv3z3b2st2jbw"},{"post_id":"clp7x1952000mv3z39lqc2ecv","category_id":"clp7x1952000nv3z374avbca4","_id":"clp7x1955001iv3z35kkp490p"},{"post_id":"clp7x1952000pv3z341mb8q1p","category_id":"clp7x1955001gv3z3h637e0fm","_id":"clp7x1955001jv3z31n4d6bar"},{"post_id":"clp7x1956001lv3z35ie105dj","category_id":"clp7x1955001gv3z3h637e0fm","_id":"clp7x1956001nv3z3f81hh5pl"},{"post_id":"clp7x1956001kv3z3a8182w5o","category_id":"clp7x1956001mv3z3caxw71ul","_id":"clp7x1956001ov3z37lhye5t0"}],"PostTag":[],"Tag":[]}}